前文链接->从源码层面,深入理解 Bert 框架
前边从源码层面对BertModel类进行了一个深入的解析,接下来从云源码层面对 BertModel 类中的BertEmbeddings模块进行解析
1. BertEmbeddings 类源码
class BertEmbeddings(nn.Module):
"""Construct the embeddings from word, position and token_type embeddings.
"""
def __init__(self, config):
super(BertEmbeddings, self).__init__()
self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=0)
self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)
# self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load
# any TensorFlow checkpoint file
self.LayerNorm = BertLayerNorm(config.hidden_size, eps=1e-12)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
def forward(self, input_ids, token_type_ids=None):
seq_length = input_ids.size(1)
position_ids = torch.arange(seq_length, dtype=torch.long, device=input_ids.device)
position_ids = position_ids.unsqueeze(0).expand_as(input_ids)
if token_type_ids is None:
token_type_ids = torch.zeros_like(input_ids)
words_embeddings = self.word_embeddings(input_ids)
position_embeddings = self.position_embeddings(position_ids)
token_type_embeddings = self.token_type_embeddings(token_type_ids)
embeddings = words_embeddings + position_embeddings + token_type_embeddings
embeddings = self.LayerNorm(embeddings)
embeddings = self.dropout(embeddings)
return embeddings
1. __ init __(self, config)函数
我们知道,Bert模型的输入是三个数组,分别是词向量、句向量和位置编码;
__ init __() 函数分别定义三个embedding对象,用于对词向量、句向量和位置编码进行 embedding,对这三种数据进行embedding的参数如下表所示:
名称 | embedding的数据个数 | 每个token embedding后的维度 |
---|---|---|
词向量 | 词库容量 | hidden_size(默认768) |
句向量 | 每个序列中最多包含几个句子(默认为2) | hidden_size(默认768) |
位置编码 | 最大序列长度(默认为512) | hidden_size(默认768) |
这里解释一下 embedding 的本质:
以 word embedding 为例,对于每个词语,最开始都是使用 one-hot 编码来表示,word embedding
的过程就是用一个m维的稠密向量代替 one-hot 编码的过程;word
embedding本质上是建立一个从one-hot编码到m维的稠密向量的映射 word embedding
需要建立一个词向量矩阵,矩阵中的每一行存储一个词对应的词向量;每个词one-hot编码的值=对应词向量在词向量矩阵中的行号;
每个词的词向量最初都是随机生成的,在神经网络训练的过程中,这些词向量会不断优化
然后,定义了 一个LayerNorm函数和一个dropout函数,分别执行层标准化和dropout功能
2. Forward(self, input_ids, token_type_ids=None)函数
Forward函数在PyTorch中是一个比较特殊的函数,具体请参考此文->#深入探究# PyTorch中的 forward() 方法详解
forward函数的第1行代码是获取序列的长度,第2、3行是生成位置编码序列,具体做法是:
首先生成一个0到序列长度的数组,然后使用 unsqueeze()函数将此一维数组变成二维数组,最后使用expandas()函数将数组形状变为 [batch_size,序列长度];关于unsqueeze()函数的具体介绍请参考此文章->#彻底理解# pytorch 中的
squeeze() 和 unsqueeze()函数
然后是检查 token_type_ids 数组是否为 Null,如果不存在则生成一个形状和 input_ids 相同的全零二维数组 [batch_size,序列长度],这表示每个序列数据中只包含一个句子
接下来就是根据 input_ids, token_type_ids,position_ids三个输入分别生成三个embedding数组,最后将三个数组对应位置的元素相加。通过这个操作我们可知,每个token的embedding都是三个embedding相加后的结果
这里解释一下生成position_ids的过程:
bert模型的输入是一个batch的数据,因此也要生成一个batch的position_ids;首先生成一个样本的position_ids,然后使用unsqueeze()和expand_as()函数,增加一个维度并将当前生成的position_ids信息扩展(复制)到一个batch内的其他样本中,换句话说就是生成batch_size个相同的position_ids并组合成一个数组
最后将相加后的 embedding 进行LayerNorm和dropout操作后返回
总结来说,Bert中的embedding模块做的工作就是:生成三个embedding数组并相加,而后进行LayerNorm和dropout操作后返回
接下来将对Bert的BertEncoder和BertPooler模块进行详细的解析: