bert源码解析-modeling.py
bert是transformer的encoder部分,以google-bert源代码为例。
由两个重要的class组成:
1.BertConfig 大多时候改动的参数并不多,知晓这些参数可以便于推算模型的大小,比如隐藏层大小768
class BertConfig(object):
def __init__(self,
vocab_size,
hidden_size=768,
num_hidden_layers=12,
num_attention_heads=12,
intermediate_size=3072,
hidden_act="gelu",
hidden_dropout_prob=0.1,
attention_probs_dropout_prob=0.1,
max_position_embeddings=512,
type_vocab_size=16,
initializer_range=0.02):
"""
构造参数
:param vocab_size: 词汇量大小
:param hidden_size: 隐藏层输出大小
:param num_hidden_layers: 隐藏层单元层数
:param num_attention_heads: 注意力模块个数
:param intermediate_size: 中间层输出大小 用于前向传播时 由hidden_size->intermediate_size
:param hidden_act: 隐藏层激活函数
:param hidden_dropout_prob: 隐藏层dropout
:param attention_probs_dropout_prob: 注意力部分dropout
:param max_position_embeddings: 位置编码最大值默认是512
:param type_vocab_size: token_type_ids的词典大小,用于句子上下句是否是同一个用的标识,默认是大小是2 也就是用0或1表示
:param initializer_range: 初始化方法的范围
"""
self.vocab_size = vocab_size
self.hidden_size = hidden_size
self.num_hidden_layers = num_hidden_layers
self.num_attention_heads = num_attention_heads
self.hidden_act = hidden_act
self.intermediate_size = intermediate_size
self.hidden_dropout_prob = hidden_dropout_prob
self.attention_probs_dropout_prob = attention_probs_dropout_prob
self.max_position_embeddings = max_position_embeddings
self.type_vocab_size = type_vocab_size
self.initializer_range = initializer_range
2.BertModel 模型部分
模型部分主要分成三个部分说明,对应的可以看transformer单个block的结构。
从初始化方法__init__开始说明 初始化定义:
config 即上述BertConfig is_training 判断是否是训练,如果不是训练则不需要进行dropout,因为dropout是为了避免训练过程过拟合 input_ids 输入句子的数字化表示,例如源代码注释 tf.constant([[31, 51, 99], [15, 5, 0]]) input_mask 表示该位置是否有数字,长度和input_ids一致 token_type_ids 字的type use_one_hot_embeddings 输入初始化词是否使用独热编码 scope tf变量作用域的名称,默认是“bert”
初始化三个输入: input_shape 大小是的[batch_size, seq_length] input_mask 大小是的[batch_size, seq_length] token_type_ids 大小是的[batch_size, seq_length]
主要结构如下,一个个来介绍。
- 声明变量作用域(“bert”)
- 变量作用域(“embeddings”)
- 词嵌入
- 位置编码及mask编码
- 变量作用域(“encoder”)
- 变量作用域(“pooler”)
(1)embedding-词嵌入
①初始化词嵌入矩阵 矩阵大小及 [vocab_size, embedding_size] 词表大小*词向量维度
②输入input_ids拍平和嵌入矩阵相乘得到输入的嵌入矩阵,再reshape成[batch_size, seq_length, embedding_size]输出。
详细注释如下:
def embedding_lookup(input_ids,
vocab_size,
embedding_size=128,
initializer_range=0.02,
word_embedding_name="word_embeddings",
use_one_hot_embeddings=False):
"""
获取词嵌入
:param input_ids: 输入词的id
:param vocab_size: 词典大小
:param embedding_size: 词嵌入输出维度
:param initializer_range: 生成截断正态分布的初始化的标准差
:param word_embedding_name: 词嵌入再网络中的name
:param use_one_hot_embeddings: 判断是使用何种初始编码方式,one-hot或tf.gather
:return:
"""
#如果输入大小是[batch_size, seq_length]
#如果输入是二维的 reshape成三维[batch_size, seq_length, 1]
if input_ids.shape.ndims == 2:
input_ids = tf.expand_dims(input_ids, axis=[-1])
#这个矩阵大小就是[vocab_size, embedding_size] 那么每个词都可以通过one-hot获取到其对应的嵌入矩阵
embedding_table = tf.get_variable(
name=word_embedding_name,
shape=[vocab_size, embedding_size],
initializer=create_initializer(initializer_range))
#输入id reshape 成一维 例如[[1,2],[3,4]]=>[1,2,3,4]
flat_input_ids = tf.reshape(input_ids, [-1])
#获取输出矩阵 两种方式
#1.用one-hot与embedding_table相乘得到嵌入矩阵
#2.tf.gather直接切片提取
#得到output是(batch_size * seq_length) * embedding_size
if use_one_hot_embeddings:
one_hot_input_ids = tf.one_hot(flat_input_ids, depth=vocab_size)
output = tf.matmul(one_hot_input_ids, embedding_table)
else:
output = tf.gather(embedding_table, flat_input_ids)
#获取input_ids的 [batch_size, seq_length]
input_shape = get_shape_list(input_ids)
#reshape的尺寸input_shape[0:-1]是[batch_size, seq_length] + 1*embedding_size
#所以输出是[batch_size, seq_length, embedding_size]
output = tf.reshape(output,
input_shape[0:-1] + [input_shape[-1] * embedding_size])
return (output, embedding_table)
(2)embedding-位置编码及mask编码
①token type 用于标识当前字的类型,比如上下句标识。“小明爱学习,小林爱学习”标识上下句[0,0,0,0,0,1,1,1,1,1] bert-chinese模型默认大小为2
token_type_ids 输入大小是 [batch_size, seq_length],对应的嵌入矩阵是[token_type_vocab_size, embedding_size]
输入拍平之后和嵌入矩阵相乘在reshape得到[batch_size, seq_length, embedding_size]直接和词嵌入相加
②use_position_embeddings 位置编码,因为注意力机制部分没有位置信息,所以输入时候单独添加,注意!!bert的位置是通过参数学习的,不是使用sin cos组合得到。
初始化嵌入矩阵 大小为[max_position_embeddings, embedding]
根据seq_length直接截取前seq_length即可,但是此时矩阵大小是[seq_length,embedding ]
因为每个batch使用相同的位置向量,为了能直接相加,进行广播到batch size 得到[batch_size, seq_length, embedding_size]
和上述output相加
③接入layer_norm_and_dropout 输出
详细注释如下:</