【NLP】第十四章:Transformer论文解读
【NLP】第十四章:Transformer论文解读¶
Transformer的论文名称是《Attention Is All You Need》,这篇论文是深度学习史上最重要的论文之一,因为Transformer是继MLP、CNN、RNN之后的第四大深度学习基础架构,所以原始论文非常有必要认真读一读。本篇整理了我精读过程的关注点,当然核心的内容都在前面5个篇章展开详细写过了。本篇展示的是前面没有涉及到的边角知识点,算是对前面篇章的一个补充。
我本人是不擅长翻译的,尤其是这种专业论文,背后有大量的背景知识,所以看不懂的同学还是建议从MLP开始一点点,每个细节都扎实学透后,再来理解这篇文章。此时你再看,即使有些单词或者语法不是很明白,你也可以猜的七七八八了。
transformer是google于2017年提出的模型架构,论文地址:[1706.03762] Attention Is All You Need ,大家可自行下载阅读。
1、论文作者及作者的贡献的描述
2、摘要部分
3、结论部分
4、引言部分
5、背景部分
扩展神经GPU(Extended Neural GPU, ENGPU)是一种基于卷积神经网络的神经网络模型,专门设计用于高效地处理长序列的任务。它旨在减少传统循环神经网络在处理长序列时的计算开销,通过并行化计算来加速训练和推理过程。 "GPU" 表示模型的设计灵感来源于 GPU 的并行计算能力,但它并不指代实际的硬件。模型本身仍然是一种神经网络模型。
ByteNet是一种用于序列建模的神经网络架构,主要用于NLP任务。它是基于卷积神经网络(CNN)设计的,它结合了卷积操作和残差连接,使用卷积层来处理序列数据,并且通过残差连接帮助在训练时保持梯度稳定,从而捕捉长距离的依赖。
卷积网络中有一个重要的概念就是感受野,在我专门讲卷积层时,画了几个非常通俗的图,给大家展示了感受野的概念。所以清楚感受野的同学,自然也就知道,感受野再大,也不可能覆盖全部序列。因为感受野就像一个滑窗的效果,有的窗口大点,有的窗口小点,但是不管大小,它的本质都是窗口。那既然是窗口,你就别想通过窗口看到序列的全部了,因为上一个窗口的信息是不传递到小一个窗口的。也所以卷积网络是很难处理长序列数据的。
但是注意力机制,是一个注意力层就计算了一个序列中的全部样本!所以注意力层天然就适合处理长序列数据。但同时这也是注意力模型的代价所在!代价就是计算量啊!假如你一个序列是10000个样本,你的注意力层是不是得学一个10000x10000的注意力矩阵啊!
这是一个理解维度。另外一个理解维度是:卷积网络虽然是独立的小滑窗看序列的,但它是可以有一系列的卷积核组成,就是卷积是多通道看这个序列,那不同通道就会看到不同的数据模式,这是卷积的强大之处。所以Transformer为了也能有卷积的这种效果,Transformer就加入了多头的概念,就是多头注意力机制。所以多头其实就是为了模拟卷积层的多通道的效果。
6、模型架构
上面的架构图基本就是Transformer的全部内容了。但是你只看图,只看3.1的两段英文,你是完全理解不了Transformer的,因为这张图背后后非常多的知识点,但凡你有思维断点,你就串不起来。所以建议回看我前面写的七、八万字的Transfomer解读,再回看论文,你就可以踏实体会论文的内容了。
所以,本文并不是再重复写前面已经写过的内容。本部分是结合论文,补齐前面没有提到的点。所以后面我就自己挑点内容写了,算是对之前写的一个点对点的补齐:
7、两个embedding层和最后的linear层共享参数
意思就是数据集WMT2014,这个英德数据集,中的源文本source(英文语句)和目标文本target(德文语句)经过bpe编码后,最小的单元是subword,而这些subword中有很多是可以共享语义的。因为英语和德语同属日耳曼语族,所以这些subword很多是可以共享语义的。
也就是使用bpe生成的词表是一个大的共享词表,做词嵌入时只有对应语言的embeeding被激活。这样做的意义是当目标语言和源语言有相同subword的时候可以共享语义信息。所以Transformer论文中的两个embedding层是共享参数的。但对于中英翻译来说的话可能意义不大因为没有相同的subword,权重共享会在做softmax时加大计算量,所以实际中是否使用还要权衡。
至于softmax前面的linear层,因为这个linear层的输出是tgt字典的长度。或者说这个linear层是把embedding后的词向量再映射会到标签编码,所以这个linear层和权重结构和embedding层的权重结构是一样的,而且功能是互逆的,所以,这两个层也是可以共享参数的。当然linear的bias就无法共享了。
共享参数的好处就是减少参数量了,模型会好训练一点。
8、Transformer的计算效率
所以架构的设计,是综合你的数据、硬件、效果来综合评估和抉择的。
9、训练部分
10、模型超参数的设置
补充:代码相关的知识点
1、PyTorch中Tensor和tensor的区别
(1)torch.Tensor()是python类,更明确地说,是默认张量类型torch.FloatTensor()的别名,torch.Tensor([1,2])会调用Tensor类的构造函数__init__,生成单精度浮点类型的张量。
(2)torch.tensor()则是python函数,函数原型是:torch.tensor(data, dtype=None, device=None, requires_grad=False), 其中data可以是list, tuple, NumPy ndarray, scalar和其他类型。 torch.tensor会从data中的数据部分做拷贝,而不是直接引用,根据原始数据类型生成相应的torch.LongTensor、torch.FloatTensor和torch.DoubleTensor。
所以,二者底层实现是不一样的。因为函数调用要拷贝参考,而类属性则可以直接引用,所以使用类比使用函数性能会好一点。其他就没必要深究了,建议使用torch.Tensor()。
2、torch.autograd.Variable的用法
将pytorch中的张量封装成Variable对象。Variable对象将张量作为其内部状态,主要作用就是保存张量数据。此外还提供一系列有用的方法和属性来简化神经网络模型的构建和训练过程,使得神经网络编程更加简单、直观和高效。比如:
(1)Variable有requires_grad()属性,可以自动计算梯度(autograd),使得在反向传播过程中能够自动计算损失函数对模型参数的梯度;而volatile=True的节点不会求导,即使requires_grad=True,也不会进行反向传播,对于不需要反向传播的情景,该参数可以实现一定速度的提升,并节省一半的显存,因为其不需要保存梯度。
(2)Variable有save()方法和loadstate_dict()方法,用来保存和恢复模型的状态,使得模型的训练和预测过程可以方便地进行断点续传;
(3)Variable 可以进行数据增强(data augmentation),使得模型能够在训练过程中更好地泛化。
3、pytorch中backward计算梯度的过程
如果Tensor是非标量(non-scalar)的(即是说Y中有不止一个y,即Y=[y1,y2,…]),且requires_grad=True。那么backward函数需要指定gradient,它的形状应该和Variable的长度匹配。因为gradient的长度体与Y的长度一直才能保存每一个yi的梯度值啊。
关于梯度、关于正向传播、反向传播、计算图等这些概念不是特别清楚的同学,建议参考【深度学习】第四章:反向传播-梯度计算-更新参数-CSDN博客
4、unsqueeze和squeeze的升维降维
5、nn.Module中register_buffer用法
register_buffer是Module类中的一个方法,用于记录不需要计算梯度但要跟随模型参数一起保存、加载或者移动(cuda)的变量。和BatchNorm中的均值running_mean和方差running_var类似,都是不需要被计算梯度,但是需要保存在模型中。
register_buffer(name, tensor, persistent=True)
name(str):字符串,指定被调用的名字。
tensor(Tensor or None):初始化该注册缓冲器张量,如果为None,则不会保存在模型中,只是暂存。
persistent(default: True):True则可以跟随模型被保存model.save_state_dict()和加载model.load_state_dict()
6、一些计算函数
7、画图技巧
8、制作掩码矩阵
当调用tensor.masked_fill()时,PyTorch会遍历掩码中的每个元素进行保留或替换。但是masked_fill操作是基于C/C++的实现,因此在处理大规模数据时性能较高。
9、手动构建一个Embedding类来实现文本嵌入层:
10、字典
11、一些细节
