1. BERT,Transformer的模型架构与详解

1.1 认识BERT


学习目标

  • 了解什么是BERT.
  • 掌握BERT的架构.
  • 掌握BERT的预训练任务.

什么是BERT

  • BERT是2018年10月由Google AI研究院提出的一种预训练模型.
    • BERT的全称是Bidirectional Encoder Representation from Transformers.
    • BERT在机器阅读理解顶级水平测试SQuAD1.1中表现出惊人的成绩: 全部两个衡量指标上全面超越人类, 并且在11种不同NLP测试中创出SOTA表现. 包括将GLUE基准推高至80.4% (绝对改进7.6%), MultiNLI准确度达到86.7% (绝对改进5.6%). 成为NLP发展史上的里程碑式的模型成就.

BERT的架构

  • 总体架构: 如下图所示, 最左边的就是BERT的架构图, 可以很清楚的看到BERT采用了Transformer Encoder block进行连接, 因为是一个典型的双向编码模型.

bert - 图1


  • 从上面的架构图中可以看到, 宏观上BERT分三个主要模块.
    • 最底层黄色标记的Embedding模块.
    • 中间层蓝色标记的Transformer模块.
    • 最上层绿色标记的预微调模块.

  • Embedding模块: BERT中的该模块是由三种Embedding共同组成而成, 如下图

bert - 图2


  • Token Embeddings 是词嵌入张量, 第一个单词是CLS标志, 可以用于之后的分类任务.
  • Segment Embeddings 是句子分段嵌入张量, 是为了服务后续的两个句子为输入的预训练任务.
  • Position Embeddings 是位置编码张量, 此处注意和传统的Transformer不同, 不是三角函数计算的固定位置编码, 而是通过学习得出来的.
  • 整个Embedding模块的输出张量就是这3个张量的直接加和结果.

  • 双向Transformer模块: BERT中只使用了经典Transformer架构中的Encoder部分, 完全舍弃了Decoder部分. 而两大预训练任务也集中体现在训练Transformer模块中.

  • 预微调模块:
    • 经过中间层Transformer的处理后, BERT的最后一层根据任务的不同需求而做不同的调整即可.
    • 比如对于sequence-level的分类任务, BERT直接取第一个[CLS] token 的final hidden state, 再加一层全连接层后进行softmax来预测最终的标签.

  • 对于不同的任务, 微调都集中在预微调模块, 几种重要的NLP微调任务架构图展示如下

bert - 图3


  • 从上图中可以发现, 在面对特定任务时, 只需要对预微调层进行微调, 就可以利用Transformer强大的注意力机制来模拟很多下游任务, 并得到SOTA的结果. (句子对关系判断, 单文本主题分类, 问答任务(QA), 单句贴标签(NER))

  • 若干可选的超参数建议如下:


Batch size: 16, 32
Learning rate (Adam): 5e-5, 3e-5, 2e-5
Epochs: 3, 4


BERT的预训练任务

  • BERT包含两个预训练任务:
    • 任务一: Masked LM (带mask的语言模型训练)
    • 任务二: Next Sentence Prediction (下一句话预测任务)

  • 任务一: Masked LM (带mask的语言模型训练)
    • 关于传统的语言模型训练, 都是采用left-to-right, 或者left-to-right + right-to-left结合的方式, 但这种单向方式或者拼接的方式提取特征的能力有限. 为此BERT提出一个深度双向表达模型(deep bidirectional representation). 即采用MASK任务来训练模型.
    • 1: 在原始训练文本中, 随机的抽取15%的token作为参与MASK任务的对象.
    • 2: 在这些被选中的token中, 数据生成器并不是把它们全部变成[MASK], 而是有下列3种情况.
      • 2.1: 在80%的概率下, 用[MASK]标记替换该token, 比如my dog is hairy -> my dog is [MASK]
      • 2.2: 在10%的概率下, 用一个随机的单词替换token, 比如my dog is hairy -> my dog is apple
      • 2.3: 在10%的概率下, 保持该token不变, 比如my dog is hairy -> my dog is hairy
    • 3: 模型在训练的过程中, 并不知道它将要预测哪些单词? 哪些单词是原始的样子? 哪些单词被遮掩成了[MASK]? 哪些单词被替换成了其他单词? 正是在这样一种高度不确定的情况下, 反倒逼着模型快速学习该token的分布式上下文的语义, 尽最大努力学习原始语言说话的样子. 同时因为原始文本中只有15%的token参与了MASK操作, 并不会破坏原语言的表达能力和语言规则.

  • 任务二: Next Sentence Prediction (下一句话预测任务)
    • 在NLP中有一类重要的问题比如QA(Quention-Answer), NLI(Natural Language Inference), 需要模型能够很好的理解两个句子之间的关系, 从而需要在模型的训练中引入对应的任务. 在BERT中引入的就是Next Sentence Prediction任务. 采用的方式是输入句子对(A, B), 模型来预测句子B是不是句子A的真实的下一句话.
    • 1: 所有参与任务训练的语句都被选中作为句子A.
      • 1.1: 其中50%的B是原始文本中真实跟随A的下一句话. (标记为IsNext, 代表正样本)
      • 1.2: 其中50%的B是原始文本中随机抽取的一句话. (标记为NotNext, 代表负样本)
    • 2: 在任务二中, BERT模型可以在测试集上取得97%-98%的准确率.

小节总结

  • 学习了什么是BERT.
    • BERT是一个基于Transformer Encoder的预训练语言模型.
    • BERT在11种NLP测试任务中创出SOAT表现.
  • 学习了BERT的结构.
    • 最底层的Embedding模块, 包括Token Embeddings, Segment Embeddings, Position Embeddings.
    • 中间层的Transformer模块, 只使用了经典Transformer架构中的Encoder部分.
    • 最上层的预微调模块, 具体根据不同的任务类型来做相应的处理.
  • 学习了BERT的两大预训练任务.
    • MLM任务(Masked Language Model), 在原始文本中随机抽取15%的token参与任务.
      • 在80%概率下, 用[MASK]替换该token.
      • 在10%概率下, 用一个随机的单词替换该token.
      • 在10%概率下, 保持该token不变.
    • NSP任务(Next Sentence Prediction), 采用的方式是输入句子对(A, B), 模型预测句子B是不是句子A的真实的下一句话.
      • 其中50%的B是原始文本中真实跟随A的下一句话.(标记为IsNext, 代表正样本)
      • 其中50%的B是原始文本中随机抽取的一句话. (标记为NotNext, 代表负样本)



1.2 Transformer的结构是什么样的? 各个子模块各有什么作用?


学习目标

  • 掌握Encoder模块的结构和作用
  • 掌握Decoder模块的结构和作用
  • 掌握其他模块的结构和作用

Encoder模块

  • Encoder模块的结构和作用:
    • 经典的Transformer结构中的Encoder模块包含6个Encoder Block.
    • 每个Encoder Block包含一个多头自注意力层, 和一个前馈全连接层.

  • 关于Encoder Block:
    • 在Transformer架构中, 6个一模一样的Encoder Block层层堆叠在一起, 共同组成完整的Encoder, 因此剖析一个Block就可以对整个Encoder的内部结构有清晰的认识.

  • 多头自注意力层(self-attention):
    • 首先来看self-attention的计算规则图:

bert - 图4


  • 上述attention可以被描述为将query和key-value键值对的一组集合映射到输出, 输出被计算为values的加权和, 其中分配给每个value的权重由query与对应key的相似性函数计算得来. 这种attention的形式被称为Scaled Dot-Product Attention, 对应的数学公式形式如下:

bert - 图5


  • 所谓的多头self-attention层, 则是先将Q, K, V经过参数矩阵进行映射, 再做self-attention, 最后将结果拼接起来送入一个全连接层即可.

bert - 图6


  • 上述的多头self-attention, 对应的数学公式形式如下:

bert - 图7


  • 多头self-attention层的作用: 实验结果表明, Multi-head可以在更细致的层面上提取不同head的特征, 总体计算量和单一head相同的情况下, 提取特征的效果更佳.

  • 前馈全连接层模块
    • 前馈全连接层模块, 由两个线性变换组成, 中间有一个Relu激活函数, 对应的数学公式形式如下:

bert - 图8


  • 注意: 原版论文中的前馈全连接层, 输入和输出的维度均为d_model = 512, 层内的连接维度d_ff = 2048, 均采用4倍的大小关系.

  • 前馈全连接层的作用: 单纯的多头注意力机制并不足以提取到理想的特征, 因此增加全连接层来提升网络的能力.

