上圖是經典的雙向RNN模型,咱們知道該模型是經過遞歸的方式運行,雖然適合對序列數據建模,可是缺點也很明顯「它沒法並行執行」也就沒法利用GPU強大的並行能力,再加上各類門控機制,運行速度很慢。通常而言,編碼器輸出編碼向量C做爲解碼器輸入,可是因爲編碼向量C中全部的編碼器輸入值貢獻相同,致使序列數據越長信息丟失越多。算法
CNN網絡相比RNN網絡,它雖然能夠並行執行,可是沒法一次捕獲全局信息,經過上圖可得咱們須要屢次遍歷,多個卷積層疊加增大感覺野。
谷歌的作法是Attention is All You Need !網絡
如圖所示是Transformer的總體結構,咱們將詳細介紹每一部分,先從左邊的編碼器開始。dom
A: 這一步,我想你們已經很是熟悉了,將詞彙錶轉爲embedding維度的向量(onehot和embedding區別)。
B: 僅僅使用attention有一個致命短板,它對序列數據的順序免疫,即:沒法捕獲序列的順序。好比對翻譯任務,咱們知道順序很是重要,單詞順序變更甚至會產生徹底不一樣的意思。所以增長Position Embedding給每一個位置編號,每一個編號對應一個向量,這樣每一個詞向量都會有一個位置向量,以此來定位。函數
如圖所示,Position Embedding計算公式,將id爲p的位置映射爲一個dpos維的位置向量,這個向量的第i個元素的數值就是PEi(p),位置編碼算法固然不止一種,可是不一樣算法必需要解決的的問題就是可以處理未知長度的序列。假設位置向量有4維,實際位置向量可能以下所示:性能
結合位置向量和詞向量咱們有兩種方式,一種是將二者拼接成一個新向量,另外一種是使二者維度相同而後相加獲得新向量。編碼
C:殘差鏈接,隨後是D: layer-normalization。spa
隨着網絡層數的加深,會帶來梯度消失,梯度爆炸以及過擬合問題。針對此問題,咱們通常採用權重正則化,批標準化,更換激活函數等等措施,可是當網絡層數進一步增長,會出現網絡退化問題,即:訓練集精度開始降低。使用殘差能夠解決此問題,目前使用殘差的網絡能夠達到幾百層。
E:Multi-head注意力機制.net
上圖是attention計算過程,咱們分解步驟,依次來看。翻譯
multi-headed attention:就是有多組上面那樣的attention,最後將結果拼接起來,其中,每組attention權重不共享。code
計算公式以下:
總體計算過程以下圖所示:
F:全鏈接網絡,兩個線性函數,一個非線性函數(Relu):
解碼器:
A:解碼器attention計算的內部向量和編碼器的輸出向量,計算源句和目標句之間的關係,在Transformer以前,attention機制就應用在這裏。
B:線性層是一個全鏈接層,神經元數量和詞表長度相等,而後添加softmax層,將機率最高值對應的詞做爲輸出。
Position_embedding:
def Position_Embedding(inputs,position_size): """ :param inputs: shape=(batch_size,timestep,word_size) :param position_size: int_ :return: shape=(1,seq_len.size,position_size) """ # inputs: shape=(batch_size,timestep,word_size) batch_size,seq_len=tf.shape(inputs)[0],tf.shape(inputs)[1] # shape=(position_size,) position_j=1./tf.pow(10000.,2*tf.range(position_size,dtype=tf.float32)/position_size) # shape=(1,position_size) position_j=tf.expand_dims(position_j,axis=0) # shape=(seq_len.size,) position_i=tf.range(tf.cast(seq_len,tf.float32),dtype=tf.float32) # shape=(seq_len.size,1) position_i=tf.expand_dims(position_i,axis=1) # 這是上面維度擴展的緣由 position_ij=tf.matmul(position_i,position_j) # shape=(seq_len.size,position_size) # 在axis=1,即:seq_len.size 拼接 position_ij=tf.concat([tf.cos(position_ij),tf.sin(position_ij)],axis=1) # shape=(1,seq_len.size,position_size) position_embedding=tf.expand_dims(position_ij,axis=0)+tf.zeros((batch_size,seq_len,position_size)) return position_embedding
Mask:
def Mask(inputs,seq_len,mode='mul'): """ :param mode: 'mul':將多餘部分置零,用於全鏈接層以前 'add':將多餘部分減去一個大的常數,用於softmax以前 """ if seq_len == None: return inputs else: # shape=(seq_len.size,seq_len.size) mask=tf.cast(tf.sequence_mask(seq_len,dtype=tf.float32)) for _ in range(len(inputs.shape)-2): # shape=(seq_len.size,seq_len.size,1) mask=tf.expand_dims(mask,2) if mode == 'mul': return inputs*mask if mode == 'add': return inputs-(1-mask)*1e12
tf.sequence_mask 使用示例:
mask=tf.sequence_mask([1,2,3,4,5],maxlen=5) print(mask)
Dense:
def Dense(inputs,output_size,bias=True,seq_len=None): input_size=int(inputs.shape[-1]) # shape=(input_size,output_size)均勻分佈,取值區間(-0.005,0.005) W=tf.Variable(tf.random_uniform([input_size,output_size],minval=-0.005,maxval=0.005)) # 是否使用'b' # (咱們在使用BN的時候,'b'能夠不使用。緣由是,BN改變數據分佈,'b'的調解會被BN覆蓋) if bias == True: b=tf.Variable(tf.random_uniform([output_size],minval=-0.005,maxval=0.005)) else: b=0 # reshape 到2維 outputs=tf.matmul(tf.reshape(inputs,(-1,input_size)),W)+b # reshape 到3維 outputs=tf.reshape(outputs,tf.concat([tf.shape(inputs)[:-1],[output_size]],axis=0)) if seq_len != None: outputs=Mask(outputs,seq_len,'mul') return outputs
Attention:
def Attention(Q,K,V,nb_head,size_per_head,Q_len=None,V_len=None): # 對Q、K、V分別做線性映射 Q = Dense(Q, nb_head * size_per_head, False) Q = tf.reshape(Q, (-1, tf.shape(Q)[1], nb_head, size_per_head)) Q = tf.transpose(Q, [0, 2, 1, 3]) K = Dense(K, nb_head * size_per_head, False) K = tf.reshape(K, (-1, tf.shape(K)[1], nb_head, size_per_head)) K = tf.transpose(K, [0, 2, 1, 3]) V = Dense(V, nb_head * size_per_head, False) V = tf.reshape(V, (-1, tf.shape(V)[1], nb_head, size_per_head)) V = tf.transpose(V, [0, 2, 1, 3]) # 計算內積,而後mask,而後softmax A = tf.matmul(Q, K, transpose_b=True) / tf.sqrt(float(size_per_head)) A = tf.transpose(A, [0, 3, 2, 1]) A = Mask(A, V_len, mode='add') A = tf.transpose(A, [0, 3, 2, 1]) A = tf.nn.softmax(A) # 輸出並mask O = tf.matmul(A, V) O = tf.transpose(O, [0, 2, 1, 3]) O = tf.reshape(O, (-1, tf.shape(O)[1], nb_head * size_per_head)) O = Mask(O, Q_len, 'mul') return O
Transformer如今大有取代RNN之勢,但依然存在一些缺點。首先,Transformer雖然使用到了位置向量,可是對序列位置要求很高的項目作的並很差。
本文內容部分參考The Illustrated Transformer