注意力机制与Transformer之间的关系是紧密且核心的。Transformer模型的设计完全基于注意力机制,可以说注意力机制是Transformer模型的灵魂。

       在传统的序列到序列(Sequence-to-Sequence)模型中,循环神经网络(RNN)或长短期记忆网络(LSTM)等结构被用来捕捉输入序列的上下文信息,但它们在处理长序列时存在梯度消失/爆炸的问题,并且难以并行化计算。

       Transformer模型通过彻底革新这一架构,引入了自注意力机制来替代循环结构。在Transformer中:

1. 自注意力机制允许模型对每个输入位置的信息都能同时考虑序列中所有其他位置的信息,而不是依赖于前一时刻的隐藏状态。这意味着模型能够直接从整个序列中“聚焦”(attend to)对当前预测最重要的部分。

2. 每个输入位置会生成Query、Key和Value三种向量表示,通过对这些向量的点积和softmax操作计算出权重系数,然后加权求和Value向量以得到新的上下文相关的表示。

3. Transformer中的多头注意力进一步增强了模型的表达能力,它将注意力机制分解为多个平行的子空间,每个子空间关注输入的不同方面,最后将所有子空间的结果融合在一起。

4. 为了保留序列的位置信息,Transformer使用了位置编码(Positional Encoding),确保模型在利用注意力机制捕获全局上下文的同时,不会丢失输入元素间的相对顺序。

因此,注意力机制不仅是Transformer区别于传统序列模型的关键创新,也是其能够实现高效并行计算以及卓越性能的核心所在。

1、注意力

       灵长类动物的视觉系统接收了大量的感官输入,这些感官输入远远超出了大脑能够完全处理的能力。然而,并非所有刺激的影响都是同等的。意识的汇聚和专注使灵长类动物能够在复杂的视觉环境中将注意力引向感兴趣的物体,例如猎物和天敌。只关注一小部分信息的能力对进化更加有意义,使人类得以生存和成功。

       在进化过程中,灵长类动物发展出了一套高度适应环境变化和生存需求的注意力系统。这套系统的核心在于其能够根据情境的重要性和紧迫性,对视觉输入进行有效筛选,并将有限的认知资源集中在关键信息上。

       首先,大脑中的初级视觉皮层负责初步处理大量感官输入,通过快速扫描和识别图像的基本特征,如边缘、颜色和运动方向等。随后,更高级的视觉区域如纹状体、前额叶和顶叶等参与进来,实现对视觉信息的选择性注意和工作记忆的维持。

        选择性注意机制使得灵长类动物能够在复杂的视觉环境中迅速锁定目标物体,例如食物来源、潜在威胁(如天敌)以及重要的社交信号。这种能力允许它们在瞬息万变的自然环境中做出及时而准确的反应,提高捕食效率、降低被捕食风险并优化社会交往。

       此外,意识的汇聚和专注体现在了大脑神经网络的高度协调运作上,涉及从自下而上的刺激驱动过程到自上而下的任务导向调节。这两种机制相互作用,确保了在复杂场景中对相关信息的关注与提取。

       总之,灵长类动物(包括人类)具备高效分配认知资源的能力,使他们能在海量感官输入中“挑选”重要信息,这一特性对于生物的生存繁衍至关重要,也是我们成功应对复杂生活环境的基础。

2、注意力经济

       自经济学研究稀缺资源分配以来,人们正处在“注意力经济”时代,即人类的注意力(大量人群的注意力)被视为可以交换的、有限的、有价值的且稀缺的商品 。     

       在注意力经济时代,企业和市场参与者意识到人类注意力作为一种有限资源的重要性,并且开始系统性地研究和争夺这一资源。随着信息技术的发展,尤其是互联网、社交媒体和其他数字媒体的普及,信息变得前所未有的丰富和易得,但人们的注意力却无法同步增长,反而可能因信息过载而变得更加稀缺

        在这样的背景下,吸引并保持用户的注意力成为商业竞争的核心要素之一。企业通过创新内容、个性化推荐、精准营销等方式,努力优化用户体验,以争取更多用户关注时间。同时,广告商愿意为能在有效时间内触及潜在消费者的注意力支付费用,使得注意力直接转化为经济价值。

        此外,数字化平台和媒介也在不断探索新的商业模式和服务方式,如提供高质量内容、游戏化体验、社交互动等,从而提高用户粘性和活跃度,间接或直接将注意力转化为收入。这种对注意力资源的竞争和管理也促使了数据分析、算法优化等相关技术的发展,以便更高效地利用和分配注意力资源。

3、注意力的稀缺性

      注意力的稀缺性是指人类的认知资源有限,不能同时关注和处理所有进入意识的信息。这一概念在认知心理学中被广泛接受,并在日常生活中有直观体现。由于大脑的信息加工能力存在瓶颈,人们必须对海量输入的信息进行选择和过滤,只将有限的注意力集中在少数最相关、最重要或最显著的事物上。具体来说:

  1. 信息过载:现代生活中的信息来源极其丰富,无论是工作、学习还是娱乐,都可能造成大量信息涌入。面对这种环境,人的注意力成了珍贵且有限的资源。

  2. 多任务处理成本:尽管可以同时进行多项活动(例如一边听音乐一边阅读),但真正高质量的多任务处理(即同一时间高效完成多个认知要求高的任务)往往受限于注意力的稀缺性。当试图同时处理多个任务时,可能会导致效率降低、错误增多以及总体表现下滑。

  3. 认知负荷理论:该理论指出,人的工作记忆容量有限,过多的任务会增加认知负荷,使得个体难以保持高效率的工作状态或有效吸收新知识。

  4. 注意力切换的成本:从一个任务转移到另一个任务时,需要消耗认知资源来重新定向注意力。频繁的注意力切换会导致效率损失,因此合理分配和管理注意力至关重要。

  5. 经济效益与决策制定:在经济学和行为科学中,注意力的稀缺性也被视为一种重要的约束条件,影响着个人和组织的决策过程和资源配置。

       总之,注意力的稀缺性提醒我们,有效的信息处理和任务执行需要合理安排注意力资源,避免不必要的分散,并通过策略性分配来提高工作效率和个人生活质量。

4、注意力的管理分配资源能力

       整个人类历史中,这种只将注意力引向感兴趣的一小部分信息的能力使人类的大脑能够更明智地分配资源来生存、成长和社交,例如发现天敌、寻找食物和伴侣。     

       注意力的管理与分配资源能力,是指个体在面对众多信息输入和任务需求时,如何合理、高效地调配和使用有限的认知资源来关注和处理重要信息。以下几点详细说明了这种能力的重要性及实现方式:

  1. 选择性注意:人类大脑无法同时处理所有感官接收到的信息,因此需要通过选择性注意机制过滤无关或次要的刺激,将注意力集中在最关键的任务上。

  2. 优先级设定:根据任务的重要性和紧迫性设定优先级,以决定哪些任务应首先得到注意力资源。这通常涉及到对长期目标的理解以及短期情境的判断。

  3. 时间管理和规划:通过有效的时间管理和任务规划,避免认知资源过度分散,例如采用番茄工作法等技巧进行专注工作和定时休息,提高工作效率。

  4. 抗干扰能力:提升抵抗内外部干扰的能力,如关闭不必要的通知、创造安静的工作环境、培养专注力等,使注意力更集中于当前活动。

  5. 多任务处理策略:虽然人的注意力很难真正并行处理多个高要求任务,但可以通过交替执行不同任务或在同一主题内切换子任务的方式,相对高效地分配注意力资源。

  6. 身心健康维护:保持良好的睡眠质量、饮食习惯和适度运动有助于维持大脑的最佳状态,从而优化注意力资源的分配和利用。

       管理分配注意力资源不仅关乎个人在日常生活和工作中取得成功的能力,也是现代心理学、神经科学等领域研究的核心内容之一。通过理解并掌握相关的理论与实践方法,人们可以在复杂环境中更好地聚焦关键信息、高效完成任务,并最终提高生活质量。

