编辑
2024-07-02
AI
0
请注意,本文编写于 143 天前,最后修改于 110 天前,其中某些信息可能已经过时。

目录

背景
简述
原理
编码器
词嵌入
位置编码
编码
自注意力机制
多头自注意力
前馈神经网络
解码器
线性变化和softmax
编解码器协同工作
常见问题
幻觉
Transformer变种

背景

对于ChatGPT等大语言模型的技术原理,简单说来就是它们通过预测最有可能出现的下一个词来生成文本。这种效果类似于搜索引擎的自动补全功能,每当我们输入一个单词或者短句,输入框就开始预测后续的文本,概率越高的词排在越前面。那么,模型具体是如何计算各个词出现的概率的呢?

简述

首先,这些模型基于大量的文本数据进行训练,通过神经网络(比如下文提到的基于Transformer架构的神经网络)来学习语言的模式和结构。在训练过程中,模型会不断调整其内部参数,以最大化对训练数据中下一个词的预测准确性。

其次,模型使用的是一种称为“自回归”的方法,即它会根据前面的词语来预测下一个词。每次预测时,模型会计算出所有可能词的概率分布,然后选择概率最高的词作为输出,这些概率是通过复杂的数学计算得出的,涉及大量的矩阵运算和激活函数。模型的每一层都会对输入进行处理,逐层提取出更高层次的特征,最终在输出层生成一个概率分布。

总之,ChatGPT等生成式大语言模型通过大量数据的训练和复杂的神经网络计算,来预测最有可能的下一个词,从而实现文本生成。

而让ChatGPT成功出圈的底层技术框架就是Transformer。2017年,一篇谷歌的论文《Attention Is All You Need》 提出Transformer架构,这标志着在自然语言处理领域中从传统的循环神经网络(RNN)向Transformer架构的转变。这篇论文的重要贡献在于引入了自注意力机制(Self-Attention),通过这一机制,Transformer能够并行地处理输入序列中的各个位置信息,从而更好地捕捉长距离依赖关系。

随着Transformer的提出,它迅速在各种自然语言处理任务中取得了突出的成绩,如机器翻译、文本生成、语言理解等。这种架构的优势在于能够有效地处理长距离依赖和上下文理解,而不像传统的RNN架构那样容易受到梯度消失或梯度爆炸问题的困扰。同时,Transformer的提出也促进了预训练模型的发展,例如BERT(Bidirectional Encoder Representations from Transformers)和GPT(Generative Pre-trained Transformer)系列模型。这些模型在大规模文本语料库上进行预训练,并在特定任务上进行微调,取得了许多自然语言处理领域的最新突破。

原理

如下图,Transformer模型主要由两大部分组成:编码器(Encoder)和解码器(Decoder)。每个部分都是由多个相同的层堆叠而成,每层包含了多头注意力机制(Multi-head Attention)和位置全连接前馈网络。

Transformer模型的核心概念包括以下几个方面:

  • 注意力机制(Attention Mechanism):这是Transformer模型的核心组件,能够有效捕捉序列中的长距离依赖关系,从而提升语言理解能力。
  • 自注意力(Self-Attention):这是Transformer模型中特殊的一种注意力机制,用于计算序列中每个词语与其他词语之间的关联。
  • 位置编码(Positional Encoding):由于Transformer模型没有使用循环神经网络(RNN)或卷积神经网络(CNN),因此需要位置编码来捕捉序列中的位置信息。
  • 多头注意力(Multi-Head Attention):这是对自注意力的扩展方法,能够有效捕捉序列中的多个关注点。
  • 编码器-解码器架构(Encoder-Decoder Architecture):Transformer模型采用了这种架构,编码器负责将输入序列编码为内部表示,解码器则从这些内部表示生成输出序列。

编码器

