BERT简介

毫不夸张的讲,谷歌AI实验室的BERT深刻影响了NLP的格局。

想象一下这样一个在大量未标注数据集中训练的模型,你仅仅只需要做一点的微调,就可以在11个不同的NLP任务上取得 SOTA结果。没错,BERT就是这样,它彻底改变了我们设计NLP模型的方式。
BERT之后,许多NLP架构、训练方法与语言模型如雨后春笋般涌现,比如谷歌的TransformerXL、OpenAI’s GPT-2、 XLNet、ERNIE2.0、 RoBERTa等。
注:在这篇文章中,我会提及许多Transformer的内容,如果你对Transformer不了解的话,可以先看看这篇文章——How do Transformers Work in NLP? A Guide to the Latest State-of-the-Art Models.

什么是BERT?

你一定听说过BERT,也知道了它是多么不可思议以致改变了NLP的领域格局,但BERT究竟是什么?
以下是BERT团队对该框架的描述:
BERT全称Bidirectional Encoder Representations from Transformers(Transformers的双向编码表示),对未标注的文本,通过上下文约束预训练深层双向表示。训练完成后,只需要对BERT预训练模型进行fine-tune,再加上针对特定任务的输出层就可以取得SOTA结果。
对新人来说这样的解释不够明白,但这确实很好的总结了BERT的机制。接下来,我们一点点的进行剖析。
首先可以明确的是,BERT全称Bidirectional Encoder Representations from Transformers,名字中的每一个单词都有其意义,我们会在后面的段落一一介绍。从BERT的名字中,我们能得到最重要信息就是:BERT是基于Transformer架构的。
其次,BERT是在大量的未标注文本上预训练得到,包括整个Wikipedia(有25亿单词)和图书语料库(8亿单词)。
预训练这一步对BERT来讲是至关重要的。正是由于如此庞大的语料库的支撑,模型在训练过程中才能对语言的工作原理进行更深入、更准确的提取,通过此过程提取到的知识对所有NLP任务来说都是‘万滑油’。
然后,BERT是“深度双向”模型,双向就意味着BERT在训练过程中关注当前位置的上下文信息。

上下文信息对准确理解语义很重要的。看下面这个例子,两句话中都包含了同一个单词“bank”:

BETR捕获上下文信息

如果我们想仅依靠上文或者下文的信息去理解“bank”的含义,那么对这两句话中的“bank”,我们是无法区分它们的不同含义的。
解决方法就是在预测之前同时考虑上下文信息,BERT就是这样做的。
最后,BERT最吸引人的在于,我们仅仅通过在模型后根据自己的需求加上输出层部分就可以在各类NLP任务取得SOTA结果。


Word2Vec和GloVe

预训练模型从大量未标注文本数据中学习语言表示的思想来源于词嵌入,如Word2Vec and GloVe。

词嵌入改变了进行NLP任务的方式。通过嵌入,我们能够捕获单词的上下文关系。

图中所示的这些嵌入方法被广泛用于下游NLP任务的训练模型,以便获取较好的预测结果。
之前的嵌入方法的一大缺陷在于只使用了很浅的语言模型,那就意味着它们捕获到的信息是有限的。
另外一个缺陷就是这些嵌入模型没有考虑单词的上下文。就像之前提到的“bank”例子,在不同的语境下同一个单词可能会有不同的含义。
然而,WordVec之类的模型将不同语境中的“bank”以同样的向量表示。
于是,一些重要的信息被遗漏了。

ELMo与ULMFiT

ELMo是对语言多义性问题提出的解决方案——针对那些在不同上下文中具有不同含义的单词。
从训练浅层前馈网络(Word2vec)开始,我们逐步过渡到使用复杂的双向LSTM结构来训练词嵌入。
这意味着同一单词根据其所在的上下文可以具有多个ELMO嵌入。

从那时起,我们开始注意到预训练的优势将使其在NLP任务中发挥重要作用。

