BERT详解(附带ELMo、GPT介绍)
首先我会详细阐述BERT原理,然后简单介绍一下ELMO以及GPT
BERT详解
BERT全称为Bidirectional Encoder Representation from Transformer,是Google以无监督的方式利用大量无标注文本「炼成」的语言模型,其架构为Transformer中的Encoder(BERT=Encoder of Transformer)
我在Transformer详解中已经详细的解释了所有Transformer的相关概念,这里就不再赘述
以往为了解决不同的NLP任务,我们会为该任务设计一个最合适的神经网络架构并做训练,以下是一些简单的例子
不同的NLP任务通常需要不同的模型,而设计这些模型并测试其performance是非常耗成本的(人力,时间,计算资源)。如果有一个能直接处理各式NLP任务的通用架构该有多好?
随着时代演进,不少人很自然地有了这样子的想法,而BERT就是其中一个将此概念付诸实践的例子
Google在预训练BERT时让它同时进行两个任务:
- 漏字填空(完型填空),学术点的说法是 Masked Language Model
- 判断第2个句子在原始本文中是否跟第1个句子相接(Next Sentence Prediction)
对正常人来说,要完成这两个任务非常简单。只要稍微看一下前后文就知道完形填空任务中[MASK]
里应该填退了
;而醒醒吧
后面接你没有妹妹
也十分合理
接下来我会分别详细介绍论文中这两个任务的设计细节
BERT语言模型任务一:Masked Language Model
在BERT中,Masked LM(Masked Language Model)构建了语言模型,简单来说,就是随机遮盖或替换一句话里面的任意字或词,然后让模型通过上下文预测那一个被遮盖或替换的部分,之后做Loss的时候也只计算被遮盖部分的Loss,这其实是一个很容易理解的任务,实际操作如下:
- 随机把一句话中15%的token(字或词)替换成以下内容:
- 这些token有80%的几率被替换成
[MASK]
,例如my dog is hairy→my dog is [MASK] - 有10%的几率被替换成任意一个其它的token,例如my dog is hairy→my dog is apple
- 有10%的几率原封不动,例如my dog is hairy→my dog is hairy
- 这些token有80%的几率被替换成
- 之后让模型预测和还原被遮盖掉或替换掉的部分,计算损失的时候,只计算在第1步里被随机遮盖或替换的部分,其余部分不做损失,其余部分无论输出什么东西,都无所谓
这样做的好处是,BERT并不知道[MASK]替换的是哪一个词,而且任何一个词都有可能是被替换掉的,比如它看到的apple可能是被替换的词。这样强迫模型在编码当前时刻词的时候不能太依赖当前的词,而要考虑它的上下文,甚至根据上下文进行"纠错"。比如上面的例子中,模型在编码apple时,根据上下文my dog is,应该把apple编码成hairy的语义而不是apple的语义
BERT语言模型任务二:Next Sentence Prediction
我们首先拿到属于上下文的一对句子,也就是两个句子,之后我们要在这两个句子中加一些特殊的token:[CLS]上一句话[SEP]下一句话[SEP]
。也就是在句子开头加一个[CLS]
,在两句话之间和句末加[SEP]
,具体地如下图所示
可以看到,上图中的两句话明显是连续的。如果现在有这么一句话[CLS]我的狗很可爱[SEP]企鹅不擅长飞行[SEP]
,可见这两句话就不是连续的。在实际训练中,我们会让这两种情况出现的数量为1:1
Token Embedding
就是正常的词向量,即PyTorch中的nn.Embedding()
Segment Embedding
的作用是用embedding的信息让模型分开上下句,我们给上句的token全0,下句的token全1,让模型得以判断上下句的起止位置,例如
[CLS]我的狗很可爱[SEP]企鹅不擅长飞行[SEP]
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
Position Embedding
和Transformer中的不一样,不是三角函数,而是学习出来的
Multi-Task Learning
BERT预训练阶段实际上是将上述两个任务结合起来,同时进行,然后将所有的Loss相加,例如
Input:
[CLS] calculus is a branch of math [SEP] panda is native to [MASK] central china [SEP]
Targets: false, south
----------------------------------
Input:
[CLS] calculus is a [MASK] of math [SEP] it [MASK] developed by newton and leibniz [SEP]
Targets: true, branch, was
Fine-Tuning
BERT的Fine-Tuning共分为4中类型,以下内容、图片均来自台大李宏毅老师Machine Learning课程(以下内容 图在上,解释在下)
如果现在的任务是classification,首先在输入句子的开头加一个代表分类的符号[CLS]
,然后将该位置的output,丢给Linear Classifier,让其predict一个class即可。整个过程中Linear Classifier的参数是需要从头开始学习的,而BERT中的参数微调就可以了
这里李宏毅老师有一点没讲到,就是为什么要用第一个位置,即[CLS]
位置的output。这里我看了网上的一些博客,结合自己的理解解释一下。因为BERT内部是Transformer,而Transformer内部又是Self-Attention,所以[CLS]
的output里面肯定含有整句话的完整信息,这是毋庸置疑的。但是Self-Attention向量中,自己和自己的值其实是占大头的,现在假设使用$w_1$的output做分类,那么这个output中实际上会更加看重$w_1$,而$w_1$又是一个有实际意义的字或词,这样难免会影响到最终的结果。但是[CLS]
是没有任何实际意义的,只是一个占位符而已,所以就算[CLS]
的output中自己的值占大头也无所谓。当然你也可以将所有词的output进行concat,作为最终的output
如果现在的任务是Slot Filling,将句子中各个字对应位置的output分别送入不同的Linear,预测出该字的标签。其实这本质上还是个分类问题,只不过是对每个字都要预测一个类别
如果现在的任务是NLI(自然语言推理)。即给定一个前提,然后给出一个假设,模型要判断出这个假设是 正确、错误还是不知道。这本质上是一个三分类的问题,和Case 1差不多,对[CLS]
的output进行预测即可
如果现在的任务是QA(问答),举例来说,如上图,将一篇文章,和一个问题(这里的例子比较简单,答案一定会出现在文章中)送入模型中,模型会输出两个数s,e,这两个数表示,这个问题的答案,落在文章的第s个词到第e个词。具体流程我们可以看下面这幅图
首先将问题和文章通过[SEP]
分隔,送入BERT之后,得到上图中黄色的输出。此时我们还要训练两个vector,即上图中橙色和黄色的向量。首先将橙色和所有的黄色向量进行dot product,然后通过softmax,看哪一个输出的值最大,例如上图中$d_2$对应的输出概率最大,那我们就认为s=2
同样地,我们用蓝色的向量和所有黄色向量进行dot product,最终预测得$d_3$的概率最大,因此e=3。最终,答案就是s=2,e=3
你可能会觉得这里面有个问题,假设最终的输出s>e怎么办,那不就矛盾了吗?其实在某些训练集里,有的问题就是没有答案的,因此此时的预测搞不好是对的,就是没有答案
以上就是BERT的详细介绍,参考以下文章
ELMo
ELMo是Embedding from Language Model的缩写,它通过无监督的方式对语言模型进行预训练来学习单词表示
这篇论文的想法其实非常简单,但是效果却很好。它的思路是用深度的双向LSTM在大量未标注数据上训练语言模型,如下图所示
在实际任务中,对于输入的句子,我们使用上面的语言模型来处理它,得到输出向量,因此这可以看作是一种特征提取。但是ELMo与普通的Word2Vec或GloVe不同,ELMo得到的Embedding是有上下文信息的
未完待续
- 二叉树的基本概念和遍历
- Java中MD5加密算法实现方法——附上具体代码
- 新手,Visual Studio 2015 配置Boost库,如何编译和选择,遇到无法打开文件“libboost_thread-vc140-mt-gd-1_63.lib“的解决办法
- Java中处理正则表达式的工具类——总有一个适合你
- 【Spark研究】用Apache Spark进行大数据处理第一部分:入门介绍
- “一切都是消息”--MSF(消息服务框架)入门简介
- 【Spark研究】用Apache Spark进行大数据处理第二部分:Spark SQL
- Android基础总结(2)——活动Activity
- Java实现的IP处理工具类——可用于项目
- 使用SQLServer同义词和SQL邮件,解决发布订阅中订阅库丢失数据的问题
- 一次误报引发的DNS检测方案的思考:DNS隧道检测平民解决方案
- Andriod基础——Adapter类
- ORM查询语言(OQL)简介--高级篇:脱胎换骨
- 用Java实现处理日期的工具类——常用日期处理方法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Vim 最常用命令
- JSP 页面中的 路径问题
- Servlet 3.0 文件上传与下载
- PyCharm 下 Turtle无提示有警告
- JSP 中 out.print() 和 out.write() 区别
- IDEA 导入web项目
- IDEA 连接数据库
- 图解面试题:如何找到破产玩家?
- python面向对象学习(一)
- playbook中when的使用
- 当遇到跨域开发时, 我们如何处理好前后端配置和请求库封装(koa/axios版)
- 你想要的Android性能优化系列:内存优化 !
- phabricator介绍与搭建
- nginx反向代理与负载均衡
- APP | edxposed框架+trustmealredy模块抓包小程序