零基础入门深度学习(10) - Transformer (3/3)

时隔八年,再次提笔续写这个系列。毫无疑问,智能时代已经到来,AGI(通用人工智能)似乎也不再是遥不可及的梦想。大模型,作为目前最接近实现AGI的技术,甚至已经可以写代码了!这对于程序员真是数十年未有之大变局,因此学好、用好大模型,就将一步跨过JavaScript、Java、C++直接走到鄙视链的顶端,用自然语言编程,成为超级程序员。希望这个系列帮助你快速从零基础达到真入门级水平,理解大模型才能用好大模型。零基础意味着你不需要太多的数学知识,只要会写程序就行了。虽然文中会有很多公式你也许看不懂,但同时也会有更多的代码,程序员的你一定能看懂的(我周围是一群狂热的Clean Code程序员,所以我写的代码也不会很差)。

往期回顾

在前面的文章中,我们介绍了全连接神经网络、卷积神经网络、循环神经网络等,可以它们看做是不同结构的神经网络。本文将继续从结构这个角度出发,介绍一种新的、极其重要的神经网络结构:Transformer。从时间来看,它也不是那么新,毕竟是2017年出现的,到现在(2025年)也将近十年了。然而,没有一个更新的网络结构比它还成功,也说明其结构设计是极为优秀的。Transformer出现后,用了5年左右的时间,逐渐取代卷积神经网络成为当红一哥,又用另外5年开辟了一个全新的时代:大模型时代。可以说,目前所有的大模型,都是以Transformer结构为基础的各种变体。如果您刚刚开始学习AI,可以尝试快速跳过Transformer之前的部分,把主要的精力放在Transformer的学习上。

由于Transformer内容较多,为了保持一个良好的节奏,我将整个内容分成三个部分。第一部分比较简单,是一些基础知识的介绍;第二部分重点讲述transformer的注意力机制,这也是它的核心部分;第三部分讲述模型的整体结构,以及训练和推理。

Transformer整体结构

Transformer总体上采用的是编码器+解码器的结构,这是当时(2017年)解决seq2seq(序列到序列)问题最好的架构。机器翻译就是一个典型的seq2seq问题,这类问题的重点是输入序列Token和输出序列Token不存在严格的一对一关系。例如英译中翻译,输出的中文词和输入的英文单词并不存在严格的一一对应关系,且两个序列长度也不一样。因此,必须整体性的理解输入序列,以及整体性的生成输出序列,而不能把序列的每个Token拆出来单独处理。故而,称其为序列到序列问题。

编码器+解码器结构正是为这种seq2seq问题而设计的(当然,今天这种结构的应用范围已经远不止seq2seq,我们将在未来的文章中陆续介绍),总的来说,编码器负责输入序列的理解,即将输入序列映射到语义空间的一系列Token中;解码器负责输出序列的生成,即以语义空间的一系列Token为前置条件,生成输出序列的Token。这种结构的信息流如下图所示:

Transformer的模型结构遵循上述原理,如下图所示:

可以看到,Transformer的架构比之前我们介绍的CNN、RNN都要复杂的多(这也是我们将其分成三篇文章来介绍的原因),但有了之前两篇文章学习的基础,理解这个模型结构并不困难,我们接下来将逐个部分拆解介绍。

编码器结构

编码器负责接收输入序列inputs,输出是编码到语义空间中的一系列Tokens,编码器的输入和输出Tokens 数量相等。首先,inputs经过Input Embedded,成为词向量的序列。然后,按照每个Token的位置不同加上对应的位置编码 Positional Encoding,成为后续Transformer层的输入。这些操作具体内容我们已经在本系列的第一篇文章中详细介绍过了,不再赘述。

编码器的层由Multi-Head AttentionAdd & NormFeed Forward等子层组成。其中,Multi-Head AttentionFeed Forward我们已经在本系列的第二篇文章中的多头注意力按位置前馈网络两节中详细介绍过了,不再赘述。我们下面详细介绍一下Add & Norm,它的计算公式是:
\text{AddNorm(x)} = \text{LayerNorm}(x + \text{Sublayer}(x))

