译者 | 朱先忠
审校 | 重楼
简介
GPT等语言模型最近变得非常流行,并被应用于各种文本生成任务,例如在ChatGPT或其他会话人工智能系统中。通常,这些语言模型规模巨大,经常使用超过数百亿个参数,并且需要大量的计算资源和资金来运行。
在英语模型的背景下,这些庞大的模型被过度参数化了,因为它们使用模型的参数来记忆和学习我们这个世界的各个方面,而不仅仅是为英语建模。如果我们要开发一个应用程序,要求模型只理解语言及其结构,那么我们可能会使用一个小得多的模型。
注意:您可以在本文提供的Jupyter笔记本https://github.com/dhruvbird/ml-notebooks/blob/main/next_word_probability/inference-next-word-probability.ipynb上找到在训练好的模型上运行推理的完整源代码。
问题描述
假设我们正在构建一个滑动键盘系统,该系统试图预测你下一次在手机上键入的单词。基于滑动模式跟踪的模式,用户想要的单词存在很多可能性。然而,这些可能的单词中有许多并不是英语中的实际单词,可以被删除。即使在这个最初的修剪和消除步骤之后,仍有许多候选者,我们需要为用户选择其中之一作为建议。
为了进一步修剪这个候选词列表,我们可以使用基于深度学习的语言模型,该模型能够查看所提供的上下文,并告诉我们哪一个候选者最有可能完成句子。
例如,如果用户键入句子“I’ve scheduled this(我已经安排好了)”,然后按如下图所示滑动一个模式:
然后,用户可能想要的一些英语单词是:
- messing(搅乱)
- meeting(会议)
然而,如果我们仔细想想,很可能用户的意思是“开会”,而不是“捣乱”,因为句子前半部分有“scheduled(预定)”一词。
考虑到目前为止我们所知道的一切,我们可以选择什么方案以便通过编程进行调整呢?让我们在下面的部分发挥头脑风暴,想出一些解决方案。
头脑风暴解决方案
算法和数据结构
使用第一性原理(first principles),从数据语料库开始,找到组合在一起的成对单词,并训练一个马尔可夫模型(https://en.wikipedia.org/wiki/Markov_model)来预测句子中出现成对单词的概率,这似乎是合理的。但是,您会注意到这种方法存在两个重要问题。
- 空间利用率:英语中有25万到100万个单词,其中不包括数量不断增长的大量专有名词。因此,任何建模一对单词出现在一起的概率的传统软件解决方案都必须维护一个具有250k*250k=625亿个单词对的查询表,这显然有点过分。其实,许多单词配对似乎不经常出现,可以进行修剪。即使在修剪之后,仍然存在很多对单词需要考虑。
- 完整性:对一对单词的概率进行编码并不能公正地解决手头的问题。例如,当你只看最近的一对单词时,前面的句子上下文就完全丢失了。在“你的一天过得怎么样(How is your day coming)”这句话中,如果你想检查“来了(coming)”之后的单词,你会有很多以“来了“开头的配对。这会漏掉该单词之前的整个句子上下文。人们可以想象使用单词三元组等……但这加剧了上述空间利用问题。
接下来,让我们把重点转移到利用英语本质的解决方案上,看看这是否能对我们有所帮助。
自然语言处理
从历史上看,NLP(自然语言处理)领域涉及理解句子的词性,并使用这些信息来执行这种修剪和预测决策。可以想象这样的情形:使用与每个单词相关联的一个POS标签来确定句子中的下一个单词是否有效。
然而,计算一个句子的词性的过程本身就是一个复杂的过程,需要对语言有专门的理解,正如NLTK的词性标记页面所证明的那样。
接下来,让我们来看一种基于深度学习的方法,它需要更多的标记数据,但不需要那么多的语言专业知识来构建。
深度学习(神经网络)
深度学习的出现颠覆了NLP领域。随着基于LSTM和Transformer的语言模型的发明,解决方案通常包括向模型抛出一些高质量的数据,并对其进行训练以预测下一个单词。
从本质上讲,这就是GPT模型正在做的事情。GPT模型总是被不断训练来预测给定句子前缀的下一个单词(标记)。
例如,给定句子前缀“It is so would a would”,模型很可能会为句子后面的单词提供以下高概率预测。
- day(白天)
- experience(经验)
- world(世界)
- life(生活)
以下单词完成句子前缀的概率也可能较低。
- red(红色)
- mouse(老鼠)
- line(线)
Transformer模型体系结构(machine_learning_model)是ChatGPT等系统的核心。然而,对于学习英语语义的更受限制的应用场景,我们可以使用更便宜的运行模型架构,例如LSTM(长短期记忆)模型。
LSTM模型
接下来,让我们构建一个简单的LSTM模型,并训练它来预测给定标记(token)前缀的下一个标记。现在,你可能会问什么是标记。
符号化
通常,对于语言模型,标记可以表示:
- 单个字符(或单个字节)
- 目标语言中的整个单词
- 介于1和2之间的词汇,这通常被称为子词
将单个字符(或字节)映射到标记是非常有限制的,因为我们要重载该标记,以保存关于它发生位置的大量上下文。这是因为,例如,“c”这个字符出现在许多不同的单词中,在我们看到“c”之后预测下一个字符需要我们认真观察引导上下文。
将一个单词映射到一个标记也是有问题的,因为英语本身有25万到100万个单词。此外,当一个新词被添加到语言中时会发生什么呢?我们需要回去重新训练整个模型来解释这个新词吗?
子词标记化(Sub-word tokenization)被认为是2023年的行业标准。它将频繁出现在一起的字节的子字符串分配给唯一的标记。通常,语言模型有几千(比如4000)到数万(比如60000)个独特的标记。确定什么构成标记的算法由BPE(字节对编码:Byte pair encoding)算法确定。
要选择词汇表中唯一标记的数量(称为词汇表大小),我们需要注意以下几点:
- 如果我们选择的标记太少,我们就会回到每个角色一个标记的模式,模型很难学到任何有用的内容。
- 如果我们选择了太多的标记,我们最终会出现这样的情况:模型的嵌入表覆盖了模型的其余权重,并且很难在受约束的环境中部署模型。嵌入表的大小将取决于我们为每个标记使用的维度的数量。使用256、512、786等大小并不罕见。如果我们使用512的标记嵌入维度,并且我们有100k个标记,那么我们最终会得到一个在内存中使用200MiB的嵌入表。
因此,我们需要在选择词汇量时进行平衡。在本例中,我们选取6600个标记,并用6600的词汇大小训练我们的标记生成器。接下来,让我们来看看模型定义本身。
PyTorch模型
模型本身非常简单。我们创建了以下几个层:
- 标记嵌入(词汇大小=6600,嵌入维数=512),总大小约为15MiB(假设嵌入表的数据类型为4字节的float32类型)
- LSTM(层数=1,隐藏尺寸=786),总尺寸约为16MiB
- 多层感知器(786至3144至6600维度),总尺寸约93MiB
整个模型具有约31M个可训练参数,总大小约为120MiB。
下面给出的是模型的PyTorch代码。
class WordPredictionLSTMModel(nn.Module):
def __init__(self, num_embed, embed_dim, pad_idx, lstm_hidden_dim, lstm_num_layers, output_dim, dropout):
super().__init__()
self.vocab_size = num_embed
self.embed = nn.Embedding(num_embed, embed_dim, pad_idx)
self.lstm = nn.LSTM(embed_dim, lstm_hidden_dim, lstm_num_layers, batch_first=True, dropout=dropout)
self.fc = nn.Sequential(
nn.Linear(lstm_hidden_dim, lstm_hidden_dim * 4),
nn.LayerNorm(lstm_hidden_dim * 4),
nn.LeakyReLU(),
nn.Dropout(p=dropout),
nn.Linear(lstm_hidden_dim * 4, output_dim),
)
#
def forward(self, x):
x = self.embed(x)
x, _ = self.lstm(x)
x = self.fc(x)
x = x.permute(0, 2, 1)
return x
#
#
分享说明:转发分享请注明出处。