文章目录

  • 一、ELMO
    • 1.TagLM – “Pre-ELMo”
    • 2.ELMo: Embeddings from Language Models
  • 二、ULMfit
  • 三、Transformer
    • 1.编码器
      • (1)词向量+位置编码
      • (2)多头注意力层
      • (3)前馈神经网络层
    • 2.解码器
  • 四、BERT
    • 1.BERT的输入
    • 2.预训练任务1:Masked LM
    • 3.预训练任务2:Next Sentence Prediction

之前介绍的Word Vector方法如Word2Vec, GloVe, fastText等存在两个大问题

  • 对于一个 word type 总是是用相同的表示,不考虑这个 word token 出现的上下文,比如 star 这个单词,有天文学上的含义以及娱乐圈中的含义不同,我们可以进行非常细粒度的词义消歧
  • 我们对一个词只有一种表示,但是单词有不同的方面,包括语义,句法行为,以及表达 / 含义

一、ELMO

1.TagLM – “Pre-ELMo”

TagLM的思想是想要获得单词在上下文的意思,但标准的 RNN 学习任务只在 task-labeled 的小数据上(如 NER ),可以通过半监督学习的方式在大型无标签数据集上训练 NLM,而不只是词向量。TagLM使用与上文无关的单词嵌入 + RNN model 得到的 hidden states 作为特征输入,如下图所示,

左侧Char CNN / RNN + Token Embedding 作为第一层 bi-LSTM 的输入得到的 hidden states 与右侧 预训练的 bi-LM(冻结的) 的 hidden states 连接起来输入到第二层的 bi-LSTM 中

2.ELMo: Embeddings from Language Models

ELMO的基本思想是使用长上下文而不是上下文窗口学习 word token 向量

ELMO利用双向的LSTM结构,对于某个语言模型的目标,在大量文本上进行预训练,从LSTM layer中得到contextual embedding,其中较低层的LSTM代表了比较简单的语法信息,而上层的LSTM捕捉的是依赖于上下文的语义信息。对于下游的任务,再将这些不同层的向量线性组合,再做监督学习。

ELMo目标是 performant 但语言模型不要太大

  • 使用2个biLSTM层
  • (仅)使用字符CNN构建初始单词表示
  • 2048 个 char n-gram filters 和 2 个 highway layers,512 维的 projection
  • 4096 dim hidden/cell LSTM状态,使用 512 dim的对下一个输入的投影
  • 使用残差连接
  • 绑定 token 的输入和输出的参数(softmax),并将这些参数绑定到正向和反向LMs之间
  • TagLM 中仅仅使用堆叠LSTM的顶层,ELMo 认为BiLSTM所有层都是有用的

对于k位置的标记,ELMO模型用2L+1个向量 R 来表示,其中1个是不依赖于上下文的表示,通常是用之前提及的word embedding或者是基于字符的CNN来得到 x 。L层前向 LSTM和 反向 LSTM每层会分别产生一个依赖于上文/下文的表示 h ,我们可以将他们一起简计。

得到每层的embedding后,对于每个下游的任务,我们可以计算其加权的表示即下图中的 ELMo

  • γ衡量ELMo对任务的总体有用性,是为特定任务学习的全局比例因子
  • s是 softmax 归一化的混合模型权重,是 BiLSTM 的加权平均值的权重,对不同的任务是不同的

采用了ELMO预训练产生的contextual embedding之后,在各项下游的NLP任务中,准确率都有显著提高。

二、ULMfit

ULMfit的基本思想:

  • 在大型通用领域的无监督语料库上使用 biLM 训练
  • 在目标任务数据上调整 LM
  • 对特定任务将分类器进行微调

使用大型的预训练语言模型是一种提高性能的非常有效的方法。随着计算的扩大,得到了诸如GPT、BERT等Transformer模型

三、Transformer

参考文章:3W字长文带你轻松入门视觉transformer

Google所提基于transformer的seq2seq整体结构如下所示:

其包括6个结构完全相同的编码器,和6个结构完全相同的解码器,其中每个编码器和解码器设计思想完全相同,只不过由于任务不同而有些许区别,整体详细结构如下所示:

由于基于transformer的翻译任务已经转化为分类任务(目标翻译句子有多长,那么就有多少个分类样本),故在解码器最后会引入fc+softmax层进行概率输出,训练也比较简单,直接采用ce loss即可,对于采用大量数据训练好的预训练模型,下游任务仅仅需要训练fc层即可。下面结合代码和原理进行深入分析。

