Press "Enter" to skip to content

BERT模型超酷炫,上手又太难?请查收这份BERT快速入门指南!

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

 

在过去的几年中,用于处理语言的机器学习模型取得了突飞猛进的进展。这些进展已经走出了实验室,开始为一些先进的数字产品赋能。最近成为 Google 搜索背后主要力量的 就是一个很好的例子。Google 认为这一进步(或者说在搜索中应用自然语言理解技术的进步)代表着「过去五年中最大的飞跃,也是搜索历史上最大的飞跃之一」。

 

本文是一篇简单的教程,介绍如何使用不同的 BERT 对句子进行分类。本文中的例子深入浅出,也足以展示 BERT 使用过程中所涉及的关键概念。

 

除了这篇博文,我还准备了一份对应的 notebook 代码,链接如下:

 

https://github.com/jalammar/jalammar.github.io/blob/master/notebooks/bert/A_Visual_Notebook_to_Using_BERT_for_the_First_Time.ipynb。

 

你也可以在 colab 中运行这份代码:

 

https://colab.research.google.com/github/jalammar/jalammar.github.io/blob/master/notebooks/bert/A_Visual_Notebook_to_Using_BERT_for_the_First_Time.ipynb

 

数据集: SST2

 

本文示例中使用的数据集为「SST2」,该数据集收集了一些影评中的句子,每个句子都有标注,好评被标注为 1,差评标注为 0。

 

 

sentence label
a stirring , funny and finally transporting re imagining of beauty and the beast and 1930s horror films 1
apparently reassembled from the cutting room floor of any given daytime soap 0
they presume their audience won’t sit still for a sociology lesson 0
this is a visually stunning rumination on love , memory , history and the war between art and commerce 1
jonathan parker ‘s bartleby should have been the be all end all of the modern office anomie films 1

 

: 句子情感分类

 

我们的目标是创建一个分类器,它的输入是一句话(即类似于数据集中的句子),并输出一个 1(表示这句话体现出了积极的情感)或是 0(表示这句话体现出了消极的情感)。整体的框架如下图所示:

 

 

实际上,整个模型由两个子模型组成:

 

DistilBERT 先对句子进行处理,并将它提取到的信息传给下个模型。DistilBERT 是 BERT 的缩小版,它是由 HuggingFace 的一个团队开发并开源的。它是一种更轻量级并且运行速度更快的模型,同时基本达到了 BERT 原有的性能。

 

另外一个模型,是 scikit learn 中的 Logistic 回归模型,它会接收 DistilBERT 处理后的结果,并将句子分类为积极或消极(0 或 1)。

 

我们在两个模型之间传递的数据是 768 维的向量。我们可以把这个向量看做能够用于分类任务的句子嵌入。

 

 

