注:
此博文僅為了解transformer架構,如果使用,建議直接調用庫就行了
Transformer的優勢
相比之前占領市場的LSTM和GRU模型,Transformer有兩個顯著的優勢:
1. Transformer能夠利用分布式GPU進行并行訓練,提升模型訓練效率。
2. 在分析預測更長的文本時,捕捉間隔較長的語義關聯效果更好.
認識Transformer架構
Transformer模型的作用:
基于seq2seq架構的transformer模型可以完成NLP領域研究的典型任務,如機器翻譯,文本生成等.同時又可以構建預訓練語言模型,用于不同任務的遷移學習.
聲明:
在接下來的架構分析中,我們將假設使用Transformer模型架構處理從一種語言文本到另一種語言文本的翻譯工作,因此很多命名方式遵循NLP中的規則.比如:Embeddding層將稱作文本嵌入層,Embedding層產生的張量稱為詞嵌入張量,它的最后一維將稱作詞向量等
Transformer總體架構可分為四個部分:
輸入部分
源文本嵌入層及其位置編碼器
文本嵌入層的作用:無論是源文本嵌入還是目標文本嵌入,都是為了將文本中詞匯的數字表示轉變為向量表示,希望在這樣的高維空間捕捉詞匯間的關系.
位置編碼器的作用:因為在Transformer的編碼器結構中,并沒有針對詞匯位置信息的處理,因此需要在Embedding層后加入位置編碼器,將詞匯位置不同可能會產生不同語義的信息加入到詞嵌入張量中,以彌補位置信息的缺失.
# 構建Embedding類實現文本嵌入層代碼
import torch
import torch.nn as nn
import math
from torch.autograd import Variableclass Embeddings(nn.Module):def __init__(self, d_model, vocab):"""類的初始化Args:d_model (_type_): 詞嵌入的維度vocab (_type_): 詞表的大小"""super(Embeddings, self).__init__()self.lut = nn.Embedding(vocab, d_model)self.d_model = d_modeldef forward(self, x):"""前向傳播函數Args:x (_type_): 輸入的索引張量,形狀為 (L, N)Returns:_type_: 詞嵌入張量,形狀為 (L, N, d_model)"""return self.lut(x) * math.sqrt(self.d_model)
文本嵌入層使用
d_model=512
vocab=1000
x=Variable(torch.LongTensor([[1,2,3,4,5],[6,7,8,9,10]]))
embedding_layer=Embeddings(d_model,vocab)
output=embedding_layer(x)
print(output)
位置編碼器
# 位置編碼器
class PositionalEncoding(nn.Module):def __init__(self, d_model, dropout, max_len=5000):"""位置編碼器Args:d_model (_type_): 詞嵌入的維度dropout (_type_): 置零的比例,讓一定的神經元失效max_len (int, optional): 每個句子的最大長度. Defaults to 5000."""super(PositionalEncoding, self).__init__()self.dropout = nn.Dropout(p=dropout)# 初始化位置編碼矩陣pe = torch.zeros(max_len, d_model)position = torch.arange(0, max_len).unsqueeze(1)div_term=torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) /d_model))pe[:, 0::2] = torch.sin(position * div_term)pe[:, 1::2] = torch.cos(position * div_term)pe = pe.unsqueeze(0)self.register_buffer('pe', pe)def forward(self, x):"""_summary_Args:x (_type_): 文本序列的的詞嵌入表示"""x=x+Variable(self.pe[:, :x.size(1)], requires_grad=False)return self.dropout(x)
使用?
# 繪制詞匯向量中特征的分布曲線
import matplotlib.pyplot as plt
import numpy as npplt.figure(figsize=(15, 5))
pe=PositionalEncoding(20,0)
y=pe(Variable(torch.zeros(1, 100,20)))
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
plt.legend(['dim %d'%p for p in [4, 5, 6, 7]])
# 輸出效果分析:
# 每條顏色的曲線代表某一個詞匯中的特征在不同位置的含義.
# 保證同一詞匯隨著所在位置不同它對應位置嵌入向量會發生變化.
# 正弦波和余弦波的值域范圍都是1到﹣1這又很好的控制了嵌入數值的大小,有助于梯度的快速計算.
目標文本嵌入層及其位置編碼器
輸出部分
線性層的作用:通過對上一步的線性變化得到指定維度的輸出,也就是轉換維度的作用.
softInax層的作用:使最后一維的向量中的數字縮放到0-1的概率值域內,并滿足他們的和為1.
import torch.nn.functional as F
class Generator(nn.Module):def __init__(self, d_model, vocab):super(Generator, self).__init__()self.proj = nn.Linear(d_model, vocab)def forward(self, x):return F.log_softmax(self.proj(x), dim=-1)
編碼器部分
由N個編碼器層堆疊而成
每個編碼器層由兩個子層連接結構組成
第一個子層連接結構包括一個多頭自注意力子層和規范化層以及一個殘差連接
第二個子層連接結構包括一個前饋全連接子層和規范化層以及一個殘差連接
class Encoder(nn.Module):def __init__(self, layer, N):"""_summary_Args:layer (_type_): 編碼器層N (_type_): 編碼器層個數"""super(Encoder, self).__init__()self.layers=clones(layer, N)self.norm=LayerNorm(layer.size)def forward(self, x, mask):for layer in self.layers:x=layer(x, mask)return self.norm(x)
解碼器部分
由N個解碼器層堆疊而成
每個解碼器層由三個子層連接結構組成
第一個子層連接結構包括一個多頭自注意力子層和規范化層以及一個殘差連接
第二個子層連接結構包括一個多頭注意力子層和規范化層以及一個殘差連接
第三個子層連接結構包括一個前饋全連接子層和規范化層以及一個殘差連接
class Decoder(nn.Module):def __init__(self, layer, N):super(Decoder, self).__init__()self.layers=clones(layer, N)self.norm=LayerNorm(layer.size)def forward(self, x, memory, src_mask, tgt_mask):for layer in self.layers:x=layer(x, memory, src_mask, tgt_mask)return self.norm(x)
組裝構建各部件
class EncoderDecoder(nn.Module):def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):"""編碼器-解碼器結構Args:encoder (_type_): 編碼器對象decoder (_type_): 解碼器對象src_embed (_type_): 源數據嵌入函數tgt_embed (_type_): 目標數據嵌入函數generator (_type_): 類別生成器對象"""super(EncoderDecoder, self).__init__()self.encoder = encoderself.decoder = decoderself.src_embed = src_embedself.tgt_embed = tgt_embedself.generator = generatordef forward(self, src, tgt, src_mask, tgt_mask):"""_summary_Args:src (_type_): 源數據tgt (_type_): 目標數據src_mask (_type_): 源數據掩碼張量tgt_mask (_type_): 目標數據掩碼張量"""return self.decode(self.encode(src, src_mask), src_mask,tgt, tgt_mask)def encode(self, src, src_mask):"""編碼函數Args:src (_type_): _description_src_mask (_type_): _description_"""return self.encoder(self.src_embed(src), src_mask)def decode(self, memory, src_mask, tgt, tgt_mask):"""解碼函數Args:memory (_type_): _description_src_mask (_type_): _description_tgt (_type_): _description_tgt_mask (_type_): _description_"""return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)
def make_model(src_vocab, tgt_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1):"""_summary_Args:src_vocab (_type_): 源數據詞匯總數tgt_vocab (_type_): 目標數據詞匯總數N (int, optional): 子層堆疊數量. Defaults to 6.d_model (int, optional): 詞向量維度. Defaults to 512.d_ff (int, optional): 前饋全連接網絡的變換矩陣維度. Defaults to 2048.head (int, optional): 多頭注意力的多頭數. Defaults to 8.dropout (float, optional): 置零比率. Defaults to 0.1."""c=copy.deepcopyattn=MultiHeadedAttention(head,d_model)ff=PositionwiseFeedForward(d_model,d_ff,dropout)position=PositionalEncoding(d_model,dropout)model=EncoderDecoder(Encoder(EncoderLayer(d_model,c(attn),c(ff),dropout),N),Decoder(DecoderLayer(d_model,c(attn),c(attn),c(ff),dropout),N),nn.Sequential(Embeddings(d_model,src_vocab),c(position)),nn.Sequential(Embeddings(d_model,tgt_vocab),c(position)),Generator(d_model,tgt_vocab))for p in model.parameters():if p.dim()>1:nn.init.xavier_uniform_(p)return model
copy任務
任務描述:針對數字序列進行學習,學習的最終目標是使輸出與輸入的序列相同
任務意義:?copy任務在模型基礎測試中具有重要意義,因為copy操作對于模型來講是一條明顯規律,因此模型能否在短時間內,小數據集中學會它,可以幫助我們斷定模型所有過程是否正常,是否已具備基本學習能力
from pyitcast.transformer_utils import Batchdef data_generator(V,batch,nbatches):"""數據集生成器Args:V (_type_): 隨機生成數字的最大值+1batch (_type_): 放入多少數據集后一次更新nbatches (_type_): 一共要輸入多少次batch"""for i in range(nbatches):data=torch.from_numpy(np.random.randint(1,V,size=(batch,10)))data[:,0]=1source=Variable(data,requires_grad=False)target=Variable(data,requires_grad=False)yield Batch(source,target,10)
專用名詞
掩碼張量
什么是掩碼張量?
掩代表遮掩,碼就是我們張量中的數值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩還是1位置被遮掩可以自定義,因此它的作用就是讓另外一個張量中的一些數值被遮掩,也可以說被替換,它的表現形式是一個張量。
掩碼張量的作用:
在transformer中,掩碼張量的主要作用在應用attention(將在下一小節講解)時,有一些生成的attention張量中的值計算有可能已知了未來信息而得到的,未來信息被看到是因為訓練時會把整個輸出結果都一次性進行Embedding,但是理論上解碼器的的輸出卻不是一次就能產生最終結果的,而是一次次通過上一次結果綜合得出的,因此,未來的信息可能被提前利用.所以,我們會進行遮掩。
def subsquest_mask(size):"""生成掩碼張量代碼Args:size (_type_): 掩碼張量最后兩個維度的大小,它的最后兩維形成一個方陣"""attn_shape=(1, size, size)subsquest_mask=np.triu(np.ones(attn_shape), k=1).astype('uint8')return torch.from_numpy(1-subsquest_mask)
注意力機制
什么是注意力?
我們觀察事物時,之所以能夠快速判斷一種事物(當然允許判斷是錯誤的),?是因為我們大腦能夠很快把注意力放在事物最具有辨識度的部分從而作出判斷,而并非是從頭到尾的觀察一遍事物后,才能有判斷結果.正是基于這樣的理論,就產生了注意力機制.
注意力計算規則:
它需要三個指定的輸入Q(query),?K(key),?V(value),?然后通過公式得到注意力的計算結果,這個結果代表query在key和value作用下的表示.而這個具體的計算規則有很多種,我這里只介紹我們用到的這一種.
什么是注意力機制:
注意力機制是注意力計算規則能夠應用的深度學習網絡的載體,除了注意力計算規則外,還包括一些必要的全連接層以及相關張量處理,使其與應用網絡融為一體.使用自注意力計算規則的注意力機制稱為自注意力機制.
注意力機制的計算規則?
import torch.nn.functional as Fdef attention(query, key, value, mask=None, dropout=None):"""注意力機制的實現代碼Args:query (_type_): 查詢向量,是一段準備被概括的文本key (_type_): 鍵向量,是給出的提示value (_type_): 值向量,是大腦中的對提示K的延伸mask (_type_, optional): 掩碼張量. Defaults to None.dropout (_type_, optional): 丟棄率. Defaults to None.Returns:_type_: 注意力機制的輸出"""d_k=query.size(-1)scores=torch.matmul(query, key.transpose(-2, -1))/math.sqrt(d_k)if mask is not None:scores=scores.masked_fill(mask==0, -1e9)p_attn=F.softmax(scores, dim=-1)if dropout is not None:p_attn=dropout(p_attn)return torch.matmul(p_attn, value), p_attn
多頭注意力機制
只有使用了一組線性變化層,即三個變換張量對Q,K,V分別進行線性變換,這些變換不會改變原有張量的尺寸,因此每個變換矩陣都是方陣。
得到輸出結果后,多頭的作用才開始顯現,每個頭開始從詞義層面分割輸出的張量,也就是每個頭都想獲得一組Q,K,V?進行注意力機制的計算,但是句子中的每個詞的表示只獲得一部分,也就是只分割了最后一維的詞嵌入向量,這就是所謂的多頭。
將每個頭的獲得的輸入送到注意力機制中,就形成多頭注意力機制.
多頭注意力機制的作用:這種結構設計能讓每個注意力機制去優化每個詞匯的不同特征部分,從而均衡同一種注意力機制可能產生的偏差,讓詞義擁有來自更多元的表達,實驗表明可以從而提升模型效果。
import copydef clones(module, N):"""用于生成相同的網絡層Args:module (_type_): 要克隆的目標網絡層N (_type_): 需要克隆的數量"""return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])class MultiHeadedAttention(nn.Module):def __init__(self, head, embedding_dim, dropout=0.1):"""多頭注意力機制的實現代碼Args:head (_type_): 頭數embedding_dim (_type_): 詞嵌入維度dropout (_type_, optional): 丟棄率. Defaults to 0.1."""super(MultiHeadedAttention, self).__init__()# 保證d_model是頭數的整數倍assert embedding_dim%head==0self.d_k=embedding_dim // headself.head=headself.embedding_dim=embedding_dimself.linears=clones(nn.Linear(embedding_dim, embedding_dim), 4)self.attn=Noneself.dropout=nn.Dropout(p=dropout)def forward(self, query, key, value, mask=None):"""前向邏輯結構Args:query (_type_): _description_key (_type_): _description_value (_type_): _description_mask (_type_, optional): _description_. Defaults to None."""if mask is not None:mask=maskbatch_size=query.size(0)query, key, value = [model(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2) for model, x in zip(self.linears, (query, key, value))]x, self.attn = attention(query, key, value, mask, self.dropout)x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.head * self.d_k)return self.linears[-1](x)
前饋全連接層
在Transformer中前饋全連接層就是具有兩層線性層的全連接網絡.
前饋全連接層的作用:
考慮注意力機制可能對復雜過程的擬合程度不夠,通過增加兩層網絡來增強模型的能力.
class PositionwiseFeedForward(nn.Module):def __init__(self, d_model, d_ff, dropout=0.1):"""_summary_Args:d_model (_type_): _description_d_ff (_type_): 詞嵌入的維度dropout (float, optional): _description_. Defaults to 0.1."""super(PositionwiseFeedForward, self).__init__()self.w1=nn.Linear(d_model, d_ff)self.w2=nn.Linear(d_ff, d_model)self.dropout=nn.Dropout(dropout)def forward(self, x):return self.w2(self.dropout(torch.relu(self.w1(x))))
規范化層
規范化層的作用:
它是所有深層網絡模型都需要的標準網絡層,因為隨著網絡層數的增加,通過多層的計算后參數可能開始出現過大或過小的情況,這樣可能會導致學習過程出現異常,模型可能收斂非常的慢.因此都會在一定層數后接規范化層進行數值的規范化,使其特征數值在合理范圍內.
class LayerNorm(nn.Module):def __init__(self, features, eps=1e-6):"""_summary_Args:features (_type_): 詞嵌入維度eps (_type_, optional): 防止分母為0. Defaults to 1e-6."""super(LayerNorm, self).__init__()self.a2=nn.Parameter(torch.ones(features))self.b2=nn.Parameter(torch.zeros(features))self.eps=epsdef forward(self, x):"""_summary_Args:x (_type_): 上一層的輸出"""mean=x.mean(-1, keepdim=True)std=x.std(-1, keepdim=True)return self.a2*(x-mean)/(std+self.eps)+self.b2
子層連接結構
輸入到每個子層以及規范化層的過程中,還使用了殘差鏈接(跳躍連接),因此我們把這一部結構整體叫做子層連接(代表子層及其鏈接結構),在每個編碼器層中,都有兩個子層,這兩個子層加上周圍的鏈接結構就形成了兩個子層連接結構.
class SublayerConnection(nn.Module):def __init__(self, size, dropout):"""_summary_Args:size (_type_): 詞嵌入維度dropout (_type_): 隨機抑制比率"""super(SublayerConnection, self).__init__()self.norm=LayerNorm(size)self.dropout=nn.Dropout(dropout)def forward(self, x, sublayer):return x+self.dropout(sublayer(self.norm(x)))
編碼器層
編碼器層的作用:作為編碼器的組成單元,每個編碼器層完成一次對輸入的特征提取過程,即編碼過程
class EncoderLayer(nn.Module):def __init__(self, size, self_attn, feed_forward, dropout):"""_summary_Args:size (_type_): 詞維度大小self_attn (_type_): 多頭注意力層feed_forward (_type_): 實例化的前饋全連接層dropout (_type_): _description_"""super(EncoderLayer, self).__init__()self.self_attn=self_attnself.feed_forward=feed_forwardself.sublayer=clones(SublayerConnection(size, dropout),2)self.size=sizedef forward(self, x, mask):x=self.sublayer[0](x, lambda x:self.self_attn(x, x, x, mask))return self.sublayer[1](x, self.feed_forward)
解碼器層
class DecoderLayer(nn.Module):def __init__(self, size, self_attn, src_attn, feed_forward, dropout):"""_summary_Args:size (_type_): 詞維度大小self_attn (_type_): 注意力機制,自注意力src_attn (_type_): 非自注意力機制feed_forward (_type_): 前饋全連接層dropout (_type_): _description_"""super(DecoderLayer, self).__init__()self.size=sizeself.self_attn=self_attnself.src_attn=src_attnself.feed_forward=feed_forwardself.sublayer=clones(SublayerConnection(size, dropout), 3)def forward(self, x, memory, src_mask, tgt_mask):"""_summary_Args:x (_type_): 上一層輸入memory (_type_): 來自編碼器層語義儲存變量src_mask (_type_): 源數據掩碼張量tgt_mask (_type_): 目標數據掩碼張量"""m=memoryx=self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))x=self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))return self.sublayer[2](x, self.feed_forward)