ULMFiT更进一步,在文档分类任务中,即使只有很少的数据(少于100),对该框架训练的语言模型进行微调就能够提供出色的结果。这意味着ULMFiT解决了NLP任务中的迁移学习问题。
这是我们提出的NLP迁移学习黄金公式:
NLP迁移学习 = 预训练 + 微调
 在ULMFIT之后,许多NLP任务根据上述公式进行训练,并获得了新的基准。

 

OpenAI的GPT

OpenAI’s GPT进一步扩展了ULMFiT和ELMo中引入的pre-training和fine-tuning方法。
GPT的关键是用基于Transformer的结构取代了基于LSTM的语言建模结构。
不仅是文档分类任务,GPT模型还可以对其他NLP任务进行
fine-tuned,例如常识推理,语义相似性和阅读理解。
OpenAI的GPT在多项任务获得SOTA结果,验证了Transformer架构的鲁棒性和有效性。  
就这样,BERT在Transformer的基础上横空出世,并给NLP领域带来巨大变革。

BERT出世

至此,解决NLP任务离不开这两步:
1. 在未标注的大型文本语料库上训练语言模型(无监督或半监督)
2. 针对特定的NLP任务对大型语言模型进行微调,以充分利用预训练模型的大量知识(监督)
接下来,我们将详细了解BERT如何训练模型,并在未来一段时间内成为NLP领域的行业标杆。
BERT是如何工作的?干货讲解
深入BERT,理解为什么BERT建立的语言模型如此有效。
1. BERT的结构
BERT架构建立在Transformer之上。 我们目前有两个可用的变体:
BERT Base:12层(transformer模块),12层注意力,1.1亿参数
BERT Large:24层(transformer模块),16层注意力,3.4亿参数

与OpenAI的GPT模型相比,BERT Base模型大小与其相似,同时BERT Base的所有transformer层都仅包括编码部分。
如果你对transformer结构了解不是很清楚,建议你先读一下这篇文章。

现在我们已经了解了BERT的整体架构。在正式构建模型之前,需要先进行一些文本处理工作。

2. 文本预处理
BERT背后的开发人员添加了一组特定的规则来表示模型的输入文本。其中许多都是创造性的设计选择可以让模型表现更好。
首先,每个输入嵌入都是三个嵌入的组合:
1.位置嵌入:BERT学习并使用位置嵌入来表达单词在句子中的位置。添加该嵌入是为了克服Transformer的局限性,与RNN不同,Transformer无法捕获“序列”或“顺序”信息
2.段嵌入:BERT也可以将句子作为任务的输入(问题-解答)。因此,它为第一句话和第二句话学习了独特的嵌入,以帮助模型区分它们。在上面的示例中,所有为EA的标记都属于句子A(对于EB一样)
3.令牌嵌入:这些是从WordPiece令牌词汇表中为特定令牌学习的嵌入
对于给定的令牌,其输入表示形式是通过将相应的令牌,段和位置嵌入相加而构造的。
这种全面的嵌入方案包含许多有用的模型信息。
这些预处理步骤的组合使BERT如此通用。
3. 预训练任务
BERT对两项NLP任务进行预训练:
遮掩语言模型
下句预测
让我们更详细地理解这两个任务!
a. 遮掩语言模型(双向)

BERT是深层的双向模型,该网络从第一层到最后一层始终关注当前单词的上下文进行信息捕获。

单词序列预测

传统的语言模型要么是利用从右到左的文本信息进行训练预测下一个单词(例如GPT),要么是使用从左到右的文本信息进行训练,这使模型不可避免的丢失一些信息,从而导致错误。

ELMo试图通过训练两个LSTM语言模型(一个利用从左到右的文本信息,一个利用从右到左的文本信息),并将它们进行连接来解决这个问题。这样虽然在一定程度上取得进步,但还远远不够。

