BERT 的整体结构如下图所示,其是以 Transformer 为基础构建的,使用 WordPiece 的方法进行数据预处理,最后通过 MLM 任务和下个句子预测任务进行预训练的语言表示模型。下面我们从 BERT 的结构:Transformer 出发,来一步步详细解析一下 BERT。
来源
Transformer首先介绍 BERT 模型结构的基础:Transformer。Transformer 是一个完全基于注意力机制(Attention mechanism)的模块,对比 RNN(Recurrent Neural Network),当输入的句子是长句子时,RNN 可能会遗忘之前句子中出现的字词,而 Transformer 的注意力机制使得句子中重要的字词的权重增大,从而保证不会被遗忘。并且 Transformer 另一个巨大的优势在于,它可以使用并行的方法运行计算,从而加快了速度。
Transformer 的具体结构如下图:
来源
从上图我们可以看到 Transformer 的内部结构为:输入的 inputs 要经过 Input Embedding 模块进行向量化,然后加上对其的 Positional Encoding,然后数据向上进入由 Multi-Head Attention,Add & Norm,Feed Forward 以及又一个 Add & Norm 构成的 N 个整体之中。
其中的 Multi-Head Attention 最为关键,在开始介绍 Transformer 的 Multi-Head Attention 机制之前,我们先简单说一下 Positional Encoding。
Positional Encoding,从字面意思来讲是位置编码,就是用来表示输入句子向量中每个字词所对应的位置。由于 Tranformer 的结构不同,无法像 RNN 一样获取句子的时序信息,所以需要使用 Positional Encoding 表示字词在句子中的先后顺序。一种常见的计算方式是使用正弦函数和余弦函数来构造每个位置的值,后来的研究发现通过可训练的参数来实现的也能够达到同样的效果,BERT 模型中就是通过可训练参数的方法来实现的。
现在来介绍 Transformer 结构的重点:Multi-Head Attention。Multi-Head Attention 的组成因子是 Self-Attention,顾名思义,Self-Attention 就是自注意力,即语句对自身计算注意力权重。公式表示为:
![1](C:\Users\jie\Documents\notebook\PyTorch\BERT 预训练模型及文本分类\1.png)
公式 (7) 中,取 QQ 中一个行向量为例(也就是每一个输入样本中 xi 对应的 qi),用 qiqi 乘上每一个样本对应的 kiki,再除以注意力头的维度,就得到每个样本对应的注意力值。接下来,再使用 Softmax 函数将值转换为和为 1 的概率值(向量形式)并乘上 V,得到经过注意力机制计算后的输出值。
该流程也可以参考下图:
可以看出,BERT 的 Bidirection 特性就在 Self-Attention 机制中得到了体现,即计算句子中的注意力对某个词的分布时,既考虑了在该词左侧的词,也考虑了在该词右侧的词。
现在我们已经了解了 Self-Attention,Multi-Heah Attention 实际上就是多个 Self-Attention 的堆叠。如下图,多层叠加的 Self-Attention 组成了 Multi-Head Attention。不过因为多层的缘故,最后所有 Self-Attention 会生成多个大小相同的矩阵,处理方式是把这些矩阵拼接起来,然后通过乘上一个参数矩阵得到最后的计算结果。
来源
Multi-Head Attention 通过多层的 Self-Attention 可以将输入语句映射到不同的子空间中,于是能够更好地理解到语句所包含的信息。
下面引入一段 BERT 模型对 Self-Attention 的实现代码片段:
# 取自 hugging face 团队实现的基于 pytorch 的 BERT 模型 class BERTSelfAttention(nn.Module): # BERT 的 Self-Attention 类 def __init__(self, config): # 初始化函数 super(BERTSelfAttention, self).__init__() if config.hidden_size % config.num_attention_heads != 0: raise ValueError( "The hidden size (%d) is not a multiple of the number of attention " "heads (%d)" % (config.hidden_size, config.num_attention_heads)) self.num_attention_heads = config.num_attention_heads self.attention_head_size = int(config.hidden_size / config.num_attention_heads) self.all_head_size = self.num_attention_heads * self.attention_head_size self.query = nn.Linear(config.hidden_size, self.all_head_size) self.key = nn.Linear(config.hidden_size, self.all_head_size) self.value = nn.Linear(config.hidden_size, self.all_head_size) def transpose_for_scores(self, x): # 调整维度,转换为 (batch_size, num_attention_heads, hidden_size, attention_head_size) new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) x = x.view(*new_x_shape) return x.permute(0, 2, 1, 3) def forward(self, hidden_states): # 前向传播函数 mixed_query_layer = self.query(hidden_states) mixed_key_layer = self.key(hidden_states) mixed_value_layer = self.value(hidden_states) query_layer = self.transpose_for_scores(mixed_query_layer) key_layer = self.transpose_for_scores(mixed_key_layer) value_layer = self.transpose_for_scores(mixed_value_layer) # 将"query"和"key"点乘,得到未经处理注意力值 attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) attention_scores = attention_scores / math.sqrt(self.attention_head_size) # 使用 softmax 函数将注意力值标准化成概率值 attention_probs = nn.Softmax(dim=-1)(attention_scores) context_layer = torch.matmul(attention_probs, value_layer) context_layer = context_layer.permute(0, 2, 1, 3).contiguous() new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) context_layer = context_layer.view(*new_context_layer_shape) return context_layer