编码组件由多个编码器(encoders)组成。在论文中,通常是将6个编码器堆叠在一起——这个数字并没有什么特殊的意义,你可以尝试使用其他数量的编码器。解码组件则由与编码器数量相同的解码器(decoders)组成。

具体来说,编码器的堆叠有助于模型捕捉更深层次的特征和关系。每个编码器由多个子层组成,包括自注意力机制和前馈神经网络,它们没有共享参数。通过多层堆叠,模型能够逐层提取和整合输入数据的复杂特征。

词嵌入

像大部分NLP应用一样,我们首先将每个输入单词通过词嵌入算法转换为词向量。

输入的文本首先会被token化,也就是先把输入拆分成各个token。

Token可以被视为文本的最小构成单元。根据不同的token化方法,短词可能每个词是一个token,而长词则可能被分解成多个token。例如,在自然语言处理领域,token化方法有多种,如字节对编码(BPE)和词汇表方法,它们会根据词的长度和复杂性来决定如何分解文本。短词如“cat”通常会作为一个完整的token,而较长的词如“internationalization”可能会被拆分成多个token,如“inter”, “national”, “ization”,以便更好地处理和理解文本内容。这种分解方式有助于提高模型的处理效率和准确性

然后,每个token会被用一个整数数字表示,这个数字被叫做token ID。这样做是因为计算机内部是无法存储文字的,任何字符最终都得用数字来表示。嵌入层的作用是让每个token都用向量表示,向量可以被简单的看为一串儿数字。

为什么已经用一个数字表示token了,又要用一串数字表示各个token?其中一个原因是一串数字能表达的含义是大于一个数字的,能包含更多语法语义信息等等 这就好比男人和女人这两个词,它们都在描述人类,但性别又是完全相反的。如果只用一个数据表示,那这个数字大小之间应该距离很大,还是距离很小呢

但如果有多个数字,我们就可以进行更多维度的表示,就比如说第一个数字可以表示是雌性的程度;第二个数字表示年龄大的程度;第3个表示社会阶层高的程度

相似的词所对应的嵌入向量在向量空间里距离也更近,而一些没啥关系的词之间的距离就很远。这有助于模型利用数学计算向量空间里的距离,去捕捉不同词在语义和语法等方面的相似性。

因此,词向量不仅可以帮模型理解词的语义,也可以捕捉词与词之间的复杂关系。我们这里为了直观,是用3维向量空间表示的,把向量长度相应简化成了3,而提出Transformer的论文里,向量长度是512,GPT-3是12288,所以可以想象能包含多少信息。

词嵌入过程只发生在最底层的编码器中。所有的编码器都有一个相同的特点,即它们接收一个向量列表。在底层(最开始)编码器中它就是词向量,但是在其他编码器中,它就是下一层编码器的输入(也是一个向量列表)。

一个编码器接收向量列表作为输入,接着将向量列表中的向量传递到自注意力层进行处理,然后传递到前馈神经网络层中,将输出结果传递到下一个编码器中。

位置编码

通过编码器的嵌入层得到词向量后,接下来的步骤是对这些向量进行位置编码。位置编码是将表示各个词在文本中顺序的向量与上一步得到的词向量相加,然后将结果传递给编码器。这样做的意义在于,模型不仅能够理解每个词的语义,还能捕捉到词在句子中的位置,从而理解不同词之间的顺序关系。具体来说,位置编码提供了位置信息,使得模型能够区分出“猫在树上”和“树在猫上”这样的句子结构差异。

位置编码通常采用固定的正弦和余弦函数来生成,这些函数能够为每个词的位置生成唯一的向量表示。

如下图,这个阶段中,各个词在文本中顺序的向量和上一步得到的词向量相加

相对位置编码机制:在Transformer中,为了能够在不造成时间混乱的情况下重复使用状态,引入了相对位置编码的概念。相对位置编码与传统的绝对位置编码不同,它只编码token之间的相对位置关系,而不是token与固定起始点的绝对位置。这种编码方式使得模型能够在处理长序列时更有效地利用位置信息,并且可以泛化至比在训练过程中观察到的长度更长的注意力长度。

