Bidirectional Encoder Representations from Transformers,即Bert;
本笔记主要是对谷歌Bert架构的入门学习:
目标是熟练掌握Bert及其变体来执行实际的自然语言处理任务;利用Bert模型超强的理解能力来简化自然语言处理任务;
要求环境 Python3
Transformer完全依赖于注意力机制,并摒弃了循环;使用了一种特殊的注意力机制,称为自注意力(self-attention)
;
实例:文本翻译(从英文翻译为法文)
N个编码器:
Transformer中的编码器不止一个,而是由一组N个编码器串联而成,一个编码器的输出作为下一个编码器的输入;
原句的特征会由最后一个编码器输出,编码器的作用就是提取原句中的特征;
Transformer原论文中“Attention is all your need”中,作者使用的N=6;
关于Transformer的学习可以参考之前总结的blog:
每个编码器的组成:
I am good -> 【多头注意力层 -> 前馈网络层 】-> …
每一个编码器的构造都是相同的,并且包含两个部分:
首先我们要了解“自注意力机制”:
以一句话为例:“A dog ate the food beacause it was hungry”,计算机模型要理解it的意思是dog还是food,自注意力机制有助解决该问题;
模型会一次计算每个单词的特征值,当计算每个词的特征值时,模型都需要遍历每个词与句子中其他词的关系;模型可以通过词与词之间的关系来更好的理解当前词的意思;
我们以一个简单的句子“I am good”为例:
词
的特征向量,这个特征向量也是需要通过训练获得的;[x1, x2, x3]
;矩阵X的维度是[句子的长度 x 词嵌入向量维度]
,可以是512;关于三个新的矩阵QKV我们来具体看下:
Q、K、V三个矩阵的行数与X一致,它们的每一行分别表示一个单词的q、k、v向量;向量的维度由参与计算的权重矩阵决定,可以是64;
那么,为什么要使用Q、K、V三个矩阵?如何才能用自注意力模型?我民额继续;
理解注意力机制:
已经计算得到Q、K、V矩阵,是要应用于注意力机制的;我们知道,要计算一个词的特征值,自注意力机制会使该词与给定句子中所有词联系起来;了解一个词与句子中所有词相关程度有助于更精确地计算特征值;
自注意力机制的4个步骤:
Q·K^T
;softmax(Q·K^T/√dk)
;Z = softmax(Q·K^T/√dk)V
(WordNum x 1);import numpy as np
test_a = np.array([[1,2,3],[4,5,6],[4,5,6]])
test_b = np.array([[1,4,5,6],[2,7,8,9],[3,10,11,12]])
print(test_a.shape,test_b.shape)
# 点积计算
np.matmul(test_a, test_b)
(3, 3) (3, 4)
array([[ 14, 48, 54, 60],
[ 32, 111, 126, 141],
[ 32, 111, 126, 141]])
# 求和
np.matmul(test_a, test_b).sum(axis=1)
array([176, 410, 410])
单词“I”的自注意力值:假设“I”的分数向量是[0.9, 0.07, 0.03]
,那么其自注意力值就包含了90%的值向量v1(I)、7%的值向量v2(am)、3%的值向量v3(good);
再看之前的例子,“A dog ate the food beacause it was hungry”,计算机模型要理解it的意思是dog还是food,可以计算it这个词的自注意力值,它对dog词的值分量的权重会更大;
这也说明了:通过自注意力机制,我们可以了解一个词与句子中所有词的相关程度;
注意力矩阵Z由句子中所有单词的自注意力值组成,公式为Z = softmax(Q·K^T/√dk)V
;
自注意力机制也被称为“缩放点积注意力机制”;
多头注意力层:
使用多头注意力的逻辑为:使用多个注意力矩阵,可以提高注意力矩阵的准确性;
我们知道:为了计算注意力矩阵,需要创建三个新的矩阵QKV,为此还要引入三个新的权重矩阵;要计算多个注意力矩阵,就需要这样的多组数据;
假设我们有8个注意力矩阵,Z1到Z8,将所有的注意力头(就是注意力矩阵)串联起来,并将结果乘以一个新的权重矩阵W0
,从而得到最终的注意力矩阵;
Multi-head attention = Concatenate(Z1,Z2...,Z8) W0
位置编码:(position encoding)
指词在句子中的位置编码;仍以“I am good”为例,在循环神经网络RNN中,句子是逐字送入学习网络,最终完全理解整个句子;但在Transformer网络则是将句子中所有词并行输入到神经网络(并行有助于缩短训练时间,同时有利于学习长期依赖);
既然是并行输入,那么就无法保留词序,而词序有很重要,因此也要为Transformer输入一些关于词序的信息;
对于给定的句子:
[句子长度 x 嵌入维度]
,如3 x 512;位置编码矩阵P,其shape与X相同,只需将P添加到X中,再输入神经网络,就可以让输入矩阵既包含词的嵌入,也包含词在句子中的位置信息,X = X + P
;
一种计算位置编码矩阵P的方式——“使用正弦函数来计算位置编码”
而一个编码器模块是由 多头注意力层 和前馈网络层 两部分组成,而此前我们已经了解了多头注意力层,接下来看下前馈网络层;
前馈网络层:
在编码器模块中,前馈网络层 接在 多头注意力层后;它由两个有ReLU激活函数的全连接层组成,前馈网络的参数在句子的不同位置是相同的,但在不同的编码器模块上是不同的;
编码器的“叠加和归一组件”:
输入矩阵 | 编码器模块 | 编码器模块 | 编码器模块 | 编码器模块 | 编码器模块 | |
---|---|---|---|---|---|---|
输入 | → | 多头注意力层→ | 叠加和归一组件 | → | 前馈网络层→ | 叠加和归一组件 |
↓ | ↑ | ↓ | ↑ | |||
→ | → | → | → | → | → |
现在回顾下编码器及前后的部分:
N个叠加的编码器的输出特征值记为R,再把R作为输入传给解码器,解码器将基于这个输入生成目标句;
I am good => 通过编码器学习原句,并计算特征值 => 解码器将特征值作为输入,生成目标句”我很好“;
类似编码器叠加N个,解码器也可以有N个叠加,一个解码器的输入会作为输入传给下一个解码器;值得注意的是:编码器输出的特征值,将作为输入传给所有解码器
,因此一个解码器有两个输入,一个是来自前一解码器的输出,另一个是编码器输出的特征值;
生成目标句的过程:
表示句子开始,生成目标句的第一个词”我“;
和”我“作为输入,并试图生成目标句中的下一个词;
、我、很、好作为输入,试图生成下一个词,如果生成的标记为
则表示句子结束;就意味着解码器已经完成了对目标句的生成;对于解码器的输入,实际同样需要将其转换为嵌入矩阵
,为其添加位置编码
;
解码器模块:
解码器模块 | 解码器模块 | 解码器模块 | ||
---|---|---|---|---|
→ | 带掩码的多头注意力层→ | 多头注意力层→ | 前馈网络层 | → |
内部包含三个子层:
相比编码器模块,多了带掩码的多头注意力层;
带掩码的多头注意力层:
标记添加到目标句的开头,并在每一步将下一个预测词与输入结合起来,以预测目标句;过程举例说明如下:
我很好
输入解码器(实际输入是添加了位置编码的嵌入矩阵X),预测输出为我很好
;上一步生成的词作为输入
;[, "我"]
,并没有其他词;我也也需要实现这样的方式来训练模型:模型的注意力机制应该只与该词之前的单词有关;
相邻的词,模型应只看见
,那么就应该掩盖
后边的词; 掩码 掩码 掩码
我 掩码 掩码
我 很 掩码
我 很 好
这样的掩码有助于自注意力机制只注意模型在测试期间可以使用的词;
我们知道:
import numpy as np
test_a = np.array([[1,2,3],[4,5,6],[4,5,6]], dtype=np.float32)
matrix = np.triu(np.ones(test_a.shape) * -np.inf, 1)
test_a = test_a + matrix
test_a
array([[ 1., -inf, -inf],
[ 4., 5., -inf],
[ 4., 5., 6.]])
def softmax(x):
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum(axis=0)
# 使用-inf掩盖的向量,在计算softmax时,不会分摊权重
test_a[0],softmax(test_a[0])
(array([ 1., -inf, -inf]), array([1., 0., 0.]))
带掩码的多头注意力层输出的注意力矩阵M,送到解码器的下一个子层,这是一个多头注意力层;
多头注意力层:
我们之前提到过:编码器输出的特征值,将作为输入传给所有解码器,更准确的描述是:将作为输入传给所有解码器的多头注意力子层;即解码器中的多头注意力层有两个输入
:一个来自带掩码的多头注意力层,另一个是编码器输出的特征值;
由于涉及编码器与解码器的交互,解码器的这一子层也被称为
编码器-解码器注意力层
;
我们用R来表示编码器输出的特征值(每个词都对应一个特征向量,因此这里的R实际是一个矩阵),用M表示由带掩码的多头注意力层输出的注意力矩阵;我们知道多头注意力机制第1步就是创建Q K V三个矩阵(通过将输入矩阵乘以权重矩阵),但是R和M两个输入,究竟用谁?
答案是:我们使用M作为输入矩阵来创建查询矩阵Q,使用R作为输入矩阵创建K和V矩阵;
细节的:
)与所有键向量k1(I)、k2(am)、k3(good)的点积,因此第1行表示目标词
与原句中所有词的相似度;最终得到的所有行对应 查询矩阵(目标句特征)与键矩阵(原句特征)的相似度;计算h个注意力矩阵后,将它们串联起来,乘以一个新的权重矩阵,得到最终的注意力矩阵;将其输入解码器的下一个子层,即前馈网络层;
前馈网络层:
关于线性层和softmax层:
词汇量
(这个应该是指词源量,而不是原句的词量,输出的词的索引值,也是要对应到总词汇表的);即Transformer架构;
可以通过最小化损失函数来训练Transformer网络,解码器预测的是词汇的概率分布,并选择概率最高的词作为输出,因此损失函数的选择,需要让预测的概率分布和实际的概率分布之间的差异最小化,这样可以将损失函数定义为交叉熵损失函数,并使用Adam算法来优化训练过程;
需要注意的是,为了防止过拟合,可以将dropout方法应用于每个子层的输出,以及嵌入和位置编码的总和(这里说的可能是输入矩阵);
了解了Transformer 编码器-解码器架构,了解使用的不同子层;我们了解到自注意力机制将一个词与句子中的所有词联系起来,以便更好的理解这个词;使用Q K V三个矩阵计算自注意力值;如何计算位置编码,以及如何用它来捕捉词序;
下一章,我们将学习Bert,以及它如何使用Transformer来对上下文嵌入进行学习。