如果你读过我以前写的关于 Iluustrated BERT 的文章(https://jalammar.github.io/illustrated-bert/),这个向量其实就是以词 [CLS] 为输入的第一个位置上的结果。

 

模型训练

 

尽管整个模型包含了两个子模型,但是我们只需要训练 logistic 回归模型。至于 DistillBERT,我们会直接使用已经在英文上预训练好的模型。然而,这个模型不需要被训练,也不需要微调,就可以进行句子分类。BERT 训练时的一般目标让我们已经有一定的句子分类能力了,尤其是 BERT 对于第一个位置的输出(也就是与 [CLS] 对应的输出)。我觉得这主要得益于 BERT 的第二个训练目标——次句预测(Next sentence classification)。该目标似乎使得它第一个位置的输出封装了句子级的信息。Transformers 库给我们提供了 DistilBERT 的一种实现以及预训练好的模型。

 

 

教程概览

 

这篇教程的计划如下:我们首先用预训练好的 distilBERT 来生成 2,000 个句子的嵌入。

 

 

后面我们就不会再涉及 distilBERT 了。剩下的都是 Scikit Learn 的工作了,我们接下来要做的就是将数据集分成训练集和测试集。

 

 

将 distilBert(模型 #1)的输出划分为训练集和测试集后,就得到了我们训练和评估 logistic 回归(模型 #2)的数据集了。请注意,在现实中,sklearn 在将数据划分为训练集和测试集之前,会将样本打乱,而不是直接按照数据原来的顺序取前 75%。

 

接下来,我们要在训练集上训练逻辑logistic 回归模型了:

 

 

每个预测值是怎幺计算出来的?

 

在我们深入研究训练模型的代码前,让我们先看一下训练好的模型是如何计算出其预测值的。假设我们正在对句子「a visually stunning rumination on love」进行分类。第一步要做的就是用 BERT 分词器(tokenizer)将这些单词(word)划分成词(token)。然后,我们再加入句子分类所需的特殊词(在句子开始加入 [CLS],末端加入 [SEP])。

 

 

分词器要做的第三步就是查表,将这些词(token)替换为嵌入表中对应的编号,我们可以从预训练模型中得到这张嵌入表。如果对词嵌入不太了解,请参阅「The Illustrated Word2vec」:

 

https://jalammar.github.io/illustrated-word2vec/。

 

 

注意,只需要一行代码就可以完成分词器的所有工作:

 

tokenizer.encode("a visually stunning rumination on love", add_special_tokens=True)

 

现在,我们输入的句子是可以传递给 DistilBERT 的形式。

 

如果你读过「Illustrated BERT」(https://jalammar.github.io/illustrated-bert/),这一步也可以被可视化为下图:

 

 

DistilBERT 的数据流

 

将输入向量传递给 DistilBert 之后的工作方式就跟在 BERT 中一样,每个输入的词都会得到一个由 768 个浮点数组成的输出向量。

 

 

由于这是个句子分类任务,我们只关心第一个向量(与 [CLS] 对应的向量)。

 

该向量就是我们输入给 logistic 回归模型的向量。

 

 

从这一步开始,logistic 回归模型的任务就是:根据它在训练阶段学到的经验,对这个向量进行分类。我们可以把整个预测过程想象成这样:

 

 

我们将在下一节中讨论整个训练过程及其相关代码。

 

代码

 

在本节中,我们重点介绍训练这个句子分类模型的代码。读者可以从 colab 和 github 上获取完成本任务所需的所有 notebook 代码(链接见本文第二段)。

 

首先,我们需要导入相关的程序包。

 

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

 

数据集的链接如下:https://github.com/clairett/pytorch-sentiment-classification/。我们可以直接将其导入为一个 pandas 数据帧。

 

df = pd.read_csv('https://github.com/clairett/pytorch-sentiment-classification/raw/master/data/SST2/train.tsv', delimiter='\t', header=None)

 

我们可以通过「df.head()」查看前五行数据帧的内容:

 

df.head()

 

输出如下:

 

 

导入预训练好的 DistilBERT 模型与分词器

 

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/tokenizertokenizer = tokenizer_class.from_pretrained(pretrained_weights)
model = model_class.from_pretrained(pretrained_weights)

 

现在,我们可以对数据集进行分词操作了。请注意,我们这里要做的事情跟上文中的示例不太一样。上文中的例子只对一个句子进行了分词和处理。在这里,我们要将所有句子作为同一批进行分词和处理。在 notebook 中,考虑到算力的问题,我们只用了 2000 个句子。

 

分词

 

tokenized = df[0].apply((*lambda* x: tokenizer.encode(x, add_special_tokens=True)))
T

 

上面的代码将所有句子转化为了编号的列表。

 

 

现在,数据集变成了一个包含很多列表结构(或是 Pandas 的数组和数据帧)的列表。在 DistilBERT 可以将它们作为输入数据处理之前,我们需要把这些向量整理成相同的维度(在较短的句子后面填充上编号「0」)。你可以在 notebook 中看到「填充」操作对应的代码,它们就是 python 字符串和数组的简单操作。

 

完成「填充」操作后,我们就得到了 BERT 可以接收的矩阵/张量了。

 

 

DistilBERT 的处理

 

现在,我们根据填充后的词(token)矩阵创建一个输入张量,并将其传递给 DistilBERT

 

input_ids = torch.tensor(np.array(padded))
<strong>with</strong> torch.no_grad():
last_hidden_states = model(input_ids)

 

这一步完成后,会将 DistilBERT 的输出赋给「last_hidden_states」。这是一个维度为(句子数,序列中的最大词数,DistilBERT 模型中的隐藏层数)的元组。在本例中,这个维度就是(2000,66,768),因为我们有 2000 个句子,2000 个句子中最长的序列长度为 66,DistilBERT 模型中有 768 个隐藏层。

 

 

展开 BERT 的输出张量

 

接下来我们要把这个三维的输出张量展开。我们可以先分析一下它的维度:

 

 

回顾这些句子的「一生」

 

每一行代表数据集中的一个句子。第一个句子的处理过程如下图所示:

 

 

切片得到重要的部分

 

对于句子分类任务来说,我们只对 BERT 得到的 [CLS] 对应的输出感兴趣,所以我们只保留「立方体」中 [CLS] 对应的切片,删掉了其它内容。

 

 

这就是从三维张量中获取我们需要的二维张量的方法:

 

 # Slice the output for the first position for all the sequences, take all hidden unit outputs
 features = last_hidden_states[0][:,0,:].numpy()

 

现在,「features」是一个二维的 numpy 数组,它包含了我们数据集中所有句子的嵌入。

 

 

我们从 BERT 输出中切片得到的张量。

 

Logistic 回归中的数据集

 

现在我们已经得到了 BERT 的输出,并且把训练 logistic 回归模型的数据集组装好了。这 768 列是特征,我们还给出了原始数据集中的标签。

 

 

我们用于训练 logistic 回归的有标签数据集。这些特征就是 BERT 中对应 [CLS](位置 #0)的部分,也就是我们前一张图中切片得到的部分。每一行对应着数据集中的一个句子,每一列对应着 BERT/DistilBERT 模型中顶层 transformer 模块中前馈神经网络的隐藏单元。

 

在完成了机器学习传统的训练集/测试集划分操作后,我们就可以创建我们自己的 logistic 回归模型,并用我们的数据集进行训练了。

 

labels = df[1]
train_features, test_features, train_labels, test_labels = train_test_split(features, labels)

 

上面的代码把数据集分成了训练集和测试集:

 

 

接下来,我们要在训练集上训练 logistic 回归模型。

 

lr_clf = LogisticRegression()
lr_clf.fit(train_features, train_labels)

 

现在模型已经训练好了,我们可以用测试集对其进行评估。

 

lr_clf.score(test_features, test_labels)

 

上面一段代码的输出告诉我们这个模型获得了 81% 的准确率。

 

评分标准

 

作为参照,在这个数据集上所能达到的最高的准确率是 96.8。在这个任务中,我们同样可以训练 DistilBERT 来提高其得分,也就是通常所说的调优过程,该过程可以更新 BERT 的权重参数,并让其在句子分类任务中(通常被称为「下游任务」)获得更好的性能。经过调优后的 DistilBERT 可以达到 90.7 的准确率,而完整的 BERT 可以达到 94.9。

 

Notebook 代码

 

本文开头给出了相关 notebook 代码的链接,自己去探索一下吧!

 

这就是本文的全部内容了,这些内容对于第一次接触 BERT 的人来说应该是非常适用的。而下一步就是查看文档并试着进行调优了。你也可以回过头来把代码中的 DistilBERT 转换成 BERT,看看它又是如何工作的。

Be First to Comment

发表评论

邮箱地址不会被公开。 必填项已用*标注