Decoder模块

  • Decoder模块的结构和作用:
    • 经典的Transformer结构中的Decoder模块包含6个Decoder Block.
    • 每个Decoder Block包含三个子层.
      • 一个多头self-attention层
      • 一个Encoder-Decoder attention层
      • 一个前馈全连接层

  • Decoder Block中的多头self-attention层
    • Decoder中的多头self-attention层与Encoder模块一致, 但需要注意的是Decoder模块的多头self-attention需要做look-ahead-mask, 因为在预测的时候”不能看见未来的信息”, 所以要将当前的token和之后的token全部mask.

  • Decoder Block中的Encoder-Decoder attention层
    • 这一层区别于自注意力机制的Q = K = V, 此处矩阵Q来源于Decoder端经过上一个Decoder Block的输出, 而矩阵K, V则来源于Encoder端的输出, 造成了Q != K = V的情况.
    • 这样设计是为了让Decoder端的token能够给予Encoder端对应的token更多的关注.

  • Decoder Block中的前馈全连接层
    • 此处的前馈全连接层和Encoder模块中的完全一样.

  • Decoder Block中有2个注意力层的作用: 多头self-attention层是为了拟合Decoder端自身的信息, 而Encoder-Decoder attention层是为了整合Encoder和Decoder的信息.

Add & Norm模块

  • Add & Norm模块接在每一个Encoder Block和Decoder Block中的每一个子层的后面. 具体来说Add表示残差连接, Norm表示LayerNorm.
    • 对于每一个Encoder Block, 里面的两个子层后面都有Add & Norm.
    • 对于每一个Decoder Block, 里面的三个子层后面都有Add & Norm.
    • 具体的数学表达形式为: LayerNorm(x + Sublayer(x)), 其中Sublayer(x)为子层的输出.

  • Add残差连接的作用: 和其他神经网络模型中的残差连接作用一致, 都是为了将信息传递的更深, 增强模型的拟合能力. 试验表明残差连接的确增强了模型的表现.

  • Norm的作用: 随着网络层数的额增加, 通过多层的计算后参数可能会出现过大, 过小, 方差变大等现象, 这会导致学习过程出现异常, 模型的收敛非常慢. 因此对每一层计算后的数值进行规范化可以提升模型的表现.

位置编码器Positional Encoding

  • Transformer中直接采用正弦函数和余弦函数来编码位置信息, 如下图所示:

bert - 图9


  • 需要注意: 三角函数应用在此处的一个重要的优点, 因为对于任意的PE(pos+k), 都可以表示为PE(pos)的线性函数, 大大方便计算. 而且周期性函数不受序列长度的限制, 也可以增强模型的泛化能力.

bert - 图10


小节总结

  • Encoder模块
    • 经典的Transformer架构中的Encoder模块包含6个Encoder Block.
    • 每个Encoder Block包含两个子模块, 分别是多头自注意力层, 和前馈全连接层.
      • 多头自注意力层采用的是一种Scaled Dot-Product Attention的计算方式, 实验结果表明, Mul ti-head可以在更细致的层面上提取不同head的特征, 比单一head提取特征的效果更佳.
      • 前馈全连接层是由两个全连接层组成, 线性变换中间增添一个Relu激活函数, 具体的维度采用4倍关系, 即多头自注意力的d_model=512, 则层内的变换维度d_ff=2048.
  • 2: Decoder模块
    • 经典的Transformer架构中的Decoder模块包含6个Decoder Block.
    • 每个Decoder Block包含3个子模块, 分别是多头自注意力层, Encoder-Decoder Attention层, 和前馈全连接层.
      • 多头自注意力层采用和Encoder模块一样的Scaled Dot-Product Attention的计算方式, 最大的 区别在于需要添加look-ahead-mask, 即遮掩”未来的信息”.
      • Encoder-Decoder Attention层和上一层多头自注意力层最主要的区别在于Q != K = V, 矩阵Q来源于上一层Decoder Block的输出, 同时K, V来源于Encoder端的输出.
      • 前馈全连接层和Encoder中完全一样.
  • 3: Add & Norm模块
    • Add & Norm模块接在每一个Encoder Block和Decoder Block中的每一个子层的后面.
    • 对于每一个Encoder Block, 里面的两个子层后面都有Add & Norm.
    • 对于每一个Decoder Block, 里面的三个子层后面都有Add & Norm.
    • Add表示残差连接, 作用是为了将信息无损耗的传递的更深, 来增强模型的拟合能力.
    • Norm表示LayerNorm, 层级别的数值标准化操作, 作用是防止参数过大过小导致的学习过程异常 , 模型收敛特别慢的问题.
  • 4: 位置编码器Positional Encoding
    • Transformer中采用三角函数来计算位置编码.
    • 因为三角函数是周期性函数, 不受序列长度的限制, 而且这种计算方式可以对序列中不同位置的编码的重要程度同等看待.



1.3 Transformer结构中的Decoder端具体输入是什么? 在训练阶段和预测阶段一致吗?


学习目标

  • 掌握Transformer结构中的Decoder端的输入张量特点和含义.
  • 掌握Decoder在训练阶段的输入是什么.
  • 掌握Decoder在预测阶段的输入是什么.

Decoder端的输入解析

  • Decoder端的架构: Transformer原始论文中的Decoder模块是由N=6个相同的Decoder Block堆叠而成, 其中每一个Block是由3个子模块构成, 分别是多头self-attention模块, Encoder-Decoder attention模块, 前馈全连接层模块.

  • 6个Block的输入不完全相同:
    • 最下面的一层Block接收的输入是经历了MASK之后的Decoder端的输入 + Encoder端的输出.
    • 其他5层Block接收的输入模式一致, 都是前一层Block的输出 + Encoder端的输出.

  • Decoder在训练阶段的输入解析:
    • 从第二层Block到第六层Block的输入模式一致, 无需特殊处理, 都是固定操作的循环处理.
    • 聚焦在第一层的Block上: 训练阶段每一个time step的输入是上一个time step的输入加上真实标签序列向后移一位. 具体来说, 假设现在的真实标签序列等于”How are you?”, 当time step=1时, 输入张量为一个特殊的token, 比如”SOS”; 当time step=2时, 输入张量为”SOS How”; 当time step=3时, 输入张量为”SOS How are”, 以此类推…
    • 注意: 在真实的代码实现中, 训练阶段不会这样动态输入, 而是一次性的把目标序列全部输入给第一层的Block, 然后通过多头self-attention中的MASK机制对序列进行同样的遮掩即可.

  • Decoder在预测阶段的输入解析:
    • 同理于训练阶段, 预测时从第二层Block到第六层Block的输入模式一致, 无需特殊处理, 都是固定操作的循环处理.
    • 聚焦在第一层的Block上: 因为每一步的输入都会有Encoder的输出张量, 因此这里不做特殊讨论, 只专注于纯粹从Decoder端接收的输入. 预测阶段每一个time step的输入是从time step=0, input_tensor=”SOS”开始, 一直到上一个time step的预测输出的累计拼接张量. 具体来说:
      • 当time step=1时, 输入的input_tensor=”SOS”, 预测出来的输出值是output_tensor=”What”;
      • 当time step=2时, 输入的input_tensor=”SOS What”, 预测出来的输出值是output_tensor=”is”;
      • 当time step=3时, 输入的input_tensor=”SOS What is”, 预测出来的输出值是output_tensor=”the”;
      • 当time step=4时, 输入的input_tensor=”SOS What is the”, 预测出来的输出值是output_tensor=”matter”;
      • 当time step=5时, 输入的input_tensor=”SOS What is the matter”, 预测出来的输出值是output_tensor=”?”;
      • 当time step=6时, 输入的input_tensor=”SOS What is the matter ?”, 预测出来的输出值是output_tensor=”EOS”, 代表句子的结束符, 说明解码结束, 预测结束.

小节总结

  • 1: 在Transformer结构中的Decoder模块的输入, 区分于不同的Block, 最底层的Block输入有其特殊的地方. 第二层到第六层的输入一致, 都是上一层的输出和Encoder的输出.
  • 2: 最底层的Block在训练阶段, 每一个time step的输入是上一个time step的输入加上真实标签序列向后移一位. 具体来看, 就是每一个time step的输入序列会越来越长, 不断的将之前的输入融合进来.
  • 3: 最底层的Block在训练阶段, 真实的代码实现中, 采用的是MASK机制来模拟输入序列不断添加的过程.
  • 4: 最底层的Block在预测阶段, 每一个time step的输入是从time step=0开始, 一直到上一个time step的预测值的累积拼接张量. 具体来看, 也是随着每一个time step的输入序列会越来越长. 相比于训练阶段最大的不同是这里不断拼接进来的token是每一个time step的预测值, 而不是训练阶段每一个time step取得的groud truth值.



1.4 Transformer中一直强调的self-attention是什么? 为什么能发挥如此大的作用? 计算的时候如果不使用三元组(Q, K, V), 而仅仅使用(Q, V)或者(K, V)或者(V)行不行?