1.编码器

(1)词向量+位置编码

首先将单词使用词嵌入例如word2vec方法转化成n维向量,之后进行位置编码。

位置编码的意义在于因为transformer内部没有类似RNN的循环结构,没有捕捉顺序序列的能力。为了解决这个问题,在编码词向量时会额外引入了位置编码position encoding向量表示两个单词i和j之间的距离,简单来说就是在词向量中加入了单词的位置信息。

在论文中采用的是sin-cos规则来进行位置编码,具体做法是:

将向量的维度切分为奇数行和偶数行,偶数行采用sin函数编码,奇数行采用cos函数编码,然后按照原始行号拼接

def get_position_angle_vec(position):# hid_j是0-511,d_hid是512,position表示单词位置0~N-1return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]# 每个单词位置0~N-1都可以编码得到512长度的向量
sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
# 偶数列进行sin
sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i
# 奇数列进行cos
sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1

在最后将词向量编码和位置编码向量相加,得到多头注意力层输入

(2)多头注意力层

在介绍多头注意力之前首先介绍一下自注意力。首先定义三个可学习矩阵 W (相当于线性层,维度一般为 词向量维度 * 词向量维度),将 X 和三个矩阵相乘得到 Q、K、V三个输出。然后将Q和K进行点乘计算向量相似性并进行归一化;采用softmax转换为概率分布;将概率分布和V进行加权求和即可。可由下图表示:

多头注意力相当于多个自注意力的集成,并在两方面提高了注意力层的性能:

  • 它扩展了模型专注于不同位置的能力
  • 它给出了注意力层的多个表示子空间

简单来说多头注意力就是类似于分组操作,将输入X分别输入到8个self-attention层中,得到8个Z矩阵输出,之后对结果按列拼成一个大的特征矩阵,特征矩阵经过一层全连接后得到输出。另外,多头注意力加入了残差设计和层归一化操作,目的是为了防止梯度消失,加快收敛。

单头注意力代码:

class ScaledDotProductAttention(nn.Module):''' Scaled Dot-Product Attention '''def __init__(self, temperature, attn_dropout=0.1):super().__init__()self.temperature = temperatureself.dropout = nn.Dropout(attn_dropout)def forward(self, q, k, v, mask=None):# self.temperature是论文中的d_k ** 0.5,防止梯度过大# QxK/sqrt(dk)attn = torch.matmul(q / self.temperature, k.transpose(2, 3))if mask is not None:# 屏蔽不想要的输出attn = attn.masked_fill(mask == 0, -1e9)# softmax+dropoutattn = self.dropout(F.softmax(attn, dim=-1))# 概率分布xVoutput = torch.matmul(attn, v)return output, attn

多头注意力代码:

class MultiHeadAttention(nn.Module):''' Multi-Head Attention module '''# n_head头的个数,默认是8# d_model编码向量长度,例如本文说的512# d_k, d_v的值一般会设置为 n_head * d_k=d_model,# 此时concat后正好和原始输入一样,当然不相同也可以,因为后面有fc层# 相当于将可学习矩阵分成独立的n_head份def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):super().__init__()# 假设n_head=8,d_k=64self.n_head = n_headself.d_k = d_kself.d_v = d_v# d_model输入向量,n_head * d_k输出向量# 可学习W^Q,W^K,W^V矩阵参数初始化self.w_qs = nn.Linear(d_model, n_head * d_k, bias=False)self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False)self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False)# 最后的输出维度变换操作self.fc = nn.Linear(n_head * d_v, d_model, bias=False)# 单头自注意力self.attention = ScaledDotProductAttention(temperature=d_k ** 0.5)self.dropout = nn.Dropout(dropout)# 层归一化self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)def forward(self, q, k, v, mask=None):# 假设qkv输入是(b,100,512),100是训练每个样本最大单词个数# 一般qkv相等,即自注意力residual = q# 将输入x和可学习矩阵相乘,得到(b,100,512)输出# 其中512的含义其实是8x64,8个head,每个head的可学习矩阵为64维度# q的输出是(b,100,8,64),kv也是一样q = self.w_qs(q).view(sz_b, len_q, n_head, d_k)k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)# 变成(b,8,100,64),方便后面计算,也就是8个头单独计算q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)if mask is not None:mask = mask.unsqueeze(1)   # For head axis broadcasting.# 输出q是(b,8,100,64),维持不变,内部计算流程是:# q*k转置,除以d_k ** 0.5,输出维度是b,8,100,100即单词和单词直接的相似性# 对最后一个维度进行softmax操作得到b,8,100,100# 最后乘上V,得到b,8,100,64输出q, attn = self.attention(q, k, v, mask=mask)# b,100,8,64-->b,100,512q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)q = self.dropout(self.fc(q))# 残差计算q += residual# 层归一化,在512维度计算均值和方差,进行层归一化q = self.layer_norm(q)return q, attn

