BERT是一种预训练语言模型(pre-trained language model, PLM),其全称是Bidirectional Encoder Representations from Transformers。下面从语言模型和预训练开始展开对预训练语言模型BERT的介绍。
语言模型 :对于任意的词序列,它能够计算出这个序列是一句话的概率。比如词序列A:“知乎|的|文章|真|水|啊”,这个明显是一句话,一个好的语言模型也会给出很高的概率,再看词序列B:“知乎|的|睡觉|苹果|好快”,这明显不是一句话,如果语言模型训练的好,那么序列B的概率就很小很小。
下面给出较为正式的定义。假设我们要为中文创建一个语言模型,V 表示词典, V = { 猫,狗,机器,学习,语言,模型,…}, 。语言模型就是这样一个模型:给定词典 V,能够计算出任意单词序列(比如:单词序列[
、
、
...]、[
、
、
])是一句话的概率。probability =
从文本生成角度来看,我们也可以给出如下的语言模型定义:给定一个短语(一个词组或一句话),语言模型可以生成(预测)接下来的一个词。
从字面上看,预训练模型(pre-training model)是先通过一批语料进行模型训练,然后在这个初步训练好的模型基础上,再继续训练或者另作他用。预训练模型的训练和使用分别对应两个阶段:预训练阶段(pre-training)和 微调(fune-tuning)阶段。
预训练阶段一般会在超大规模的语料上,采用无监督(unsupervised)或者弱监督(weak-supervised)的方式训练模型,期望模型能够获得语言相关的知识,比如句法,语法知识等等。经过超大规模语料的”洗礼”,预训练模型往往会是一个Super模型,一方面体现在它具备足够多的语言知识,一方面是因为它的参数规模很大。
微调阶段是利用预训练好的模型,去定制化地训练某些任务,使得预训练模型”更懂”这个任务。例如,利用预训练好的模型继续训练文本分类任务,将会获得比较好的一个分类结果,直观地想,预训练模型已经懂得了语言的知识,在这些知识基础上去学习文本分类任务将会事半功倍。利用预训练模型去微调的一些任务(例如前述文本分类)被称为下游任务(down-stream)。
从BERT的全称,Bidirectional Encoder Representation from Transformer(来自Transformer的双向编码器表征),可以看出BERT是基于Transformer模型的,只是其中的编码器。输入一个句子,Transformer的编码器会输出句子中每个单词的向量表示。而双向则是由于Transformer编码器是双向的。它的输入是完整的句子,在指定某个Token时,BERT已经读入了它两个方向上的所有单词。举个例来理解BERT是如何从Transformer中得到双向编码表示的。
假设我们有一个句子A:He got bit by Python,现在我们把这个句子输入Transformer并得到了每个单词的上下文表示(嵌入表示)作为输出。Transformer的编码器通过多头注意力机制理解每个单词的上下文,然后输出每个单词的嵌入向量。如下图所示,我们输入一个句子到Transformer的编码器,它输出句子中每个单词的上下文表示。下图中代表单词He的向量表示,每个单词向量表示的大小应当于每个编码器隐藏层的大小。假设编码器层大小为768,那么单词的向量表示大小也就是768。

BERT初期有两个版本,分为base版和large版本。base版一共有110M参数,large版有340M的参数,两个版本的BERT的参数量都是上亿的。
L:Transformer blocks 层数;H:hidden size ;A:the number of self-attention heads
Bert是Transformer的encoder部分,使用大量的未标记文本数据进行预训练,从而学习并掌握某种语言的表达形式。结构上使用了基于多头注意力机制的transformer,训练中采取两种不同的训练方式:(Masked Language Model)隐蔽语言模型、(Next Sentence Prediction)下一结构预测。其中双向主要体现在bert的训练任务一中:隐蔽语言模型。
Masked LM 可以形象地称为完形填空问题,随机掩盖掉每一个句子中15%的词,用其上下文来去判断被盖住的词原本应该是什么。随机Mask语料中15%的Token,然后将masked token位置的最终隐层向量送入softmax,来预测masked token。 举例来说,有这样一个未标注句子 my dog is hairy ,我们可能随机选择了hairy进行遮掩,就变成 my dog is [mask] ,训练模型去预测 [mask] 位置的词,使预测出 hairy的可能性最大,在这个过程中就将上下文的语义信息学习并体现到模型参数中。
而在下游的NLP任务fine-tuning阶段中不存被Mask的词,为了和后续任务保持一致,作者按一定比例在需要预测的词的位置上输入了原词或输入了某个随机的词。[MASK]通过attention均结合了左右上下文的信息,这体现了双向。以下是MASK策略:
在任何一个词都有可能是被替换掉的条件下,强迫模型在编码当前时刻不能太依赖于当前的词,而是要考虑它的上下文,甚至根据上下文进行纠错。所以训练预料中有必须正确的信息(10%)、未知的信息(80% MASK,使模型具有预测能力)、错误的信息(加入噪声10%,使模型具有纠错能力),模型才能获取全局全量的信息。
很多下游任务(QA和natural language inference)都是基于两个句子之间关系的理解,基于此项任务,为了增强模型对句子之间关系的理解能力。 所以预测句子关系,判断两个句子之间是否是有关联,在训练过程中,BERT会抽全50%有关联的句子(这里的句子是指有联系的Token序列),百分之50的概率随机抽选两无关的句子,然后让BERT模型判断这两个句子是否相关。其输入形式是,开头是一个特殊符号[CLS],然后两个句子之间用[SEP]隔断:
Input = [CLS] the man went to [MASK] store [SEP]he bought a gallon [MASK] milk [SEP]
Label = IsNext
Input = [CLS] the man [MASK] to the store [SEP]penguin [MASK] are flight ##less birds[SEP]
Label = NotNext
在把数据喂给BERT之前,通过下面三个嵌入层将输入转换为嵌入向量:词嵌入(Token embedding);段嵌入(Segment embedding);位置嵌入(Position embedding)。
以下面句子为例,展现三种嵌入向量的表示。
Sentence A: Paris is a beautiful city.
Sentence B: I love Paris.
1.Token Embedding(词嵌入)
表示的是词向量,既可以是原始词向量(源码中是token在词汇表中对应的索引),也可以是经过word2vector或者是glove等模型处理后的静态向量。在实际代码实现中,输入文本在送入token embeddings 层之前要先进行tokenization处理。此外,两个特殊的token会被插入到tokenization的结果的开头 ([CLS])和结尾 ([SEP])
第一步:使用使用WordPiece分词器分词。
#第一步:使用WordPiece分词器分词
tokens = [Paris, is, a, beautiful, city, I, love, Paris]
第二步:在一个句子前面,添加[CLS]标记。
tokens = [ [CLS], Paris, is, a, beautiful, city, I, love, Paris]
第三步:在每个句子的结尾,添加[SEP]标记。
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP]]
特别说明:
[CLS]标记只加在第一个句子前面,而[SEP]标记加到每个句子末尾。
[CLS]标记用于分类任务,而[SEP]标记用于表示每个句子的结尾。
在把所有的标记喂给BERT之前,我们使用一个叫作标记嵌入的嵌入层转换这些标记为嵌入向量。这些嵌入向量的值会在训练过程中学习。经过学习,得到了每个token的词嵌入向量。