相对于GPT与ELMo,BERT在利用上下文信息这一方面取得重要突破,如上图所示。
图中的箭头表示一层到下一层的信息流,顶部的绿色框表示每个输入单词的最终表示。
从以上图片可以明显看到:BERT是双向的,GPT是单向的(从左到右的信息流),ELMo是浅层双向的。
关于遮掩语言模型——这是BERT双向编码的奥秘。
对这样一个句子——“I love to read data science blogs on Analytics Vidhya”,我们怎样用它训练双向语言模型呢。
我们首先将“Analytics”替换为“[MASK]”, “[MASK]”  表示将该位置的单词掩盖。
然后我们需要训练一个模型,使其能够预测mask掉的单词:“I love to read data science blogs on [MASK] Vidhya.”
这就是遮掩语言模型的关键。BERT的作者还介绍了一些遮掩语言模型的注意事项:
为了防止模型过于关注特定位置或被遮掩的标记,研究人员随机遮掩15%的单词
被遮掩的单词并不总是[MASK]取代,在针对特定任务的微调阶段是不需要[MASK]标记的
为此,研究人员的一般做法是:(对 15%需要[MASK] 单词 )
(15%的)80%的单词被[MASK]遮掩
其余10%的单词被其他随机单词取代
其余10%的单词保持不变
在我之前的一篇文章中,我详细介绍了如何在Python中实现遮掩语言模型:Introduction to PyTorch-Transformers: An Incredible Library for State-of-the-Art NLP (with Python code)
b. 下句预测
遮掩语言模型(MLMs)学习单词之间的关系。
此外,BERT还对下句预测任务进行训练以学习句子之间的关系。
这类任务的典型例子就是问答系统。
任务很简单,给A和B两个句子,判断B是A之后的下一句,或只是一个随机句子?
由于这是一个二分类问题,将语料库中的句子分解为句子对就可以得到大量训练数据。与MLMs类似,作者也给出在进行下句预测任务时的注意事项。具体通过这个例子进行说明:
对于一个包含10万句子的数据集,我们可以得到5万句子对作训练数据。
训练数据中的50%,第二句是真实的下句
另外的50%,第二句是语料库中的随机句子
前50%的标签是‘IsNext’,后50%的标签是‘NotNext’
在建模过程中结合遮掩语言模型(MLMs)和下句预测(NSP)两个预训练任务,这就使得BERT成为一个与任务无关的模型,经过简单fine-tuning即可适用到其他下游任务。
在python中使用BERT进行文本分类
你对BERT的可能性一定有各种期待。确实如此,我们在具体的NLP应用中可以通过各种方式利用BERT预训练模型的优势。
最有效的方法之一就是根据自己的任务和特定数据进行微调, 然后,我们可以将BERT中的嵌入用作文本文档的嵌入。
接下来,我们将学习如何将BERT的嵌入应用到自己的任务上。至于如何对整个BERT模型进行微调,我会在另一篇文章中进行介绍。

为了提取BERT的嵌入,我们将使用一个非常实用的开源项目Bert-as-Service:

BERT-As-Service

由于BERT需要大量代码以及许多安装包的支持,对普通用户而言直接运行BERT是不现实的,为此开源项目BERT-As-Service来帮助我们便捷的使用BERT。通过该项目,我们仅仅通过两行代码就可以调用BRRT对句子进行编码。
安装BERT-As-Service
BERT-As-Service运行方式十分简单。它创建了一个BERT服务器,我们可以在notebook中编写ython代码进行访问。通过该方式,我们只需将句子以列表形式发送,服务器就会返回这些句子的BERT嵌入。
我们可以通过pip安装服务器和客户端。它们可以单独安装在本地计算机,也可以安装到不同的计算机上:

$ pip install bert-serving-server # server 
$ pip install bert-serving-client # client

另外,由于运行BERT对GPU要求较高,我建议你在云GPU平台或是其他具有高计算能力的计算机上安装bert-serving-server。
同时,bert-serving-server对python和Tensorflow的版本要求为:Python >= 3.5 ; TensorFlow >= 1.10。
然后,在终端下载图示的预训练模型(选择你需要的即可),并对下载的zip文件进行解压。

下图是已发布的BERT预训练模型:

BERT模型

我这里选择BERT Uncased下载并解压:

$ wget https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip && unzip uncased_L-12_H-768_A-12.zip

