class PositionalEncoding(nn.Module): "Implement the PE function." def __init__(self, d_model, dropout, max_len=5000): #d_model=512,dropout=0.1, #max_len=5000代表事先准備好長度為5000的序列的位置編碼,其實沒必要, #一般100或者200足夠了。 super(PositionalEncoding, self).__init__() self.dropout = nn.Dropout(p=dropout) # Compute the positional encodings once in log space. pe = torch.zeros(max_len, d_model) #(5000,512)矩陣,保持每個位置的位置編碼,一共5000個位置, #每個位置用一個512維度向量來表示其位置編碼 position = torch.arange(0, max_len).unsqueeze(1) # (5000) -> (5000,1) div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)) # (0,2,…, 4998)一共准備2500個值,供sin, cos調用 pe[:, 0::2] = torch.sin(position * div_term) # 偶數下標的位置 pe[:, 1::2] = torch.cos(position * div_term) # 奇數下標的位置 pe = pe.unsqueeze(0) # (5000, 512) -> (1, 5000, 512) 為batch.size留出位置 self.register_buffer('pe', pe) def forward(self, x): x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False) # 接受1.Embeddings的詞嵌入結果x, #然后把自己的位置編碼pe,封裝成torch的Variable(不需要梯度),加上去。 #例如,假設x是(30,10,512)的一個tensor, #30是batch.size, 10是該batch的序列長度, 512是每個詞的詞嵌入向量; #則該行代碼的第二項是(1, min(10, 5000), 512)=(1,10,512), #在具體相加的時候,會擴展(1,10,512)為(30,10,512), #保證一個batch中的30個序列,都使用(疊加)一樣的位置編碼。 return self.dropout(x) # 增加一次dropout操作 # 注意,位置編碼不會更新,是寫死的,所以這個class里面沒有可訓練的參數。
注意,位置編碼不會更新,是寫死的,所以這個class里面沒有可訓練的參數。
為了計算這個公式,上面的代碼寫的比較風騷,以2i為偶數為例子:
將字編碼和位置嵌入聯合使用:
nn.Sequential(Embeddings(d_model,src_vocab), PositionalEncoding(d_model,dropout)) # 例如,d_model=512, src_vocab=源語言的詞表大小, #dropout=0.1即 dropout rate
摘自:https://zhuanlan.zhihu.com/p/107889011
補充另外一種實現:
import torch import torch.nn as nn import numpy as np class PositionalEncoding(nn.Module): def __init__(self, d_model, max_seq_len): """初始化。 Args: d_model: 一個標量。模型的維度,論文默認是512 max_seq_len: 一個標量。文本序列的最大長度 """ super(PositionalEncoding, self).__init__() # 根據論文給的公式,構造出PE矩陣 position_encoding = np.array([ [pos / np.power(10000, 2.0 * (j // 2) / d_model) for j in range(d_model)] for pos in range(max_seq_len)]) # 偶數列使用sin,奇數列使用cos position_encoding[:, 0::2] = np.sin(position_encoding[:, 0::2]) position_encoding[:, 1::2] = np.cos(position_encoding[:, 1::2]) position_encoding = torch.from_numpy(position_encoding) # 在PE矩陣的第一行,加上一行全是0的向量,代表這`PAD`的positional encoding # 在word embedding中也經常會加上`UNK`,代表位置單詞的word embedding,兩者十分類似 # 那么為什么需要這個額外的PAD的編碼呢?很簡單,因為文本序列的長度不一,我們需要對齊, # 短的序列我們使用0在結尾補全,我們也需要這些補全位置的編碼,也就是`PAD`對應的位置編碼 pad_row = torch.zeros([1, d_model]) position_encoding = torch.cat((pad_row, position_encoding)) print(position_encoding) # 嵌入操作,+1是因為增加了`PAD`這個補全位置的編碼, # Word embedding中如果詞典增加`UNK`,我們也需要+1。看吧,兩者十分相似 self.position_encoding = nn.Embedding(max_seq_len + 1, d_model) self.position_encoding.weight = nn.Parameter(position_encoding,requires_grad=False) def forward(self, input_len): """神經網絡的前向傳播。 Args: input_len: 一個張量,形狀為[BATCH_SIZE, 1]。每一個張量的值代表這一批文本序列中對應的長度。 Returns: 返回這一批序列的位置編碼,進行了對齊。 """ # 找出這一批序列的最大長度 max_len = torch.max(input_len) tensor = torch.cuda.LongTensor if input_len.is_cuda else torch.LongTensor # 對每一個序列的位置進行對齊,在原序列位置的后面補上0 # 這里range從1開始也是因為要避開PAD(0)的位置 input_pos = tensor( [list(range(1, len + 1)) + [0] * (max_len - len) for len in input_len]) return self.position_encoding(input_pos) pe = PositionalEncoding(10, 5) input_lens = torch.tensor([[4],[3],[2]], dtype=torch.long) pe(input_lens)