2.Segment Embedding (段嵌入)
段嵌入用来区别两种句子。因为Bert中存在着两个任务,一个是隐藏语言模型,另一个是预测句子关系,所以在输入时需要区分两个句子。 如果输入数据由两个句子拼接而成,如果词语是属于第一个句子A,那么该标记会映射到嵌入;反之属于句子 B,则映射到嵌入
。

如果输入仅仅只有一个句子,那么它的segment embedding只会映射到 。

3.Position Embedding(位置编码)
学习出来的embedding向量。与Transformer不同,Transformer中是预先设定好的值。

4.最终表示
如下图所示,首先我们将给定的输入序列分词为标记列表,然后喂给标记嵌入层,片段嵌入层和位置嵌入层,得到对应的嵌入表示。然后,累加所有的嵌入表示作为BERT的输入表示。

bert模型的输出可以包括四个:
1. last_hidden_state
torch.FloatTensor类型的,最后一个隐藏层的序列的输出。大小是(batch_size, sequence_length, hidden_size) sequence_length是我们截取的句子的长度,hidden_size是768。
2.pooler_output
torch.FloatTensor类型的,[CLS]的这个token的输出,输出的大小是(batch_size, hidden_size)。
3.hidden_states
tuple(torch.FloatTensor)这是输出的一个可选项,如果输出,需要指定config.output_hidden_states=True,它也是一个元组,它的第一个元素是embedding,其余元素是各层的输出,每个元素的形状是(batch_size, sequence_length, hidden_size)。
4.attentions
这也是输出的一个可选项,如果输出,需要指定config.output_attentions=True,它也是一个元组,它的元素是每一层的注意力权重,用于计算self-attention heads的加权平均值。
Transformers(以前称为pytorch-transformers和pytorch-pretrained-bert)提供用于自然语言理解(NLU)和自然语言生成(NLG)的最先进的模型(BERT,GPT-2,RoBERTa,XLM,DistilBert,XLNet,CTRL ...) ,拥有超过32种预训练模型,支持100多种语言,并且在TensorFlow 2.0和PyTorch之间具有深厚的互操作性。我们借助Transformers来实现bert的调用。
import transformers
#实例化bert模型
bert_model = transformers.BertModel.from_pretrained(
pretrained_model_name_or_path = '/ssd/Spider/Baidu_NER/Pre_Model/chinese_roberta_wwm_large_ext/',
output_hidden_states=True,
output_attentions=True)
#bert需要的三种输入形式
def encoder(vocab_path,sentence):
#将text_list embedding成bert模型可用的输入形式
tokenizer = transformers.BertTokenizer.from_pretrained(vocab_path)
tokenizer = tokenizer(
sentence,
return_tensors='pt' # 返回的类型为pytorch tensor
)
input_ids = tokenizer['input_ids']
token_type_ids = tokenizer['token_type_ids']
attention_mask = tokenizer['attention_mask']
return input_ids,token_type_ids,attention_mask
sentence = "中华人民共和国万岁"
#生成三种bert需要的输入形式
input_ids,token_type_ids,attention_mask = encoder(
vocab_path="/ssd/Spider/Baidu_NER/Pre_Model/chinese_roberta_wwm_large_ext/vocab.txt",
sentence = sentence)
#调用bert模型
sentence_outputs = bert_model(input_ids,token_type_ids,attention_mask)
如下图所示:
input_ids表示的是分词后在token中添加了[CLS]和[SEP]标记之后的id表示。token_types_ids则表示的是Segment Embeddings ,如果输入数据由两个句子拼接而成,如果词语是属于第一个句子,则Segment Embeddings 对应的位置是0,如果属于第二个句子,则segment Embeddings对应的位置为1。



我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序