Transformer模型使用位置编码(Positional Encoding)来为每个token引入位置信息,因为自注意力机制本身不包含位置信息。位置编码的设计使得模型能够区分不同位置的token,从而理解序列中的顺序关系。具体来说,位置编码是通过正弦和余弦函数来实现的。

每个token会被转换成一个高维向量,例如512维的向量:[0.12142, 0.34181, ...., -0.21231]。这个向量可以分为奇数和偶数两个部分。奇数部分使用cos函数进行编码,而偶数部分使用sin函数进行编码。 具体步骤如下:

  • 奇数部分编码:对于向量中的奇数位置,使用cos函数结合当前token的位置信息pos进行编码。公式为:
PE(pos,2i)=cos(pos100003idmodel)PE_{(pos,2i)}=\cos\left(\frac{pos}{10000^{\frac{3i}{d_{model}}}}\right)

其中,pos是token的位置,i是维度索引,dmodel是向量的维度dmodel是向量的维度(例如512)。

  • 偶数部分编码:对于向量中的偶数位置,使用sin函数结合当前token的位置信息pos进行编码。公式为:
PE(pos,2i+1)=sin(pos100002idmadel)PE_{(pos,2i+1)}=\sin\left(\frac{pos}{10000^{\frac{2i}{d_{madel}}}}\right)

其中,pos是token的位置,i是维度索引,dmodel是向量的维度。

  • 位置编码与token嵌入相加:将上述得到的位置编码向量与token的嵌入向量相加,得到位置编码后的输入嵌入(Positional Input Embeddings)。这种方法确保了模型在处理序列数据时能够感知到每个token的位置,从而更好地理解序列的结构和顺序。

编码

编码器的核心功能在于将输入转换成一种更为抽象的表示形式,这种表示形式通常是向量,即一组数字。这些数字不仅保留了输入文本的词汇信息和顺序关系,还捕捉了语法和语义上的重要特征。编码器能实现这一点的关键在于其自注意力机制。

输入到编码器的句子首先会经过一个自注意力(self-attention)层,这一层的作用是帮助编码器在对每个单词进行编码时,能够关注到输入句子中的其他单词及其之间的关系。例如,在处理句子“猫在树上”,自注意力机制可以帮助模型理解“猫”和“树”之间的空间关系,以及“在”这个词所表达的动作关系。我们将在后续的文章中深入探讨自注意力机制的细节。

自注意力层的输出会被传递到前馈(feed-forward)神经网络中。前馈神经网络将对每个单词的位置进行独立处理,进一步提取和整合特征。这种网络结构确保了每个单词的位置都能得到充分的关注和处理,从而提高模型的表示能力和预测精度。

通过这种机制,编码器能够生成更丰富、更有意义的向量表示,这对于后续的任务如翻译、文本生成和信息检索等都至关重要。自注意力机制的引入使得模型在处理复杂语言现象时表现得更加出色,提升了整体的理解和生成能力。

自注意力机制

我们先来看一个句子

The animal didn’t cross the street because it was too tired

这个“it”在这个句子是指什么呢?它指的是street还是这个animal呢?这对于人类来说是一个简单的问题,但是对于算法则不是。当模型处理这个单词“it”的时候,自注意力机制会允许“it”与“animal”建立联系。

随着模型处理输入序列中的每个单词,自注意力机制会关注整个输入序列中的所有单词,这有助于模型更好地对当前单词进行编码。

如果你熟悉RNN(循环神经网络),可以回想一下它是如何维持隐藏层状态的。RNN会将它已经处理过的前面的所有单词或向量的表示与它正在处理的当前单词或向量结合起来,从而在每一步都更新其隐藏状态。而自注意力机制则通过在每个时间步上考虑整个输入序列中的所有单词,将所有相关单词的信息融入到当前处理的单词中。

