写在前面
update@2020.02.10
最近在看paddle相关,于是就打算仔细过一遍百度ERNIE的源码。之前粗看的时候还没有ERNIE2.0、ERNIE-tiny,整体感觉跟BERT也挺类似的,不知道更新了之后会是啥样~看完也会整理跟下面类似的总结,刚好也在研究paddle或ERNIE的同学可以加我一起讨论哈哈哈
原内容@2019.05.16
BERT 模型也出来很久了, 之前有看过论文和一些博客对其做了解读:NLP 大杀器 BERT 模型解读[1],但是一直没有细致地去看源码具体实现。最近有用到就抽时间来仔细看看记录下来,和大家一起讨论。
注意,源码阅读系列需要提前对 NLP 相关知识有所了解,比如 attention 机制、transformer 框架以及 python 和 tensorflow 基础等,关于 BERT 的原理不是本文的重点。
附上关于 BERT 的资料汇总:BERT 相关论文、文章和代码资源汇总[2]
今天要介绍的是 BERT 最主要的模型实现部分-----BertModel,代码位于
- modeling.py 模块[3]
除了代码块外部,在代码块内部也有注释噢
如有解读不正确,请务必指出~
1、配置类(BertConfig)
这部分代码主要定义了 BERT 模型的一些默认参数,另外包括了一些文件处理函数。
「参数具体含义」
- vocab_size:词表大小
- hidden_size:隐藏层神经元数
- num_hidden_layers:Transformer encoder 中的隐藏层数
- *num_attention_heads:*multi-head attention 的 head 数
- intermediate_size:encoder 的“中间”隐层神经元数(例如 feed-forward layer)
- hidden_act:隐藏层激活函数
- hidden_dropout_prob:隐层 dropout 率
- attention_probs_dropout_prob:注意力部分的 dropout
- max_position_embeddings:最大位置编码
- type_vocab_size:token_type_ids 的词典大小
- initializer_range:truncated_normal_initializer 初始化方法的 stdev
这里要注意一点,可能刚看的时候对type_vocab_size
这个参数会有点不理解,其实就是在next sentence prediction
任务里的Segment A
和 Segment B
。在下载的bert_config.json
文件里也有说明,默认值应该为 2。参考这个 Issue[4]
2、获取词向量(Embedding_lookup)
对于输入 word_ids,返回 embedding table。可以选用 one-hot 或者 tf.gather()
「参数具体含义」
- input_ids:word id 【batch_size, seq_length】
- vocab_size:embedding 词表
- embedding_size:embedding 维度
- initializer_range:embedding 初始化范围
- word_embedding_name:embeddding table 命名
- use_one_hot_embeddings:是否使用 one-hotembedding
- Return:【batch_size, seq_length, embedding_size】
3、词向量的后续处理(embedding_postprocessor)
我们知道 BERT 模型的输入有三部分:token embedding
,segment embedding
以及position embedding
。上一节中我们只获得了 token embedding,这部分代码对其完善信息,正则化,dropout 之后输出最终 embedding。注意,在 Transformer 论文中的position embedding
是由 sin/cos 函数生成的固定的值,而在这里代码实现中是跟普通 word embedding 一样随机生成的,可以训练的。作者这里这样选择的原因可能是 BERT 训练的数据比 Transformer 那篇大很多,完全可以让模型自己去学习。
4、构造 attention_mask
该部分代码的作用是构造 attention 可视域的 attention_mask, 因为每个样本都经过 padding 过程,在做self-attention的是padding的部分不能attend到其他部分上。输入为形状为 [batch_size, from_seq_length,...] 的 padding 好的 input_ids 和形状为 [batch_size, to_seq_length] 的 mask 标记向量。
5、注意力层(attention layer)
这部分代码是「multi-head attention」的实现,主要来自《Attention is all you need》这篇论文。考虑key-query-value
形式的 attention,输入的from_tensor
当做是 query, to_tensor
当做是 key 和 value,当两者相同的时候即为 self-attention。关于 attention 更详细的介绍可以转到【理解 Attention 机制原理及模型[5]】。
总结一下,attention layer 的主要流程:
- 对输入的 tensor 进行形状校验,提取
batch_size、from_seq_length 、to_seq_length
; - 输入如果是 3d 张量则转化成 2d 矩阵;
- from_tensor 作为 query, to_tensor 作为 key 和 value,经过一层全连接层后得到 query_layer、key_layer 、value_layer;
- 将上述张量通过
transpose_for_scores
转化成 multi-head; - 根据论文公式计算 attention_score 以及 attention_probs(注意 attention_mask 的 trick):
- 将得到的 attention_probs 与 value 相乘,返回 2D 或 3D 张量
6、Transformer
接下来的代码就是大名鼎鼎的 Transformer 的核心代码了,可以认为是"Attention is All You Need"原始代码重现。可以参见【原始论文[6]】和【原始代码[7]】。
配上下图一同使用效果更佳,因为 BERT 里只有 encoder,所有 decoder 没有姓名
7、函数入口(init)
BertModel 类的构造函数,有了上面几节的铺垫,我们就可以来实现 BERT 模型了。
总结一哈
有了以上对源码的深入了解之后,我们在使用 BertModel 的时候就会更加得心应手。举个模型使用的简单栗子:
在 BERT 模型构建这一块的主要流程:
- 对输入序列进行 Embedding(三个),接下去就是‘Attention is all you need’的内容了
- 简单一点就是将 embedding 输入 transformer 得到输出结果;
- 详细一点就是 embedding --> N *【multi-head attention --> Add(Residual) &Norm--> Feed-Forward --> Add(Residual) &Norm】;
- 哈,是不是很简单~
- 源码中还有一些其他的辅助函数,不是很难理解,这里就不再啰嗦。
以上~
本文参考资料
[2]
BERT 相关论文、文章和代码资源汇总: http://www.52nlp.cn/bert-paper-%E8%AE%BA%E6%96%87-%E6%96%87%E7%AB%A0-%E4%BB%A3%E7%A0%81%E8%B5%84%E6%BA%90%E6%B1%87%E6%80%BB
[3]
modeling.py 模块: https://github.com/google-research/bert/blob/master/modeling.py
[4]
参考这个 Issue: https://github.com/google-research/bert/issues/16
[6]
原始论文: https://arxiv.org/abs/1706.03762
[7]
原始代码: https://github.com/tensorflow/tensor2tensor/blob/master/tensor2tensor/models/transformer.py