5、生物学中的注意力提示   

       在生物学中,注意力提示(attentional cues)是指大脑和神经系统用来引导、优化或过滤感觉输入的一种内在机制。这种机制帮助我们从环境中筛选出重要信息,忽略无关紧要的干扰,并将认知资源集中在最有价值的数据上。

以下是一些关于生物体内注意力提示的体现:

  1. 视觉注意:眼睛运动(如眼跳或扫视行为)就是一种明显的注意力提示。当我们关注一个特定的目标时,眼球会快速移动到该目标上,使得高分辨率的中央凹区域能够清晰地感知它。此外,视觉皮层中的神经活动也反映了对显著刺激的优先处理。

  2. 听觉注意:大脑可以识别并强调声音信号中的关键元素,例如,在嘈杂背景中听到自己名字的能力,这就是自下而上的注意力提示。同时,根据任务需求和预期(自上而下的控制),大脑可以有选择性地聆听某个声源或某种类型的声音。

  3. 多感官整合:大脑通过整合来自不同感觉通道的信息来提高注意力效能,比如视觉和听觉线索相结合可以更准确地定位声源。

  4. 前额叶功能:前额叶是负责执行功能和工作记忆的关键脑区,它参与制定注意力策略,指导注意力资源向相关目标分配,并抑制无关干扰。

  5. 神经递质系统:多巴胺等神经递质在调节注意力过程中起着重要作用。它们可以帮助确定哪些刺激具有潜在的价值和奖赏关联,从而吸引我们的注意力。

       生物学中的注意力提示是一个涉及多个大脑结构、神经回路以及化学传递系统的复杂过程,确保我们在不断变化的环境中有效地捕获、加工和利用有限的认知资源。

        在视觉世界中,注意力机制帮助我们从大量视觉输入信息中筛选出最相关或最重要的部分进行处理。威廉·詹姆斯提出的双组件(two-component)框架为理解这一过程提供了一个基础模型。

        非自主性提示(Involuntary or Stimulus-driven Attention): 这个组件强调的是注意力被自动、无意识地吸引到具有显著特征的刺激上,如颜色对比强烈的物体、运动的物体、突然出现的新奇事物等。这是由感觉信息本身的特性驱动的,不依赖于个体的主观意愿,即“自下而上的”注意过程。例如,在一片静止的树叶中,一只突然飞过的蝴蝶会迅速抓住我们的视线。

       自主性提示(Voluntary or Goal-directed Attention): 另一方面,自主性提示是指个体根据内在目标和计划主动分配注意力资源的过程,也称为“自上而下的”注意过程。例如,当我们寻找特定物品时(如在一堆书中找一本特定的书),即使有其他视觉干扰存在,也能通过设定目标和意图来引导注意力集中在需要搜索的区域或特征上。

       结合这两种成分,视觉注意力系统能够在复杂的视觉场景中动态且灵活地调整焦点,从而有效地处理重要信息并忽略无关内容。现代神经科学的研究进一步揭示了大脑如何通过多脑区协同作用实现这种注意力调控机制,如前额叶、顶叶、丘脑以及视皮层等多个结构在网络中的交互活动。

6、注意力机制的框架

       自主性的与非自主性的注意力提示解释了人类的注意力的方式,下面来看看如何通过这两种注意力提示,用神经网络来设计注意力机制的框架。(设计的灵感和依据来源,实证科学)

      自主性和非自主性注意力提示为神经网络中的注意力机制设计提供了灵感。在深度学习中,基于这两种类型的注意力,可以构建多种注意力模型来模拟人类的认知过程,并优化信息处理。

  1. 非自主性(Bottom-up)注意力机制: 在视觉场景中,非自主性注意力通常对应于对显著特征的响应。在神经网络中,可以通过计算输入特征图谱中的显著性得分来实现这一点,如使用Saliency Maps或Feature Importance Maps。例如,在计算机视觉领域,可以通过检测图像中的亮度、颜色对比度或者特定特征的变化来突出显示潜在重要的区域,类似于人眼对运动或高对比度区域的快速捕捉。

  2. 自主性(Top-down)注意力机制: 自主性注意力是由任务目标和上下文驱动的,允许模型根据具体需求有选择地关注输入中的某些部分。在神经网络中,这通常通过向模型注入额外的指导信号来实现,如Transformer架构中的自注意力机制。自注意力层能够依据当前的隐藏状态动态地确定哪些输入元素对于当前任务最为重要。在自然语言处理中,一个单词可能因为与问题相关的关键词匹配而获得更高的注意力权重。

结合两种注意力提示的设计实例包括:

  • 混合注意力模型:将非自主性特征显著性与自主性任务相关性相结合,形成一种既能捕获局部显著特征又能体现全局任务导向的混合注意力机制。

  • 迭代注意力网络:在网络结构中循环嵌入多轮注意力机制,每一轮都可以看作是对上一轮结果的进一步聚焦或调整,既包含了对外部刺激的自发响应,也体现了内部决策目标的逐步细化。

       通过借鉴生物心理学中关于自主性和非自主性注意力的研究成果,神经网络模型研究者们创建了各种各样的注意力机制,这些机制不仅提高了模型的表现力和泛化能力,而且使得机器学习系统更接近于人类认知系统的运作方式。

7、注意力汇聚

       只使用“非自主性提示”,想将选择偏向于感官输入,则可以简单地使用参数化的全连接层。

       "是否包含自主性提示",将注意力机制与全连接层或汇聚层区别开来。

        在注意⼒机制的背景下,⾃主性 提⽰被称为查询(query)。给定任何查询,注意⼒机制通过注意⼒汇聚(attention pooling)将选择引导⾄ 感官输⼊(sensory inputs,例如中间特征表⽰)。在注意⼒机制中,这些感官输⼊被称为值(value)。

        注意力机制框架下的注意力机制的主要组件:查询(Query)(自主性提示)和键(key)(非自主性提示)之间的交互形成了注意力汇聚:注意力汇聚有选择性地汇聚了值(Value)(感官输入)以生成最终的输出。

       如下图所示,注意力机制通过注意力汇聚将查询(自主性提示)和键(非自主性提示)结合在一起,实现对值(感官输入,包含更广)的选择倾向,形成注意力输出。

8、注意力模型

       这一在这个框架下的模型,称为主要的讨论内容。由简单到复杂,介绍内容包括Nadaraya-Waston核回归模型(1964),注意力评分函数(具体介绍两个典型评分函数)、Bahdanau注意力(没有严格单向对齐限制的可微注意力模型)、多头注意力、自注意力、位置编码、Transformer。

8.1 注意力汇聚

       介绍注意力汇聚的更多细节,以便宏观上了解注意力机制在实践中的运作方式。

       具体以1964年提出的Nadaraya-Waston核回归模型为例,这是一个简单但完整的例子,可以用于演示具有注意力机制的机器学习。

8.1.1 平均汇聚

f\left ( x \right )=\frac{1}{n}\sum_{i=1}^{n}y_{i}

8.1.2 非参数注意力汇聚

f\left ( x \right )=\frac{1}{n}\sum_{i=1}^{n}\frac{K\left ( x-x_{i} \right )}{\sum_{j=1}^{n}K\left ( x-x_{j} \right )}y_{i}

