参照之前的 Transformer 的结构,在 Multi-Head Attention 之后是 Add & Norm,将经过注意力机制计算后的向量和原输入相加并归一化,进入 Feed Forward Neural Network,然后再进行一次和输入的相加并完成归一化。
分词方法 WordPiece前面介绍了 BERT 的具体结构,下面介绍 BERT 使用的数据预处理方法,数据预处理对于模型的训练十分重要,关系到模型的训练效率和准确率的提升。BERT 在对数据预处理时,使用了 WordPiece 的方法,WordPiece 从字面意思理解就是把字词拆成一片一片的。
举个例子来讲,如 look,looked,looking 这三个词,它们其实有同样的意思,但如果我们以词作为单位,那它们就会被认为成不同的词。在英语中这样的情况十分常见,所以为了解决这个问题,WordPiece 会把这三个词拆分成 look,look 和 ##ed,look 和 ##ing,这个方法把词本身和时态表示拆分开,不但能够有效减少词表的大小,提高效率,还能够提升词的区分度。
不过,这个方法对中文是无效的,因为在中文中每个字都是最小的单位,不像英文使用空格分词,并且许多词还能够进一步拆分,所以对中文使用 WordPiece 就相当于按字分割,这也是 BERT 的中文预训练模型的一个局限。因此,尽管 BERT 中文预训练模型效果很好,但也还存在可以改进的空间。有一些研究者就从这个角度出发对中文 BERT 进行了改进,如这篇论文:中文全词覆盖 BERT ,研究者在预训练的数据处理过程中将原 BERT 中 WordPiece 的分词方法换成了中文分词的方法,然后对词整体添加掩膜,最后进行预训练。在中文数据集测试上,使用这个改进后的预训练模型的测试结果优于使用原版 BERT 的中文预训练模型的测试结果。
BERT 预训练模型上面介绍了 BERT 的结构和 BERT 进行数据预处理使用的 WordPiece 方法,接下来,我们将要介绍 BERT 在预训练阶段的两个任务:遮蔽语言模型和句子预测任务。也正是这两个任务,使得 BERT 学到了对自然语言的理解。
遮蔽语言模型与常见的训练从左向右语言模型(Left-To-Right Language Model)的预训练任务不同,BERT 是以训练遮蔽语言模型(Masked Language Model)作为的预训练目标,具体来说就是把输入的语句中的字词随机用 [Mask] 标签覆盖,然后训练模型结合被覆盖的词的左侧和右侧上下文进行预测。可以看出,BERT 的做法与从左向右语言模型只通过左侧语句预测下一个词的做法相比,遮蔽语言模型能够生成同时融合了左、右上下文的语言表示。这种做法能够使 BERT 学到字词更完整的语义表示。
BERT 的论文中提到,增加掩膜的具体方式为:先对语句进行 WordPiece 分割,分割后选择句中 15% 的字符,例如选择到了第 ii 字符,接下来:
以 80%80% 的概率使用 [Mask] 替换。
以 10%10% 的概率使用一个随机的字符替换。
以 10%10% 的概率不进行操作。
下面我们使用在 PyTorch-Transformers 模型库中封装好的 BERTForMaskedLM() 类来实际看一下 BERT 在预训练后对遮蔽字的预测效果。首先,需要安装 PyTorch-Transformers。
!pip install pytorch-transformers==1.0 # 安装 PyTorch-TransformersPyTorch-Transformers 是一个以 PyTorch 深度学习框架为基础构建的自然语言处理预训练模型库,早前称之为 pytorch-pretrained-bert,如果已正式成为独立项目。
使用 PyTorch-Transformers 模型库,先设置好准备输入模型的例子,使用 BertTokenizer() 建立分词器对象对原句进行分词,然后对照词表将词转换成序号。
import torch from pytorch_transformers import BertTokenizer model_name = \'bert-base-chinese\' # 指定需下载的预训练模型参数 # BERT 在预训练中引入了 [CLS] 和 [SEP] 标记句子的开头和结尾 samples = [\'[CLS] 中国的首都是哪里? [SEP] 北京是 [MASK] 国的首都。 [SEP]\'] # 准备输入模型的语句 tokenizer = BertTokenizer.from_pretrained(model_name) tokenized_text = [tokenizer.tokenize(i) for i in samples] input_ids = [tokenizer.convert_tokens_to_ids(i) for i in tokenized_text] input_ids = torch.LongTensor(input_ids) input_ids