初次使用BERT的可视化指南

初次使用BERT的可视化指南

在这里插入图片描述
在过去几年里,处理语言的机器学习模型的进展一直在迅速加快。这一进步已经离开了研究实验室,开始为一些领先的数字产品提供动力。这方面的一个很好的例子是最近公布的BERT模型如何成为谷歌搜索背后的主要力量。谷歌认为,这一步(即自然语言理解在搜索领域的应用进展)代表了过去五年最大的飞跃,也是搜索历史上最大的飞跃之一。

这篇文章是关于如何使用BERT的变体对句子进行分类的简单教程。作为第一个介绍,这是一个足够基本的示例,但也足够高级,可以展示所涉及的一些关键概念。

数据集:SST2
在本例中,我们将使用的数据集是SST2,其中包含电影评论中的句子,每个句子都标记为正样本(值为1)或负样本(值为0):
在这里插入图片描述
模型:情感分类
我们的目标是创建一个模型,该模型接受一个句子(就像我们的数据集中的那些句子一样),并生成1(表示句子带有积极情绪)或0(表示句子带有消极情绪)。我们可以把它想象成这样:
在这里插入图片描述
实际上,该模型是由两个模型组成的。

  • DistilBERT 处理这个句子,并将从中提取的一些信息传递给下一个模型。DistilBERT 是BERT的一个小版本,由HuggingFace的团队开发和开源。它是伯特的一个更轻、更快的版本,与它的性能大致相当。
  • 下一个模型是来自scikit learn的基本逻辑回归模型,它将接受DistilBERT处理的结果,并将句子分为正或负(分别为1和0)。

我们在两个模型之间传递的数据是一个大小为768的向量。我们可以把这个向量看作是我们可以用来分类的句子的嵌入。
在这里插入图片描述
训练模型

虽然我们将使用两个模型,但我们只训练逻辑回归模型。对于 DistillBERT,我们将使用一个已经过预先训练过并掌握了英语的模型。然而,这个模型既没有经过训练,也没有经过 finetune 来进行句子分类。然而,从 BERT 通用目标的训练中,我们得到了一些句子分类的能力。对于第一个位置(与[CLS] token 相关联)的 BERT 输出尤其如此。我认为这是由于 BERT 的第二个训练目标 — 下一个句子的分类。这个目标似乎是训练模型将句子的意义压缩到了第一个位置的输出中。transformer库为我们提供了 DistilBERT 的实现以及模型的预训练版本。
在这里插入图片描述
教程概述
这就是本教程的策略。我们将首先使用训练好的distilBERT来生成2000个句子的嵌入。
在这里插入图片描述
在这一步之后,我们将不再接触distilBERT。这些都是我从这里学到的。我们做通常的训练/测试划分这个数据集:
在这里插入图片描述
然后在训练集上训练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:
在这里插入图片描述

max_len = 0
for i in tokenized.values:
    if len(i) > max_len:
        max_len = len(i)
print(max_len)
padded = np.array([i + [0]*(max_len-len(i)) for i in tokenized.values])
attention_mask = np.where(padded != 0, 1, 0)
print(attention_mask.shape)

使用 DistilBERT 处理
现在,我们从填充后的 token 矩阵中创建了一个输入张量,并将其传递给 DistilBERT。

input_ids = torch.tensor(padded).long()
attention_mask = torch.tensor(attention_mask).long()

with torch.no_grad():
    last_hidden_states = model(input_ids, attention_mask=attention_mask)

运行此步骤后,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 后的DistilBERT达到了90.7的准确率分数。全尺寸的 BERT 模型可以达到94.9。

原文:https://jalammar.github.io/a-visual-guide-to-using-bert-for-the-first-time/
作者:Jay Alammar

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章