上图是经典的双向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