从函数到神经网络
事实上函数就是一种变换,对数据进行变换得到我们所需要的结果。
符号主义和联结主义
早期的人工智能->符号主义,即用精确的函数来表示一切。但是很多时候,我们没办法找到一个精确的函数来描述某个关系,退而求其次选择一个近似解也不错,也就是说函数没必要精确的通过每一个点,它只需要最接近结果就好了,这就是联结主义。
激活函数
但是当数据稍微变化一下,出现曲线的时候,简单的线性函数就没办法解决这个问题了。那我们的目标就是将线性函数转为非线性函数,这可以通过套一个非线性函数来做到,比如平方、正弦函数、指数函数。这就是激活函数。
常用的激活函数如ReLU其实并不复杂,但是就能起到很好的效果。
这还不够,正常情况下我们不会只有一个输入x,再者只有一个激活函数可能不会达到理想的结果,所以可以无限套娃(不止是两层)
通过将多个输入进行整合,我们在激活函数外部引入一层线性变换,再施加激活函数,并可将这一结构逐层嵌套。
采用这种方式,能够逐步构建出高度复杂的非线性关系,理论上具备逼近任意连续函数的能力。
神经网络
当网络层数过多时,结构会显得过于复杂。为了更好地描述这一系统,我们引入“神经元”的概念:图中的每一个小圆圈代表一个神经元。多个神经元相互连接形成的网状结构,称为神经网络。
通常,我们将最左侧接收原始输入
的部分称为输入层,将最右侧产生最终输出
的部分称为输出层。而在两者之间、由多层神经元构成的中间层次,则被称为隐藏层(Hidden Layer),隐藏层的主要作用是对输入数据进行复杂的特征提取和变换。
随着函数的不断嵌套,神经网络在深度上逐步扩展,原本作为输出的部分演变为中间的隐藏层。隐藏层的核心功能是对输入数据进行多层次的非线性变换与特征提取,逐步挖掘数据中深层次的内在规律,为最终的输出提供有力支撑。
从结构上看,就像是一个信号从左向右传播的过程,这一过程被称为前向传播(Forward Propagation)。通过堆叠多个隐藏层以及调整每层中神经元的数量,神经网络能够构建出高度复杂的非线性映射关系。
尽管整个结构看似复杂,但我们最终的目标依然明确而简单:寻找一组参数的近似解——即根据已知的输入 x 和输出 y,反推出所有连接权重 w 和偏置 b,使得网络能够尽可能准确地拟合数据背后的潜在函数。
如何计算神经网络的参数
从前文我们可以得知,其实我们的核心本质还是根据已知的输入 x 和输出 y,反推出所有连接权重 w 和偏置 b
那什么样的w和b是好的呢?我们需要的w和b是够让函数的输出尽可能拟合真实数据真实数据那一组参数。
对于以下图片,我们直觉上看出显然第一个拟合的更好。
那如何用数学语言去描述拟合的好呢?
我们使用真实值
与模型预测值 $$ \hat{y}$$ 之间的误差来衡量单个样本的预测精度,其表达式为:$$|y - \hat{y}|$$为了评估模型在整个数据集上的整体拟合效果,我们将所有样本的误差(即这些“距离”)相加,得到预测值与真实值之间的总偏差。这类用于衡量模型预测误差的函数,称为损失函数(Loss Function)——它量化了模型预测结果与真实数据之间的不一致性。
在实际应用中,为了便于数学处理(如求导),我们通常对误差进行平方处理,并去除绝对值,同时对所有样本取平均,从而得到一种广泛使用的损失函数:均方误差(Mean Squared Error, MSE):
从模型参数的角度来看,预测值
是由输入x 、权重 $$ w$$ 和偏置 $$ b$$ 决定的,因此损失函数 $$ L$$ 本质上是关于参数 $$ w$$ 和 $$ b$$ 的函数:$$L = L(w, b)$$我们的目标就是通过调整参数
和 b ,使损失函数 $$ L$$ 尽可能小,从而让模型的预测结果尽可能接近真实值。怎么能让函数值最小呢?
如何让损失函数最小?——梯度下降原理
根据微积分的基本原理,函数的最值通常出现在**极值点**或**边界点**。而极值点的一个关键特征是:**导数为零**。
1. 简单情况:解析求解(适用于线性回归)
当参数较少时(例如在线性回归中只有
和 $ b $),我们可以尝试通过令偏导数为零来求解最优参数:
解这个方程组,就能得到使损失函数最小的参数值。
这种方法对应的是经典的**线性回归**(Linear Regression)——通过寻找一条最优直线来拟合输入
与输出 $$ y$$ 的关系。但这种方法只适用于结构简单、可解析求导的模型。
2. 复杂情况:梯度下降法(适用于神经网络)
神经网络通常是高度非线性的复杂函数,其损失函数维度高、结构复杂,无法直接求解导数为零的方程。此时,我们采用一种迭代优化方法:**梯度下降法**(Gradient Descent)。
我们不需要一步到位地找到最小值,而是从某个初始参数出发,**一步步调整
和 **$ b $**,使得损失函数 $$ L$$ 逐步减小,最终逼近最小值。具体策略是:
调整参数 $ w $,观察损失函数
的变化;
如果增大
导致
增大,说明我们应**减小** $ w $;
反之,若
减小,则当前方向正确。
而损失函数L随着参数w变化而变化的程度的数学表达,就是**偏导数**,
如果
:说明
增大时,$ L $ 会增大 → 想要减小 $ L $,就应该**减小 **$ w $**。
如果
:说明
增大时,$ L $ 会减小 → 应该**增大 **$ w $**。
所以,**让
向 $$ -\frac{\partial L}{\partial w}$$ 的方向变化**,就能使 $$ L$$ 减小,我们要做的就是让w和b不断的往偏导数的反方向去变化。 具体变化的快慢,我们再增加一个系数(学习率)来控制。
所有参数的偏导数组成的向量,称为**梯度**(Gradient),记作:
梯度下降的流程
初始化:随机设置参数
前向传播:计算当前预测值
和损失
反向传播:计算梯度
参数更新:沿梯度反方向调整参数
重复:直到损失收敛或达到最大迭代次数
不断变化w和b使得损失函数不断减小,进而求出最后的w和b,这个过程就叫做梯度下降。
求解偏导数
公示很好理解,偏导数怎么求就成了目前最大的难题?
如果是简单的一元二次函数,求偏导数当然非常简单,但我们都知道,神经网络整体所代表的函数非常复杂,直接求偏导几乎不可能。
虽然函数很复杂,但是层与层之间的关系还是很简单的
用一个简单的例子来说明。要求L对w1的偏导,只需要用链式法则分别求图中的三个偏导,再相乘就好了。而由于我们可以从右向左计算这些偏导数,然后调整每一层的参数,计算前一层的时候用到的偏导数的值,后面也会用到(更左边的层),所以可以让这些值从右向左传播,这个过程就叫做反向传播。
小结:我们通过前向传播根据输入x计算输出y,再根据反向传播计算损失函数 关于每个参数的梯度,每个参数都向梯度的反方向变化一点,这个就是神经网络的一次训练。经过多轮训练使得损失函数足够小,就得到了我们想要的函数。
调教神经网络咋这么难?
上文提到,我们的目标是让 损失函数尽可能小 ,从而使模型的预测函数尽可能接近真实的数据规律。听起来似乎“损失越小越好”?但事实并非如此。
过拟合
看下图可知,从训练数据的角度来看,右边的函数拟合效果更优;但在对新数据进行预测时,其表现可能反而不如左边的模型准确。
这种在训练数据上表现的很完美,在新数据表现的很糟糕的现象就叫做 过拟合(Overfitting),模型把训练数据学得太好了,好到连数据中的噪声、异常值甚至随机波动都一并记了下来。在没见过的数据上表现的能力,我们称为泛化能力。
那我们该怎么解决过拟合呢?很简单,模型太复杂了,这个函数其实只是一个简单的线性函数,选一个简单一点的模型就好了,这就告诉我们,模型也不是越大越好。
与此相对,也可以通过增加训练数据的量来解决这个问题。数据越充足,模型越不容易过拟合。
有一些情况下,我们没有办法直接收集更多数据,可以用原来的数据创造更多的数据(图像旋转、镜像、滤镜、裁剪),这就叫做数据增强,这就能让模型不会因为输入的一点波动而产生很大的结果差异。
训练过程本质上是模型参数不断调整的过程。如果能限制参数的过度变化,避免其走向过于复杂或极端的值,就有助于提升模型的泛化能力。
一个直观的思路是:如果发现模型在训练集上持续变好,但在验证集上的性能开始下降(即可能开始过拟合),我们就提前停止训练。这种方法被称为早停法(Early Stopping)。
通过这种方式,我们可以在模型尚未“学过头”之前及时叫停,从而有效缓解过拟合问题,提升其对新数据的适应能力。
但显然,这方法还是太粗糙了,有没有更精细的方法呢?
我们只需在原始损失函数的基础上,加入对参数本身大小的惩罚项。这样,当参数调整使得损失函数下降的幅度,还不及参数增大所带来的惩罚增幅时,新的总损失函数反而会上升。这意味着该次调整并不合适。
常见的做法是惩罚参数的绝对值之和(称为 L1 正则化),或者惩罚其平方和(称为 L2 正则化)。其中,平方和在参数较大时带来的惩罚更重,因此对抑制大参数的效果更强。这种通过添加惩罚项来控制模型复杂度的方法,统称为正则化。而惩罚力度的强弱,则由一个额外引入的参数——正则化系数——来控制。
类似地,之前我们在梯度下降中引入的学习率,也是一个不通过训练更新、而是人为设定来调控训练过程的参数。这类用于控制学习过程的参数,统称为超参数。正则化系数就是典型的超参数之一,它不参与模型的拟合过程,却深刻影响着模型的泛化能力。
除了上述方法,还有一个简单到令人发指的方式,为了防止让模型过于依赖某几个参数,我们在每次训练时都随机丢弃掉一部分参数,这种方法叫dropout(丢弃)。
现在我们了解了如何避免过拟合,那我们是否能训练出一个非常好的神经网络了呢?不,还有很多问题:
问题 | 描述 | 解决方案 |
---|---|---|
梯度消失 | 神经网络越深,梯度反向传播时越来越小,参数更新困难 | 使用残差网络(ResNet)来缓解深层网络中的梯度衰减问题 |
梯度爆炸 | 梯度数值越来越大,导致参数更新失控 | 使用梯度裁剪(Gradient Clipping)控制更新幅度;结合合理的权重初始化和输入数据归一化,使梯度分布更稳定 |
收敛速度慢 | 可能陷入局部最优解或参数更新过程中出现剧烈震荡 | 采用动量法(Momentum)、RMSProp、Adam 等自适应优化算法,提升收敛速度并减少震荡 |
计算开销大 | 数据规模庞大,完整的前向传播和反向传播计算耗时严重 | 使用小批量梯度下降(Mini-batch Gradient Descent)或优化计算框架(如分布式训练、混合精度训练)来降低开销 |
对此,我们当然也有很多办法,这里简单提一下,留待补充。
神经网络中永远也搞不明白的矩阵和CNN
矩阵表示和CNN
现在回到开始,假设我们有一个这样的神经网络,虽然参数不多,但是看起来已经很麻烦了,这时候可以考虑用矩阵来简化一下。
这样原本复杂的式子就可以表示为Y = g(WX+b)
与此同时,当神经网络的层数越来越多的时候,也需要用合适的方法来表示。我们把输入层用a[0]表示,中间的层就用a[1]、a[2]来表示,以此类推。
第一层、第二层和通式如下所示。这样不仅我们看起来更简单、更抽象了,更有利于研究更深的问题了。同时,矩阵运算相比之前的式子,可以更好的利用GPU的并行运算特性,能加速神经网络的训练和推理过程。
我们再来看之前的神经网络,可以发现每一个节点都和前一层的所有节点相连接,这个并非神经网络所必需的,而这种连接方式叫做全连接。
全连接层有一个显而易见的缺点,对于下面的这个例子,输入30*30的灰度图像,第一层的神经元数量是900,下一层神经元的数量是1000,在一个全连接层之后就需要90万个参数,这太庞大啦。
并且这还只是把图像平铺开,不包含每个像素之间的位置关系,如果图片稍稍平移或改变一些局部信息,但所有的神经元都会和之前不一样,这就是不能很好的理解图像的局部模式。
那怎么办?让我们随便在图像中取一个3×3的块,将他的灰度值与另一个固定的矩阵做运算(对应位置相乘,最后求和),遍历整张图片的所有位置,得出的数值形成一个新的图像,这种方式就叫做卷积运算。刚刚给出的矩阵就叫做卷积核。
卷积核不是一个新的概念,它早就被应用于传统图像处理领域,不同的卷积核可以达到不同的处理效果(轮廓、锐化、模糊)。
但区别在于,图像处理中的卷积核是已知的,神经网络中我们用到的卷积核是未知的,它同样由参数构成,是被训练出来的一组值。
回到经典的神经网络结构,其实就是把一个全连接层替换为了卷积层,不仅能减少参数的数量,还能更有效的捕捉到图像中的局部信息。从公式上看,也就是把原来的矩阵标准乘法(叉乘)替换为了卷积运算。
这样我们的神经网络示意图就能简化为新的形式,从很多个小圈,变成一层一层。
在图像识别的卷积网络中,通常还会多出一层池化层,池化层的作用是降低维度的同时保留主要特征,减少计算量。图中的卷积层、池化层、全连接层都可以有多个,而这种适用于图像识别领域的神经网络结构就叫做卷积神经网络(Convolutional Neural Network,CNN)。
卷积神经网络的训练过程很容易可视化,我们可以观测每一轮从原始图像中提取了什么样的特征,虽然这些都是中间隐藏层的事情,但是却能神奇的观察出一些实际意义。
卷积神经网络依旧有它的局限性,一般来讲它只适用于处理静态数据,对于时间序列、文本、视频、音频等动态数据,就需要其他的神经网络结构了。
语言居然可以被计算出来?从 RNN 到 Transformer
给你一句话,让你判断某个词的褒贬,如果用一个函数来实现这个功能,该怎么做呢?
先别急,先让我们考虑如何把这些文字作为参数输入,变成计算机能够识别的数字,这个过程就叫做编码。
具体的编码方式有很多种:
一种简单粗暴的方法:每一个文字或词组都用一个数字来代表,建一个非常大的映射关系表
这样有几个显而易见的缺点,第一,只用一个数字表示,不仅要建的表很大,维度也很低(只有一维),第二,数字和数字之间无法表示字与字、词与词之间的联系,对语言理解没有任何意义。
为了解决维度低的问题,有人提出了one-hot编码,即准备一个维度非常高的向量,每个字只有向量中一个位置是1,其余全是0。虽然维度低的问题被解决了,但是维度好像又太高了,非常稀疏,向量之间都是正交的,词和词之间仍然无法找到相关性。
那有没有能解决以上两种问题的方法呢?有的,这种方法就是词嵌入。
通过词嵌入的方式得到的词向量,维度不高不低,每个位置可以理解为一个特征值,但这个特征是通过训练得到的,我们并不知道代表着什么。那这种方式如何表示词与词之间的语义相关性呢?可以用两个向量的点积或余弦相似度来表示向量之间的相关性,进而表示词语之间的相关性。
这样就将自然语言之间的联系转为可以用数学公式计算的方式。 同时,一些数学上的计算结果可能反映出一些很微妙的关系,例如一个训练好的词嵌入矩阵,很可能使得桌子-椅子 = 鼠标 - 键盘。
把所有词向量组成一个大矩阵,这个大矩阵就叫做嵌入矩阵,每一列表示一个词向量。矩阵中的值由训练得到,比较经典的方法是word2vec,不展开讲解(挖坑)。
虽然这样表示的维度比起one-hot已经大大下降,但是也超过了人能直接理解的二维、三维,我们管这些向量所在的空间叫做潜空间。 我们无法理解潜空间中的位置关系,词和词的关系虽然可以用点积或者余弦向量的形式显示出来,但我们需要一些方法能够把潜空间降维至2-3维,方便我们直观看到词与词之间的关系。
这样我们可以用词嵌入的方法将文本转为数据,放入输入端的神经元中了。
但这样就可以了吗?举个例子,下图中左边的五个词转为5个词向量,每个词向量假设为300维度,那么输入层就要有1500个神经元,这样当然是可行的,但是有两个新问题:
- 输入层太大了,并且长度不固定,是变长的;
- 无法体现词语的先后顺序,仅仅是把它平铺展开形成了一个非常大的向量。
这就像之前图像识别的时候,我们把一张图片所有的像素点展开成一个大向量,一股脑送入输入层一样,既增加了神经元的个数,有无法体现词之间的关联。在CNN中,我们可以通过卷积的方式来提取图像特征,那nlp领域中,我们可以通过什么办法,既能解决词语之间的先后顺序问题,又能降低输入层的参数量呢?
回到经典的神经网络,但是不是一次输入一句话,而是输入X是一个词,输出Y就是这个词是褒义还是贬义,当然这里的X、W都是矩阵,之后不再展开。
第二个词来的时候我们用同样的函数得到结果,我们用尖括号表示是第几个词,这样就能得到顺序关系。可以发现在第二个词的计算过程中,完全没有让第一个词的任何信息参与进来,怎么办呢?
我们可以让第一个词计算之后,先别输出结果Y,输出一个隐藏状态H1,然后再经过一次非线性变换,得到输出Y1。
接下来,这个第一个词得到的隐藏层H1和第二个词的输入X2一起参与运算,先得到H2,再然后再经过一次非线性变换,得到输出Y2,以此类推。 这样前面一个词的信息就能不断的往下传递,直到传到最后一句话的最后一个词中,就能把所有词的信息都囊括起来了。
当然这里的W值并不是一样的,有专门针对词向量的Wxh矩阵,有专门针对隐藏状态的Whh矩阵,以及最终计算出结果的Why矩阵,当然,偏置项b也是如此。
把这个图简化一下,就能得到循环神经网络RNN。 这个RNN模型就具备了理解词与词之间先后顺序的能力,可以判断一句话中各个单词的褒贬词性,还能给出一句话,不断生成下一个字,以及完成翻译等自然语言处理工作。
可以从矩阵和公式的角度来看这个计算过程,从公式来看,和传统的神经网络相比,RNN只是多了一个前一时刻的隐藏状态而已。
那么RNN是否就完美了呢?当然不,RNN依旧存在两个问题:
1、信息会随着时间步的增多而逐渐丢失,无法捕捉长期依赖,而有的语句的关键信息恰好在很远的地方
2、RNN必须顺序处理,每个时间步必须依赖上一个时间步的隐藏状态的计算结果
虽然有一些方法在改进以上两点问题,但是还不够好(已过气),那么是否有一个可以彻底抛弃按顺序计算的新方案呢?
那就是Transformer!
Transformer 其实是个简单到令人困惑的模型
用神经网络进行文字翻译,先用词嵌入的方式,把每个词都转换成词向量,假设每个词向量纬度就是6。
如果直接丢到一个全连接神经网络中,那每个词都没有上下文信息,并且长度只能一一对应,显然不行。
如果用循环神经网络RNN,又面临串形计算,而且如果句子太长,也会导致长期依赖困难的问题,也不ok。
前面说的方法都不太行,那到底什么方法可行呢?
单头注意力(Single-Head Attention)
首先,为了让输入包含每个词之间的位置信息(前后顺序等),给每个词一个位置编码,表示这个词在整个句子中出现的位置。把这个位置编码加到原来的词向量中,现在这个词就有了位置信息。
但是现在每个词中没有其他词的上下文信息,注意不到其他词的存在。我们用Wq矩阵和第一个词向量相乘得到维度不变的向量q1,用Wk矩阵和第一个词向量相乘得到k1,用Wv矩阵和第一个词向量相乘得到v1,这里的Wq、Wk、Wv矩阵都是可以通过训练得到的权重值。
接着对其他词向量也和相同的WQKV矩阵相乘,分别得到自己对应的向量。
当然,实际GPU运算时,是通过拼接而成的矩阵做乘法,不是一步一步分开计算的,得到的直接就是所有词向量的qkv,就是三个大矩阵(Q、K、V)。
不过为了方便理解,还是拆成一个个的词向量来看。
现在我们的词向量已经通过线性变换映射为了QKV,
让q1和k2做点积得到a12,这代表在第一个词的视角里第一个词和第二个词的相似度,同理,和k3做点积得到a13,代表第二个词的视角里第一个词和第三个词的相似度,以此类推。
拿到相似度系数 a11、a12、a13、a14 后,将它们分别与对应的词向量 v 相乘,再将结果相加,得到向量 a1。这个过程可以理解为:从第一个词的视角出发,根据其他词与它的相似度高低赋予相应的权重,加权融合所有词的词向量。最终得到的 a1 就是在第一个词的视角下,聚合了整个上下文信息的表示。
同理,其他几个词也按照这种方式得到a2、a3、a4,这代表每个词都把其他词的词向量,按照和自己的相似度权重,加到了自己的词向量中。
通过上面的计算过程,我们把原来词向量变成了一组新的词向量,这组新的词向量中每一个都包含了位置信息和其他词上下文信息,这就是注意力机制attention做的事情。
总结一下,单头注意力机制就是从输入X通过权重矩阵得到QKV,然后先用Q和K相乘得到一个相似度系数矩阵,然后再和V相乘,最终得到包含上下文信息的词向量矩阵。(当然我们忽略掉了缩放、掩码和一层softmax处理
多头注意力(Multi-Head Attention)
但是两个词的关系并不是固定的,对于注意力机制来说,如果只通过一种方式计算一次相关性,灵活性就太低了。
所以我们可以增加权重矩阵Wq、Wk、Wv的数量,把之前得到的QKV通过两个权重矩阵计算得到两组新的QKV,给每个词两个学习机会,每组QKV称为一个头。
再次通过之前的注意力计算方式,得到每个头对应的输出向量 a。然后将这些来自不同头的输出向量拼接(concatenate) 起来,就得到了一个融合了多个视角信息的综合表示。
在我们刚刚的例子中,使用了两个头,这种让模型从多个子空间并行学习不同特征的机制,就称为多头注意力(Multi-Head Attention)。
📝 补充说明:虽然有些教学视频或图示为了便于理解,会画成“先计算一次 QKV,再分到不同头进行第二次变换”,给人一种“两步走”的印象,但实际上——每个头的 Q、K、V 都是直接从原始输入通过各自独立的权重矩阵一步投影得到的,并没有真正的“中间 QKV”作为输入再做第二步。
但从数学上看,多次线性变换可以合并为一次矩阵运算,因此即使理解为“先统一映射,再拆分到各头”,只要最终的参数结构等价,结果是完全一致的。这也说明了为什么用一个大矩阵一次性计算所有头的 QKV,在功能上等价于多个小矩阵分别计算。
以上就是Transformer架构最核心的东西啦,attention is all you need!
让我们对照论文中的架构图来看看:
首先第一步就是通过词嵌入的方式转换成词向量矩阵 input Embedding。
第二步加入位置信息 对应图上 Postional Encoding
第三步 经过多头注意力机制的处理,输入的矩阵维度和输入没有变化,给每个词向量增加了上下文信息,对应 Muti-head attention
第四步 添加了残差网络和归一化处理,是为了解决梯度消失,并且让分布更加稳定而做的优化,对应图上Add&Norm
我们可以看到多头注意力机制在transformer架构里重复出现了很多次,可见其重要性。
看看transfomer里的多头注意力机制,首先QKV分别经过线性变换拆分成多组,相当于给了多次机会学习到不同的相似度关系,依次经过注意力机制运算后,把预算结果拼接起来,当然最后的多头结果并不是单纯拼接,还需要再次进行一次权重矩阵的乘法(也就是线性变化)。
现在看这两个公式,
所谓注意力机制运算就是QK矩阵相乘,经过缩放
,再经过softmax乘处理,最后和V相乘
对于多头情况,就是先将q、k、v矩阵经过多个权重矩阵,拆分到多个头中。分别经过注意力机制的计算,最后合并起来,再经过一次
矩阵运算得到输出。回过头看看transformer的图,左边的部分叫编码器,右边的部分叫做解码器
当我们要对一句话进行翻译时,我们训练这个时间网络的过程是:
输入源文本:首先,将需要翻译的句子作为输入。
词嵌入:通过词嵌入(Word Embedding)技术,将输入的文本序列转换为对应的向量表示。
位置编码:由于模型本身不包含循环或卷积结构,无法感知序列顺序,因此需要加入位置编码(Positional Encoding),为向量注入词语在序列中的位置信息。
编码器处理:
- 向量序列进入编码器(Encoder)。
- 经过多头自注意力机制(Multi-Head Self-Attention)处理,该机制允许模型在不同位置关注输入序列的不同部分。
- 注意力输出与原始输入进行残差连接(Residual Connection),然后进行层归一化(Layer Normalization)。
- 接着,通过一个全连接前馈网络(Feed-Forward Network)进行进一步处理。
- 该网络的输出再次与输入进行残差连接并进行层归一化。
- 编码器最终的输出(即经过处理的序列向量)将作为后续解码器中“Encoder-Decoder Attention”层的键(Key)和值(Value)矩阵(KV矩阵)。
解码器处理:
- 解码器(Decoder)的输入是目标语言序列(翻译结果),在训练时通常是完整的目标句子(或其移位版本)。
- 同样经过词嵌入和位置编码,得到向量表示。
- 首先经过一个带掩码的多头自注意力层(Masked Multi-Head Self-Attention)。这里的“掩码”至关重要:它确保在生成第
t
个位置的输出时,模型只能“看到”第t
个位置之前(包括t
)的输入,而无法访问后续位置的信息。这模拟了实际推理过程——翻译是逐词生成的,生成当前词时不应知晓未来的词。
- 该层的输出同样经过残差连接和层归一化。
- 然后进入“Encoder-Decoder Attention”层。在此层中,解码器上一步的输出作为查询(Query)矩阵(Q矩阵),与编码器提供的 KV 矩阵进行注意力计算,从而使解码器能够关注源句子中的相关信息。
- 此注意力输出再经过残差连接和层归一化。
- 最后,通过一个全连接前馈网络,并再次进行残差连接和层归一化。
输出预测:
- 解码器最终的输出向量,会通过一个线性变换层(Linear Layer),将其维度映射到目标词汇表(Vocabulary)的大小。
- 接着,应用 Softmax 函数,将线性层的输出转换为一个概率分布,表示下一个最可能出现的词在整个词表中的概率。
- 在推理阶段,我们选择概率最高的那个词作为当前时刻的翻译输出。此过程重复进行,直到生成完整的翻译句子。