(3)前馈神经网络层

class PositionwiseFeedForward(nn.Module):''' A two-feed-forward-layer module '''def __init__(self, d_in, d_hid, dropout=0.1):super().__init__()# 两个fc层,对最后的512维度进行变换self.w_1 = nn.Linear(d_in, d_hid) # position-wiseself.w_2 = nn.Linear(d_hid, d_in) # position-wiseself.layer_norm = nn.LayerNorm(d_in, eps=1e-6)self.dropout = nn.Dropout(dropout)def forward(self, x):residual = xx = self.w_2(F.relu(self.w_1(x)))x = self.dropout(x)x += residualx = self.layer_norm(x)return x

将多头注意力层和前馈层连起来构建了一个encoder层

class EncoderLayer(nn.Module):def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):super(EncoderLayer, self).__init__()self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)def forward(self, enc_input, slf_attn_mask=None):# Q K V是同一个,自注意力# enc_input来自源单词嵌入向量或者前一个编码器输出enc_output, enc_slf_attn = self.slf_attn(enc_input, enc_input, enc_input, mask=slf_attn_mask)enc_output = self.pos_ffn(enc_output)return enc_output, enc_slf_attn

将多个encoder层堆叠在一起得到了transformer的 encoder

class Encoder(nn.Module):def __init__(self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,d_model, d_inner, pad_idx, dropout=0.1, n_position=200):# nlp领域的词嵌入向量生成过程(单词在词表里面的索引idx-->d_word_vec长度的向量)self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)# 位置编码self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)self.dropout = nn.Dropout(p=dropout)# n个编码器层self.layer_stack = nn.ModuleList([EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)for _ in range(n_layers)])# 层归一化self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)def forward(self, src_seq, src_mask, return_attns=False):# 对输入序列进行词嵌入,加上位置编码enc_output = self.dropout(self.position_enc(self.src_word_emb(src_seq)))enc_output = self.layer_norm(enc_output)# 作为编码器层输入for enc_layer in self.layer_stack:enc_output, _ = enc_layer(enc_output, slf_attn_mask=src_mask)return enc_output

2.解码器

解码器和编码器类似,区别在于以下几个地方:

  • 解码开始标志位BOS_WORD,解码结束标志位EOS_WORD
  • 使用了带有mask的MultiHeadAttention。使用mask的意义是我们不希望在训练解码器时一个单词一个单词按照顺序训练出来,我们希望能够像编码器一样并行的将所有目标单词预测出来,而且我们不能利用到后面单词的嵌入向量信息,所以我们用一个mask构成下三角矩阵,右上角全部设置为负无穷(相当于忽略),从而实现当解码第一个字的时候,第一个字只能与第一个字计算相关性,当解出第二个字的时候,只能计算出第二个字与第一个字和第二个字的相关性。
  • 解码器内部的带有mask的MultiHeadAttention的qkv向量输入来自目标单词嵌入或者前一个解码器输出,三者是相同的,但是后面的MultiHeadAttention的qkv向量中的kv来自最后一层编码器的输入,而q来自带有mask的MultiHeadAttention模块的输出。

单个解码块

class DecoderLayer(nn.Module):''' Compose with three layers '''def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):super(DecoderLayer, self).__init__()self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)def forward(self, dec_input, enc_output,slf_attn_mask=None, dec_enc_attn_mask=None):# 标准的自注意力,QKV=dec_input来自目标单词嵌入或者前一个解码器输出dec_output, dec_slf_attn = self.slf_attn(dec_input, dec_input, dec_input, mask=slf_attn_mask)# KV来自最后一个编码层输出enc_output,Q来自带有mask的self.slf_attn输出dec_output, dec_enc_attn = self.enc_attn(dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)dec_output = self.pos_ffn(dec_output)return dec_output, dec_slf_attn, dec_enc_attn