具体来说,自注意力机制会计算每个单词与其他所有单词的注意力权重,这些权重反映了它们之间的相关性。然后,模型使用这些权重来加权平均其他单词的表示,从而生成当前单词的新的表示。这种方法允许模型在处理每个单词时,动态地关注到与其相关的其他单词,从而捕捉到更丰富的上下文信息。

相比于RNN,自注意力机制具有更高的并行性,因为它在每一步都可以同时处理所有单词,而不需要像RNN那样依赖于前一步的隐藏状态。这使得自注意力机制在处理长序列时更加高效和稳定,避免了RNN中常见的梯度消失和爆炸问题。

注意力机制实现(计算)步骤

  • 从每个编码器的输入向量(每个单词的词向量)中生成三个向量,Query、Key、Value,也就是说对于每个单词,我们创造一个查询向量、一个键向量和一个值向量,这三个向量是通过词嵌入与三个权重矩阵后相乘创建的。

  • 计算当前词向量与输入句子所有词向量的分数,分数会决定将多少注意力放在这些词上。分数通过将当前词向量对应的Query向量与各个单词的Key向量点积得到,第一个分数是q1与k1点积,第二个分数为q1与k2点积,以此类推。
  • 将分数除以Query向量长度的平方跟(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定,这里也可以使用其它值,8只是默认值),然后通过softmax传递结果,softmax的作用是使所有单词的分数归一化,得到的分数都是正值且和为1。softmax分数决定了每个单词对编码当下位置的贡献。
  • 归一化的得分与各个单词的Value向量相乘,此做法的直觉是保持想要关注的单词的值不变,而且消除不相关的单词,即对各个单词的Value向量加权。
  • 对加权值向量求和(自注意力的另一种解释就是在编码某个单词时,就是将所有单词的表示(值向量)进行加权求和,而权重是通过该词的表示(键向量)与被编码词表示(查询向量)的点积并通过softmax得到),然后即得到自注意力层在该位置的输出。

这样自注意力的计算就完成了,得到的向量就可以传给前馈神经网络。 所以在编码器输出的结果里,表示各个词的向量,会根据上下文信息进行调整,同一个词根据上下文中有不同的抽象表示。

多头自注意力

多头自注意力机制是进一步的优化工作,编码器不只是包含一个自注意力模块,而是包含多个,每个头都有其独特的注意力权重,专注于文本中的不同特征或方面。例如,有些头专注于动词,有些头专注于修饰词,有些头专注于情感,还有些头专注于命名实体等。具体可以类比 CNN 中的多个卷积核。

这些自注意力头之间可以并行运算,这意味着它们的计算过程互不干扰。每个自注意力头的权重都是模型在之前的训练过程中,通过处理大量文本逐步学习和调整的。

利用这个机制,它给出了注意力层的多个“表示子空间”(representation subspaces)。接下来我们将看到,对于“多头”注意机制,我们有多个查询/键/值权重矩阵集(Transformer使用八个注意力头,因此我们对于每个编码器/解码器有八个矩阵集合)。这些集合中的每一个都是随机初始化的,在训练之后,每个集合都被用来将输入词嵌入(或来自较低编码器/解码器的向量)投影到不同的表示子空间中。在“多头”注意机制下,我们为每个头保持独立的查询/键/值权重矩阵,从而产生不同的查询/键/值矩阵。如果我们做与上述相同的自注意力计算,只需八次不同的权重矩阵运算,我们就会得到八个不同的Z矩阵。

前馈层不需要8个矩阵,它只需要一个矩阵(由每一个单词的表示向量组成)。所以我们需要一种方法把这八个矩阵压缩成一个矩阵。那该怎么做?其实可以直接把这些矩阵拼接在一起,然后用一个附加的权重矩阵W0与它们相乘。

下图是完整流程。

前馈神经网络