这里\text{Sublayer}(x) 指代任意子层,如Multi-Head AttentionFeed ForwardAdd & Norm包含了两个重要的设计:残差网络层归一化,接下来我们分别介绍。

我们看到编码器的每个子层都采用了残差网络设计,即子层的输入和输出之间有直连。这种设计使得子层学习的并不是输入到输出的映射,而是输入和输出的残差的映射,即:
Y = X + \text{Sublayer}(X)

因此,
\text{Sublayer}(x) = Y - X

这种设计最早出现在ResNet中,它的优势是在训练时可以将梯度通过直连路线直接传递到对应的组件,从而根本上解决了深度神经网络中的梯度消失问题。在未出现残差网络之前,最深的深度神经网络只能达到20层左右,超过这个深度,就会由于梯度消失导致网络的低层得不到训练,但残差网络的深度却可以达到100层以上。因此Transformer也借鉴了ResNet这个优秀的设计。

层归一化对模型的训练收敛是不可或缺的。某一层输出的变化往往导致下一层输入之和的强相关变化,尤其当使用ReLU单元时,其输出可能产生显著波动。这表明,若固定每层输入之和的均值与方差,可缓解协变量偏移问题。通过加入层归一化算子,可以保证训练稳定性,加速模型收敛,以及减少模型参数初始化的影响。层归一化的计算公式如下:
\begin{align} \text{LayerNorm}(x) &= \frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}*\gamma+\beta \\ \mu &= \frac{1}{Dx}\sum_{i=1}^{D_x}x_i \\ \sigma^2 &= \frac{1}{Dx}\sum_{i=1}^{D_x}(x_i-\mu)^2 \end{align}

其中 \mu 是单个Token所有维度的均值,\sigma 是单个所有维度的标准差。因此,当序列长度为 S 时,需要计算的 \mu\sigma 都是S个。\gamma\beta 是模型的可学习参数,对归一化后的Token的每个维度进行仿射变换(缩放和平移),使得归一化后的数据分布,更符合激活函数中的非线性变化。因此,当每个Token的维度为 D_x 时,\gamma\beta 数量都是 D_x 个。

LayerNorm示例代码如下:

import numpy as np

# 维度定义
S = 3          # 序列长度
Dx = 4         # 输入维度
eps = 1e-5

# 模型可学习参数定义
W_gamma = np.ones(Dx)
W_beta = np.zeros(Dx)

# 输入定义
X=np.array([[0.312, 0.395, -1.343, 2.102],
       [1.232, 2.567, -0.123, 0.838],
       [2.134, -3.123, 0.123, -0.271]])
       
       
def layer_norm(x):
    # Layer Normalization层实现
    mean = x.mean(-1, keepdims=True)
    std = x.std(-1, keepdims=True)
    y = (x - mean) / (std + eps)
    output = y * gamma + beta
    return output
    

# 测试
print(layer_norm(X))

将上述子层串联在一起,就可以得到一个编码器层,一个完整的Transformer编码器包含 N_x 个编码器层。

解码器结构

解码器负责生成新的序列。Transformer解码器按照自回归的方式工作:解码器根据当前输入序列 token_{[1..i-1]} 输出下一个 token_i,再将token_i加入到输入序列中,解码器根据新的输入序列 token_{[1..i]} 输出下一个 token_{i+1},如此循环直到整个序列生成完毕。如下图所示:

注意里面有两个特殊的Token:[start]和[end],前者作为解码器输入的第一个Token,标识解码开始,解码器输出新序列的第一个Token;后者是解码器输出的最后一个Token,标志整个序列生成完毕。

解码器的结构和编码器非常相似,除了两点不同:第一点不同,解码器只能根据已经生成的Token去生成新的Token,因此需要使用因果注意力机制,即用Masked Multi-Head Attention替代了编码器中的Multi-Head Attention因果注意力机制已在系列的上一篇文章中已经详细介绍过,本文不再赘述。

第二点不同,解码器在生成每个Token时,需要将编码器的输出序列也作为输入序列的一部分,因此需要一个额外的Multi-Head Attention子层实现编码器和解码器的交叉注意力机制。具体来说,解码器根据上一子层Masked Multi-Head Attention的输出投影 \mathbf{q} 向量,根据编码器对应层的输出投影 \mathbf{k}\mathbf{v} 向量,再根据注意力公式进行计算,从而使得解码器输出的Token可以融合编码器输出Token的信息。

