语言模型赋予了计算机理解和生成人类语言的能力。它结合了统计学原理和深度神经网络技术,通过对大量的样本数据进行复杂的概率分布分析来学习语言结构的内在模式和相关性。具体地,语言模型可根据上下文中已出现的词序列,使用概率推断来预测接下来可能出现的词汇。接下来主要介绍一些基础的语言模型,如Transformer、自动编码器、自回归模型等。
2.1 Transformer
Transformer模型是深度学习,尤其是自然语言处理(NLP)领域的一次重大突破。
从概念上看,Transformer模型可以被视为一个黑盒子,以极其翻译任务为例,它能够接收某种语言的输入文本,并输出对应语言的翻译版本
从内部结构来看,Transformer由编码器(encoder)和解码器(decoder)量大部分构成,这两部分在原始的"Attention is all you need"论文中包含6个模块,但在实际应用中,这个数字可以根据具体任务进行调整。
以机器翻译为例,Transformer的工作流程大致分为以下几个步骤:
-
获取句子中每个单词的表示向量X,X可以通过词嵌入(embedding)得到。X中包含单词数据特征和位置信息。如果以简单的框来表示向量,则每个单词都可以被表示为一个高纬向量,最终的输入句子被表示为一个词向量矩阵。
-
将得到的词向量矩阵传入编码器部分,每个单词的词向量都会经过编码块,在经过6个编码块后可以得到编码矩阵C。单词向量矩阵表示为Xn*d,n代表输入语句中的单词个数,d代表向量的维度。
-
解码器部分也是由6个完全相同的解码块堆叠而成,解码时,解码块会接收编码器部分输出的编码矩阵C和上一个解码块的输出,即解码层会根据当前词i和i之前的词信息翻译下一个单词i+1。在实际的执行过程中,i+1位置后的单词需要被掩盖点以防止i+1知道后面的信息。
Transformer的核心结构由词嵌入、编码器、解码器、输出生成四个部分组成,如下图所示
2.1.1 词嵌入
Transformer中输入单词的词嵌入包含单词编码和位置编码。单词编码用于编码单词的语义,位置编码用于编码单词的位置
1. 单词编码
单词编码(word embedding)是一种以数字方式表示句子中单词的方法,该方法用来表示单词语义特征。这里介绍三种编码方式:神经网络编码、词向量编码和全局词向量表示
(1)神经网络编码
基于神经网络语言模型(Neural Network Language Model,NNLM)的编码方式最早由Bengio等人提出,用于解决统计语言模型中常见的维度灾难问题。该方法是训练一个神经网络,在训练中,每个参与的句子告诉模型由哪些单词在语义上相近,最终模型为每个单词生成一种分布式表示,这种表示能够捕捉并保留单词的语义和语法关系
Bengio的神经网络主要由三个部分组成:一个词嵌入层,用于生成词嵌入表示,且单词之间参数共享;一个或多个隐藏层,用于生成词嵌入的非线性关系;一个激活层,用于生成整个词汇表中每个单词的概率分布
该网络使用损失函数在反向传播过程中更新参数,并尝试找到单词之间相对较好的依赖关系,同时保留语义和语法属性。下面通过简单的代码来理解其训练过程
1)单词索引
对单词建立索引,句子中每个单词都会被分配一个数字
word_list = " ".join(raw_sentence).split()
word_list = list(set(word_list))
word2id = {w: i for i, w in enumerate(word_list)}
id2word = {i: w for i, w in enumerate(word_list)}
n_class = len(word2id)
2)构建模型
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.embeddings = nn.Embedding(n_class, m)
self.hidden1 = nn.Linear(n_step * m, n_hidden, bias=False)
self.ones = nn.Parameter(torch.ones(n_hidden))
self.hidden2 = nn.Linear(n_hidden, n_class, bias=False)
self.hidden2 = nn.Linear(n_step * m, n_class, bias=False) #final layer
self.bias = nn.Parameter(torch.ones(n_class))
def forward(self, x):
X = self.embedding(X)
X = X.view(-1, n_step * m)
tanh = torch.tanh(self.bias + self.hidden1(X))
output = self.bias + self.hidden3(X) + self.hidden2(tanh)
return output
在该过程中,首先初始化词嵌入层。词嵌入层相当于一个查找表,被索引表示的单词通过词嵌入层,然后再通过第一个隐藏层并与偏置量求和,求和结果传递给tanh函数。最后计算输出,代码如下:
output = self.b + self.hidden3(X) + self.hidden2(tanh)
3)损失函数
这里使用交叉熵损失函数,并将模型输出传递给softmax函数获得单词的概率分布
criterion = nn.CrossEntropyLoss()
4)进行训练
经过训练最终得到单词的编码结果
(2)词向量编码
词向量(Word2vector)模型由Mikolov等人在2013年提出,比Bengio的神经网络语言模型的复杂性更小。词向量模型可以在更大的数据集中训练,但缺点是如果数据较少,就无法像神经网络语言模型那样精确地表征数据。词向量模型包含两种模式:词袋(Bag-of-Words)模型和跳跃(Skip-gram)模型。
词袋模型又称为CBOW模型,它基于目标词前后的n个单词来预测目标单词。假设句子为:“她在踢毽子”,以"毽子"作为目标词,取n=2,那么[正,在,毽,子]等前后单词及目标单词踢将被一起输入给模型。CBOW模型通过计算log2V来降低计算词表中单词分布概率的复杂性,V代表词汇表大小。该模型速度更快,效率更高。
同样的,通过代码来理解CBOW模型,其训练过程如下:
1)定义一个窗口函数,该函数提取目标单词的左右各n个单词
def CBOW(raw_text, window_size=2):
data = []
for i in range(window_size, len(raw_text) - window_size):
context = [raw_text[i - window_size], raw_text[i - (window_size - 1)], raw_text[i + (window_size - 1)], raw_text[i + window_size]]
target = raw_text[i]
data.append((context, target))
return data
上述CBOW函数包含两个输入参数:数据和窗口大小。窗口大小定义了应该从单词左侧和右侧提取多少个单词。for循环首先定义了句子中迭代的开始索引和结束索引,即从句子中的第3个单词开始到倒数第3个单词结束。在循环内部将窗口提取到窗口大小为2,此时窗口提取到的单词为"正" “在” “毽"和"子”,目标单词是"踢",当i=2(窗口大小)时,代码如下:
context = [raw_text[2-2], raw_text[2 - (2-1)], raw_text[i + (2-1)], raw_text[i + 2]]
target = raw_text[2]
执行CBOW函数:
data = CBOW(raw_text)
print(data[0])
上述输出为:
Output:
(["正","在","毽","子"], "踢")
2)构造模型
CBOW模型只包含一个词嵌入层、一个经过ReLU层的隐藏层和一个输出层。代码如下:
class CBOW_Model(torch.nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOW_Model, self).__init__()
self.embeddings = nn.Embbeding(vocab_size, embedding_dim)
self.linear1 = nn.Linear(embedding_dim, 128)
self.activation_function1 = nn.ReLU()
self.linear2 = nn.Linear(128, vocab_size)
def forward(self, inputs):
embeds = sum(self.embeddings(inputs)).view(1, -1)
out = self.linear1(embeds)
out = self.activation_function1(out)
out = self.linear2(out)
return out
该模型非常简单,单词索引输入层嵌入层,之后经过隐藏层,隐藏层输出经过一个非线性层ReLU之后经过输出层得到最终结果。
3)损失函数
与神经网络语言模型一样,采用交叉熵损失函数。优化器选择随机梯度下降,代码如下:
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
4)训练模型
训练代码与神经网络语言模型袋代码一致,具体如下:
for epoch in range(50):
total_loss = 0
for context, target in data:
context_vector = make_context_vector(context, word_to_ix)
output = model(context_vector)
target = torch.tensor([word_to_ix[target]])
total_loss += loss_function(output, target)
optimizer.zero_grad()
total_loss.backward()
optimizer.step()
最终将语句单词转化为数字序列
对比词向量模型,跳跃模型是基于目标单词预测目标单词的上下邻近单词。假设目标单词n=2,同样以句子"她正在踢毽子"为例,单词"踢"将被输入模型用于预测目标单词(“正”,“在”,“毽”,“子”)。其结构如下图:
跳跃模型与词袋模型类似,不同点在于创建上下文和目标单词。
相比词袋模型,跳跃模型增加了计算的复杂度,因为它必须根据一定数量的相邻单词来预测邻近单词。在实际的语句中,距离较远的单词相关性往往比较差
(3)全局词向量表示
全局词向量表示(Global Vectors for Word Representation,GloVe)是一种基于全局信息来获得词向量的方法。该方法使用了语料库的全局统计特征,也使用了局部的上下文特征,GloVe通过引入共现矩阵(Co-occurrence Probabilities Matrix)来表征。
定义词到词的共现矩阵为X,Xij代表单词j出现在单词i的上下文的次数,Xi表示出现在单词i的上下文的词的总数。Pij = P(j|i) = Xij/Xi表示单词j出现在单词i的上下文中的概率。
定义词i、j与词k的共现概率为P = Pik / Pjk,词与词之间的关系可以通过共现概率体现,即如果词k与词i相近,与词j较远,则希望Pik / Pjk越大越好,如果词k与词j相近,与词i较远,则希望Pik / Pjk越小越好,如果词k与词i和词j都相近或都较远,则Pik / Pjk趋近于1
共现概率既很好地区分了相关词和不相关词,又反映了相关词的关联程度。GloVe模型就是基于该共现概率的信息构建的,其核心目标是为了每一个词生成一个词向量,这样词向量就能映射出词与词之间的共现概率,以此捕捉它们的语义关系。假设定义GloVe模型为F,则上述共现概率可以使用如下公式来表示:
F(wi, wj, wk) = Pik / Pjk
其中,w 代表词向量
综上,单词词嵌入编码方法的特点如下:
- 神经网络语言模型的性能优于早期的统计模型
- 神经网络语言模型解决了维度灾难问题,并通过其分布式表示保留了上下文语义和句法属性,但计算成本很高
- 词向量模型降低了计算复杂性,比神经网络语言模型效率更高,它可以在大量数据上进行训练,用高维向量表示
- 词向量模型有两种:词袋模型和跳跃模型。前者比后者运算更快
- GloVe纳入了全局信息但无法解决一词多义和陌生词问题
2. 位置编码
位置编码(Position Embedding)用来表示句子中单词的位置,且每个位置被赋予唯一的表示。位置编码要满足以下特点:
- 编码值可以表示单词在句子中的绝对位置,且是有界的
- 句子长度不一致时单词间的相对位置距离也要保持一致
- 可以表示从未见过的句子长度
Transformer使用正余弦函数来进行位置编码。其公式如下:
PE(pos, 2i) = sin(pos/10000^2i/d model)
PE(pos, 2i+1) = cos(pos/10000^2i/d model)
其中,pos表示单词在句子中的位置,d表示PE的维度(与单词编码一样),2i表示偶数的维度,2i+1表示奇数维度(即2i<=d, 2i+1 <=d)。使用这种公式计算PE有以下的好处:
- 能够适应比训练集中所有句子更长的句子。假设训练集中最长的句子有20个单词,此时有一个长度为21的句子,则使用公式计算的方法可以计算出第21位的编码
- 能够较容易地计算出相对位置。对于固定长度的间距k,PE(pos+k)可以用PE(pos)计算得到
接下来通过代码来理解位置编码
import numpy as np
import matplotlib.pyplot as plt
def getPositionEncoding(seq_len, d, n=10000):
P = np.zeros((seq_len, d))
for k in range(seq_len):
for i in np.arange(int(d/2)):
denominator = np.power(n, 2*i/d)
P[k, 2*i] = np.sin(k/denominator)
P[k, 2*i+1] = np.cos(k/denominator)
return P
P = getPositionEncoding(seq_len=4, d=4, n=100)
print(P)
[
[0. 1. 0. 1. ]
[0.84147098 0.54030231 0.09983342 0.99500417]
[0.90929743 -0.41614684 0.19866933 0.98006658]
[0.14112001 -0.9899925. 0.29552021 0.95533649]
]
看一下n=10000和d=512的不同位置正弦曲线
def plotSinusoid(k, d=512, n=10000):
x = np,arange(0, 100, 1)
denominator = np.power(n, 2*x/d)
y = np.sin(k/denominator)
plt.plot(x, y)
plt.title('k = ' + str(k))
fig = plt.figure(figsize=(15, 4))
for i in range(4):
plt.subplot(141 + i)
plotSinusoid(i*4)
每个位置k对应于不同位置的正弦曲线,它将位置编码为向量。
即正弦曲线的波长形成几何级数,并且变化范围为2pai到2pai n,该方案的优点如下:
- 正弦和余弦函数的值在[-1, 1]范围内,即编码值是有界的
- 由于每个位置的正弦曲线不同,因此可以采用独特的方式对每个位置进行编码,即每个位置的编码都是唯一的
- 基于正弦变化可以测量或量化不同位置之间的相似性,从而能够对单词的相对位置进行编码
进一步,通过Matplotlib来可视化位置矩阵
P = getPositionEncoding(seq_len=100, d=512, n=10000)
cat = plt.matshow(P)
plt.gcf().colorbar(cax)
综上,将整个句子所有单词的位置编码向量与单词编码向量相加得到输出矩阵,整个流程如下图:
2.1.2 编码器
编码器由多头注意力、加法和归一化、前馈层组成
1. 多头注意力
在介绍多头注意力之前,首先介绍一下自注意力
自注意力是机器学习使用的一种学习机制,属于仿生学的一种应用,即人类会把注意力放在重点关注的信息上。在自然语言处理任务中,自注意力用于捕获输入序列内的依赖性和关系,让模型通过关注自身来识别和权衡输入序列不同部分的重要性。在编码器中,自注意力的输入参数有三个:查询(query)、键(key)、值(value)
这三个参数在结构上很相似,都是被参数化的向量。每个单词的词嵌入向量都由这三个矩阵向量来表征,帮助计算机理解和处理句子中单词之间的关系。这三个矩阵向量的作用各不相同。
1)查询
该矩阵表示正在评估其上下文的目标单词。系统通过使用查询矩阵转换该目标单词的表示形式,生成一个查询向量。此查询向量用于衡量其与句子中其他单词的关联度
2)键
该矩阵用于生成句子中每个单词对应的键向量。通过对每个键向量和目标单词的查询向量进行比较,计算得出目标单词与句子中其他词之间的相关性。查询向量和关键向量之间的相似度分数越高,表示相应单词之间的关系越紧密
3)值
该矩阵用于生成句子中所有单词的值向量,这些向量保存每个单词的上下文信息。使用查询向量和键向量计算相似度分数后,系统计算值向量的加权和。每个值向量的权重由相似度分数确定,确保最终的上下文表示更多地受到相关单词的影响
如下图所示,语句中单词"一"的查询矩阵向量q3与所有词的键矩阵向量k进行计算,得到注意力得分y31、y32、y33、y34。之后分别与自身的值矩阵向量做乘积,得到每个词抽取信息完毕的向量,最后所有向量求和得到z3,即为单词"一"经过注意力后的结果
Transformer中的自注意力使用的是多头注意力,即通过组合多个类似的注意力计算给予了Transformer更大的辨别能力。多头注意力结构如下:
在多头注意力中,查询、键和值分别通过单独的线性层,每个层都有自己的权重,产生三个结果,分别称为Q、K、V。然后在缩放点积注意力中基于注意力公式进行组合运算。
在上述过程中,Q、K、V携带了序列中每个单词的编码表示,之后注意力计算将每个单词与序列中的其他单词结合起来,以便注意力分数对序列中每个单词的分数进行编码。在该过程中提到了掩码。由于输入的序列可能具有不同的长度,因此需要使用填充标记对句子进行扩展对齐长度,以便可以将固定的向量输入Transformer中,这里的掩码主要是为了填充部分的注意力输出为0,确保填充标记不会对注意力分数产生影响。
2. 加法和归一化
加法过程是一种残差机制,主要为了解决深层神经网络训练过程的不稳定性,即深度神经网络随着层数的增加,损失逐渐减小然后趋于稳定,继续增加层数损失反而增大的现象。简单来说就是用来防止梯度消失。
归一化用来归一化参数,加快训练速度,提高训练的稳定性。
3. 前馈层
前馈层作为注意力层后面的子层,由两个线性层或致密层构成。第一层的大小为(d_model, d_ffn),第二层的大小为(d_ffn,d_model)。
通过以下代码来理解这个过程
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model: int, d_ffn: int, dropout: float = 0.1):
"""
Args:
d_model: dimension of embeddings
d_ffn: dimension of feed-forward network
dropout: probablity of dropout occurring
"""
super().__init__()
self.w_1 = nn.Linear(d_model, d_ffn)
self.w_2 = nn.Linear(d_ffn, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
"""
Args:
x: output from attention (batch_size, seq_length, d_model)
Returns:
expanded-and-contracted representation (batch_size, seq_length, d_model)
"""
return self.w_2(self.dropout(self.w_1(x).relu()))
2.1.3 解码器
解码器也由多头注意力、加法和归一化、前馈层组成,每个解码器包含两层多头注意力,这两个多头注意力与编码器中的多头注意力作用不同。
1. 掩码多头注意力
掩码多头注意力与编码器不一致的地方在于掩盖的步骤,此处的掩码过程主要为了防止解码器在预测下一个单词时"偷看"目标语句的其余部分
2. 多头注意力
解码器中的多头注意力与编码器中的主要区别在于,它的K、V矩阵不是基于上一个解码器的输出计算而来的,而是来自编码器的输入,但Q矩阵还是根据解码器的输出计算得到的。计算过程与编码器中的一样,掩码步骤也与编码器的一致
2.1.4 解码头
解码器将输出传递给解码头,解码头将接收到的解码器输出向量映射为单词分数。词库中的每个单词在句子中的每个位置都会有一个分数。假设最终输出的句子有4个词,词库中有1000个词,那么在这4个词的每个位置都会生成1000个分数,这些分数代表词库中每个词出现在句子中每个位置的可能性。之后这些分数将送入softmax层,softmax将这些分数转换为概率(加起来为1.0)。在每个位置上,找到概率最高的单词的索引,然后将该索引映射到词汇表中相应的单词。最后,这些单词形成Transformer的输出序列。
2.2 自动编码器
自动编码器(Autoencoder)是一类神经网络,通过无监督学习重建其输入数据,同时在这个过程中实现数据的有效压缩。
自动编码器的结构主要包括两个部分:编码器网络和解码器网络。编码器网络负责将高维的输入数据转换成更低维的表示,而解码器网络则从这些低维表示中恢复出原始数据。这种结构不仅能够捕捉数据中的关键特征,而且有助于在解码过程中有效地重建数据。
自动编码器的核心是学习输入数据的有效表示。这一过程涉及编码器将数据编码为低维表示,解码器再基于这些表示重建原始数据。网络通过学习一个接近恒等的映射来实现这一点,其参数优化过程可以通过计算输入与重建输出之间的均方差来量化。
2.2.1 ELMo
前面介绍了一些常用的词嵌入方法,如NNLM、Word2vector、GloVe等,这几种方法本质上都是先训练好一个语言模型,然后输入单词取模型的隐藏层表示来作为单词的词向量表示。这些方法都是静态的表示方法,即一旦模型确定则单词的词向量表示也确定。虽然这些语言模型的词表征方法在下游任务中取得了优异的表现。但大多情况下单词的词义是随语境变化的,因此这些方法得到的词向量有一定的缺陷,尤其无法表达多义词。
与静态词嵌入方法不同ELMo(来自语言模型的嵌入)采用动态方法生成词嵌入,它基于训练好的语言模型,并能根据上下文实时调整词嵌入,有效地缓解了词义歧义的问题。
ELMo模型来自2018年的论文,在论文中提出了深度双向语言模型来表征词向量。该模型的原理是,首先训练好一个静态的语言模型来表征词向量,该模型存在上述提到的缺陷,即无法理解歧义,当执行实际任务时,可实时根据上下文单词的语义去调整之前表征好的词向量,这些经过调整后的词向量更能表达具体语境中的含义,从而解决多义词问题。该方法是一种结合上下文语境的动态调整思路。ELMo模型是采用双向LSTM构建,在一个大型文本语料库中训练而成的。
ELMo的训练过程包含两个阶段,分别是预训练和下游任务。预训练阶段与之前的语言模型训练类似,预计训练的目标是根据目标词的上下文预测目标词。
左侧的双向LSTM代表正向编码器,负责从前往后提取信息,输入为句子序列中从左到右(除目标词外)的上文,右侧的双向LSTM代表逆向编码器,负责从后往前提取信息,输入为句子序列中从右到左的逆序下文。
ELMo的预训练过程完成后,单词经过ELMo模型可以得到三层编码信息,Ei代表最底层的单词特征信息编码,从下往上第一层LSTM包含了句法特征的单词位置编码信息,第二层LSTM包含了语义特征的单词位置编码信息。
在预训练好的网络结构完成后,如何将其应用于特性的下游任务,比如问答问题?假设有一个问句X,首先,将这个句子X输入已经预训练好的ELMo网络中。通过这个过程,句子X中的每个单词在ELMo网络中都会生成对应的三层编码信息。接下来,分别为这三层编码信息分配一个权重a,这些权重是可以通过学习得到的。然后,根据各自的权重进行加权求和,将这三层编码融合成一个词向量,这样句子中的每个单词都有一个对应的词向量。在执行问答任务时,问句X中的每个词对应的词向量作为任务的输入,这样可以利用预训练ELM。网络中的语境信息,从而增强下游任务的性能。
2.2.2 BERT
BERT(Bidirectional Encoder Representations from Transformers,来自Transformer的双向编码器表征法),旨在通过联合左侧和右侧的上下文,从未标记文本中预训练出一个深度双向表示模型。BERT的结构中只包含Transformer的编码部分,与按顺序(从左到右或从右到左)读取文本输入的方向模型不同,BERT的编码器会读取整个单词序列,这被认为是双向读取,准确来说是非定向的。BERT的这种特性允许模型根据单词的周围环境(包括左侧和右侧)来学习单词的上下文,它的训练结果表明双向训练的语言模型比单向的语言模型更深入地了解语言上下文和流程。
1. 模型结构
在前面提到Transformer的编码器结构由多头注意力、加法和归一化、前馈层堆叠而成。BERT中的每一个Transformer层都是由这样的编码器单元构成的。
2. 预训练
BERT的预训练方式包括两种:MLM(Masked Language Model掩码语言模型)和NSP(Next Sentence Prediction,下句预测)
(1)MLM
MLM任务的目标是使模型能够根据词的上下文推断被掩盖的词。训练时,输入序列中约15%的词会被选中,并作为标签去预测,这15%中:
- 80%的词替换为[mask],这主要是为了让模型在没有看到原始词的条件下通过上下文来正确预测该词
- 10%的词用随机词来替换,这迫使模型在每个词上学习全局语境特征准确预测该位置的原始词,从而使BERT获得更好的语境词向量,有助于解决一词多义问题
- 10%的词保持不变,即BERT模型由1.5%比例的词是已知的,这是为了防止模型总是预测被掩盖的词
基于以上的处理方式,BERT模型会尝试根据序列中掩盖单词周围的上下文来预测掩盖单词的原始值
(1)NSP
在进行NSP训练时,模型接受一对句子作为输入,其目标是判断这两个句子是否在原文中是连续的。为了帮助模型在训练中区分两个句子,输入句子对按以下方式进行处理:
- 50%的句子对(句子A和句子B)是相邻的,即在原始语料中,句子B是跟在句子A后面的
- 剩余50%的句子对,句子B不是句子A的后续句子,而是从语料库中随机选取的一个不相关的句子
- 对句子对添加特殊的标记,即句子A开头插入[CLS]标记,句子B末尾插入[SEP]标记,句子A和句子B中间插入[SEP]标记
- [CLS]标记用来区分句子A和句子B是否连续
通过以上处理后,输入句子对用于训练一个理解句子关系的BERT模型
3. 微调
除了输出层,BERT的预训练和微调都使用了相同的架构,且使用相同的预训练参数来初始化不同下游任务的模型。在微调过程中,所有的参数都会被微调。[CLS]是在每个输入示例前面添加的特殊符号,[SEP]是特殊的分隔符标记,如分割问题或答案。
BERT可以通过微调被灵活应用在多种不同的语言处理任务上,且只需要在模型结构上做很小的改动:
- 对于文本分类任务(如情感分析),BERT的微调非常直接。可以在[CLS]标记的输出向量上(该向量旨在捕捉输入序列的整体意义)添加一个简单的分类层。这个分类层将针对特定的分类任务进行训练,比如区分文本是正面情感还是负面情感
- 对于问答发任务,BERT需预测文本序列中答案的位置。这可以通过预训练模型的基础上,在答案的开始和结束为止各加入一个预测层来实现。这些层会学习并识别出每个单词是否为答案的开始或结束,且在训练过程中优化这些预测
- 对于命名实体识别(NER)任务,BERT模型需要识别出文本中的命名实体,如人名、组织名、地点或时间等。为了完成这个任务,BERT对每个词的向量表示都会输出给一个特别设计的分类层,该层能够预测每个词所对应的实体类别(或无实体类别)
总之,通过在BERT的输出层上添加定制任务的特定层,并在有监督的任务数据上进行微调,BERT能够灵活地适应一系列不同的NLP任务且表现出色。这种方法的优势在于,微调过程利用了BERT深层次预训练模型中丰富语言知识,提高了特定模型的性能。
基于BERT还有很多衍生模型,如WWM、HEZHA、MacBERT和ERNIE等,分别有不同的特点
1)WWM
WWM是谷歌在2019年发布的BERT升级版本,该方法引入了全词掩码。简单来说,原有基于WordPiece的分词方式会把一个完整的词切分为若干个子词,在生成预训练样本时,这些子词会被随机掩盖,在WWM中,如果一个词的部分WordPoece子词被掩盖,则该词会被完整掩盖掉。该方法能更好地处理语义信息,尤其是对那些由多个词符组成的词
2)HEZHA
HEZHA是一个专门为中文优化的BERT变种,由华为诺亚方舟实验室开发,对BERT模型的预训练和微调过程进行了改进,即增加相对位置编码函数、全词掩盖、混合精度训练和改进优化器,使之更适合中文的语言特点,提升了中文文本的处理效果
3)MacBERT
MacBERT来自哈工大SCIR实验室,该论文提出在预训练阶段相似的单词来掩盖,缩小了预训练和微调阶段的差异
4)ERNIE
ERNIE由百度开发,整合了丰富的知识图谱信息。ERNIE在预训练阶段使用了实体级的掩盖策略,能够理解实体之间的关系。通过对训练数据中的词法结构、语法结构、语义信息进行统一建模,极大地增强了通用语义表示能力,ERNIE能更好地理解和处理复杂语言结构和知识信息。
以上这些衍生模型都是在原始BERT的基础上进行了特定的改进,以适应不同的语言特性、提高模型的性能或解决特定的任务。这些模型展示了预训练语言模型在自然语言处理领域的快速发展和多样化应用
2.3 自回归模型
自回归模型(AutoRegressive Model, AR模型)是基于过去数据来预测当前数据的线性模型。例如,了解某产品今天的价格,可以借此预测其明天的价格。在数学上,定义自回国模型为AR§,其中p代表模型的阶数,即用于预测当前值的过去观测值的数量。如果以X表示时间序列,那么AR(1)代表一个简单的自回归模型
2.3.1 GPT
GPT(Generative Pre-Trained,生成式预训练)模型是使用Transformer架构的神经网络模型。2018年6月,OpenAI发布论文首次提出GPT模型框架,该框架通过预训练和微调,使用与任务无关的单一模型来实现强大的自然语言理解(NLU)。接下来将从生成式预训练、对Transformer结构的改进及GPT的演进历史三个角度对GPT模型框架进行阐释。
1. 生成式预训练
在GPT开发之前,大多数SoTA NLP模型都是使用监督学习专门针对特定任务进行训练的,例如问答、文本涵蕴、语义相似性评估、文档分类等。然而,监督模型有两个主要问题:
1)标记数据的稀缺
监督模型通常需要大量带注释的数据来学习某项任务,而这些数据通常不容易获得,这使得经过区分训练的模型难以充分执行
2)概括能力差
监督模型无法概括所有训练的任务之外的任务
基于以上问题,GPT提出了在多种未标记文本的语料库上对语言模型进行生成式预训练,然后对每个特定下游任务进行区分性微调,取得了巨大成功
在机器学习领域,有判别式模型(Discriminative Model)和生成式模型(Generative Model)两种
因此,生成式模型更适合大模型数据学习,判别式模型更适合小样本标注数据。GPT中使用了生成式的预训练和判别式的微调。以GPT-1为例,GPT-1语言模型是使用BooksGorpus数据集进行训练的。BooksGorpus包含约7000本未出版的书籍,这些书籍有助于在未见过的数据上训练语言模型。该语料库还包含长段的连续文本,这有助于模型处理远程依赖关系。具体的训练过程如下:
- 判别式模型
- 学习类判别边界
- 最大化条件概率:P(Y|X)给定输入X,最大化标签Y概率
- 生成式模型
- 学习类条件分布
- 最大化联合概率P(X|Y)
1)无监督语言模型建模(预训练)
在巨大的文本库上学习高容量的语言模型,即给定无监督的token集合u = {u1, …, un},使用标准语言模型目标(Language Modeling Objective)来最大化似然函数
实验中,使用多层Transformer解码器作为语言模型,这与原始的Transformer有些不同,属于Transformer的变体。该模型对输入上下文token应用多头注意力操作,然后通过位置前馈层在token上输出分布
2)有监督的微调
经过无监督训练得到训练模型后,进一步训练参数适应有监督的目标任务。假设一个带标签的数据集C,每个样本由一系列的token x1, …, xn组成,且对应标签y。通过预训练模型将输入传递给transformer模块的激活层,之后输入给线性层
同时,在微调过程中将语言模型作为辅助目标,有助于提高监督模型的泛化能力和快速收敛
本质上,监督微调是通过在Transformer模型中添加线性层和Softmax层来获取下游任务的任务标签来实现的
3)特定任务的输入转换
对于一些文本分类任务,可以直接按以上所述对预训练模型进行微调。但对于一些其他任务,如文本蕴涵或问答,它们的特点是结构化的输入,如有序句子对或文档、问题和答案的三元组等。由于预训练模型是在连续的文本序列上训练的,因此对于此类任务需要对输入做一些修改以适应预训练模型。整体的逻辑是需要将结构化输入转换为一个有序的序列,以使预训练模型可以进行处理。对于不同的任务,处理方法也不同
4)文本蕴涵(内涵逻辑推理)
对于文本继承,将前提P和假设H的token序列用分隔符$连接
5)相似性任务
对于相似性任务,两个被比较的句子没有固定的顺序。为了表达这一点,将输入序列进行修改以包含两个句子顺序的所有可能性,句子之间通过分隔符连接,分别处理每个序列以生成两个序列的表示,并在送入线性输出层之前按元素宽度相加。
6)问答和常识推理
对于有一个上下文文档z,一个问题q和一组可能的答案{ak} 的样本,首先将文档和问题连接组成上下文,之后通过分隔符($)与每个可能的答案连接组成序列。每个序列都通过模型独立处理,结果输入线性层之后通过Softmax层输出所有答案的可能性分布。
2. 对Transformer结构的改进
GPT的模型设计遵循了原始Transformer的工作原理,不同之处在于GPT只保留了解码层,从而使得模型更为简单。整体上,GPT模型由12个解码器堆叠成,每个解码器包含掩码多头注意力层、正则层、前馈层等。
3. GPT演进历史
最早的语言模型是基于规则的系统,需要大量的人类输入才能发挥作用。然而,随着机器学习和深度学习技术的出现,这些模型的复杂性和准确性急剧增加。整体上,GPT模型的演进历史可以概括如下:
1)GPT-1
2018年6月,OpenAI推出了第一个GPT模型,即GPT-1。包含1.17亿个参数,通过BooksCorpus数据集(约7000本书)进行训练,并主张通过大规模、无监督预训练+有监督微调进行模型构建。GPT-1能够几乎与人类写作无异的连贯句子和段落,具有开创性意义。
2)GPT-2
2019年2月,OpenAI发布GPT-2模型,它是GPT-1的升级版,具有15亿个参数,在更大规模的数据集上训练(约40GB数据集),同时训练过程去掉微调,强调零样本和多任务学习。该模型能够生成更长、更连贯的文本。然而,由于其强大的能力,开发者未完全公开其版本,因担心它可能被用于生成假新闻和误导性内容。
3)GPT-3
2020年5月,OpenAI发布GPT-3模型。它的创建者使用大量文本数据(约570GB数据集,包括书籍、文章和网页)对其进行训练,它拥有1750亿个参数,训练过程同时采用少样本学习模式。GPT-3能够生成与人类编写的内容几乎无法区分的文本。GPT-3的性能非常出色,已被用于各种应用,例如自然语言处理、文本分类和聊天机器人
4)Instruct GPT
2022年2月,OpenAI发布Instruction GPT模型。该模型主要是在GPT3的基础上增加了监督微调(Supervised Fine-tuning)环节,并且基于此,进一步加入了奖励模型,通过RM训练模型来对学习模型进行增强学习优化。
5)ChatGPT
2022年11月30日,OpenAI发布ChatGPT模型,可以理解为一个多轮迭代训练后的InstructionGPT,并在此基础上增加了对话聊天功能。ChatGPT的发布是自然语言处理领域的重大发展。ChatGPT的发布是自然语言处理领域的重大发展。ChatGPT已经过大型对话数据语料库的训练,这使其能够理解并响应自然语言查询。
6)GPT-4
2023年3月14日,OpenAI发布GPT-4,GPT-4是一个先进的多模态大语言模型,不仅能够处理文本输入,还能够理解和分析图像内容。GPT-4的训练资料包含了广泛的公共数据集以及第三方提供商授权使用的数据集。该模型经过了强化学习过程,即依赖于人类审查员和AI系统生成反馈的优化。强化学习过程提升了模型的性能和适应性,确保GPT-4的输出既符合人类的期望,又严格遵循既定的政策和指导原则。
从GPT-1模型到GPT-4模型,随着深度学习技术的发展,GPT语言模型取得了巨大的进步,随着GPT语言模型的不断发展,在塑造人工智能技术的未来及释放对语言理解和生成的新见解方面具有巨大的潜力。
2.3.2 LLaMA
LLaMA(Large Language Model Meta AI, Meta AI大型语言模型)是Meta AI的FAIR团队于2002年12月至2023年2月开发的,它的诞生是自然语言处理研究领域的一个重要里程碑。
1. LLaMA模型简介
LLaMA有7B、13B、33B和65B四种不同参数规模的模型,不同的模型大小可以适应不同的研究需求和算力资源。从这些模型的评测表现来看,LLaMA-13B仅比1/10规模的参数在多数的Benchmarks上性能优于GPT-3(175B),而LLaMA-65B可比肩业内最好的模型CHinchilla-70B和PaLM-540B
2. 预训练数据
LLaMA的预训练数据来自多种不同的数据源,涵盖了多个领域
3. 模型结构
LLaMA的整体架构仍然是Transformer的解码器架构,但在Transformer的基础上进一步做了3方面改进:
1)预归一化(Pre-normalization)
为了提高训练的稳定性,对每一个解码器的子层输入进行归一化而不是输出进行归一化,归一化方式采用均方根标准化(Root Mean Square layer Normalization,RMSNorm)
2)SwiGLM激活函数
为了提升性能,在前馈层用SwiGLU激活函数替换ReLU函数
3)旋转嵌入(Rotary Embeddings)
去掉了绝对位置嵌入,并在每个网络层中添加旋转位置嵌入(Rotary Positional Embeddings,RoPE)
2.4 本章小节
本章介绍了语言模型的基础原理,包含了Transformer、自动编码器、自回归模型等的原理。在Transformer的原理介绍中,以语言翻译为例介绍了Transformer的工作流程,以及词嵌入、编码器、解码器等核心部分。而在词嵌入中,详细介绍了单词编码(神经网络编码和词向量编码)和位置编码,并说明底层构造原因。在编码器和解码器中,重点讲解了注意力机器和前馈层。在自动编码器的原理一节中,介绍了ELMo和BERT的原理和内部机制。最后,在自回归模型中,介绍了GPT和LLaMA,当下最热门的语言模型,介绍了数学原理和对原始transformer结构的改进。