多个解码块堆叠

class Decoder(nn.Module):def __init__(self, n_trg_vocab, d_word_vec, n_layers, n_head, d_k, d_v,d_model, d_inner, pad_idx, n_position=200, dropout=0.1):# 目标单词嵌入self.trg_word_emb = nn.Embedding(n_trg_vocab, d_word_vec, padding_idx=pad_idx)# 位置嵌入向量self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)self.dropout = nn.Dropout(p=dropout)# n个解码器self.layer_stack = nn.ModuleList([DecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)for _ in range(n_layers)])# 层归一化self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):# 目标单词嵌入+位置编码dec_output = self.dropout(self.position_enc(self.trg_word_emb(trg_seq)))dec_output = self.layer_norm(dec_output)# 遍历每个解码器for dec_layer in self.layer_stack:  # 需要输入3个信息:目标单词嵌入+位置编码、最后一个编码器输出enc_output# 和dec_enc_attn_mask,解码时候不能看到未来单词信息dec_output, dec_slf_attn, dec_enc_attn = dec_layer(dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)return dec_output

四、BERT

BERT的全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder。模型的主要创新点都在pre-train方法上,即用了Masked LM和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation。

BERT其和GPT一样均是采用的transformer的结构,相对于GPT来说,其是双向结构的,而GPT是单向的

对比ELMo,虽然都是“双向”,但ELMO将上下文当作特征,无监督的语料和我们真实的语料还是有区别的,不一定的符合我们特定的任务,是一种双向的特征提取。

1.BERT的输入

BERT的输入可以是单一的一个句子或者是句子对,实际输入的Embedding由三种Embedding求和而成:

  • Token Embeddings是词向量,第一个单词是CLS标志,可以用于之后的分类任务
  • Segment Embeddings用来区别两种句子,因为预训练不光做LM还要做以两个句子为输入的分类任务
  • Position Embeddings和之前文章中的Transformer不一样,不是三角函数而是学习出来的
  • 每个句子开头为 CLS 符号,结尾 SEP 符号,两个句子的情况用 SEP 做分隔

2.预训练任务1:Masked LM

第一步预训练的目标就是做语言模型,如果使用预训练模型处理其他任务,那人们想要的肯定不止某个词左边的信息,而是左右两边的信息。而考虑到这点的模型ELMo只是将left-to-right和right-to-left分别训练拼接起来。因此BERT采用了深度双向模型

为了训练一个深度双向表示(deep bidirectional representation),研究团队采用了一种简单的方法,即随机屏蔽(masking)部分输入token,然后只预测那些被屏蔽的token。最终的损失函数只计算被mask掉那个token。

训练数据生成器随机选择15%的token,然后执行以下过程:

  • 80%的时间:用[MASK]标记替换单词,例如,my dog is hairy → my dog is [MASK]
  • 10%的时间:用一个随机的单词替换该单词,例如,my dog is hairy → my dog is apple
  • 10%的时间:保持单词不变,例如,my dog is hairy → my dog is hairy.

因为序列长度太大(512)会影响训练速度,所以90%的steps都用seq_len=128训练,余下的10%步数训练512长度的输入。

3.预训练任务2:Next Sentence Prediction

因为涉及到QA和NLI之类的任务,增加了第二个预训练任务,目的是让模型理解两个句子之间的联系。选择一些句子对A与B,其中50%的数据B是A的下一条句子,剩余50%的数据B是语料库中随机选择的,输入这两个句子,模型预测B是不是A的下一句。预训练的时候可以达到97-98%的准确度。