学习目标

  • 掌握self-attention的机制和原理.
  • 掌握为什么要使用三元组(Q, K, V)来计算self-attention.

self-attention的机制和原理

  • self-attention是一种通过自身和自身进行关联的attention机制, 从而得到更好的representation来表达自身.
  • self-attention是attention机制的一种特殊情况:
    • 在self-attention中, Q=K=V, 序列中的每个单词(token)都和该序列中的其他所有单词(token)进行attention规则的计算.
  • attention机制计算的特点在于, 可以直接跨越一句话中不同距离的token, 可以远距离的学习到序列的知识依赖和语序结构.

bert - 图11


  • 从上图中可以看到, self-attention可以远距离的捕捉到语义层面的特征(its的指代对象是Law).
  • 应用传统的RNN, LSTM, 在获取长距离语义特征和结构特征的时候, 需要按照序列顺序依次计算, 距离越远的联系信息的损耗越大, 有效提取和捕获的可能性越小.
  • 但是应用self-attention时, 计算过程中会直接将句子中任意两个token的联系通过一个计算步骤直接联系起来,

  • 关于self-attention为什么要使用(Q, K, V)三元组而不是其他形式:
    • 首先一条就是从分析的角度看, 查询Query是一条独立的序列信息, 通过关键词Key的提示作用, 得到最终语义的真实值Value表达, 数学意义更充分, 完备.
    • 这里不使用(K, V)或者(V)没有什么必须的理由, 也没有相关的论文来严格阐述比较试验的结果差异, 所以可以作为开放性问题未来去探索, 只要明确在经典self-attention实现中用的是三元组就好.

小节总结

  • self-attention机制的重点是使用三元组(Q, K, V)参与规则运算, 这里面Q=K=V.
  • self-attention最大的优势是可以方便有效的提取远距离依赖的特征和结构信息, 不必向RNN那样依次计算产生传递损耗.
  • 关于self-attention采用三元组的原因, 经典实现的方式数学意义明确, 理由充分, 至于其他方式的可行性暂时没有论文做充分的对比试验研究.



1.5 Transformer为什么需要进行Multi-head Attention? Multi-head Attention的计算过程是什么?


学习目标

  • 掌握Transformer中应用多头注意力的原因.
  • 掌握Transformer中多头注意力的计算方式.

采用Multi-head Attention的原因

  • 1: 原始论文中提到进行Multi-head Attention的原因是将模型分为多个头, 可以形成多个子空间, 让模型去关注不同方面的信息, 最后再将各个方面的信息综合起来得到更好的效果.
  • 2: 多个头进行attention计算最后再综合起来, 类似于CNN中采用多个卷积核的作用, 不同的卷积核提取不同的特征, 关注不同的部分, 最后再进行融合.
  • 3: 直观上讲, 多头注意力有助于神经网络捕捉到更丰富的特征信息.

Multi-head Attention的计算方式

  • 1: Multi-head Attention和单一head的Attention唯一的区别就在于, 其对特征张量的最后一个维度进行了分割, 一般是对词嵌入的embedding_dim=512进行切割成head=8, 这样每一个head的嵌入维度就是512/8=64, 后续的Attention计算公式完全一致, 只不过是在64这个维度上进行一系列的矩阵运算而已.
  • 2: 在head=8个头上分别进行注意力规则的运算后, 简单采用拼接concat的方式对结果张量进行融合就得到了Multi-head Attention的计算结果.

小节总结

  • 学习了Transformer架构采用Multi-head Attention的原因.
    • 将模型划分为多个头, 分别进行Attention计算, 可以形成多个子空间, 让模型去关注不同方面的信息特征, 更好的提升模型的效果.
    • 多头注意力有助于神经网络捕捉到更丰富的特征信息.
  • 学习了Multi-head Attention的计算方式.
    • 对特征张量的最后一个维度进行了分割, 一般是对词嵌入的维度embedding_dim进行切割, 切割后的计算规则和单一head完全一致.
    • 在不同的head上应用了注意力计算规则后, 得到的结果张量直接采用拼接concat的方式进行融合, 就得到了Multi-head Attention的结果张量.



1.6 Transformer相比于RNN/LSTM有什么优势? 为什么?


学习目标

  • 掌握Transformer相比于RNN/LSTM的优势和背后的原因.

Transformer的并行计算

  • 对于Transformer比传统序列模型RNN/LSTM具备优势的第一大原因就是强大的并行计算能力.
    • 对于RNN来说, 任意时刻t的输入是时刻t的输入x(t)和上一时刻的隐藏层输出h(t-1), 经过运算后得到当前时刻隐藏层的输出h(t), 这个h(t)也即将作为下一时刻t+1的输入的一部分. 这个计算过程是RNN的本质特征, RNN的历史信息是需要通过这个时间步一步一步向后传递的. 而这就意味着RNN序列后面的信息只能等到前面的计算结束后, 将历史信息通过hidden state传递给后面才能开始计算, 形成链式的序列依赖关系, 无法实现并行.
    • 对于Transformer结构来说, 在self-attention层, 无论序列的长度是多少, 都可以一次性计算所有单词之间的注意力关系, 这个attention的计算是同步的, 可以实现并行.

Transformer的特征抽取能力

  • 对于Transformer比传统序列模型RNN/LSTM具备优势的第二大原因就是强大的特征抽取能力.
    • Transformer因为采用了Multi-head Attention结构和计算机制, 拥有比RNN/LSTM更强大的特征抽取能力, 这里并不仅仅由理论分析得来, 而是大量的试验数据和对比结果, 清楚的展示了Transformer的特征抽取能力远远胜于RNN/LSTM.
    • 注意: 不是越先进的模型就越无敌, 在很多具体的应用中RNN/LSTM依然大有用武之地, 要具体问题具体分析.

小节总结

  • 学习了Transformer相比于RNN/LSTM的优势和原因.
    • 1: 第一大优势是并行计算的优势.
    • 2: 第二大优势是特征提取能力强.



1.7 为什么说Transformer可以代替seq2seq?


学习目标

  • 掌握Transformer可以替代seq2seq的核心原因.

seq2seq的两大缺陷

  • 1: seq2seq架构的第一大缺陷是将Encoder端的所有信息压缩成一个固定长度的语义向量中, 用这个固定的向量来代表编码器端的全部信息. 这样既会造成信息的损耗, 也无法让Decoder端在解码的时候去用注意力聚焦哪些是更重要的信息.
  • 2: seq2seq架构的第二大缺陷是无法并行, 本质上和RNN/LSTM无法并行的原因一样.

Transformer的改进

  • Transformer架构同时解决了seq2seq的两大缺陷, 既可以并行计算, 又应用Multi-head Attention机制来解决Encoder固定编码的问题, 让Decoder在解码的每一步可以通过注意力去关注编码器输出中最重要的那些部分.

小节总结

  • 学习了seq2seq架构的两大缺陷.
    • 第一个缺陷是Encoder端的所有信息被压缩成一个固定的输出张量, 当序列长度较长时会造成比较严重的信息损耗.
    • 第二个缺陷是无法并行计算.
  • 学习了Transformer架构对seq2seq两大缺陷的改进.
    • Transformer应用Multi-head Attention机制让编码器信息可以更好的展示给解码器.
    • Transformer可以实现Encoder端的并行计算.



1.8 self-attention公式中的归一化有什么作用? 为什么要添加scaled?


学习目标

  • 理解softmax函数的输入是如何影响输出分布的.
  • 理解softmax函数反向传播进行梯度求导的数学过程.
  • 理解softmax函数出现梯度消失的原因.
  • 理解self-attention计算规则中归一化的原因.

self-attention中的归一化概述

  • 训练上的意义: 随着词嵌入维度d_k的增大, q * k 点积后的结果也会增大, 在训练时会将softmax函数推入梯度非常小的区域, 可能出现梯度消失的现象, 造成模型收敛困难.
  • 数学上的意义: 假设q和k的统计变量是满足标准正态分布的独立随机变量, 意味着q和k满足均值为0, 方差为1. 那么q和k的点积结果就是均值为0, 方差为d_k, 为了抵消这种方差被放大d_k倍的影响, 在计算中主动将点积缩放1/sqrt(d_k), 这样点积后的结果依然满足均值为0, 方差为1.

softmax的梯度变化

  • 这里我们分3个步骤来解释softmax的梯度问题:
    • 第一步: softmax函数的输入分布是如何影响输出的.
    • 第二步: softmax函数在反向传播的过程中是如何梯度求导的.
    • 第三步: softmax函数出现梯度消失现象的原因.

  • 第一步: softmax函数的输入分布是如何影响输出的.
    • 对于一个输入向量x, softmax函数将其做了一个归一化的映射, 首先通过自然底数e将输入元素之间的差距先”拉大”, 然后再归一化为一个新的分布. 在这个过程中假设某个输入x中最大的元素下标是k, 如果输入的数量级变大(就是x中的每个分量绝对值都很大), 那么在数学上会造成y_k的值非常接近1.
    • 具体用一个例子来演示, 假设输入的向量x = [a, a, 2a], 那么随便给几个不同数量级的值来看看对y3产生的影响


