精华内容
参与话题
问答
  • Bert

    2019-10-25 10:27:13
    Bert
  • BERT

    万次阅读 多人点赞 2018-12-04 15:09:20
    原文链接:The Illustrated BERT, ELMo, and co. (How NLP Cracked Transfer Learning) 作者:Jay Alammar BERT论文地址:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding ...

    原文链接:The Illustrated BERT, ELMo, and co. (How NLP Cracked Transfer Learning)

    作者:Jay Alammar

    BERT论文地址:BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

    前言   

          2018年可谓是自然语言处理(NLP)的元年,在我们如何以最能捕捉潜在语义关系的方式  来辅助计算机对的句子概念性的理解 这方面取得了极大的发展进步。此外, NLP领域的一些开源社区已经发布了很多强大的组件,我们可以在自己的模型训练过程中免费的下载使用。(可以说今年是NLP的ImageNet时刻,因为这和几年前计算机视觉的发展很相似)

          上图中,最新发布的BERT是一个NLP任务的里程碑式模型,它的发布势必会带来一个NLP的新时代。BERT是一个算法模型,它的出现打破了大量的自然语言处理任务的记录。在BERT的论文发布不久后,Google的研发团队还开放了该模型的代码,并提供了一些在大量数据集上预训练好的算法模型下载方式。Goole开源这个模型,并提供预训练好的模型,这使得所有人都可以通过它来构建一个涉及NLP的算法模型,节约了大量训练语言模型所需的时间,精力,知识和资源。

        BERT集成了最近一段时间内NLP领域中的一些顶尖的思想,包括但不限于 Semi-supervised Sequence Learning (by Andrew Dai and Quoc Le), ELMo (by Matthew Peters and researchers from AI2 and UW CSE), ULMFiT (by fast.ai founder Jeremy Howard and Sebastian Ruder), and the OpenAI transformer (by OpenAI researchers RadfordNarasimhanSalimans, and Sutskever), and the Transformer (Vaswani et al).。

      你需要注意一些事情才能恰当的理解BERT的内容,不过,在介绍模型涉及的概念之前可以使用BERT的方法。 

     

    示例:句子分类

    使用BERT最简单的方法就是做一个文本分类模型,这样的模型结构如下图所示:

         

    为了训练一个这样的模型,(主要是训练一个分类器),在训练阶段BERT模型发生的变化很小。该训练过程称为微调,并且源于 Semi-supervised Sequence Learning 和 ULMFiT.。

         为了更方便理解,我们下面举一个分类器的例子。分类器是属于监督学习领域的,这意味着你需要一些标记的数据来训练这些模型。对于垃圾邮件分类器的示例,标记的数据集由邮件的内容和邮件的类别2部分组成(类别分为“垃圾邮件”或“非垃圾邮件”)。

    模型架构  

          现在您已经了解了如何使用BERT的示例,让我们仔细了解一下他的工作原理。

    BERT的论文中介绍了2种版本:

    • BERT BASE - 与OpenAI Transformer的尺寸相当,以便比较性能
    • BERT LARGE - 一个非常庞大的模型,它完成了本文介绍的最先进的结果。

    BERT的基础集成单元是Transformer的Encoder。关于Transformer的介绍可以阅读作者之前的文章:The Illustrated Transformer,该文章解释了Transformer模型 - BERT的基本概念以及我们接下来要讨论的概念。

      2个BERT的模型都有一个很大的编码器层数,(论文里面将此称为Transformer Blocks) - 基础版本就有12层,进阶版本有24层。同时它也有很大的前馈神经网络( 768和1024个隐藏层神经元),还有很多attention heads(12-16个)。这超过了Transformer论文中的参考配置参数(6个编码器层,512个隐藏层单元,和8个注意头)

    模型输入

     输入的第一个字符为[CLS],在这里字符[CLS]表达的意思很简单 - Classification (分类)。

    BERT与Transformer 的编码方式一样。将固定长度的字符串作为输入,数据由下而上传递计算,每一层都用到了self attention,并通过前馈神经网络传递其结果,将其交给下一个编码器。

          这样的架构,似乎是沿用了Transformer 的架构(除了层数,不过这是我们可以设置的参数)。那么BERT与Transformer 不同之处在哪里呢?可能在模型的输出上,我们可以发现一些端倪。

    模型输出

        每个位置返回的输出都是一个隐藏层大小的向量(基本版本BERT为768)。以文本分类为例,我们重点关注第一个位置上的输出(第一个位置是分类标识[CLS]) 。如下图

            该向量现在可以用作我们选择的分类器的输入,在论文中指出使用单层神经网络作为分类器就可以取得很好的效果。原理如下。:

      例子中只有垃圾邮件和非垃圾邮件,如果你有更多的label,你只需要增加输出神经元的个数即可,另外把最后的激活函数换成softmax即可。

    Parallels with Convolutional Nets(BERT VS卷积神经网络)

    对于那些具有计算机视觉背景的人来说,这个矢量切换应该让人联想到VGGNet等网络的卷积部分与网络末端的完全连接的分类部分之间发生的事情。你可以这样理解,实质上这样理解也很方便。

     词嵌入的新时代〜

          BERT的开源随之而来的是一种词嵌入的更新。到目前为止,词嵌入已经成为NLP模型处理自然语言的主要组成部分。诸如Word2vec和Glove 等方法已经广泛的用于处理这些问题,在我们使用新的词嵌入之前,我们有必要回顾一下其发展。

    Word Embedding Recap

         为了让机器可以学习到文本的特征属性,我们需要一些将文本数值化的表示的方式。Word2vec算法通过使用一组固定维度的向量来表示单词,计算其方式可以捕获到单词的语义单词与单词之间的关系。使用Word2vec的向量化表示方式可以用于判断单词是否相似,对立,或者说判断“男人‘与’女人”的关系就如同“国王”与“王后”。(这些话是不是听腻了〜 emmm水文必备)。另外还能捕获到一些语法的关系,这个在英语中很实用。例如“had”与“has”的关系如同“was”与“is”的关系。

         这样的做法,我们可以使用大量的文本数据来预训练一个词嵌入模型,而这个词嵌入模型可以广泛用于其他NLP的任务,这是个好主意,这使得一些初创公司或者计算资源不足的公司,也能通过下载已经开源的词嵌入模型来完成NLP的任务。

    更多关于词嵌入的信息,可以阅读我的之前文章:什么是文本的词嵌入?

    ELMo:语境问题

    上面介绍的词嵌入方式有一个很明显的问题,因为使用预训练好的词向量模型,那么无论上下文的语境关系如何,每个单词都只有一个唯一的且已经固定保存的向量化形式“。Wait a minute “ - 出自(Peters et. al., 2017McCann et. al., 2017, and yet again Peters et. al., 2018 in the ELMo paper )

    Wait a minute ”这是一个欧美日常梗,示例:

                             我:兄弟,你认真学习深度,没准能拿80W年薪啊。

                             你:Wait a minute,这么好,你为啥不做。 

     这和中文的同音字其实也类似,用这个举一个例子吧, '长' 这个字,在 '长度' 这个词中表示度量,在 '长高' 这个词中表示增加。那么为什么我们不通过”长'周围是度或者是高来判断它的读音或者它的语义呢?嗖嘎,这个问题就派生出语境化的词嵌入模型。

    EMLo改变Word2vec类的将单词固定为指定长度的向量的处理方式,它是在为每个单词分配词向量之前先查看整个句子,然后使用bi-LSTM来训练它对应的词向量。

         ELMo为解决NLP的语境问题作出了重要的贡献,它的LSTM可以使用与我们任务相关的大量文本数据来进行训练,然后将训练好的模型用作其他NLP任务的词向量的基准。

         ELMo的秘密是什么?

        ELMo会训练一个模型,这个模型接受一个句子或者单词的输入,输出最有可能出现在后面的一个单词。想想输入法,对啦,就是这样的道理。这个在NLP中我们也称作Language Modeling。这样的模型很容易实现,因为我们拥有大量的文本数据且我们可以在不需要标签的情况下去学习。

    上图介绍了ELMo预训练的过程的步骤的一部分:

    我们需要完成一个这样的任务:输入“Lets stick to”,预测下一个最可能出现的单词,如果在训练阶段使用大量的数据集进行训练,那么在预测阶段我们可能准确的预测出我们期待的下一个单词。比如输入“机器”,在‘’学习‘和‘买菜’中它最有可能的输出会是‘学习’而不是‘买菜’。

    从上图可以发现,每个展开的LSTM都在最后一步完成预测。

    对了真正的ELMo会更进一步,它不仅能判断下一个词,还能预测前一个词。(Bi-Lstm)

    ELMo通过下图的方式将hidden states(的初始的嵌入)组合咋子一起来提炼出具有语境意义的词嵌入方式(全连接后加权求和)

    ULM-FiT:NLP领域应用迁移学习

             ULM-FiT机制让模型的预训练参数得到更好的利用。所利用的参数不仅限于embeddings,也不仅限于语境embedding,ULM-FiT引入了Language Model和一个有效微调该Language Model来执行各种NLP任务的流程。这使得NLP任务也能像计算机视觉一样方便的使用迁移学习。

    The Transformer:超越LSTM的结构

            Transformer论文和代码的发布,以及其在机器翻译等任务上取得的优异成果,让一些研究人员认为它是LSTM的替代品,事实上却是Transformer比LSTM更好的处理long-term dependancies(长程依赖)问题。Transformer Encoding和Decoding的结构非常适合机器翻译,但是怎么利用他来做文本分类的任务呢?实际上你只用使用它来预训练可以针对其他任务微调的语言模型即可。

    OpenAI Transformer:用于语言模型的Transformer解码器预训练

         事实证明,我们并不需要一个完整的transformer结构来使用迁移学习一个很好的语言模型来处理NLP任务。我们只需要Transformer的解码器就行了。The decoder is a good choice because it’s a natural choice for language modeling (predicting the next word) since it’s built to mask future tokens – a valuable feature when it’s generating a translation word by word.

          该模型堆叠了十二个Decoder层。 由于在该设置中没有Encoder,因此这些Decoder将不具有Transformer Decoder层具有的Encoder - Decoder attention层。 然而,取而代之的是一个self attention层(masked so it doesn’t peak at future tokens)。

           通过这种结构调整,我们可以继续在相似的语言模型任务上训练模型:使用大量的未标记数据集训练,来预测下一个单词。举个列子:你那7000本书喂给你的模型,(书籍是极好的训练样本~比博客和推文好很多。)训练框架如下:

    Transfer Learning to Downstream Tasks

            通过OpenAI的transformer的预训练和一些微调后,我们就可以将训练好的模型,用于其他下游NLP任务啦。(比如训练一个语言模型,然后拿他的hidden state来做分类。),下面就介绍一下这个骚操作。(还是如上面例子:分为垃圾邮件和非垃圾邮件)

    OpenAI论文概述了许多Transformer使用迁移学习来处理不同类型NLP任务的例子。如下图例子所示:

     

    BERT: From Decoders to Encoders

        OpenAI transformer为我们提供了基于Transformer的精密的预训练模型。但是从LSTM到Transformer的过渡中,我们发现少了些东西。ELMo的语言模型是双向的,但是OpenAI的transformer是前向训练的语言模型。我们能否让我们的Transformer模型也具有Bi-Lstm的特性呢?

    R-BERT:“Hold my beer”

    Masked Language Model

    BERT说:“我要用 transformer 的 encoders”

    Ernie不屑道:“呵呵,你不能像Bi-Lstm一样考虑文章”

    BERT自信回答道:“我们会用masks”

    解释一下Mask:

    语言模型会根据前面单词来预测下一个单词,但是self-attention的注意力只会放在自己身上,那么这样100%预测到自己,毫无意义,所以用Mask,把需要预测的词给挡住。

    如下图:

    Two-sentence Tasks

         我们回顾一下OpenAI transformer处理不同任务的输入转换,你会发现在某些任务上我们需要2个句子作为输入,并做一些更为智能的判断,比如是否相似,比如 给出一个维基百科的内容作为输入,同时在放入一条针对该条目的问题,那么我们的算法模型能够处理这个问题吗?

         为了使BERT更好的处理2个句子之间的关系,预训练的过程还有一个额外的任务:给定2个句子(A和B),A与B是否相似?(0或者1)

    特殊NLP任务

    BERT的论文为我们介绍了几种BERT可以处理的NLP任务:

    1. 短文本相似 
    2. 文本分类
    3. QA机器人
    4. 语义标注

     

    BERT用做特征提取

        微调方法并不是使用BERT的唯一方法,就像ELMo一样,你可以使用预选训练好的BERT来创建语境化词嵌入。然后你可以将这些嵌入提供给现有的模型。

    哪个向量最适合作为上下文嵌入? 我认为这取决于任务。 本文考察了六种选择(与微调模型相比,得分为96.4):

    如何使用BERT


     

    使用BERT的最佳方式是通过 BERT FineTuning with Cloud TPUs 谷歌云上托管的笔记。如果你未使用过谷歌云TPU可以试试看,这是个不错的尝试。另外BERT也适用于TPU,CPU和GPU

    下一步是查看BERT仓库中的代码:

    1. 该模型在modeling.pyBertModel类)中构建,与vanilla Transformer编码器完全相同。
    2. run_classifier.py是微调过程的一个示例。它还构建了监督模型的分类层。如果要构建自己的分类器,请查看该文件中的create_model()方法。
    3. 可以下载几种预先训练的模型。涵盖102种语言的多语言模型,这些语言都是在维基百科的数据基础上训练而成的。
    4. BERT不会将单词视为tokens。相反,它注重WordPieces。 tokenization.py是将你的单词转换为适合BERT的wordPieces的tokensizer。

    您还可以查看BERT的PyTorch实现 AllenNLP库使用此实现允许将BERT嵌入与任何模型一起使用。

     

    如果感觉有用,留个赞吧~~

    QQ:470581985

    微信:lsq960124

    打赏一下作者:

    展开全文
  • bert

    2019-03-23 15:24:41
    bert 创建日期 星期六 23 三月 2019 从模型的创新角度看一般,创新不大,但是实验的效果太好了,基本刷新了很多NLP的任务的最好性能,另外一点是BERT具备广泛的通用性,就是说绝大部分NLP任务都可以采用类似的两...

    bert

    创建日期 星期六 23 三月 2019

    从模型的创新角度看一般,创新不大,但是实验的效果太好了,基本刷新了很多NLP的任务的最好性能,另外一点是BERT具备广泛的通用性,就是说绝大部分NLP任务都可以采用类似的两阶段模式直接去提升效果。

    与最近的语言表征模型不同,BERT基于所有层中的左、右语境进行联合调整,来预训练深层双向表征。只需要增加一个输出层,就可以对预训练的BERT表征进行微调,就能够为更多的任务创建当前的最优模型

    1、预训练模型
    BERT是一个预训练的模型,那么什么是预训练呢?举例子进行简单的介绍
    假设已有A训练集,先用A对网络进行预训练,在A任务上学会网络参数,然后保存以备后用,当来一个新的任务B,采取相同的网络结构,网络参数初始化的时候可以加载A学习好的参数,其他的高层参数随机初始化,之后用B任务的训练数据来训练网络,当加载的参数保持不变时,称为"frozen",当加载的参数随着B任务的训练进行不断的改变,称为“fine-tuning”,即更好地把参数进行调整使得更适合当前的B任务

    优点:当任务B的训练数据较少时,很难很好的训练网络,但是获得了A训练的参数,会比仅仅使用B训练的参数更优

    Task #1: Masked LM
    为了训练双向特征,这里采用了Masked Language Model的预训练方法,随机mask句子中的部分token,然后训练模型来预测被去掉的token。

    具体操作是:

    随机mask语料中15%的token,然后将masked token 位置输出的final hidden vectors送入softmax,来预测masked token。

    这里也有一个小trick,如果都用标记[MASK]代替token会影响模型,所以在随机mask的时候采用以下策略:

    1)80%的单词用[MASK]token来代替

    my dog is hairy → my dog is [MASK]
    2)10%单词用任意的词来进行代替

    my dog is hairy → my dog is apple

    3)10%单词不变

    my dog is hairy → my dog is hairy

    Task 2#: Next Sentence Prediction
    为了让模型捕捉两个句子的联系,这里增加了Next Sentence Prediction的预训练方法,即给出两个句子A和B,B有一半的可能性是A的下一句话,训练模型来预测B是不是A的下一句话
    Input = [CLS] the man went to [MASK] store [SEP]
                 penguin [MASK] are flight ## less birds [SEP]
    Label = NotNext
                 he bought a gallon [MASK] milk [SEP]
    Label = IsNext
    Input = [CLS] the man [MASK] to the store [SEP]
    训练模型,使模型具备理解长序列上下文的联系的能力

    2、BERT模型
    BERT:全称是Bidirectional Encoder Representation from Transformers,即双向Transformer的Encoder,BERT的模型架构基于多层双向转换解码,因为decoder是不能获要预测的信息的,模型的主要创新点都在pre-traing方法上,即用了Masked LM和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation

    其中“双向”表示模型在处理某一个词时,它能同时利用前面的词和后面的词两部分信息,这种“双向”的来源在于BERT与传统语言模型不同,它不是在给你大牛股所有前面词的条件下预测最可能的当前词,而是随机遮掩一些词,并利用所有没被遮掩的词进行预测

    下图展示了三种预训练模型,其中 BERT 和 ELMo 都使用双向信息,OpenAI GPT 使用单向信息


     

    可以从git上下载已经训练好的公开模型,对下载的模型文件进行解压,可以看到里面有5个文件,其中bert_model.ckpt开头的文件是负责模型变量载入的,而vocab.txt是训练时中文文本采用的字典,而bert_config.json是BERT在训练时,可选调整的一些参数

    3、BERT的输入部分

     

    bert的输入部分是个线性序列,两个句子通过分隔符分割,最前面和最后增加两个标识符号。每个单词有三个embedding:位置信息embedding,这是因为NLP中单词顺序是很重要的特征,需要在这里对位置信息进行编码;单词embedding,这个就是我们之前一直提到的单词embedding;第三个是句子embedding,因为前面提到训练数据都是由两个句子构成的,那么每个句子有个句子整体的embedding项对应给每个单词。把单词对应的三个embedding叠加,就形成了Bert的输入。

    如上图所示,输入有A句[my dog is cute]和B句[he likes playing]这两个自然句,我们首先需要将每个单词及特殊符号都转化为词嵌入向量,因为神经网络只能进行数值计算。其中特殊符[SEP]是用于分割两个句子的符号,前面半句会加上分割码A,后半句会加上分割码B
    因为要建模句子之间的关系,BERT 有一个任务是预测 B 句是不是 A 句后面的一句话,而这个分类任务会借助 A/B 句最前面的特殊符 [CLS] 实现,该特殊符可以视为汇集了整个输入序列的表征。
    最后的位置编码是 Transformer 架构本身决定的,因为基于完全注意力的方法并不能像 CNN 或 RNN 那样编码词与词之间的位置关系,但是正因为这种属性才能无视距离长短建模两个词之间的关系。因此为了令 Transformer 感知词与词之间的位置关系,我们需要使用位置编码给每个词加上位置信息。

    总结一下:
    (1)token embeddings表示的是词向量,第一个单词是CLS,可以用于之后的分类任务
    (2)segment embeddings用来区别两种句子,因为预训练不光做LM还要做以两个句子为输入的分类任务
    (3)position embeddings表示位置信息

    4、NLP的四大类任务
    (1)序列标注:分词、实体识别、语义标注……
    (2)分类任务:文本分类、情感计算……
    (3)句子关系判断:entailment、QA、自然语言推理
    (4)生成式任务:机器翻译、文本摘

    上图给出示例,对于句子关系类任务,很简单,和GPT类似,加上一个起始和终结符号,句子之间加个分隔符即可。对于输出来说,把第一个起始符号对应的Transformer最后一层位置上面串接一个softmax分类层即可。对于分类问题,与GPT一样,只需要增加起始和终结符号,输出部分和句子关系判断任务类似改造;对于序列标注问题,输入部分和单句分类是一样的,只需要输出部分Transformer最后一层每个单词对应位置都进行分类即可。从这里可以看出,上面列出的NLP四大任务里面,除了生成类任务外,Bert其它都覆盖到了,而且改造起来很简单直观

    5、模型的评价
    (1)优点

    BERT是截止至2018年10月的最新的的state of the art模型,通过预训练和精调可以解决11项NLP的任务。使用的是Transformer,相对于rnn而言更加高效、能捕捉更长距离的依赖。与之前的预训练模型相比,它捕捉到的是真正意义上的bidirectional context信息

    (2)缺点

    作者在文中主要提到的就是MLM预训练时的mask问题:

    1)[MASK]标记在实际预测中不会出现,训练时用过多[MASK]影响模型表现;

    2)每个batch只有15%的token被预测,所以BERT收敛得比left-to-right模型要慢(它们会预测每个token)

    6、GLUE语料集的介绍
    实验数据以及对应的NLP任务
    MNLI:蕴含关系推断
    QQP:问题对是否等价
    QNLI:句子是都回答问句
    SST-2:情感分析
    CoLA:句子语言性判断
    STS-B:语义相似
    MRPC:句子对是都语义等价
    RTE:蕴含关系推断
    WNLI:蕴含关系推断



     

    import argparse
    
    from torch.utils.data import DataLoader
    
    from .model import BERT
    from .trainer import BERTTrainer
    from .dataset import BERTDataset, WordVocab
    
    
    def train():
        parser = argparse.ArgumentParser()
    
        parser.add_argument("-c", "--train_dataset", required=True, type=str, help="train dataset for train bert")
        parser.add_argument("-t", "--test_dataset", type=str, default=None, help="test set for evaluate train set")
        parser.add_argument("-v", "--vocab_path", required=True, type=str, help="built vocab model path with bert-vocab")
        parser.add_argument("-o", "--output_path", required=True, type=str, help="ex)output/bert.model")
    
        parser.add_argument("-hs", "--hidden", type=int, default=256, help="hidden size of transformer model")
        parser.add_argument("-l", "--layers", type=int, default=8, help="number of layers")
        parser.add_argument("-a", "--attn_heads", type=int, default=8, help="number of attention heads")
        parser.add_argument("-s", "--seq_len", type=int, default=20, help="maximum sequence len")
    
        parser.add_argument("-b", "--batch_size", type=int, default=64, help="number of batch_size")
        parser.add_argument("-e", "--epochs", type=int, default=10, help="number of epochs")
        parser.add_argument("-w", "--num_workers", type=int, default=5, help="dataloader worker size")
    
        parser.add_argument("--with_cuda", type=bool, default=True, help="training with CUDA: true, or false")
        parser.add_argument("--log_freq", type=int, default=10, help="printing loss every n iter: setting n")
        parser.add_argument("--corpus_lines", type=int, default=None, help="total number of lines in corpus")
        parser.add_argument("--cuda_devices", type=int, nargs='+', default=None, help="CUDA device ids")
        parser.add_argument("--on_memory", type=bool, default=True, help="Loading on memory: true or false")
    
        parser.add_argument("--lr", type=float, default=1e-3, help="learning rate of adam")
        parser.add_argument("--adam_weight_decay", type=float, default=0.01, help="weight_decay of adam")
        parser.add_argument("--adam_beta1", type=float, default=0.9, help="adam first beta value")
        parser.add_argument("--adam_beta2", type=float, default=0.999, help="adam first beta value")
    
        args = parser.parse_args()
    
        print("Loading Vocab", args.vocab_path)
        vocab = WordVocab.load_vocab(args.vocab_path)
        print("Vocab Size: ", len(vocab))
    
        print("Loading Train Dataset", args.train_dataset)
        train_dataset = BERTDataset(args.train_dataset, vocab, seq_len=args.seq_len,
                                    corpus_lines=args.corpus_lines, on_memory=args.on_memory)
    
        print("Loading Test Dataset", args.test_dataset)
        test_dataset = BERTDataset(args.test_dataset, vocab, seq_len=args.seq_len, on_memory=args.on_memory) \
            if args.test_dataset is not None else None
    
        print("Creating Dataloader")
        train_data_loader = DataLoader(train_dataset, batch_size=args.batch_size, num_workers=args.num_workers)
        test_data_loader = DataLoader(test_dataset, batch_size=args.batch_size, num_workers=args.num_workers) \
            if test_dataset is not None else None
    
        print("Building BERT model")
        bert = BERT(len(vocab), hidden=args.hidden, n_layers=args.layers, attn_heads=args.attn_heads)
    
        print("Creating BERT Trainer")
        trainer = BERTTrainer(bert, len(vocab), train_dataloader=train_data_loader, test_dataloader=test_data_loader,
                              lr=args.lr, betas=(args.adam_beta1, args.adam_beta2), weight_decay=args.adam_weight_decay,
                              with_cuda=args.with_cuda, cuda_devices=args.cuda_devices, log_freq=args.log_freq)
    
        print("Training Start")
        for epoch in range(args.epochs):
            trainer.train(epoch)
            trainer.save(epoch, args.output_path)
    
            if test_data_loader is not None:
                trainer.test(epoch)
                
                
                
                
                
              import torch.nn as nn
    
    from .transformer import TransformerBlock
    from .embedding import BERTEmbedding
    
    
    class BERT(nn.Module):
        """
        BERT model : Bidirectional Encoder Representations from Transformers.
        """
    
        def __init__(self, vocab_size, hidden=768, n_layers=12, attn_heads=12, dropout=0.1):
            """
            :param vocab_size: vocab_size of total words
            :param hidden: BERT model hidden size
            :param n_layers: numbers of Transformer blocks(layers)
            :param attn_heads: number of attention heads
            :param dropout: dropout rate
            """
    
            super().__init__()
            self.hidden = hidden
            self.n_layers = n_layers
            self.attn_heads = attn_heads
    
            # paper noted they used 4*hidden_size for ff_network_hidden_size
            self.feed_forward_hidden = hidden * 4
    
            # embedding for BERT, sum of positional, segment, token embeddings
            self.embedding = BERTEmbedding(vocab_size=vocab_size, embed_size=hidden)
    
            # multi-layers transformer blocks, deep network
            self.transformer_blocks = nn.ModuleList(
                [TransformerBlock(hidden, attn_heads, hidden * 4, dropout) for _ in range(n_layers)])
    
        def forward(self, x, segment_info):
            # attention masking for padded token
            # torch.ByteTensor([batch_size, 1, seq_len, seq_len)
            mask = (x > 0).unsqueeze(1).repeat(1, x.size(1), 1).unsqueeze(1)
    
            # embedding the indexed sequence to sequence of vectors
            x = self.embedding(x, segment_info)
    
            # running over multiple transformer blocks
            for transformer in self.transformer_blocks:
                x = transformer.forward(x, mask)
    
            return x


     

    展开全文
  • 中文应用bert

    千次阅读 2019-04-11 23:06:57
    中文应用bert 调用腾讯AILab 实验室开源的bert-as-service代码 首先下载bert中文预训练模型 环境配置 pip install bert-serving-server # server pip install bert-serving-client # client, independent of `bert-...

    中文应用bert

    调用腾讯AILab 实验室开源的bert-as-service代码
    首先下载bert中文预训练模型
    在这里插入图片描述

    环境配置

    pip install bert-serving-server  # server
    pip install bert-serving-client  # client, independent of `bert-serving-server`
    

    启动bert-serving-server

    cd bert
    bert-serving-start -model_dir ./chinese_L-12_H-768_A-12 -num_worker 1 -max_seq_len 100 
    

    在这里插入图片描述

    新开一个shell,相当于开一个客户端

    
    from bert_serving.client import BertClient
    bc = BertClient(ip='localhost',check_version=False, check_length=False)
    vec = bc.encode(['CSDN中文IT知识服务集团,业务有IT信息传播、技术交流、教育培训和专业技术人才服务。旗下有网络社区、学习平台和交流平台。'])
    print(vec)
    

    在这里插入图片描述

    展开全文
  • BERT ***** New March 11th, 2020: Smaller BERT Models ***** This is a release of 24 smaller BERT models (English only, uncased, trained with WordPiece masking) referenced in Well-Read Students Learn ...
  • 解析BERT

    千次阅读 2019-07-26 14:38:45
    什么是BERTBERT是Transformer的双向编码器表示的缩写。它是由Google在2018年末开发和发布的一种新型语言模型。像BERT这样的预训练语言模型在许多自然语言处理任务中发挥着重要作用,例如问答,命名实体识别,自然...

    什么是BERT?

    BERT是Transformer的双向编码器表示的缩写。它是由Google在2018年末开发和发布的一种新型语言模型。像BERT这样的预训练语言模型在许多自然语言处理任务中发挥着重要作用,例如问答,命名实体识别,自然语言推理,文本分类等等
    BERT是一种基于微调的多层双向变压器编码器。此时,介绍Transformer架构非常重要。

    什么是变压器?

    2017年,谷歌发表了一篇题为“注意力都是你需要的”的论文,该论文提出了一种基于注意力的结构来处理与序列模型相关的问题,例如机器翻译。传统的神经机器翻译大多使用RNN或CNN作为编码器 - 解码器的模型库。然而,谷歌的基于注意力的变形金刚模型放弃了传统的RNN和CNN公式。该模型高度并行运行,因此在提高翻译性能的同时,培训速度也非常快。
    让我们退后一步,理解注意力。

    什么是注意力?

    注意机制可以看作是模糊记忆的一种形式。内存由模型的隐藏状态组成,模型选择从内存中检索内容。在我们深入了解Attention之前,让我们简要回顾一下Seq2Seq模型。传统的机器翻译基本上是基于Seq2Seq模型。该模型分为编码器层和解码器层,并由RNN或RNN变体(LSTM,GRU等)组成。编码器矢量是从模型的编码器部分产生的最终隐藏状态。该向量旨在封装所有输入元素的信息,以帮助解码器进行准确的预测。它充当模型的解码器部分的初始隐藏状态。Seq2Seq模型的主要瓶颈是需要将源序列的全部内容压缩为固定大小的矢量。如果文本稍长,则很容易丢失文本的某些信息。为了解决这个问题,注意力应运而生。注意机制通过允许解码器回顾源序列隐藏状态,然后将其加权平均值作为附加输入提供给解码器来缓解该问题。使用Attention,顾名思义,模型在解码阶段选择最适合当前节点的上下文作为输入。注意与传统的Seq2Seq模型有两个主要区别。首先,编码器向解码器提供更多数据,编码器将所有节点的隐藏状态提供给解码器,

    https://jalammar.github.io/images/seq2seq_7.mp4

    其次,解码器不直接使用所有编码器提供的隐藏状态作为输入,而是采用选择机制来选择与当前位置最匹配的隐藏状态。为此,它尝试通过计算每个隐藏状态的得分值并对得分进行softmax计算来确定哪个隐藏状态与当前节点最密切相关,这允许隐藏状态的更高相关性具有更大小数值,不太相关的隐藏状态具有较低的小数值。然后它将每个隐藏状态乘以其softmaxed得分,从而放大具有高分数的隐藏状态,并淹没具有低分数的隐藏状态。该评分练习在解码器侧的每个时间步骤完成。
    https://jalammar.github.io/images/attention_process.mp4
    现在让我们在下面的可视化中将整个事物放在一起,看看注意过程是如何工作的:

    • 注意解码器RNN接收令牌的嵌入和初始解码器隐藏状态。
    • RNN处理其输入,产生输出和新的隐藏状态向量(h4)。输出被丢弃。
    • 注意步骤:我们使用编码器隐藏状态和h4向量来计算该时间步长的上下文向量(C4)。
    • 我们将h4和C4连接成一个向量。
    • 我们通过前馈神经网络(与模型共同训练的一个)传递此向量。
    • 前馈神经网络的输出指示该时间步长的输出字。
    • 重复下一步的步骤
      https://jalammar.github.io/images/attention_tensor_dance.mp4

    回到transformer

    变压器模型使用编码器 - 解码器架构。在Google发表的论文中,编码器层由6个编码器堆叠,解码器层相同。每个编码器和解码器的内部结构如下 -

    在这里插入图片描述

    编码器由两层组成,一个自注意层和一个前馈神经网络。自我关注有助于当前节点不仅关注当前单词,而且还获得上下文的语义。解码器还包含编码器提到的双层网络,但在两层中间还有一个关注层,以帮助当前节点获得需要注意的关键内容。
    以下是Transformer架构的详细结构 -
    在这里插入图片描述

    让我们分解各个组件。

    自我关注

    自我关注是Transformer将其他相关单词的“理解”转换为我们正在处理的单词的一种方式。
    首先,自我关注计算三个新的向量。在论文中,向量的维度是512维。我们分别将这三个向量称为Query,Key和Value。这三个向量是通过将字嵌入向量与随机初始化矩阵(文中的维数为(64,512))相乘而产生的,其值在反向传播过程中被更新。

    在这里插入图片描述

    接下来,我们计算自我关注的分数值,它确定当我们在某个位置编码单词时对输入句子的其余部分的注意力。该小数值的计算方法使用Query和Key向量。然后我们将结果除以常数。这里我们除以8.这个值通常是上面提到的矩阵的第一维的平方根,也就是64的平方根8.然后我们对所有得分进行softmax计算。结果是每个单词与当前位置的单词的相关性。当然,当前位置的相关性一词肯定会很大。最后一步是将Value向量与softmax结果相乘并添加它们。结果是当前节点处的自我关注的价值。

    在这里插入图片描述

    这种通过查询和密钥之间的相似度来确定值的权重分布的方法被称为缩放的点积注意。

    多头注意力

    本文中更强大的部分是增加了另一种自我关注机制,称为“多头”关注,它不仅仅初始化了一组Q,K,V矩阵。相反,初始化多个组,变换器使用8个组,因此最终结果是8个矩阵。

    在这里插入图片描述

    前馈神经网络不能接受8个矩阵,因此我们需要一种方法将8个矩阵减少到1.为此,我们首先将8个矩阵连接在一起得到一个大矩阵,然后将这个组合矩阵与一个随机初始化矩阵相乘得到最后的矩阵。让我们来看看整个过程。

    在这里插入图片描述

    Transformer以三种不同的方式使用多头注意力:
    在“编码器 - 解码器关注”层中,查询来自先前的解码器层,并且存储器键和值来自编码器的输出。这允许解码器中的每个位置都参与输入序列中的所有位置。这模拟了序列到序列模型中典型的编码器 - 解码器注意机制。
    编码器包含自我关注层。在自我关注层中,所有键,值和查询来自相同的位置,在这种情况下,是编码器中前一层的输出。编码器中的每个位置都可以处理编码器前一层中的所有位置。
    类似地,解码器中的自注意层允许解码器中的每个位置参与解码器中的所有位置直到并包括该位置。我们需要防止解码器中的向左信息流以保持自回归属性。我们通过屏蔽(设置为-∞)softmax输入中与非法连接相对应的所有值来实现缩放点产品注意内部。这将在解码器部分中更详细地探讨,我们将讨论掩蔽。

    位置编码

    到目前为止,我们没有办法解释变换器模型中输入序列中的单词顺序。为了解决这个问题,变换器在编码器和解码器层的输入端增加了一个额外的矢量位置编码。尺寸与嵌入尺寸相同。此位置编码的值将添加到嵌入值中,并作为输入发送到下一层。有许多位置编码选项,包括学习和修复。
    在这里插入图片描述

    残差连接和图层规范化

    在编码器和解码器中,在两个子层中的每一个周围采用残余连接,然后进行层标准化。跳过连接或剩余连接用于允许梯度直接流过网络,而不通过非线性激活功能。非线性激活函数本质上是非线性的,导致梯度爆炸或消失(取决于权重)。从概念上说,跳过连接形成一条“总线”,它在网络中流动,反过来,梯度也可以沿着它向后流动。标准化有助于解决称为内部协变量偏移的问题。内部协变量移位是指在神经网络中发生的协变量移位,即从(例如)第2层到第3层。这是因为,当网络学习并且权重被更新时,网络中特定层的输出分布发生变化。这迫使较高层适应该漂移,这减慢了学习速度。在对神经网络中的输入进行归一化后,我们不必担心输入特征的规模差别很大。要了解图层规范化,将其与批量标准化进行对比非常有用。小批量包含具有相同数量功能的多个示例。小批量是矩阵 - 如果每个输入是多维的,则为张量 - 其中一个轴对应于批次,另一个轴 - 或轴 - 对应于特征尺寸。批量标准化规范化批次维度中的输入要素。图层规范化的关键特性是它可以对要素之间的输入进行标准化。在批量标准化中,统计数据是在批次中计算的,并且对于批次中的每个示例都是相同的。相反,在层规范化中,统计数据是跨每个特征计算的,并且与其他示例无关。

    在这里插入图片描述

    将剩余连接和层规范化结合在一起。

    在这里插入图片描述

    解码器

    回到Transformer体系结构图,我们可以看到解码器部分类似于编码器部分,但底部有一个掩盖的多头注意。Mask表示屏蔽某些值的掩码,以便在更新参数时它们不起作用。Transformer模型中有两种掩码 - 填充掩码和序列掩码。填充掩码用于所有缩放的点积注意,并且序列掩码仅用于解码器的自我注意。
    填充掩码解决了输入序列具有可变长度的问题。具体来说,我们在较短的序列后填0。但是如果输入序列太长,则会截取左侧的内容,并直接丢弃多余的内容。因为这些填充的位置实际上没有意义,我们的注意机制不应该集中在这些位置,所以我们需要做一些处理。具体方法是在这些位置的值上加一个非常大的负数(负无穷大),这样这些位置的概率在softmax之后将接近0!填充掩码实际上是一个张量,每个值都是一个布尔值,false值是我们想要处理的值。
    序列掩码旨在确保解码器无法查看将来的信息。也就是说,对于序列,在time_step t,我们的解码输出应该仅取决于t之前的输出,而不取决于t之后的输出。这特定于Transformer架构,因为我们没有RNN,我们可以按顺序输入序列。在这里,我们一起输入所有内容,如果没有掩码,多头注意力将考虑每个位置的整个解码器输入序列。我们通过生成上三角矩阵来实现这一点,上三角形的值全为零,并将该矩阵应用于每个序列。
    为了解码器的自我关注,使用缩放的点积注意,并且添加填充掩码和序列掩码作为attn_mask。在其他情况下,attn_mask等于填充掩码。
    另一个细节是解码器输入将向右移动一个位置。这样做的一个原因是我们不希望我们的模型在训练期间学习如何复制我们的解码器输入,但我们想要了解给定编码器序列和模型已经看到的特定解码器序列,预测下一个单词/字符。如果我们不移位解码器序列,则模型学习简单地“复制”解码器输入,因为位置i的目标字/字符将是解码器输入中的字/字符i。因此,通过将解码器输入移位一个位置,我们的模型需要预测仅看到单词/字符1,…,i-1的位置i的目标字/字符在解码器序列中。这可以防止我们的模型学习复制/粘贴任务。我们用句子开头令牌填充解码器输入的第一个位置,因为由于右移,该位置将是空的。类似地,我们将一个句末结尾标记附加到解码器输入序列以标记该序列的结尾,并且它还附加到目标输出语句。

    输出层

    在完全执行解码器层之后,为了将得到的矢量映射到来自词汇表的单词,最后添加全连接层和
    s​​oftmax层。
    线性层是一个简单的完全连接的神经网络,它将解码器堆栈产生的矢量投影到一个更大,更大的矢量中,称为logits矢量。让我们假设我们的模型知道从训练数据集中学到的10,000个独特的英语单词(我们的模型的“输出词汇表”)。这将使logits矢量10,000个细胞宽 - 每个细胞对应于一个唯一单词的得分。这就是我们如何解释模型的输出,然后是线性层。然后,softmax层将这些分数转换为概率(所有正数,所有加起来都为1.0)。选择具有最高概率的单元,并且将与其相关联的单词作为该时间步的输出。

    在这里插入图片描述

    回到BERT

    BERT基于Transformer架构。它是一种深度,双向深度神经网络模型。Google最初发布了两个版本,如下图所示。这里L表示变压器的层数,H表示输出的维数,A表示多头注意的数量。在这两个版本中,前馈大小设置为4层。
    BERTBASE:L = 12,H = 768,A = 12,总参数= 110M
    BERTLARGE:L = 24,H = 1024,A = 16,总参数= 340M
    使用BERT有两个阶段:预训练和微调。在预训练期间,模型在不同的预训练任务上训练未标记的数据。对于微调,首先使用预先训练的参数初始化BERT模型,并使用来自下游任务的标记数据对所有参数进行微调。每个下游任务都有单独的微调模型,即使它们使用相同的预先训练的参数进行初始化。BERT的一个显着特点是它跨越不同任务的统一架构。预训练架构与最终下游架构之间的差异很小。在微调期间,所有参数都经过微调。

    在这里插入图片描述

    BERT训练前流程

    BERT预训练阶段包括两个无监督预测任务,一个是掩蔽语言模型,另一个是下一句预测。
    蒙面语言模型 - 由于双向功能(双向性)和BERT使用的多层自我关注机制的效果,为了训练深度双向表示,一些百分比(本文中为15%)输入令牌的输入被简单地随机掩盖,然后预测那些被屏蔽的令牌。对应于掩模标记的最终隐藏向量被馈送到词汇表上的输出softmax,如在标准LM中。与从左到右的语言模型预训练不同,MLM目标允许表示融合的左侧和右侧的上下文,这使得可以预先训练深度双向变换器。虽然这允许获得双向预训练模型,但缺点是预训练和微调之间存在不匹配,因为在微调期间不会出现[MASK]标记。为了缓解这种情况,作者并不总是用实际的[MASK]令牌替换“蒙面”单词。训练数据生成器随机选择15%的令牌位置进行预测。如果选择了第i个令牌,则将其替换为(1)[MASK]令牌80%的时间(2)随机令牌10%的时间(3)未更改的第i个令牌10%时间。
    下一句话预测 - 。为了训练理解句子关系以及单词之间的语义关系的模型,BERT还预先训练二进制化的下一句预测任务,该任务可以从任何文本语料库中非常容易地生成。为A和B选择一些句子,其中50%的数据B是A的下一个句子,剩余的50%的数据B是在语料库中随机选择的,并学习相关性。添加这种预训练的目的是许多NLP任务(如QA和NLI)需要理解两个句子之间的关系,以便预训练模型能够更好地适应这些任务。

    标记化 - BERT不会将单词视为标记。相反,它看着WordPieces。这意味着一个单词可以分解为多个子单词。这种标记化在处理词汇单词时是有益的,它可以帮助更好地表示复杂的单词。

    BERT模型输入

    BERT的输入可以是单词序列中的单个句子或句子对(例如,[问题,答案])。对于给定的单词,其输入表示可以由三部分嵌入求和组成。嵌入的可视化表示如下所示:

    在这里插入图片描述

    令牌嵌入表示单词向量。第一个字是CLS标志,可用于后续分类任务。对于非分类任务,可以忽略CLS标志。段嵌入用于区分两个句子,因为预训练不仅是语言模型,而且是具有两个句子作为输入的分类任务。位置嵌入编码字顺序。

    用于下游NLP任务的BERT微调

    对于每个下游NLP任务,我们只需将特定于任务的输入和输出插入BERT并对端到端的所有参数进行微调。在输入处,来自预训练的句子A和句子B可以类似于释义中的句子对,蕴涵中的假设前提对,问题回答中的问题 - 通道对等。在输出处,令牌表示被馈送到用于令牌级别任务的输出层,例如序列标记或问题回答,并且[CLS]表示被馈送到输出层以进行分类,例如蕴涵或情绪分析。与预训练相比,微调相对便宜。

    在这里插入图片描述

    BERT用于特征提取

    微调方法不是使用BERT的唯一方法。您可以使用预先训练的BERT创建语境化词嵌入。然后,您可以将这些嵌入提供给您现有的模型 - 这个过程本文显示了在命名实体识别等任务上微调BERT的产量结果。

    在这里插入图片描述

    哪个向量最适合作为上下文嵌入?这取决于任务。本文考察了六种选择(与得分为96.4的微调模型相比):

    在这里插入图片描述

    展开全文
  • 为了进一步促进中文信息处理的研究发展,我们发布了基于全词遮罩(Whole Word Masking)技术的中文预训练模型BERT-wwm,以及与此技术密切相关的模型:BERT-wwm-ext,RoBERTa-wwm-ext,RoBERTa-wwm-ext-large, RBT3, ...
  • [NLP自然语言处理]谷歌BERT模型深度解析

    万次阅读 多人点赞 2018-10-15 17:49:18
    我的机器学习教程「美团」算法工程师带你入门机器学习 已经开始更新了,欢迎大家订阅~ 任何关于算法、编程、AI行业知识或博客内容的问题,可以随时扫码关注公众号「图灵的猫」,加入”...BERT模型代码已经发布,...
  • Add BERT support

    2020-11-26 08:32:18
    <div><p>Add support for BERT. Specifically: <ul><li> <p>Use the special pair sentence concatenation with the [SEP] token (<code>src/tasks.py) </li><li> <p>Use BERT-specific task modules: single-...

空空如也

1 2 3 4 5 ... 20
收藏数 8,948
精华内容 3,579
关键字:

BERT