自然语言处理实战第二版(MEAP)(五)(1)https://developer.aliyun.com/article/1519661
TranslationTransformer 推断示例 1
现在你可以在一个示例文本上使用你的translate_sentence()
函数了。由于你可能不懂德语,你可以从测试数据中随机选择一个例子。试试这个句子:“Eine Mutter und ihr kleiner Sohn genießen einen schönen Tag im Freien.” 在 OPUS 数据集中,字符大小写已折叠,所以你输入到你的变压器的文本应该是:“eine mutter und ihr kleiner sohn genießen einen schönen tag im freien.” 你期望的正确翻译是:“A mother and her little [or young] son are enjoying a beautiful day outdoors.”
代码清单 9.23 在 test_data 索引 10 处加载样本
>>> example_idx = 10 >>> src = vars(test_data.examples[example_idx])['src'] >>> trg = vars(test_data.examples[example_idx])['trg'] >>> src ['eine', 'mutter', 'und', 'ihr', 'kleiner', 'sohn', 'genießen', 'einen', 'schönen', 'tag', 'im', 'freien', '.'] >>> trg ['a', 'mother', 'and', 'her', 'young', 'song', 'enjoying', 'a', 'beautiful', 'day', 'outside', '.']
看起来 OPUS 数据集并不完美 - 目标(翻译)的标记序列在 “song” 和 “enjoying” 之间缺少动词 “are”。而且,德语单词 “kleiner” 可以翻译为 “little” 或 “young”,但 OPUS 数据集示例只提供了一种可能的 “正确” 翻译。那个 “young song” 是什么意思,似乎有点奇怪。也许这是 OPUS 测试数据集中的一个打字错误。
现在,你可以将src
标记序列通过你的翻译器,看看它是如何处理这种歧义的。
代码清单 9.24 翻译测试数据样本
>>> translation, attention = translate_sentence(src, SRC, TRG, model, device) >>> print(f'translation = {translation}') translation = ['a', 'mother', 'and', 'her', 'little', 'son', 'enjoying', 'a', 'beautiful', 'day', 'outside', '.', '<eos>']
有趣的是,在 OPUS 数据集中,德语单词“son”(“sohn”)的翻译出现了拼写错误。该数据集错误地将德语中的“sohn”翻译为英语中的“song”。根据上下文,模型表现出了良好的推断能力,推断出母亲(可能)和她的小(年幼的)“son”在一起。该模型给出了形容词“little”,而不是“young”,这是可以接受的,因为德语单词“kleiner”的直译是“smaller”。
让我们把注意力集中在,嗯,注意力 上。在你的模型中,你定义了一个 CustomDecoder,它保存了每次前向传递时每个解码器层的平均注意力权重。你有来自翻译的 attention 权重。现在编写一个函数,使用 matplotlib
来可视化每个解码器层的自注意力。
列表 9.25:用于可视化翻译变压器解码器层自注意力权重的函数
>>> import matplotlib.pyplot as plt >>> import matplotlib.ticker as ticker ... >>> def display_attention(sentence, translation, attention_weights): ... n_attention = len(attention_weights) ... ... n_cols = 2 ... n_rows = n_attention // n_cols + n_attention % n_cols ... ... fig = plt.figure(figsize=(15,25)) ... ... for i in range(n_attention): ... ... attention = attention_weights[i].squeeze(0) ... attention = attention.cpu().detach().numpy() ... cax = ax.matshow(attention, cmap='gist_yarg') ... ... ax = fig.add_subplot(n_rows, n_cols, i+1) ... ax.tick_params(labelsize=12) ... ax.set_xticklabels([''] + ['<sos>'] + ... [t.lower() for t in sentence]+['<eos>'], ... rotation=45) ... ax.set_yticklabels(['']+translation) ... ax.xaxis.set_major_locator(ticker.MultipleLocator(1)) ... ax.yaxis.set_major_locator(ticker.MultipleLocator(1)) ... ... plt.show() ... plt.close()
该函数在序列中的每个索引处绘制注意力值,原始句子在 x 轴上,翻译句子在 y 轴上。我们使用 gist_yarg 颜色图,因为它是一种打印友好的灰度方案。现在展示“母亲和儿子享受美好的一天”句子的注意力。
列表 9.26:可视化测试示例翻译的自注意力权重
>>> display_attention(src, translation, attention_weights)
查看最初的两个解码器层的图表,我们可以看到一个区域的注意力开始沿着对角线发展。
图 9.6:测试翻译示例:解码器自注意力层 1 和 2
在后续的三层和四层中,注意力似乎变得更加精细。
图 9.7:测试翻译示例:解码器自注意力层 3 和 4
在最后两层中,我们看到注意力在直接词对词翻译的地方有很强的权重,沿着对角线,这是你可能期望的。请注意有阴影的文章名词和形容词名词对的聚类。例如,“son”明显在单词“sohn”上有权重,但也注意到了“kleiner”。
图 9.8:测试翻译示例:解码器自注意力层 5 和 6
你随机选择了这个例子来自测试集,以了解模型的翻译能力。注意力图似乎显示出模型正在捕捉句子中的关系,但单词重要性仍然强烈地与位置有关。换句话说,原始句子中当前位置的德语单词通常被翻译为目标输出中相同或类似位置的英语单词。
翻译变压器推理示例 2
再看另一个例子,这次是来自验证集的例子,在输入序列和输出序列的从句顺序不同的情况下,看看注意力是如何起作用的。在接下来的列表中加载并打印索引 25 处的验证样本数据。
列表 9.27 在 valid_data 索引 25 处加载样本
>>> example_idx = 25 ... >>> src = vars(valid_data.examples[example_idx])['src'] >>> trg = vars(valid_data.examples[example_idx])['trg'] ... >>> print(f'src = {src}') >>> print(f'trg = {trg}') src = ['zwei', 'hunde', 'spielen', 'im', 'hohen', 'gras', 'mit', 'einem', 'orangen', 'spielzeug', '.'] trg = ['two', 'dogs', 'play', 'with', 'an', 'orange', 'toy', 'in', 'tall', 'grass', '.']
即使你的德语理解能力不强,很明显 orange toy(“orangen spielzeug”)在源句的末尾,而 in the tall grass 在中间。然而,在英语句子中,“in tall grass” 完成了句子,而“with an orange toy” 是“play”行为的直接接受者,在句子的中间部分。用你的模型翻译这个句子。
列表 9.28 翻译验证数据样本
>>> translation, attention = translate_sentence(src, SRC, TRG, model, device) >>> print(f'translation = {translation}') translation = ['two', 'dogs', 'are', 'playing', 'with', 'an', 'orange', 'toy', 'in', 'the', 'tall', 'grass', '.', '<eos>']
对于一个大约花费 15 分钟训练的模型来说,这是一个相当令人兴奋的结果(取决于你的计算能力)。再次通过调用 display_attention() 函数,绘制注意力权重,传入 src、translation 和 attention。
列表 9.29 可视化验证示例翻译的自注意力权重
>>> display_attention(src, translation, attention)
这里我们展示了最后两层(第 5 和第 6 层)的图表。
图 9.9 验证翻译示例:解码器自注意力层 5 和 6
这个样本出色地展示了注意力权重如何打破了位置-序列模式,实际上关注了句子中更早或更晚的单词。它真正展示了多头自注意机制的独特性和力量。
总结这一节,你将计算模型的 BLEU(双语评估助手)得分。torchtext
包提供了一个函数,bleu_score,用于执行计算。你使用下面的函数,同样来自特雷维特先生的笔记本,对数据集进行推理并返回得分。
>>> from torchtext.data.metrics import bleu_score ... >>> def calculate_bleu(data, src_field, trg_field, model, device, max_len = 50): ... trgs = [] ... pred_trgs = [] ... for datum in data: ... src = vars(datum)['src'] ... trg = vars(datum)['trg'] ... pred_trg, _ = translate_sentence( ... src, src_field, trg_field, model, device, max_len) ... # strip <eos> token ... pred_trg = pred_trg[:-1] ... pred_trgs.append(pred_trg) ... trgs.append([trg]) ... ... return bleu_score(pred_trgs, trgs)
计算你的测试数据得分。
>>> bleu_score = calculate_bleu(test_data, SRC, TRG, model, device) >>> print(f'BLEU score = {bleu_score*100:.2f}') BLEU score = 37.68
与本·特雷维特(Ben Trevett)的教程代码进行比较,一个卷积序列到序列模型^([25])获得了 33.3 的 BLEU,而较小规模的 Transformer 得分约为 35。你的模型使用了与原始的“注意力机制就是一切”Transformer 相同的维度,因此它表现良好并不奇怪。
9.3 双向反向传播和“BERT”
有时你想要预测序列中的某些东西 —— 也许是一个被屏蔽的单词。Transformer 也可以处理这个。而且模型不需要局限于以“因果”的方式从左到右读取文本。它也可以从右边的掩码另一侧从右到左读取文本。在生成文本时,你的模型被训练来预测的未知单词位于文本的末尾。但是 Transformer 也可以预测一个内部单词,例如,如果你试图揭开米勒报告中被涂黑的秘密部分。
当你想预测一个未知的词 在 你的示例文本中时,你可以利用被遮罩词之前和 之后 的单词。人类读者或 NLP 流水线可以从任何位置开始。对于 NLP,你总是有一个特定长度的特定文本需要处理。因此,你可以从文本的末尾或开头开始…或 两者都!这就是 BERT 利用的洞察力,用于创建任何文本的任务无关嵌入。它是在通常任务上训练的,即预测遮罩词,类似于你在第六章中使用 skip-grams 训练单词嵌入的方式。而且,就像在单词嵌入训练中一样,BERT 通过屏蔽单词并训练双向 transformer 模型来恢复被遮罩的单词,从未标记的文本中创建了大量有用的训练数据。
2018 年,Google AI 的研究人员推出了一种称为 BERT 的新语言模型,即“双向编码器从 transformer 中获取的表示”^([26])。“BERT” 中的 “B” 是指 “双向”。它不是以芝麻街的角色命名的,而是指 “双向编码器从 transformer 中获取的表示” - 基本上就是一个双向 transformer。双向 transformer 对机器来说是一个巨大的进步。在下一章,第十章中,你将了解到帮助 transformer(升级版 RNN)在许多最困难的 NLP 问题中登顶的三个技巧之一是什么。赋予 RNN 读取双向文本的能力就是其中之一,它帮助机器在阅读理解任务中超越人类。
BERT 模型有两种版本(配置) — BERT BASE 和 BERT LARGE — 由一堆带有前馈和注意层的编码器 transformer 组成。与之前的 transformer 模型(如 OpenAI GPT)不同,BERT 使用了遮罩语言建模(MLM)目标来训练一个深度双向 transformer。MLM 包括在输入序列中随机遮罩标记,然后尝试从上下文预测实际标记。MLM 目标比典型的从左到右的语言模型训练更强大,它允许 BERT 在所有层中通过连接标记的左右上下文来更好地概括语言表示。BERT 模型是在英文维基百科中(不包括表格和图表,共 2500M 个单词)和 BooksCorpus(也是 GPT 的训练基础,共 800M 个单词)上以半监督的方式预训练的。通过简单调整输入和输出层,模型可以被微调以在特定的句子级和标记级任务上达到最先进的结果。
9.3.1 分词和预训练
输入到 BERT 的序列可以模糊地表示一个单独的句子或一对句子。 BERT 使用 WordPiece 嵌入,每个序列的第一个令牌始终设置为特殊的*[CLS]令牌。 句子通过尾随的分隔符令牌[SEP]*进行区分。 序列中的令牌进一步通过单独的段嵌入进行区分,每个令牌分配给句子 A 或 B。 此外,还向序列添加了位置嵌入,以便于序列的每个位置的输入表示由相应的令牌、段和位置嵌入的总和形成,如下图所示(来自已发表的论文):
在预训练期间,输入令牌的一部分被随机屏蔽(使用*[MASK]令牌),模型预测这些屏蔽令牌的实际令牌 ID。 在实践中,选择了 15%的 WordPiece 令牌进行训练,然而,这样做的一个缺点是在微调过程中没有[MASK]令牌。 为了解决这个问题,作者提出了一个公式,以使被选中的令牌(15%)在 80%的时间内替换为[MASK]令牌。 对于其他 20%,他们将令牌替换为随机令牌的 10%的时间,并保留原始令牌的 10%的时间。 除了这个 MLM 目标预训练,还进行了次要训练以进行下一句子预测(NSP)。 许多下游任务,如问答(QA),依赖于理解两个句子之间的关系,不能仅通过语言建模来解决。 对于 NSP 训练的波段,作者通过为每个样本选择句子 A 和 B 并将它们标记为IsNext和NotNext*,生成了一个简单的二元 NSP 任务。 预训练的 50%的样本中的选择是语料库中句子 B 跟随句子 A,另一半的句子 B 是随机选择的。 这个简单的解决方案表明有时候人们无需过度思考问题。
9.3.2 微调
对于大多数 BERT 任务,你会想要加载 BERT[BASE]或 BERT[LARGE]模型,并使用所有参数从预训练进行初始化,然后针对你的特定任务对模型进行微调。微调通常是直接的;只需将任务特定的输入和输出插入,并开始对所有参数进行训练。与初始预训练相比,模型的微调要便宜得多。BERT 在许多任务上表现出色。例如,在发布时,BERT 在通用语言理解评估(GLUE)基准测试中超过了当前最先进的 OpenAI GPT 模型。并且在斯坦福问答数据集(SQuAD v1.1)上,BERT 超过了最佳系统(集成模型),这个任务是从给定的维基百科段落中选择包含给定问题答案的文本片段。不出所料,对于这个任务的一个变种,SQuAD v2.0,BERT 也是最好的,它允许问题的简短答案在文本中可能不存在。
9.3.3 实现
在前面的章节中,从原始 Transformer 的讨论中借鉴来的,对于 BERT 的配置,L 表示 Transformer 层的数量,隐藏层大小为 H,自注意力头的数量为 A。BERT[BASE]的尺寸为 L=12,H=768,A=12,总共有 1.1 亿个参数。BERT[LARGE]有 L=24,H=1024,A=16,总共有 3.4 亿个参数!大型模型在所有任务上都超过了基准模型的性能,但是取决于你可用的硬件资源,你可能会发现使用基准模型已经足够了。对于基准和大型配置,预训练模型都有大小写(cased) 和 不区分大小写(uncased) 的版本。不区分大小写(uncased) 版本在预训练 WordPiece 分词之前将文本转换为全小写,而 大小写(cased) 模型的输入文本没有作任何更改。
BERT 的原始实现是作为 TensorFlow tensor2tensor 库的一部分进行开源的 ^([27])。 TensorFlow Hub 的作者们在 BERT 学术论文发布时,编写了一个 Google Colab 笔记本 ^([28]),展示了如何对 BERT 进行句对分类任务的微调。运行这个笔记本需要注册以访问 Google Cloud Platform Compute Engine,并获取一个 Google Cloud Storage 存储桶。在撰写本文时,谷歌似乎仍然给首次用户提供货币积分,但通常情况下,一旦你耗光了最初试用积分,就需要支付以获得计算能力的费用。
注意
随着您深入研究 NLP 模型,特别是使用具有深度变压器堆栈的模型,您可能会发现您当前的计算机硬件不足以满足训练和/或微调大型模型的计算密集型任务。您将需要评估建立个人计算机以满足工作负载的成本,并将其与用于人工智能的按使用量付费的云和虚拟计算方案进行权衡。我们在本文中提及了基本的硬件要求和计算选项,然而,讨论 “正确” 的个人电脑设置或提供竞争性计算选项的详尽列表超出了本书的范围。除了刚刚提到的 Google 计算引擎之外,附录中还有设置亚马逊网络服务(AWS)GPU 的说明。
BERT 模型的 Pytorch 版本是在 pytorch-pretrained-bert
库中实现的 ^([29]),后来又被纳入了不可或缺的 HuggingFace transformers 库中 ^([30])。您最好花一些时间阅读网站上的 “入门” 文档以及变压器模型和相关任务的摘要。要安装 transformers 库,只需使用 pip install transformers
。安装完成后,使用 BertModel.from_pre-trained()
API 按名称加载一个。您可以在下面的列表中打印加载的 “bert-base-uncased” 模型的摘要,以了解其架构。
列表 9.30 BERT 架构的 Pytorch 摘要
>>> from transformers import BertModel >>> model = BertModel.from_pre-trained('bert-base-uncased') >>> print(model)
BertModel( (embeddings): BertEmbeddings( (word_embeddings): Embedding(30522, 768, padding_idx=0) (position_embeddings): Embedding(512, 768) (token_type_embeddings): Embedding(2, 768) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) (encoder): BertEncoder( (layer): ModuleList( (0): BertLayer( (attention): BertAttention( (self): BertSelfAttention( (query): Linear(in_features=768, out_features=768, bias=True) (key): Linear(in_features=768, out_features=768, bias=True) (value): Linear(in_features=768, out_features=768, bias=True) (dropout): Dropout(p=0.1, inplace=False) ) (output): BertSelfOutput( (dense): Linear(in_features=768, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) (intermediate): BertIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): BertOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ... # #1 (11): BertLayer( (attention): BertAttention(...) (intermediate): BertIntermediate( (dense): Linear(in_features=768, out_features=3072, bias=True) ) (output): BertOutput( (dense): Linear(in_features=3072, out_features=768, bias=True) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ) ) ) (pooler): BertPooler( (dense): Linear(in_features=768, out_features=768, bias=True) (activation): Tanh() ) )
导入 BERT 模型后,您可以显示其字符串表示以获取其结构的摘要。如果您考虑设计自己的自定义双向变压器,这是一个很好的起点。但在大多数情况下,您可以直接使用 BERT 来创建准确表示大多数文本含义的英文文本编码。预训练的 BERT 模型可能是您应用程序所需的全部,例如聊天机器人意图标记(分类或标记)、情感分析、社交媒体管理、语义搜索和常见问题解答。如果您考虑在向量数据库中存储嵌入以进行语义搜索,那么普通的 BERT 编码是您的最佳选择。
在下一节中,您将看到如何使用预训练的 BERT 模型识别有毒社交媒体消息的示例。然后,您将看到如何通过在数据集上进行额外的训练周期来微调 BERT 模型以适用于您的应用程序。您将看到,微调 BERT 可以显著提高您的有毒评论分类准确性,而不会过度拟合。
9.3.4 对预训练的 BERT 模型进行微调以进行文本分类
2018 年,Conversation AI ^([31]) 团队(由 Jigsaw 和 Google 联合创办)举办了一场 Kaggle 竞赛,旨在开发一个模型来检测在线社交媒体帖子中的各种毒性。当时,LSTM 和卷积神经网络是当时的最新技术。具有注意力的双向 LSTM 在这场比赛中取得了最佳成绩。BERT 的承诺是它可以同时从当前正在处理的转换器的当前单词的左右单词中学习单词上下文。这使得它特别适用于创建多用途的编码或嵌入向量,用于解决诸如检测有毒社交媒体评论之类的分类问题。而且由于 BERT 是在一个大型语料库上预训练的,所以你不需要庞大的数据集或超级计算机,就能够利用 迁移学习 的力量来微调一个性能良好的模型。
在这一节中,你将使用该库快速微调一个预先训练好的 BERT 模型,用于分类有毒的社交媒体帖子。之后,你将进行一些调整,以改进模型,以期在消除恶意行为,清除网络喷子的道路上取得成功。
一个有毒的数据集
你可以从 kaggle.com 下载"有毒评论分类挑战"数据集(archive.zip
)。^([32]) 如果愿意,你可以将数据放在你的 $HOME/.nlpia2-data/
目录中,和本书的其他大型数据集一起。当你解压 archive.zip
文件时,你会看到它包含了训练集(train.csv
)和测试集(test.csv
)作为单独的 CSV 文件。在现实世界中,你可能会将训练集和测试集合并起来,创建自己的验证和测试示例样本。但为了使你的结果与竞赛网站上看到的结果可比,你首先只会使用训练集。
首先,使用 pandas 加载训练数据,并查看接下来列表中显示的前几个条目。通常,你会想查看数据集中的示例,以了解数据的格式。尝试做与你要求模型执行的相同任务通常是有帮助的,以查看它是否是一个合理的 NLP 问题。这里是训练集中的前五个示例。幸运的是,数据集被排序为首先包含非有毒的帖子,所以你不必在这一节的最后读取任何有毒评论。如果你有一个名叫"Terri"的祖母,你可以在这一节的最后一个代码块的最后一行闭上你的眼睛 ;-)
。
列表 9.31 载入有毒评论数据集
>>> import pandas as pd >>> df = pd.read_csv('data/train.csv') # #1 >>> df.head() comment_text toxic severe obscene threat insult hate Explanation\nWhy the edits made 0 0 0 0 0 0 D'aww! He matches this backgrou 0 0 0 0 0 0 Hey man, I'm really not trying 0 0 0 0 0 0 "\nMore\nI can't make any real 0 0 0 0 0 0 You, sir, are my hero. Any chan 0 0 0 0 0 0 >>> df.shape (159571, 8)
哎呀,幸运的是,前五条评论都不是淫秽的,所以它们可以打印在这本书中。
花时间与数据相处。
通常在这一点上,你会探索和分析数据,关注文本样本的特性和标签的准确性,也许会问自己关于数据的问题。评论通常有多长?句子长度或评论长度是否与毒性有关?考虑专注于一些severe_toxic评论。它们与仅仅有毒的评论有什么不同?类分布是什么样的?你是否需要在训练技术中考虑到类别不平衡?
你想要进行训练,所以让我们将数据集分割为训练集和验证(评估)集。由于可供模型调整的样本数量几乎为 160,000 个,我们选择使用 80-20 的训练-测试分割。
列表 9.32 将数据分割为训练集和验证集
>>> from sklearn.model_selection import train_test_split >>> random_state=42 >>> labels = ['toxic', 'severe', 'obscene', 'threat', 'insult', 'hate'] >>> X = df[['comment_text']] >>> y = df[labels] >>> X_train, X_test, y_train, y_test = train_test_split( ... X, y, test_size=0.2, ... random_state=random_state) # #1
现在你已经将数据存储在一个具有描述性列名的 Pandas DataFrame 中,你可以使用这些列名来解释模型的测试结果。
还有一个最后的 ETL 任务需要你处理,你需要一个包装函数来确保传递给转换器的示例批次具有正确的形状和内容。你将使用simpletransformers
库,该库为各种 Hugging Face 模型设计了包装器,用于分类任务,包括多标签分类,不要与多类别或多输出分类模型混淆。Scikit-Learn 包还包含一个MultiOutputClassifier
包装器,你可以使用它来创建多个评估器(模型),每个目标标签对应一个。
重要
多标签分类器是一个模型,它为每个输入输出多个不同的预测离散分类标签(‘toxic’,‘severe’和’obscene’),这允许你的文本被赋予多个不同的标签。就像托尔斯泰的*《安娜·卡列尼娜》*中的虚构家庭一样,一条有毒的评论可以以多种不同的方式毒害人,而且同时进行。你可以将多标签分类器视为向文本应用标签或表情符号。为了避免混淆,你可以将你的模型称为“标签器”或“标签模型”,以免别人误解你。
由于每个评论可以被分配多个标签(零个或多个),因此 MultiLabelClassificationModel
是解决这类问题的最佳选择。 根据文档^([34]),MultiLabelClassificationModel
模型期望以 ["text", [label1, label2, label3, …]]
的格式提供训练样本。 这样做可以保持数据集的外部形状不变,无论您想要跟踪多少不同类型的毒性。 Hugging Face transformers
模型可以处理具有任意数量可能标签(标签)的数据结构,但您需要在管道内保持一致,在每个示例中使用相同数量的可能标签。 您需要一个具有恒定维度数量的multihot零和一向量,以便您的模型知道在哪里放置对每种毒性的预测。 下一个列表显示了如何安排包含在训练和评估模型期间运行的包装器函数的数据批次。
列表 9.33 为模型创建数据集
>>> def get_dataset(X, y): ... data = [[X.iloc[i][0], y.iloc[i].values.tolist()] for i in range(X.shape[0])] ... return pd.DataFrame(data, columns=['text', 'labels']) ... >>> train_df = get_dataset(X_train, y_train) >>> eval_df = get_dataset(X_test, y_test) >>> train_df.shape, eval_df.shape ((127656, 2), (31915, 2)) >>> train_df.head() # #1 text labels 0 Grandma Terri Should Burn in Trash \nGrandma T... [1, 0, 0, 0, 0, 0] 1 , 9 May 2009 (UTC)\nIt would be easiest if you... [0, 0, 0, 0, 0, 0] 2 "\n\nThe Objectivity of this Discussion is dou... [0, 0, 0, 0, 0, 0] 3 Shelly Shock\nShelly Shock is. . .( ) [0, 0, 0, 0, 0, 0] 4 I do not care. Refer to Ong Teng Cheong talk p... [0, 0, 0, 0, 0, 0]
现在您可以看到,如果母亲和祖母是欺负者侮辱的目标,那么该数据集对毒性的门槛相当低。 这意味着即使您要保护极端敏感或年轻的用户,该数据集也可能会有所帮助。 如果您试图保护现代成年人或习惯于在线体验残酷的数字原住民,您可以从其他来源增加这个数据集的更极端的例子。
使用 simpletransformers
检测有毒评论
现在您有一个函数,用于将带标签的文本批次传递给模型并打印一些消息以监视您的进度。 所以现在是选择要下载的 BERT 模型的时候了。 您只需要设置几个基本参数,然后就可以准备加载预训练的 BERT 进行多标签分类并开始微调(训练)。
列表 9.34 设置训练参数
>>> import logging >>> logging.basicConfig(level=logging.INFO) # #1 >>> model_type = 'bert' # #2 >>> model_name = 'bert-base-cased' >>> output_dir = f'{model_type}-example1-outputs' >>> model_args = { ... 'output_dir': output_dir, # where to save results ... 'overwrite_output_dir': True, # allow re-run without having to manually clear output_dir ... 'manual_seed': random_state, # #3 ... 'no_cache': True, ... }
在下面的列表中,您加载了预训练的 bert-base-cased
模型,配置为输出我们有毒评论数据中的标签数(总共 6 个),并使用您的 model_args
字典初始化进行训练^([35])。
列表 9.35 载入预训练模型并微调
>>> from sklearn.metrics import roc_auc_score >>> from simpletransformers.classification import MultiLabelClassificationModel >>> model = MultiLabelClassificationModel( ... model_type, model_name, num_labels=len(labels), ... args=model_args) You should probably TRAIN this model on a downstream task to be able to use it for predictions and inference >>> model.train_model(train_df=train_df) # #1
train_model()
为您完成了繁重的工作。 它加载了您选择的预训练bert-base-cased模型的预训练 BertTokenizer
并将其用于对 train_df['text']
进行标记以用于训练模型的输入。 该函数将这些输入与 train_df[labels]
结合生成一个 TensorDataset
,然后将其与 PyTorch DataLoader
包装,然后以批次进行迭代,以构成训练循环。
换句话说,只需几行代码和一次通过您的数据(一个时期),您就可以微调具有 1100 万参数的 12 层变换器! 下一个问题是:它有助于还是损害了模型的翻译能力? 让我们对评估集运行推理并检查结果。
列表 9.36 评估
>>> result, model_outputs, wrong_predictions = model.eval_model(eval_df, ... acc=roc_auc_score) # #1 >>> result {'LRAP': 0.9955934600588362, 'acc': 0.9812396881786198, 'eval_loss': 0.04415484298031397}
ROC(Receiver Operating Characteristic curve)AUC(曲线下面积)指标通过计算分类器在精度与召回率曲线(曲线)下的积分(面积)来平衡分类器可能出现的所有错误方式。这确保了那些自信错误的模型比那些在预测概率值上更接近真相的模型受到更严厉的惩罚。而simpletransformers
包中的roc_auc_score
会给出所有示例和每个文本可能选择的所有不同标签的微平均值。
ROC AUC 微平均分数实质上是所有predict_proba
错误值的总和,或者说是预测概率值与人类标注者给定的 0 或 1 值之间的距离。在测量模型准确性时,记住这个心理模型是一个好主意。准确性只是离人类标注者认为的正确答案有多接近,而不是关于被标注的词语的意义、意图或影响的绝对真相。毒性是一个非常主观的质量。
AUC 分数为 0.981 并不算太差。虽然它不会为你赢得任何称赞^([36]),但它确实提供了对你的训练模拟和推理是否设置正确的鼓励性反馈。
eval_model()
和train_model()
的实现方法都可以在MultiLabelClassificationModel
和ClassificationModel
的基类中找到。评估代码会让你感到熟悉,因为它使用了with torch.no_grad()
上下文管理器来进行推理,这是人们所期望的。建议你花些时间看看这些方法的实现情况。特别是train_model()
对于查看在训练和评估过程中如何使用下一部分中选择的配置选项是有帮助的。
更好的 BERT
现在你已经有了一个模型的初步版本,可以进行更细致的调整,以帮助你的基于 BERT 的模型做得更好。在这种情况下,“更好”简单地意味着有更高的 AUC 分数。就像在现实世界中一样,你需要决定在你的特定情况中什么是更好的。因此,不要忘记关注模型的预测如何影响使用你的模型的人或企业的用户体验。如果你能找到一个更好的度量标准,更直接地衡量对于你的用户来说什么是“更好”,那么你应该将它替换掉这段代码中的 AUC 分数。
在上一个示例中执行的训练代码基础上,您将致力于提高模型的准确性。通过一些预处理稍微清理文本是相当直接的。该书的示例源代码附带了一个我们编写的实用程序TextPreprocessor
类,用于替换常见的拼写错误,扩展缩略语并执行其他杂项清理,如删除额外的空白字符。继续并将加载的train.csv数据框中的comment_text
列重命名为original_text
。将预处理器应用于原始文本,并将精炼后的文本存储回comment_text
列。
列表 9.37 预处理评论文本
>>> from preprocessing.preprocessing import TextPreprocessor >>> tp = TextPreprocessor() loaded ./inc/preprocessing/json/contractions.json loaded ./inc/preprocessing/json/misc_replacements.json loaded ./inc/preprocessing/json/misspellings.json >>> df = df.rename(columns={'comment_text':'original_text'}) >>> df['comment_text'] = df['original_text'].apply( ... lambda x: tp.preprocess(x)) # #1 >>> pd.set_option('display.max_colwidth', 45) >>> df[['original_text', 'comment_text']].head() original_text comment_text 0 Explanation\nWhy the edits ... Explanation Why the edits made... 1 D'aww! He matches this back... D'aww! He matches this backgro... 2 Hey man, I'm really not try... Hey man, i am really not tryin... 3 "\nMore\nI can't make any r... " More I cannot make any real ... 4 You, sir, are my hero. Any ... You, sir, are my hero. Any cha...
文本清理完成后,将重点转向调整模型初始化和训练参数。在第一次训练运行中,由于未向模型提供max_sequence_length
的显式值,您接受了默认的输入序列长度(128)。BERT-base 模型可以处理最大长度为 512 的序列。随着max_sequence_length
的增加,您可能需要减小train_batch_size
和eval_batch_size
以将张量适应 GPU 内存,具体取决于您可用的硬件。您可以对评论文本的长度进行一些探索,以找到最佳的最大长度。请注意,某些时候您将获得收益递减,使用较大的序列导致更长的训练和评估时间,并不会显著提高模型的准确性。对于此示例,选择一个最大长度为 300 的max_sequence_length
,介于默认值 128 和模型容量之间。还要显式选择train_batch_size
和eval_batch_size
以适应 GPU 内存。
警告
如果在训练或评估开始后不久显示 GPU 内存异常,则会很快意识到批处理大小设置过大。您不一定想要基于此警告来最大化批处理大小。警告可能仅在训练运行后期出现,并破坏长时间的训练会话。对于batch_size
参数,更大并不总是更好。有时,较小的批处理大小将有助于让您的训练在梯度下降中更具随机性。更随机有时可以帮助您的模型跳过高维度非凸误差表面上的峰值和鞍点。
记得在第一次微调运行中,模型正好训练了一个时期。您认为模型可能需要更长时间的训练才能取得更好的结果的直觉很可能是正确的。您希望找到在模型对训练样本过拟合之前需要进行训练的数量的平衡点。配置选项以在训练期间启用评估,以便您还可以设置早停的参数。在训练期间的评估分数用于通知早停。因此,设置evaluation_during_training=True
以启用它,还要设置use_early_stopping=True
。随着模型学会泛化,我们预期在评估步骤之间的性能会出现波动,因此您不希望仅仅因为准确性从最新的评估步骤的上一个值下降而停止训练。配置早停的耐心,即连续几次评估没有改善(定义为大于某个增量)时终止训练的次数。您将设置early_stopping_patience=4
,因为您有点耐心,但也有自己的极限。使用early_stopping_delta=0
,因为没有任何改善量都不会太小。
在训练期间重复将这些转换器模型保存到磁盘(例如,在每个评估阶段或每个时期之后)需要时间和磁盘空间。对于本示例,您希望保留在训练过程中生成的最佳模型,因此请指定best_model_dir
来保存表现最佳的模型。将其保存到output_dir
下的位置很方便,这样您运行更多实验时,所有的训练结果都可以组织起来。
>>> model_type = 'bert' >>> model_name = 'bert-base-cased' >>> output_dir = f'{model_type}-example2-outputs' # #1 >>> best_model_dir = f'{output_dir}/best_model' >>> model_args = { ... 'output_dir': output_dir, ... 'overwrite_output_dir': True, ... 'manual_seed': random_state, ... 'no_cache': True, ... 'best_model_dir': best_model_dir, ... 'max_seq_length': 300, ... 'train_batch_size': 24, ... 'eval_batch_size': 24, ... 'gradient_accumulation_steps': 1, ... 'learning_rate': 5e-5, ... 'evaluate_during_training': True, ... 'evaluate_during_training_steps': 1000, ... 'save_eval_checkpoints': False, ... "save_model_every_epoch": False, ... 'save_steps': -1, # saving model unnecessarily takes time during training ... 'reprocess_input_data': True, ... 'num_train_epochs': 5, # #2 ... 'use_early_stopping': True, ... 'early_stopping_patience': 4, # #3 ... 'early_stopping_delta': 0, ... }
通过调用model.train_model()
来训练模型,就像之前做的那样。一个变化是你现在要evaluate_during_training
,所以你需要包括一个eval_df
(你的验证数据集)。这允许您的训练例程在训练模型时估计您的模型在现实世界中的表现如何。如果验证准确性连续几个(early_stoping_patience
)时期下降,您的模型将停止训练,以免继续恶化。
>>> model = MultiLabelClassificationModel( ... model_type, model_name, num_labels=len(labels), ... args=model_args) >>> model.train_model( ... train_df=train_df, eval_df=eval_df, acc=roc_auc_score, ... show_running_loss=False, verbose=False)
您的最佳模型在训练期间保存在best_model_dir
中。不用说,这是您想要用于推断的模型。评估代码段已更新,通过在模型类的构造函数中将best_model_dir
传递给model_name
参数来加载模型。
>>> best_model = MultiLabelClassificationModel( ... model_type, best_model_dir, ... num_labels=len(labels), args=model_args) >>> result, model_outputs, wrong_predictions = best_model.eval_model( ... eval_df, acc=roc_auc_score) >>> result {'LRAP': 0.996060542761153, 'acc': 0.9893854727083252, 'eval_loss': 0.040633044850540305}
现在看起来更好了。0.989 的准确率使我们有机会与 2018 年初的顶级挑战解决方案竞争。也许你认为 98.9%的准确率可能有点太好以至于难以置信。你是对的。精通德语的人需要挖掘一些翻译以找到模型的所有翻译错误。而误判的负样本-被错误标记为正确的测试样本-会更难找到。
如果你和我一样,可能没有一个流利的德语翻译者。所以这里有一个更注重英语的翻译应用的快速示例,你可能更能欣赏,即语法检查和修正。即使你还是一个英语学习者,你也会欣赏到拥有一个个性化工具来帮助你写作的好处。个性化语法检查器可能是帮助你发展强大沟通技巧和推进你的 NLP 职业的个人杀手级应用。
9.4 自测题
- 与其他深度学习层(如 CNN,RNN 或 LSTM 层)相比,转换器层的输入和输出维度有什么不同?
- 如何扩展像 BERT 或 GPT-2 这样的转换器网络的信息容量?
- 估计在特定标记数据集上获得高准确度所需的信息容量的经验法则是什么?
- 什么是衡量 2 个深度学习网络相对信息容量的好方法?
- 有什么方法可以减少训练转换器所需的标记数据量,以解决摘要等问题?
- 如何衡量摘要器或翻译器的准确性或损失,当可能存在不止一个正确答案时?
9.5 总结
- 通过保持每层的输入和输出一致,转换器获得了它们的关键超能力-无限可堆叠性。
- 转换器结合了三个关键创新,以实现改变世界的 NLP 能力:BPE 分词,多头注意力和位置编码。
- GPT 转换器架构是大多数文本生成任务(如翻译和会话式聊天机器人)的最佳选择。
- 尽管已经发布了 5 年以上(当本书发布时)BERT 转换器模型仍然是大多数 NLU 问题的正确选择。
- 如果你选择了一个高效的预训练模型,你可以通过精调它来在许多困难的 Kaggle 问题上取得竞争力,只需使用像笔记本电脑或免费的在线 GPU 资源这样的经济设备。
第十章:现实世界中的 10 个大型语言模型
本章涵盖内容
- 了解对话型 LLMs(如 ChatGPT)的工作原理
- 非法破解 LLM 以获取其程序员不希望其说的内容
- 识别 LLM 输出中的错误、错误信息和偏见
- 使用您自己的数据对 LLMs 进行微调
- 通过语义搜索为您的查询找到有意义的搜索结果
- 使用近似最近邻算法加速您的向量搜索
- 使用 LLMs 生成基于事实的格式良好的文本
如果将基于 Transformer 的语言模型的参数数量增加到令人费解的规模,您可以实现一些令人惊讶的结果。研究人员将这些产生的意外称为* emergent properties*,但它们可能是一个幻觉。 ^([1]) 自从普通大众开始意识到真正大型变压器的能力以来,它们越来越被称为大型语言模型(LLMs)。其中最耸人听闻的惊喜是使用 LLMs 构建的聊天机器人可以生成听起来智能的文本。您可能已经花了一些时间使用诸如 ChatGPT,You.com 和 Llamma 2 的对话型 LLMs。和大多数人一样,您可能希望如果您在提示它们方面变得熟练,它们可以帮助您在职业生涯中取得进展,甚至在个人生活中也能帮助您。和大多数人一样,您可能终于感到松了一口气,因为您终于有了一个能够给出直接、智慧的回答的搜索引擎和虚拟助手。本章将帮助您更好地使用 LLMs,以便您不仅仅是听起来智能。
本章将帮助您理解生成 LLMs 的工作方式。我们还将讨论 LLMs 实际应用中的问题,以便您可以聪明地使用它们并将对自己和他人的伤害降至最低:
- 错误信息:在社交媒体上训练的 LLMs 将放大错误信息
- 可靠性:LLMs 有时会在您的代码和文字中插入错误,这些错误非常难以察觉
- 对学习的影响:使用不当,LLMs 可能降低您的元认知能力
- 对集体智慧的影响:用虚假和非人类的文本淹没信息领域会贬值真实而深思熟虑的人类生成的思想。
- 偏见:LLMs 具有算法偏见,这些偏见以我们很少注意到的方式伤害我们,除非它影响到我们个人,导致分裂和不信任
- 可访问性:大多数人没有获得有效使用 LLMs 所需的资源和技能,这使得已经处于不利地位的人更加不利。
- 环境影响:2023 年,LLMs 每天排放超过 1000 公斤的二氧化碳当量[2]] [3]]
通过构建和使用更智能、更高效的 LLMs,您可以减轻许多这些伤害。这就是本章的全部内容。您将看到如何构建生成更智能、更可信、更公平的 LLMs。您还将了解如何使您的 LLMs 更高效、更节约,不仅减少环境影响,还帮助更多人获得 LLMs 的力量。
10.1 大型语言模型(LLMs)
最大的 LLMs 具有超过一万亿的参数。这么大的模型需要昂贵的专门硬件和数月时间在高性能计算(HPC)平台上进行计算。在撰写本文时,仅在 Common Crawl 的 3TB 文本上训练一个适度的 100B 参数模型就至少需要花费 300 万美元^([4])。即使是最粗糙的人脑模型也必须具有超过 100 万亿个参数,以解释我们神经元之间的所有连接。LLMs 不仅具有高容量的“大脑”,而且它们已经吞食了一座文本山——所有 NLP 工程师在互联网上找到的有趣文本。结果发现,通过跟随在线对话,LLMs 可以非常擅长模仿智能的人类对话。甚至负责设计和构建 LLMs 的大型技术公司的工程师们也被愚弄了。人类对任何看起来有意图和智能的事物都有一种软肋。我们很容易被愚弄,因为我们把周围的一切都拟人化了,从宠物到公司和视频游戏角色。
这对研究人员和日常科技用户来说都是令人惊讶的。原来,如果你能预测下一个词,并加入一点人类反馈,你的机器人就能做更多事情,而不仅仅是用风趣的话语逗乐你。基于 LLMs 的聊天机器人可以与你进行关于极其复杂话题的似乎智能的对话。它们可以执行复杂的指令,撰写文章或诗歌,甚至为你的在线辩论提供看似聪明的论点。
但是有一个小问题——LLMs 不具备逻辑、合理性,甚至不是智能。推理是人类智能和人工智能的基础。你可能听说过人们如何谈论 LLMs 能够通过真正困难的智力测试,比如智商测试或大学入学考试。但是 LLMs 只是在模仿。记住,LLMs 被训练用于各种标准化测试和考试中的几乎所有问答对。一个被训练了几乎整个互联网的机器可以通过仅仅混合它以前见过的单词序列来表现得很聪明。它可以重复出看起来很像对任何曾经在网上提出的问题的合理答案的单词模式。
提示
那么计算复杂度呢?在计算机科学课程中,你会将问答问题的复杂度估计为 (O(n²)),其中 n 是可能的问题和答案的数量 - 一个巨大的数字。变形金刚可以通过这种复杂性来学习隐藏的模式,以告诉它哪些答案是正确的。在机器学习中,识别和重用数据中的模式的能力被称为 泛化。泛化能力是智能的标志。但是 LLN 中的 AI 并不是对物理世界进行泛化,而是对自然语言文本进行泛化。LLN 只是在 “假装”,通过识别互联网上的单词模式来假装智能。我们在虚拟世界中使用单词的方式并不总是反映现实。
你可能会对与 ChatGPT 等 LLN 进行的对话的表现印象深刻。LLN 几乎可以自信并且似乎很聪明地回答任何问题。但是 似乎 并不总是如此。如果你问出了正确的问题,LLN 会陷入 幻觉 或者纯粹是胡言乱语。而且几乎不可能预测到它们能力的这些空白。这些问题在 2022 年 ChatGPT 推出时立即显现出来,并在随后由其他人尝试推出时继续存在。
为了看清楚事情的真相,测试 ChatGPT 背后的 LLN 的早期版本可能会有所帮助。不幸的是,你只能下载到 OpenAI 在 2019 年发布的 GPT-2,他们至今仍未发布 15 亿参数的完整模型,而是发布了一个拥有 7.75 亿参数的半尺寸模型。尽管如此,聪明的开源开发者仍然能够反向工程一个名为 OpenGPT-2 的模型。1(#_footnotedef_5 “查看脚注。”) 在下面,你将使用官方的 OpenAI 半尺寸版本,以便让你感受到无基础 LLN 的局限性。稍后我们将向您展示如何通过扩大规模和添加信息检索来真正改善事物。
>>> from transformers import pipeline, set_seed >>> generator = pipeline('text-generation', model='openai-gpt') >>> set_seed(0) # #1 >>> q = "There are 2 cows and 2 bulls, how many legs are there?" >>> responses = generator( ... f"Question: {q}\nAnswer: ", ... max_length=5, # #2 ... num_return_sequences=10) # #3 >>> answers = [] >>> for resp in responses: ... text = resp['generated_text'] ... answers.append(text[text.find('Answer: ')+9:]) >>> answers ['four', 'only', '2', 'one', '30', 'one', 'three', '1', 'no', '1']
当 ChatGPT 推出时,GPT-3 模型在常识推理方面并没有任何进展。随着模型规模和复杂性的扩大,它能够记忆越来越多的数学问题答案,但它并没有基于真实世界的经验进行泛化。即使发布了更新的版本,包括 GPT-3.5 和 GPT-4.0,通常也不会出现常识逻辑推理技能。当被要求回答关于现实世界的技术或推理问题时,LLN 往往会生成对于外行人来说看起来合理的胡言乱语,但是如果你仔细观察,就会发现其中存在明显的错误。而且它们很容易被越狱,强迫一个 LLN 说出(如毒性对话)LLN 设计者试图防止它们说出的话。2(#_footnotedef_6 “查看脚注。”)
有趣的是,推出后,模型在应对推出时遇到困难的问题时逐渐变得更好了。他们是怎么做到的?像许多基于 LLM 的聊天机器人一样,ChatGPT 使用 带有人类反馈的强化学习(RLHF)。这意味着人类反馈被用来逐渐调整模型权重,以提高 LLM 下一个词预测的准确性。对于 ChatGPT,通常有一个 喜欢按钮,你可以点击它,让它知道你对提示的答案感到满意。
如果你仔细想想,喜欢按钮会激励以这种方式训练的 LLM 鼓励用户点击喜欢按钮,通过生成受欢迎的词语。这类似于训练狗、鹦鹉甚至马匹,让它们知道你对它们的答案满意时,它们会表现出进行数学运算的样子。它们将在训练中找到与正确答案的相关性,并使用它来预测下一个词(或蹄子的跺地声)。就像对于马智能汉斯一样,ChatGPT 无法计数,也没有真正的数学能力。([7])这也是社交媒体公司用来制造炒作、把我们分成只听到我们想听到的声音的回音室的同样伎俩,以保持我们的参与,以便他们可以挟持我们的注意力并将其出售给广告商。([8])
OpenAI 选择以“受欢迎程度”(流行度)作为其大型语言模型的目标。这最大化了注册用户数和产品发布周围的炒作。这个机器学习目标函数非常有效地实现了他们的目标。OpenAI 的高管夸耀说,他们在推出后仅两个月就拥有了 1 亿用户。这些早期采用者用不可靠的自然语言文本涌入互联网。新手 LLM 用户甚至用虚构的参考文献创建新闻文章和法律文件,这些文献必须被精通技术的法官驳回。^([9])
想象一下,你的 LLM 将用于实时回答初中学生的问题。或者你可能想使用 LLM 回答健康问题。即使你只是在社交媒体上使用 LLM 来宣传你的公司。如果你需要它实时回应,而不需要持续由人类监控,你需要考虑如何防止它说出对你的业务、声誉或用户有害的话。你需要做的不仅仅是直接将用户连接到 LLM。
减少 LLM 毒性和推理错误有三种流行的方法:
- 扩展:使其更大(并希望更聪明)
- 防护栏:监控它以检测和防止它说坏话
- 接地:用真实世界事实的知识库增强 LLM。
- 检索:用搜索引擎增强 LLM,以检索用于生成响应的文本。
接下来的两个部分将解释扩展和防护栏方法的优点和限制。你将在第 n 章学习关于接地和检索的知识。
10.1.1 扩大规模
LLM 的一个吸引人之处在于,如果你想提高你的机器人能力,只需要添加数据和神经元就可以了。你不需要手工制作越来越复杂的对话树和规则。OpenAI 押注数十亿美元的赌注是,他们相信只要添加足够的数据和神经元,处理复杂对话和推理世界的能力就会相应增强。这是一个正确的押注。微软投资了超过十亿美元在 ChatGPT 对于复杂问题的合理回答能力上。
然而,许多研究人员质疑模型中的这种复杂性是否只是掩盖了 ChatGPT 推理中的缺陷。许多研究人员认为,增加数据集并不能创造更普遍智能的行为,只会产生更自信和更聪明的-听上去如此-文本。本书的作者并不是唯一一个持有这种观点的人。早在 2021 年,*在《关于随机鹦鹉的危险性:语言模型能太大吗?》*一文中,杰出的研究人员解释了 LLM 的理解表象是一种幻觉。他们因为质疑 OpenAI 的“喷洒祈祷”人工智能方法的伦理性和合理性而被辞退,这种方法完全依赖于更多的数据和神经网络容量能够创建出智能。
图 10.1 概述了过去三年中 LLM 大小和数量的快速增长的简要历史。
图 10.1 大型语言模型大小
为了对比这些模型的大小,具有万亿个可训练参数的模型的神经元之间的连接数量不到一个平均人脑的 1%。这就是为什么研究人员和大型组织一直在投资数百万美元的计算资源,以训练最大的语言模型所需的资源。
许多研究人员和他们的公司支持者都希望通过增加模型规模来实现类似人类的能力。而这些大型科技公司的研究人员在每个阶段都得到了回报。像 BLOOM 和 InstructGPT 这样的 100 亿参数模型展示了 LLM 理解和适当回答复杂指令的能力,例如从克林贡语到人类的情书创作。而万亿参数模型如 GPT-4 则可以进行一次学习,其中整个机器学习训练集都包含在一个单一的对话提示中。似乎,LLM 的每一次规模和成本的增加都为这些公司的老板和投资者创造了越来越大的回报。
模型容量(大小)每增加一个数量级,似乎就会解锁更多令人惊讶的能力。在 GPT-4 技术报告中,OpenAI 的研究人员解释了出现的令人惊讶的能力。这些是投入了大量时间和金钱的研究人员,他们认为规模(和注意力)就是你需要的全部,所以他们可能不是最佳的评估其模型新出现属性的人员。开发 PaLM 的 Google 研究人员也注意到了他们自己的缩放研究“发现”的所有新出现属性。令人惊讶的是,Google 的研究人员发现,他们测量到的大多数能力根本不是新出现的,而是这些能力线性地、次线性地或根本不扩展(flat)。在超过三分之一的智能和准确性基准测试中,研究人员发现,LLM 学习方法和随机机会相比并没有任何改善。
这里有一些代码和数据,你可以用它们来探索论文“大型语言模型的新能力”的结果。
>>> import pandas as pd >>> url = 'https://gitlab.com/tangibleai/nlpia2/-/raw/main/src/nlpia2' >>> url += '/data/llm/llm-emmergence-table-other-big-bench-tasks.csv' >>> df = pd.read_csv(url, index_col=0) >>> df.shape # #1 (211, 2) >>> df['Emergence'].value_counts() Emergence linear scaling 58 flat 45 # #2 PaLM 42 sublinear scaling 27 GPT-3/LaMDA 25 PaLM-62B 14 >>> scales = df['Emergence'].apply(lambda x: 'line' in x or 'flat' in x) >>> df[scales].sort_values('Task') # #3 Task Emergence 0 abstract narrative understanding linear scaling 1 abstraction and reasoning corpus flat 2 authorship verification flat 3 auto categorization linear scaling 4 bbq lite json linear scaling .. ... ... 125 web of lies flat 126 which wiki edit flat 127 winowhy flat 128 word problems on sets and graphs flat 129 yes no black white sublinear scaling [130 rows x 2 columns] # #4
代码片段给出了由 Google 研究人员编目的 130 个非新出现能力的字母采样。 "flat"标签意味着增加 LLM 的大小并没有显著增加 LLM 在这些任务上的准确性。你可以看到 35%(45/130
)的非新出现能力被标记为“flat”缩放。 "Sublinear scaling"意味着增加数据集大小和参数数量只会越来越少地增加 LLM 的准确性,对 LLM 大小的投资回报逐渐减少。对于被标记为缩放次线性的 27 个任务,如果你想达到人类水平的能力,你将需要改变你语言模型的架构。因此,提供这些数据的论文表明,目前基于变压器的语言模型在大部分最有趣的任务上根本不会缩放,这些任务是需要展示智能行为的。
Llama 2
你已经尝试过拥有 775 亿参数的 GPT-2 了。当你将规模扩大 10 倍时会发生什么呢?在我写这篇文章的时候,Llama 2、Vicuna 和 Falcon 是最新且性能最好的开源模型。Llama 2 有三种规模,分别是 70 亿、130 亿和 700 亿参数版本。最小的模型,Llama 2 7B,可能是你唯一能在合理时间内下载并运行的。
Llama 2 7B 模型文件需要 10 GB 的存储空间(和网络数据)来下载。一旦 Llama 2 权重在 RAM 中被解压缩,它很可能会在您的机器上使用 34 GB 或更多的内存。这段代码从 Hugging Face Hub 下载了模型权重,在我们的 5G 互联网连接上花了超过 5 分钟的时间。所以确保在第一次运行此代码时有其他事情可做。即使模型已经被下载并保存在您的环境中,加载模型到 RAM 中可能也需要一两分钟的时间。为了对您的提示生成响应,可能还需要几分钟,因为它需要对生成的序列中的每个标记进行 70 亿次乘法运算。
当使用在付费墙或商业许可证后面的模型时,您需要使用访问令牌或密钥进行身份验证,以证明您已接受其服务条款。在 Llama 2 的情况下,您需要“拥抱”扎克伯格及其 Meta 巨头,以便访问 Llama 2。
- 在 huggingface.co/join (
huggingface.co/join
) 创建一个 Hugging Face 帐户 - 使用相同的电子邮件申请在 ai.meta.com 上下载 Llama 的许可证 (
ai.meta.com/resources/models-and-libraries/llama-downloads/
) - 复制您的 Hugging Face(HF)访问令牌,该令牌位于您的用户配置文件页面上
- 创建一个包含您的 HF 访问令牌字符串的
.env
文件:echo "HF_TOKEN=hf_…" >> .env
- 使用
dotenv.load_dotenv()
函数将令牌加载到您的 Python 环境中 - 使用
os.environ
库将令牌加载到 Python 中的变量中。
这是代码中的最后两个步骤:
>>> import dotenv, os >>> dotenv.load_dotenv() >>> env = dict(os.environ) # #1 >>> auth_token = env['HF_TOKEN'] >>> auth_token # #2 'hf_...'
现在您已经准备好使用 Hugging Face 提供的令牌和 Meta 的祝福来下载庞大的 Llama 2 模型了。您可能想从最小的模型 Llama-2-7B 开始。即使它也需要 10 GB 的数据
>>> from transformers import LlamaForCausalLM, LlamaTokenizer >>> model_name = "meta-llama/Llama-2-7b-chat-hf" >>> tokenizer = LlamaTokenizer.from_pretrained( ... model_name, ... token=auth_token) # #1 >>> tokenizer LlamaTokenizer( name_or_path='meta-llama/Llama-2-7b-chat-hf', vocab_size=32000, special_tokens={'bos_token': AddedToken("<s>"...
注意,令牌化器只知道 32,000 个不同的标记(vocab_size
)。您可能还记得有关字节对编码(BPE)的讨论,这使得即使对于最复杂的大型语言模型,这种较小的词汇量也是可能的。如果您可以下载令牌化器,则您的 Hugging Face 帐户必须已成功连接到您的 Meta 软件许可证申请。
要尝试令牌化,请令牌化一个提示字符串,并查看令牌化器的输出。
>>> prompt = "Q: How do you know when you misunderstand the real world?\n" >>> prompt += "A: " # #1 >>> input_ids = tokenizer(prompt, return_tensors="pt").input_ids >>> input_ids # #2 tensor([[ 1, 660, 29901, ... 13, 29909, 29901, 29871]])
请注意,第一个令牌的 ID 是 “1”。当然,字母 Q 不是字典中的第一个令牌。这个令牌是用于 “” 语句起始令牌,标记器会自动在每个输入令牌序列的开头插入这个令牌。此外,请注意标记器创建了一个编码的提示批次,而不仅仅是一个单一的提示,即使您只想提出一个问题。这就是为什么输出中会看到一个二维张量,但您的批次中只有一个令牌序列用于您刚刚编码的一个提示。如果您愿意,您可以通过在一系列提示(字符串)上运行标记器,而不是单个字符串,来一次处理多个提示。
现在,您应该准备好下载实际的 Llama 2 模型了。
重要提示
我们的系统总共需要 34 GB 的内存才能将 Llama 2 加载到 RAM 中。当模型权重被解压缩时,Llama 2 至少需要 28 GB 的内存。您的操作系统和正在运行的应用程序可能还需要几个额外的千兆字节的内存。我们的 Linux 系统需要 6 GB 来运行多个应用程序,包括 Python。在加载大型模型时,请监控您的 RAM 使用情况,并取消任何导致您的计算机开始使用 SWAP 存储的进程。
LLaMa-2 模型需要 10 GB 的存储空间,因此从 Hugging Face 下载可能需要一段时间。下面的代码在运行 .from_pretrained()
方法时会下载、解压并加载模型权重。我们的 5G 网络连接花了超过 5 分钟。而且,即使模型已经下载并保存在本地缓存中,可能也需要一两分钟才能将模型权重加载到内存 (RAM) 中。
>>> llama = LlamaForCausalLM.from_pretrained( ... model_name, # #1 ... token=auth_token)
最后,您可以在提示字符串中向 Llama 提出哲学问题。生成提示的响应可能需要几分钟,因为生成的序列中的每个令牌都需要 70 亿次乘法运算。在典型的 CPU 上,这些乘法运算会花费一两秒的时间来生成每个令牌。根据您对哲学化大型语言模型的耐心程度,确保限制最大令牌数量在合理范围内。
>>> max_answer_length = len(input_ids[0]) + 30 >>> output_ids = llama.generate( ... input_ids, ... max_length=max_answer_length) # #1 >>> tokenizer.batch_decode(output_ids)[0] Q: How do you know when you misunderstand the real world? A: When you find yourself constantly disagreeing with people who have actually experienced the real world.
很好!看来 Llama 2 愿意承认它在现实世界中没有经验!
如果您想让用户体验更加有趣,可以一次生成一个令牌。即使生成所有令牌所需的时间不变,但这种方式可以让交互感觉更加生动。在每个令牌生成之前的那一刻停顿,几乎会让人着迷。当您运行以下代码时,请注意您的大脑是如何尝试预测下一个令牌的,就像 Llama 2 一样。
>>> prompt = "Q: How do you know when you misunderstand the real world?\nA:" >>> input_ids = tokenizer(prompt, return_tensors="pt").input_ids >>> input_ids >>> print(prompt, end='', flush=True) >>> while not prompt.endswith('</s>'): ... input_ids = tokenizer(prompt, return_tensors="pt").input_ids ... input_len = len(input_ids[0]) ... output_ids = llama.generate( ... input_ids, max_length=input_len + 1) ... ans_ids = output_ids[0][input_len:] ... output_str = tokenizer.batch_decode( ... output_ids, skip_special_tokens=False)[0] ... if output_str.strip().endswith('</s>'): ... break ... output_str = output_str[4:] # #1 ... tok = output_str[len(prompt):] ... print(tok, end='', flush=True) ... prompt = output_str
这种一次一个令牌的方法适用于生成型聊天机器人,可以让您看到如果允许大型语言模型发挥其冗长和详细的能力会有怎样的效果。在这种情况下,Llama 2 将模拟关于认识论的更长的问答对话。Llama 2 正在尽力继续我们在输入提示中使用 “Q:” 和 “A:” 触发的模式。
Q: How do you know when you misunderstand the real world? A: When you realize that your understanding of the real world is different from everyone else's. Q: How do you know when you're not understanding something? A: When you're not understanding something, you'll know it. Q: How do you know when you're misunderstanding something? A: When you're misunderstanding something, you'll know it. Q: How do you know when you're not getting it? A: When you're not getting it, you'll know it.
羊驼 2 常识推理和数学
您花费了大量时间和网络带宽来下载和运行一个规模化的 GPT 模型。问题是:它能否更好地解决您在本章开头向 GPT-2 提出的常识数学问题?
>>> q = "There are 2 cows and 2 bulls, how many legs are there?" >>> prompt = f"Question: {q}\nAnswer: " >>> input_ids = tokenizer(prompt, return_tensors="pt").input_ids >>> input_ids tensor([[ 1, 894, 29901, 1670, 526, 29871, 29906, 274, 1242, 322, 29871, 29906, 289, 913, 29879, 29892, 920, 1784, 21152, 526, 727, 29973, 13, 22550, 29901, 29871]])
一旦您拥有了 LLM 提示的令牌 ID 张量,您可以将其发送给 Llama,看看它认为您会喜欢跟随您的提示的令牌 ID。这似乎就像是一只羊驼在数牛腿,但实际上它只是试图预测您会喜欢的令牌 ID 序列。
>>> output_token_ids = llama.generate(input_ids, max_length=100) ... tokenizer.batch_decode(output_token_ids)[0] # #1
你能发现羊驼输出中的错误吗?
<s> Question: There are 2 cows and 2 bulls, how many legs are there? Answer: 16 legs. Explanation: * Each cow has 4 legs. * Each bull has 4 legs. So, in total, there are 4 + 4 = 8 legs.</s>
即使这次答案是正确的,但更大的模型自信地错误地解释了它的逻辑。它甚至似乎没有注意到它给出的答案与它在数学解释中使用的答案不同。LLM 对我们用数字表示的数量没有理解。它们不理解数字(或者话说,单词)的含义。LLM 将单词视为它试图预测的一系列离散对象。
想象一下,如果您想使用 LLM 来教数学,要检测和纠正 LLM 逻辑错误会有多困难。想象一下,这些错误可能会以何种隐秘的方式破坏您学生的理解。您可能甚至都不必想象,您可能在人们之间关于信息和逻辑的实际对话中看到过这种情况,这些信息和逻辑是从大型语言模型或由大型语言模型编写的文章中获得的。如果您直接使用 LLM 与用户推理,那么您正在对他们造成伤害并腐化社会。最好编写一个确定性基于规则的聊天机器人,该机器人具有有限数量的问题和教师故意设计的解释。您甚至可以从教师和教科书作者用于生成文字问题的过程中推广,以自动生成几乎无限数量的问题。Python hypothesis
包用于软件单元测试,MathActive
包用于简单的数学问题,您可以将其用作生成自己数学问题课程的模式。^([13])
每当你发现自己被越来越大的语言模型的合理性所愚弄时,记住这个例子。您可以通过运行 LLM 并查看令牌 ID 序列来提醒自己发生了什么。这可以帮助您想出示例提示,揭示 LLM 所训练的示例对话的瑞士奶酪中的漏洞。
自然语言处理实战第二版(MEAP)(五)(3)https://developer.aliyun.com/article/1519664