f\left ( x \right )=\sum_{i=1}^{n}\alpha \left ( x,x_{i} \right )y_{i}

8.1.3 带参数注意力汇聚

f\left ( x \right )=\sum_{i=1}^{n}\alpha \left ( x,x_{i} \right )y_{i}

                                     =\frac{1}{n}\sum_{i=1}^{n}\frac{exp\left (-\frac{1}{2}\left ( \left (x-x_{i} \right )w \right )^{2} \right )}{\sum_{j=1}^{n}exp\left (-\frac{1}{2}\left ( \left (x-x_{j} \right )w \right )^{2} \right )}y_{i}

8.2 注意力评分函数

       注意力权重,即键对应的值的概率分布。从注意力的角度来看,分配给每个值\mathbf{v}_{i}的注意力权重取决于一个函数\mathbf{\alpha} \left ( \mathbf{q},\mathbf{k}_{i} \right ),这个函数以值所对应的键\mathbf{k}_{i}和查询\mathbf{q}作为输入。

f\left ( \mathbf{q},(\mathbf{k}_{1},\mathbf{v}_{1}) ,...,(\mathbf{k}_{m},\mathbf{v}_{m})\right )= \sum_{i=1}^{m}\mathbf{\alpha} \left ( \mathbf{q},\mathbf{k}_{i} \right )\mathbf{v}_{i}\in \mathbb{R}^{v}

图 计算注意力汇聚的输出为值的加权和

其中,查询\mathbf{q}和键\mathbf{k_{i}}的注意力权重\mathbf{\alpha} \left ( \mathbf{q},\mathbf{k}_{i} \right ),该权重是一个标量,是通过将注意力评分函数a将两个向量映射成标量a\left ( \mathbf{q},\mathbf{k}_{i}\right ),再经过softmax运算得到的:

\mathbf{\alpha} \left ( \mathbf{q},\mathbf{k}_{i} \right )=softmax\left ( a\left ( \mathbf{q},\mathbf{k}_{i}\right ) \right )=\frac{exp\left (a\left ( \mathbf{q},\mathbf{k}_{i}\right ) \right )}{\sum_{j=1}^{m}exp\left (a\left ( \mathbf{q},\mathbf{k}_{j}\right ) \right )}\in \mathbb{R}

加性注意力:

a\left ( \mathbf{q},\mathbf{k}\right )=\mathbf{w}_{v }^{\top}tanh\left ( \mathbf{W}_{q}\mathbf{q} +\mathbf{W}_{k}\mathbf{k}\right )\in \mathbb{R}

缩放点积注意力:

a\left ( \mathbf{q},\mathbf{k}\right )=\mathbf{q}^{\top}\mathbf{k}/\sqrt{d}

8.3 Bahdanau注意力

将上下文变量视为注意力集中的输出。

\mathbf{c}_{​{t}'}=\sum_{t=1}^{T}\alpha \left ( \mathbf{c}_{​{t}'},\mathbf{h}_{t}\right )\mathbf{h}_{t}

8.4 多头注意力

与其只使用单独一个注意力汇聚,我们可以用独立学习到的h组不同的线性投影来变换查询、键和值。

\mathbf{h}_{i}=f\left (\mathbf{ W}_{i}^{\left (q \right )}\mathbf{q},\mathbf{ W}_{i}^{\left (k \right )}\mathbf{k},\mathbf{ W}_{i}^{\left (v \right )}\mathbf{v}\right )\in \mathbb{R}^{p_{v}}

\mathbf{H}=\mathbf{W}_{o}\begin{bmatrix} \mathbf{h}_{1} \\ \vdots \\ \mathbf{h}_{h} \end{bmatrix}\in \mathbb{R}^{p_{o}}

8.5 自注意力

       每个查询都会关注所有的键-值对,并生成一个注意力输出。由于查询、键、值来自同一组输入,因此被称为自注意力(self-attention),也被称为内部注意力(intra-attention)。

       给定一个词元组成的输入序列\mathbf{x}_{1},\cdots ,\mathbf{x}_{n},其中任意\textbf{x}_{i}\in \mathbb{R}^{d}\left ( 1\leq i\leq n \right )。该序列的自注意输出为一个长度相同的序列\mathbf{y}_{1},\cdots ,\mathbf{y}_{n},其中

\mathbf{y}_{i}=f\left (\mathbf{ x}_{i},\left (\mathbf{ x}_{1},\mathbf{ x}_{1} \right ),\cdots ,\left (\mathbf{ x}_{n},\mathbf{ x}_{n} \right )\right )\in \mathbb{R}^{d}

8.6 位置编码

       在处理词元序列时,循环神经网络是逐个重复地处理词元的,而自注意力则因为并行计算而放弃了顺序操作。

       为了使用序列的顺序信息,通过在输入表示中添加位置编码来注入绝对的或相对的位置信息。编码(Encoding)通常是指将原始数据或信息转化为计算机可处理的格式的过程。位置编码可以通过学习得到,也可以直接固定。

       介绍一个具体编码方式:基于正弦函数和余弦函数的固定位置编码。

       基于正弦函数和余弦函数的固定位置编码是Transformer模型中用于捕获输入序列中词序信息的一种重要方法。在原始Transformer论文《Attention is All You Need》中提出的方案,位置编码(Positional Encoding)为序列中的每个位置生成一个与词嵌入维度相同的向量,并将这个向量直接加到该位置对应的词嵌入上。

       假设输入表示\textbf{X}\in \mathbb{R}^{n\times d}包含一个序列中n个词元的d维嵌入表示。位置编码使用相同形状的位置嵌入矩阵\textbf{P}\in \mathbb{R}^{n\times d},输出\mathbf{X}+\mathbf{P}。矩阵\mathbf{P}i行、第2j列和第2j+1列上的元素分别为:

p_{i,2j}=sin\left ( \frac{i}{10000^{2j/d}} \right )

p_{i,2j+1}=cos\left ( \frac{i}{10000^{2j/d}} \right ).

9. Transformer

       与CNN和RNN相比,自注意力同时具有并行计算和最短最大路径长度这两个优势。因此,使用自注意力来设计深度架构是很有吸引力的。Transformer模型完全基于注意力机制,没有任何卷积层或循环神经网络层。

       Transformer最初是应用于在文本数据上的序列到序列学习,现在已经推广到各种现代的深度学习中,例如语言、视觉、语音和强化学习领域。

图   transformer架构 

 上面两幅展示了Transformer的中文架构和英文架构,英文架构类的模块名称和具体代码一一对应,方便大家对照代码、理解和使用。从宏观角度来看,Transformer的编码器和解码器都是基于自注意力的模块叠加而成的。其中,编码器是由多个相同的层叠加而成的,每个层有两个子层,第一个子层是多头自注意力(multi-head self-attention),第二个子层是基于位置的前馈网络(pointwise feed-forward network)。解码器也是由多个相同的层叠加而成,除了编码器中描述的两个子层,解码器还有第三个子层,称为编码器-解码器注意力层(encoder-decoder attention)。

9.1、编码器

