过年放了七天假,每年第一件事就是立一个flag——希望今年除了能够将技术学扎实之外,还希望能够将所学能够用来造福社会,好像flag立得有点大了。没关系,套用一句电影台词为自己开脱一下——人没有梦想,和咸鱼有什么区别。闲话至此,进入今天主题:Transformer。谷歌于2017年提出Transformer网络架构,此网络一经推出就引爆学术界。目前,在NLP领域,Transformer模型被认为是比CNN,RNN都要更强的特征提取器。

Transformer算法简介

Transformer引入了self-attention机制,同时还借鉴了CNN领域中残差机制(Residuals),由于以上原因导致transformer有如下优势:

  • 模型表达能力较强,由于self-attention机制考虑到了句子之中词与词之间的关联,
  • 抛弃了RNN的循环结构,同时借用了CNN中的残差结构加快了模型的训练速度。

接下来我们来看看transformer的一些细节:

  • 首先Scaled Dot-Product Attention步骤是transformer的精髓所在,作者引入Q,W,V参数通过点乘相识度去计算句子中词与词之间的关联重要程度。其大致过程如图所示,笔者将会在实战部分具体介绍此过程如何实现。

    Scaled Dot-Product Attention

  • 第二个是muti-head步骤,直白的解释就是将上面的Scaled Dot-Product Attention步骤重复执行,然后将每次执行的结果拼接起来,需要注意的是每次重复执行Scaled Dot-Product Attention步骤的参数并不共享。

    Multi-Head

  • 第三个步骤就是残差网络结构——将muti-head步骤的输出和原始输入之间相加。这里不明白的可以参考笔者之前介绍残差网络的文章。

接下来就是实战部分,实战部分只使用了muti-head attention或者说是self-attention的向量表示作为最终特征进行文本分类。

Transformer文本分类实战

数据载入

下方代码的作用是将情感分析数据读入,格式为一句话和一个label:
sen_1 : 1
sen_2 : 0
1代表正面情绪,0代表负面情绪。

#! -*- coding: utf-8 -*-
from keras import backend as K from keras.engine.topology import Layer import numpy as np from keras.preprocessing import sequence from keras.layers import * from keras import Model from keras.callbacks import TensorBoard data = np.load("imdb.npz") x_test = data["x_test"] x_train = data["x_train"] y_test = data["y_test"] y_train = data["y_train"]

数据预处理

由于文本数据长短不一,下面代码可将数据padding到相同的长度。

from itertools import chain
all_word = list(chain.from_iterable(list(x_train)))
all_word = set(all_word) max_features = len(all_word) data_train = sequence.pad_sequences(x_train,200)

Self-attention

这里详细介绍一下模型最关键的部分Scaled Dot-Product Attention的构建过程,如图一 Scaled Dot-Product Attention:

图一 self-attention

  • 1.首先申明三个待优化的参数W_k,W_q,W_v,
  • 2.将输入X分别和W_k,W_q,W_v进行点乘,得到q_1,k_1,v_1,此过程可以理解成将同一句话中的词映射到三个不同的向量空间,这里笔者将三个不同的向量空间命名为Q空间,K空间和V空间,如图二 Query,Key,Value metrix

    图二 Query,Key,Value metrix

  • 3.然后计算Q空间的某一个词在K空间所以词向量分别点乘得分,之后将这些得分通过softmax函计算一个重要度系数。然后用计算出来的重要度系数乘上该词在V空间的词向量并加和得到该词最终的词向量表示,整个过程如图三 Softmax所示,这样就可以得到一句话经过self-attention后的向量表示Z

    图三 Softmax

上述整个过程就是Scaled Dot-Product Attention,本质上考虑到了一个句子中不同词之间的关联程度,这个过程或多或少增强了句子语义的表达。下方为keras定义的self-attention层的代码,这里加入了muti-head和mask功能的实现。