a = 1时, y3 = 0.5761168847658291
a = 10时, y3 = 0.9999092083843412
a = 100时, y3 = 1.0


  • 采用一段实例代码将a在不同取值下, 对应的y3全部画出来, 以曲线的形式展示:


from math import exp
from matplotlib import pyplot as plt
import numpy as np
f = lambda x: exp(x 2) / (exp(x) + exp(x) + exp(x 2))
x = np.linspace(0, 100, 100)
y_3 = [f(x_i) for x_i in x]
plt.plot(x, y_3)
plt.show()


  • 得到如下的曲线:

bert - 图12


  • 从上图可以很清楚的看到输入元素的数量级对softmax最终的分布影响非常之大.
  • 结论: 在输入元素的数量级较大时, softmax函数几乎将全部的概率分布都分配给了最大值分量所对应的标签.

  • 第二步: softmax函数在反向传播的过程中是如何梯度求导的.
    • 首先定义神经网络的输入和输出:

bert - 图13


  • 反向传播就是输出端的损失函数对输入端求偏导的过程, 这里要分两种情况, 第一种如下所示:

bert - 图14


  • 第二种如下所示:

bert - 图15


  • 经过对两种情况分别的求导计算, 可以得出最终的结论如下:

bert - 图16


  • 第三步: softmax函数出现梯度消失现象的原因.
    • 根据第二步中softmax函数的求导结果, 可以将最终的结果以矩阵形式展开如下:

bert - 图17


  • 根据第一步中的讨论结果, 当输入x的分量值较大时, softmax函数会将大部分概率分配给最大的元素, 假设最大元素是x1, 那么softmax的输出分布将产生一个接近one-hot的结果张量y_ = [1, 0, 0,…, 0], 此时结果矩阵变为:

bert - 图18


  • 结论: 综上可以得出, 所有的梯度都消失为0(接近于0), 参数几乎无法更新, 模型收敛困难.


维度与点积大小的关系

  • 针对为什么维度会影响点积的大小, 原始论文中有这样的一点解释如下:


To illustrate why the dot products get large, assume that the components of q and k
are independent random variables with mean 0 and variance 1. Then their doct product,
q*k = (q1k1+q2k2+……+q(d_k)k(d_k)), has mean 0 and variance d_k.


  • 我们分两步对其进行一个推导, 首先就是假设向量q和k的各个分量是相互独立的随机变量, X = q_i, Y = k_i, X和Y各自有d_k个分量, 也就是向量的维度等于d_k, 有E(X) = E(Y) = 0, 以及D(X) = D(Y) = 1.
  • 可以得到E(XY) = E(X)E(Y) = 0 * 0 = 0
  • 同理, 对于D(XY)推导如下:

bert - 图19


  • 根据期望和方差的性质, 对于互相独立的变量满足下式:

bert - 图20


  • 根据上面的公式, 可以很轻松的得出q*k的均值为E(qk) = 0, D(qk) = d_k.
  • 所以方差越大, 对应的qk的点积就越大, 这样softmax的输出分布就会更偏向最大值所在的分量.
  • 一个技巧就是将点积除以sqrt(d_k), 将方差在数学上重新”拉回1”, 如下所示:

bert - 图21


  • 最终的结论: 通过数学上的技巧将方差控制在1, 也就有效的控制了点积结果的发散, 也就控制了对应的梯度消失的问题!

小节总结

  • 1: 学习了softmax函数的输入是如何影响输出分布的.
    • softmax函数本质是对输入的数据分布做一次归一化处理, 但是输入元素的数量级对softmax最终的分布影响非常之大.
    • 在输入元素的数量级较大时, softmax函数几乎将全部的概率分布都分配给了最大值分量所对应的标签.
  • 2: 学习了softmax函数在反向传播的过程中是如何梯度求导的.
    • 具体的推导过程见讲义正文部分, 注意要分两种情况讨论, 分别处理.
  • 3: 学习了softmax函数出现梯度消失现象的原因.
    • 结合第一步, 第二步的结论, 可以很清楚的看到最终的梯度矩阵接近于零矩阵, 这样在进行参数更新的时候就会产生梯度消失现象.
  • 4: 学习了维度和点积大小的关系推导.
    • 通过期望和方差的推导理解了为什么点积会造成方差变大.
    • 理解了通过数学技巧除以sqrt(d_k)就可以让方差恢复成1.



1.9 Transformer架构的并行化是如何进行的? 具体体现在哪里?


学习目标

  • 掌握Transformer架构的并行化是如何进行的.
  • 理解为什么采用这样的方式可以实现Transformer的并行化.

Transformer架构中Encoder的并行化

  • 首先Transformer的并行化主要体现在Encoder模块上.

bert - 图22


  • 1: 上图最底层绿色的部分, 整个序列所有的token可以并行的进行Embedding操作, 这一层的处理是没有依赖关系的.
  • 2: 上图第二层土黄色的部分, 也就是Transformer中最重要的self-attention部分, 这里对于任意一个单词比如x1, 要计算x1对于其他所有token的注意力分布, 得到z1. 这个过程是具有依赖性的, 必须等到序列中所有的单词完成Embedding才可以进行. 因此这一步是不能并行处理的. 但是从另一个角度看, 我们真实计算注意力分布的时候, 采用的都是矩阵运算, 也就是可以一次性的计算出所有token的注意力张量, 从这个角度看也算是实现了并行, 只是矩阵运算的”并行”和词嵌入的”并行”概念上不同而已.
  • 3: 上图第三层蓝色的部分, 也就是前馈全连接层, 对于不同的向量z之间也是没有依赖关系的, 所以这一层是可以实现并行化处理的. 也就是所有的向量z输入Feed Forward网络的计算可以同步进行, 互不干扰.

Transformer架构中Decoder的并行化

  • 其次Transformer的并行化也部分的体现在Decoder模块上.

bert - 图23


  • 1: Decoder模块在训练阶段采用了并行化处理. 其中Self-Attention和Encoder-Decoder Attention两个子层的并行化也是在进行矩阵乘法, 和Encoder的理解是一致的. 在进行Embedding和Feed Forward的处理时, 因为各个token之间没有依赖关系, 所以也是可以完全并行化处理的, 这里和Encoder的理解也是一致的.
  • 2: Decoder模块在预测阶段基本上不认为采用了并行化处理. 因为第一个time step的输入只是一个”SOS”, 后续每一个time step的输入也只是依次添加之前所有的预测token.
  • 3: 注意: 最重要的区别是训练阶段目标文本如果有20个token, 在训练过程中是一次性的输入给Decoder端, 可以做到一些子层的并行化处理. 但是在预测阶段, 如果预测的结果语句总共有20个token, 则需要重复处理20次循环的过程, 每次的输入添加进去一个token, 每次的输入序列比上一次多一个token, 所以不认为是并行处理.

小节总结

  • 学习了Transformer架构中Encoder模块的并行化机制.
    • Encoder模块在训练阶段和测试阶段都可以实现完全相同的并行化.
    • Encoder模块在Embedding层, Feed Forward层, Add & Norm层都是可以并行化的.
    • Encoder模块在self-attention层, 因为各个token之间存在依赖关系, 无法独立计算, 不是真正意义上的并行化.
    • Encoder模块在self-attention层, 因为采用了矩阵运算的实现方式, 可以一次性的完成所有注意力张量的计算, 也是另一种”并行化”的体现.
  • 学习了Transformer架构中Decoder模块的并行化机制.
    • Decoder模块在训练阶段可以实现并行化.
    • Decoder模块在训练阶段的Embedding层, Feed Forward层, Add & Norm层都是可以并行化的.
    • Decoder模块在self-attention层, 以及Encoder-Decoder Attention层, 因为各个token之间存在依赖关系, 无法独立计算, 不是真正意义上的并行化.
    • Decoder模块在self-attention层, 以及Encoder-Decoder Attention层, 因为采用了矩阵运算的实现方式, 可以一次性的完成所有注意力张量的计算, 也是另一种”并行化”的体现.
    • Decoder模块在预测计算不能并行化处理.



1.10 BERT模型的优点和缺点?


学习目标

  • 理解BERT模型的优点和原因.
  • 理解BERT模型的缺点和原因.