9.1.1 编码器介绍

        从宏观⻆度来看,Transformer的编码器是由多个相同的层叠加⽽ 成的,每个层都有两个⼦层(⼦层表⽰为sublayer)。第⼀个⼦层是多头⾃注意⼒(multi-head self-attention) 汇聚;第⼆个⼦层是基于位置的前馈⽹络(positionwise feed-forward network)。

     具体来说,在计算编码器 的⾃注意⼒时,查询、键和值都来⾃前⼀个编码器层的输出。受残差⽹络的启发,每个⼦层都采⽤了残差连接(residual connection)。在Transformer中,对于序列中任何位置的任何输⼊x\in \mathbb{R}^{d},都要求满足sublayer\left ( x \right )\in \mathbb{R}^{d},以便残差连接满⾜x+sublayer\left ( x \right )\in \mathbb{R}^{d}。在残差连接的加法计算之后,紧接着应⽤层 规范化(layer normalization)。因此,输⼊序列对应的每个位置,Transformer编码器都将输出⼀个d维表⽰向量。

9.1.2 编码器中各模块的实现

1. Positionwise FNN实现(对照架构图中的Positionwise FNN组件)

        基于位置的前馈⽹络(Positionwise FNN)对序列中的所有位置的表⽰进⾏变换时使⽤的是同⼀个多层感知机(MLP),这就是称前馈⽹络是基于位置的(positionwise)的原因。在下⾯的实现中,输⼊X的形状(批量⼤⼩,时间步数或序列 ⻓度,隐单元数或特征维度)将被⼀个两层的感知机转换成形状为(批量⼤⼩,时间步数,ffn_num_outputs) 的输出张量。

代码:

#@save
class PositionWiseFFN(nn.Module):
"""基于位置的前馈⽹络"""
   def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,**kwargs):
       super(PositionWiseFFN, self).__init__(**kwargs)
       self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
       self.relu = nn.ReLU()
       self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)

   def forward(self, X):
       return self.dense2(self.relu(self.dense1(X)))

代码解释: 

这段代码是用PyTorch框架定义的一个类 PositionWiseFFN,它是一个基于位置的前馈神经网络(Position-wise Feed-Forward Network),通常用于Transformer架构中。此类实现了一个简单的两层全连接神经网络,每一层后接ReLU激活函数。

代码解释:

类定义:

class PositionWiseFFN(nn.Module):
    """基于位置的前馈⽹络"""
这里定义了一个名为 PositionWiseFFN 的类,该类继承自 PyTorch 中的 nn.Module 类,这是构建神经网络模块的基础类。

初始化方法 __init__:

def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs, **kwargs):
    super(PositionWiseFFN, self).__init__(**kwargs)
    self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
    self.relu = nn.ReLU()
    self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)
初始化时,通过 super().__init__(**kwargs) 调用了父类 nn.Module 的初始化方法。
定义了两个线性层(全连接层):self.dense1 和 self.dense2。dense1 输入维度为 ffn_num_input,输出维度为 ffn_num_hiddens;dense2 输入维度与隐藏层输出维度相同,即 ffn_num_hiddens,输出维度为 ffn_num_outputs。
同时定义了一个ReLU激活函数 self.relu,在前馈过程中将被应用在线性层之间。

前向传播方法 forward:

def forward(self, X):
    return self.dense2(self.relu(self.dense1(X)))
在这个方法中,定义了输入数据 X 经过模型的处理流程。首先,X 通过第一个线性层 self.dense1 计算输出。
然后,对线性层的输出应用ReLU激活函数 self.relu 进行非线性变换。
最后,将ReLU激活后的结果送入第二个线性层 self.dense2 进行计算,得到最终的输出。
总结来说,这个类实现了 Transformer 中的一个基本单元——位置感知前馈神经网络,其结构为:输入 -> Linear -> ReLU -> Linear -> 输出。这个模块应用于每个位置上的输入特征上,独立地进行计算并增加非线性表达能力。
2. Add & norm组件的实现

       现在我们关注架构图中的加法和规范化(Add & norm)组件。正如在本节开头所述,这是由残差连接和紧随其后的层规范化组成的。两者都是构建有效的深度架构的关键。

       层规范化和批量规范化:后续将解释在⼀个⼩批量的样本内基于批量规范化对数据进⾏重新中⼼化和重新缩放的调整。层规范化和批量规范化的⽬标相同,但层规范化是基于特征维度进⾏规范化。尽管批量规范化在计算机视觉中被⼴泛 应⽤,但在⾃然语⾔处理任务中(输⼊通常是变⻓序列)批量规范化通常不如层规范化的效果好。 

现在可以使⽤残差连接和层规范化来实现AddNorm类。暂退法也被作为正则化⽅法使⽤。

代码:

class AddNorm(nn.Module):
"""残差连接后进⾏层规范化"""
      def __init__(self, normalized_shape, dropout, **kwargs):
          super(AddNorm, self).__init__(**kwargs)
          self.dropout = nn.Dropout(dropout)
          self.ln = nn.LayerNorm(normalized_shape)

      def forward(self, X, Y):
          return self.ln(self.dropout(Y) + X)

代码解释:

这段代码定义了一个名为 AddNorm 的类,该类继承自 PyTorch 中的 nn.Module 类。这个类在Transformer架构中实现了一个残差连接(Residual Connection)与层规范化(Layer Normalization)相结合的模块。

代码解释:

类定义:
python
class AddNorm(nn.Module):
    """残差连接后进行层规范化"""
这个类表示一个神经网络模块,其功能是在执行层规范化操作之前先进行残差连接。
初始化方法 __init__:
python
def __init__(self, normalized_shape, dropout, **kwargs):
    super(AddNorm, self).__init__(**kwargs)
    self.dropout = nn.Dropout(dropout)
    self.ln = nn.LayerNorm(normalized_shape)
初始化时,调用父类 nn.Module 的初始化方法。
定义了一个 dropout 层,使用了给定的 dropout 参数来控制随机失活的比例。
创建了一个 nn.LayerNorm 对象,用于对指定维度大小(normalized_shape)的数据进行层规范化处理。
前向传播方法 forward:
python
def forward(self, X, Y):
    return self.ln(self.dropout(Y) + X)