将所有文件解压之后,就可以启动BERT服务了:

$ bert-serving-start -model_dir uncased_L-12_H-768_A-12/ -num_worker=2 -max_seq_len 50

现在,你可以在Python中通过bert-serving-client调用BERT-As-Service。看代码吧!
打开一个新的Jupyter Notebook,我们想要获取“I love data science and analytics vidhya”的BERT嵌入。

from bert_serving.client import BertClient

# 使用IP地址连接BERT服务器; 如果是本机服务器的话不需要IP
bc = BertClient(ip=”服务器的IP地址”)    
# 获取嵌入  
embedding = bc.encode([“I love data science and analytics vidhya.”])    
# 对返回的嵌入形状进行确认,应该是1×768    
print(embedding.shape)
IP地址是BERT服务器或云平台的IP; 如果是本机服务器的话不需要填写IP
由于该句被BERT架构中的768个隐藏单元表示,最终返回的嵌入形状是(1,768)。
问题描述:对Twitter上仇恨言论进行分类
接下来使用真实数据集测试BERT的效果。我们将使用Twitter的“仇恨言论”分类数据集,该数据集中的推文被标注为是或者否。
你可以从此链接problem statement on the DataHack platform了解或下载该数据集。
为简单起见,如果一条推文带有种族主义或性别歧视情绪,我们就认为该推文包含仇恨言论。 于是,本次任务就是将种族主义或性别歧视推文与其他推文进行分类。

我们将使用BERT对数据集中的每条推文进行嵌入,然后使用这些嵌入训练文本分类模型。

任务流程

接下来看代码部分:

import pandas as pd    
import numpy as np
    
# 导入训练数据   
train = pd.read_csv('BERT_proj/train_E6oV3lV.csv', encoding='iso-8859-1')    
train.shape

相信你对推特并不陌生,在很多推文中总有一些随机符号和数字(又名聊天语言!)。我们的数据集也是这样,为此,需要对数据集进行预处理,然后再传入BERT:

现在,我们需要将清理后的数据集划分为训练集与验证集:

from sklearn.model_selection import train_test_split   
 
# 划分训练集与验证集 
X_tr, X_val, y_tr, y_val = train_test_split(train.clean_text, train.label, test_size=0.25, random_state=42)    
print('X_tr shape:',X_tr.shape)

接下来,对测试集与验证集的所有推文进行BERT嵌入:

from bert_serving.client import BertClient
    
# 使用IP连接BERT服务器
bc = BertClient(ip="YOUR_SERVER_IP")    
# 获得训练集与测试集的嵌入   
X_tr_bert = bc.encode(X_tr.tolist())    
X_val_bert = bc.encode(X_val.tolist())

该构建模型了!我们先来训练分类模型:

from sklearn.linear_model import LogisticRegression
    
# LR模型    
model_bert = LogisticRegression()    
# 训练    
model_bert = model_bert.fit(X_tr_bert, y_tr)    
# 预测  
pred_bert = model_bert.predict(X_val_bert)
查看分类准确率:
from sklearn.metrics import accuracy_score
    
print(accuracy_score(y_val, pred_bert))

可以看到,即使只有很小的数据集,我们也很容易达到95%左右的准确率。实在是不可思议!
你最好在其他任务上亲自实践一下BERT嵌入,并将你的结果分享到下面的评论区。
下一篇文章,我会在另外一个数据集上使用Fine-tune的BERT模型,并比较其性能。
超越BERT:NLP的最新技术
BERT激起了人们对NLP领域的极大兴趣,尤其是Transformer的广泛应用。 这也导致越来越多的实验室和组织开始研究pre-training, transformers 和 fine-tuning等任务。
BERT之后,一些新的项目在NLP各项任务中取得了更好的结果。 比如RoBERTa,这是Facebook AI对BERT和DistilBERT的改进,而后者其实就是BERT的更轻巧,便捷的版本。
你可以在regarding State-of-the-Art NLP in this article了解更多BERT之后的改进模型。

本文发布于: https://www.analyticsvidhya.com ,2019/9/25.           

发表评论