2018年是nlp重大改革的一年,bert的横空出世使得未来nlp的发展有利跨时代的进步,其在多个领域的完美成功使nlp研究方向有了很大的改变,一直到现在很多比赛的榜单都是由bert或者其畸形霸占。本文让我们简单了解bert,看看它是如何达到如此成就的。

Bert:

bert其实并没想象中的那么复杂,只要了解其中的几个知识点和两幅图便可以知晓它的原理和思想。而bert与其他网络之间也有很多参考和改变,详细见前篇博客:Pre-training in nlp | Ripshun Blog

知识点:

  • contextual word representation 语义词表征
  • pre-training and fine-tuning 预处理和微调
  • transfromer
  • WordPiece:字面意思,把word拆成piece一片一片
  • (token,position,segment) embedding
  • Masked LM And Next Sentence Prediction (NSP)

contextual word representation

传统的词向量嵌入方法(word2vec等等)没有考虑词的语义,即一词多义。在ELMO之中注意到了这一点。ELMO通过双向LSTM长记忆神将网络结构预训练词语的语义。在bert中也参考了这种思想。

pre-training and fine-tuning

预处理表示在训练开始之前使用别人已经学习完大量数据集后的参数,微调指的是在训练过程中由自己给出的数据集来轻微调整参数来满足需要。这种模式在GPT中就有了很好的运用。

而在bert的结构中不难发现,bert使用多层transfromer进行预处理,使得token有了很好的训练成果,再将训练完的模型放入用户自己微调后的模型之中,使得bert可以适应不同的nlp任务。

transfromer

在图片中可以看出bert的预处理主要使用的transfromer,transfromer的原理在前篇已经介绍了:Attention is All you need论文解读 | Ripshun Blog,其主要的思想在于self-attention之中,而如何运用self-attention也是Bert与GPT的区别,在GPT中使用单向的结构使得当前token的attention只于前面的token有关,这样忽略了后面token的影响,这就是Bert最大的改变之处,bert将单向的结构转为同时训练的双向结构,那么它是如何实现的呢?让我们围绕这个问题进行探索。

Masked LM(MLM) And Next Sentence Prediction (NSP)

在bert的论文中给出了两种形式的训练模式:

  1. MLM:在一个句子中使用随机屏蔽掉 15% 的词,也就是用 [MASK] 取代它们,然后去预测这些词。就像英语的完形填空一样,根据上下文填词。具体过程是这 15% 的词,80% 的几率是用 [MASK] 取代,10% 的几率是用一个随机的词取代,10% 的几率不变。而这种模式使得在训练一个句子时不用从前往后单向的进行,而是同时深度双向的表示。
  2. NSP 是专门为像 QA 这种需要理解两个句子之间关系的任务提出的,具体训练过程是一半的句子 B 是句子 A 的下一句,一半的句子 B 不是句子 A的下一句,预测句子 B 是不是 句子 A 的下一句。B 不是句子 A的下一句,预测句子 B 是不是 句子 A 的下一句。

(token,position,segment) embedding

在bert中输入格式主要由三个嵌入向量构成(token,position,segment) embedding,它们分别代表词语的3个特征。

embedding就是把一个东西映射为多维空间的一个点,即一个vector;那么token embedding就是把token映射为一个token的vector;然后,position embedding就是把位置信息映射为位置空间的一个点,也就是一个vector;同理,segment embedding就是把segment信息(表示这个token是属于哪个segment的,不同的segment通过【SEP】分割)映射为segment的vector。

Bert 模型结构

通过上面的知识点我们大概能知道Bert到底是运用了什么技术,以及他的特点,那么bert模型便可以很容易的构建出来。

可以看到BERT相对与GPT其实没什么变化,唯一的变换在于训练的方式,上面我们说了bert是双向的,即输入时mask掉一些token,所以Bert相对与GPT多了从后向前的箭头。而其内部其实由很多块Transformers的encoder组成。这样bert的预训练模型就构建出来了。

看一看到Bert预训练模型的输入和输出维度是一样的,所以我们很容易进行微调构建相对应的任务。让我们用一个简单的代码构建一个多分类问题吧。

Bert多分类代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BertForSequenceClassification(BertPreTrainedModel):
    def __init__(self, config, num_labels=2, ...):
        super(BertForSequenceClassification, self).__init__(config)
        self.num_labels = num_labels
        self.bert = BertModel(config, ...)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)
        self.classifier = nn.Linear(config.hidden_size, num_labels)
          ...
 
    def forward(self, input_ids, token_type_ids=None, attention_mask=None, labels=None, ...):
        outputs = self.bert(input_ids, token_type_ids, attention_mask, ...)
        ...
        pooled_output = self.dropout(pooled_output)
        logits = self.classifier(pooled_output)
 
        if labels is not None:
            loss_fct = CrossEntropyLoss()
            loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1))
            return loss
        elif self.output_attentions:
            return all_attentions, logits
        return logit

这是bert官方BertForSequenceClassification类的具体实现方法,可以看到实现一个简单的多分类问题,只在预训练模型BertModel后面加入了一个dropout和一个Linear层。

总结

Bert作为一个预训练模型主要使用Transfromers的encoder层,通过MLM的掩码机制实现双向训练,而Bert进行下游任务扩展时由于预训练后的维度与原始维度相同也很容易实现,只需要对相应任务做一些微调便可实现。

 
目前共有0条评论
  • 暂无Trackback
你目前的身份是游客,评论请输入昵称和电邮!