在Transformer模型中,归一化后的残差输出会被送入一个点对点前馈网络进行进一步处理。这个点对点前馈网络由若干个线性层组成,中间还包含ReLU激活函数。通过这种结构,前馈网络能够对注意力机制的输出进行更深层次的处理。点对点前馈网络会将输入的结果与经过前馈网络处理后的输出相加,从而形成残差连接。然后,这个结果会再次进行归一化处理。这样的设计使得模型不仅能够捕捉到输入数据中的复杂特征,还能确保信息在每一层之间有效传递。

假设使用 ReLU 作为激活函数。 定义 FFN 的两个线性层参数如下(简化为 2 维,实际中维度会更高):

  • 第一层权重 W1 和偏置 b1,维度从 2 映射到某个隐藏维度,比如 4(为了简化,我们直接使用 4)。
  • 第二层权重 W2 和偏置 b2,维度从 4 映射回 2。

假设参数如下:

第一层线性变换:

  • 计算 z1=xW1+b1,其中 x=[0.5,0.731059]。

激活函数:

  • 应用 ReLU 激活函数 a1=max(0,z1)。

第二层线性变换:

  • 计算 z2=a1W2+b2。

输出:

  • 输出 z2 就是前馈神经网络的最终输出,这个输出将会被送回模型的下一层或用于解码器的进一步处理。

解码器

解码器是大语言模型生成单词的关键部分。通过前面的编码器,我们得到了输入序列中各个token的抽象表示,这些表示可以传递给解码器。解码器在开始时还会接受一个特殊的起始值,这个值表示输出序列的开头。这种设计的原因在于,解码器不仅会将来自编码器的输入序列的抽象表示作为输入,还会将之前已经生成的文本作为输入,以保持输出的连贯性和上下文相关性。 它的组成部分和编码器类似,主要包括:

  • 多头自注意力(Multi-Head Attention):多头自注意力是一种扩展自注意力的方法,它可以有效地捕捉序列中的多个关注点。
  • 位置编码(Positional Encoding):由于Transformer模型中没有使用循环神经网络(RNN)或卷积神经网络(CNN),因此需要使用位置编码来捕捉序列中的位置信息。
  • 层ORMAL化(Layer Normalization):层ORMAL化是一种常用的正则化技术,它可以有效地减少模型的过拟合。

具体的生成过程仍然是要经过多个步骤,首先和编码器一样,文本要经过嵌入层和位置编码,然后被输入进多头自注意力层,但它和编码器里的自注意力层有点不一样。当编码器在处理各个词的时候,它会关注输入序列里所有其它词。但解码器中自注意力只会关注这个词和它前面的其它词,后面的词要被遮住,不去关注,这样做是为了确保解码器生成文本时,遵循正确的时间顺序,不能给他偷看到后面。在预测下一个词时,只使用前面的词作为上下文,这种类型的多头注意力,被叫做带掩码的多头自注意力。

在带掩码的多头自注意力机制之后,解码器还包含一个多头注意力层。这一层是前面编码器输出的输入序列的抽象表示发挥作用的地方。通过这个多头注意力层,解码器能够捕捉编码器的输出和解码器即将生成的输出之间的对应关系。这个注意力层会将原始输入序列的信息融合到输出序列的生成过程中,从而确保生成的输出序列能够准确反映输入序列的内容和结构。

解码器里的前馈神经网络作用和编码器里的类似,也是通过额外的计算来增强模型的表达能力,而且和编码器一样,解码器同样是多个堆叠到一起的,这可以增加模型的性能,有助于处理复杂的输入输出关系。

解码器的最后阶段,包含一个线性层和一个Softmax层,它们两加一块的共同作用是把解码器输出的表示转换为词汇表的概率分布。比如,我们如何把浮点数变成一个单词?这便是线性变换层要做的工作,它之后就是Softmax层。

线性变化和softmax