class Attention(Layer): def __init__(self, nb_head, size_per_head, **kwargs): self.nb_head = nb_head self.size_per_head = size_per_head self.output_dim = nb_head * size_per_head super(Attention, self).__init__(**kwargs) def build(self, input_shape): self.WQ = self.add_weight(name='WQ', shape=(input_shape[0][-1], self.output_dim), initializer='glorot_uniform', trainable=True) self.WK = self.add_weight(name='WK', shape=(input_shape[1][-1], self.output_dim), initializer='glorot_uniform', trainable=True) self.WV = self.add_weight(name='WV', shape=(input_shape[2][-1], self.output_dim), initializer='glorot_uniform', trainable=True) super(Attention, self).build(input_shape) def Mask(self, inputs, seq_len, mode='mul'): if seq_len == None: return inputs else: mask = K.one_hot(seq_len[:, 0], K.shape(inputs)[1]) mask = 1 - K.cumsum(mask, 1) for _ in range(len(inputs.shape) - 2): mask = K.expand_dims(mask, 2) if mode == 'mul': return inputs * mask if mode == 'add': return inputs - (1 - mask) * 1e12 def call(self, x): # 如果只传入Q_seq,K_seq,V_seq,那么就不做Mask # 如果同时传入Q_seq,K_seq,V_seq,Q_len,V_len,那么对多余部分做Mask if len(x) == 3: Q_seq, K_seq, V_seq = x Q_len, V_len = None, None elif len(x) == 5: Q_seq, K_seq, V_seq, Q_len, V_len = x # 对Q、K、V做线性变换 Q_seq = K.dot(Q_seq, self.WQ) Q_seq = K.reshape(Q_seq, (-1, K.shape(Q_seq)[1], self.nb_head, self.size_per_head)) Q_seq = K.permute_dimensions(Q_seq, (0, 2, 1, 3)) K_seq = K.dot(K_seq, self.WK) K_seq = K.reshape(K_seq, (-1, K.shape(K_seq)[1], self.nb_head, self.size_per_head)) K_seq = K.permute_dimensions(K_seq, (0, 2, 1, 3)) V_seq = K.dot(V_seq, self.WV) V_seq = K.reshape(V_seq, (-1, K.shape(V_seq)[1], self.nb_head, self.size_per_head)) V_seq = K.permute_dimensions(V_seq, (0, 2, 1, 3)) # 计算内积,然后mask,然后softmax A = K.batch_dot(Q_seq, K_seq, axes=[3, 3]) / self.size_per_head ** 0.5 A = K.permute_dimensions(A, (0, 3, 2, 1)) A = self.Mask(A, V_len, 'add') A = K.permute_dimensions(A, (0, 3, 2, 1)) A = K.softmax(A) # 输出并mask O_seq = K.batch_dot(A, V_seq, axes=[3, 2]) O_seq = K.permute_dimensions(O_seq, (0, 2, 1, 3)) O_seq = K.reshape(O_seq, (-1, K.shape(O_seq)[1], self.output_dim)) O_seq = self.Mask(O_seq, Q_len, 'mul') return O_seq def compute_output_shape(self, input_shape): return (input_shape[0][0], input_shape[0][1], self.output_dim)

位置编码

接下来定义一个位置编码层,由于是输入是句子属于一个序列,加入位置编码会使得语义表达更准确。