CS224n自然语言处理(四)——单词表示及预训练,transformer和BERT相关推荐

  1. BERT(预训练Transformer模型)

    目录 一.前言 二.随机遮挡,进行预测 三.两句话是否原文相邻 四.两者结合起来 五.总结 六.参考链接 一.前言 Bert在18年提出,19年发表,Bert的目的是为了预训练Transformer模 ...

  2. 文本生成与自动摘要:基于生成式预训练Transformer的实现与优化

    作者:禅与计算机程序设计艺术 1.简介 文本生成是自然语言处理领域中非常重要的问题之一.在不断地探索学习新知识和技能的同时,越来越多的人也需要通过自己创造或整合的手段,将自己的想法.观点和信息转化成语 ...

  3. Achuan读论文:用于远程监督关系抽取的微调预训练transformer语言模型

    Fine-tuning Pre-Trained Transformer Language Models to Distantly Supervised Relation Extraction 用于远程 ...

  4. 【Pytorch基础教程36】基于Ernie预训练模型和Bert的新闻分类

    文章目录 一.新闻分类任务 1.1 中文数据集 1.2 数据特点 1.3 跑起代码 二. 预训练语言模型ERNIE 2.1 ERNIE模型结构 2.2 bert模型结构 三.项目代码 1. bert模 ...

  5. 使用huggingface的Transformers预训练自己的bert模型+FineTuning

    ① 将"softmax+交叉熵"推广到多标签分类问题 多分类问题引申到多标签分类问题(softmax+交叉熵) 作者苏剑林论述了将多分类任务下常用的softmax+CE的方式,推广 ...

  6. 预训练语言模型 | (3) Bert

    原文链接 目录 1. 背景 2. Bert流程和技术细节 3. 总结 1. 背景 在bert之前,将预训练的embedding应用到下游任务的方式大致可以分为2种,一种是feature-based,例 ...

  7. 《预训练周刊》第22期:Fastformer:加法注意力就是你所需要的、用于蛋白建模的大规模预训练语言模型...

    No.22 智源社区 预训练组 预 训 练 研究 观点 资源 活动 关于周刊 超大规模预训练模型是当前人工智能领域研究的热点,为了帮助研究与工程人员了解这一领域的进展和资讯,智源社区整理了第22期&l ...

  8. 《预训练周刊》第28期:M6-10T:高效多万亿参数预训练的共享去链接范式、大模型自然语言处理的最新进展综述...

    No.28 智源社区 预训练组 预 训 练 研究 观点 资源 活动 关于周刊 本期周刊,我们选择了11篇预训练相关的论文,涉及模型训练.图像编码.气候文本.对比学习.文本生成评估.小样本学习.决策推理 ...

  9. 自然语言处理NLP,如何使用AMBERT算法建立多粒度token预训练语言模型

    字节跳动 Xinsong Zhang.李航两位研究者在细粒度和粗粒度标记化的基础上,提出了一种新的预训练语言模型,他们称之为 AMBERT(一种多粒度 BERT).在构成上,AMBERT 具有两个编码 ...

  10. 【自然语言处理】【文本生成】UniLM:用于自然语言理解和生成的统一语言模型预训练

    UniLM:用于自然语言理解和生成的统一语言模型预训练 <Unified Language Model Pre-training for Natural Language Understandi ...

最新文章

  1. 一些防止 Java 代码被反编译的方法
  2. 机器学习(MACHINE LEARNING)主成分分析(PCA降维)
  3. MFC视图滚动条的基本使用和C语言输出三角形的MFC版本
  4. 【Elasticsearch】消除 Elasticsearch 中的重复文档
  5. Zabbix-1.8.14 安装
  6. Android权限全记录(转)
  7. 从Asp.net实现资源全球化和本地化
  8. 读《About Face 4 交互设计精髓》19
  9. WBS工作分解结构法
  10. 【爱思考】CISP考试基本介绍
  11. ubuntu20更改登录背景图片
  12. 免费的文档翻译教程;免费将很长的文档英文翻译成中文
  13. c语言黑熊,BLACK BRUIN黑熊扭矩马达C5510000AA
  14. 天基实业怎样投资理财收益稳定
  15. SQL上机练习题目及答案
  16. Linux 之鸟哥的私房菜
  17. Swift——仿微信发起群聊悬浮框实现
  18. 9.4 二进制字符串类型
  19. 用c语言表达圣诞节快乐的英文,圣诞节快乐用英文怎么说
  20. 软件开发流程及思考—采访某项目经理

热门文章

  1. 国内外各大免费搜索引擎、导航网址提交入口(转载)
  2. 盈通785G显卡超频/开核教程
  3. Redis入门指南笔记
  4. Advertising on Instagram 如何在Instagram上发布广告 Lynda课程中文字幕
  5. 汽车故障检测仪计算机教程,道通MS诊断仪在线编程刷隐藏908SPRO汽车故障检测电脑...
  6. 数字滤波器的研究背景
  7. VAR模型Stata实例操作
  8. 从入门到放弃C语言-入门篇(1)
  9. 阿里云服务器 发送邮件无法连接smtp的解决方案
  10. WPF|分享一个登录界面设计