BERT的优点

  • 1: 通过预训练, 加上Fine-tunning, 在11项NLP任务上取得最优结果.
  • 2: BERT的根基源于Transformer, 相比传统RNN更加高效, 可以并行化处理同时能捕捉长距离的语义和结构依赖.
  • 3: BERT采用了Transformer架构中的Encoder模块, 不仅仅获得了真正意义上的bidirectional context, 而且为后续微调任务留出了足够的调整空间.

BERT的缺点

  • 1: BERT模型过于庞大, 参数太多, 不利于资源紧张的应用场景, 也不利于上线的实时处理.
  • 2: BERT目前给出的中文模型中, 是以字为基本token单位的, 很多需要词向量的应用无法直接使用. 同时该模型无法识别很多生僻词, 只能以UNK代替.
  • 3: BERT中第一个预训练任务MLM中, [MASK]标记只在训练阶段出现, 而在预测阶段不会出现, 这就造成了一定的信息偏差, 因此训练时不能过多的使用[MASK], 否则会影响模型的表现.
  • 4: 按照BERT的MLM任务中的约定, 每个batch数据中只有15%的token参与了训练, 被模型学习和预测, 所以BERT收敛的速度比left-to-right模型要慢很多(left-to-right模型中每一个token都会参与训练).

小节总结

  • 学习了BERT模型的3个优点:
    • 在11个NLP任务上取得SOAT成绩.
    • 利用了Transformer的并行化能力以及长语句捕捉语义依赖和结构依赖.
    • BERT实现了双向Transformer并为后续的微调任务留出足够的空间.
  • 学习了BERT模型的4个缺点:
    • BERT模型太大, 太慢.
    • BERT模型中的中文模型是以字为基本token单位的, 无法利用词向量, 无法识别生僻词.
    • BERT模型中的MLM任务, [MASK]标记在训练阶段出现, 预测阶段不出现, 这种偏差会对模型有一定影响.
    • BERT模型的MLM任务, 每个batch只有15%的token参与了训练, 造成大量文本数据的”无用”, 收敛速度慢, 需要的算力和算时都大大提高.



1.11 BERT的MLM任务中为什么采用了80%, 10%, 10%的策略?


学习目标

  • 理解在MLM任务中采用80%, 10%, 10%策略的原因.

MLM任务中的策略约定分析

  • 1: 首先, 如果所有参与训练的token被100%的[MASK], 那么在fine-tunning的时候所有单词都是已知的, 不存在[MASK], 那么模型就只能根据其他token的信息和语序结构来预测当前词, 而无法利用到这个词本身的信息, 因为它们从未出现在训练过程中, 等于模型从未接触到它们的信息, 等于整个语义空间损失了部分信息. 采用80%的概率下应用[MASK], 既可以让模型去学着预测这些单词, 又以20%的概率保留了语义信息展示给模型.
  • 2: 保留下来的信息如果全部使用原始token, 那么模型在预训练的时候可能会偷懒, 直接照抄当前token信息. 采用10%概率下random token来随机替换当前token, 会让模型不能去死记硬背当前的token, 而去尽力学习单词周边的语义表达和远距离的信息依赖, 尝试建模完整的语言信息.
  • 3: 最后再以10%的概率保留原始的token, 意义就是保留语言本来的面貌, 让信息不至于完全被遮掩, 使得模型可以”看清”真实的语言面貌.

小节总结

  • BERT中MLM任务中的[MASK]是以一种显示的方式告诉模型”这个词我不告诉你, 你自己从上下文里猜”, 非常类似于同学们在做完形填空. 如果[MASK]意外的部分全部都用原始token, 模型会学习到”如果当前词是[MASK], 就根据其他词的信息推断这个词; 如果当前词是一个正常的单词, 就直接照抄”. 这样一来, 到了fine-tunning阶段, 所有单词都是正常单词了, 模型就会照抄所有单词, 不再提取单词之间的依赖关系了.
  • BERT中MLM任务以10%的概率填入random token, 就是让模型时刻处于”紧张情绪”中, 让模型搞不清楚当前看到的token是真实的单词还是被随机替换掉的单词, 这样模型在任意的token位置就只能把当前token的信息和上下文信息结合起来做综合的判断和建模. 这样一来, 到了fine-tunning阶段, 模型也会同时提取这两方面的信息, 因为模型”心理很紧张”, 它不知道当前看到的这个token, 所谓的”正常单词”到底有没有”提前被动过手脚”.



1.12 长文本预测任务如果想用BERT来实现, 要如何构造训练样本?


学习目标

  • 掌握利用BERT处理长文本的任务如何构造训练样本.

BERT处理长文本的方法

  • 首选要明确一点, BERT预训练模型所接收的最大sequence长度是512.
  • 那么对于长文本(文本长度超过512的句子), 就需要特殊的方式来构造训练样本. 核心就是如何进行截断.
    • 1: head-only方式: 这是只保留长文本头部信息的截断方式, 具体为保存前510个token (要留两个位置给[CLS]和[SEP]).
    • 2: tail-only方式: 这是只保留长文本尾部信息的截断方式, 具体为保存最后510个token (要留两个位置给[CLS]和[SEP]).
    • 3: head+only方式: 选择前128个token和最后382个token (文本总长度在800以内), 或者前256个token和最后254个token (文本总长度大于800).

小节总结

  • 学习了长文本处理如果要利用BERT的话, 需要进行截断处理.
    • 第一种方式就是只保留前面510个token.
    • 第二种方式就是只保留后面510个token.
    • 第三种方式就是前后分别保留一部分token, 总数是510.

2. ELMo, GPT等经典模型的介绍与对比

2.1 认识ELMo


学习目标

  • 了解什么是ELMo.
  • 掌握ELMo的架构.
  • 掌握ELMo的预训练任务.
  • 了解ELMo的效果和成绩.
  • 了解ELMo的优缺点.

什么是ELMo

  • ELMo是2018年3月由华盛顿大学提出的一种预训练模型.
    • ELMo的全称是Embeddings from Language Models.
    • ELMo模型的提出源于论文<< Deep Contextualized Word Representations >>.
    • ELMo模型提出的动机源于研究人员认为一个好的预训练语言模型应该能够包含丰富的句法和语义信息, 并且能够对多义词进行建模. 而传统的词向量(2013年的word2vec, 2014年的GloVe)都是上下文无关的, 也就是固定的词向量. 最典型的例子就是”apple”在不同的语境下, 应该可以表示水果或公司, 但是固定的词向量显然无法做到这一点. 因为研究团队利用新的语言模型训练一个上下文相关的预训练模型, 成为ELMo, 并在6个NLP任务上获得提升.

ELMo的架构

  • 总体架构: 如下图所示就是ELMo的架构图.

bert - 图24


  • 从上面的架构图中可以看到, 宏观上ELMo分三个主要模块.
    • 最底层黄色标记的Embedding模块.
    • 中间层蓝色标记的两部分双层LSTM模块.
    • 最上层绿色标记的词向量表征模块.

  • Embedding模块: ELMo最底层的词嵌入采用CNN对字符级进行编码, 本质就是获得一个静态的词嵌入向量作为网络的底层输入.

  • 两部分的双层LSTM模块:
    • 这是整个ELMo中最重要的部分, 架构中分成左侧的前向LSTM网络, 和右侧的反向LSTM网络.
    • ELMo的做法是我们只预训练一个Language Model, 而word embedding是通过输入的句子实时给出的, 这样单词的嵌入向量就包含了上下文的信息, 也就彻底改变了Word2Vec和GloVe的静态词向量的做法.

  • ELMo的这一模块分为左右两部分, 本质上就是一个双向LM, 对于左半部分, 给定了N个tokens(t1, t2, …, tN), Language Model通过前面k-1个位置的token序列来计算第k个token出现的概率, 构成前向双层LSTM模型.

bert - 图25


  • 同理, 对于架构中的右半部分, 给定了N个tokens(t(k+1), t(k+2), …, t(N)), Language Model通过后面N-k个位置的token序列来计算第k个token出现的概率, 构成后向双层LSTM模型.

bert - 图26


  • ELMo在训练过程中的目标函数就是最大化下面的公式:

bert - 图27


  • 词向量表征模块:
    • 因为ELMo是个语言模型, 对于每个token, 通过一个L层的双向LSTM网络可以计算出2L+1个表示向量如下:

bert - 图28


  • 从上面的公式可以清楚的看到, 有3个不同的组成部分, 第一个就是对token直接进行CNN编码的结果, 也是ELMo最底层模块的输出; 第二个就是前向LSTM的输出结果, 每一层都会有一个输出, 总共L层就会有L个输出; 第三个就是后向LSTM的输出结果, 每一层都会有一个输出, 总共L层就会有L个输出; 综合三部分的输出加在一起, 就是2L+1个输出向量.

  • 通过整个网络, 每一个token得到了2L+1个表示向量, 但是我们希望每一个token能对应一个向量. 最简单的做法就是取最上层的输出结果作为token的表示向量, 更通用的做法是加入若干参数来融合所有层的信息, 如下所示:

bert - 图29


  • 上式的意思是对于2L+1个向量, 每一个前面都加上一个权重稀疏, 然后直接融合成一个向量, 最后再乘一个系数作为最终该token的词向量.
  • 原始论文中提到最前面的那个系数, 在不同任务中取不同的值效果会有较大的差异, 需要注意在SQuAD中设置为0.01取得的效果要好于设置为1.
  • 原始论文中在进行底层token编码时, 用CNN形成了一个512维的列向量, 也就是初始嵌入维度等于512. 中间层使用了双层的LSTM分别进行前向编码和后向编码, 每层的单个LSTM输入维度是512, 输出维度也是512, 保持一致. 因为是双向编码并且分左右两部分, 所以每层的输出维度是512*2=1024, 最后进行权重融合后的向量维度就是1024.

ELMo的预训练任务

  • ELMo的本质思想:
    • 首先用一个语言模型学好一个单词的word embedding, 此时是无法区分多义词的, 但没关系. 当实际使用word embedding的时候, 该单词已经具备了特定的上下文信息, 这个时候可以根据上下文单词的语义去调整单词的word embedding表示, 这样经过调整后得到的word embedding向量就可以准确的表达单词在当前上下文中的真实含义了, 也就自然的解决了多义词问题.
    • 结论就是ELMo模型是个根据当前上下文对word embedding动态调整的语言模型.

  • ELMo的预训练采用了典型的两阶段过程:
    • 第一阶段: 利用语言模型进行预训练.
    • 第二阶段: 在做下游任务时, 从预训练网络中提取对应单词的网络各层的word embedding作为新特征补充到下游任务中.

  • 第一阶段: 语言模型预训练.
    • 再次回到ELMo的总体架构图, 网络结构采用了双层双向LSTM.

  • 目前语言模型训练的任务目标是根据单词Wi的上下文去正确预测单词Wi, Wi之前的单词序列context-before称为上文, Wi之后的单词序列context-after称为下文.

  • 架构图上左侧的前向双层LSTM代表正方向编码器, 输入的是从左向右顺序的除了预测单词Wi之外的上文context-before; 右侧的反向双层LSTM代表反方向编码器, 输入的是从右向左的逆序的下文context-after;

  • 每个编码器的深度都是L=2, 即双层LSTM叠加.

  • 使用上述的网络结构利用大量语料做语言模型任务就能预训练好这个网络. 当输入一个新句子S_new时, 句子中每个单词都能得到对应的3个embedding向量: 1-最底层的单词的word embedding. 2-中间第一层双向LSTM中对应单词位置的embedding, 这层编码对应单词的句法信息更多一些. 3-中间第二层双向LSTM中对应单词位置的embedding, 这层编码对应单词的语义信息更多一些.

  • ELMo的预训练过程不仅仅学会了单词的word embedding, 还学习了一个双层双向的LSTM网络, 这两者后续都会用到, 是整个ELMo预训练的两大产出结果.

  • 第二阶段: 下游任务的调整.
    • 比如我们的下游任务是QA问题.

  • 对于问句X, 可以先将句子X作为预训练好的ELMo网络的输入, 这样X中每个单词在ELMo中都能获得3个对应的embedding向量. 之后赋给这3个向量各自一个权重a, 这个权重a既可以是学习得来的也可以是最简单的平均分布赋值, 然后把3个向量加权求和, 整个成一个词向量. 最后将整合后的词向量作为X在自己任务的那个网络结构中对应单词的输入, 以此作为新特征补充进下游任务中. 对于回答Y可以同样处理.

  • 因为ELMo给下游提供的是每个单词的特征形式, 所以这一类预训练方法被称为”Feature-based Pre-Training”.

ELMo模型的效果

  • ELMo对于多义词问题的解决结果:

bert - 图30


  • 前面提到静态的word embedding无法解决多义词的问题, 那么ELMo引入上下文动态语义调整后的embedding word可以解决多义词问题吗? 答案正如上图所示, 而且比我们期待的解决效果要更好.

  • 上图中的例子, 对于GloVe训练出来的word embedding来说, 多义词比如play, 根据它的embedding找出最接近其语义的单词, 发现结果集合几乎全部都在体育领域, 这很明显是因为训练数据中包含play的语句中体育领域的数量明显占多数导致的.

  • 再来看使用ELMo后的效果, 根据上下文动态调整后的embedding word不仅仅能找出对应于”play”:”演出”的相同语义的句子, 而且还可以保证找出的句子中的play对应的词性也是相同的, 这真的是超出期待之外的惊喜!

  • 原始论文中提到ELMo的试验效果, 在6个NLP主流任务中性能都有不同幅度的提升, 最高的提升达到25%, 任务的覆盖范围很广, 包含句子语义关系判断, 分类任务, 阅读理解等等.

ELMo的待改进点

  • ELMo在传统静态word embedding方法(Word2Vec, GloVe)的基础上提升了很多, 但是依然存在缺陷, 有很大的改进余地.
    • 第一点: 一个很明显的缺点在于特征提取器的选择上, ELMo使用了双向双层LSTM, 而不是现在横扫千军的Transformer, 在特征提取能力上肯定是要弱一些的. 设想如果ELMo的提升提取器选用Transformer, 那么后来的BERT的反响将远不如当时那么火爆了.
    • 第二点: ELMo选用双向拼接的方式进行特征融合, 这种方法肯定不如BERT一体化的双向提取特征好.

小节总结

  • 学习了什么是ELMo.
    • ELMo是2018年3月由华盛顿大学提出的一种预训练语言模型.
    • ELMo在6种NLP测试任务中有很大的提升表现.
  • 学习了ELMo的结构.
    • ELMo架构总体上采用了双向双层LSTM的结构.
    • 最底层的Embedding模块.
    • 中间层的双向双层LSTM模块.
    • 最上层的特征融合模块.
  • 学习了ELMo的预训练任务.
    • ELMo的本质思想就是根据当前上下文对word embedding进行动态调整的语言模型.
    • ELMo的预训练是一个明显的两阶段过程.
      • 第一阶段: 利用语言模型进行预训练, 得到基础静态词向量和双向双层LSTM网络.
      • 第二阶段: 在拥有上下文的环境中, 将上下文输入双向双层LSTM中, 得到动态调整后的word embedding, 等于将单词融合进了上下文的语义, 可以更准确的表达单词的真实含义.
  • 学习了ELMo的效果.
    • 经过与GloVe静态词向量的对比, 明显可以看出ELMo的词向量可以更好的表达真实语义, 更好的解决多义词的问题.
  • 学习了ELMo的待改进点.
    • ELMo的特征提取器没有选用更强大的Transformer, 在提取特征上肯定弱于现在的最优结果.



2.2 认识GPT


学习目标

  • 了解什么是GPT.
  • 掌握GPT的架构.
  • 掌握GPT的预训练任务.

什么是GPT

  • GPT是OpenAI公司提出的一种语言预训练模型.
  • OpenAI GPT模型是在Google BERT模型之前提出的, 与BERT最大的区别在于GPT采用了传统的语言模型方法进行预训练, 即使用单词的上文来预测单词, 而BERT是采用了双向上下文的信息共同来预测单词.
    • 正是因为训练方法上的区别, 使得GPT更擅长处理自然语言生成任务(NLG), 而BERT更擅长处理自然语言理解任务(NLU).

GPT的架构

  • 再次看三个语言模型的对比架构图, 中间的就是GPT:

bert - 图31


  • 从上图可以很清楚的看到GPT采用的是单向Transformer模型, 例如给定一个句子[u1, u2, …, un], GPT在预测单词ui的时候只会利用[u1, u2, …, u(i-1)]的信息, 而BERT会同时利用上下文的信息[u1, u2, …, u(i-1), u(i+1), …, un].

  • 作为两大模型的直接对比, BERT采用了Transformer的Encoder模块, 而GPT采用了Transformer的Decoder模块. 并且GPT的Decoder Block和经典Transformer Decoder Block还有所不同, 如下图所示:

bert - 图32


  • 如上图所示, 经典的Transformer Decoder Block包含3个子层, 分别是Masked Multi-Head Attention层, encoder-decoder attention层, 以及Feed Forward层. 但是在GPT中取消了第二个encoder-decoder attention子层, 只保留Masked Multi-Head Attention层, 和Feed Forward层.

  • 作为单向Transformer Decoder模型, GPT利用句子序列信息预测下一个单词的时候, 要使用Masked Multi-Head Attention对单词的下文进行遮掩, 来防止未来信息的提前泄露. 例如给定一个句子包含4个单词[A, B, C, D], GPT需要用[A]预测B, 用[A, B]预测C, 用[A, B, C]预测D. 很显然的就是当要预测B时, 需要将[B, C, D]遮掩起来.