class Position_Embedding(Layer): def __init__(self, size=None, mode='sum', **kwargs): self.size = size # 必须为偶数 self.mode = mode super(Position_Embedding, self).__init__(**kwargs) def call(self, x): if (self.size == None) or (self.mode == 'sum'): self.size = int(x.shape[-1]) batch_size, seq_len = K.shape(x)[0], K.shape(x)[1] position_j = 1. / K.pow(10000., 2 * K.arange(self.size / 2, dtype='float32') / self.size) position_j = K.expand_dims(position_j, 0) position_i = K.cumsum(K.ones_like(x[:, :, 0]), 1) - 1 # K.arange不支持变长,只好用这种方法生成 position_i = K.expand_dims(position_i, 2) position_ij = K.dot(position_i, position_j) position_ij = K.concatenate([K.cos(position_ij), K.sin(position_ij)], 2) if self.mode == 'sum': return position_ij + x elif self.mode == 'concat': return K.concatenate([position_ij, x], 2) def compute_output_shape(self, input_shape): if self.mode == 'sum': return input_shape elif self.mode == 'concat': return (input_shape[0], input_shape[1], input_shape[2] + self.size)

而谷歌的论文直接给出了position embedding 层的公式,如下图所示。

position embeding

此公式的含义是将

id

p

的位置映射为一个

d_{pos}

维的位置向量,此向量的第

i

个元素的值就是通过上述公式算出来的

PE_i(p)

。position embeding背后的物理意义参考于参考文献第一篇:由于在数学上有sin(α+β)=sinαcosβ+cosαsinβ以及cos(α+β)=cosαcosβ−sinαsinβ,这表明位置p+k的向量可以表示成位置p的向量的线性变换,这提供了表达p相对位置信息的可能性。

模型构建

接下来使用上方定义好的的self-attention层和position embedding层进行模型构建,这里设置的8个head,意味着将self-attention流程重复做8次,这里的代码实现不是讲8个head向量拼接,而是通过keras自带的 GlobalAveragePooling1D函数将8个head的向量求和平均一下。

K.clear_session()
callbacks = [TensorBoard("log/")]
S_inputs = Input(shape=(None,), dtype='int32')
embeddings = Embedding(max_features, 128)(S_inputs) embeddings = Position_Embedding()(embeddings) # Position_Embedding O_seq = Attention(8, 16)([embeddings, embeddings, embeddings])# Self Attention O_seq = GlobalAveragePooling1D()(O_seq) O_seq = Dropout(0.5)(O_seq) outputs = Dense(1, activation='sigmoid')(O_seq) model = Model(inputs=S_inputs, outputs=outputs) model.summary()

模型的网络结构可视化输出如下:

model

模型训练

将之前预处理好的数据喂给模型,同时设置好batch size 和 epoch就可以跑起来了。由于笔者是使用的是笔记本的cpu,所以只跑一个epoch。

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(data_train, y_train,batch_size=2,epochs=1,callbacks=callbacks,validation_split=0.2)

train

结语

Transformer在各方面性能上都超过了RNN和CNN,但是其最主要的思想还是引入了self-attention,使得模型可以考虑到句子中词与词之间的相互联系,这个思想在NLP很多领域,如机器阅读(R-Net)中也曾出现。所以如何在embeding时的更好挖掘句子的语义,才是深度学习在nlp领域最需要解决的难题。

参考文献

https://spaces.ac.cn/archives/4765
https://blog.csdn.net/qq_41664845/article/details/84969266
Attention Is All You Need

