©原创作者 |疯狂的Max
ERNIE代码解读
考虑到ERNIE使用BRET作为基础模型,为了让没有基础的NLPer也能够理解代码,笔者将先为大家简略的解读BERT模型的结构,完整代码可以参见[1]。
01 BERT的结构组成
BERT的代码最主要的是由分词模块、训练数据预处理、模型结构模块等几部分组成。
1.1 分词模块
模型在训练之前,需要对输入文本进行切分,并将切分的子词转换为对应的ID。这一功能主要由BertTokenizer来实现,主要在
/models/bert/tokenization_bert.py实现。
BertTokenizer 是基于BasicTokenizer和WordPieceTokenizer 的分词器:
BasicTokenizer负责按标点、空格等分割句子,并处理是否统一小写,以及清理非法字符。
WordPieceTokenizer在词的基础上,进一步将词分解为子词(subword)。
具有以下使用方法:
- from_pretrained:从包含词表文件(vocab.txt)的目录中初始化一个分词器;
- tokenize:将文本分解为子词列表;
- convert_tokens_to_ids:将子词转化为子词对应的下标;
- convert_ids_to_tokens :将对应下标转化为子词;
- encode:对于单个句子,分解词并加入特殊词形成“[CLS], x, [SEP]”的结构并转换为词表对应下标的列表;
- decode:将encode的输出转换为句子。
1.2 训练数据预处理
训练数据的构建主要取决于预训练的任务,由于BERT的预训练任务包括预测上下句和掩码词预测是否为连续句,那么其训练数据就需要随机替换连续的语句和其中的分词,这部分功能由run_pretraining.py中的函数
create_instances_from_document实现。
该部分首先构建上下句,拼接[cls]和[sep]等特殊符号的id,构建长度为512的列表,然后根据论文中所使用的指定概率选择要掩码的子词,这部分由函数
create_masked_lm_predictions实现。
1.3 模型结构
BERT模型主要由BertEmbeddings类、BertEncoder类组成,前者负责将子词、位置和上下句标识(segment)投影成向量,后者实现文本的编码。
编码器BertEncoder又由12层相同的编码块BertLayer组成。每一层都由自注意力层BertSelfAttention和前馈神经网络层BertIntermediate以及输出层BertOutput构成,在
/models/bert/modeling_bert.py中实现。
每一层编码层的结构和功能如下:
- BertSelfAttention:负责实现子词之间的相互关注。注意,多头自注意力机制的实现是通过将维度为hidden_size 的表示向量切分成n个维度为hidden_size / n的向量,再对切分的向量分别进行编码,最后拼接编码后的向量实现的;
- BertIntermediate:将批次数据(三维张量)做矩阵相乘和非线性变化;
- BertOutput :实现归一化和残差连接;
工程小技巧: 如果模型在学习表示向量的过程中需要使用不同的编码方式,以结合图神经网络层和Transformer编码层为例,笔者建议尽量使用相同的参数初始化方式,两者都使用残差连接,这能够避免模型训练时出现梯度爆炸的问题。
此外是否需要对注意力权重进行大小的变化,如Transformer会除以向量维度的开方,则取决于图神经网络的层数,一般而言,仅使用两层或以下的图神经网络层,则无需对注意力权重做变化。
具体可以通过观察图神经网络层生成的表示向量的大小是否和Transformer编码层生成的向量大小在同一个数量级来决定,如果在同一个数量级则无需改变注意力权重,如果出现梯度爆炸的现象,那么则可以缩小注意力的权重。
02 从BERT到ERNIE
由于ERNIE是在BERT的基础上进行改进,在数据层面需要构建与文本对应的实体序列&#