bert - 图33


  • 具体的遮掩操作是在slef-attention进行softmax之前进行的, 一般的实现是将MASK的位置用一个无穷小的数值-inf来替换, 替换后执行softmax计算得到新的结果矩阵. 这样-inf的位置就变成了0. 如上图所示, 最后的矩阵可以很方便的做到当利用A预测B的时候, 只能看到A的信息; 当利用[A, B]预测C的时候, 只能看到A, B的信息.

  • 注意: 对比于经典的Transformer架构, 解码器模块采用了6个Decoder Block; GPT的架构中采用了12个Decoder Block.

bert - 图34


GPT训练过程

  • GPT的训练也是典型的两阶段过程:
    • 第一阶段: 无监督的预训练语言模型.
    • 第二阶段: 有监督的下游任务fine-tunning.

  • 第一阶段: 无监督的预训练语言模型.
    • 给定句子U = [u1, u2, …, un], GPT训练语言模型时的目标是最大化下面的似然函数:

bert - 图35


  • 有上述公式可知, GPT是一个单向语言模型, 假设输入张量用h0表示, 则计算公式如下:

bert - 图36


  • 其中Wp是单词的位置编码, We是单词本身的word embedding. Wp的形状是[max_seq_len, embedding_dim], We的形状是[vocab_size, embedding_dim].

  • 得到输入张量h0后, 要将h0传入GPT的Decoder Block中, 依次得到ht:

bert - 图37


  • 最后通过得到的ht来预测下一个单词:

bert - 图38


  • 第二阶段: 有监督的下游任务fine-tunning.
    • GPT经过预训练后, 会针对具体的下游任务对模型进行微调. 微调采用的是有监督学习, 训练样本包括单词序列[x1, x2, …, xn]和label y. GPT微调的目标任务是根据单词序列[x1, x2, …, xn]预测标签y.

bert - 图39


  • 其中Wy表示预测输出的矩阵参数, 微调任务的目标是最大化下面的函数:

bert - 图40


  • 综合两个阶段的目标任务函数, 可知GPT的最终优化函数为:

bert - 图41


小节总结

  • 学习了什么是GPT.
    • GPT是OpenAI公司提出的一种预训练语言模型.
    • 本质上来说, GPT是一个单向语言模型.
  • 学习了GPT的架构.
    • GPT采用了Transformer架构中的解码器模块.
    • GPT在使用解码器模块时做了一定的改造, 将传统的3层Decoder Block变成了2层Block, 删除了encoder-decoder attention子层, 只保留Masked Multi-Head Attention子层和Feed Forward子层.
    • GPT的解码器总共是由12个改造后的Decoder Block组成的.
  • 学习了GPT的预训练任务.
    • 第一阶段: 无监督的预训练语言模型. 只利用单词前面的信息来预测当前单词.
    • 第二阶段: 有监督的下游任务fine-tunning.



2.3 认识GPT2


学习目标

  • 掌握GPT2的架构
  • 掌握GPT2的训练任务和模型细节

GPT2的架构

  • 从模型架构上看, GPT2并没有特别新颖的架构, 它和只带有解码器模块的Transformer很像.
    • 所谓语言模型, 作用就是根据已有句子的一部分, 来预测下一个单词会是什么. 现实应用中大家最熟悉的一个语言模型应用, 就是智能手机上的输入法, 它可以根据当前输入的内容智能推荐下一个要打的字.

bert - 图42


  • GPT2也是一个语言预测生成模型, 只不过比手机上应用的模型要大很多, 也更加复杂. 常见的手机端应用的输入法模型基本占用50MB空间, 而OpenAI的研究人员使用了40GB的超大数据集来训练GPT2, 训练后的GPT2模型最小的版本也要占用超过500MB空间来存储所有的参数, 至于最大版本的GPT2则需要超过6.5GB的存储空间.
  • 自从Transformer问世以来, 很多预训练语言模型的工作都在尝试将编码器或解码器堆叠的尽可能高, 那类似的模型可以堆叠到多深呢? 事实上, 这个问题的答案也就是区别不同GPT2版本的主要因素之一. 比如最小版本的GPT2堆叠了12层, 中号的24层, 大号的36层, 超大号的堆叠了整整48层!

bert - 图43


GPT2模型的细节

  • 以机器人第一法则为例, 来具体看GPT2的工作细节.
    • 机器人第一法则: 机器人不得伤害人类, 或者目睹人类将遭受危险而袖手旁观.

  • 首先明确一点: GPT2的工作流程很像传统语言模型, 一次只输出一个单词(token).
    • GPT2之所以在生成式任务中表现优秀, 是因为在每个新单词(token)产生后, 该单词就被添加在之前生成的单词序列后面, 添加后的新序列又会成为模型下一步的新输入. 这种机制就叫做自回归(auto-regression), 如下所示:

bert - 图44


  • 其次明确一点: GPT2模型是一个只包含了Transformer Decoder模块的模型.
    • 和BERT模型相比, GPT2的解码器在self-attention层上有一个关键的差异: 它将后面的单词(token)遮掩掉, 而BERT是按照一定规则将单词替换成[MASK].
    • 举个例子, 如果我们重点关注4号位置的单词及其前序路径, 我们可以让模型只允许注意当前计算的单词和它之前的单词, 如下图所示:

bert - 图45


  • 注意: 能够清楚的区分BERT使用的自注意力模块(self-attention)和GPT2使用的带掩码的自注意力模块(masked self-attention)很重要! 普通的self-attention允许模型的任意一个位置看到它右侧的信息(下图左侧), 而带掩码的self-attention则不允许这么做(下图右侧).

bert - 图46


  • 在Transformer原始论文发表后, 一篇名为<< Generating Wikipedia by Summarizing Long Sequences >>的论文提出用另一种Transformer模块的排列方式来进行语言建模-它直接扔掉了编码器, 只保留解码器. 这个早期的基于Transformer的模型由6个Decoder Block堆叠而成:

bert - 图47


  • 上图中所有的解码器模块都是一样的, 因为只展开了第一个解码器的内部结构. 和GPT一样, 只保留了带掩码的self-attention子层, 和Feed Forward子层.
  • 这些解码器和经典Transformer原始论文中的解码器模块相比, 除了删除了第二个Encoder-Decoder Attention子层外, 其他构造都一样.

  • GPT2工作细节探究.
    • GPT2可以处理最长1024个单词的序列.
    • 每个单词都会和它的前序路径一起”流经”所有的解码器模块.

  • 对于生成式模型来说, 基本工作方式都是提供一个预先定义好的起始token, 比如记做”s”.
  • 此时模型的输入只有一个单词, 所以只有这个单词的路径是活跃的. 单词经过层层处理, 最终得到一个词向量. 该向量可以对于词汇表的每个单词计算出一个概率(GPT2的词汇表中有50000个单词). 在本例中, 我们选择概率最高的单词[“The”]作为下一个单词.
  • 注意: 这种选择最高概率输出的策略有时会出现问题-如果我们持续点击输入法推荐单词的第一个, 它可能会陷入推荐同一个词的循环中, 只有你点击第二个或第三个推荐词, 才能跳出这种循环. 同理, GPT2有一个top-k参数, 模型会从概率最大的前k个单词中抽样选取下一个单词.

bert - 图48


  • 接下来, 我们将输出的单词[“The”]添加在输入序列的尾部, 从而构建出新的输入序列[“s”, “The”], 让模型进行下一步的预测:

bert - 图49


  • 此时第二个单词的路径是当前唯一活跃的路径了. GPT2的每一层都保留了它们对第一个单词的解释, 并且将运用这些信息处理第二个单词, GPT2不会根据第二个单词重新来解释第一个单词.

  • 关于输入编码: 当我们更加深入的了解模型的内部细节时, 最开始就要面对模型的输入, 和其他自然语言模型一样, GPT2同样从嵌入矩阵中查找单词对应的嵌入向量, 该矩阵(embedding matrix)也是整个模型训练结果的一部分.

bert - 图50


  • 1: 如上图所示, 每一行都是一个词嵌入向量: 一个能够表征某个单词, 并捕获其语义的数字向量. 嵌入的维度大小和GPT2模型的大小相关, 最小的模型采用了768这个维度, 最大的采用了1600这个维度.

  • 2: 所以在整个模型运作起来的最开始, 我们需要在嵌入矩阵中查找起始单词”s”对应的嵌入向量. 但在将其输入给模型之前, 还需要引入位置编码(positional encoding), 1024分输入序列位置中的每一个都对应了一个位置编码, 同理于词嵌入矩阵, 这些位置编码组成的矩阵也是整个模型训练结果的一部分.

