点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”
作者:Jay Alammar
编译:ronghuaiyang
导读
用可视化的方式演示了如何用Bert来做一个句子分类的应用,每一步都有非常详细的图解,特别的清楚。
在过去几年里,处理语言的机器学习模型的进展一直在迅速加快。这一进步已经离开了研究实验室,开始为一些领先的数字产品提供动力。这方面的一个很好的例子是最近关于 BERT 模型如何成为谷歌搜索背后的主要力量的公告。谷歌认为,这一步(或在搜索中应用自然语言理解的进步)代表了“过去五年最大的飞跃,也是搜索历史上最大的飞跃之一”。
这篇文章是关于如何使用 BERT 的变体对句子进行分类的简单教程。作为第一个介绍,这是一个足够基本的示例,但也足够高级,可以展示所涉及的一些关键概念。
数据集: SST2
在本例中,我们将使用的数据集是SST2,其中包含电影评论中的句子,每个句子都标记为正样本(值为 1)或负样本(值为 0):
模型: 句子情感分类
我们的目标是创建一个模型,该模型接受一个句子(就像我们的数据集中的那些句子一样),并生成 1(表示句子带有积极情绪)或 0(表示句子带有消极情绪)。我们可以这样想:
实际上,该模型是由两个模型组成的。
DistilBERT处理这个句子,并将从中提取的一些信息传递给下一个模型。DistilBERT 是 BERT 的小版本,由HuggingFace的团队开发并开源。它是 BERT 的一个更轻、更快的版本,与它的性能大致相当。
下一个模型是来自 scikit-learn 的一个基本逻辑回归模型,它将接受 DistilBERT 处理的结果,并将句子分为正样本或负样本(分别为 1 和 0)。
我们在两个模型之间传递的数据是一个大小为 768 维的向量。我们可以把这个向量看作是我们可以用来分类的句子的嵌入。
模型训练
虽然我们将使用两个模型,但我们只训练逻辑回归模型。对于 DistillBERT,我们将使用一个已经过预先训练过并掌握了英语的模型。然而,这个模型既没有经过训练,也没有经过 finetune 来进行句子分类。然而,从 BERT 通用目标的训练中,我们得到了一些句子分类的能力。对于第一个位置(与[CLS] token 相关联)的 BERT 输出尤其如此。我认为这是由于 BERT 的第二个训练目标 — 下一个句子的分类。这个目标似乎是训练模型将句子的意义压缩到了第一个位置的输出中。transformer库为我们提供了 DistilBERT 的实现以及模型的预训练版本。
教程概要
这就是本教程的策略。我们将首先使用训练好的 distilBERT 来生成 2000 个句子的嵌入。
在这一步之后,我们将不再接触 distilBERT。这些都是我从这里学到的。我们对这个数据集做通常的训练/测试划分:
为distilBert(模型#1)的输出进行训练集/测试集分割,创建我们在(模型#2)上训练和评估逻辑回归的数据集。注意,在现实中,sklearn的train/test split在进行分割之前会对样本进行打乱,它并不只取数据集中出现的前75%的样本。
然后在训练集上训练 logistic 回归模型:
如何计算单个预测
在深入研究代码并解释如何训练模型之前,让我们先看看训练后的模型如何计算其预测。
让我们试着把这句话“a visually stunning rumination on love”分类。第一步是使用 BERT tokenizer 将单词首先分割成 tokens。然后,我们添加句子分类所需的特殊 tokens(在第一个位置是[CLS],在句子的末尾是[SEP])。
tokenizer 做的第三步是用嵌入表中的 id 替换每个 token,嵌入表是我们从训练模型中得到的一个组件。
注意,tokenizer 在一行代码中完成所有这些步骤:
tokenizer.encode("a visually stunning rumination on love", add_special_tokens=True)
我们的输入语句现在是传递给 DistilBERT 的正确形状。
这一步也可以用以下方式可视化:
DistilBERT 的数据流
通过 DistilBERT 传递输入向量的工作方式与 BERT 一样。输出将是每个输入 token 的向量。每个向量由 768 个数字(浮点数)组成。
因为这是一个句子分类任务,所以除了第一个向量(与[CLS]token 相关联的向量)外,我们忽略了所有其他向量。我们传递的这个向量作为逻辑回归模型的输入。
从这里开始,逻辑回归模型的工作就是根据它从训练阶段学到的知识对这个向量进行分类。我们可以把预测计算想象成这样:
我们将在下一节中讨论训练以及整个过程的代码。
代码
在本节中,我们将重点介绍训练这个句子分类模型的代码。
让我们从 importing 工具开始。
import numpy as np
import pandas as pd
import torch
import transformers as ppb # pytorch transformers
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
数据集可以从 github 上得到,因此我们只需将其直接导入到 pandas dataframe 中。
df = pd.read_csv('https://github.com/clairett/pytorch-sentiment-classification/raw/master/data/SST2/train.tsv', delimiter='\t', header=None)
我们可以使用 df.head()查看 dataframe 的前五行,看看数据是什么样的。
df.head()
输出:
导入预训练的 DistilBERT 模型和 tokenizer
model_class, tokenizer_class, pretrained_weights = (ppb.DistilBertModel, ppb.DistilBertTokenizer, 'distilbert-base-uncased')
## Want BERT instead of distilBERT? Uncomment the following line:
#model_class, tokenizer_class, pretrained_weights = (ppb.BertModel, ppb.BertTokenizer, 'bert-base-uncased')
# Load pretrained model/tokenizer
tokenizer = tokenizer_class.from_pretrained(pretrained_weights)
model = model_class.from_pretrained(pretrained_weights)
我们现在可以 tokenize 数据集了。注意,这里我们要做的事情与上面的示例稍有不同。上面的例子只处理了一个句子。在这里,我们将使用批处理的方式 tokenize 和处理所有的句子(仅为了资源考虑,notebook 将处理更小的一组示例,比如 2000 个示例)。
Tokenization
tokenized = df[0].apply((lambda x: tokenizer.encode(x, add_special_tokens=True)))
这样就把每个句子都转换成了 id 列表。
数据集当前是列表(或 panda 的 Series/DataFrame)的列表。在 DistilBERT 将其作为输入处理之前,我们需要使用 token id 0 填充更短的句子,从而使所有向量具有相同的大小。
填充之后,我们有了一个矩阵/张量,准备传给 BERT:
使用 DistilBERT 处理
现在,我们从填充后的 token 矩阵中创建了一个输入张量,并将其发送给 DistilBERT。
input_ids = torch.tensor(np.array(padded))
with torch.no_grad():
last_hidden_states = model(input_ids)
运行此步骤后,last_hidden_states
保存 DistilBERT 的输出。在我们的例子中,这是个形状为(2000,66,768)的 tuple。2000(因为我们只局限于 2000 个例子),66(这是 2000 个例子中最长序列中的标记数),768(在 DistilBERT 模型中隐藏单元的数量)。
展开 BERT 输出张量
我们来展开这个三维输出张量。我们可以先从它的维度开始:
对句子做处理的历程
输入的每一行都与数据集中的一个句子相关联。对第一句话处理路径,我们可以把它想象成这样:
对重要的部分切片
对于句子分类,我们只对 BERT 的[CLS] token 的输出感兴趣,所以我们选择立方体的那一部分并放弃其他部分。
这就是我们切片三维张量得到我们感兴趣的二维张量的方法:
# Slice the output for the first position for all the sequences, take all hidden unit outputs
features = last_hidden_states[0][:,0,:].numpy()
现在features
是一个 2d numpy 数组,其中包含数据集中所有句子的嵌入。
逻辑回归的数据集
现在我们已经有了 BERT 的输出,我们已经组装了训练逻辑回归模型所需的数据集。768 列是特征,并且我们从初始数据集中获得了标签。
我们用来训练逻辑回归的数据集。这些特征是我们在前面的图中分割的[CLS]token(位置#0)的BERT的输出向量。每一行对应数据集中的一个句子,每一列对应Bert/DistilBERT模型顶层transformer block的前馈神经网络的一个隐藏单元的输出。在完成传统的机器学习训练集/测试集分割之后,我们可以构建逻辑回归模型并针对数据集进行训练。
labels = df[1]
train_features, test_features, train_labels, test_labels = train_test_split(features, labels)
将数据集分割成训练/测试集:
接下来,在训练集上训练逻辑回归模型。
lr_clf = LogisticRegression()
lr_clf.fit(train_features, train_labels)
现在模型已经训练好了,我们可以根据测试集对它进行评分:
lr_clf.score(test_features, test_labels)
结果表明,该模型的准确率达 81%左右。
分数基准
作为参考,这个数据集的最高准确率分数是96.8。DistilBERT 可以通过训练来提高它在这个任务中的分数 —— 这个过程称为 finetune,它更新 BERT 的权重,使它在句子分类中获得更好的性能(我们可以称之为“下游任务”)。finetune 后的馏分达到了90.7的准确率分数。全尺寸的 BERT 模型可以达到94.9。
—END—
英文原文:https://jalammar.github.io/a-visual-guide-to-using-bert-for-the-first-time/
请长按或扫描二维码关注本公众号
喜欢的话,请给我个好看吧!