Transformer源代码解释之PyTorch篇
?PaperWeekly 原創(chuàng) ·?作者?|?Sherlock
學校?|?蘇州科技大學本科生
研究方向?|?自然語言處理
詞嵌入
Transformer 本質上是一種 Encoder,以翻譯任務為例,原始數據集是以兩種語言組成一行的,在應用時,應是 Encoder 輸入源語言序列,Decoder 里面輸入需要被轉換的語言序列(訓練時)。
一個文本常有許多序列組成,常見操作為將序列進行一些預處理(如詞切分等)變成列表,一個序列的列表的元素通常為詞表中不可切分的最小詞,整個文本就是一個大列表,元素為一個一個由序列組成的列表。
如一個序列經過切分后變?yōu)?["am", "##ro", "##zi", "accused", "his", "father"],接下來按照它們在詞表中對應的索引進行轉換,假設結果如 [23, 94, 13, 41, 27, 96]。假如整個文本一共 100 個句子,那么就有 100 個列表為它的元素,因為每個序列的長度不一,需要設定最大長度,這里不妨設為 128,那么將整個文本轉換為數組之后,形狀即為 100 x 128,這就對應著 batch_size 和 seq_length。
輸入之后,緊接著進行詞嵌入處理,詞嵌入就是將每一個詞用預先訓練好的向量進行映射,參數為詞表的大小和被映射的向量的維度,通俗來說就是向量里面有多少個數。注意,第一個參數是詞表的大小,如果你目前有 4 個詞,就填 4,你后面萬一進入與這 4 個詞不同的詞,還需要重新映射,為了統(tǒng)一,一開始的也要重新映射,因此這里填詞表總大小。
假如我們打算映射到 512 維(num_features 或者 embed_dim),那么,整個文本的形狀變?yōu)?100 x 128 x 512。接下來舉個小例子解釋一下:假設我們詞表一共有 10 個詞,文本里有 2 個句子,每個句子有 4 個詞,我們想要把每個詞映射到 8 維的向量。于是 2,4,8 對應于 batch_size, seq_length, embed_dim(如果 batch 在第一維的話)。
另外,一般深度學習任務只改變 num_features,所以講維度一般是針對最后特征所在的維度。
所有需要的包的導入:
import?torch import?torch.nn?as?nn from?torch.nn.parameter?import?Parameter from?torch.nn.init?import?xavier_uniform_ from?torch.nn.init?import?constant_ from?torch.nn.init?import?xavier_normal_ import?torch.nn.functional?as?F from?typing?import?Optional,?Tuple,?Any from?typing?import?List,?Optional,?Tuple import?math import?warnings X?=?torch.zeros((2,4),dtype=torch.long) embed?=?nn.Embedding(10,8) print(embed(X).shape) #?torch.Size([2,?4,?8])位置編碼
詞嵌入之后緊接著就是位置編碼,位置編碼用以區(qū)分不同詞以及同詞不同特征之間的關系。代碼中需要注意:X_ 只是初始化的矩陣,并不是輸入進來的;完成位置編碼之后會加一個 dropout。另外,位置編碼是最后加上去的,因此輸入輸出形狀不變。
Tensor?=?torch.Tensor def?positional_encoding(X,?num_features,?dropout_p=0.1,?max_len=512)?->?Tensor:r'''給輸入加入位置編碼參數:-?num_features:?輸入進來的維度-?dropout_p:?dropout的概率,當其為非零時執(zhí)行dropout-?max_len:?句子的最大長度,默認512形狀:-?輸入:?[batch_size, seq_length, num_features]-?輸出:?[batch_size, seq_length, num_features]例子:>>>?X?=?torch.randn((2,4,10))>>>?X?=?positional_encoding(X,?10)>>>?print(X.shape)>>>?torch.Size([2,?4,?10])'''dropout?=?nn.Dropout(dropout_p)P?=?torch.zeros((1,max_len,num_features))X_?=?torch.arange(max_len,dtype=torch.float32).reshape(-1,1)?/?torch.pow(10000,torch.arange(0,num_features,2,dtype=torch.float32)?/num_features)P[:,:,0::2]?=?torch.sin(X_)P[:,:,1::2]?=?torch.cos(X_)X?=?X?+?P[:,:X.shape[1],:].to(X.device)return?dropout(X)多頭注意力
多頭注意力大概分為三個部分講,分別為參數初始化,q,k,v 從何來,遮擋機制,點積注意力。
3.1 初始化參數
query,key,value 是源語言序列(本文記為 src)乘以對應的矩陣得到的,那么,那些矩陣從何而來(注意,因為大部分代碼都是從源碼中抽離出來的,因而常帶有 self 等,最后會呈現組成好的,而行文過程中不會將整個結構呈現出來):
if?self._qkv_same_embed_dim?is?False:#?初始化前后形狀維持不變#?(seq_length?x?embed_dim)?x?(embed_dim?x?embed_dim)?==>?(seq_length?x?embed_dim)self.q_proj_weight?=?Parameter(torch.empty((embed_dim,?embed_dim)))self.k_proj_weight?=?Parameter(torch.empty((embed_dim,?self.kdim)))self.v_proj_weight?=?Parameter(torch.empty((embed_dim,?self.vdim)))self.register_parameter('in_proj_weight',?None) else:self.in_proj_weight?=?Parameter(torch.empty((3?*?embed_dim,?embed_dim)))self.register_parameter('q_proj_weight',?None)self.register_parameter('k_proj_weight',?None)self.register_parameter('v_proj_weight',?None)if?bias:self.in_proj_bias?=?Parameter(torch.empty(3?*?embed_dim)) else:self.register_parameter('in_proj_bias',?None) #?后期會將所有頭的注意力拼接在一起然后乘上權重矩陣輸出 #?out_proj是為了后期準備的 self.out_proj?=?nn.Linear(embed_dim,?embed_dim,?bias=bias) self._reset_parameters()torch.empty 是按照所給的形狀形成對應的 tensor,特點是填充的值還未初始化,類比 torch.randn(標準正態(tài)分布),這就是一種初始化的方式。在 PyTorch 中,變量類型是 tensor 的話是無法修改值的,而 Parameter() 函數可以看作為一種類型轉變函數,將不可改值的 tensor 轉換為可訓練可修改的模型參數,即與 model.parameters 綁定在一起,register_parameter 的意思是是否將這個參數放到 model.parameters,None 的意思是沒有這個參數。
這里有個 if 判斷,用以判斷 q,k,v 的最后一維是否一致,若一致,則一個大的權重矩陣全部乘然后分割出來,若不是,則各初始化各的,其實初始化是不會改變原來的形狀的(如 ,見注釋)。
可以發(fā)現最后有一個 _reset_parameters() 函數,這個是用來初始化參數數值的。xavier_uniform 意思是從連續(xù)型均勻分布里面隨機取樣出值來作為初始化的值,xavier_normal_ 取樣的分布是正態(tài)分布。正因為初始化值在訓練神經網絡的時候很重要,所以才需要這兩個函數。
constant_ 意思是用所給值來填充輸入的向量。
另外,在 PyTorch 的源碼里,似乎 projection 代表是一種線性變換的意思,in_proj_bias 的意思就是一開始的線性變換的偏置。
def?_reset_parameters(self):if?self._qkv_same_embed_dim:xavier_uniform_(self.in_proj_weight)else:xavier_uniform_(self.q_proj_weight)xavier_uniform_(self.k_proj_weight)xavier_uniform_(self.v_proj_weight)if?self.in_proj_bias?is?not?None:constant_(self.in_proj_bias,?0.)constant_(self.out_proj.bias,?0.)以上便是參數初始化過程,接下來是進行 query, key 和 value 的賦值。
3.2 q,k,v從何來?
對于nn.functional.linear函數,其實就是一個線性變換,與nn.Linear不同的是,前者可以提供權重矩陣和偏置,執(zhí)行
而后者是可以自由決定輸出的維度,因為 linear 函數多層調用且無太大意義,這里省略。
def?_in_projection_packed(q:?Tensor,k:?Tensor,v:?Tensor,w:?Tensor,b:?Optional[Tensor]?=?None, )?->?List[Tensor]:r"""用一個大的權重參數矩陣進行線性變換參數:q, k, v:?對自注意來說,三者都是src;對于seq2seq模型,k和v是一致的tensor。但它們的最后一維(num_features或者叫做embed_dim)都必須保持一致。w:?用以線性變換的大矩陣,按照q,k,v的順序壓在一個tensor里面。b:?用以線性變換的偏置,按照q,k,v的順序壓在一個tensor里面。形狀:輸入:- q: shape:`(..., E)`,E是詞嵌入的維度(下面出現的E均為此意)。-?k:?shape:`(...,?E)`-?v:?shape:`(...,?E)`-?w:?shape:`(E?*?3,?E)`-?b:?shape:`E?*?3`?輸出:-?輸出列表?:`[q', k', v']`,q,k,v經過線性變換前后的形狀都一致。"""E?=?q.size(-1)#?若為自注意,則q?=?k?=?v?=?src,因此它們的引用變量都是src#?即k?is?v和q?is?k結果均為True#?若為seq2seq,k?=?v,因而k?is?v的結果是Trueif?k?is?v:if?q?is?k:return?F.linear(q,?w,?b).chunk(3,?dim=-1)else:#?seq2seq模型w_q,?w_kv?=?w.split([E,?E?*?2])if?b?is?None:b_q?=?b_kv?=?Noneelse:b_q,?b_kv?=?b.split([E,?E?*?2])return?(F.linear(q,?w_q,?b_q),)?+?F.linear(k,?w_kv,?b_kv).chunk(2,?dim=-1)else:w_q,?w_k,?w_v?=?w.chunk(3)if?b?is?None:b_q?=?b_k?=?b_v?=?Noneelse:b_q,?b_k,?b_v?=?b.chunk(3)return?F.linear(q,?w_q,?b_q),?F.linear(k,?w_k,?b_k),?F.linear(v,?w_v,?b_v)q,?k,?v?=?_in_projection_packed(query,?key,?value,?in_proj_weight,?in_proj_bias)3.3 遮擋機制
對于 attn_mask 來說,若為 2D,形狀如(L, S),L 和 S 分別代表著目標語言和源語言序列長度,若為 3D,形狀如(N * num_heads, L, S),N 代表著 batch_size,num_heads 代表注意力頭的數目。若為 ByteTensor,非 0 的位置會被忽略不做注意力;若為 BoolTensor,True 對應的位置會被忽略;若為數值,則會直接加到 attn_weights。
因為在 decoder 解碼的時候,只能看該位置和它之前的,如果看后面就犯規(guī)了,所以需要 attn_mask 遮擋住。
下面函數直接復制 PyTorch 的,意思是確保不同維度的 mask 形狀正確以及不同類型的轉換。
if?attn_mask?is?not?None:if?attn_mask.dtype?==?torch.uint8:warnings.warn("Byte?tensor?for?attn_mask?in?nn.MultiheadAttention?is?deprecated.?Use?bool?tensor?instead.")attn_mask?=?attn_mask.to(torch.bool)else:assert?attn_mask.is_floating_point()?or?attn_mask.dtype?==?torch.bool,?\f"Only?float,?byte,?and?bool?types?are?supported?for?attn_mask,?not?{attn_mask.dtype}"#?對不同維度的形狀判定if?attn_mask.dim()?==?2:correct_2d_size?=?(tgt_len,?src_len)if?attn_mask.shape?!=?correct_2d_size:raise?RuntimeError(f"The?shape?of?the?2D?attn_mask?is?{attn_mask.shape},?but?should?be?{correct_2d_size}.")attn_mask?=?attn_mask.unsqueeze(0)elif?attn_mask.dim()?==?3:correct_3d_size?=?(bsz?*?num_heads,?tgt_len,?src_len)if?attn_mask.shape?!=?correct_3d_size:raise?RuntimeError(f"The?shape?of?the?3D?attn_mask?is?{attn_mask.shape},?but?should?be?{correct_3d_size}.")else:raise?RuntimeError(f"attn_mask's?dimension?{attn_mask.dim()}?is?not?supported")與attn_mask不同的是,key_padding_mask是用來遮擋住 key 里面的值,詳細來說應該是<PAD>,被忽略的情況與 attn_mask 一致。
#?將key_padding_mask值改為布爾值 if?key_padding_mask?is?not?None?and?key_padding_mask.dtype?==?torch.uint8:warnings.warn("Byte?tensor?for?key_padding_mask?in?nn.MultiheadAttention?is?deprecated.?Use?bool?tensor?instead.")key_padding_mask?=?key_padding_mask.to(torch.bool)先介紹兩個小函數,logical_or,輸入兩個 tensor,并對這兩個 tensor 里的值做邏輯或運算,只有當兩個值均為 0 的時候才為False,其他時候均為True,另一個是masked_fill,輸入是一個 mask,和用以填充的值。mask 由 1,0 組成,0 的位置值維持不變,1 的位置用新值填充。
a?=?torch.tensor([0,1,10,0],dtype=torch.int8) b?=?torch.tensor([4,0,1,0],dtype=torch.int8) print(torch.logical_or(a,b)) #?tensor([?True,??True,??True,?False])r?=?torch.tensor([[0,0,0,0],[0,0,0,0]]) mask?=?torch.tensor([[1,1,1,1],[0,0,0,0]]) print(r.masked_fill(mask,1)) #?tensor([[1,?1,?1,?1], #?????????[0,?0,?0,?0]])其實 attn_mask 和 key_padding_mask 有些時候對象是一致的,所以有時候可以合起來看。-inf做 softmax 之后值為 0,即被忽略。
3.4 點積注意力
from?typing?import?Optional,?Tuple,?Any def?scaled_dot_product_attention(q:?Tensor,k:?Tensor,v:?Tensor,attn_mask:?Optional[Tensor]?=?None,dropout_p:?float?=?0.0, )?->?Tuple[Tensor,?Tensor]:r'''在query,?key,?value上計算點積注意力,若有注意力遮蓋則使用,并且應用一個概率為dropout_p的dropout參數:-?q:?shape:`(B,?Nt,?E)`?B代表batch?size,?Nt是目標語言序列長度,E是嵌入后的特征維度-?key:?shape:`(B,?Ns,?E)`?Ns是源語言序列長度-?value:?shape:`(B,?Ns,?E)`與key形狀一樣-?attn_mask:?要么是3D的tensor,形狀為:`(B,?Nt,?Ns)`或者2D的tensor,形狀如:`(Nt,?Ns)`-?Output:?attention?values:?shape:`(B,?Nt,?E)`,與q的形狀一致;attention?weights:?shape:`(B,?Nt,?Ns)`例子:>>>?q?=?torch.randn((2,3,6))>>>?k?=?torch.randn((2,4,6))>>>?v?=?torch.randn((2,4,6))>>>?out?=?scaled_dot_product_attention(q,?k,?v)>>>?out[0].shape,?out[1].shape>>>?torch.Size([2,?3,?6])?torch.Size([2,?3,?4])'''B,?Nt,?E?=?q.shapeq?=?q?/?math.sqrt(E)#?(B,?Nt,?E)?x?(B,?E,?Ns)?->?(B,?Nt,?Ns)attn?=?torch.bmm(q,?k.transpose(-2,-1))if?attn_mask?is?not?None:attn?+=?attn_mask?#?attn意味著目標序列的每個詞對源語言序列做注意力attn?=?F.softmax(attn,?dim=-1)if?dropout_p:attn?=?F.dropout(attn,?p=dropout_p)#?(B,?Nt,?Ns)?x?(B,?Ns,?E)?->?(B,?Nt,?E)output?=?torch.bmm(attn,?v)return?output,?attn?接下來將三個部分連起來:
def?multi_head_attention_forward(query:?Tensor,key:?Tensor,value:?Tensor,num_heads:?int,in_proj_weight:?Tensor,in_proj_bias:?Optional[Tensor],dropout_p:?float,out_proj_weight:?Tensor,out_proj_bias:?Optional[Tensor],training:?bool?=?True,key_padding_mask:?Optional[Tensor]?=?None,need_weights:?bool?=?True,attn_mask:?Optional[Tensor]?=?None,use_seperate_proj_weight?=?None,q_proj_weight:?Optional[Tensor]?=?None,k_proj_weight:?Optional[Tensor]?=?None,v_proj_weight:?Optional[Tensor]?=?None, )?->?Tuple[Tensor,?Optional[Tensor]]:r'''形狀:輸入:- query:`(L, N, E)`-?key:?`(S,?N,?E)`-?value:?`(S,?N,?E)`-?key_padding_mask:?`(N,?S)`-?attn_mask:?`(L,?S)`?or?`(N?*?num_heads,?L,?S)`輸出:-?attn_output:`(L,?N,?E)`-?attn_output_weights:`(N,?L,?S)`'''tgt_len,?bsz,?embed_dim?=?query.shapesrc_len,?_,?_?=?key.shapehead_dim?=?embed_dim?//?num_headsq,?k,?v?=?_in_projection_packed(query,?key,?value,?in_proj_weight,?in_proj_bias)if?attn_mask?is?not?None:if?attn_mask.dtype?==?torch.uint8:warnings.warn("Byte?tensor?for?attn_mask?in?nn.MultiheadAttention?is?deprecated.?Use?bool?tensor?instead.")attn_mask?=?attn_mask.to(torch.bool)else:assert?attn_mask.is_floating_point()?or?attn_mask.dtype?==?torch.bool,?\f"Only?float,?byte,?and?bool?types?are?supported?for?attn_mask,?not?{attn_mask.dtype}"if?attn_mask.dim()?==?2:correct_2d_size?=?(tgt_len,?src_len)if?attn_mask.shape?!=?correct_2d_size:raise?RuntimeError(f"The?shape?of?the?2D?attn_mask?is?{attn_mask.shape},?but?should?be?{correct_2d_size}.")attn_mask?=?attn_mask.unsqueeze(0)elif?attn_mask.dim()?==?3:correct_3d_size?=?(bsz?*?num_heads,?tgt_len,?src_len)if?attn_mask.shape?!=?correct_3d_size:raise?RuntimeError(f"The?shape?of?the?3D?attn_mask?is?{attn_mask.shape},?but?should?be?{correct_3d_size}.")else:raise?RuntimeError(f"attn_mask's?dimension?{attn_mask.dim()}?is?not?supported")if?key_padding_mask?is?not?None?and?key_padding_mask.dtype?==?torch.uint8:warnings.warn("Byte?tensor?for?key_padding_mask?in?nn.MultiheadAttention?is?deprecated.?Use?bool?tensor?instead.")key_padding_mask?=?key_padding_mask.to(torch.bool)#?reshape?q,k,v將Batch放在第一維以適合點積注意力#?同時為多頭機制,將不同的頭拼在一起組成一層q?=?q.contiguous().view(tgt_len,?bsz?*?num_heads,?head_dim).transpose(0,?1)k?=?k.contiguous().view(-1,?bsz?*?num_heads,?head_dim).transpose(0,?1)v?=?v.contiguous().view(-1,?bsz?*?num_heads,?head_dim).transpose(0,?1)if?key_padding_mask?is?not?None:assert?key_padding_mask.shape?==?(bsz,?src_len),?\f"expecting?key_padding_mask?shape?of?{(bsz,?src_len)},?but?got?{key_padding_mask.shape}"key_padding_mask?=?key_padding_mask.view(bsz,?1,?1,?src_len).???\expand(-1,?num_heads,?-1,?-1).reshape(bsz?*?num_heads,?1,?src_len)if?attn_mask?is?None:attn_mask?=?key_padding_maskelif?attn_mask.dtype?==?torch.bool:attn_mask?=?attn_mask.logical_or(key_padding_mask)else:attn_mask?=?attn_mask.masked_fill(key_padding_mask,?float("-inf"))#?若attn_mask值是布爾值,則將mask轉換為floatif?attn_mask?is?not?None?and?attn_mask.dtype?==?torch.bool:new_attn_mask?=?torch.zeros_like(attn_mask,?dtype=torch.float)new_attn_mask.masked_fill_(attn_mask,?float("-inf"))attn_mask?=?new_attn_mask#?若training為True時才應用dropoutif?not?training:dropout_p?=?0.0attn_output,?attn_output_weights?=?_scaled_dot_product_attention(q,?k,?v,?attn_mask,?dropout_p)attn_output?=?attn_output.transpose(0,?1).contiguous().view(tgt_len,?bsz,?embed_dim)attn_output?=?linear(attn_output,?out_proj_weight,?out_proj_bias)if?need_weights:#?average?attention?weights?over?headsattn_output_weights?=?attn_output_weights.view(bsz,?num_heads,?tgt_len,?src_len)return?attn_output,?attn_output_weights.sum(dim=1)?/?num_headselse:return?attn_output,?None接下來構建 MultiheadAttention 類:
class?MultiheadAttention(nn.Module):r'''參數:embed_dim:?詞嵌入的維度num_heads:?平行頭的數量batch_first:?若`True`,則為(batch,?seq,?feture),若為`False`,則為(seq,?batch,?feature)例子:>>>?multihead_attn?=?MultiheadAttention(embed_dim,?num_heads)>>>?attn_output,?attn_output_weights?=?multihead_attn(query,?key,?value)'''def?__init__(self,?embed_dim,?num_heads,?dropout=0.,?bias=True,kdim=None,?vdim=None,?batch_first=False)?->?None:factory_kwargs?=?{'device':?device,?'dtype':?dtype}super(MultiheadAttention,?self).__init__()self.embed_dim?=?embed_dimself.kdim?=?kdim?if?kdim?is?not?None?else?embed_dimself.vdim?=?vdim?if?vdim?is?not?None?else?embed_dimself._qkv_same_embed_dim?=?self.kdim?==?embed_dim?and?self.vdim?==?embed_dimself.num_heads?=?num_headsself.dropout?=?dropoutself.batch_first?=?batch_firstself.head_dim?=?embed_dim?//?num_headsassert?self.head_dim?*?num_heads?==?self.embed_dim,?"embed_dim?must?be?divisible?by?num_heads"if?self._qkv_same_embed_dim?is?False:self.q_proj_weight?=?Parameter(torch.empty((embed_dim,?embed_dim)))self.k_proj_weight?=?Parameter(torch.empty((embed_dim,?self.kdim)))self.v_proj_weight?=?Parameter(torch.empty((embed_dim,?self.vdim)))self.register_parameter('in_proj_weight',?None)else:self.in_proj_weight?=?Parameter(torch.empty((3?*?embed_dim,?embed_dim)))self.register_parameter('q_proj_weight',?None)self.register_parameter('k_proj_weight',?None)self.register_parameter('v_proj_weight',?None)if?bias:self.in_proj_bias?=?Parameter(torch.empty(3?*?embed_dim))else:self.register_parameter('in_proj_bias',?None)self.out_proj?=?nn.Linear(embed_dim,?embed_dim,?bias=bias)self._reset_parameters()def?_reset_parameters(self):if?self._qkv_same_embed_dim:xavier_uniform_(self.in_proj_weight)else:xavier_uniform_(self.q_proj_weight)xavier_uniform_(self.k_proj_weight)xavier_uniform_(self.v_proj_weight)if?self.in_proj_bias?is?not?None:constant_(self.in_proj_bias,?0.)constant_(self.out_proj.bias,?0.)if?self.bias_k?is?not?None:xavier_normal_(self.bias_k)if?self.bias_v?is?not?None:xavier_normal_(self.bias_v)def?forward(self,?query:?Tensor,?key:?Tensor,?value:?Tensor,?key_padding_mask:?Optional[Tensor]?=?None,need_weights:?bool?=?True,?attn_mask:?Optional[Tensor]?=?None)?->?Tuple[Tensor,?Optional[Tensor]]:if?self.batch_first:query,?key,?value?=?[x.transpose(1,?0)?for?x?in?(query,?key,?value)]if?not?self._qkv_same_embed_dim:attn_output,?attn_output_weights?=?F.multi_head_attention_forward(query,?key,?value,?self.num_heads,self.in_proj_weight,?self.in_proj_bias,self.dropout,?self.out_proj.weight,?self.out_proj.bias,training=self.training,key_padding_mask=key_padding_mask,?need_weights=need_weights,attn_mask=attn_mask,?use_separate_proj_weight=True,q_proj_weight=self.q_proj_weight,?k_proj_weight=self.k_proj_weight,v_proj_weight=self.v_proj_weight)else:attn_output,?attn_output_weights?=?F.multi_head_attention_forward(query,?key,?value,?self.num_heads,self.in_proj_weight,?self.in_proj_bias,self.dropout,?self.out_proj.weight,?self.out_proj.bias,training=self.training,key_padding_mask=key_padding_mask,?need_weights=need_weights,attn_mask=attn_mask)if?self.batch_first:return?attn_output.transpose(1,?0),?attn_output_weightselse:return?attn_output,?attn_output_weights接下來可以實踐一下,并且把位置編碼加起來,可以發(fā)現加入位置編碼和進行多頭注意力的前后形狀都是不會變的。
#?因為batch_first為False,所以src的shape:`(seq, batch, embed_dim)` src?=?torch.randn((2,4,100)) src?=?positional_encoding(src,100,0.1) print(src.shape) multihead_attn?=?MultiheadAttention(100,?4,?0.1) attn_output,?attn_output_weights?=?multihead_attn(src,src,src) print(attn_output.shape,?attn_output_weights.shape)#?torch.Size([2,?4,?100]) #?torch.Size([2,?4,?100])?torch.Size([4,?2,?2])搭建Transformer
4.1 Encoder Layer
class?TransformerEncoderLayer(nn.Module):r'''參數:d_model:?詞嵌入的維度(必備)nhead:?多頭注意力中平行頭的數目(必備)dim_feedforward:?全連接層的神經元的數目,又稱經過此層輸入的維度(Default?=?2048)dropout:?dropout的概率(Default?=?0.1)activation:?兩個線性層中間的激活函數,默認relu或gelulay_norm_eps:?layer?normalization中的微小量,防止分母為0(Default?=?1e-5)batch_first:?若`True`,則為(batch, seq, feture),若為`False`,則為(seq, batch, feature)(Default:False)例子:>>>?encoder_layer?=?TransformerEncoderLayer(d_model=512,?nhead=8)>>>?src?=?torch.randn((32,?10,?512))>>>?out?=?encoder_layer(src)'''def?__init__(self,?d_model,?nhead,?dim_feedforward=2048,?dropout=0.1,?activation=F.relu,layer_norm_eps=1e-5,?batch_first=False)?->?None:super(TransformerEncoderLayer,?self).__init__()self.self_attn?=?MultiheadAttention(d_model,?nhead,?dropout=dropout,?batch_first=batch_first)self.linear1?=?nn.Linear(d_model,?dim_feedforward)self.dropout?=?nn.Dropout(dropout)self.linear2?=?nn.Linear(dim_feedforward,?d_model)self.norm1?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.norm2?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.dropout1?=?nn.Dropout(dropout)self.dropout2?=?nn.Dropout(dropout)self.activation?=?activation????????def?forward(self,?src:?Tensor,?src_mask:?Optional[Tensor]?=?None,?src_key_padding_mask:?Optional[Tensor]?=?None)?->?Tensor:src?=?positional_encoding(src,?src.shape[-1])src2?=?self.self_attn(src,?src,?src,?attn_mask=src_mask,?key_padding_mask=src_key_padding_mask)[0]src?=?src?+?self.dropout1(src2)src?=?self.norm1(src)src2?=?self.linear2(self.dropout(self.activation(self.linear1(src))))src?=?src?+?self.dropout(src2)src?=?self.norm2(src)return?src用小例子看一下
encoder_layer?=?TransformerEncoderLayer(d_model=512,?nhead=8) src?=?torch.randn((32,?10,?512)) out?=?encoder_layer(src) print(out.shape) #?torch.Size([32,?10,?512])4.2 Encoder
class?TransformerEncoder(nn.Module):r'''參數:encoder_layer(必備)num_layers:encoder_layer的層數(必備)norm:?歸一化的選擇(可選)例子:>>>?encoder_layer?=?TransformerEncoderLayer(d_model=512,?nhead=8)>>>?transformer_encoder?=?TransformerEncoder(encoder_layer,?num_layers=6)>>>?src?=?torch.randn((10,?32,?512))>>>?out?=?transformer_encoder(src)'''def?__init__(self,?encoder_layer,?num_layers,?norm=None):super(TransformerEncoder,?self).__init__()self.layer?=?encoder_layerself.num_layers?=?num_layersself.norm?=?normdef?forward(self,?src:?Tensor,?mask:?Optional[Tensor]?=?None,?src_key_padding_mask:?Optional[Tensor]?=?None)?->?Tensor:output?=?positional_encoding(src,?src.shape[-1])for?_?in?range(self.num_layers):output?=?self.layer(output,?src_mask=mask,?src_key_padding_mask=src_key_padding_mask)if?self.norm?is?not?None:output?=?self.norm(output)return?output用小例子看一下
encoder_layer?=?TransformerEncoderLayer(d_model=512,?nhead=8) transformer_encoder?=?TransformerEncoder(encoder_layer,?num_layers=6) src?=?torch.randn((10,?32,?512)) out?=?transformer_encoder(src) print(out.shape) #?torch.Size([10,?32,?512])4.3 Decoder Layer:
class?TransformerDecoderLayer(nn.Module):r'''參數:d_model:?詞嵌入的維度(必備)nhead:?多頭注意力中平行頭的數目(必備)dim_feedforward:?全連接層的神經元的數目,又稱經過此層輸入的維度(Default?=?2048)dropout:?dropout的概率(Default?=?0.1)activation:?兩個線性層中間的激活函數,默認relu或gelulay_norm_eps:?layer?normalization中的微小量,防止分母為0(Default?=?1e-5)batch_first:?若`True`,則為(batch, seq, feture),若為`False`,則為(seq, batch, feature)(Default:False)例子:>>>?decoder_layer?=?TransformerDecoderLayer(d_model=512,?nhead=8)>>>?memory?=?torch.randn((10,?32,?512))>>>?tgt?=?torch.randn((20,?32,?512))>>>?out?=?decoder_layer(tgt,?memory)'''def?__init__(self,?d_model,?nhead,?dim_feedforward=2048,?dropout=0.1,?activation=F.relu,layer_norm_eps=1e-5,?batch_first=False)?->?None:super(TransformerDecoderLayer,?self).__init__()self.self_attn?=?MultiheadAttention(d_model,?nhead,?dropout=dropout,?batch_first=batch_first)self.multihead_attn?=?MultiheadAttention(d_model,?nhead,?dropout=dropout,?batch_first=batch_first)self.linear1?=?nn.Linear(d_model,?dim_feedforward)self.dropout?=?nn.Dropout(dropout)self.linear2?=?nn.Linear(dim_feedforward,?d_model)self.norm1?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.norm2?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.norm3?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.dropout1?=?nn.Dropout(dropout)self.dropout2?=?nn.Dropout(dropout)self.dropout3?=?nn.Dropout(dropout)self.activation?=?activationdef?forward(self,?tgt:?Tensor,?memory:?Tensor,?tgt_mask:?Optional[Tensor]?=?None,?memory_mask:?Optional[Tensor]?=?None,tgt_key_padding_mask:?Optional[Tensor]?=?None,?memory_key_padding_mask:?Optional[Tensor]?=?None)?->?Tensor:r'''參數:tgt:?目標語言序列(必備)memory:?從最后一個encoder_layer跑出的句子(必備)tgt_mask:?目標語言序列的mask(可選)memory_mask(可選)tgt_key_padding_mask(可選)memory_key_padding_mask(可選)'''tgt2?=?self.self_attn(tgt,?tgt,?tgt,?attn_mask=tgt_mask,key_padding_mask=tgt_key_padding_mask)[0]tgt?=?tgt?+?self.dropout1(tgt2)tgt?=?self.norm1(tgt)tgt2?=?self.multihead_attn(tgt,?memory,?memory,?attn_mask=memory_mask,key_padding_mask=memory_key_padding_mask)[0]tgt?=?tgt?+?self.dropout2(tgt2)tgt?=?self.norm2(tgt)tgt2?=?self.linear2(self.dropout(self.activation(self.linear1(tgt))))tgt?=?tgt?+?self.dropout3(tgt2)tgt?=?self.norm3(tgt)return?tgt用小例子看一下:
decoder_layer?=?nn.TransformerDecoderLayer(d_model=512,?nhead=8) memory?=?torch.randn((10,?32,?512)) tgt?=?torch.randn((20,?32,?512)) out?=?decoder_layer(tgt,?memory) print(out.shape) #?torch.Size([20,?32,?512])4.4 Decoder
class?TransformerDecoder(nn.Module):r'''參數:decoder_layer(必備)num_layers:?decoder_layer的層數(必備)norm:?歸一化選擇例子:>>>?decoder_layer?=TransformerDecoderLayer(d_model=512,?nhead=8)>>>?transformer_decoder?=?TransformerDecoder(decoder_layer,?num_layers=6)>>>?memory?=?torch.rand(10,?32,?512)>>>?tgt?=?torch.rand(20,?32,?512)>>>?out?=?transformer_decoder(tgt,?memory)'''def?__init__(self,?decoder_layer,?num_layers,?norm=None):super(TransformerDecoder,?self).__init__()self.layer?=?decoder_layerself.num_layers?=?num_layersself.norm?=?normdef?forward(self,?tgt:?Tensor,?memory:?Tensor,?tgt_mask:?Optional[Tensor]?=?None,memory_mask:?Optional[Tensor]?=?None,?tgt_key_padding_mask:?Optional[Tensor]?=?None,memory_key_padding_mask:?Optional[Tensor]?=?None)?->?Tensor:output?=?tgtfor?_?in?range(self.num_layers):output?=?self.layer(output,?memory,?tgt_mask=tgt_mask,memory_mask=memory_mask,tgt_key_padding_mask=tgt_key_padding_mask,memory_key_padding_mask=memory_key_padding_mask)if?self.norm?is?not?None:output?=?self.norm(output)return?output用小例子看一下:
decoder_layer?=TransformerDecoderLayer(d_model=512,?nhead=8) transformer_decoder?=?TransformerDecoder(decoder_layer,?num_layers=6) memory?=?torch.rand(10,?32,?512) tgt?=?torch.rand(20,?32,?512) out?=?transformer_decoder(tgt,?memory) print(out.shape) #?torch.Size([20,?32,?512])總結一下,其實經過位置編碼,多頭注意力,Encoder Layer 和 Decoder Layer 形狀不會變的,而 Encoder 和 Decoder 分別與 src 和 tgt 形狀一致。
4.5 Transformer
class?Transformer(nn.Module):r'''參數:d_model:?詞嵌入的維度(必備)(Default=512)nhead:?多頭注意力中平行頭的數目(必備)(Default=8)num_encoder_layers:編碼層層數(Default=8)num_decoder_layers:解碼層層數(Default=8)dim_feedforward:?全連接層的神經元的數目,又稱經過此層輸入的維度(Default?=?2048)dropout:?dropout的概率(Default?=?0.1)activation:?兩個線性層中間的激活函數,默認relu或gelucustom_encoder:?自定義encoder(Default=None)custom_decoder:?自定義decoder(Default=None)lay_norm_eps:?layer?normalization中的微小量,防止分母為0(Default?=?1e-5)batch_first:?若`True`,則為(batch, seq, feture),若為`False`,則為(seq, batch, feature)(Default:False)例子:>>>?transformer_model?=?Transformer(nhead=16,?num_encoder_layers=12)>>>?src?=?torch.rand((10,?32,?512))>>>?tgt?=?torch.rand((20,?32,?512))>>>?out?=?transformer_model(src,?tgt)'''def?__init__(self,?d_model:?int?=?512,?nhead:?int?=?8,?num_encoder_layers:?int?=?6,num_decoder_layers:?int?=?6,?dim_feedforward:?int?=?2048,?dropout:?float?=?0.1,activation?=?F.relu,?custom_encoder:?Optional[Any]?=?None,?custom_decoder:?Optional[Any]?=?None,layer_norm_eps:?float?=?1e-5,?batch_first:?bool?=?False)?->?None:super(Transformer,?self).__init__()if?custom_encoder?is?not?None:self.encoder?=?custom_encoderelse:encoder_layer?=?TransformerEncoderLayer(d_model,?nhead,?dim_feedforward,?dropout,activation,?layer_norm_eps,?batch_first)encoder_norm?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.encoder?=?TransformerEncoder(encoder_layer,?num_encoder_layers)if?custom_decoder?is?not?None:self.decoder?=?custom_decoderelse:decoder_layer?=?TransformerDecoderLayer(d_model,?nhead,?dim_feedforward,?dropout,activation,?layer_norm_eps,?batch_first)decoder_norm?=?nn.LayerNorm(d_model,?eps=layer_norm_eps)self.decoder?=?TransformerDecoder(decoder_layer,?num_decoder_layers,?decoder_norm)self._reset_parameters()self.d_model?=?d_modelself.nhead?=?nheadself.batch_first?=?batch_firstdef?forward(self,?src:?Tensor,?tgt:?Tensor,?src_mask:?Optional[Tensor]?=?None,?tgt_mask:?Optional[Tensor]?=?None,memory_mask:?Optional[Tensor]?=?None,?src_key_padding_mask:?Optional[Tensor]?=?None,tgt_key_padding_mask:?Optional[Tensor]?=?None,?memory_key_padding_mask:?Optional[Tensor]?=?None)?->?Tensor:r'''參數:src:?源語言序列(送入Encoder)(必備)tgt:?目標語言序列(送入Decoder)(必備)src_mask:?(可選)tgt_mask:?(可選)memory_mask:?(可選)src_key_padding_mask:?(可選)tgt_key_padding_mask:?(可選)memory_key_padding_mask:?(可選)形狀:-?src:?shape:`(S,?N,?E)`,?`(N,?S,?E)`?if?batch_first.-?tgt:?shape:`(T,?N,?E)`,?`(N,?T,?E)`?if?batch_first.-?src_mask:?shape:`(S,?S)`.-?tgt_mask:?shape:`(T,?T)`.-?memory_mask:?shape:`(T,?S)`.-?src_key_padding_mask:?shape:`(N,?S)`.-?tgt_key_padding_mask:?shape:`(N,?T)`.-?memory_key_padding_mask:?shape:`(N,?S)`.[src/tgt/memory]_mask確保有些位置不被看到,如做decode的時候,只能看該位置及其以前的,而不能看后面的。若為ByteTensor,非0的位置會被忽略不做注意力;若為BoolTensor,True對應的位置會被忽略;若為數值,則會直接加到attn_weights[src/tgt/memory]_key_padding_mask?使得key里面的某些元素不參與attention計算,三種情況同上-?output:?shape:`(T,?N,?E)`,?`(N,?T,?E)`?if?batch_first.注意:src和tgt的最后一維需要等于d_model,batch的那一維需要相等例子:>>>?output?=?transformer_model(src,?tgt,?src_mask=src_mask,?tgt_mask=tgt_mask)'''memory?=?self.encoder(src,?mask=src_mask,?src_key_padding_mask=src_key_padding_mask)output?=?self.decoder(tgt,?memory,?tgt_mask=tgt_mask,?memory_mask=memory_mask,tgt_key_padding_mask=tgt_key_padding_mask,memory_key_padding_mask=memory_key_padding_mask)return?outputdef?generate_square_subsequent_mask(self,?sz:?int)?->?Tensor:r'''產生關于序列的mask,被遮住的區(qū)域賦值`-inf`,未被遮住的區(qū)域賦值為`0`'''mask?=?(torch.triu(torch.ones(sz,?sz))?==?1).transpose(0,?1)mask?=?mask.float().masked_fill(mask?==?0,?float('-inf')).masked_fill(mask?==?1,?float(0.0))return?maskdef?_reset_parameters(self):r'''用正態(tài)分布初始化參數'''for?p?in?self.parameters():if?p.dim()?>?1:xavier_uniform_(p)用個小例子側試一下:
到此為止,PyTorch 的 Transformer 庫我已經全部實現,相比于官方的版本,我自己手寫的這個少了較多的判定語句,所以使用時請確保自己看了我的教程,目明白基本的輸入輸出,有些不必要的調用,打包均已省略,另有一些不必要的函數已經全部自己重寫寫過。
另外,所有代碼均經過測試,若有問題,定是你的問題,完整版代碼:
https://github.com/sherlcok314159/ML/blob/main/nlp/models/functions.py
特別鳴謝
感謝 TCCI 天橋腦科學研究院對于 PaperWeekly 的支持。TCCI 關注大腦探知、大腦功能和大腦健康。
更多閱讀
#投 稿?通 道#
?讓你的文字被更多人看到?
如何才能讓更多的優(yōu)質內容以更短路徑到達讀者群體,縮短讀者尋找優(yōu)質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發(fā)出更多的可能性。?
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優(yōu)質內容,可以是最新論文解讀,也可以是學術熱點剖析、科研心得或競賽經驗講解等。我們的目的只有一個,讓知識真正流動起來。
?????稿件基本要求:
? 文章確系個人原創(chuàng)作品,未曾在公開渠道發(fā)表,如為其他平臺已發(fā)表或待發(fā)表的文章,請明確標注?
? 稿件建議以?markdown?格式撰寫,文中配圖以附件形式發(fā)送,要求圖片清晰,無版權問題
? PaperWeekly 尊重原作者署名權,并將為每篇被采納的原創(chuàng)首發(fā)稿件,提供業(yè)內具有競爭力稿酬,具體依據文章閱讀量和文章質量階梯制結算
?????投稿通道:
? 投稿郵箱:hr@paperweekly.site?
? 來稿請備注即時聯系方式(微信),以便我們在稿件選用的第一時間聯系作者
? 您也可以直接添加小編微信(pwbot02)快速投稿,備注:姓名-投稿
△長按添加PaperWeekly小編
????
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
總結
以上是生活随笔為你收集整理的Transformer源代码解释之PyTorch篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 直播 | ICML 2021论文解读:具
- 下一篇: Autoformer: 基于深度分解架构