BERT论文理解-理论版

目录

    • BERT模型架构
    • 输入表征
    • 预训练任务
    • 代码实现
      • Encoder编码器模块

BERT模型架构

BERT模型架构是一种多层双向变换器(Transformer)编码器。至于什么是变换器的注释及实现,参考哈佛Vaswani等人(2017)的优秀代码指南(http://nlp.seas.harvard.edu/2018/04/03/attention.html)

BERT有两种大小:
(1)Base版:L=12 ; H= 768; A=12 总参数=110M
(2)Large版:L=24; H=1024; A=16 总参数=340M
【其中L为 层数(即Transformer blocks变换器块)表征; H为 隐节点大小表征; A为自注意力数目表征】

BERT,OpenAIGPT和ELMo的比较预训练模型架构间差异
BERT使用双向变换器;OpenAI GPT使用从左到右的变换器,ELMo使用独立训练的从左到右和从右到左LSTM级联来生成下游任务的特征。三种模型中只有BERT表征基于所有层左右两侧语境。

输入表征

输入表征 表征 单个文本句子或一对文本句子;
“序列”可以是单个句子或两个句子打包在一起
输入表征通过对相应词块的词块嵌入(word embedding)、段嵌入(segment embedding)和位嵌入(position embedding)求和来构造。即
word embedding + segment embedding + position embedding = input embedding
即下面很经典的图所示:

其中:
【CLS】token对应该词块的最终隐藏状态;用在分类任务上。对于非分类任务,将忽略此向量。

预训练任务

(1)任务一:MLM - 遮蔽语言模型
随机地遮蔽蔽每个序列中所有WordPiece词块的15%,然后仅预测那些被遮蔽词块。
这个任务确实可以让我们的模型训练更深度的表征,相当于完形填空任务。

但有两个不足之处:
<1> 预训练和微调之间的不匹配,因为在微调期间从未看到[MASK]词块。
为了缓解这个问题,并非15%中所有的单词都用【MASK】替代;而是
其中10%用随机词替换遮蔽词;其中10%保持单词不变;剩下的80%用[MASK]词块替换单词。
【由于模型不知道那些单词是要被预测的或者那些单词已经被随机替换了,因此会被迫地去尽可能保持每个输入词块的表征意义】
【随即替换只占 0.15 x 0.1 = 0.015,即1.5%的部分,这对模型的语言理解能力损伤不大】

<2> 每批中只预测15%的词块,这表明模型可能需要更多的预训练步骤才能收敛. 训练成本虽大,但出来的效果绝对是值得的

(2)任务二:预测下一句

很多重要的下游任务,例如问答(QA)和自然语言推理(NLI),都是基于对两个文本句子间关系的理解。
具体来说,选择句子A和B作为预训练样本:B有50%的可能是A的下一句,也有50%的可能是来自语料库的随机句子

输入=[CLS]男子去[MASK]商店[SEP]他买了一加仑[MASK]牛奶[SEP]
Label= IsNext
输入=[CLS]男人[面具]到商店[SEP]企鹅[面具]是飞行##少鸟[SEP]
Label= NotNext

为了生成每个训练输入序列,我们从语料库中采样两个文本跨度,我们将其称为“句子”,即使它们通常比单个句子长得多(但也可以更短)。第一个句子接收A嵌入,第二个句子接收B嵌入。B有50%可能刚好是A嵌入后的下一个句子,亦有50%可能是个随机句子。
对它们采样,使得两个句子的组合长度≦512个词块。

假设每批训练:
训练批量大小为256个序列,每个序列seq len 的长度是 512,则 一个批量中 256 x 512 = 131072,持续1,000,000个步骤,则需要处理 131,072 x 1,000,000 = 131,000,000,000
针对于33亿个单词语料库,则是大概40个周期。

代码实现


从图出发,input进去,先循环N次Encoder编码器模块;再经过N次的Decoder解码器模块;(N通常为6)

Encoder编码器模块

由于N=6,需要重复六次,则定义如下:

def clones(module, N):
    """
    :module -- 编码器Encoder
    :N -- 重复的次数 
    """
    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])

定义Encoder模块

class Encoder(nn.Module):
	"""
	定义Encoder模块
	"""   
    def __init__(self, layer, N):
    	"""
    	:layer -- 层数
    	:N -- 重复次数
    	"""
        super(Encoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)
        
    def forward(self, x, mask):
        """
        :x -- 
        :mask --  
        """
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x)
class LayerNorm(nn.Module):
    "Construct a layernorm module (See citation for details)."
    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(features))
        self.b_2 = nn.Parameter(torch.zeros(features))
        self.eps = eps

    def forward(self, x):
        mean = x.mean(-1, keepdim=True)
        std = x.std(-1, keepdim=True)
        return self.a_2 * (x - mean) / (std + self.eps) + self.b_2

未完待续。。。

你可能感兴趣的:(bert,自然语言处理,深度学习)