一个完整的Transformer解码器包含 N_x 个解码器层,在Transformer的设计中,编码器和解码器层数相同。这样,每个解码器层都可以将对应的编码器层输出作为输入,从而可以同步顶层、中层和低层信息。

解码器最终如何输出词表中的词呢?当解码器的最后一个Transformer层输出后,这个输出经过一次线性变换Linear,将维度从词向量维度 Dx 变为词表的大小 V,再经过带温度Softmax归一化后,使之产生概率的意义。即最后的输出Output Probabilities是一个向量,其维度是词表大小 V,这个向量的每个元素对应词表中的词的预测概率。对这个概率进行采样,就可以输出对应的词。

带温度的Softmax公式如下:

\text{Softmax}(x_i) = \frac{e^{x_i / T}}{\sum_{j=1}^{n} e^{x_j / T}}

其中:

  • x_i 是词表中第 i 个 词对应的logit。
  • T 是温度参数。
  • n 是词表的总数。

当温度 T 接近 0 时,概率分布会变得非常尖锐,模型的输出的确定性加强。特别的,当T=0时,模型只会输出具有最高logit的词,输出是完全确定的。当 T 取值增大时,概率分布会变得更加平滑,模型输出的随机性加强,会表现出更多的创造力。

Transformer与RNN的对比

自注意力机制同RNN相比,拥有两个巨大的优势:

  • 更强的长距离依赖能力。学习长距离依赖能力的一个关键因素是前向和反向信号在网络中需要穿越的路径长度。这些路径在输入和输出序列的任意位置组合之间的长度越短,学习长距离依赖就越容易。自注意力层由于是Token之间两两直接计算权重,因此是一步操作连接所有到位置,而循环层则需要 O(n) 顺序操作。
  • 并行训练能力。自注意力机制可以在训练时同时并行计算所有Token,而循环层每计算当前Token都要先计算出上一个Token,无法并行计算。因此,虽然自注意力层的计算量大于循环层,但由于训练时可以并行计算,反而更加高效。
  • 更具有可解释性。通过检查模型中的注意力分布,可以观察到各个注意力头明显学会了执行不同的任务,许多还表现出与句子的句法和语义结构相关的行为。如下图所示:

因此,Transformer要比RNN强大的多,可以处理非常复杂的任务。例如,现在最强的Transformer模型,已经可以生成上千行的代码,最长可处理的序列可长达上百万个Tokens。当然,Transformer也付出了代价,同RNN向比,其劣势主要包括:

  • 计算复杂度与序列长度成平方关系。由于Transformer对序列中每个Token都要两两计算注意力权重,导致其计算复杂度与序列长度是 O(n^2),这是目前限制Transformer处理更长序列的主要瓶颈。而RNN的计算复杂度则为O(n),因此在生成或理解长序列时,RNN的算力需求要小得多(但实际上没什么用,因为RNN对长距离依赖学习能力太差,导致它处理不了长序列)。

Transformer和RNN的故事远没有结束,结合二者优点,很可能是未来新模型结构的出路。

小节

Transformer出现后,经过不断的发展,现已成为最重要的模型。从2017年开始,总体沿着三条路线发展,如下图所示:

  • 效率优化路线:期望解决 O(n^2) 计算复杂度问题,主要尝试与RNN相结合,既能保留并行训练、长距离建模能力强的优点,又能像RNN一样将计算复杂度降至 O(n)
  • 长文本路线:期望能够支持更长的上下文,将上下文长度扩展至100万以上,直至无限长度。
  • 多模态路线:在同一个模型Transformer中,支持更多的模态。目前的多模态模型已经可以支持声音、图像、文本、视频的输入和生成,未来还可能支持更多的模态。

我们会在后续的文章中更详细的介绍相关信息,敬请期待。

参考资料

  1. Attenion Is All You Need
  2. Transformer Normalization

相关文章

零基础入门深度学习(8) - Transformer (1/3)
零基础入门深度学习(9) - Transformer (2/3)
零基础入门深度学习(10) - Transformer (3/3)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容