在 forward 方法中,模型接收两个输入变量 X 和 Y。
先将输入 Y 通过 dropout 层,以一定概率丢弃部分激活值,从而增加模型的泛化能力。
将经过 dropout 操作后的 Y 与原始输入 X 相加,实现残差连接,允许信息直接从上一层传递到下一层。
最后,将相加的结果送入 self.ln 即 Layer Normalization 层进行规范化处理,确保每一层的输出具有稳定的分布,有利于梯度传播和训练过程。
所以,整个 AddNorm 模块的作用是首先对输入 Y 进行可能的随机失活,然后将其与另一个输入 X 做残差连接,并对结果应用层规范化,这是Transformer模型中常见的结构之一。
3. Multi-head attention组件的实现

      在实践中,当给定相同的查询、键和值的集合时,我们希望模型可以基于相同的注意⼒机制学习到不同的⾏ 为,然后将不同的⾏为作为知识组合起来,捕获序列内各种范围的依赖关系(例如,短距离依赖和⻓距离依 赖关系)。因此,允许注意⼒机制组合使⽤查询、键和值的不同⼦空间表⽰(representation subspaces可能是有益的。

       为此,与其只使⽤单独⼀个注意⼒汇聚,我们可以⽤独⽴学习得到的h组不同的 线性投影(linear projections) 来变换查询、键和值。然后,这h组变换后的查询、键和值将并⾏地送到注意⼒汇聚中。最后,将这h个注意 ⼒汇聚的输出拼接在⼀起,并且通过另⼀个可以学习的线性投影进⾏变换,以产⽣最终输出。这种设计被称 为多头注意⼒(multihead attention)。对于h个注意⼒汇聚输出,每⼀个注意⼒汇聚都 被称作⼀个头(head)。下图 展⽰了使⽤全连接层来实现可学习的线性变换的多头注意⼒。

图 多头注意⼒:多个头连结后做线性变换 

      模型:     

\mathbf{h}_{i}=f\left (\mathbf{ W}_{i}^{\left (q \right )}\mathbf{q},\mathbf{ W}_{i}^{\left (k \right )}\mathbf{k},\mathbf{ W}_{i}^{\left (v \right )}\mathbf{v}\right )\in \mathbb{R}^{p_{v}}

\mathbf{H}=\mathbf{W}_{o}\begin{bmatrix} \mathbf{h}_{1} \\ \vdots \\ \mathbf{h}_{h} \end{bmatrix}\in \mathbb{R}^{p_{o}}

       在实现过程中通常选择缩放点积注意⼒作为每⼀个注意⼒头。为了避免计算代价和参数代价的⼤幅增⻓,我 们设定p_{q} = p_{k} = p_{v} = p_{o}/h。值得注意的是,如果将查询、键和值的线性变换的输出数量设置为p_{q}h = p_{k}h = p_{v}h = p_{o},则可以并⾏计算h个头。在下⾯的实现中,p_{o}是通过参数num_hiddens指定的。

     代码:

#@save
class MultiHeadAttention(nn.Module):
"""多头注意⼒"""
      def __init__(self, key_size, query_size, value_size, num_hiddens,
                   num_heads, dropout, bias=False, **kwargs):
          super(MultiHeadAttention, self).__init__(**kwargs)
          self.num_heads = num_heads
          self.attention = d2l.DotProductAttention(dropout)
          self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
          self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
          self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
          self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)

      def forward(self, queries, keys, values, valid_lens):
          # queries,keys,values的形状:
          # (batch_size,查询或者“键-值”对的个数,num_hiddens)
          # valid_lens 的形状:
          # (batch_size,)或(batch_size,查询的个数)
          # 经过变换后,输出的queries,keys,values 的形状:
          # (batch_size*num_heads,查询或者“键-值”对的个数,
          # num_hiddens/num_heads)
          queries = transpose_qkv(self.W_q(queries), self.num_heads)
          keys = transpose_qkv(self.W_k(keys), self.num_heads)
          values = transpose_qkv(self.W_v(values), self.num_heads)
          if valid_lens is not None:
          # 在轴0,将第⼀项(标量或者⽮量)复制num_heads次,
          # 然后如此复制第⼆项,然后诸如此类。
          valid_lens = torch.repeat_interleave(
          valid_lens, repeats=self.num_heads, dim=0)
          # output的形状:(batch_size*num_heads,查询的个数,
          # num_hiddens/num_heads)
          output = self.attention(queries, keys, values, valid_lens)
          # output_concat的形状:(batch_size,查询的个数,num_hiddens)
          output_concat = transpose_output(output, self.num_heads)
          return self.W_o(output_concat)

代码详细解释:

代码定义了一个名为MultiHeadAttention的类,它是实现Transformer中多头注意力机制的核心模块。这个类基于PyTorch框架构建,并且遵循了Dive into Deep Learning (d2l) 一书中的实现风格。以下是对代码逐段详细解释:

类定义与初始化:

继承自nn.Module,这是PyTorch中所有神经网络模块的基础类。

初始化函数__init__接收多个参数:

key_size, query_size, value_size: 分别是键、查询和值向量的维度。
num_hiddens: 每个注意力头内部的隐藏层维度(每个头的输入和输出维度)。
num_heads: 多头注意力的头数。
dropout: 注意力机制中的 dropout 率,用于防止过拟合。
bias: 可选布尔值,决定线性变换层是否使用偏置项。
定义内部属性:

self.num_heads:保存注意力头的数量。
self.attention:实例化一个d2l.DotProductAttention对象,这是一个点积注意力子模块,包含了缩放点积注意力计算以及可能的dropout操作。
self.W_q, self.W_k, self.W_v:分别对应三个线性层,将查询、键、值映射到隐藏层维度(即num_hiddens)。
self.W_o:最后一个线性层,将从多头注意力得到的结果转换回原始的num_hiddens维度。
forward方法:

输入包括queries, keys, values,它们通常是从编码器或解码器的不同位置获取的特征向量,形状为 (batch_size, query_or_key_value_pairs_num, num_hiddens);valid_lens 是序列的有效长度,对于变长序列做掩码处理时有用,其形状可以是 (batch_size,) 或 (batch_size, query_num)。

首先通过对应的线性层(self.W_q, self.W_k, self.W_v)将查询、键和值投影到新的空间,并通过transpose_qkv函数进行转置和重塑,使得形状变为 (batch_size*num_heads, query_or_key_value_pairs_num, num_hiddens/num_heads),这样就可以实现并行计算多个注意力头。

如果传入了有效长度valid_lens,则会对其进行复制以匹配多个注意力头的数量。

调用self.attention(点积注意力)来计算注意力得分并加权求和得到上下文向量,同时应用dropout和masking。

通过transpose_output函数对结果进行反向转置和重塑,合并来自所有注意力头的输出,恢复成单个注意力头的形状(batch_size, query_num, num_hiddens)。

最后,将整合后的注意力输出通过线性层self.W_o进一步映射到最终的输出维度。

总之,这段代码实现了多头注意力机制,它能够并行地执行多个注意力头的计算,并将各个头的输出融合在一起,从而增强了模型捕捉不同表示子空间中信息的能力。

9.1.3 编码器的整体实现

1. EncoderBlock

有了组成Transformer编码器的基础组件,现在可以先实现编码器中的⼀个层。下⾯EncoderBlock类包含 两个⼦层:多头⾃注意⼒和基于位置的前馈⽹络,这两个⼦层都使⽤了残差连接和紧随的层规范化。

代码:

class EncoderBlock(nn.Module):
"""Transformer编码器块"""
      def __init__(self, key_size, query_size, value_size, num_hiddens,
                   norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                   dropout, use_bias=False, **kwargs):
          super(EncoderBlock, self).__init__(**kwargs)
          self.attention = d2l.MultiHeadAttention(
          key_size, query_size, value_size, num_hiddens, num_heads, dropout,use_bias)
          self.addnorm1 = AddNorm(norm_shape, dropout)
          self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)
          self.addnorm2 = AddNorm(norm_shape, dropout)

      def forward(self, X, valid_lens):
          Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
          return self.addnorm2(Y, self.ffn(Y))

代码解释:

这段代码定义了一个名为EncoderBlock的类,它是Transformer模型中的编码器部分的基本组成单元,即一个编码块。这个类继承自PyTorch的nn.Module基类,并在初始化函数中构建了两个主要组成部分:多头注意力机制(Multi-Head Attention)和前馈神经网络(Position-wise Feed-Forward Network, FFN)。同时,该类还包含了两个用于添加残差连接(Add)与层归一化(Norm)的子模块。

初始化方法 __init__:

接收多个参数,如关键尺寸(key_size)、查询尺寸(query_size)、值尺寸(value_size)、隐藏层大小(num_hiddens)、层归一化的形状(norm_shape)、FFN输入维度(ffn_num_input)、FFN隐藏层维度(ffn_num_hiddens)、注意力头的数量(num_heads)、dropout率等。
定义内部属性:
attention: 使用d2l库中的MultiHeadAttention类创建一个多头注意力子模块,其参数由传入的关键、查询、值尺寸以及隐藏层大小、注意力头数量和dropout率确定。
addnorm1 和 addnorm2: 分别是两次残差连接后接层归一化的组合模块,这里使用的是自定义的AddNorm类,它包含加法操作(Add)和层归一化(Norm),并接收norm_shape和dropout作为参数。
ffn: 创建一个位置感知的前馈神经网络子模块,利用PositionWiseFFN类实现,其参数包括FFN的输入维度、隐藏层维度和输出维度(此处与隐藏层大小相同)。
正向传播方法 forward:

输入参数为X(编码器的输入序列)和valid_lens(有效序列长度)。
首先,将X传递给attention子模块计算多头注意力结果,并通过addnorm1模块进行残差连接和层归一化,得到中间表示Y。
最后,将经过注意力机制处理后的Y传递给ffn子模块进行前馈神经网络计算,再通过addnorm2模块进行第二次残差连接和层归一化,从而得到最终的编码块输出。
综上所述,EncoderBlock类实现了Transformer编码器的一个完整基本块,包括多头注意力机制和前馈神经网络结构,并结合了残差连接和层归一化来优化训练过程和提高模型性能。
 2. TransformerEncoder

       实现的Transformer编码器的代码中,堆叠了num_layers个EncoderBlock类的实例。由于这⾥使⽤的是 值范围在−1和1之间的固定位置编码,因此通过学习得到的输⼊的嵌⼊表⽰的值需要先乘以嵌⼊维度的平⽅ 根进⾏重新缩放,然后再与位置编码相加。

代码:

class TransformerEncoder(d2l.Encoder):
"""Transformer编码器"""
      def __init__(self, vocab_size, key_size, query_size, value_size,
                   num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                   num_heads, num_layers, dropout, use_bias=False, **kwargs):
          super(TransformerEncoder, self).__init__(**kwargs)
          self.num_hiddens = num_hiddens
          self.embedding = nn.Embedding(vocab_size, num_hiddens)
          self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
          self.blks = nn.Sequential()
          for i in range(num_layers):
          self.blks.add_module("block"+str(i),
          EncoderBlock(key_size, query_size, value_size, num_hiddens,
                       norm_shape, ffn_num_input, ffn_num_hiddens,
                       num_heads, dropout, use_bias))
      def forward(self, X, valid_lens, *args):
      # 因为位置编码值在-1和1之间,
      # 因此嵌⼊值乘以嵌⼊维度的平⽅根进⾏缩放,
      # 然后再与位置编码相加。
          X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
          self.attention_weights = [None] * len(self.blks)
          for i, blk in enumerate(self.blks):
              X = blk(X, valid_lens)
              self.attention_weights[i] = blk.attention.attention.attention_weights
          return X

代码解释: 

这段代码定义了一个名为TransformerEncoder的类,它是基于深度学习框架实现的一个Transformer模型的编码器部分。这个类继承自d2l库中的Encoder基类,并在初始化和前向传播过程中实现了以下功能:

初始化方法 __init__:

接收一系列参数,包括词汇表大小(vocab_size)、关键尺寸(key_size)、查询尺寸(query_size)、值尺寸(value_size)、隐藏层大小(num_hiddens)、归一化形状(norm_shape)、FFN输入维度(ffn_num_input)、FFN隐藏层维度(ffn_num_hiddens)、注意力头数量(num_heads)、编码器层数量(num_layers)以及dropout率等。
创建一个词嵌入层:使用nn.Embedding来将输入的词索引映射到隐藏向量空间。
创建一个位置编码层:通过d2l.PositionalEncoding类为输入序列添加位置信息,以帮助模型理解序列中元素的位置关系。
构建编码器块:利用循环结构创建指定数量(num_layers)的EncoderBlock实例,并将其串联成一个序列,存储在self.blks属性中。
正向传播方法 forward:

输入参数为X(输入序列的词索引),valid_lens(有效序列长度列表,用于处理变长序列时的掩码操作)以及其他可能的额外参数。
首先,对输入序列进行词嵌入操作并应用位置编码。这里乘以math.sqrt(self.num_hiddens)是为了确保在加入位置编码后,数值范围保持合理,同时避免了因维度增大而导致的梯度消失或爆炸问题。
然后,遍历所有的编码器块,对每个编码块执行前向传播计算,并将当前块的注意力权重保存在self.attention_weights列表中,便于后续可视化或分析。
最终返回经过所有编码块处理后的输出表示X。
总结起来,TransformerEncoder类构建了一个完整的Transformer编码器结构,它包含了词嵌入、位置编码以及多层由多头注意力机制和前馈神经网络组成的编码块,在处理输入序列时能够捕获上下文依赖和位置信息。

9.2、解码器

      Transformer解码器也是由多个相同的层组成。每个层都是用DecoderBlock实现的。

       在DecoderBlock(解码器模块)类中,实现的每个层,包含三个子层:解码器自注意力(decoder attention)、编码器-解码器注意力(encoder-decoder attention)和基于位置的前馈网络(PositionWiseFFN)。这些子层也都被残差连接和紧随的层规范化围绕。

        在掩蔽多头解码器自注意力层(第一个子层,decoder attention)中,查询、键和值都来自上一个解码器层的输出。

       序列到序列模型,在训练阶段,其输出序列的所有位置(时间步)的词元都是已知的;然而,在预测阶段,其输出序列的词元是逐个生成的。在解码器的任何时间步中,只有生成的词元才能用于解码器的自注意力计算中。

       为了在解码器中保留自回归的属性,其掩蔽自注意力设定了参数dec_valid_lens,以便任何查询都只会与解码器中所有已生成词元位置(即知道该查询位置为止)进行注意力计算。

补充解释:什么是自回归属性?

自回归属性(Autoregressive Property)是指在统计模型或机器学习模型中,当前状态或者输出值能够通过之前一个或多个时间步长的状态或输出值的线性组合来预测的特性。在时间序列分析和序列生成任务中,这种属性尤为重要。

具体到机器学习领域,例如在自然语言处理(NLP)中的Transformer架构中,解码器部分就体现了自回归属性。在生成文本时,解码器在预测下一个单词时只能依赖于已知的、先前生成的单词序列,而不能“偷看”未来还未生成的部分。这意味着模型是按照从左到右的顺序逐个生成序列元素,每个新生成的元素都基于历史生成的信息。

数学上,自回归模型通常表示为AR(p)模型,其中p代表模型考虑过去p个时间点上的数据来进行当前时刻预测的程度:

代码:

class DecoderBlock(nn.Module):
"""解码器中第i个块"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, i, **kwargs):
        super(DecoderBlock, self).__init__(**kwargs)
        self.i = i
        self.attention1 = d2l.MultiHeadAttention(
        key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.attention2 = d2l.MultiHeadAttention(
        key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm2 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,num_hiddens)
        self.addnorm3 = AddNorm(norm_shape, dropout)
     def forward(self, X, state):
         enc_outputs, enc_valid_lens = state[0], state[1]
         # 训练阶段,输出序列的所有词元都在同⼀时间处理,
         # 因此state[2][self.i]初始化为None。
         # 预测阶段,输出序列是通过词元⼀个接着⼀个解码的,
         # 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表⽰
         if state[2][self.i] is None:
             key_values = X
         else:
             key_values = torch.cat((state[2][self.i], X), axis=1)
         state[2][self.i] = key_values
         if self.training:
             batch_size, num_steps, _ = X.shape
             # dec_valid_lens的开头:(batch_size,num_steps),
             # 其中每⼀⾏是[1,2,...,num_steps]
             dec_valid_lens = torch.arange(
                 1, num_steps+1,device=X.device).repeat(batch_size, 1)
         else:
             dec_valid_lens = None
         # ⾃注意⼒
         X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
         Y = self.addnorm1(X, X2)
         # 编码器-解码器注意⼒。
         # enc_outputs的开头:(batch_size,num_steps,num_hiddens)
         Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
         Z = self.addnorm2(Y, Y2)
         return self.addnorm3(Z, self.ffn(Z)), state

 代码解释:

这段代码定义了一个名为DecoderBlock的类,它继承自PyTorch的nn.Module类,用于构建Transformer解码器中的一个基本块。这个类主要包含两个多头注意力机制和一个位置感知前馈神经网络(Position-wise Feed-Forward Network, PFFN),以及相应的归一化和残差连接层。

参数说明:

key_size: 表示在多头注意力机制中键向量(Key)的维度。
query_size: 表示查询向量(Query)的维度,在解码过程中通常是与键向量相同的维度。
value_size: 表示值向量(Value)的维度,同样通常与键向量相同。
num_hiddens: 指隐藏层神经元的数量,也是自注意力和编码器-解码器注意力输出的特征维度。
norm_shape: 用于设置Layer Normalization层输入形状的参数,可能是一个元组表示通道数和序列长度。
ffn_num_input: 前馈神经网络(Position-wise FFN)的第一个全连接层的输入维度,通常与num_hiddens相同。
ffn_num_hiddens: 前馈神经网络中间层神经元的数量,即隐藏层大小。
num_heads: 多头注意力中并行工作的注意力头的数量。
dropout: 在训练过程中使用的随机失活率,以防止过拟合。
i: 这个解码块在解码器堆叠结构中的索引编号,有助于区分不同的解码块状态。
类初始化时做的事情:

使用super().__init__(**kwargs)调用父类nn.Module的初始化方法,并传递其他可变关键字参数。

将传入的i赋给实例变量self.i,用于记录该解码块在解码器层级中的位置。

初始化两个d2l.MultiHeadAttention对象,分别对应于自注意力机制(decoder自身的上下文依赖)和编码器-解码器注意力机制(利用编码器信息)。它们都使用了相同的参数配置,包括key_size, query_size, value_size, num_hiddens, num_heads和dropout。

初始化两个AddNorm层,分别用于将上述两个注意力机制的输出与原始输入进行残差连接和层归一化处理。

初始化一个PositionWiseFFN对象,代表位置感知前馈神经网络,其输入、中间层和输出维度分别为ffn_num_input, ffn_num_hiddens, num_hiddens。

最后,初始化第三个AddNorm层,用于对前馈神经网络的输出执行残差连接和层归一化操作。

forward(...)方法:

输入X为当前解码器的输入,state是一个包含编码器输出、有效长度以及其他中间结果的状态元组。

在训练阶段和预测阶段处理方式有所不同。在训练时,一次性处理整个输出序列;而在预测阶段,逐词解码,因此需要保存中间解码状态。

对于自注意力子层,根据state[2][self.i]是否为None来决定key_values,即合并当前解码块之前的输出与当前输入。

计算自注意力输出X2,并经过AddNorm得到Y。

使用编码器-解码器注意力机制计算Y2,同样通过AddNorm得到Z。

最后,将Z传递给前馈神经网络,并再次通过AddNorm层与原始Z相加,得到最终的输出。

方法返回更新后的输出以及整个解码过程中的状态。

我们构建由num_layers个DecoderBlock实例组成的完整的Transformer解码器。最后,通过⼀个全连 接层计算所有vocab_size个可能的输出词元的预测值。解码器的⾃注意⼒权重和编码器解码器注意⼒权重都 被存储下来,⽅便⽇后可视化的需要。

代码

class TransformerDecoder(d2l.AttentionDecoder):
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, **kwargs):
        super(TransformerDecoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.num_layers = num_layers
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                DecoderBlock(key_size, query_size, value_size, num_hiddens,
                             norm_shape, ffn_num_input, ffn_num_hiddens,
                             num_heads, dropout, i))
        self.dense = nn.Linear(num_hiddens, vocab_size)
  
    def init_state(self, enc_outputs, enc_valid_lens, *args):
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]
    def forward(self, X, state):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self._attention_weights = [[None] * len(self.blks) for _ in range (2)]
        for i, blk in enumerate(self.blks):
            X, state = blk(X, state)
            # 解码器⾃注意⼒权重
            self._attention_weights[0][
                i] = blk.attention1.attention.attention_weights
            # “编码器-解码器”⾃注意⼒权重
            self._attention_weights[1][
                i] = blk.attention2.attention.attention_weights
            return self.dense(X), state
     @property
     def attention_weights(self):
     return self._attention_weights

代码解释:

这段代码定义了一个名为TransformerDecoder的类,它继承自d2l.AttentionDecoder。这个类实现了Transformer模型中的解码器部分,用于序列生成任务,如机器翻译。

初始化方法 __init__:

接受一系列参数,包括词汇表大小(vocab_size)、注意力机制中关键、查询和值向量的维度、隐藏层大小(num_hiddens)、正则化层形状(norm_shape)、前馈神经网络输入与隐藏层单元数(ffn_num_input, ffn_num_hiddens)、注意力头数量(num_heads)、编码器层数量(num_layers)以及dropout率等。

初始化词嵌入层(embedding layer),将输入的词索引映射到隐藏空间。

初始化位置编码层(positional encoding),为输入序列添加位置信息。

创建一个nn.Sequential容器,通过循环结构添加指定数量的DecoderBlock实例作为解码器块,这些块在构造时传入相应的参数,并通过i来区分不同的解码块。

定义全连接层(dense layer),用于将最后的隐藏状态转换为词汇表大小的输出概率分布。

init_state方法:

初始化解码器的状态,返回一个包含编码器输出、有效长度列表以及一个所有解码块中间状态(默认为None)的列表。
forward方法:

首先对输入序列进行词嵌入并加上位置编码,同时对嵌入结果进行缩放以保证数值稳定性。

通过遍历所有的解码块执行前向传播计算,保存每个解码块的自注意力权重(decoder self-attention weights)和编码器-解码器注意力权重(encoder-decoder attention weights)。

返回经过解码器处理后的最终输出和更新后的状态。

属性 attention_weights:

提供一个只读属性,用于获取整个解码过程中各个解码块的注意力权重。这里包含了两组注意力权重,一组是解码器内部各时间步之间的自注意力权重,另一组是编码器和当前解码时间步之间的注意力权重。这些权重可以用来可视化或分析模型关注的重点。

10、各种注意力机制

       注意力机制(Attention Mechanism)在深度学习中是一种用于处理序列数据的有效工具,它允许模型根据当前任务的上下文动态地聚焦于输入数据的不同部分。自2014年左右首次引入以来,注意力模型已经在自然语言处理、计算机视觉、语音识别等众多领域取得了显著成果。以下是更多不同类型的注意力模型:

  1. 加权求和注意力(Additive Attention / Bahdanau Attention)

    由Dzmitry Bahdanau等人在2014年的论文《Neural Machine Translation by Jointly Learning to Align and Translate》中提出,是最早应用于神经机器翻译中的注意力机制,通过一个加性函数计算查询与每个键的对齐分数,然后将这些分数作为权重分配给值进行加权求和。
  2. 缩放点积注意力(Scaled Dot-Product Attention / Luong Attention)

    由Thang Luong等人进一步发展,简化了注意力计算过程,采用点积方式计算对齐分数,并引入了缩放因子以防止数值不稳定问题。这种注意力形式后来成为Transformer模型的核心组成部分。
  3. 多头注意力(Multi-Head Attention)

    在2017年由Ashish Vaswani等人提出的Transformer架构中得到广泛应用,通过并行执行多个注意力“头”来分别关注输入的不同子空间特征,增强了模型捕捉复杂依赖关系的能力。
  4. 自我注意力(Self-Attention)

    同样来自Transformer模型,它不再依赖于固定的顺序或外部信息,而是让序列中的每个元素都能够与其他所有元素相互作用,找出自身的相关性。这对于捕获长距离依赖非常有效。
  5. 因果注意力(Causal Attention)

    在序列生成任务中,为了防止未来信息泄露到当前步骤的预测中,采用了因果掩码的自我注意力,使得模型只能基于过去的输入进行预测。
  6. 局部注意力(Local Attention)

    对于长序列,全范围的注意力可能会导致计算效率低下。局部注意力仅考虑输入序列的一个局部窗口,从而减少计算复杂度。
  7. 稀疏注意力(Sparse Attention)

    通过设计特定的模式或启发式方法降低注意力矩阵的稠密度,如Blockwise Self-Attention、Reformer中的Locality Sensitive Hashing等技术。
  8. 可解释注意力(Interpretable Attention)

    一些模型设计了具有明确解释性的注意力机制,比如可视化文本摘要中哪些词语受到了更多关注,或者图像分类中网络关注图像的哪个区域。
  9. 层次注意力(Hierarchical Attention)

    在处理像文档级别的任务时,可以先在词级别应用注意力,然后再在句子级别应用注意力,形成层次结构化的信息抽取。
  10. 动态卷积注意力(Dynamic Convolutional Attention)

    结合卷积神经网络和注意力机制,在处理序列数据时能够适应不同的时间步长或上下文长度。

以上只列举了一部分常见的注意力模型类型,随着研究的不断深入,还会有更多创新的注意力机制被提出并应用于各类AI任务中。

11、Transformers库

       Hugging Face Transformers库是一个广泛使用的Python库,它集成了大量的预训练模型,涵盖了自然语言处理(NLP)领域的多种任务和架构。随着时间的推移,该库中包含的模型数量不断增加,Transformers库已经整合了包括但不限于以下预训练模型:

  1. BERT 及其变体:BERT, BERT-Base, BERT-Large, DistilBERT, ALBERT, RoBERTa, SpanBERT等。

  2. GPT系列:GPT, GPT-2, GPT-3(虽然GPT-3不直接提供完整模型,但支持通过API使用),以及相关的开源实现如GPT-NeoX、GPT-J等。

  3. Transformer-XL 和 XLNet,分别用于处理长文本序列和引入顺序感知自回归机制。

  4. T5 文本到文本转换模型及各种规模版本。

  5. MT5 是多语言版的T5,用于处理多种语言的任务。

  6. Electra 采用了生成对抗网络思路进行预训练。

  7. Reformer 和 Longformer 针对长文档优化的模型,减少内存消耗并提高效率。

  8. BERTweet 专门针对推文数据训练的BERT模型。

  9. XLM-RoBERTa (XLM-R) 是一个大规模的多语言预训练模型。

  10. DeBERTa 和 DeBERTa-v2 对BERT进行了改进,增强了模型的表达能力。

  11. BigBird 是Google提出的可以处理更长上下文的稀疏注意力Transformer模型。

  12. ConvBERT 结合了卷积神经网络和Transformer的优点。

      此外,库中还包括大量针对特定任务或语言定制的预训练模型,例如语音相关的Wav2Vec2、Speech2Text,以及其他公司和研究团队发布的各种预训练模型。随着NLP领域的发展和社区贡献,Transformers库中的模型种类会持续增长和更新。要获取最新最全的模型列表,建议直接访问Hugging Face Model Hub查看。

11.1 Transformers库预训练模型的调用:

在Hugging Face Transformers库中,调用预训练模型进行预测和微调通常涉及以下步骤:

载入预训练模型

Python
1 from transformers import AutoModel, AutoTokenizer
2
3 # 指定模型名称或路径
4 model_name = "bert-base-uncased"  # 例如使用BERT的预训练模型
5
6 # 加载模型与对应的tokenizer
7 tokenizer = AutoTokenizer.from_pretrained(model_name)
8 model = AutoModel.from_pretrained(model_name)

Hugging Face Transformers库中的model_name可以是众多预训练模型的名称,这些模型涵盖了多种架构和任务。以下是一些流行和广泛使用的预训练模型示例:

  • BERT 系列:

    • bert-base-uncased
    • bert-large-cased
    • bert-base-multilingual-cased
  • GPT 系列:

    • gpt2
    • gpt-neo
    • gpt-j
    • gpt3(通过API访问)
  • GPT-3衍生模型 (如在Transformers库中可用的小型化版本):

    • gpt3-small
    • gpt3-medium
    • gpt3-large
  • RoBERTa 系列:

    • roberta-base
    • roberta-large
  • DistilBERT:

    • distilbert-base-uncased
  • ALBERT:

    • albert-base-v2
    • albert-large-v2
  • T5:

    • t5-small
    • t5-base
    • t5-large
  • XLM-RoBERTa:

    • xlm-roberta-base
    • xlm-roberta-large
  • Electra:

    • electra-base-discriminator
    • electra-large-generator

以及更多来自不同研究机构和个人贡献者上传到Hugging Face Model Hub上的模型。最新或所有可用模型列表,请查阅模型库以获取最准确和最新的信息。

预测(快速推理)

可以使用pipeline API来简化预测过程,比如文本分类任务:

Python
1 from transformers import pipeline
2
3 # 创建一个文本分类pipeline
4 classifier = pipeline("text-classification", model=model_name)
5
6 # 进行预测
7 prediction = classifier("This is an example text to classify.")
8 print(prediction)

对于更复杂的任务,如生成、问答等,也有相应的pipeline。

自定义预测(手动处理输入)

如果你需要对模型的输入输出有更多控制,可以手动编码和解码:

Python
1# 对文本进行编码
2inputs = tokenizer("Hello, world!", return_tensors="pt")
3
4# 获取模型预测
5outputs = model(**inputs)
6
7# 根据模型类型处理输出,例如对于Bert模型提取[CLS]标记的隐藏状态用于分类任务
8last_hidden_state = outputs.last_hidden_state[:, 0]
9
10# 对于生成任务,如GPT-2,则需使用模型的生成方法
11generated_text = model.generate(inputs["input_ids"], max_length=100, num_return_sequences=1, no_repeat_ngram_size=2, do_sample=True)
12decoded_text = tokenizer.decode(generated_text[0], skip_special_tokens=True)

微调模型

微调通常包括数据预处理、构建DataLoader、设置训练参数,并调用Trainer类:

Python
1from transformers import Trainer, TrainingArguments
2
3# 定义训练参数
4training_args = TrainingArguments(
5    output_dir='./results',
6    num_train_epochs=3,
7    per_device_train_batch_size=8,
8    logging_dir='./logs',
9    evaluation_strategy="epoch",
10)
11
12# 定义微调函数
13def compute_metrics(eval_pred):
14    predictions, labels = eval_pred
15    # 根据具体任务计算指标,例如准确率
16    accuracy = (predictions.argmax(axis=-1) == labels).float().mean()
17    return {"accuracy": accuracy}
18
19# 训练集、验证集准备
20train_dataset = ...  # 使用datasets库加载或自定义数据集
21eval_dataset = ...
22
23# 初始化Trainer
24trainer = Trainer(
25    model=model,
26    args=training_args,
27    train_dataset=train_dataset,
28    eval_dataset=eval_dataset,
29    compute_metrics=compute_metrics,
30)
31
32# 开始微调
33trainer.train()

请注意,以上代码仅为示例,并且实际使用时需要根据特定任务需求进行调整。同时,请确保已安装最新版本的Transformers库并查阅官方文档以获取最新API指南。

12、各种(商业)应用

待续……

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