作者:王鹏你妹
链接:https://www.jianshu.com/p/704893b996f9
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Attention is all your need 谷歌的超强特征提取网络——Transformer相关推荐

  1. 深度学习核心技术精讲100篇(十一)-Google利器超强特征提取网络(Transformer)

    前言 谷歌于2017年提出 Transformer网络架构,此网络一经推出就引爆学术界.目前,在NLP领域,Transformer模型被认为是比CNN,RNN都要更强的特征提取器. Transform ...

  2. 谷歌研究院出品:高效 Transformer 模型最新综述

    2021-01-02 15:23:28 编译 | Mr Bear 编辑 | 陈彩娴 近年来,基于自注意力机制的 Transformer 模型在自然语言处理.计算机视觉.强化学习等领域的学术研究中取得了 ...

  3. 谷歌提出新型卷积网络EfficientNet: 推理速度升5.1倍参数减少88%,需要我们的验证

    推理速度升5.1倍参数减少88%:谷歌提出新型卷积网络EfficientNet 谷歌提出了一项新型模型缩放方法:利用复合系数统一缩放模型的所有维度,该方法极大地提升了模型的准确率和效率.谷歌研究人员基 ...

  4. android不能在主线程,android.os.NetworkOnMainThreadException 在4.0之后谷歌强制要求连接网络不能在主线程进行访问(示例代码)...

    谷歌在4.0系统以后就禁止在主线程中进行网络访问了,原因是: 主线程是负责UI的响应,如果在主线程进行网络访问,超过5秒的话就会引发强制关闭, 所以这种耗时的操作不能放在主线程里.放在子线程里,而子线 ...

  5. 2.阅读笔记Stacked Attention Networks for Image Question Answering(堆叠式注意力网络)

    Stacked Attention Networks for Image Question Answering(堆叠式注意力网络) 一,介绍 这篇论文提出了一种用于图像问答任务的堆叠式注意力网络SAN ...

  6. 实现100倍加速!谷歌开源超强张量计算库TensorNetwork

    乾明 发自 凹非寺  量子位 报道 | 公众号 QbitAI 量子系统复杂,暴力计算无效,原有张量网络(Tensor Network)难以广泛规模使用,让开发高温超导体等复杂问题受限于此. 现在,谷歌 ...

  7. 谷歌提出超强预训练模型CoCa,在ImageNet上微调Top-1准确率达91%!在多个下游任务上SOTA!...

    关注公众号,发现CV技术之美 本文分享论文『CoCa: Contrastive Captioners are Image-Text Foundation Models』,Google Research ...

  8. 六项任务、多种数据类型,谷歌、DeepMind提出高效Transformer评估基准

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 来自:机器之心 自诞生以来,Transformer 在不同领域得到了广泛应用,研究人员 ...

  9. 谷歌、DeepMind提出高效Transformer评估基准

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:机器之心 AI博士笔记系列推荐 周志华<机器学习> ...

最新文章

  1. 主流框架中DOMContentLoaded事件的实现
  2. DOM-添加元素、节点
  3. 怎么用class引入svg_【蓝湖指北】走向设计巅峰,从蓝湖 Sketch 插件开始,用它!...
  4. Linux操作系统ssh默认22端口修改方法
  5. 子矩阵的最大累加和问题
  6. 4月22日(牛马不对嘴)
  7. libevent源码学习-----event操作
  8. 相机+激光雷达重绘3D场景
  9. 基于消息与.Net Remoting的分布式处理架构
  10. codeforces 339A-C语言解题报告
  11. 八年磨一剑,阿里云ApsaraDB for HBase2.0正式上线
  12. php 两个单词 正则表达式字符前_【阅读整理】正则表达式 - 基础篇
  13. CLR via C# 中关于装箱拆箱的摘录
  14. php生成站点地图,php生成百度站点地图sitemap.xml
  15. Eclipse编译时函数报错:Undefined reference to 'pthread_create'
  16. 如何测试一个串口调试助手软件,串口调试助手(SComAssistant)
  17. python实现不重复排列组合_python 实现排列组合
  18. Interpreter(解释器)
  19. 行列式的组合定义及其应用--反对称阵的Pfaffian
  20. 关于hadoop运行成功但是无法链接web页面

热门文章

  1. 2022-2028年中国纺织服装专业市场深度调研及前景预测报告(全卷)
  2. python 实现桶排序
  3. 【数据结构】链表中的 指针,地址
  4. spark,hadoop区别
  5. 自己动手实现20G中文预训练语言模型示例
  6. python的print格式化输出,以及使用format来控制。
  7. Python多版本pip安装库的问题
  8. 一个隐马尔科夫模型的应用实例:中文分词
  9. TVM yolov3优化代码修改(编译运行OK)
  10. CPU,GPU,Memory调度