bert - 图51


  • 经历前面的1, 2两步, 输入单词在进入模型第一个transformer模块前的所有处理步骤就结束了. 综上所述, GPT2模型包含两个权值矩阵: 词嵌入矩阵和位置编码矩阵. 而输入到transformer模块中的张量就是这两个矩阵对应的加和结果.

bert - 图52


  • transformer模块的堆叠:
    • 最底层的transformer模块处理单词的步骤:
      • 首先通过自注意力层处理, 接着将其传递给前馈全连接层, 这其中包含残差连接和Layer Norm等子层操作.
      • 最底层的transformer模块处理结束后, 会将结果张量传递给第二层的transformer模块, 继续进行计算.
      • 每一个transformer模块的处理方式都是一样的, 不断的重复相同的模式, 但是每个模块都会维护自己的self-attention层和Feed Forward层的权重值.

bert - 图53


  • GPT2的自注意力机制回顾
    • 自然语言的含义是极度依赖上下文的, 比如下面所展示的”机器人第二法则”:
      • 机器人必须遵守人类给它的命令, 除非该命令违背了第一法则.

  • 在上述语句中, 有三处单词具有指代含义, 除非我们知道这些词所精确指代的上下文, 否则根本不可能理解这句话的真实语义.
  • 当模型处理这句话的时候, 模型必须知道以下三点:
  • [它]指代机器人.
  • [命令]指代前半句话中人类给机器人下达的命令, 即[人类给它的命令].
  • [第一法则]指代机器人第一法则的完整内容.

  • 这就是自注意力机制所做的工作, 它在处理每个单词之前, 融入了模型对于用来解释某个单词的上下文的相关单词的理解. 具体的做法是: 给序列中的每一个单词都赋予一个相关度得分, 本质上就是注意力权重.

  • 看下图, 举个例子, 最上层的transformer模块在处理单词”it”的时候会关注”a robot”, 所以”a”, “robot”, “it”, 这三个单词与其得分相乘加权求和后的特征向量会被送入之后的Feed Forward层.

bert - 图54


  • 自注意力机制沿着序列的每一个单词的路径进行处理, 主要由3个向量组成:
  • 1: Query(查询向量), 当前单词的查询向量被用来和其它单词的键向量相乘, 从而得到其它词相对于当前词的注意力得分.
  • 2: Key(键向量), 键向量就像是序列中每个单词的标签, 它使我们搜索相关单词时用来匹配的对象.
  • 3: Value(值向量), 值向量是单词真正的表征, 当我们算出注意力得分后, 使用值向量进行加权求和得到能代表当前位置上下文的向量.

bert - 图55


  • 如上图所示, 一个简单的比喻是在档案柜中找文件. 查询向量Query就像一张便利贴, 上面写着你正在研究的课题. 键向量Key像是档案柜中文件夹上贴的标签. 当你找到和便利贴上所写相匹配的文件夹时, 拿出对应的文件夹, 文件夹里的东西便是值向量Value.
  • 将单词的查询向量Query分别乘以每个文件夹的键向量Key,得到各个文件夹对应的注意力得分Score.

bert - 图56


  • 我们将每个文件夹的值向量Value乘以其对应的注意力得分Score, 然后求和, 得到最终自注意力层的输出, 如下图所示:

bert - 图57


  • 这样将值向量加权混合得到的结果也是一个向量, 它将其50%的注意力放在了单词”robot”上, 30%的注意力放在了”a”上, 还有19%的注意力放在了”it”上.

  • 模型的输出:
    • 当最后一个transformer模块产生输出之后, 模型会将输出张量乘上词嵌入矩阵:

bert - 图58


  • 我们知道, 词嵌入矩阵的每一行都对应模型的词汇表中一个单词的嵌入向量. 所以这个乘法操作得到的结果就是词汇表中每个单词对应的注意力得分, 如下图所示:

bert - 图59


  • 一般来说, 我们都采用贪心算法, 选取得分最高的单词作为输出结果(top_k = 1).
  • 但是一个更好的策略是对于词汇表中得分较高的一部分单词, 将它们的得分作为概率从整个单词列表中进行抽样(得分越高的单词越容易被选中).
  • 通常会用一个折中的方法, 即选取top_k = 40, 这样模型会考虑注意力得分排名前40的单词.

bert - 图60


  • 如上图所示, 模型就完成了一个时间步的迭代, 输出了一个单词. 接下来模型会不断的迭代, 直至生成完整的序列(序列长度达到1024的上限, 或者序列的某一个时间步生成了结束符).

小节总结

  • 学习了GPT2的架构:
    • GPT2只采用了Transformer架构中的Decoder模块.
    • GPT2是在GPT基础上发展处的更强大的语言预训练模型.
  • 学习了GPT2的工作细节:
    • GPT2可以处理最长1024个单词的序列.
    • 每个单词都会和它的前序路径一起”流经”所有的解码器模块.
    • GPT2本质上也是自回归模型.
    • 输入张量要经历词嵌入矩阵和位置编码矩阵的加和后, 才能输入进transformer模块中.
  • 学习了GPT2自注意力机制的细节:
    • 首先, GPT2的自注意力是Masked self-attention, 只能看见左侧的序列, 不能看见右侧的信息.
    • Query, Key, Value这三个张量之间的形象化的例子, 生动的说明了各自的作用和运算方式.
    • 最后的输出可以采用多个方法, 贪心方案, 概率分布方案, 或者top-k方案等.



2.4 请详述BERT, GPT, ELMo模型的对比和各自的优缺点?


学习目标

  • 理解BERT, GPT, ELMo相互间的不同点.
  • 理解BERT, GPT, ELMo相互比较下的各自优点和缺点.

BERT, GPT, ELMo之间的不同点

  • 关于特征提取器:
    • ELMo采用两部分双层双向LSTM进行特征提取, 然后再进行特征拼接来融合语义信息.
    • GPT和BERT采用Transformer进行特征提取.
    • 很多NLP任务表明Transformer的特征提取能力强于LSTM, 对于ELMo而言, 采用1层静态token embedding + 2层LSTM, 提取特征的能力有限.
  • 单/双向语言模型:
    • 三者之中, 只有GPT采用单向语言模型, 而ELMo和BERT都采用双向语言模型.
    • ELMo虽然被认为采用了双向语言模型, 但实际上是左右两个单向语言模型分别提取特征, 然后进行特征拼接, 这种融合特征的能力比BERT一体化的融合特征方式弱.
    • 三者之中, 只有ELMo没有采用Transformer. GPT和BERT都源于Transformer架构, GPT的单向语言模型采用了经过修改后的Decoder模块, Decoder采用了look-ahead mask, 只能看到context before上文信息, 未来的信息都被mask掉了. 而BERT的双向语言模型采用了Encoder模块, Encoder只采用了padding mask, 可以同时看到context before上文信息, 以及context after下文信息.

BERT, GPT, ELMo各自的优点和缺点

  • ELMo:
    • 优点:
      • 从早期的Word2Vec预训练模型的最大缺点出发, 进行改进, 这一缺点就是无法解决多义词的问题.
      • ELMo根据上下文动态调整word embedding, 可以解决多义词的问题.
    • 缺点:
      • ELMo使用LSTM提取特征的能力弱于Transformer.
      • ELMo使用向量拼接的方式融合上下文特征的能力弱于Transformer.
  • GPT:
    • 优点:
      • GPT使用了Transformer提取特征, 使得模型能力大幅提升.
    • 缺点:
      • GPT只使用了单向Decoder, 无法融合未来的信息.
  • BERT:
    • 优点:
      • BERT使用了双向Transformer提取特征, 使得模型能力大幅提升.
      • 添加了两个预训练任务, MLM + NSP的多任务方式进行模型预训练.
    • 缺点:
      • 模型过于庞大, 参数量太多, 需要的数据和算力要求过高, 训练好的模型应用场景要求高.
      • 更适合用于语言嵌入表达, 语言理解方面的任务, 不适合用于生成式的任务.

小节总结

  • 学习了BERT, GPT, ELMo之间的区别:
    • 三者所选取的特征提取器不同.
      • BERT采用的是Transformer架构中的Encoder模块.
      • GPT采用的是Transformer架构中的Decoder模块.
      • ELMo采用的双层双向LSTM模块.
    • 三者所采用的语言模型单/双向不同.
      • BERT采用的是最彻底的双向语言模型, 可以同时关注context before和context after.
      • GPT采用的是单向语言模型, 即Transformer中的Decoder, 由于采用了mask机制, 所以未来信息context after都不可见.
      • ELMo表面上被认为是双向语言模型, 但实际上是左右两个单向LSTM模型分别提取特征, 在进行简单的拼接融合.

_