1 概览
为简单起见,我们可将模型看做一个黑箱,输入提示输出符合需求的结果:
在阐释模型架构时,需要涉及 数据如何输入模型的问题 以及 流行的模型架构本身。在本文中我们主要讨论 Transformer 架构。再开始之前,为了下面讨论的方便,我们将训练集看做语言或词元集的Kleene闭包上的一个分布:
其中是语言中的一个词元序列。
2 分词 (Tokenization)
在解决句子如何输入模型的过程中,必须要经过 将句子拆分成词元序列 这个问题,例如“the mouse ate the cheese”这句话经过分词后变为这样的词元序列。
一般而言,NLP 范畴中的分词通常指一个文本序列中的最小单元。可以是单词、标点符号、数字或其他类型的语言元素。在英文等语言中,每个词中间往往有空格和/或标点符号的分隔,而以中日韩语言的句子往往难以分词。请看下面的例子:
工作和尚未工作的 * 工作/和/尚未/工作/的 * 工作/和尚/未/工作/的
为什么我们需要做分词呢?在英文等语言中这样做似乎是比较自然的:若干个字母组合成的单词才会有意义。而中文等因为没有空格,引起的各种歧义现象大幅提升,进行分词有助于模型更加高效地进行处理与计算。我们希望模型每次得到的是一个有意义的最小基础单元,我相信这和我们人类在听见/看见句子时所做的是相同的。事实上,神经网络只能对张量进行运算,因此输入的每一个词元都要被转化成向量。回顾上面的例子,我们稍不严谨地考虑:和这两个词元序列显然不同,因此得到的向量也不同。如果不进行分词,模型需要自己学习这样的不同,训练就会变得困难。
2.1 基于空格的分词
最简单的基于空格的分词方法是直接按照空格或空格进行分词。以英语为例,以空格分词显然会忽略讨论以下几种情况:
连字符连接的词: father-in-law
、green-eyed
、state-of-the-art含有句号的词: Ph.D.
、i.e.
、e.g.缩略词: don't
那么如何分词可以相对比较科学高效?下面给出几个参考的原则:
不能产生太多词元,否则序列会变得难以建模 不能产生太少的词元,否则单词之间无法共享参数 每个词元应该是在语言学上或统计学上有意义的单位
2.2 字节对编码 (Byte Pair Encoding)
BPE 算法常用于数据压缩领域,需要通过模型训练数据进行学习以获取待分词文本的频率特征,其伪代码如下:
假设语料是下面这句话:
I = [["the car", "the cat", "the rat"]]
将每一项切分成字符,得到
[['t', 'h', 'e', '$\space$', 'c', 'a', 'r'],
['t', 'h', 'e', '$\space$', 'c', 'a', 't'],
['t', 'h', 'e', '$\space$', 'r', 'a', 't']]
然后对每一个子列表中的元素求并集,得到
V = ['t', 'h', 'e', 'c', 'a', 'r', 't', '$\space$']
接着执行循环,将出现频率最高的两个元素融合形成新的元素替换在原切分后的列表中的出现,并将其添加到词汇表中。
2.3 Unicode 引起的问题
Unicode 为了兼容多语言字符创造了大量的码位,而在单个训练集中很可能只用到其中极少部分的码位。因此需要减少数据的稀疏性,例如对字节而不是 Unicode 运行 BPE 算法。但这种方法在不同的 Unicode 编码规则下有一定的差异,需要注意。
2.4 Unigram 模型
仅用频率来分词似乎缺少合理性,Unigram 模型考虑定义一个目标函数来确认一个较好的分词。这样的方法可以对语料的场景进行适应。Unigram 模型被 SentencePiece 工具所支持,并与 BPE 一起使用。
给定一个词汇表,元素元组的 似然值 定义为每个元素的出现频率的乘积(这里因为可能没有优化完成,故称元素,而不是词元):
自然地,元素出现频率有归一化特性:
Unigram 模型的优化问题即为最大似然,即
其中是给定输入句子的一个分词方案。在已知词汇表的情况下,元素出现频率可被 EM 算法估计,后者通过最大化边缘似然函数来进行计算:
其中是语料库的句子数量。因为蕴含在中,因此通过最大化可以做到对的隐式估计。
然而在实际情况下,词汇表是一个被优化的对象,无法事先得到最终的词汇表。因此我们考虑使用迭代的方法求解最优的词汇表:
从训练语料库中启发式地产生一个尽可能大的种子词汇表 重复下面的步骤直到词汇表达到希望的规模: 固定词汇表,使用 EM 算法优化 对于每个元素,计算损失;计算方法为将这个元素从当前词汇表中移除后边缘似然函数会下降多少 根据计算得到的损失将词汇表中的所有元素排序,然后只留下前,注意需要留下只含有单个字母的元素以防止词元不在词汇表中的现象
3 模型架构
3.1 上下文向量表征
在介绍模型架构之前还需要介绍上下文向量表征。上下文向量表征不同于上下文无关向量表征,它是一个函数,其中是词元序列的长度,是预先设置的向量维数。需要注意的是,上下文无关向量表征在接受同一个词元时的输出总是相同(我们可以将其理解为一个纯函数),而上下文向量表征接受的相同词元在不同上下文作为输入时得到的输出向量不同。
3.2 语言模型的分类
当前对语言模型的分类主要为以下三类:
编码器(Encoder-Only) 解码器(Decoder-Only) 编解码器(Encoder-Decoder)
下面就这三类模型进行展开讨论
3.2.1 编码器
使用该架构的著名模型有 BERT、RoBERTa 等。这样的架构生成上下文向量表征,但不能直接生成文本。上下文表征常常用于分类任务,形势比较简单,如情感分析(给定输入句子,判断句子是积极还是消极)。该架构的优势是可以对文本的上下文信息有更好地理解,缺点是不能自然地生成完整文本,需要更多的训练目标(如掩码语言建模,即随机遮盖文本中的一些词,然后使用模型推断缺失的词)
3.2.2 解码器
使用该架构的著名模型是 GPT 系列,是常见的 自回归 语言模型(对于给定的序列,模型会尝试预测下一个词元,在生成过程中,会利用到模型本身先前生成的词元作为模型的输入)。相比于编码器架构,解码器架构能够自然地生成完整的词元序列,有简单的训练目标(即极大似然);其缺点是只能依赖左侧上下文。
3.2.3 编解码器
使用该架构的是最初的 Transformer 模型、BART、T5 等。该架构结合了上述两种架构的优点,使用上下文向量表征处理输入词元序列,并产生输出词元序列。这样的架构形式上可以写为:
3.3 语言模型理论
3.3.1 基础架构
首先需要介绍将词元序列转换为向量序列的函数。EmbedToken()
函数将词元序列中的每个词元转化为每个词元对应的向量(上下文无关):
现在定义一类抽象的序列模型函数SequenceModel
,接受上下文无关嵌入(Embedding),返回上下文相关嵌入:
其中 Bengio 等人提出的前馈网络FeedForwardSequenceModel
就是一个典型例子。
3.3.2 循环神经网络(RNN)
循环神经网络是一类与一般神经网络不同的结构。它们维护网络单元的“内部状态”,并在不断接受新的输入时改变自己的状态。其典型例子是简单 RNN、长短期记忆模型(LSTM)和门控单元(GRU)。
RNN 接受一个隐藏状态和单个输入,输出向量输出并更新状态:
为了解决 RNN 只能捕获左侧上下文的问题,双向 RNN 在输入后使用正向序列和反转后的序列分别输出,然后将输出按照向量逐个连接成两倍于 RNN 输入维数的向量(为了简便起见,这里省去了隐藏状态):
由于链式求导法则,RNN 随着输入序列长度的增加会产生梯度消失或爆炸的问题。于是 LSTM 引进了“记忆”和“遗忘”的概念,以消解 RNN 的这一问题。
3.3.3 Transformer
3.3.3.1 注意力机制
注意力机制是 Transformer 的核心,它相当于在数据库中进行查找:给定一个查询(Query)和序列,注意力机制寻找与对应的。在实际操作中,我们通过线性变换将每一个分别变换成“键”和“值”:
然后对查询做另一个线性变换,最后将变换后的与进行比较(做内积):
得到每个的内积分数,最后通过`softmax`函数进行指数归一化得到在词元序列上的“分布”,并以此对输出进行加权:进一步地,我们可以考虑使用多套QKV矩阵组成多头注意力。
如果给定的查询本身就是序列中的元素,我们称这时的模型为自注意力。自注意力使得序列中的每个词元可以自由通信,在此之后的前馈层(Feed Forward)提供进一步的连接。Transformer 的主要模块就是自注意力机制和前馈层的复合:
3.3.3.2 一些其他技巧
残差连接(Residual Connection)在保持子模块输入输出形状不变的情况下进行跳跃连接:
这样可以减轻梯度消失带来的问题
层归一化(Layer Normalization)层归一化保证变换后的张量的元素不会太大,其中带有可训练参数和
位置嵌入(Position Embedding)读者可以很快意识到,一个词元序列中的词元顺序是影响整个词元序列意义的重要因素,而 Transformer 与 RNN 架构不同点在于它消解了词元序列时间维度上的先后顺序。这时就需要位置嵌入来保证模型的输入蕴含了序列的某种顺序。常用的位置嵌入方法是在词向量中加入三角函数的位置嵌入:
其中表示词元在序列中的位置,表示词向量中每一维度所处的位置。
3.4 GPT-3
GPT-3 架构为将 Transformer 模块堆叠次得到的模型:
参数分配如下:
隐藏状态: 前馈层维度: 注意力头数目: 上下文长度:
不同版本的 Transformer 之间存在差异,主要可归结为以下几个方面:
层归一化的位置 是否应用 Dropout 是否应用稀疏 Transformer(Sparse Transformer) 使用不同的掩码(mask)操作




