机器学习从入门到放弃:Transfomer-现代大模型的基石
一、前言
随着 ChatGPT 的横空出世,全世界的目光都聚焦在了生成式 AI 上。本次将介绍 Transformer 的发展历史、基本原理,也是记录总结自己在学习路上的所得。
首先我想聊聊 NLP 的发展路线,这样对于后面的 transformer 可能会有更好的理解。自从计算机诞生之初,让计算机理解人类的语言,一直是计算机科学研究的前沿方向。早在1950年,A.M. Turing 就已经开创性的发表过一篇文章
Computing Machinery and Intelligence.
在文章中他提出了一个模仿游戏,一共有 a,b,c 三个玩家,让 c 来根据 a,b 的回答,来猜测他们相应的性别。a 的工作是用来迷惑,让 c 做出错误的判断,而 b 的工作是配合 c,让 c 尽可能的猜对。这时我们把 c 换成一台计算机,如果这次能和上次保持一样的结果,那么就说机器通过了图灵测试。这个问题还有一个变种,那就是让参与者同时对计算机还有人类来进行提问,基于他们的回答,让他们判断对方到底是计算机还是人类,如果参与者的平均误判超过了30%,同样我就指出计算机通过了图灵测试。虽然现在的 openai 一直没有公布 ChatGPT 是否通过了图灵测试,但是我相信应该是通过测试了的。
图灵提出了一个影响深远的问题:Can Machine Think?
所以这里引发了一个哲学的问题,What is thinking? 你首先需要定义什么是思考🤔?但是这问题太抽象了,这个问题慢慢简化总结为,机器能不能做到我们人类,我们思考者所做的事情?要解答这个问题,为了让计算机可以模仿我们人类,最大程度的接近人类的行为,理解我们人类的自然语言就是第一步。所以 NLP 自然语言处理,无疑是人工智能发展中至关重要的一环。
在1980到1990年代,人们提出了一个比较通用的语言模型的构想,可以服务于各行各业。这里的模型主要基于统计方法,基于马尔可夫假设,一个词语的出现概率,只和前面的 n 个词语有关,而与更早的词语或者说往后的词语无关。那么自然而然的就产生了二元模型(bigram模型)
其中产生的一个新的词语,只和它的现在的词语和前一个词语有关,和 ngram 模型跟它前 n-1 个词语有关系。但是随着 n 的增大,你所需要记录的 n 的概率就成指数上升了。所以为了应对这个问题,人们发明了CNN, RNN 这一类的卷积神经网络,通过层数的迭代,而可以学习到更多参数,让模型达到更好的效果。
二、Attention机制
Attention 这里如果用神经网络来解释的话,就是我们如果要识别人类语言,那么最开始的工作非常容易想到,那就是把目标语言和输入语言一对一的进行转化翻译,也就是 one input -> one output。
每个全连接网络负责1对1的输出,把每一个向量输入 fully-connected network 里面,其实这里也就是单纯的词向量转化比如 word2vec 模型。它可以把每次词语一一对应的进行翻译,把中文的“我”变成“me”,“你”变成“you”。
但是这样子只是解决了词义的转换,而语义的理解和解析,这种模型架构里还没有很好的解决。比如我输入 "i saw a saw" ,我期待的是机器能把第一个 saw 翻译成动词,而第二个 saw 翻译成名词。但是对于这种全连接网络来说是不可能做到的,因为 FC 接收的是同一个输入“saw”,它怎么可能会翻译成动词“看见”,和名词“锯子”呢?所以这里人们的解决方案是上面提到的 bigram 模型。也就是把它相相关联的词语向量都作为输入,进入 FC 中。
这里的输入是3个词向量,我们也就称其为 n=3 的的一个 sequnece window,显而易见的是如果 n 越大,那么这个模型的关联输入也就越大,那么它的输出也就是越准确,语义的理解自然也就越好了。但是如果某个任务的 n 需要膨胀到等于整个输入的 seq,也就是说需要把整个输入的话作为输入的 window,它才能表现的比较好的时候,那么势必会造成运算量的成倍增加,FC需要学习的参数可能就会非常多,而且这是一个不确定的值。
所以有没有一个更好的方法来考虑整个 FC 的 input 信息呢?所以这里的解决方式就是自注意力的机制
self-attention 专门处理所有的关联咨询,也就是 attention 层会考虑所有输入 input 并把他变成一个输出 ouput 给到 FC ,FC 负责处理某一个位置的资讯,并且将其进行转化为输出。所以 attention 机制是怎么运作的呢?
实际上在 attention 中主要是计算与每个输入之间的关联程度,比如 a1 分别和 a2, a3, a4 求 dotproduct 点积。如下
两个输入向量分别乘以两个不同的矩阵 Wq 和 Wk 得到 q 和 k 的新向量,然后再进行 dotproduct 得到新向量 α,这个向量就表示了输入的两个 a1 和 a2 向量的关联程度。在 attention 层内的计算关联方式则如下图
每个输入的向量都和 Wk 矩阵相乘然后计算出 k1 ~ k4 的关联向量,用输入的 a1 向量和 Wq 向量相乘,获取 q1 输入向量,这样 q1 分别和 k1 ~ k4 做 dotproduct 就可以获取到当前 a1 输入和每一个其他输入之间的联系了,最后在经过一层 soft-max 函数就可以让结果分布靠近 0 或者 1的值了,这样就可以准确的描述是否关联这样子的显式表达。
我个人的理解是这里的注意层,实际上可以分层很多层的,比如词性,积极词,消极词,语气词这些可以靠训练集去获取划分不同的注意层,然后通过训练集训练处每一个层的 Wk 和 Wq 矩阵,所以说 Wk 和 Wq 是可以让机器学出来的。接下来就是 b1 ~ b4 整个 attention 的输出是怎么获取到的
三、Attention计算流程
这里先看 b2 的输出计算流程,a2 会和其他的输入向量 a1,a3,a4 做关联计算,得到 a'21 , a'22,a'23,a'24 向量,然后再分别和向对应的 vn 向量做点积,这里的v是每个a向量乘以矩阵 Wv 得到的。
所有的计算都可以看成矩阵的计算,我们可以把整个输入 a1~a4 作为一个输入矩阵 I,得到同样长度的举证 Q
同样的道理。矩阵 K 和 V 可以这样子获取
而 a'nm 的计算可以看成是矩阵和向量的相乘,也就是 k1-4 组成一个只有一列的矩阵然后和 q1 向量相乘
所有的 a' 都可以组成一个新的矩阵,如下,这个矩阵里面存的就是每个 q 和 k 的 attention 分布
再对矩阵中的每一个做一下 soft-max
接着在根据 v 向量做矩阵相乘,就可以得到最终的输出 b1~b4
四、Multi-Head多头注意力的计算
多头注意力(Multi-head Attention)是一种在深度学习中用于处理序列数据的机制,最常见于自然语言处理(NLP)任务中,特别是在序列到序列(Sequence-to-Sequence)模型中。它是由"Transformer"模型中引入的一项关键技术。
在传统的注意力机制中,模型会对输入序列中的每个位置都计算一个权重,以便在编码阶段关注不同位置的信息。然而,对于长序列来说,单一的注意力可能不足以捕捉到不同位置之间的复杂依赖关系。为了更好地处理这种情况,多头注意力被提出。
多头注意力通过在注意力机制中引入多个独立的注意力头(Attention Head)来扩展模型的能力。每个注意力头都会学习到不同的注意力权重,从而使模型能够在不同的表示空间中进行关注。具体而言,它通过执行多次自注意力(Self-Attention)操作,每个自注意力操作都产生一个输出,而这些输出最终会被拼接在一起形成多头注意力的最终输出。
如下图也表示了在多头注意力中的计算:
这里使用 2 head 的方式,来计算两个头的注意力,方法是把三个向量 q、k、v 分别计算成两个 head 的注意力向量。然后在去做 attention 计算得到 bi,1。同样的道理我们通过第二个头的向量,可以计算出 bi,2。两者再乘以一个矩阵参数Wo就变成这一层的注意力 bi 了。
在注意力机制中,所有的关联计算都是矩阵进行的,也就是说可以并发的计算其关联性,但是并发也由此带来一些问题,就是没办法知道输入的位置信息。因为有可能输入的位置信息对于整个任务来说会比较重要,比如知道位置信息可以让机器知道某个产生的动词不宜出现在句首。
为了将位置信息引入到模型中,通常会使用位置编码(Positional Encoding)。位置编码是一种特殊的向量表示,用于表示输入序列中每个位置的相对位置信息。位置编码的目的是为了让模型在处理序列数据时能够区分不同位置的单词或标记,因为注意力机制本身并没有显式地包含序列中的位置信息。
在Transformer模型中,位置编码是通过添加一个位置嵌入向量(Positional Embedding Vector)来实现的。这个向量会与输入的词嵌入向量(Word Embedding Vector)相加,从而将位置信息融合到输入的表示中。
在《Attention is all you need》这个篇论文里使用的是正弦余弦编码(Sine and Cosine Positional Encoding):这是Transformer模型中使用的默认方法。它基于正弦和余弦函数的周期性特征来编码位置信息。位置编码向量的每个维度对应不同频率的正弦或余弦函数,根据位置索引的奇偶性来确定使用正弦函数还是余弦函数。这种编码方式能够让模型学习到序列中不同位置之间的相对距离。
不过 positional encode 也还是一个尚待研究的问题,就是你可以自定义位置表示函数。
上面的计算注意力过程可以用一张图来表示:
操作包括:
- 首先对Q、K、V做一次线性映射,将输入维度均为
- 然后在采用Scaled Dot-Product Attention计算出结果
- 多次进行上述两步操作,然后将得到的结果进行合并
- 将合并的结果进行线性变换
这里每一层 layer 中都会进行无数次"多头注意"的合并,并且通过累加 layer 层数,来提供更多的注意力方向的学习。
总的公式概括如下:
根据公式,在结合上面的操作来解释的话,那么其中第1步的线性变换参数为,,,第4步的线性变化参数为,第3步计算的次数为 h
五、Encoder 和 Decoder
大部分的序列处理模型都采用encoder-decoder结构,其中encoder将输入序列 (x1,x2,...,xn-1,xn) 映射到连续表示 z→=(z1,z2,...,zn-1,zn) ,然后decoder生成一个输出序列 (y1,y2,...,yn-1,yn) ,每个时刻输出一个结果。Transformer模型延续了这个模型,整体架构如上图所示。
Encoder有N=6层,每层包括两个 sub-layers:
- 第一个sub-layer是 multi-head self-attention mechanism,用来计算输入的 self-attention
- 第二个sub-layer是简单的全连接网络。
在每个 sub-layer 中都模拟了残差网络,每个 sub-layer 的输出都是大部分的序列处理模型都采用 encoder-decoder 结构,其中 encoder 将输入序列 (x1,x2,...,xn-1,xn) 映射到连续表示 z→=(z1,z2,...,zn-1,zn) ,然后 decoder 生成一个输出序列 (y1,y2,...,yn-1,yn) ,每个时刻输出一个结果:
其中Sublayer(x) 表示Sub-layer对输入 x 做的映射,为了确保连接,所有的sub-layers和embedding layer输出的维数都相同,也就是 self-attention 层的 layer 层数为,d-model=512。在图中的 Add 标识的是对每一层都会做残差计算,也就是:
在 encoder 之前的输入,其实还需要对属于的词做 embedding。单词的 Embedding 有很多种方式可以获取,例如可以采用 Word2Vec、Glove 等算法预训练得到,也可以在 Transformer 中训练得到。
Transformer 中除了单词的 Embedding,还需要使用位置 Embedding 表示单词出现在句子中的位置。像上面提到的,位置 Embedding 用 PE表示,PE 的维度与单词 Embedding 是一样的。PE 可以通过训练得到,也可以使用某种公式计算得到。在 Transformer 中采用了后者,公式的计算方式也很多,论文中使用的计算公式如下:
其中,pos 表示单词在句子中的位置,d 表示 PE的维度 (与词 Embedding 一样),2i 表示偶数的维度,2i+1 表示奇数维度 (即 2i≤d, 2i+1≤d)。使用这种公式计算 PE 有以下的好处:
- 使 PE 能够适应比训练集里面所有句子更长的句子,假设训练集里面最长的句子是有 20 个单词,突然来了一个长度为 21 的句子,则使用公式计算的方法可以计算出第 21 位的 Embedding。
- 可以让模型容易地计算出相对位置,对于固定长度的间距 k,PE(pos+k) 可以用 PE(pos) 计算得到。因为 Sin(A+B) = Sin(A)Cos(B) + Cos(A)Sin(B), Cos(A+B) = Cos(A)Cos(B) - Sin(A)Sin(B)。
将单词的词 Embedding 和位置 Embedding 相加,就可以得到单词的表示向量 x,x 就是 Transformer 的输入。
而在 Decoder 层,其实你发现图中的的过程和 encoder 的过程都差不多,只是多了个 Masked Multi-Head Attention 的机制。
在 Decoder 中也是N=6层,每层包括3个 sub-layers:
- 第一个是Masked multi-head self-attention,也是计算输入的self-attention,但是因为是生成过程,因此在某个时刻 Ti 的时候,大于 Ti 的时刻都没有结果,只有小于 Ti 的时刻有结果,因此需要做Mask
- 第二个sub-layer是全连接网络,与Encoder相同。
- 第三个sub-layer是对encoder的输入进行attention计算。
同时 Decoder 中的 self-attention 层需要进行修改,因为只能获取到当前时刻之前的输入,因此只对时刻之前的时刻输入进行attention计算,这也称为Mask操作。
六、Add & Norm
在看这部分的时候,我是非常懵逼的。完全看不懂这一层是个什么东西。所以读者在理解这一层的内容的时候可能需要参考其他的一些资料。
Add & Norm 层由 Add 和 Norm 两部分组成,其计算公式如下:
Add 指 X+MultiHeadAttention(X),是一种残差连接,通常用于解决多层网络训练的问题,可以让网络只关注当前差异的部分,在 ResNet 中经常用到。因为在残差网络没有被应用之前,人们会发现一个训练的怪异现象,就是训练的次数少的模型比训练次数多的模型效果更好。因为其实每一层求解的梯度,随着层数越多,它会越来越小,如果深度较深,容易出现很多个小于1的数连乘,梯度消失(梯度趋近0)。而把输入加入到输出再经过一个 F(x) 中的话,无论连乘是否会造成梯度消失,都有一个1作为保底,保证梯度不会消失。在做梯度后向传播时,即使长路梯度消失造成靠前的神经元参数得不到优化, 也可以通过 identity=X 抄近路优化。所以能使较深的网络训练起来。
Norm指 Layer Normalization,通常用于 RNN 结构,Layer Normalization 会将每一层神经元的输入都转成均值方差都一样的,这样可以加快收敛。
七、总结
Transformer 与 RNN 不同,可以比较好地并行训练。 Transformer 本身是不能利用单词的顺序信息的,因此需要在输入中添加位置 Embedding,否则 Transformer 就是一个词袋模型了。
Transformer 的重点是 Self-Attention 结构,其中用到的 Q, K, V矩阵通过输出进行线性变换得到。
Transformer 中 Multi-Head Attention 中有多个 Self-Attention,可以捕获单词之间多种维度上的相关系数 attention score。
Reference
[1] https://github.com/Kyubyong/transformer
[2] https://baijiahao.baidu.com/s?id=1651219987457222196&wfr=spider&for=pc
[3] https://arxiv.org/abs/1706.03762
[4] https://www.bilibili.com/video/BV1v3411r78R?p=3&vd_source=122a8013b3ca1b80a99d763a78a2bc50
[5] https://www.bilibili.com/video/BV1bV41177ap/?spm_id_from=333.880.my_history.page.click
[6] https://mp.weixin.qq.com/s?__biz=MzU1NTU3Njg4Mw==&mid=2247484435&idx=1&sn=64db5d053b967600546cecb8e22bf23a&chksm=fbd37f4fcca4f659ff16bfe04012bf3d59dae2c600502fa3b76db1f2ac7fd48f339ed7480008&scene=21#wechat_redirect