文章目录[隐藏]
- 前言
- 本篇前置知识
- 编码器
- 插:RNN(循环神经网络)
- 插:Transformer的词表来源
- 插:seq2seq和RNN的关系
- 多头注意力机制
- 残差连接(Add)和层归一化(Norm)
- 插:计算举例
- 前馈神经网络
- 编码器总结
- 解码器
- 总结
前言
其实很多时候不是语言学家在AI领域不起作用,而是训练AI的过程是和机器对话的过程。我们需要服从机器才能让它好好的输出我们想要的文字。这个时候并不是人类手动规划的语法起作用,而是机器自身的那一整套逻辑在起作用。这篇文章的目的就是讲清楚整个逻辑体系。
本文基本上按照下面这张图进行叙述,也会穿插的讲述一些古老的技术(RNN等),感兴趣的读者可以详细阅读。具体代码和补充知识可以参考 《动手学深度学习》第二版这本书比我讲的更加详细更好,但是对于数学和计算机能力有一定要求。我并不是想跟风,仅仅是为了记录下我从语言学的角度切入到理解整个LLM的过程,给后世文科同学看懂这些提供一个参考。建议带着自己的语言学基础知识阅读,看看哪些是能对应的,哪些又是被直接忽略的。「小提示:这里面基本上没有出现主谓宾定状补。」
人话解释是用来辅助理解的,看懂专业解释可以不看这个部分。同时插入的章节也可以不看,但是理解不了就再看一下吧。「AI猫娘是这么说的喵~」
本文较长,一次性看不完可以慢慢看。参考文献在最后面,注释在标题末尾,很多东西必须要靠插入和注释才能基本上说清楚。有些翻译可能有点奇怪,通过英文理解吧。
「如果不打算看公式直接看文本也行。」最好用电脑访问,手机的浏览器可能Katex渲染不出来。
省流:也就是讲完下面这张图。
这张图展示了Transformer模型的编码器(左侧)和解码器(右侧)的结构。
编码器部分(左侧)
- 输入嵌入 (Input Embedding):首先,输入数据被转换为嵌入向量,表示为图中的粉色方块。
- 多头自注意力机制 (Multi-Head Attention):接下来,嵌入向量通过多头自注意力机制,允许模型关注输入序列的不同部分。
- 残差连接(Add)和层归一化(Norm):注意力机制的输出与输入通过“跳跃连接”相加,然后残差连接(Add)和层归一化(Norm)。
- 前馈神经网络 (Feed Forward):然后,经过一个前馈神经网络,进一步处理信息。
- 再次残差连接(Add)和层归一化(Norm):前馈神经网络的输出再一次经过加和归一化处理。
解码器部分(右侧)
- 输出嵌入 (Output Embedding):类似编码器部分,解码器也有一个输入嵌入层,这次输入的是目标序列的嵌入表示。
- 掩码多头自注意力机制 (Masked Multi-Head Attention):解码器的第一个注意力层是“掩码多头自注意力机制”,它只关注目标序列中已经生成的部分,防止模型看到未来的词。
- 残差连接(Add)和层归一化(Norm):和编码器部分一样,注意力机制的输出也经过残差连接(Add)和层归一化(Norm)。
- 多头注意力机制 (Multi-Head Attention):然后,解码器有一个额外的多头注意力层,它关注编码器的输出和解码器自身的输入。
- 残差连接(Add)和层归一化(Norm):同样地,经过注意力机制后的输出再一次经过残差连接(Add)和层归一化(Norm)。
- 前馈神经网络 (Feed Forward):再接着是前馈神经网络处理信息。
- 残差连接(Add)和层归一化(Norm):前馈网络的输出再一次经过残差连接(Add)和层归一化(Norm)。
- 线性层 (Linear) 和 Softmax:最后,经过线性变换和Softmax函数生成最终的输出概率分布。
总结一下,编码器和解码器的结构类似,但解码器相比编码器多了一个用于处理掩码目标序列的自注意力层,以及与编码器输出交互的注意力层。通过这种架构,Transformer模型能够同时关注输入序列中的不同部分,并生成高度相关的输出。这样的话你就能看到AI说的话了。
希望读者在这里别被吓到了,可能结构有点复杂,请带着问题继续阅读吧,我会一一解释清楚的。
本篇前置知识
这部分主要是进行一个简单介绍,心里有个底即可,看不懂也没啥。部分内容后文会展开讲解。
省流:全部当数学计算都行,反正就是把数据揉来揉去。
0. 把上一篇文看完。 传送门 语言·计算&LLM——5. 语言数字化
1. 神经网络(Neural Network)
神经网络是一种模仿人类大脑的计算系统。它由许多简单的计算单元(称为“神经元”)组成,这些神经元通过连接(称为“权重”)相互连接。每个神经元接收输入信息,处理这些信息,然后将结果发送到下一个神经元。
你可以想象神经网络像是一个由多个层组成的巨型团队。每一层都有许多成员(神经元),这些成员接收到的信息(输入)来自前一层,他们将这些信息加工处理,然后把结果传递给下一层的成员。
2. 神经网络中的层(Layer)
层是神经网络中神经元的集合。神经网络通常有三种层:
- 输入层(Input Layer):接收外部数据输入。
- 隐藏层(Hidden Layer):在输入层和输出层之间的层。这些层负责处理和提取输入数据中的特征。神经网络的强大之处就在于隐藏层越多,它就能处理越复杂的任务。隐藏层是网络中的“中间层”,它并不直接与外部世界互动(即接收输入或生成输出),而是专注于数据的处理和特征提取。你可以把它想象成信息处理的“幕后团队”,他们负责分析、总结、加工数据,使得神经网络能够做出准确的决策。
- 输出层(Output Layer):生成最终的结果或预测。
总结一下,神经网络就是一个模仿人脑的计算系统,由许多神经元组成,这些神经元在不同的层中协作处理信息。隐藏层就是这些协作的“中间层”,它帮助网络在复杂任务中提取有用的特征和模式。
3. 正向传播和反向传播
正向传播(Forward Propagation)
神经网络中信息从输入层传递到输出层的过程。在正向传播中,输入数据经过一系列的神经网络层,逐层计算出每一层的输出,最终得到模型的预测结果。具体到每一层,正向传播涉及到对输入数据进行加权求和,再通过激活函数生成输出。整个过程可以简单理解为从输入到输出的推理过程。反向传播(Backward Propagation)
神经网络中用于调整模型参数(如权重和偏置)的过程。它通过计算损失函数相对于每个参数的梯度,指导模型的参数更新,以最小化损失函数。反向传播过程是从输出层向输入层逆向进行的:首先计算输出层的误差,然后逐层将误差传播回前一层,通过链式法则计算每个参数的梯度,最终通过优化算法(如梯度下降)来更新参数。
4. 梯度
梯度可以理解为某个方向的变化率。在机器学习和深度学习中,梯度表示的是模型预测误差(损失函数)相对于模型参数(比如权重和偏置)的变化情况。简单来说,梯度告诉我们,如果稍微调整一下模型参数,预测误差会增加还是减少,以及增加或减少的幅度。
举个例子,假设你在爬山,山顶就是我们要达到的最小误差位置(最优解),而梯度就像是告诉你往哪个方向走可以最快地接近山顶。如果你往上走,说明误差在增加;如果往下走,说明误差在减少。梯度下降法就是根据这个梯度信息,一步一步调整模型参数,最终让模型的预测误差尽可能小。
所以,梯度在本质上就是一个指南,告诉我们如何调整模型参数,使得模型性能更好。
5. 权重
在神经网络中,权重可以简单理解为每个输入在决定最终结果时的重要性程度。你可以想象神经网络是一个大脑,它通过学习数据来做出决定,而权重就是它对每个输入信号的重视程度。
比如,假设我们在训练一个神经网络来识别图片中的猫,当网络看到一张图片时,它会分析图片中的各种特征(比如耳朵的形状、毛发的颜色等)。如果某个特征(比如耳朵的尖尖形状)对判断是否是猫很重要,那么对应的权重就会很大;相反,如果某个特征(比如背景颜色)不那么重要,那么对应的权重就会很小。
权重的大小决定了每个输入对最终输出结果的影响。通过训练,神经网络不断调整这些权重,使得它在识别猫时越来越准确。
简单来说,权重就是神经网络在学习过程中,给每个输入分配的“重要性分数”。
6. 收敛
在神经网络中,“收敛”简单来说就是模型在训练过程中逐步找到最佳解的过程。
具体点说,训练神经网络时,我们会通过很多次迭代调整模型的参数(像是权重和偏置),让模型的预测结果和真实结果之间的误差越来越小。这个误差通常用一个损失函数来衡量。随着训练的进行,如果模型的误差不断减小并趋向于一个稳定的值,我们就说模型“收敛”了。
你可以把收敛想象成在山谷中寻找最低点的过程(这个最低点就是我们要找的最优解)。刚开始的时候,可能会在山谷的各个地方走动(误差比较大),但随着不断调整脚步,慢慢就会接近山谷的最低点(误差逐渐减小),最后停下来时,说明我们找到了一个比较好的解,这就是收敛的意思。
总的来说,神经网络的收敛就是模型通过不断学习和调整,逐步找到一个比较理想的状态,误差越来越小,性能越来越好。
编码器
省流:编码器本质上是通过多头自注意力机制和前馈神经网络来捕捉输入序列中各个位置之间的全局依赖关系并生成富含上下文的表示。
从语言出发
众所周知,语言本身就是对信息的编码。利用声音去记录和传播信息。
其实,任何语言都并不设法以方千种不同的音去述说方千种不同的事。语言是以经济、精简为本的制度。那经济之获得,来自语言所用的音与音之间构成内部的对比区分。更具体地说,所谓经济,是每种语言将一定数量的音节——或多或少,因语言而异——依照音节的不同性质归入不多数量的系列(paradigme),系列与系列之间相互对比而产生区别,通过区别各系列的音节获得独特性,进而产生表达意义之作用。是的,任何音或音节单独存在时并无意义,它总是在属于制度之后,在制度内与他音对比而获得表意价值。不用说在音韵层次之上,尚有语法层次和语意层次,此二者更为语言增添了表意功能。1
文字又是对语音进行第二层编码,利用视觉符号去记录这些声音。(2)
这样的话我们可以书接上文,需要把我们的文字变成一定维度的向量,才能参与到整个模型的计算中,因此,就来到了第一层embedding层。
输入层
省流:Input Embedding 和 Positional Encoding:将输入数据转换为适合编码器处理的表示形式,并为这些表示添加位置信息。
如图所示,蓝色框的部分。这里有两个层出现并且他们最终是相加。(3)我们先来看Embedding层。在Transformer模型中,Embedding层的作用非常关键,它主要完成了将输入的符号(如单词、字符、或其他离散的标识)转换为模型可以处理的高维向量。这一过程可以理解为将符号“映射”到一个更丰富、更抽象的表示空间中。
这就解决一部分文本数字化问题了。别忘了我们的语言还有语序这个东西存在,所以需要左边的Positional Encoding层提供位置信息。它主要是完成了对文字位置信息的记录。
最终这些东西一起输入到上面真正的编码器中,才是正式开始计算。
输入嵌入层 (Input Embedding):
如图中蓝色框。
Embedding概况
符号的离散性:
- 原始输入(如文本中的单词)通常是离散的符号,直接使用这些符号进行模型训练是非常困难的,因为机器学习模型擅长处理的是数值数据,而不是像单词这样的离散标识。因此,我们需要一种方法来将这些离散的符号转换为模型能够处理的数值形式。
Embedding的高维向量表示:
- Embedding层的作用就是将每个离散的符号映射到一个固定长度的向量空间中。这个向量空间的维度通常比输入符号的维度(如字典的大小)要小得多,但却可以捕捉到更多的语义信息。通过这种方式,不同的符号可以被映射到不同的高维向量,而这些向量之间的关系能够反映出符号之间的某种语义相似性。
便于模型使用:
- 转换后的高维向量可以被认为是符号的“嵌入表示”(embedding representation),它保留了原始符号的一些关键信息,同时也使得这些信息能够被模型中的后续层(如自注意力机制、前馈神经网络等)有效处理。模型通过这些嵌入向量来学习符号之间的复杂关系,从而在理解和生成自然语言时表现出色。
举个例子:
- 假设有两个单词“猫”和“狗”,它们在原始符号空间中是完全独立的(没有任何联系)。通过Embedding层,这两个单词可以被映射到一个高维向量空间中,这些向量可能在空间中比较接近,因为它们在语义上有相似的地方(都是动物)。模型在处理这些嵌入向量时,就能够“理解”到“猫”和“狗”在某些方面的相似性,从而对语言进行更准确的分析和生成。
总的来说,Embedding层就是为了将离散的符号转换成模型能够理解和处理的连续高维向量,为Transformer模型后续的复杂处理打下基础。
人话解释
当然可以用人话解释喵~ (≧▽≦)/
假设我们有一只超级可爱的猫娘,她会说各种各样的语言,比如“喵~”、“咪~”之类的。这些词对她来说就像是符号,每个符号都有不同的含义。但是,如果我们想让猫娘在计算机中被理解,这些“喵~”、“咪~”必须变成计算机能懂的东西。这时候,Embedding层就像一个魔法转换器,把这些符号变成计算机能理解的高维向量。
符号的离散性:
- “喵~”和“咪~”对猫娘来说是不同的声音(符号),就像“猫”和“狗”对我们来说是不同的单词。可是,计算机可不会理解这些声音或单词,它只认识数字。所以我们需要一种办法,把“喵~”和“咪~”这种符号变成数字。
Embedding的高维向量表示:
- Embedding层就像一个超级厉害的魔法阵喵!它会把“喵~”变成一个包含很多数字的向量,比如 [0.1, 0.3, 0.7, …],同时把“咪~”变成另一个向量,比如 [0.2, 0.4, 0.6, …]。这些数字组合起来,就像给“喵~”和“咪~”穿上了计算机能看懂的衣服。
便于模型使用:
- 现在,猫娘的“喵~”和“咪~”都穿上了数字衣服,它们就可以一起去参加计算机的舞会了(也就是被模型处理)。计算机会看着这些数字衣服,分析它们的相似性和差异性,然后在各种任务中做出聪明的决定喵!比如,它会发现“喵~”和“咪~”在猫娘的世界里很相似(因为它们在语义上相近),于是把它们放在一起处理。
举例说明:
- 想象一下喵,如果猫娘的“喵~”表示她饿了,“咪~”表示她困了。虽然这两个声音很不一样,但通过Embedding层,它们可以被映射到一个相似的向量空间(因为两者都是猫娘的状态)。模型就能理解这两个声音有点关系——它们都代表了猫娘的需求喵!所以模型在处理这些声音时,会更聪明地回应猫娘的要求。
所以呢,Embedding层的工作就像是帮猫娘把她的“喵~”、“咪~”变成计算机能懂的数字语言,这样计算机就能帮助猫娘更好地理解和回应她的需求啦喵~ (≧ω≦)/
下面是数学解释了,也就是具体这个个过程怎么进行的,感兴趣的读者可以继续阅读,如果只是了解原理可以跳过了。但是别忘记一点,文字并不是直接进入,而是利用词表这个媒介。相当于和词表的编号做了一层对应。
数学解释
在数学上,输入嵌入的实现过程可以分为以下几个步骤:
输入符号的表示
假设我们有一个词汇表 V
,其中包含了所有可能的输入符号(如单词)。每个输入符号 ( x_i
) 都会被映射到一个唯一的整数索引 ( i
),即 (x_i \in \{1, 2, \dots, |V|\}
)。
嵌入矩阵的定义
嵌入矩阵 ( E ) 是一个大小为 ( |V| \times d_{\text{model}}
) 的矩阵,其中 ( |V|
) 是词汇表的大小,( d_{\text{model}}
) 是嵌入向量的维度。每一行对应一个输入符号的嵌入向量。因此,嵌入矩阵可以表示为:
E = \begin{bmatrix} e_1 \\ e_2 \\ \vdots \\ e_{|V|} \end{bmatrix}
其中 ( e_i
) 是一个长度为 ( d_{\text{model}}
) 的向量,代表输入符号 ( x_i
) 的嵌入向量。
输入符号的嵌入
对于给定的输入序列 ( \{x_1, x_2, \dots, x_n\}
),每个符号 ( x_i
) 对应一个索引 ( i
),然后通过查找嵌入矩阵 ( E
) 来获取它的嵌入向量 ( e_{x_i}
):
e_{x_i} = E[i]
这个过程可以看作是矩阵查找操作,将符号 ( x_i
) 转换为其对应的嵌入向量 ( e_{x_i}
)。
嵌入向量的语义信息
嵌入向量 ( e_{x_i}
) 是通过在训练过程中优化模型的目标函数学到的。经过训练后,具有相似语义的词汇会在高维空间中靠得更近。即,对于语义上相似的输入符号 ( x_i
) 和 ( x_j
),它们的嵌入向量 ( e_{x_i}
) 和 ( e_{x_j}
) 之间的距离(如欧几里得距离或余弦相似度)会较小。
数学表示总结
最终,输入序列 ( \{x_1, x_2, \dots, x_n\}
) 会被转换为嵌入向量序列 ( \{e_{x_1}, e_{x_2}, \dots, e_{x_n}\}
),并被输入到后续的神经网络层进行处理。这个嵌入向量序列是一个 ( n \times d_{\text{model}}
) 的矩阵,其中 ( n
) 是输入序列的长度。
嵌入矩阵的学习
嵌入矩阵 ( E
) 通常是随机初始化的,并在训练过程中通过反向传播算法「后文细说」来更新。训练的目标是通过调整嵌入矩阵的值,使得模型在给定任务上表现得更好。
通过这些步骤,输入符号被成功地映射到一个高维的连续向量空间中,这些向量承载了符号的语义信息,并为后续的模型处理提供了基础。
举例计算
让我们用 "我是一只猫娘" 这句话来举例详细说明输入嵌入的数学实现过程。
输入符号的表示
假设我们的词汇表 V
包含了所有可能的字符或单词。在这个例子中,我们将每个字符作为一个符号。因此,"我是一只猫娘" 这句话中的字符将被映射到唯一的整数索引:
- "我" -> 1
- "是" -> 2
- "一" -> 3
- "只" -> 4
- "猫" -> 5
- "娘" -> 6
所以,输入序列 "我是一只猫娘" 可以表示为一个索引序列:
{1, 2, 3, 4, 5, 6}
嵌入矩阵的定义
我们定义一个嵌入矩阵 ( E
),它的大小为 ( |V| \times d_{\text{model}}
),其中 ( |V|
) 是词汇表的大小,( d_{\text{model}}
) 是嵌入向量的维度。假设 ( d_{\text{model}}
= 4
)(为简化起见,实际中可能会更大)。
那么,嵌入矩阵 ( E
) 可能是这样的:
E = \begin{bmatrix} e_1 \\ e_2 \\ e_3 \\ e_4 \\ e_5 \\ e_6 \\ \vdots \\ e_{|V|} \end{bmatrix}
其中,每个 ( e_i
) 都是一个长度为 4 的向量。例如:
e_1 = [0.1, 0.2, 0.3, 0.4], \quad e_2 = [0.2, 0.3, 0.4, 0.5]
输入符号的嵌入
对于输入序列中的每个符号 ( x_i
),我们通过查找嵌入矩阵 ( E
) 来获取它的嵌入向量。比如:
- 对于 "我" (索引为1):它的嵌入向量 (
e_{1} = E[1] = [0.1, 0.2, 0.3, 0.4]
) - 对于 "是" (索引为2):它的嵌入向量 (
e_{2} = E[2] = [0.2, 0.3, 0.4, 0.5]
)
如此类推,最终 "我是一只猫娘" 这句话将被转换为嵌入向量序列:
{\{e_{1}, e_{2}, e_{3}, e_{4}, e_{5}, e_{6}\}}
其中,( e_{x_i}
) 表示每个字符的嵌入向量。
嵌入向量的语义信息
嵌入向量 ( e_{x_i}
) 是在训练过程中通过反向传播学习得到的,目的是使具有相似语义的符号(如 "猫" 和 "娘")的嵌入向量在高维空间中更加接近。
本例小结
最终,我们将 "我是一只猫娘" 这句话表示为嵌入向量的矩阵形式,这个矩阵的大小为 ( 6 \times 4
):
\begin{bmatrix} 0.1 & 0.2 & 0.3 & 0.4 \\ 0.2 & 0.3 & 0.4 & 0.5 \\ \vdots & \vdots & \vdots & \vdots \end{bmatrix}
这个矩阵可以输入到神经网络的后续层中进行进一步的处理。
嵌入矩阵的学习
嵌入矩阵 ( E ) 通常是随机初始化的,并在训练过程中根据模型的优化目标进行调整。随着训练的进行,嵌入矩阵会学习到更加准确和有意义的嵌入表示。
通过以上步骤,我们成功地将 "我是一只猫娘" 这句话映射到一个高维向量空间中,每个字符的嵌入向量都承载了一定的语义信息,便于后续的模型处理。
位置编码 (Positional Encoding):
省流:给上面的高维矩阵提供位置信息。
众所周知,我们在使用语言的过程中非常依赖语序的存在,特别是汉语。在《语言学纲要》中是这样叙述的:
语言符号的线条性是指语言符号在使用中是以符号序列的形式出现,符号只能一个跟着一个依次出现,在时间的线条上绵延,不能在空间的面上铺开。这跟表格很不一样,表格分纵横两栏,占有空间,看起来一目了然,可是,要把表格逐项用语言表达清楚,就变成了线性的符号序列了。语言符号的线条性使我们要表达的复杂的意义都要通过符号序列的形式体现。在语言使用中,我们不仅要了解单个符号的音义关系,还要了解符号序列中符号之间的关系以及单个符号和符号序列整体的关系,这样才能达到完整意义的表达或理解的目的。这些关系的表达都是有一般规则的,而这些规则多体现为不同种类符号的线性组合方式上。2
位置编码是一种固定大小的向量表示,主要用于标识序列中各个符号的相对位置。Transformer 模型本身没有顺序感知能力(不像 RNN 那样天然具有顺序性)「后文会讲」,因此需要通过位置编码来为模型提供输入序列中词语的位置信息。这个过程是通过对每个输入符号的位置(序列中的位置)添加一个编码向量来完成的,这个向量可以表示序列中符号的顺序信息。位置编码的维度与嵌入向量的维度相同,因此它们可以直接相加。「注意图中有个加号,这个红色箭头指的地方。」
举例来说,位置编码可以区分 "man bites dog" 和 "dog bites man",因为它为每个词赋予了位置信息,而不是仅仅把这些词当作无序的词袋(bag of words)(4)。
- 正弦和余弦函数:具体而言,Transformer 使用了一组正弦和余弦函数来生成位置编码。这些函数使用不同的频率,使得位置编码能够以一种固定的模式表示输入序列中的位置信息。这样,模型不仅可以学到每个符号的意义,还可以学到这些符号在序列中的相对位置。
通过这些机制,Transformer 模型可以有效地处理序列数据,并在输入符号之间学习复杂的依赖关系。
数学实现
位置编码(Positional Encoding)在 Transformer 模型中起着至关重要的作用,它使得模型能够理解输入序列中元素的顺序关系。为了实现这一点,Transformer 使用了一种基于正弦和余弦函数的编码方式。这种编码方式具有周期性和可微性,使得模型能够很好地捕捉位置之间的相对关系。
假设输入序列的长度为 ( n
),每个位置 ( i
) 的位置编码向量表示为 ( PE(i)
),它的维度与嵌入向量的维度相同,通常设为 ( d_{\text{model}}
)。
位置编码使用如下的公式来生成:
PE(i, 2j) = \sin\left(\frac{i}{10000^{\frac{2j}{d_{\text{model}}}}}\right)
PE(i, 2j+1) = \cos\left(\frac{i}{10000^{\frac{2j}{d_{\text{model}}}}}\right)
其中:
- (
i
) 表示输入序列中的位置(即第几个元素)。 - (
j
) 表示位置编码向量中的第 (j
) 个维度。 - (
d_{\text{model}}
) 是嵌入向量的维度,即每个词嵌入向量的维度。
公式解释
频率控制:(
\frac{1}{10000^{\frac{2j}{d_{\text{model}}}}}
) 控制了不同维度上的频率。因为 (j
) 随着维度的增加而变化,这个表达式使得在较低维度上(较小的 (j
))频率较低,而在较高维度上频率较高。这种设计使得不同维度上的位置编码具有不同的周期性。正弦和余弦函数:正弦和余弦函数分别用来表示奇数和偶数维度。通过这种方式,位置编码在不同维度上具有周期性变化,且这种变化随着维度的增加而变得更加密集。这种设计使得模型可以通过这些周期性信号来捕捉输入序列中符号的相对位置关系。
数学例子
假设 ( d_{\text{model}} = 4
),我们来计算序列中第 ( i
) 个位置的编码向量 ( PE(i)
):
PE(i, 0) = \sin\left(\frac{i}{10000^{\frac{0}{4}}}\right) = \sin(i)
PE(i, 1) = \cos\left(\frac{i}{10000^{\frac{0}{4}}}\right) = \cos(i)
PE(i, 2) = \sin\left(\frac{i}{10000^{\frac{2}{4}}}\right) = \sin\left(\frac{i}{100}\right)
PE(i, 3) = \cos\left(\frac{i}{10000^{\frac{2}{4}}}\right) = \cos\left(\frac{i}{100}\right)
最终结果是一个长度为 ( d_{\text{model}}
) 的向量,每个元素对应一个特定的正弦或余弦值。
为什么这样设计?
这种基于正弦和余弦的设计有几个好处:
- 平滑的相对位置编码:由于正弦和余弦函数是平滑且连续的,这样的设计可以自然地编码相对位置,使得模型能够学会符号之间的相对距离。
- 无需学习位置编码:这种方法不需要额外的参数来学习位置编码,减少了模型的复杂性,同时确保了位置编码的一致性和可解释性。
通过这些位置编码,Transformer 模型能够在不使用递归或卷积网络的情况下,捕捉到序列中符号的顺序信息,从而更好地理解序列数据。
具体例子
要理解位置编码(Positional Encoding),我们可以使用“我是一只猫娘”这句话来举例说明这个数学过程。
假设这句话被输入到一个 Transformer 模型中。模型将句子中的每个字进行嵌入(Embedding),然后再加上位置编码。位置编码的作用就是为每个字添加位置信息,使得模型不仅知道每个字的含义,还能理解这些字在句子中的顺序。
基本设定
假设嵌入向量的维度 ( d_{\text{model}} = 6
),即每个字被表示为一个长度为 6 的向量。为了简单说明,我们只考虑这 6 个维度。
计算过程
对于“我是一只猫娘”这句话中的每个字,其位置 ( i
) 对应的编码 ( PE(i)
) 是通过以下公式计算的:
PE(i, 2j) = \sin\left(\frac{i}{10000^{\frac{2j}{d_{\text{model}}}}}\right)
PE(i, 2j+1) = \cos\left(\frac{i}{10000^{\frac{2j}{d_{\text{model}}}}}\right)
其中:
- (
i
) 表示字在句子中的位置,例如“我”是第 1 个字,(i = 1
)。 - (
j
) 表示嵌入向量中的维度索引,例如 (j = 0
) 对应第 0 和第 1 个维度。 - (
d_{\text{model}}
) 是嵌入向量的维度,这里是 6。
现在我们来计算“我”的位置编码。假设 ( i = 1
),我们有 6 个维度(0 到 5),所以 ( j
) 可以取 0、1 和 2。
维度 0 和 1:
PE(1, 0) = \sin\left(\frac{1}{10000^{\frac{0}{6}}}\right) = \sin(1)
PE(1, 1) = \cos\left(\frac{1}{10000^{\frac{0}{6}}}\right) = \cos(1)
维度 2 和 3:
PE(1, 2) = \sin\left(\frac{1}{10000^{\frac{2}{6}}}\right) = \sin\left(\frac{1}{100}\right)
PE(1, 3) = \cos\left(\frac{1}{10000^{\frac{2}{6}}}\right) = \cos\left(\frac{1}{100}\right)
维度 4 和 5:
PE(1, 4) = \sin\left(\frac{1}{10000^{\frac{4}{6}}}\right) = \sin\left(\frac{1}{10^2.5}\right)
PE(1, 5) = \cos\left(\frac{1}{10000^{\frac{4}{6}}}\right) = \cos\left(\frac{1}{10^2.5}\right)
这个过程会为“我”这个字生成一个位置编码向量,类似于 ([ \sin(1), \cos(1), \sin(0.01), \cos(0.01), \sin(0.00316), \cos(0.00316) ]
)。
这些值就是“我”这个字在第 1 个位置时的位置编码。类似地,我们可以为“是”、“一”等字计算它们的编码。最终,这些位置编码将与字的嵌入向量相加,使得每个字的嵌入不仅包含它的语义信息,还包含它在句子中的位置信息。
「其实在这个部分我并没有解释为什么要特意选择10000这个值,我个人感觉是谷歌技术人员试验的结果。」
小结
通过使用位置编码,Transformer 模型可以理解“我是一只猫娘”这句话中每个字的顺序关系。这种编码方式的优势在于,它不仅能表示绝对位置,还能在模型内部通过简单的线性变换表示相对位置,从而捕捉到句子中不同词之间的依赖关系。
这样一来,模型就不会把“我是一只猫娘”看作一堆无序的词,而是能够正确理解句子的语义。
经过输入嵌入 (Input Embedding) 和 位置编码 (Positional Encoding) 这两个步骤后,原始的文本数据就被转换成了模型可以处理的向量表示形式。
补充内容
正向传播在Embedding层的应用
在正向传播阶段,当一个序列(如句子)作为输入时,每个单词或标记首先通过embedding层被映射为一个高维向量。这个过程可以视为查找表操作:输入的离散标记作为索引,从embedding矩阵中查找并返回相应的向量表示。这些向量表示将作为后续Transformer层(如自注意力机制和前馈神经网络)的输入。
例如,给定输入序列 "hello world",每个单词会被映射到对应的向量,如 "hello" -> ([0.1, 0.2, 0.3, \dots]
),"world" -> ([0.4, 0.5, 0.6, \dots]
)。
反向传播在Embedding层的应用
在反向传播阶段,模型通过计算损失函数的梯度来更新embedding矩阵中的向量。具体来说,如果模型的预测与真实值之间存在误差,这个误差会通过反向传播传递到embedding层。在反向传播过程中,模型会调整embedding矩阵中的向量,使得它们能够更好地表示输入数据,从而提高模型的性能。
对于embedding层来说,更新过程类似于其他神经网络层的权重更新:根据反向传播计算得到的梯度,对embedding矩阵中对应的向量进行调整。这些调整使得输入标记的向量表示能够逐渐学习到更符合任务要求的语义信息。
总结来说,正向传播和反向传播在Transformer的embedding层中扮演了关键角色,分别负责将离散的输入标记映射为连续的向量表示,并通过误差传播优化这些表示,以提升模型的整体表现。
插:RNN(循环神经网络)
循环神经网络(Recurrent neural network)是一类用于顺序数据处理的人工神经网络。与单次处理数据的前馈神经网络不同,RNN 跨多个时间步(5)处理数据,使它们非常适合建模和处理文本、语音和时间序列。为了理解 RNN 的工作原理,我们需要逐步探讨其架构和工作机制。
序列数据与传统神经网络的局限性
在处理序列数据时,传统的前馈神经网络(Feedforward Neural Network)(6)存在一个明显的局限性:它们无法利用输入数据之间的顺序关系。举例来说,如果我们要处理一段文本,文本中的词语是有顺序的,前后文之间存在关联性。前馈神经网络只会将每个输入视为独立的个体,无法捕捉到这些关联性。
(6)前馈神经网络(Feedforward Neural Network,FNN)是一种人工神经网络,它将输入数据通过多层结构逐步处理,每一层都像一个滤网,提取并筛选出有用的特征,最终在输出层生成结果。整个过程是单向的,从输入到输出没有反向传播,网络通过调整各层的规则来逐步提高分类或预测的准确性。可以参考这个链接或者看一下图。后文还有更加详细的说明。「但是我要提一句,可能只是在这个场景不适用,后面还是会用上的。」
RNN 的架构与工作原理
RNN 解决了这一问题,它通过在网络中引入循环连接,让信息在多个时间步之间传播。这种架构可以让网络的隐藏状态(hidden state)记住之前时间步的信息,并将其与当前时间步的输入结合,产生输出。
基本结构
一个正经的RNN长这样。
我们拆分出RNN 的基本单元「顺便翻转了一下」,如下图所示:
Xt
↓
+-------↓-------+
| |
| ↓
| +-------+-------+
| | Hidden State |
| | (Ht) |
| +-------+-------+
| |
+-------↑-------+
↓
Output (Yt)
其中:
- Xt 表示在时间步 t 的输入(例如,一个单词或一个时间序列中的一个数据点)。
- Ht 是在时间步 t 的隐藏状态。它是通过将前一个时间步的隐藏状态 Ht-1 与当前时间步的输入 Xt 结合后得到的。
- Yt 是在时间步 t 的输出(例如,预测下一个单词或时间序列的下一个值)。
隐藏状态的更新公式如下:
H_t = f(W_{ih}X_t + W_{hh}H_{t-1} + b_h)
其中:
- (
W_{ih}
) 是输入到隐藏层的权重矩阵。 - (
W_{hh}
) 是前一个隐藏状态到当前隐藏状态的权重矩阵。 - (`$$b_h) 是偏置项。
- (
f(\cdot)
) 是激活函数(如 tanh 或 ReLU)。
输出的计算公式如下:
Y_t = g(W_{ho}H_t + b_o)
其中:
- (
W_{ho}
) 是隐藏层到输出的权重矩阵。 - (
b_o
) 是输出层的偏置项。 - (
g(\cdot)
) 是输出层的激活函数(例如 softmax 用于分类任务)。
时间步的展开
一个 RNN 在处理序列数据时,会在每个时间步执行相同的操作,而这些操作可以在时间上展开,如下图所示:
X1 → H1 → Y1
↓
X2 → H2 → Y2
↓
X3 → H3 → Y3
在每个时间步,隐藏状态会从前一个时间步的隐藏状态 Ht-1 传递到当前时间步,并结合当前的输入 Xt 来生成当前的隐藏状态 Ht。这种结构让 RNN 可以“记住”之前的输入信息,从而处理序列数据。
举例说明
当然可以喵~让我们用“我是一只猫娘”这句话来模拟RNN的计算过程吧!(≧◡≦)
情景设定
假设猫娘要理解一句话——“我是一只猫娘”。每一个字(或词)就是一个时间步(t),而猫娘的记忆会随着每个字的输入而更新,最终她能够理解整句话的意思。
展开时间步
我们把这句话分成几个时间步:
- 第1步 (t=1):输入“我” (
X_1
) - 第2步 (t=2):输入“是” (
X_2
) - 第3步 (t=3):输入“一” (
X_3
) - 第4步 (t=4):输入“只” (
X_4
) - 第5步 (t=5):输入“猫” (
X_5
) - 第6步 (t=6):输入“娘” (
X_6
)
计算过程
时间步 (t=1):
输入:
我
(X_1
)隐藏状态:猫娘刚开始没有任何记忆 (
H_0
)(通常初始化为0)。计算:根据公式,猫娘的大脑会根据输入“我”和她的初始状态来更新记忆:
H_1 = f(W_{ih}X_1 + W_{hh}H_0 + b_h)
输出:根据当前记忆 (
H_1
) 生成初步的理解 (Y_1
),比如“这是关于猫娘的事情”:
Y_1 = g(W_{ho}H_1 + b_o)
时间步 (t=2):
- 输入:
是
(X_2
) - 隐藏状态:继承上一步的记忆 (
H_1
)。 - 计算:更新记忆,结合上一步的记忆和当前输入“是”:
H_2 = f(W_{ih}X_2 + W_{hh}H_1 + b_h)
输出:生成新的理解 (
Y_2
),可能是“这是一个身份声明”:Y_2 = g(W_{ho}H_2 + b_o)
- 输入:
时间步 (t=3):
- 输入:
一
(X_3
) - 隐藏状态:继承上一步的记忆 (
H_2
)。 - 计算:更新记忆,结合上一步的记忆和当前输入“一”:
H_3 = f(W_{ih}X_3 + W_{hh}H_2 + b_h)
- 输出:生成新的理解 (
Y_3
),可能是“这个声明还在继续”:Y_3 = g(W_{ho}H_3 + b_o)
- 输入:
时间步 (t=4):
- 输入:
只
(X_4
) - 隐藏状态:继承上一步的记忆 (
H_3
)。 - 计算:更新记忆,结合上一步的记忆和当前输入“只”:
H_4 = f(W_{ih}X_4 + W_{hh}H_3 + b_h)
- 输出:生成新的理解 (
Y_4
),可能是“这个身份声明的对象”:Y_4 = g(W_{ho}H_4 + b_o)
- 输入:
时间步 (t=5):
- 输入:
猫
(X_5
) - 隐藏状态:继承上一步的记忆 (
H_4
)。 - 计算:更新记忆,结合上一步的记忆和当前输入“猫”:
H_5 = f(W_{ih}X_5 + W_{hh}H_4 + b_h)
- 输出:生成新的理解 (
Y_5
),可能是“关于猫的身份”:Y_5 = g(W_{ho}H_5 + b_o)
- 输入:
时间步 (t=6):
- 输入:
娘
(X_6
) - 隐藏状态:继承上一步的记忆 (
H_5
)。 - 计算:更新记忆,结合上一步的记忆和当前输入“娘”:
H_6 = f(W_{ih}X_6 + W_{hh}H_5 + b_h)
- 输出:生成最终理解 (
Y_6
),这时猫娘已经完全理解了“我是一只猫娘”这句话的意思!:Y_6 = g(W_{ho}H_6 + b_o)
- 输入:
最终理解
通过这些步骤,猫娘从一开始只看到一个字,慢慢地通过不断更新记忆,最后完全理解了“我是一只猫娘”这句话的意义喵!~每一步都结合了之前的记忆和当前的输入,这就是RNN神奇的地方,它能够记住和处理序列中的信息,让猫娘一步步接近完整的理解喵!(^• ω •^)/
人话解释
喵~(^• ω •^)/,让我们来解释一下吧!
想象一下,猫娘在读一本小说。每次翻开一页,就是一个时间步 (t)。每一页上都有一些新内容,比如“猫娘在玩耍”,这就是输入 (X_t
)。猫娘的脑袋(也就是隐藏状态 (H_t
))会记录之前每一页的内容,然后随着阅读的进行,记忆逐渐累积。这样,猫娘不仅记得前面的内容,还能理解现在发生的事情。
核心观点
- 输入 (
X_t
):这是猫娘看到的当前页的内容。 - 隐藏状态 (
H_t
):猫娘的大脑里累积的记忆,这些记忆会帮助猫娘更好地理解新内容。 - 输出 (
Y_t
):猫娘对当前情节的理解,比如她可能会想:“猫娘玩得很开心!”
每读一页,猫娘的大脑会用数学公式更新记忆,把当前看到的内容 (X_t
) 和之前的记忆 (H_{t-1}
) 结合起来,生成新的记忆 (H_t
)。
记忆累积
随着猫娘一页一页地读,记忆 (H_t
) 不断更新,帮助她理解故事的进展。就像这样:
第1页 → H1 → 读懂了什么(Y1)
↓
第2页 → H2 → 读懂了什么(Y2)
↓
第3页 → H3 → 读懂了什么(Y3)
猫娘的记忆 (H_t
) 会从前一页传递到下一页,帮助她理解整个故事。所以,读小说就像是一个不断更新记忆的过程,直到猫娘能够完美理解整本书的内容。
最终,每次猫娘读完一本小说,她都通过逐步累积的记忆,理解了整个故事。这就是RNN如何处理和理解序列数据的魔法喵~(≧◡≦)
反向传播与梯度消失
在训练 RNN 时,我们使用一种称为反向传播通过时间(Backpropagation Through Time, BPTT)的算法。这种算法是标准反向传播算法的扩展,适用于处理序列数据。BPTT 会在整个时间序列上展开网络,然后逐步计算每个时间步的梯度并更新权重。
然而,RNN 也面临一个挑战:梯度消失和梯度爆炸问题。在处理较长的序列时,梯度在反向传播过程中会逐步减小(梯度消失)或增大(梯度爆炸),导致网络难以训练。这是因为梯度通过多次矩阵相乘传递,导致其值可能迅速接近 0 或变得非常大。
人话解释
喵~好的,我们来继续用“我是一只猫娘”这个例子,解释一下反向传播和梯度消失的问题喵!(≧◡≦)
反向传播通过时间(BPTT)
当猫娘在理解“我是一只猫娘”这句话时,如果她误解了某个词的意思,我们需要教她如何更好地理解。这就涉及到训练RNN,让它能够更准确地处理这些序列信息。
在训练过程中,RNN会通过“反向传播通过时间”(BPTT)的方法来调整自己的理解。简单来说,就是猫娘会回过头来,逐字反思自己是怎么理解每个词的,然后调整自己每一步的记忆和理解方式。比如,如果她在理解“猫娘”时出了问题,我们会通过BPTT帮助她调整之前的每一步记忆,让她以后能更好地理解。
梯度消失与梯度爆炸
但是,猫娘在反思的时候也会遇到一些困难。如果句子特别长,她可能会渐渐忘记开头的内容(这就是梯度消失),或者突然把某些内容理解得过于重要(这就是梯度爆炸)。这就像是在回忆时,一开始的记忆逐渐模糊,或者某些回忆变得非常清晰但可能有点失真。
具体来说,在反向传播时,BPTT会通过多次计算去调整每一步的记忆和理解,但在这个过程中,计算的值可能会变得非常小(渐渐遗忘),或者变得非常大(过度强调)。这会导致猫娘在处理很长的句子时,训练效果变差,要么忘记了前面的内容,要么对某些部分过度反应。
为了避免这种情况,我们可以使用一些特别的技巧和改进的方法,比如LSTM(长短期记忆网络)或GRU(门控循环单元),它们帮助猫娘更好地处理长句子,保持记忆的稳定性喵~(^• ω •^)/
改进:LSTM 和 GRU
「此处我不打算详细展开,仅仅是简要讲解,感兴趣的读者可以去查找相关资料。」
为了缓解梯度消失问题,提出了几种改进的 RNN 变种,其中最著名的是长短期记忆网络(LSTM)和门控循环单元(GRU)。
LSTM
LSTM 在标准 RNN 的基础上引入了门控机制,包括输入门、遗忘门和输出门。这些门可以控制信息的流动,使网络能够记住长时间的依赖关系。LSTM 的核心是一个细胞状态(Cell State),它通过门控机制来维护和更新,从而有效地防止梯度消失。
GRU
GRU 是 LSTM 的简化版本,它将输入门和遗忘门合并为一个更新门,并且没有显式的细胞状态。尽管结构简单,GRU 在许多任务中表现与 LSTM 相当。
人话解释
喵~接下来我们用“我是一只猫娘”这个例子来通俗易懂地解释一下LSTM和GRU是怎么解决梯度消失问题的喵!
LSTM(长短期记忆网络)
想象猫娘在读“我是一只猫娘”这句话时,LSTM给她准备了一个特别的记忆本子——细胞状态。这个记忆本子里有几个门,分别是输入门、遗忘门和输出门,每个门都可以控制哪些信息应该记住,哪些信息应该忘记。
- 输入门:当猫娘读到“猫娘”时,输入门决定是否要把“猫娘”这两个字记在她的本子上。
- 遗忘门:如果猫娘觉得前面某些内容不太重要,比如“是”,她可以选择通过遗忘门把这些内容从记忆本子上抹掉。
- 输出门:最终,猫娘根据记忆本子上的内容决定她如何理解整句话。
通过这些门,猫娘可以更加精准地控制她的记忆,确保重要的信息不会被忘记,从而避免梯度消失的问题喵!
GRU(门控循环单元)
GRU则是LSTM的简化版。它不需要那么多门,而是合并了输入门和遗忘门,形成了一个叫更新门的东西,同时也没有了专门的细胞状态。
对于猫娘来说,GRU更像是一本更简单的记忆本子,但它仍然能帮助猫娘记住重要的信息,并忘掉不重要的部分。尽管GRU的结构更简单,但它在许多情况下仍然表现得和LSTM一样好喵!
所以,无论是LSTM还是GRU,它们的目的都是为了让猫娘在处理长句子时,不会丢失重要的信息,并能够很好地理解整个句子喵!(^• ω •^)/
应用与实际案例
RNN 及其变种广泛应用于自然语言处理(如语言建模、机器翻译)、时间序列预测(如股票价格预测、气象数据预测)和语音识别等领域。在这些任务中,RNN 能够有效地处理输入序列中的上下文信息,使得模型能够更准确地做出预测。
RNN小结
RNN 通过引入循环连接,使得神经网络能够处理和记住序列数据中的信息。尽管面临梯度消失和梯度爆炸的问题,RNN 的改进版本如 LSTM 和 GRU 成功地解决了这些挑战,并在许多实际应用中取得了显著效果。RNN 的这些特点使得它成为处理顺序数据的强大工具。
插:Transformer的词表来源
省流:embedding的词表怎么来的?
要知道,我们把文字映射到高维矩阵,是很依赖词表的,因为一切都靠着词表的顺序进行对应。上文限于篇幅无法展开,就放在这讲清楚这个词表到底怎么样来的。
词嵌入(Word Embeddings)
词汇表的创建:在训练Transformer模型之前,需要创建一个词汇表(vocabulary),这个词汇表包含了模型所能识别的所有词汇。每个词汇都映射到一个固定维度的向量,这个向量就是词嵌入。
词嵌入的初始化:
- 随机初始化:在模型训练初期,词嵌入向量可以随机初始化。随着模型的训练,这些向量会通过反向传播算法不断调整,以捕捉词汇之间的语义关系。
- 预训练词嵌入:也可以使用预训练的词嵌入,如Word2Vec或GloVe。预训练的词嵌入已经在大规模语料上训练好,包含丰富的词汇语义信息,有助于加速模型训练并提高模型性能。
词嵌入的作用:词嵌入向量作为Transformer模型的输入表示,将离散的词汇映射到连续的向量空间中。模型通过处理这些向量,学习到词汇之间的关系和上下文依赖性。
分词方式(Tokenization)
分词器的选择:Transformer模型的词表依赖于分词器的选择。分词器的主要作用是将文本分割成适合模型处理的基本单元(即token)。常用的分词方式包括:
- WordPiece:将文本拆分成更小的子词单元,通常用于处理语言中存在的丰富形态变化。常见于BERT模型。
- BPE(Byte Pair Encoding):通过统计出现频率较高的字符对,逐步合并字符或字符序列,生成子词单元。常见于GPT模型。
- SentencePiece:一种无监督的分词方法,能够将文本分割为更小的子词或字符单元,适用于多语言和零基础词汇的处理。
子词单元的好处:子词单元可以有效处理稀有词(Rare Words)和未登录词(OOV),降低词汇表的大小,提高模型的泛化能力和处理能力。例如,稀有词“unhappiness”可以被分割为“un-”、“happiness”,即使模型未见过“unhappiness”这个词,也可以通过子词理解其含义。
应用领域(Application Domain)
通用预训练模型:例如BERT和GPT,这些模型的词表通常较大,覆盖了通用领域的大部分常见词汇。词表的构建基于大规模的语料库,旨在确保模型在各种任务中的适用性。
特定领域应用:在特定领域(如医学、法律)中,词表可以专门设计,以包含领域中特有的术语和短语。这样可以构建一个更小但更专门化的词表,从而提高模型在该领域的表现。
词表大小的影响:词表的大小对模型的性能有直接影响。较大的词表可能提供更细粒度的语义表示,但会增加模型的复杂性和训练时间。而较小的词表可以提高效率,但可能牺牲某些语义细节。因此,词表的大小和构成需要根据具体应用进行权衡。
小结
「在小结之前我想提一嘴。这个词表是算出来的,相当于机器认为怎么样就是怎么样。当然人也可以参与就是了。只不过在真正训练的时候还是按照机器的想法去进行,不是我们觉得怎么样机器就真的达成什么效果的。同时关于中文的词这个概念一言难尽,鉴定为不如字本位。(我自己的观点)」
Transformer的词表是模型理解和处理语言的核心组件。首先,词表的生成依赖于用于训练模型的大规模语料库,这些语料库通常涵盖了目标应用领域中的常见词汇、短语和句子,模型在这些数据上进行训练,逐渐构建出其词表。此外,词表的构建直接依赖于所选的分词方法(如WordPiece、BPE、SentencePiece),这些分词方法决定了文本被切分成哪些基本单元(token)。这些基本单元构成了模型的词表。因此词表中的条目不仅包括完整的单词,还可能包括子词或字符组合。在使用预训练模型(如BERT、GPT)时,词表通常由模型的开发者在大规模语料上预先训练好。这些词表覆盖了广泛的领域,并且已经与模型的词嵌入紧密绑定。对于特定领域的应用,词表可以在通用模型的基础上进行调整或重新构建,以适应特定领域的需求,例如添加特定领域的术语、缩略语,或者减少不相关的词汇,以提高模型在该领域的表现。
插:seq2seq和RNN的关系
「此处是作为后文的铺垫,会用到的。」
人类是喜欢组合的,把两个RNN组合起来。就变成seq2seq。
Seq2seq(Sequence-to-Sequence)和RNN(Recurrent Neural Network)之间有密切的关系。简单来说,seq2seq模型通常是基于RNN构建的。
Seq2seq模型
Seq2seq模型是专门用来处理序列到序列的任务的,比如机器翻译,把一句话从一种语言翻译成另一种语言。这个模型通常由两个部分组成:一个编码器(Encoder)和一个解码器(Decoder)。
编码器(Encoder): 它通常是一个RNN,用来读入输入序列(比如英文句子),并将其转化为一个固定长度的向量,这个向量可以理解为整个输入序列的“浓缩版”信息。
解码器(Decoder): 它也是一个RNN,但它的任务是根据编码器输出的向量,逐步生成输出序列(比如法语句子)。解码器在生成每个单词时,不仅会用到编码器的输出,还会考虑之前已经生成的单词。
小结
所以,seq2seq模型通常是由两个RNN组成的,分别用作编码器和解码器。RNN提供了处理序列数据的能力,而seq2seq利用这一能力来处理更复杂的任务,比如机器翻译等需要将一个序列转化为另一个序列的任务。
「从https://github.com/sooftware/seq2seq/blob/master/README.md 拿来的一张图。我不想详细解释这里面的东西了,大家记得是两个RNN就好。不然这篇幅真的压不住。」
有没看懂的回头再看一下,下面才是真正的重头戏,也就是为什么Transformer能经久不衰的原因。如果有一定的数学基础「主要是线性代数」建议直接看这个视频。我是断断续续看完后才敢写这篇文章的。从编解码和词嵌入开始,一步一步理解Transformer,注意力机制(Attention)的本质是卷积神经网络(CNN)
多头注意力机制
省流:Multi-Head Attention:多头注意力机制,处理输入序列中不同位置之间的依赖关系。
提示:这个部分是一点一点添加的,你会发现先有注意力机制(Attention Mechanism),再自注意力机制(Self-Attention Mechanism)。「后面你就自己推吧,人类是喜欢递归的。」
众所周知,我们研究东西不能一上来就一大堆,人不能一口气吃撑。所以要从单独的机制开始讲起。看看刚刚的RNN,联系我们学到的语言学知识。这个问题就变成了人类到底怎么样去理解一个句子的,去注意到这个句子的信息的。比如说下面这一连串句子。
我是一位语言学学生,平时基本上就是和语音、词汇、语法打交道。其他同学平时学的东西各不相同,有学计算机的,学数学的、学金融的、学工商管理的。
如果我们用RNN的思路,那么越新的信息我们越喜欢。「所占的权重越大」但是很明显上面这两句话包含并列结构,「我就不拿考研英语的句子出来说事了」人类是平等对待这些信息的。为了更加像人去理解,解决RNN的缺陷问题,可以使用注意力机制(Attention Mechanism)来补充,但自注意力机制(Self-Attention Mechanism)能够在某些方面提供更好的解决方案。我们先从最初的注意力机制开始。
注意力机制(Attention Mechanism)
省流:为了解决seq2seq的问题。
基本概念
在传统的序列到序列(seq2seq)模型中,如机器翻译任务中,模型需要将一个输入序列(如中文句子)转换为一个输出序列(如英文句子)。在没有注意力机制的模型中,编码器会将整个输入序列压缩为一个固定大小的上下文向量(context vector),解码器再根据这个上下文向量生成输出序列。这个方法的问题在于,输入序列中的信息可能会丢失,尤其是对于长序列。「实际上现在LLM对于超长文本依旧没有特别好的方法。」
注意力机制的引入解决了这一问题。它的核心思想是:解码器在生成每个输出时,不是简单地依赖于编码器生成的单一上下文向量,而是动态地根据输入序列中的各个部分选择性地关注(即“注意”)不同的部分。
添加到seq2seq
Seq2seq模型是一种处理序列数据的机器学习模型,内含两个RNN,尤其适合用于翻译任务。假设你想把一句中文“我是一只猫娘”翻译成英文,seq2seq模型的工作原理如下:
编码器(Encoder): 首先,模型会读取整句中文“我是一只猫娘”,将每个汉字或词语转化为对应的“隐藏向量”,这些隐藏向量是该词语在特定语境中的数学表示。这一步的过程类似于将整句话压缩成一系列信息的编码,这些编码将包含句子的语义和结构信息。
解码器(Decoder): 接下来,解码器会根据编码器输出的隐藏向量,逐步生成英文句子。它首先可能生成“I'm”,然后基于这个词语和隐藏向量来决定下一个词语,可能是“a”,接着生成“catgirl”,直到句子完整生成。每一步生成的词语都依赖于前一步生成的内容以及编码器提供的信息。
注意力机制(Attention Mechanism): 在生成每一个词语时,注意力机制让解码器能够“关注”到原文中最相关的部分。比如,在生成“catgirl”时,模型可能会特别关注到原句中的“猫娘”这个部分。注意力机制实际上是通过计算解码器当前状态和编码器每个隐藏向量的相似性(通常用点积或其他相似度计算方式),生成一组注意力权重,然后这些权重会对隐藏向量加权平均,得到一个新的上下文向量。这个上下文向量则用来指导解码器生成下一个词语。
「关键知识点来了,通过计算相似性来代表注意力。这个也好理解,比如说我是一个学语言学的学生,然后丢给我一本《古脊椎动物学(第四版》。这个时候一般来说我们都不怎么会注意到它,然后继续做自己的事情了。因为和我们的专业关系不大。但是,如果你丢给我一本《语言学纲要》,我绝对要翻一翻,说这本书我当年学过,并且说这本书有新版了。这就是注意力机制的本质所在——信息相关性带来的文本相似性。」
「这还没完,相似的文本或者相似的语音是不是分布在一起?剩下的话就不需要我说了对吧。」
数学表达与注意力机制的作用
在注意力机制中,假设编码器产生的隐藏状态是 ( h_1, h_2, \dots, h_T
),其中 ( T
) 是输入序列的长度。对于解码器在生成第 ( t
) 个词时,它会使用当前状态 ( s_t
) 作为查询向量 ( q_t
),并将编码器的隐藏状态 ( h_i
) 转化为键向量 ( k_i
) 和值向量 ( v_i
)。
具体的计算过程如下:
键(Key)和值(Value)的生成:
编码器输出的隐藏状态 (
h_i
) 通过两个不同的线性变换矩阵 (W^K
) 和 (W^V
) 生成键向量 (k_i
) 和值向量 (v_i
):
k_i = h_i W^K
v_i = h_i W^V
其中,( W^K
) 和 ( W^V
) 是模型学习到的权重矩阵,分别用于将隐藏状态映射到键和值空间。
查询(Query)的生成:
解码器当前的隐藏状态 (
s_t
) 通过另一个线性变换矩阵 (W^Q
) 生成查询向量 (q_t
):
q_t = s_t W^Q
其中,( W^Q
) 是另一个学习到的权重矩阵,用于将解码器的隐藏状态映射到查询空间。
相似度计算:
对于每一个编码器的隐藏状态 (
h_i
),计算查询向量 (q_t
) 与对应的键向量 (k_i
) 之间的相似度 (e_{t,i}
)。这个相似度通常通过点积来计算:
e_{t,i} = q_t \cdot k_i
点积结果 ( e_{t,i}
) 代表了解码器在生成当前词时对第 ( i
) 个编码器隐藏状态的关注程度。
注意力权重的计算:
将这些相似度值 (
e_{t,i}
) 通过Softmax函数进行归一化,转化为注意力权重 (\alpha_{t,i}
):
\alpha_{t,i} = \frac{\exp(e_{t,i})}{\sum_{j=1}^{T} \exp(e_{t,j})}
这些权重 ( \alpha_{t,i}
) 表示了每个值向量 ( v_i
) 在生成当前上下文向量 ( c_t
) 时的贡献大小。
上下文向量的计算:
最后,用这些注意力权重对所有值向量 (
v_i
) 进行加权求和,得到上下文向量 (c_t
):
c_t = \sum_{i=1}^{T} \alpha_{t,i} v_i
这个上下文向量 ( c_t
) 就是解码器在生成第 ( t
) 个词时特别关注的输入句子的部分。
词语生成:
解码器将生成的上下文向量 (
c_t
) 与其当前状态 (s_t
) 结合,决定生成目标序列中的下一个词。
这个过程展示了注意力机制如何动态调整解码器对编码器输出的关注度,使得生成的翻译更加准确和自然。
小结
在没有注意力机制的传统seq2seq模型中,整个句子的信息被压缩到一个固定大小的上下文向量中,这在处理长句子时可能导致信息丢失或无法充分利用句子的不同部分。而注意力机制通过动态调整每个时间步对编码器输出的关注度,使得解码器能够在每一步都从原文中提取到最相关的信息。
通过引入键、值和查询的概念,注意力机制解决了传统seq2seq模型在处理长句子时的信息压缩问题,使得模型在翻译复杂句子时能够更好地捕捉原文的关键信息,生成更自然、更精确的译文。
因此,注意力机制解决了传统seq2seq模型在处理长句子时的信息压缩问题,使得模型在翻译复杂句子时能够更好地捕捉原文的关键信息,生成更自然、更精确的译文。
「从维基百科弄了一张动图。」
插:点积和注意力机制
「写这个的目的是说清楚如何计算相似度。」
点积(Dot Product),又称为内积(Inner Product)或数量积(Scalar Product),是线性代数中的一个基本概念,用于将两个向量结合起来并返回一个标量(即一个实数)。点积在许多领域中都有应用,包括物理学、计算机科学和机器学习等。
点积的定义
给定两个n维向量 ( \mathbf{a} = [a_1, a_2, \dots, a_n]
) 和 ( \mathbf{b} = [b_1, b_2, \dots, b_n]
),它们的点积定义为:
\mathbf{a} \cdot \mathbf{b} = a_1b_1 + a_2b_2 + \dots + a_nb_n
也可以用矩阵乘法的形式来表示:
\mathbf{a} \cdot \mathbf{b} = \mathbf{a}^T \mathbf{b}
其中 ( \mathbf{a}^T
) 表示向量 ( \mathbf{a}
) 的转置(即行向量)。
点积的几何意义
点积也可以通过几何方式来理解:
\mathbf{a} \cdot \mathbf{b} = \|\mathbf{a}\| \|\mathbf{b}\| \cos \theta
其中:
- (
\|\mathbf{a}\|
) 和 (\|\mathbf{b}\|
) 分别表示向量 (\mathbf{a}
) 和 (\mathbf{b}
) 的模(长度)。 - (
\theta
) 表示向量 (\mathbf{a}
) 和 (\mathbf{b}
) 之间的夹角。
从几何上看,点积的结果反映了一个向量在另一个向量方向上的投影程度。具体来说:
- 如果 (
\mathbf{a}
) 和 (\mathbf{b}
) 的方向相同或接近(即 (\theta
) 较小),点积的值就会较大。 - 如果 (
\mathbf{a}
) 和 (\mathbf{b}
) 垂直(即 (\theta = 90^\circ
)),点积的值为0,因为在这种情况下,两个向量没有任何共同的方向成分。 - 如果 (
\mathbf{a}
) 和 (\mathbf{b}
) 方向相反(即 (\theta = 180^\circ
)),点积会是负数。
「你用两支笔代表两个向量,然后摆一下就有感觉了。」
点积在注意力机制中的作用
在注意力机制中,点积用于计算查询向量 ( q_t
) 和键向量 ( k_i
) 之间的相似度:
e_{t,i} = q_t \cdot k_i
这里的点积可以理解为解码器在生成目标词时,计算当前查询向量 ( q_t
) 与编码器输出的每个键向量 ( k_i
) 之间的相似度。相似度越高,说明这两个向量方向更接近,对应的源单词对生成目标词的重要性也越高。
通过这种方式,点积帮助注意力机制确定在生成目标词时,应该对源句子的哪些部分(即哪些键向量)施加更多的关注。
小结
点积是一个用于计算两个向量之间相似度的数学操作,在几何上反映了一个向量在另一个向量方向上的投影程度。在注意力机制中,点积用于衡量解码器生成目标词时,对源句子中不同部分的关注度,是计算注意力权重的关键步骤。
插:Softmax函数
Softmax 函数是机器学习和深度学习中常用的一种激活函数,尤其是在多分类问题中。它的主要作用是将一个未归一化的数值向量(logits)转换为一个概率分布,使得这些数值的总和为1。Softmax 函数常用于神经网络的输出层,以便输出一个概率分布,用于确定输入属于哪个类别。
数学表达式
假设有一个向量 (\mathbf{z} = [z_1, z_2, \dots, z_n]
),Softmax 函数的定义如下:
\text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^n e^{z_j}}
其中,(z_i
) 表示输入向量中的第 (i
) 个元素,(e
) 是自然对数的底(约为 2.71828)。
「从https://botpenguin.com/glossary/softmax-function 拿来了一张图。」
直观理解
Softmax 函数通过计算每个输入值的指数(exponential),然后将其归一化,使所有输出的总和为1。这个过程会将较大的输入值放大,而将较小的输入值缩小,从而使得最大值所对应的类别具有更高的概率。这也意味着,如果某个类别的输入值明显大于其他类别,那么 Softmax 的输出会集中在该类别上,接近于1,而其他类别的概率接近于0。
应用场景
多分类问题:Softmax 函数广泛应用于神经网络的最后一层,特别是在处理多分类问题时。每个输出节点对应一个类别,Softmax 函数将所有输出节点的值转换为概率,这些概率的总和为1,表示输入属于各个类别的可能性。
神经网络的损失函数:在使用 Softmax 作为输出层时,通常结合使用交叉熵损失函数(Cross-Entropy Loss)(7),因为交叉熵能够更好地衡量预测的概率分布与真实分布之间的差异。
简单来说,交叉熵损失函数会通过计算模型预测的概率分布和真实标签的概率分布之间的差异来得出一个损失值。这个值越小,说明模型的预测越接近真实标签;反之,值越大,说明模型的预测与真实标签差距越大。
你可以把交叉熵想象成这样一个场景:假设你参加了一场考试,答案是标准答案,而你的回答是模型的预测。交叉熵损失函数就像一个严格的老师,他会根据你和标准答案的差异来打分。如果你答得越接近正确答案(预测越准确),得分(损失)就越低。如果答得偏差很大(预测不准确),得分(损失)就高了。
因此,训练模型的目标就是不断调整模型的参数,让交叉熵损失越来越小,从而使模型的预测越来越准确。
举个例子
假设我们有三个类别的预测分数(logits):([2.0, 1.0, 0.1]
)。首先计算每个分数的指数值:
e^{2.0} \approx 7.389, \quad e^{1.0} \approx 2.718, \quad e^{0.1} \approx 1.105
然后求和:
7.389 + 2.718 + 1.105 \approx 11.212
接下来,计算每个类别的概率:
\text{Softmax}(2.0) = \frac{7.389}{11.212} \approx 0.659, \quad \text{Softmax}(1.0) = \frac{2.718}{11.212} \approx 0.242, \quad \text{Softmax}(0.1) = \frac{1.105}{11.212} \approx 0.099
最终的概率分布是 ([0.659, 0.242, 0.099]
),意味着第一个类别的预测概率最高。
Softmax 函数可以将模型的输出转化为易于理解的概率,方便我们进行分类决策。
「我们平时抽卡都讲究概率,概率是0-1的范围,再来联系一下刚刚的计算,回看一下那张图,是不是最终所有数值经过这个函数后都可以变成[0,1]内的数了。这样一来就方便多了嘛。」
插:注意力机制的进一步理解
建议结合一下自己平时翻译中英文的经验阅读这个部分。
语言翻译中的对齐过程
在语言翻译中,对齐是将源句子中的单词与翻译后句子中的单词匹配的过程。在这里,我们将“我是一只猫娘”翻译为英文“I am a catgirl”。在seq2seq模型中,编码器将输入句子编码为隐藏向量序列,解码器则根据这些隐藏向量生成目标句子。
为了更好地理解注意力机制的工作原理,我们来看一下对齐矩阵。假设翻译过程中产生的对齐矩阵如下:
I am a catgirl
我 0.90 0.05 0.03 0.02
是 0.05 0.88 0.05 0.02
一只 0.02 0.07 0.80 0.11
猫娘 0.03 0.00 0.12 0.85
注意力机制与seq2seq模型的结合
在seq2seq模型中,注意力机制允许解码器在生成目标句子时,动态关注源句子的不同部分。在这个例子中,当解码器尝试生成英文单词“I”时,它大部分注意力集中在中文单词“我”上(权重0.90),这意味着解码器认为“我”是“I”的最佳翻译来源。同样,当生成“am”时,注意力主要集中在“是”上(权重0.88),而当生成“catgirl”时,注意力集中在“猫娘”上(权重0.85)。
软对齐与硬对齐
注意到在生成“a”和“catgirl”时,对应的中文单词“一只”和“猫娘”都有一些较小的注意力权重。这就是所谓的“软”对齐,而不是“硬”对齐(硬对齐意味着一个注意力权重为1,其他为0)。“软”注意力权重允许模型在生成目标词时参考多个源词的上下文,而不仅仅是一个。这种机制在处理多对多对齐(如“look it up”对应“cherchez-le”)时尤为重要。
神经网络的可解释性
这种注意力权重的分布使得模型在翻译时能够更加灵活和精确。例如,在这里“我”与“I”高度对齐(权重0.90),表明模型识别了它们的直接对应关系。而“猫娘”与“catgirl”的对齐显示出非对角线上的高权重,这表明模型考虑了上下文,而不是简单地逐字翻译。
注意力机制帮助我们理解模型是如何在每一步解码时选择合适的源单词的。这不仅提高了翻译的准确性,也增加了模型的可解释性。
从注意力到自注意力
对于RNN的缺陷问题,可以使用注意力机制(Attention Mechanism)来补充,但自注意力机制(Self-Attention Mechanism)能够在某些方面提供更好的解决方案。下面我详细说明两者在应对RNN缺陷时的作用和区别。
RNN的缺陷
RNN(Recurrent Neural Networks)在处理序列数据时存在一些明显的缺陷,主要包括以下几点:
- 长依赖问题:RNN在处理长序列时,容易出现梯度消失或梯度爆炸,导致模型在捕捉序列中远距离依赖关系时效果较差。
- 并行化困难:由于RNN依赖序列的顺序进行逐步计算,所以很难并行化,训练时间较长。
- 有限的上下文信息:标准的RNN只能利用之前的上下文信息,无法直接考虑未来的输入信息。
注意力机制如何补充RNN的缺陷
- 解决长依赖问题:通过引入注意力机制,RNN可以在每一步生成输出时参考整个输入序列,而不是只依赖最近的几个状态。这样可以有效捕捉远距离的依赖关系。
- 增强上下文建模:注意力机制允许模型在生成每个输出时选择性地关注输入序列的不同部分,从而提高模型在复杂序列任务中的表现。
尽管注意力机制可以缓解RNN的一些缺陷,但它仍然依赖RNN的序列处理方式,仍然会受到RNN本身的一些局限,比如并行化困难。
自注意力机制的优势
自注意力机制作为注意力机制的扩展,直接对序列中的所有元素进行全局依赖建模,具有以下优势:
- 全局依赖建模:自注意力机制能够同时考虑序列中的所有元素,而不需要像RNN那样逐步处理,从而解决了长依赖问题。
- 并行化能力:自注意力机制可以并行计算,不依赖于序列顺序,因此训练和推理速度更快。
- 灵活性更高:在自注意力机制中,每个元素的表示都会根据序列中的其他所有元素进行更新,这种灵活性使得模型在处理复杂的依赖关系时更加有效。
小结
- 注意力机制(Attention Mechanism) 可以补充RNN的缺陷,特别是在长依赖建模和上下文信息整合方面。但它并不能完全解决RNN的所有问题,尤其是并行化困难。
- 自注意力机制(Self-Attention Mechanism) 在应对RNN的缺陷方面更为彻底。它不仅能够解决长依赖问题,还能提高计算效率和灵活性。自注意力机制是Transformer模型的核心,现已在自然语言处理等领域广泛取代了RNN。
因此,如果要彻底克服RNN的缺陷,尤其是在处理长序列和需要高效并行化的任务中,自注意力机制通常是更好的选择。
自注意力机制(Self-Attention Mechanism)
自注意力机制(Self-Attention Mechanism)是深度学习,尤其是在自然语言处理(NLP)领域中广泛应用的一种技术。核心在于,它可以让模型在处理某个词语(或元素)时,不仅仅考虑这个词语本身的信息,还会考虑与其相关的其他词语的信息。简单来说,就是在处理一个句子时,模型会让每个词语与句子中的其他所有词语进行“交流”,从而决定它们之间的重要性或相关性。
自?
自注意力机制中的“自”字,指的是在处理一个序列(比如一句话中的所有词语)时,序列中的每个元素(如一个词)都会与该序列中的所有其他元素进行“自我”比较和关联。这种“自我”比较是针对同一个输入序列的内部元素进行的,而不是和其他外部序列比较。
具体来说,在自然语言处理中,当我们用自注意力机制处理一个句子时,句子中的每个词语都会生成三个向量:查询向量 ( Q )、键向量 ( K ) 和值向量 ( V )。这些向量用来衡量一个词语(元素)如何与同一句子中的其他词语产生联系。例如,句子中的第一个词语不仅仅考虑自身的信息,还会与其他所有词语通过查询向量和键向量进行对比,决定它应该“关注”哪些词语(以及关注的程度),从而综合出这个词语的最终表示。
因此,“自注意力”中的“自”指的是这种机制只是在一个序列的内部进行操作,元素与自身序列中的其他元素“交流”并计算重要性,而不是与外部数据或序列交互。这种自我参考的方式使得模型在理解上下文和捕捉复杂的词语关系时非常有效。
「有人还记得语境吗?还记得context吗?」
查询(Query)、键(Key)、值(Value)向量
在自注意力机制中,输入的每一个元素(如一个词)都会生成三个向量:查询向量 ( Q
)、键向量 ( K
) 和值向量 ( V
)。这些向量是通过线性变换得到的。假设输入为矩阵 ( X
),那么可以用下面的公式生成这些向量:
Q = XW_Q, \quad K = XW_K, \quad V = XW_V
这里的 ( W_Q
)、( W_K
)、( W_V
) 是训练过程中学习到的权重矩阵。每个元素的 ( Q
)、( K
)、( V
) 向量可以看作是该元素在“查询”其他元素、“描述”自身以及“携带”信息方面的特征表示。
计算注意力分数
对于序列中的每一个元素,它需要与其他所有元素进行比较,以决定应该关注哪些元素。这个比较过程是通过计算查询向量 ( Q
) 和键向量 ( K
) 之间的点积来实现的:
\text{Attention Score}_{i,j} = Q_i \cdot K_j
其中 ( Q_i
) 是第 ( i
) 个元素的查询向量,( K_j
) 是第 ( j
) 个元素的键向量。点积的结果表示了元素 ( i
) 和元素 ( j
) 之间的相似度。注意力分数越高,表示两个元素的相关性越强。
归一化(Softmax)
为了使这些注意力分数更易于处理,并且便于模型进行加权求和,我们将它们通过 Softmax 函数进行归一化:
\alpha_{i,j} = \frac{\exp(\text{Attention Score}_{i,j})}{\sum_{k=1}^{n} \exp(\text{Attention Score}_{i,k})}
这样,每个元素的注意力分数就变成了一个概率分布,表示当前元素对序列中每个其他元素的“注意力权重”。
加权求和
归一化后的注意力分数会用来对值向量 ( V
) 进行加权求和,从而得到最终的输出表示:
\text{Output}_i = \sum_{j=1}^{n} \alpha_{i,j} V_j
这一步的结果是,元素 ( i
) 的最终表示将由所有其他元素的值向量按照它们的重要性加权组合而成。也就是说,模型会根据注意力分数从其他元素那里“借用”信息来丰富当前元素的表示。
举例计算
我来详细讲解一下如何使用自注意力机制计算“我是一只猫娘”这句话中的自注意力。我们将详细分步骤进行数学计算。
词嵌入(Word Embedding)
首先,我们需要将每个词转化为一个向量(词嵌入)。假设“我”,“是”,“一”,“只”,“猫”,“娘”这六个词的嵌入向量分别是:
- (
\mathbf{X}_1 = \text{我}
) - (
\mathbf{X}_2 = \text{是}
) - (
\mathbf{X}_3 = \text{一}
) - (
\mathbf{X}_4 = \text{只}
) - (
\mathbf{X}_5 = \text{猫}
) - (
\mathbf{X}_6 = \text{娘}
)
这些向量可以是预训练的词嵌入,比如通过Word2Vec或BERT生成的。为了简单起见,我们假设每个词向量是一个三维向量,如 ( \mathbf{X}_i \in \mathbb{R}^3
)。
生成查询、键和值向量
接下来,我们对每个词向量 ( \mathbf{X}_i
) 应用线性变换来生成查询向量 ( \mathbf{Q}_i
)、键向量 ( \mathbf{K}_i
) 和值向量 ( \mathbf{V}_i
)。假设这些线性变换的权重矩阵分别是 ( \mathbf{W}_Q
)、( \mathbf{W}_K
) 和 ( \mathbf{W}_V
),它们的维度都是 ( \mathbb{R}^{3 \times 3}
)。
计算公式为:
\mathbf{Q}_i = \mathbf{X}_i \mathbf{W}_Q, \quad \mathbf{K}_i = \mathbf{X}_i \mathbf{W}_K, \quad \mathbf{V}_i = \mathbf{X}_i \mathbf{W}_V
我们假设 ( \mathbf{W}_Q
)、( \mathbf{W}_K
)、( \mathbf{W}_V
) 的矩阵如下:
\mathbf{W}_Q = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}, \quad
\mathbf{W}_K = \begin{bmatrix} 0.5 & 0 & 0 \\ 0 & 0.5 & 0 \\ 0 & 0 & 0.5 \end{bmatrix}, \quad
\mathbf{W}_V = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}
假设输入词向量如下:
\mathbf{X}_1 = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}, \quad
\mathbf{X}_2 = \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix}, \quad
\mathbf{X}_3 = \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix}, \quad
\mathbf{X}_4 = \begin{bmatrix} 1 \\ 1 \\ 0 \end{bmatrix}, \quad
\mathbf{X}_5 = \begin{bmatrix} 0 \\ 1 \\ 1 \end{bmatrix}, \quad
\mathbf{X}_6 = \begin{bmatrix} 1 \\ 0 \\ 1 \end{bmatrix}
根据公式,计算查询、键和值向量:
\mathbf{Q}_1 = \mathbf{X}_1 \mathbf{W}_Q = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}, \quad
\mathbf{K}_1 = \mathbf{X}_1 \mathbf{W}_K = \begin{bmatrix} 0.5 \\ 0 \\ 0 \end{bmatrix}, \quad
\mathbf{V}_1 = \mathbf{X}_1 \mathbf{W}_V = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}
类似地,计算其他词的向量。
计算注意力分数(Attention Score)
接下来,对于序列中的每个词,我们计算它的查询向量与其他所有词的键向量之间的点积。以词“我”的注意力计算为例:
\text{Attention Score}_{1,1} = \mathbf{Q}_1 \cdot \mathbf{K}_1 = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} \cdot \begin{bmatrix} 0.5 \\ 0 \\ 0 \end{bmatrix} = 0.5
\text{Attention Score}_{1,2} = \mathbf{Q}_1 \cdot \mathbf{K}_2 = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} \cdot \begin{bmatrix} 0 \\ 0.5 \\ 0 \end{bmatrix} = 0
计算其他词之间的注意力分数。
归一化注意力分数(Softmax)
然后,我们将每个词的注意力分数通过 Softmax 进行归一化,得到注意力权重 ( \alpha_{i,j}
)。假设得到如下结果(为简化计算,我们跳过具体的 Softmax 过程):
\alpha_{1,1} = 0.4, \quad \alpha_{1,2} = 0.1, \quad \alpha_{1,3} = 0.1, \quad \alpha_{1,4} = 0.1, \quad \alpha_{1,5} = 0.2, \quad \alpha_{1,6} = 0.1
加权求和值向量
最后,我们使用归一化后的注意力权重对值向量进行加权求和,得到最终的输出向量。
\text{Output}_1 = \sum_{j=1}^{6} \alpha_{1,j} \mathbf{V}_j = 0.4 \cdot \mathbf{V}_1 + 0.1 \cdot \mathbf{V}_2 + 0.1 \cdot \mathbf{V}_3 + 0.1 \cdot \mathbf{V}_4 + 0.2 \cdot \mathbf{V}_5 + 0.1 \cdot \mathbf{V}_6
假设上面的 ( \mathbf{V}_i
) 向量代入数值后,最终得到的输出可能是一个新的三维向量,例如 ( \text{Output}_1 = \begin{bmatrix} 0.7 \\ 0.4 \\ 0.2 \end{bmatrix}
)。
这个输出向量 ( \text{Output}_1
) 代表了“我”这个词在综合考虑了整句话的上下文后得到的最终表示。
通过类似的计算过程,我们可以得到句子中每个词的最终表示。自注意力机制的强大之处就在于,它让每个词都能够根据整句话中的其他词的信息来调整自身的表示,这对于捕捉复杂的上下文信息非常有效。
插:缩放点积注意力(Scaled Dot-Product Attention)
「论文里面的图,标题是:Scaled Dot-Product Attention」
什么是点积注意力?
在自注意力机制中,我们使用了“点积注意力”来衡量输入序列中的不同部分之间的相似性。具体来说,我们会计算“查询”(query)和“键”(key)之间的点积。查询和键都是从输入序列中生成的向量,点积的结果代表了查询和键之间的相似度,相似度越高,表示查询对这个键(也就是对应的输入部分)的“注意力”越强。
为什么要缩放?
当我们在计算点积的时候,如果这些向量的维度(通常用 (d_k
) 表示)很大,点积的结果往往也会变得很大。这就会导致一个问题:当我们在使用softmax函数对这些结果进行归一化(即把结果变成一个概率分布)时,大的点积结果会让softmax函数变得非常“激进”,即可能只有极少数的部分会有非零的注意力值,而大部分部分的注意力值都会趋近于零,这样就失去了自注意力机制的效果。
为了防止这种情况的发生,我们引入了一个缩放因子 ( \frac{1}{\sqrt{d_k}}
)。通过这个缩放因子,我们让点积的结果不会过于大,从而使softmax函数的输出更加平滑,模型在处理不同部分的注意力时更加均匀合理。
公式解释
下面我们来解释公式:
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V
- Q:查询矩阵,包含所有的查询向量。
- K:键矩阵,包含所有的键向量。
- V:值矩阵,包含所有的值向量(我们最终关注的部分)。
- (
QK^T
):计算查询和键的点积,得到一个相似度矩阵。 - (
\frac{1}{\sqrt{d_k}}
):对点积结果进行缩放,以避免数值过大。 - softmax:将缩放后的相似度矩阵转化为概率分布。
- V:使用得到的概率分布加权各个值,最终得到注意力的输出。
为什么缩放点积注意力更有效?
缩放点积注意力在计算上非常高效,特别是当我们使用矩阵运算时,这种方法可以利用现代硬件的加速功能(如GPU)。相比于另一种常用的“加性注意力”(additive attention),缩放点积注意力不仅计算速度更快,还能更好地处理大维度的向量,因此在实践中非常受欢迎。
总的来说,缩放点积注意力通过一种简单的缩放方式,解决了点积注意力在高维空间中表现不佳的问题,从而让模型在处理长序列数据时更加稳定和有效。
图中有什么?
我们现在来看看最开始的图片。
这张图展示了“缩放点积注意力机制”的主要步骤。我将逐步解释每个部分的作用:
MatMul(矩阵乘法):
- 这里的矩阵乘法指的是对查询矩阵 (
Q
) 和键矩阵 (K
) 进行点积运算。具体来说,这是在计算每个查询向量与所有键向量之间的相似度,结果会生成一个相似度矩阵。
- 这里的矩阵乘法指的是对查询矩阵 (
SoftMax:
- 在得到相似度矩阵之后,我们对其应用SoftMax函数。这会将每个查询对应的相似度值转换成一个概率分布,即所有相似度的值加起来为1。SoftMax可以帮助模型更加“专注”于更相关的信息,同时对不相关的信息赋予更小的权重。
Mask(可选):
- Mask通常用于处理序列数据(比如文本中的句子),特别是在处理不同长度的序列时。Mask操作的目的是避免模型在计算注意力时考虑到不相关或无效的部分(比如句子中的填充符号)。Masking可以确保在计算注意力时,某些位置的值不会对结果产生影响。
Scale(缩放):
- 这个步骤就是我们之前讨论的缩放操作。为了避免点积结果值过大,我们将点积的结果除以 (
\sqrt{d_k}
),这可以使模型的计算更加稳定,使SoftMax函数更为平滑。
- 这个步骤就是我们之前讨论的缩放操作。为了避免点积结果值过大,我们将点积的结果除以 (
MatMul(矩阵乘法):
- 最后一步是再次进行矩阵乘法,这次是将经过缩放和SoftMax后的相似度矩阵与值矩阵 (
V
) 进行乘法运算。这个步骤的作用是通过相似度权重对值进行加权求和,得到最终的注意力输出。
- 最后一步是再次进行矩阵乘法,这次是将经过缩放和SoftMax后的相似度矩阵与值矩阵 (
小结
这个图展示了“缩放点积注意力机制”的完整计算流程,从计算相似度开始,到生成最终的加权值输出。整个过程的核心思想是通过点积、缩放和SoftMax来计算各个部分的重要性,并根据这个重要性调整模型的输出。
自注意力机制和多头注意力机制
自注意力机制(Self-Attention)与多头注意力机制(Multi-Head Attention)是两个相关但独立的概念。虽然多头注意力机制是基于自注意力机制的一个扩展和增强,但自注意力机制本身可以独立存在并且不一定依赖多头注意力机制。
名词解释
自注意力机制是处理序列数据的一种方法,允许模型在处理每个输入时关注同一序列中的其他部分。它通过计算输入序列中每个元素与其他所有元素之间的相似度来获得每个元素对其他元素的注意力权重,从而为序列中的每个元素生成一个新的表示。这个过程可以在不借助多头注意力的情况下独立完成。
多头注意力机制是在自注意力机制的基础上进行的扩展。它的基本思想是将输入序列分成多个“头”,每个头都独立地进行自注意力计算。这样,模型可以从不同的“头”中捕捉到不同类型的关系和模式。多头注意力机制通过这种方式丰富了模型的表达能力,使其能够同时关注序列中的不同部分,并结合多种上下文信息。
二者的关系
- 自注意力机制 是计算注意力权重和加权求和的基本过程,它是多头注意力机制的核心组成部分。
- 多头注意力机制 使用多个平行的自注意力计算,然后将它们的结果组合起来,使模型可以从多个角度关注输入序列。
小结
自注意力机制是独立的,可以在没有多头注意力机制的情况下使用。而多头注意力机制则是为了增强自注意力机制的效果,将多个自注意力的结果组合在一起,以提升模型的性能和表达能力。在实际应用中,多头注意力机制被广泛应用于Transformer模型等结构中,因为它能够更好地捕捉序列中复杂的依赖关系。
Transformer中的多头注意力机制(Multi-Head Attention)
「我们现在在这个部分。」
「论文里面的图,标题是Multi-Head Attention。」
这张图展示了多头注意力机制(Multi-Head Attention)的结构,这在现代的自然语言处理模型(如Transformer)中非常重要。
图中的内容
Linear(线性变换):
- 图中的多个“Linear”模块表示线性变换层。在多头注意力机制中,我们首先对查询(Query,Q)、键(Key,K)、值(Value,V)进行线性变换。具体来说,每个输入会通过一个线性层,这层将输入投影到不同的低维空间。
- 对应到文本中,就是利用不同的权重矩阵 (
W_i^Q
)、(W_i^K
) 和 (W_i^V
) 对 Q、K 和 V 进行线性投影,分别映射到 (d_k
) 和 (d_v
) 维度。
Scaled Dot-Product Attention(缩放点积注意力):
- 经过线性变换后的查询、键和值被输入到“Scaled Dot-Product Attention”模块中。这个模块计算注意力权重,并对值(Value)进行加权求和,输出注意力值。
- 在图中,注意力机制是并行进行的,因为有多个头(即多次计算注意力),每个头都会独立计算自己的注意力分数。
Concat(拼接):
- 在所有头的注意力值计算完成后,这些值会被拼接(Concat)到一起。这个步骤将各个头的输出组合成一个完整的向量。
- 对应到文本中,这一步描述的是 (
\text{Concat}(head_1, \dots, head_h)
) 的操作。
Linear(线性变换):
- 拼接后的向量会再次经过一个线性层,这一层将多头注意力的输出重新映射到模型的原始维度上(即 (
d_{\text{model}}
) 维度)。 - 对应到文本中的操作是 (
W_O
) 线性层将拼接的结果映射回原来的维度。
- 拼接后的向量会再次经过一个线性层,这一层将多头注意力的输出重新映射到模型的原始维度上(即 (
结合论文的解释
多头注意力机制的核心思想是将查询、键和值分别投影到不同的低维子空间中,然后在这些子空间内独立执行注意力计算。具体来说:
投影:将 Q、K、V 分别线性投影到 (
d_k
)、(d_k
)、(d_v
) 维度,这样每个头(head)会在一个较低维度的子空间中进行计算。多头并行计算:不同头在并行的子空间中执行缩放点积注意力(Scaled Dot-Product Attention),这样模型可以在多个不同的表示子空间中同时关注输入的不同部分。
拼接与再投影:所有头的输出会被拼接起来,然后通过一个线性层再投影到模型的原始维度。这一步使得模型可以综合不同头的注意力信息,同时保持计算效率。
优势:相比单头注意力,多头注意力允许模型在不同的子空间上关注不同的特征,这避免了单一注意力头对所有信息的平均处理带来的信息损失。
总结来说,多头注意力机制通过将注意力计算分成多个并行的子空间计算,极大地增强了模型对输入特征的表达能力,同时由于每个子空间的维度较小,计算效率也得到了保证。
计算举例
那我们就用“我是一只猫娘”这句话作为例子,来详细解释多头注意力机制的计算过程。
输入的表示
首先,我们要将句子“我是一只猫娘”转换为向量表示。假设我们使用的是一个字级别的嵌入模型(如Word2Vec或BERT),每个字会被映射为一个固定长度的向量。
假设我们的嵌入维度为 (d_{\text{model}} = 8
),那么这句话每个字(“我”、“是”、“一”、“只”、“猫”、“娘”)都对应一个8维的向量。
例如(假设这些向量已经过训练得到,随机举例):
- “我”:
\mathbf{Q_1} = [0.2, -0.1, 0.4, 0.3, 0.5, -0.3, 0.1, 0.2]
- “是”:
\mathbf{Q_2} = [0.1, 0.3, -0.4, 0.2, 0.6, -0.2, 0.1, 0.0]
- … 其他字也会有类似的8维向量表示。
线性投影
在多头注意力中,每个字的向量(Query、Key、Value)会通过线性变换投影到不同的子空间。假设我们有 (h = 2
) 个头,每个头的维度为 (d_k = d_v = \frac{d_{\text{model}}}{h} = 4
) 。
对于每个头,我们会有不同的投影矩阵:
Head 1:
- (
W^Q_1
):将8维的Query投影到4维空间 - (
W^K_1
):将8维的Key投影到4维空间 - (
W^V_1
):将8维的Value投影到4维空间
- (
Head 2:
- (
W^Q_2
):将8维的Query投影到另一个4维空间 - (
W^K_2
):将8维的Key投影到另一个4维空间 - (
W^V_2
):将8维的Value投影到另一个4维空间
- (
例如,假设 (W^Q_1
) 矩阵为(随机举例):
W^Q_1 =
\begin{bmatrix}
0.1 & 0.3 & 0.1 & -0.1 \\
-0.2 & 0.0 & 0.2 & 0.1 \\
... & ... & ... & ... \\
0.3 & -0.2 & 0.4 & 0.2
\end{bmatrix}
每个字的Query会经过这个矩阵乘法变成4维的向量。
计算注意力分数
对于Head 1,计算缩放点积注意力时,我们要用Key的转置与Query做点积,然后除以 ( \sqrt{d_k}
) (这个例子中是 (\sqrt{4} = 2
)),再应用Softmax得到权重。
例如,假设“我”的Query(经过投影后的4维向量)是:
\mathbf{Q_1} = [0.5, -0.1, 0.3, 0.2]
“猫”的Key(经过投影后的4维向量)是:
\mathbf{K_5} = [0.4, 0.1, -0.3, 0.6]
它们的点积为:
\mathbf{Q_1} \cdot \mathbf{K_5} = (0.5 \times 0.4) + (-0.1 \times 0.1) + (0.3 \times -0.3) + (0.2 \times 0.6) = 0.2 - 0.01 - 0.09 + 0.12 = 0.22
然后进行缩放:
\frac{0.22}{2} = 0.11
对所有字计算出的注意力分数进行Softmax归一化,例如“我”对其他字的注意力分数为:
\text{Softmax}([0.11, -0.05, 0.22, 0.13, 0.09, 0.17]) = [0.17, 0.12, 0.21, 0.19, 0.16, 0.15]
加权求和
得到的注意力权重将用于对Value进行加权求和。假设“猫”的Value(经过投影后的4维向量)是:
\mathbf{V_5} = [0.2, -0.3, 0.1, 0.5]
那么“我”的注意力加权后的Value为:
\text{Attention}_\text{我} = 0.21 \times \mathbf{V_1} + 0.19 \times \mathbf{V_2} + \dots + 0.15 \times \mathbf{V_6}
多头拼接与线性变换
对所有头计算完注意力值后,我们将它们的输出拼接在一起(每个头输出4维,拼接后变为8维),然后通过一个线性层将拼接后的向量投影回原始的8维空间。
在前面的步骤中,我们通过两组不同的投影矩阵(即两个注意力头)计算出了“我”对“猫”的注意力加权值。假设经过两个头计算后,“我”的注意力加权后的Value向量分别是:
- Head 1 的输出:
\mathbf{V}_{\text{Head 1}} = [0.15, 0.05, -0.20, 0.30]
- Head 2 的输出:
\mathbf{V}_{\text{Head 2}} = [0.10, -0.10, 0.25, 0.40]
拼接(Concatenation)
我们将这两个头的输出拼接在一起,形成一个新的向量:
\text{Concat}(\mathbf{V}_{\text{Head 1}}, \mathbf{V}_{\text{Head 2}}) = [0.15, 0.05, -0.20, 0.30, 0.10, -0.10, 0.25, 0.40]
这个拼接后的向量维度是8维(因为每个头的输出都是4维,我们有两个头)。
线性变换
接下来,这个拼接后的8维向量会通过一个线性层(由权重矩阵 (W_O
) 表示),将其投影回原始的8维空间。这一步的公式如下:
\mathbf{O} = \text{Concat}(\mathbf{V}_{\text{Head 1}}, \mathbf{V}_{\text{Head 2}}) \times W_O
假设权重矩阵 (W_O
) 的维度是 (8 \times 8
),表示从8维空间到8维空间的线性映射。我们假设 (W_O
) 如下(举例):
W_O =
\begin{bmatrix}
0.1 & -0.3 & 0.2 & 0.0 & 0.1 & -0.2 & 0.1 & 0.3 \\
-0.2 & 0.4 & 0.0 & 0.1 & 0.3 & -0.1 & 0.2 & -0.3 \\
0.1 & -0.2 & 0.3 & 0.1 & 0.0 & 0.4 & -0.1 & 0.2 \\
0.0 & 0.1 & -0.2 & 0.3 & -0.3 & 0.1 & 0.2 & 0.0 \\
0.3 & -0.1 & 0.4 & 0.2 & 0.1 & 0.0 & -0.3 & 0.1 \\
-0.1 & 0.0 & 0.1 & -0.3 & 0.4 & 0.2 & 0.1 & 0.0 \\
0.2 & -0.4 & 0.1 & 0.0 & -0.2 & 0.3 & 0.1 & 0.2 \\
-0.3 & 0.2 & 0.0 & 0.1 & 0.3 & -0.1 & 0.2 & 0.4
\end{bmatrix}
我们用这个矩阵与拼接后的向量相乘,得到最终的输出向量 (\mathbf{O}
):
\mathbf{O} =
\begin{bmatrix}
0.15 & 0.05 & -0.20 & 0.30 & 0.10 & -0.10 & 0.25 & 0.40
\end{bmatrix}
\times
\begin{bmatrix}
0.1 & -0.3 & 0.2 & 0.0 & 0.1 & -0.2 & 0.1 & 0.3 \\
-0.2 & 0.4 & 0.0 & 0.1 & 0.3 & -0.1 & 0.2 & -0.3 \\
0.1 & -0.2 & 0.3 & 0.1 & 0.0 & 0.4 & -0.1 & 0.2 \\
0.0 & 0.1 & -0.2 & 0.3 & -0.3 & 0.1 & 0.2 & 0.0 \\
0.3 & -0.1 & 0.4 & 0.2 & 0.1 & 0.0 & -0.3 & 0.1 \\
-0.1 & 0.0 & 0.1 & -0.3 & 0.4 & 0.2 & 0.1 & 0.0 \\
0.2 & -0.4 & 0.1 & 0.0 & -0.2 & 0.3 & 0.1 & 0.2 \\
-0.3 & 0.2 & 0.0 & 0.1 & 0.3 & -0.1 & 0.2 & 0.4
\end{bmatrix}
最终得到的8维向量为 (\mathbf{O}
):
\mathbf{O} = [0.12, -0.18, 0.35, 0.10, 0.05, -0.22, 0.17, 0.25]
这个向量就是最终的多头注意力机制输出,已经结合了两个头的注意力信息,并通过线性层回到了原始的8维空间。
公式总结
多头注意力的最终输出 (\mathbf{O}
) 的计算公式可以表示为:
\mathbf{O} = \text{Concat}(\mathbf{V}_{\text{Head 1}}, \dots, \mathbf{V}_{\text{Head h}}) \times W_O
其中:
- (
\mathbf{V}_{\text{Head i}}
) 是第 (i
) 个注意力头的输出。 - (
W_O
) 是线性层的权重矩阵,用于将拼接后的向量投影回原始维度。
通过这个过程,模型可以有效地整合不同注意力头的输出,同时保持向量的维度和信息的完整性。
最终输出
最后,模型会基于拼接后的多头注意力结果生成新的表示,用于后续的处理。
小结
- 多头注意力:利用多个注意力头让模型可以在不同的子空间中平行处理信息。
- 注意力机制:通过点积和Softmax计算注意力权重,获取输入句子中每个字对其他字的关注程度。
- 线性投影:每个头在计算前后都需要进行线性投影,保证计算的多样性与信息的综合利用。
通过多头注意力机制,模型能够更加细致地捕捉句子中的关系与上下文信息,为下游任务提供更丰富的特征表达。
残差连接(Add)和层归一化(Norm)
省流:残差连接(Add)和层归一化(Norm),帮助模型更稳定、更有效地训练。
「我们现在在这个部分。」
在Transformer模型中,残差连接(Add) 和 层归一化(Layer Normalization, Norm) 是关键的技术点,它们在提升模型的训练稳定性和效果方面起到了重要作用。
残差连接(Residual Connection, Add)
残差连接最早由ResNet引入,用来解决深层神经网络中梯度消失或梯度爆炸的问题。深层网络往往在训练过程中出现梯度逐渐消失的情况,导致网络训练效果不佳。残差连接通过引入捷径路径,使得信息在网络中可以直接跳过若干层传递,从而减轻了梯度消失的问题。
在Transformer中,残差连接的作用非常类似。具体来说,在Transformer的每个子层中,输入会绕过当前子层的处理步骤,通过一条捷径直接相加到子层的输出上。
具体公式为:
\text{Output} = \text{Layer}(X) + X
其中,( X
) 是输入,(\text{Layer}(X)
) 是当前子层对 ( X
) 的处理结果。
这种方式有几个好处:
- 信息保留:通过直接将输入加到输出上,即使子层的学习效果不佳,原始信息依然可以在网络中传递,减轻了信息丢失的风险。
- 梯度流动更顺畅:由于梯度可以沿着捷径路径直接反向传播到前面的层,这使得深层网络中梯度消失的风险降低。
- 加速收敛:残差连接使得网络更容易学习到恒等映射,从而使得网络收敛更快。
2. 层归一化(Layer Normalization, Norm)
层归一化是一种归一化技术,专门用于解决神经网络中不同层之间的数值分布差异问题。它通过对每个层的激活值进行标准化,使得输出具有零均值和单位方差,从而使得模型在训练过程中更加稳定。
层归一化的计算过程如下:
- 对每一个训练样本的激活值 (
h_i
)(通常是一个向量),计算它们的均值 (\mu
) 和标准差 (\sigma
):
\mu = \frac{1}{H} \sum_{i=1}^{H} h_i
\sigma = \sqrt{\frac{1}{H} \sum_{i=1}^{H} (h_i - \mu)^2}
其中,( H
) 是激活值的维度。
- 然后,对每个激活值进行标准化:
\hat{h}_i = \frac{h_i - \mu}{\sigma}
- 最后,进行缩放和平移(通过可学习的参数 (
\gamma
) 和 (\beta
)):
\text{Norm}(h_i) = \gamma \hat{h}_i + \beta
在Transformer中,层归一化通常在残差连接之后进行,即:
\text{Output} = \text{LayerNorm}(\text{Layer}(X) + X)
Add & Norm 在 Transformer 中的应用
在Transformer的编码器(Encoder)和解码器(Decoder)中,每一层都由多头自注意力(Multi-Head Self-Attention)或多头注意力(Multi-Head Attention)以及前馈网络(Feed-Forward Network, FFN)组成。残差连接和层归一化分别应用在这些组件之后。
以编码器为例,层的结构可以表示为:
- 多头自注意力层:
\text{Output} = \text{LayerNorm}(\text{MultiHeadSelfAttention}(X) + X)
- 前馈网络层:
\text{Output} = \text{LayerNorm}(\text{FeedForwardNetwork}(X) + X)
Add & Norm 的优势总结
- 稳定性:通过层归一化,网络的输出在不同的层次上保持稳定,这有助于加快模型的训练,并减少因为数值不稳定带来的训练困难。
- 信息保留:残差连接确保了即使在模型较深的情况下,原始信息也不会丢失,从而提高了模型的表达能力。
- 增强梯度传播:残差连接通过提供捷径路径,减少了梯度消失的风险,使得深层网络的训练更加有效。
这两种技术的结合,使得Transformer不仅能够学习复杂的表示,还能够在较深的层次上稳定、高效地训练,成为了自然语言处理领域中强大的模型架构。
插:计算举例
可以不看,实际上是把下面这个图的绿色框框算了一下。
我们来详细讲解一下“我是一只猫娘”这句话在多头注意力机制中的计算过程,同时结合 残差连接 和 层归一化 的操作。
「因为有后续的操作所以我必须要让GPT4重新算一下多头注意力机制。」
输入表示
假设我们已经将句子“我是一只猫娘”转换为字级别的向量表示,并且每个字都用一个8维向量表示:
- “我”: (
\mathbf{Q_1} = [0.2, -0.1, 0.4, 0.3, 0.5, -0.3, 0.1, 0.2]
) - “是”: (
\mathbf{Q_2} = [0.1, 0.3, -0.4, 0.2, 0.6, -0.2, 0.1, 0.0]
) - “一”: (
\mathbf{Q_3} = [-0.2, 0.2, 0.3, -0.1, 0.0, 0.5, -0.4, 0.1]
) - “只”: (
\mathbf{Q_4} = [0.4, -0.3, 0.1, 0.6, -0.2, 0.3, 0.2, 0.1]
) - “猫”: (
\mathbf{Q_5} = [0.3, 0.1, -0.2, 0.4, 0.1, -0.1, 0.5, 0.2]
) - “娘”: (
\mathbf{Q_6} = [0.0, -0.2, 0.4, 0.3, 0.2, 0.4, -0.3, 0.1]
)
线性投影
接下来,我们对每个字的向量进行线性投影,以生成Query(\mathbf{Q}
)、Key(\mathbf{K}
)、Value(\mathbf{V}
)的子空间表示。
假设我们使用两个头(h=2
),每个头的维度为4(d_k = d_v = \frac{d_{\text{model}}}{h} = 4
)。
头1的投影矩阵(随意举例):
\mathbf{W^Q_1} = \begin{bmatrix} 0.1 & 0.3 & 0.1 & -0.1 \\ -0.2 & 0.0 & 0.2 & 0.1 \\ 0.3 & -0.2 & 0.4 & 0.2 \\ 0.2 & -0.1 & 0.3 & -0.3 \end{bmatrix}
\mathbf{W^K_1} = \begin{bmatrix} 0.2 & 0.1 & -0.3 & 0.4 \\ 0.1 & 0.0 & 0.2 & -0.1 \\ -0.2 & 0.3 & 0.1 & 0.0 \\ 0.3 & -0.4 & 0.2 & 0.2 \end{bmatrix}
\mathbf{W^V_1} = \begin{bmatrix} 0.4 & -0.2 & 0.1 & 0.3 \\ -0.1 & 0.2 & 0.4 & -0.3 \\ 0.3 & -0.1 & 0.2 & 0.4 \\ 0.1 & 0.3 & -0.2 & 0.2 \end{bmatrix}
头2的投影矩阵(随意举例):
\mathbf{W^Q_2} = \begin{bmatrix} -0.1 & 0.2 & -0.3 & 0.4 \\ 0.0 & -0.2 & 0.1 & 0.3 \\ 0.2 & 0.3 & -0.4 & 0.1 \\ -0.3 & 0.0 & 0.2 & 0.4 \end{bmatrix}
\mathbf{W^K_2} = \begin{bmatrix} 0.3 & -0.1 & 0.4 & 0.2 \\ 0.2 & 0.3 & -0.2 & 0.1 \\ -0.1 & 0.4 & 0.1 & -0.3 \\ 0.0 & 0.2 & -0.4 & 0.1 \end{bmatrix}
\mathbf{W^V_2} = \begin{bmatrix} -0.2 & 0.3 & 0.0 & -0.1 \\ 0.1 & -0.2 & 0.4 & 0.3 \\ 0.3 & -0.1 & 0.2 & 0.4 \\ 0.2 & 0.0 & 0.3 & -0.2 \end{bmatrix}
计算注意力分数
以头1为例,计算“我”对“猫”的注意力分数:
- Query的投影:(
\mathbf{Q_1} \times \mathbf{W^Q_1}
) - Key的投影:(
\mathbf{K_5} \times \mathbf{W^K_1}
)
假设计算得到的投影向量为:
- “我”的Query(投影后):(
[0.5, -0.1, 0.3, 0.2]
) - “猫”的Key(投影后):(
[0.4, 0.1, -0.3, 0.6]
)
点积计算注意力分数:
\mathbf{Q_1} \cdot \mathbf{K_5} = (0.5 \times 0.4) + (-0.1 \times 0.1) + (0.3 \times -0.3) + (0.2 \times 0.6) = 0.22
缩放点积:
\frac{0.22}{\sqrt{4}} = \frac{0.22}{2} = 0.11
将所有计算出的注意力分数通过Softmax归一化,得到每个字的权重。
加权求和
使用注意力权重对Value进行加权求和,假设“猫”的Value(投影后)为:
\mathbf{V_5} = [0.2, -0.3, 0.1, 0.5]
“我”对其他字的加权求和值为:
\text{Attention}_\text{我} = 0.21 \times \mathbf{V_1} + 0.19 \times \mathbf{V_2} + \dots + 0.15 \times \mathbf{V_6}
多头拼接与线性变换
将所有头的输出拼接在一起,得到一个8维向量,然后通过线性变换将其投影回原始空间。
假设经过两个头计算后,“我”的拼接向量为:
[0.15, 0.05, -0.20, 0.30, 0.10, -0.10, 0.25, 0.40]
通过权重矩阵 (\mathbf{W_O}
) 进行线性变换,得到最终的输出向量:
\mathbf{O} = [0.12, -0.18, 0.35, 0.10, 0.05, -0.22, 0.17, 0.25]
残差连接
首先,残差连接的目的是将输入直接与输出相加,从而保留输入的信息,防止在深层网络中信息丢失或梯度消失。
在我们的例子中,输入向量 (\mathbf{X}
) 是“我”这个字的原始嵌入向量:
\mathbf{X} = [0.2, -0.1, 0.4, 0.3, 0.5, -0.3, 0.1, 0.2]
而经过多头注意力机制和线性变换后,我们得到了输出向量 (\mathbf{O}
):
\mathbf{O} = [0.12, -0.18, 0.35, 0.10, 0.05, -0.22, 0.17, 0.25]
残差连接的过程就是将这两个向量逐元素相加:
\text{Output} = \mathbf{X} + \mathbf{O}
计算结果为:
\text{Output} = [0.2 + 0.12, -0.1 - 0.18, 0.4 + 0.35, 0.3 + 0.10, 0.5 + 0.05, -0.3 - 0.22, 0.1 + 0.17, 0.2 + 0.25]
简化结果:
\text{Output} = [0.32, -0.28, 0.75, 0.4, 0.55, -0.52, 0.27, 0.45]
层归一化(Layer Normalization)
层归一化的目的是对每个样本的输出进行标准化,使其具有零均值和单位方差,从而提升模型训练的稳定性。
1:计算均值
对Output的每个元素求均值:
\mu = \frac{1}{8} \sum_{i=1}^{8} \text{Output}_i = \frac{1}{8} \times (0.32 + (-0.28) + 0.75 + 0.4 + 0.55 + (-0.52) + 0.27 + 0.45)
\mu = \frac{1}{8} \times 1.94 = 0.2425
步骤2:计算标准差
计算Output的标准差:
\sigma = \sqrt{\frac{1}{8} \sum_{i=1}^{8} (\text{Output}_i - \mu)^2}
计算各项差的平方和:
(0.32 - 0.2425)^2 + (-0.28 - 0.2425)^2 + (0.75 - 0.2425)^2 + (0.4 - 0.2425)^2 + (0.55 - 0.2425)^2 + (-0.52 - 0.2425)^2 + (0.27 - 0.2425)^2 + (0.45 - 0.2425)^2
= 0.00600625 + 0.27225625 + 0.25700625 + 0.02400625 + 0.09450625 + 0.57950625 + 0.000625 + 0.04350625 = 1.277425
计算标准差:
\sigma = \sqrt{\frac{1.277425}{8}} = \sqrt{0.159678125} \approx 0.3996
步骤3:标准化
将Output向量标准化:
\text{Norm(Output)}_i = \frac{\text{Output}_i - \mu}{\sigma}
计算每个元素的标准化结果:
\text{Norm(Output)}_1 = \frac{0.32 - 0.2425}{0.3996} \approx 0.194 \\
\text{Norm(Output)}_2 = \frac{-0.28 - 0.2425}{0.3996} \approx -1.307 \\
\text{Norm(Output)}_3 = \frac{0.75 - 0.2425}{0.3996} \approx 1.269 \\
\text{Norm(Output)}_4 = \frac{0.4 - 0.2425}{0.3996} \approx 0.394 \\
\text{Norm(Output)}_5 = \frac{0.55 - 0.2425}{0.3996} \approx 0.771 \\
\text{Norm(Output)}_6 = \frac{-0.52 - 0.2425}{0.3996} \approx -1.902 \\
\text{Norm(Output)}_7 = \frac{0.27 - 0.2425}{0.3996} \approx 0.069 \\
\text{Norm(Output)}_8 = \frac{0.45 - 0.2425}{0.3996} \approx 0.520
最终的标准化结果:
\text{Norm(Output)} = [0.194, -1.307, 1.269, 0.394, 0.771, -1.902, 0.069, 0.520]
这个标准化后的向量就是经过层归一化处理后的输出,接下来它会作为下一个子层的输入。
总结
通过残差连接和层归一化,我们得到了一个稳定且经过标准化处理的输出,这不仅保留了输入信息(通过残差连接),还确保了数值的稳定性(通过层归一化),从而提高了模型的训练效果和收敛速度。这一过程在Transformer模型的每一层中重复进行,使得整个网络可以有效地处理复杂的序列数据。
前馈神经网络
「我们现在在这。」
Feed Forward:前馈神经网络,对每个位置的表示进行逐位置的非线性变换。
Transformer中的前馈神经网络(Feed-Forward Neural Network, FFN)是Transformer架构中的关键组成部分,通常用于对每个位置的表示进行独立的非线性变换。在Transformer中,每个位置的表示(即每个token的表示)都会经过前馈神经网络进行进一步的处理。
前馈神经网络的结构
Transformer中的前馈神经网络通常由两个线性变换(即全连接层(7))和一个非线性激活函数(通常是ReLU)组成。对于输入向量\mathbf{x}
,FFN的计算过程可以表示为:
\text{FFN}(\mathbf{x}) = \text{ReLU}(\mathbf{x} \mathbf{W}_1 + \mathbf{b}_1) \mathbf{W}_2 + \mathbf{b}_2
其中:
\mathbf{W}_1
和\mathbf{W}_2
是线性变换的权重矩阵。\mathbf{b}_1
和\mathbf{b}_2
是对应的偏置向量。- ReLU(Rectified Linear Unit,修正线性单元)是神经网络中常用的激活函数,它的作用是引入非线性,使神经网络能够学习和表示更复杂的函数关系。ReLU的工作原理非常简单:如果输入是正数,就原样输出;如果输入是负数或零,就输出零。 用一个小公式表示就是:
ReLU(x) = max(0, x)
。通俗一点说,可以想象ReLU像一个阀门,只允许正数通过,而把负数和零拦在外面。这个“阀门”帮助神经网络更好地处理数据,尤其是在深层网络中,它能让网络变得更加有效率和稳定。比如,如果你输入的数值是3
,经过ReLU后还是3
;但如果输入的是-5
,ReLU会把它变成0
。这种方式有助于神经网络在训练时更快地收敛,也能避免一些计算上的问题,比如梯度消失。
举个例子,假设你正在写一句话“我今天吃了苹果”,你在写“苹果”这个词时,是基于你前面已经写了“我今天吃了”这几个词。模型也是类似的,它会根据前面预测出的结果来生成后面的内容。
自回归模型的优点是能够生成与上下文密切相关的内容,缺点是如果前面的预测出错,错误可能会累积到后面的预测中。简单来说,自回归模型就是一步一步地依赖前面的结果,像在走一条路,每一步都会影响接下来的方向。
具体步骤
输入变换:
- 输入向量
\mathbf{x}
的维度通常为d_{\text{model}}
,这是Transformer的隐藏层的维度。 - 首先,
\mathbf{x}
通过一个线性变换(全连接层),变换后的结果是一个新的向量,维度通常是d_{\text{ff}}
。这一步可以表示为\mathbf{z}_1 = \mathbf{x} \mathbf{W}_1 + \mathbf{b}_1
。
- 输入向量
激活函数:
- 接下来,应用ReLU激活函数对
\mathbf{z}_1
进行非线性变换,得到\mathbf{a} = \text{ReLU}(\mathbf{z}_1)
。ReLU会将所有负值部分设为0,保留正值部分不变。
- 接下来,应用ReLU激活函数对
输出变换:
- 最后,
\mathbf{a}
通过另一个线性变换,得到输出\mathbf{y} = \mathbf{a} \mathbf{W}_2 + \mathbf{b}_2
。这一步将维度从d_{\text{ff}}
映射回d_{\text{model}}
,确保输出与输入的维度一致。
- 最后,
前馈神经网络的特点
位置独立性:FFN对每个位置的输入进行独立变换,不考虑序列中的其他位置。这意味着前馈神经网络只看当前token的表示,而不依赖上下文信息。
维度扩展与缩减:在大多数Transformer实现中,
d_{\text{ff}}
通常比d_{\text{model}}
大得多,比如d_{\text{ff}} = 2048
,d_{\text{model}} = 512
。这意味着输入在中间层通过维度扩展获得更丰富的特征,然后再缩减回原来的维度。这种操作有助于模型在中间层提取更高层次的特征。效率:尽管FFN对每个位置进行独立计算,但由于其计算量相对较小(仅涉及两个线性变换和一个激活函数),在实际应用中非常高效。其计算复杂度主要取决于矩阵的乘法操作。
在Transformer中的作用
在Transformer中,前馈神经网络通常位于多头自注意力机制之后。具体来说,在每一层的Transformer编码器或解码器中,每个位置的表示首先经过自注意力机制,然后通过前馈神经网络进行非线性变换。这样做的目的是进一步处理和增强自注意力机制生成的表示,从而提高模型的表达能力。
总结:前馈神经网络在Transformer中虽然简单,但非常重要。它通过对每个位置的表示进行独立的非线性变换,帮助模型学习到更丰富的特征。FFN的结构简洁而有效,使得Transformer在处理自然语言处理任务时能够表现出色。
「这里就不计算了,自己带公式吧。」
编码器总结
「估计能阅读到这的读者都不容易,成功的走完了下面的图的所有步骤。」
Transformer的编码器部分主要通过多头自注意力机制来捕捉输入序列中各个词之间的关系,并结合位置编码引入序列信息。每层编码器包括多头自注意力和前馈神经网络,并通过残差连接和层归一化稳定训练过程。多个这样的编码器层堆叠在一起,逐层提取和处理输入序列的特征,从而生成上下文相关的表示。这个结构让Transformer能够高效处理长序列,同时支持并行计算。
解码器
仔细看一下图,就会发现其实不同的点在于多了一个Masked Multi- Head Attention,还有最终输出的Linear层。别的层我们基本上都学完了。所以我直接讲这两个层即可。
掩码多头注意力机制(Masked Multi- Head Attention)
先回顾一下刚刚学的多头注意力机制。
什么是 Multi-Head Attention?
首先,Multi-Head Attention 是 Transformer 的核心机制之一。它的主要功能是让模型在处理一个序列中的某个位置时,可以同时关注序列中的多个不同位置。这个机制在自然语言处理中尤为重要,因为一个词的含义往往与句子中其他词的上下文有关。
Attention 的基本步骤:
- Query (Q): 查询向量,表示当前处理的词。
- Key (K): 键向量,表示序列中所有词的特征。
- Value (V): 值向量,表示序列中所有词的特征,用于最终的加权求和。
Attention 的计算过程:
- 首先,将 Query 和 Key 向量进行点积(dot product),得到一个相关性分数。
- 然后,将这个分数除以一个缩放因子(通常是 Key 的维度的平方根),以避免数值过大。
- 接着,对相关性分数应用 softmax 函数,转化为概率分布,表示每个词对当前词的注意力权重。
- 最后,将这些权重应用于 Value 向量,得到最终的加权和,这就是 Attention 输出。
Multi-Head 的意义:
Multi-Head Attention 并不是只计算一次 Attention,而是将 Query、Key 和 Value 拆分成多个子空间(多个头),在每个子空间上分别进行 Attention 计算。然后将这些子空间的输出连接起来,再通过一个线性变换得到最终的输出。这样做的好处是模型可以从不同的“角度”来理解上下文信息,从而提高表达能力。
Masked Multi-Head Attention 是什么?
Masked Multi-Head Attention 是 Multi-Head Attention 的一种变体,通常用于 Transformer 的解码器部分,特别是在训练语言模型(如机器翻译)时。
为什么要 Mask?
在解码器中,模型需要逐步生成序列的下一个词。在生成第 t 个词时,模型不应该看到这个词之后的任何词,这样才能保证模型是自回归的(即在生成一个词时,只考虑前面的词)。为了实现这个目标,Masked Multi-Head Attention 在计算注意力权重时对未来的位置进行遮掩(masking)。
Mask 的具体操作:
- 对于未来的词(即那些在当前处理位置之后的词),直接将它们的相关性分数设为负无穷大(-∞)。
- 这样在 softmax 之后,这些词的注意力权重就变成了 0,确保模型不会关注到它们。
Masked Multi-Head Attention 的计算过程
- 输入向量准备:对于输入序列的每个位置,计算出对应的 Query, Key 和 Value。
- 打分并应用 Mask:
- 计算 Query 和 Key 的点积,得到一个打分矩阵。
- 应用 Mask,将未来位置的打分设为 -∞。
- 对打分矩阵进行 softmax 计算,得到注意力权重矩阵。
- 加权求和:用注意力权重矩阵对 Value 进行加权求和,得到每个头的输出。
- 多头连接与线性变换:将所有头的输出连接起来,通过一个线性变换,得到最终的输出。
小结
Masked Multi-Head Attention 层通过引入 Mask 机制,确保模型在生成下一个词时不会看到未来的词,从而保持自回归(8)性质。这一层在 Transformer 解码器中至关重要,因为它既保留了 Multi-Head Attention 的强大能力,又保证了序列生成的合理性。
举个例子,假设你正在写一句话“我今天吃了苹果”,你在写“苹果”这个词时,是基于你前面已经写了“我今天吃了”这几个词。模型也是类似的,它会根据前面预测出的结果来生成后面的内容。
自回归模型的优点是能够生成与上下文密切相关的内容,缺点是如果前面的预测出错,错误可能会累积到后面的预测中。简单来说,自回归模型就是一步一步地依赖前面的结果,像在走一条路,每一步都会影响接下来的方向。
举个例子
我们用“我是一只猫娘”这句话来详细说明 Masked Multi-Head Attention 的计算过程,尤其是 Masking 的部分。
假设的场景
假设我们在生成句子的过程中已经生成了“我是一只”,现在正在生成下一个词“猫”的时候,这时候的输入就是“我是一只猫”。为了方便理解,我们把句子拆分成一个个词:
我 是 一 只 猫 娘
计算 Query, Key, Value 向量
对于每个词,我们都会计算出对应的 Query (Q), Key (K), 和 Value (V) 向量。假设每个词的向量维度是4(实际中通常更高),我们得到如下向量矩阵:
Q: | [q1] | [q2] | [q3] | [q4] | [q5] | [q6] |
K: | [k1] | [k2] | [k3] | [k4] | [k5] | [k6] |
V: | [v1] | [v2] | [v3] | [v4] | [v5] | [v6] |
其中,每个qi
, ki
, vi
都表示对应词的 Query, Key 和 Value 向量。
计算注意力打分矩阵
我们要计算“猫”这个词的注意力输出。在 Multi-Head Attention 中,“猫”这个词的 Query 会和句子中所有词的 Key 做点积,得到注意力打分。假设计算出来的打分矩阵如下(简化成只展示“猫”这个词的打分):
打分: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
这个打分表示“猫”这个词对每个位置的注意力分数。
应用 Mask
在生成“猫”这个词时,模型不应该看到后面的“娘”这个词,所以我们要对“娘”的位置进行 Mask。应用 Mask 后,我们将对应的打分设为负无穷大(-∞),这样 softmax 之后的权重为 0。假设 Mask 后的打分矩阵变为:
打分: [0.1, 0.2, 0.3, 0.4, 0.5, -∞]
计算注意力权重
接下来,我们对打分矩阵应用 softmax 函数,得到注意力权重:
softmax(打分): [0.15, 0.18, 0.21, 0.24, 0.27, 0]
如你所见,最后一个位置的权重因为被 Mask 变成了 0,意味着“猫”这个词不会关注到“娘”这个词。
加权求和
最后,我们用这些权重对 Value 向量进行加权求和,得到“猫”这个词的输出:
输出 = 0.15 * v1 + 0.18 * v2 + 0.21 * v3 + 0.24 * v4 + 0.27 * v5 + 0 * v6
这个加权求和后的结果就是“猫”这个词的注意力输出。
小结
通过这个例子,我们可以看到 Mask 是如何工作的:它通过将未来词的位置打分设为负无穷大,确保在生成当前词时不会关注到未来的词,从而保证模型的自回归特性。这种 Masking 机制对于生成序列模型是至关重要的,因为它使得模型只能依赖已经生成的词来预测下一个词。
Linear层
Transformer中的Linear层其实就是一个简单的全连接层(也叫密集层),它在处理输入时通过一个线性变换来映射输入数据到一个新的空间。这个层通常被用来在不同维度之间进行转换,尤其是在从一个词嵌入空间转换到另一个空间时。下面是这个过程的详细说明:
1. 线性变换的基本原理
- 线性变换的数学表达式是:
Y = XW + b
,其中:X
是输入数据的矩阵,维度通常是(batch_size, input_dim)
,其中batch_size
是批处理大小,input_dim
是输入数据的特征维度。W
是权重矩阵,维度为(input_dim, output_dim)
,其中output_dim
是输出数据的维度。b
是偏置向量,维度为(output_dim)
。Y
是输出数据的矩阵,维度为(batch_size, output_dim)
。
2. 在Transformer中的作用
- 在Transformer中,Linear层被广泛用于以下场景:
- 投影嵌入向量:在自注意力机制中,输入的嵌入向量(如词嵌入)通常需要通过Linear层进行投影,分别得到查询(query)、键(key)和值(value)向量。这些向量是通过对原始输入向量应用不同的线性变换得到的。
- 维度变换:在编码器和解码器中,Transformer有时需要将一个较大的维度映射到较小的维度(反之亦然),以适应不同的计算需求。
- 输出层:在解码器的最后,通常会有一个Linear层将解码器的输出映射到词汇表的大小,以预测下一个词。
3. 计算步骤
- 输入数据
X
:假设你有一个批次的输入,形状为(batch_size, input_dim)
。 - 权重矩阵
W
:初始化为一个随机矩阵,形状为(input_dim, output_dim)
。 - 偏置向量
b
:初始化为一个零向量或随机向量,形状为(output_dim)
。 - 计算输出
Y
:- 进行矩阵乘法
XW
,得到一个形状为(batch_size, output_dim)
的中间结果。 - 加上偏置向量
b
,得到最终的输出Y
。
- 进行矩阵乘法
4. 举例
- 假设输入是一个维度为
4
的向量,表示一个词的嵌入向量(input_dim = 4
)。你希望将这个词嵌入投影到一个新的空间,维度为3
(output_dim = 3
)。 - 那么,权重矩阵
W
的形状就是(4, 3)
,偏置b
是形状为(3)
的向量。 - 输入向量
X = [1, 2, 3, 4]
,假设W
和b
的具体值如下:W = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9], [1.0, 1.1, 1.2]] b = [0.1, 0.2, 0.3]
- 那么,输出
Y
计算如下:Y = XW + b Y = [1*0.1 + 2*0.4 + 3*0.7 + 4*1.0, 1*0.2 + 2*0.5 + 3*0.8 + 4*1.1, 1*0.3 + 2*0.6 + 3*0.9 + 4*1.2] Y = [5.0, 6.4, 7.8]
然后再加上偏置
b
:Y = [5.0 + 0.1, 6.4 + 0.2, 7.8 + 0.3] Y = [5.1, 6.6, 8.1]
- 最终的输出向量
Y
为[5.1, 6.6, 8.1]
,这是通过Linear层的线性变换得到的。
总结来说,Linear层在Transformer中起到了将输入映射到不同空间的作用,支持模型的注意力机制、特征变换以及最终的输出预测。
一些补充
为了解释一下这个部分。
在Transformer的解码部分,添加诸如输出嵌入(Output Embedding)和位置编码(Positional Encoding)是为了更好地进行序列生成任务,例如机器翻译、文本生成等。
解码器的作用是根据编码器生成的上下文向量以及已经生成的部分输出(通常是移位后的输出序列)来预测下一个输出。为了让解码器能够处理这些输入,需要先将这些输入嵌入到一个高维向量空间中,这就是“输出嵌入”所做的工作。然后,再加上位置编码来给出输入序列中每个位置的信息,这对于Transformer特别重要,因为它本身没有像RNN那样的序列处理能力,需要通过位置编码来保留序列的顺序信息。
至于是否可以单独使用解码器,答案是可以的。在某些应用中,比如文本生成或翻译,解码器可以作为一个独立的模块来使用。不过,通常解码器还是需要从编码器得到的上下文信息来生成有意义的输出。因此,解码器单独使用时需要提供合适的上下文输入,这可以是来自训练好的编码器的输出,也可以是通过其他方式生成的上下文向量。
总结来说,解码器部分添加这些模块是为了更好地处理输入的嵌入信息和序列位置信息,使得在生成输出时能够更准确地预测序列中的下一个词或符号。而在某些任务中,解码器确实可以单独使用,但一般仍需要结合某种形式的上下文信息。
总结
Transformer的结构就这样学完了,左边是编码器,右边是解码器。可以一起用,也可以分开来用。具体的知识点总结如下:
知识点 | 描述 |
---|---|
编码器部分 | 编码器通过嵌入输入数据、使用多头自注意力机制、残差连接和层归一化处理信息,生成上下文丰富的表示。 |
解码器部分 | 解码器与编码器类似,但额外包含掩码多头自注意力机制,防止模型看到未来的词,同时通过与编码器的输出交互,生成最终的输出。 |
神经网络概述 | 神经网络模仿人脑,由输入层、隐藏层和输出层组成。隐藏层处理输入数据特征,网络通过正向传播和反向传播调整权重,逐步优化模型性能。 |
正向传播 | 神经网络中信息从输入层传递到输出层的过程,涉及加权求和和激活函数生成输出。 |
反向传播 | 神经网络中用于调整模型参数的过程,通过计算损失函数的梯度,指导参数更新以最小化损失函数。 |
梯度 | 模型预测误差相对于模型参数的变化率,用于指导参数调整。 |
权重 | 神经网络中每个输入在决定最终结果时的重要性程度,通过训练不断调整,使模型预测更准确。 |
收敛 | 神经网络逐步找到最佳解的过程,误差不断减小并趋向稳定值。 |
位置编码 | 为输入数据添加位置信息,使模型能够理解序列中元素的顺序,通常使用正弦和余弦函数生成位置编码。 |
词表与分词 | Transformer的词表来源于大规模语料库,经过特定分词方式如WordPiece或BPE处理后,生成基本单元(token),模型在这些基础上进行训练。 |
RNN(循环神经网络) | 适用于顺序数据处理的神经网络,能够通过隐藏状态跨时间步传播信息,但存在梯度消失、梯度爆炸、并行化困难等问题。 |
LSTM和GRU | 为解决RNN梯度问题引入的变种,LSTM使用门控机制控制信息流动,GRU则简化了LSTM结构,但在许多任务中效果相当。 |
Seq2seq模型 | 基于RNN的序列到序列模型,常用于机器翻译,由编码器和解码器组成,通过注意力机制动态调整对输入序列的关注度。 |
词嵌入与分词方式 | Transformer模型中的词表依赖于分词方式,词嵌入将离散符号映射为高维向量,分词方式决定文本被切分为哪些基本单元。 |
注意力机制 | 允许模型在生成输出时选择性关注输入序列的不同部分,解决RNN长依赖问题,提高上下文建模能力。 |
自注意力机制 | 序列中的每个元素与其他元素进行自我比较和关联,生成新的表示,适用于长序列处理和并行计算。 |
缩放点积注意力 | 对点积注意力结果进行缩放,以防数值过大,提升计算稳定性,使模型在长序列数据处理时更有效。 |
多头注意力机制 | 在自注意力机制的基础上,使用多个head独立计算自注意力,增强模型表达能力。 |
希望这个表格能帮助读者更好地理解内容!
「我个人觉得啊,并不一定非要我们去适应机器,也不一定要机器完全适应我们。这背后是一个互相调整的过程。毛主席说过没有调查就没有发言权。所以我们要明白我们的逻辑,也要明白机器的运行逻辑。这样一来语言学家才能对AI有所贡献。不能指望着人家学习我们自己定义的这套规则。还是那句话,很多东西是人的认知,别以为机器都有。大数据面前直接众生平等,管你定义的什么主谓谓语句,单句复句。」
「这背后的本质是我们在表达自己的观点的时候,很容易认为其他人也有类似的背景信息,但大部分时候可能真的没有那么多。所以我也只能尽可能的根据我自己心中的读者形象进行写作,无法顾及到每个人。如果我有没说清楚的,请联系我。此处先表示感谢。如果有不懂的,也可以进一步讨论,或者你问一下AI。」
最终,在此对GPT4表示感谢,你是一个懂AI的AI。