NLP/Bert 源码解读 1
在上一篇文章中,我用图解详细讲述了 Bert 的组成部分和内部原理。
今天这篇文章,我们来看 Bert 的源码。下面使用的 Bert 源码,来自于 Hugging Face 的 transformers。这个项目一开始的名字是:pytorch-pretrained-bert,只包含 Bert。
后来加入了 GPT-2,RoBERTa,XLM,DistilBert,XLNet,T5,CTRL 等模型,改名为 transformers。你可以点击 model-architectures 来查看所有的模型。
transformers 的代码实现包括 PyTorch 和 Tensorflow,我这里只讲其中的 PyTorch 的源码。
你可以使用 pip install transformers
来安装这个库。
设计理念
为了让你对这个库有一个整体的认识,我先从顶层设计,来概览一下整个库包括的模块。
首先,这个库屏蔽了很多底层实现的细节,从上层使用来讲,你只需要知道 3 个大类:Configuration,Model,以及 Tokenizer。这 3 个类都都一个方法from_pretrained()
,这个方法可以加载预训练好的模型结构、权重参数以及词汇表。
- Model 是指已经预训练好的模型,包括超过 30 个模型。
- Configuration 用于加载模型的权重,设置模型的一些超参数,如网络层数,hidden_state 向量的长度,多头注意力的数量等。
- Tokenizer 存储了每个模型的词汇表,并且对文本进行预处理和编码,如把文本转换为数字,对文本进行 padding,添加 mask 等操作。
这个库提供了非常多预训练好的模型,调用from_pretrained()
方法加载模型,调用save_pretrained()
保存模型,这两个函数说明如下:
from_pretrained()
- 传入名字,加载预训练好的模型(如果本地没有对应的模型,那么自动下载模型)。
- 传入路径,加载自己训练的模型。
save_pretrained()
保存自己训练的模型。
除此之外,还提供了 2 个更顶层的 API pipeline()
和 Trainer()
,将上面 3 个类也封装起来了。
下面来看看如何使用 pipeline
。
使用 pipeline
pipeline
提供了处理 8 种 NLP 任务的能力。
- 情感分析:判断文本的情感是积极的,还是消极的
- 文本生成(英语):提供一个文本开头,模型会生成接下来的文字
- 名称实体识别:在一个句子中,标记每个单词表示的实体(人、地点等)
- 问答系统:为模型提供一些文本和一个问题,从上文本提取答案。
- 完形填空:给定一个句子,有些单词是空的(用 [MASK] 表示),填充空白的地方
- 生成摘要:给定一段长文本,生成摘要
- 翻译:把一种语言翻译为另一种语言
- 特征提取:返回一段文本的向量表示
下面的例子是情感分析任务,你可以通过 task summary 查看更多的任务例子:
1 | from transformers import pipeline |
当我们第一次使用sentiment-analysis
,程序会自动下载预训练好的模型和 tokenizer,然后我们就可以调用classifier
来进行情感分类。
1 | classifier('We are very happy to show you the 🤗 Transformers library.') |
上面加载的模型默认是 distilbert-base-uncased-finetuned-sst-2-english
,是 Bert 模型在文本分类任务上经过微调后得到的。如果你想加载其他模型,你可以使用model
参数。例如下面代码加载了nlptown/bert-base-multilingual-uncased-sentiment
,这个模型是在多语言数据集上经过训练的,支持英语、法语、荷兰语、德语、意大利语以及西班牙语。
1 | classifier = pipeline('sentiment-analysis', model="nlptown/bert-base-multilingual-uncased-sentiment") |
关于所有支持的模型,你可以通过 model hub 查看。
注意,有些预训练好的模型,对特定任务进行了微调。如果你加载的模型没有对特定任务进行微调,那么只会加载 Transformer 里面基本的网络层,而不会加载额外的 head 层(一般用于最终的分类输出),而 head 层的参数会随机初始化,这会导致每次输出的结果都不一样。
例如
bert-base-uncased
模型,这个模型来自于 Bert 的原始论文 BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding,没有对文本分类做微调,加载这个模型来做情感分类,会导致输出结果是随机的。
1 classifier = pipeline('sentiment-analysis', model="bert-base-uncased")
你可以直接使用 AutoModelForSequenceClassification
和 AutoTokenizer
的 from_pretrained()
方法,来加载你想要的模型,再传入 pipeline
。
1 | from transformers import AutoTokenizer, AutoModelForSequenceClassification |
使用 Model、Tokenizer、Configuration
我们在上面说了,pipeline
其实就是封装了Model
、Tokenizer
以及 Configuration
。
现在我们不使用 pipeline
,而是使用 Model
、Tokenizer
以及 Configuration
来构建一个模型。
首先,还是加载Model
和 Tokenizer
:
1 | from transformers import AutoTokenizer, AutoModelForSequenceClassification |
上面的代码中,使用了AutoModelForSequenceClassification
和AutoTokenizer
,这两个类会根据模型名称,自动加载对应的实现类。如果你知道自己使用的模型,可以使用具体的类名。例如你要使用 Bert,那么你可以使用 BertForSequenceClassification
和 BertTokenizer
,如下所示:
1 | from transformers import BertTokenizer, BertForSequenceClassification |
Tokenizer 的作用
上面提到,Tokenizer
的作用是预处理文本数据,主要包括 2 部分:
- 把文本划分为词。每一个模型的划分方法都不一样,都有自己对应的的
Tokenzier
,因此,我们需要通过from_pretrained()
方法加载对应模型的Tokenizer
。你可以通过 tokenizer_summary 查看所有的Tokenizer
。 - 把词转换为数字,这个步骤需要词汇表。每个训练好的模型所使用的数据集都不一样,词汇表也不一样。因此,我们需要通过
from_pretrained()
方法加载对应模型的Tokenizer
,才能把文本转换为正确的数字,构建张量,输入到模型。
Tokenizer
初始化完成之后,调用如下代码把文本转换为张量,返回的inputs
是一个 dict,包括 input_ids
和 attention_mask
:
- input_ids:表示文本的张量
- attention_mask:计算 attention 时使用的张量,默认为 1。如果使用了 padding,那么补齐的位置对应的
attention_mask
为 0
1 | inputs = tokenizer("We are very happy to show you the 🤗 Transformers library.") |
你可以传进一个文本 list,参数padding=True
,表示对齐长度;return_tensors="pt"
表示返回 PyTorch 的张量(return_tensors="tf"
表示返回 Tensorflow 的张量)。
1 | pt_batch = tokenizer( |
然后把数据输入到模型(注意需要用**
解包参数),关于 Python 中 *
和**
的作用,请查看 这篇文章:
1 | pt_outputs = pt_model(**pt_batch) |
返回的结果是一个 tuple,表示最后一个 Softmax 前一层的的输出。
你可以调用 Softmax 来获得最终的输出,并和真实的标签计算 loss。
1 | import torch.nn.functional as F |
你也可以把标签转入模型里,那么返回的 tuple 会包含 loss 和模型最后一层的输出。
1 | import torch |
你也可以在自己的数据集上训练(微调)这个网络。
当你微调完模型后,你可以使用save_pretrained()
方法保存模型和 tokenizer
。其中save_directory
是保存的路径。
1 | tokenizer.save_pretrained(save_directory) |
然后你可以使用from_pretrained()
方法加载保存的模型。
1 | tokenizer = AutoTokenizer.from_pretrained(save_directory) |
你也可以让模型返回 hidden states 和 attention weights。
1 | pt_outputs = pt_model(**pt_batch, output_hidden_states=True, output_attentions=True) |
Configuration 的作用
如果你想模型的一些超参数(如自定义 hidden state 向量的长度、dropout rate、多头注意力的数量等),但是又需要利用训练好的模型的权重参数,那么就可以使用 Configuration。
代码如下所示,加载了distilbert-base-uncased
模型,通过DistilBertConfig
定义了一些超参数。
1 | from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification |
如果你想修改类别的数量,可以在 from_pretrained()
函数中使用 num_labels
修改类别数量。代码如下所示:
1 | from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification |
总结
虽然 pipeline
的用法非常简洁,只需要最少的代码就可以调用模型,但是也缺少了模型的定制性。
一般情况下,我们使用比较多的还是 Model、Tokenizer 以及 Configuration 的组合。这里总结一下它们 3 者的功能。
- Model 可以加载已经训练好的模型,包括超过 30 个模型。
- Configuration 用于设置模型的一些超参数,如网络层数,hidden_state 向量的长度,多头注意力的数量、dropout rate 等。
- Tokenizer 存储了每个模型的词汇表,并且对文本进行预处理和编码,如把文本转换为数字,对文本进行 padding,添加 mask 等操作。
其中,Model 和 Tokenizer 是必须的,而 Configuration 只有在你需要修改一些超参数的时候才会用到。
在下一篇文章,我会讲解在 transformers 中,提供了哪些 Bert 有关的模型。
如果你觉得这篇文章对你有帮助,不妨点个赞,让我有更多动力写出好文章。
我的文章会首发在公众号上,欢迎扫码关注我的公众号张贤同学。