线性变换层是一个简单的全连接神经网络,它的作用是将解码器生成的向量投射到一个更大的向量空间,这个向量空间被称为对数几率(logits)。这个过程可以看作是将解码器的输出映射到一个更高维度的空间,以便进行后续的处理。

假设我们的模型从训练集中学习了一万个不同的英语单词,这些单词构成了模型的“输出词表”。因此,对数几率向量的长度为一万个单元,每个单元对应一个单词的分数。这些分数表示模型对每个单词作为当前时间步输出的信心。

接下来,Softmax层会将这些分数转换为概率。Softmax函数的作用是将所有分数归一化,使它们成为介于0和1之间的正数,且所有概率的总和为1。通过这种方式,Softmax层能够将对数几率向量转换为概率分布。

在这个概率分布中,概率最高的单元被选中,对应的单词便作为当前时间步的输出。例如,如果某个单词的概率最高,那么模型就会选择这个单词作为输出。这种机制确保了模型在每个时间步都能生成最可能的单词。

那解码器的整个流程会重复多次,新的token会持续生成,直到生成的是一个用来表示输出序列结束的特殊token。那现在我们就拥有了来自解码器的完整输出序列。

编解码器协同工作

编码器用来理解和表示输入序列,解码器用来生成输出序列。现在让我们看看编码器和解码器之间是如何协同工作的。

编码器首先处理输入的文本token,然后输出一组注意力向量 K 和 V。这些向量将由每个解码器在其“编码器-解码器注意力”层中使用,这有助于解码器关注输入序列中的特定token的位置信息,具体计算注意力值的方法跟编码器中是一样的,需要注意的是,这里的K、V矩阵来自于编码器的输出,而Q矩阵来自于解码器的输入。

重复回归以上的步骤,直到出现结束符号的标识,表示解码器已完成其输出。每个步骤的输出在下一个时间步骤中被反馈到底部解码器,并且解码器像编码器一样向上反馈其解码结果。就像我们对编码器输入所做的那样,我们将位置编码嵌入并添加到这些解码器输入中以指示每个单词的位置。

至此,已经分析完Transformer的编码器和解码器的全流程了。

常见问题

幻觉

现在我们知道了解码器的本质工作是预测下一个最可能的输出。

然而,模型并不具备判断输出是否符合客观事实的能力,因此我们经常会看到模型一本正经地胡说八道。这种现象在自然语言处理领域被称为“幻觉(Hallucination)”。 幻觉现象的产生主要是因为模型在生成文本时,依赖于训练数据中的模式和关联,而不是实际的事实验证。例如,当模型被要求生成关于某个主题的文本时,它会根据训练数据中的相关信息来预测下一个最可能的单词或短语,而不管这些信息是否真实或准确。 比如,33×5625的正确答案是185625,但模型认为185后面接808的概率最高

Transformer变种

实际上,在原始架构的基础上,后续出现了一些变种,主要有三个类别:仅编码器,仅解码器以及编码器-解码器

  • 仅编码器/自编码器模型(encoder-only/autoencoding model): 也叫自编码器模型,只保留了原始架构里的编码器,BERT就是这种模型的一个例子,此类模型适用于理解语言的任务,比如:
    • 掩码语言建模:让模型猜文本里被遮住的词是什么
    • 情感语言分析:让模型判断文本情感是积极还是消极等等
  • 仅编码器/自回归模型(decoder-only/autoregressive model): 也叫自回归模型,只保留了原始架构里的解码器,GPT系列都是这种模型的例子,这类模型非常擅长通过预测下一个词来实现文本生成。
  • 编码器-解码器/序列到序列模型(encoder-decoder/sequence-to-sequence model): 编码器-解码器模型,也叫序列到序列模型,同时保留了原始架构里的编码器和解码器,T5、BART都是这种模型的例子。此类模型适用于把一个序列转换成另一个序列的任务,比如翻译、总结等等

本文作者:sora

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!