雷锋网 AI 科技评论按:本文的作者是张俊林老师,他是中国中文信息学会理事,中科院软件所博士,目前在新浪微博 AI Lab 担任资深算法专家。在此之前,张俊林老师曾在阿里巴巴任资深技术专家并负责新技术团队,也曾在百度和用友担任技术经理及技术总监等职务。同时他是技术书籍《这就是搜索引擎:核心技术详解》(该书荣获全国第十二届优秀图书奖)、《大数据日知录:架构与算法》的作者。本文首发于知乎,经作者许可,雷锋网 AI 科技评论进行转载。
在辞旧迎新的时刻,大家都在忙着回顾过去一年的成绩(或者在灶台前含泪数锅),并对 2019 做着规划,当然也有不少朋友执行力和工作效率比较高,直接把 2018 年初制定的计划拷贝一下,就能在 3 秒钟内完成 2019 年计划的制定,在此表示祝贺。2018 年从经济角度讲,对于所有人可能都是比较难过的一年,而对于自然语言处理领域来说,2018 年无疑是个收获颇丰的年头,而诸多技术进展如果只能选择一项来讲的话,那么当之无愧的应该就是 Bert 模型了。在上一篇介绍 Bert 的文章「从 Word Embedding 到 Bert 模型—自然语言处理中的预训练技术发展史」里,我曾大言不惭地宣称如下两个个人判断:一个是 Bert 这种两阶段的模式(预训练+Finetuning)必将成为 NLP 领域研究和工业应用的流行方法;第二个是从 NLP 领域的特征抽取器角度来说,Transformer 会逐步取代 RNN 成为最主流的的特征抽取器。关于特征抽取器方面的判断,上面文章限于篇幅,只是给了一个结论,并未给出具备诱惑力的说明,看过我文章的人都知道我不是一个随便下结论的人(那位正在补充下一句:「你随便起来不是……」的同学请住口,请不要泄露国家机密,你可以继续睡觉,吵到其它同学也没有关系,哈哈),但是为什么当时我会下这个结论呢?本文可以看做是上文的一个外传,会给出比较详实的证据来支撑之前给出的结论。
如果对目前 NLP 里的三大特征抽取器的未来走向趋势做个宏观判断的话,我的判断是这样的:RNN 人老珠黄,已经基本完成它的历史使命,将来会逐步退出历史舞台;CNN 如果改造得当,将来还是有希望有自己在 NLP 领域的一席之地,如果改造成功程度超出期望,那么还有一丝可能作为割据一方的军阀,继续生存壮大,当然我认为这个希望不大,可能跟宋小宝打篮球把姚明打哭的概率相当;而新欢 Transformer 明显会很快成为 NLP 里担当大任的最主流的特征抽取器。至于将来是否会出现新的特征抽取器,一枪将 Tranformer 挑落马下,继而取而代之成为新的特征抽取山大王?这种担忧其实是挺有必要的,毕竟李商隐在一千年前就告诫过我们说:「君恩如水向东流,得宠忧移失宠愁。莫向樽前奏花落,凉风只在殿西头。」当然这首诗看样子目前送给 RNN 是比较贴切的,至于未来 Transformer 是否会失宠?这个问题的答案基本可以是肯定的,无非这个时刻的来临是 3 年之后,还是 1 年之后出现而已。当然,我希望如果是在读这篇文章的你,或者是我,在未来的某一天,从街头拉来一位长相普通的淑女,送到韩国整容,一不小心偏离流水线整容工业的美女模板,整出一位天香国色的绝色,来把 Transformer 打入冷宫,那是最好不过。但是在目前的状态下,即使是打着望远镜,貌似还没有看到有这种资质的候选人出现在我们的视野之内。
我知道如果是一位严谨的研发人员,不应该在目前局势还没那么明朗的时候做出如上看似有些武断的明确结论,所以这种说法可能会引起争议。但是这确实就是我目前的真实想法,至于根据什么得出的上述判断?这种判断是否有依据?依据是否充分?相信你在看完这篇文章可以有个属于自己的结论。
可能谈到这里,有些平常吃亏吃的少所以喜欢挑刺的同学会质疑说:你凭什么说 NLP 的典型特征抽取器就这三种呢?你置其它知名的特征抽取器比如 Recursive NN 于何地? 嗯,是,很多介绍 NLP 重要进展的文章里甚至把 Recursive NN 当做一项 NLP 里的重大进展,除了它,还有其它的比如 Memory Network 也享受这种部局级尊贵待遇。但是我一直都不太看好这两个技术,而且不看好很多年了,目前情形更坚定了这个看法。而且我免费奉劝你一句,没必要在这两个技术上浪费时间,至于为什么,因为跟本文主题无关,以后有机会再详细说。
上面是结论,下面,我们正式进入举证阶段。
NLP 任务的特点和图像有极大的不同,上图展示了一个例子,NLP 的输入往往是一句话或者一篇文章,所以它有几个特点:首先,输入是个一维线性序列,这个好理解;其次,输入是不定长的,有的长有的短,而这点其实对于模型处理起来也会增加一些小麻烦;再次,单词或者子句的相对位置关系很重要,两个单词位置互换可能导致完全不同的意思。如果你听到我对你说:「你欠我那一千万不用还了」和「我欠你那一千万不用还了」,你听到后分别是什么心情?两者区别了解一下;另外,句子中的长距离特征对于理解语义也非常关键,例子参考上图标红的单词,特征抽取器能否具备长距离特征捕获能力这一点对于解决 NLP 任务来说也是很关键的。
上面这几个特点请记清,一个特征抽取器是否适配问题领域的特点,有时候决定了它的成败,而很多模型改进的方向,其实就是改造得使得它更匹配领域问题的特性。这也是为何我在介绍 RNN、CNN、Transformer 等特征抽取器之前,先说明这些内容的原因。
NLP 是个很宽泛的领域,包含了几十个子领域,理论上只要跟语言处理相关,都可以纳入这个范围。但是如果我们对大量 NLP 任务进行抽象的话,会发现绝大多数 NLP 任务可以归结为几大类任务。两个看似差异很大的任务,在解决任务的模型角度,可能完全是一样的。
通常而言,绝大部分 NLP 问题可以归入上图所示的四类任务中:一类是序列标注,这是最典型的 NLP 任务,比如中文分词,词性标注,命名实体识别,语义角色标注等都可以归入这一类问题,它的特点是句子中每个单词要求模型根据上下文都要给出一个分类类别。第二类是分类任务,比如我们常见的文本分类,情感计算等都可以归入这一类。它的特点是不管文章有多长,总体给出一个分类类别即可。第三类任务是句子关系判断,比如 Entailment,QA,语义改写,自然语言推理等任务都是这个模式,它的特点是给定两个句子,模型判断出两个句子是否具备某种语义关系;第四类是生成式任务,比如机器翻译,文本摘要,写诗造句,看图说话等都属于这一类。它的特点是输入文本内容后,需要自主生成另外一段文字。
解决这些不同的任务,从模型角度来讲什么最重要?是特征抽取器的能力。尤其是深度学习流行开来后,这一点更凸显出来。因为深度学习最大的优点是「端到端(end to end)」,当然这里不是指的从客户端到云端,意思是以前研发人员得考虑设计抽取哪些特征,而端到端时代后,这些你完全不用管,把原始输入扔给好的特征抽取器,它自己会把有用的特征抽取出来。
身为资深 Bug 制造者和算法工程师,你现在需要做的事情就是:选择一个好的特征抽取器,选择一个好的特征抽取器,选择一个好的特征抽取器,喂给它大量的训练数据,设定好优化目标(loss function),告诉它你想让它干嘛…….. 然后你觉得你啥也不用干等结果就行了是吧?那你是我见过的整个宇宙中最乐观的人……. 你大量时间其实是用在调参上…….。从这个过程可以看出,如果我们有个强大的特征抽取器,那么中初级算法工程师沦为调参侠也就是个必然了,在 AutoML(自动那啥)流行的年代,也许以后你想当调参侠而不得,李斯说的「吾欲与若复牵黄犬,俱出上蔡东门逐狡兔,岂可得乎!」请了解一下。所以请珍惜你半夜两点还在调整超参的日子吧,因为对于你来说有一个好消息一个坏消息,好消息是:对于你来说可能这样辛苦的日子不多了!坏消息是:对于你来说可能这样辛苦的日子不多了!!!那么怎么才能成为算法高手?你去设计一个更强大的特征抽取器呀。
下面开始分叙三大特征抽取器。
RNN 模型我估计大家都熟悉,就不详细介绍了,模型结构参考上图,核心是每个输入对应隐层节点,而隐层节点之间形成了线性序列,信息由前向后在隐层之间逐步向后传递。我们下面直接进入我想讲的内容。
为何 RNN 能够成为解决 NLP 问题的主流特征抽取器
我们知道,RNN 自从引入 NLP 界后,很快就成为吸引眼球的明星模型,在 NLP 各种任务中被广泛使用。但是原始的 RNN 也存在问题,它采取线性序列结构不断从前往后收集输入信息,但这种线性序列结构在反向传播的时候存在优化困难问题,因为反向传播路径太长,容易导致严重的梯度消失或梯度爆炸问题。为了解决这个问题,后来引入了 LSTM 和 GRU 模型,通过增加中间状态信息直接向后传播,以此缓解梯度消失问题,获得了很好的效果,于是很快 LSTM 和 GRU 成为 RNN 的标准模型。其实图像领域最早由 HighwayNet/Resnet 等导致模型革命的 skip connection 的原始思路就是从 LSTM 的隐层传递机制借鉴来的。经过不断优化,后来 NLP 又从图像领域借鉴并引入了 attention 机制(从这两个过程可以看到不同领域的相互技术借鉴与促进作用),叠加网络把层深作深,以及引入 Encoder-Decoder 框架,这些技术进展极大拓展了 RNN 的能力以及应用效果。下图展示的模型就是非常典型的使用 RNN 来解决 NLP 任务的通用框架技术大礼包,在更新的技术出现前,你可以在 NLP 各种领域见到这个技术大礼包的身影。
上述内容简单介绍了 RNN 在 NLP 领域的大致技术演进过程。那么为什么 RNN 能够这么快在 NLP 流行并且占据了主导地位呢?主要原因还是因为 RNN 的结构天然适配解决 NLP 的问题,NLP 的输入往往是个不定长的线性序列句子,而 RNN 本身结构就是个可以接纳不定长输入的由前向后进行信息线性传导的网络结构,而在 LSTM 引入三个门后,对于捕获长距离特征也是非常有效的。所以 RNN 特别适合 NLP 这种线形序列应用场景,这是 RNN 为何在 NLP 界如此流行的根本原因。
RNN 在新时代面临的两个严重问题
RNN 在 NLP 界一直红了很多年(2014-2018?),在 2018 年之前,大部分各个子领域的 State of Art 的结果都是 RNN 获得的。但是最近一年来,眼看着 RNN 的领袖群伦的地位正在被动摇,所谓各领风骚 3-5 年,看来网红模型也不例外。
那这又是因为什么呢?主要有两个原因。
第一个原因在于一些后起之秀新模型的崛起,比如经过特殊改造的 CNN 模型,以及最近特别流行的 Transformer,这些后起之秀尤其是 Transformer 的应用效果相比 RNN 来说,目前看具有明显的优势。这是个主要原因,老人如果干不过新人,又没有脱胎换骨自我革命的能力,自然要自觉或不自愿地退出历史舞台,这是自然规律。至于 RNN 能力偏弱的具体证据,本文后面会专门谈,这里不展开讲。当然,技术人员里的 RNN 保皇派们,这个群体规模应该还是相当大的,他们不会轻易放弃曾经这么热门过的流量明星的,所以也想了或者正在想一些改进方法,试图给 RNN 延年益寿。至于这些方法是什么,有没有作用,后面也陆续会谈。
另外一个严重阻碍 RNN 将来继续走红的问题是:RNN 本身的序列依赖结构对于大规模并行计算来说相当之不友好。通俗点说,就是 RNN 很难具备高效的并行计算能力,这个乍一看好像不是太大的问题,其实问题很严重。如果你仅仅满足于通过改 RNN 发一篇论文,那么这确实不是大问题,但是如果工业界进行技术选型的时候,在有快得多的模型可用的前提下,是不太可能选择那么慢的模型的。一个没有实际落地应用支撑其存在价值的模型,其前景如何这个问题,估计用小脑思考也能得出答案。
那问题来了:为什么 RNN 并行计算能力比较差?是什么原因造成的?
我们知道,RNN 之所以是 RNN,能将其和其它模型区分开的最典型标志是:T 时刻隐层状态的计算,依赖两个输入,一个是 T 时刻的句子输入单词 Xt,这个不算特点,所有模型都要接收这个原始输入;关键的是另外一个输入,T 时刻的隐层状态 St 还依赖 T-1 时刻的隐层状态 S(t-1) 的输出,这是最能体现 RNN 本质特征的一点,RNN 的历史信息是通过这个信息传输渠道往后传输的,示意参考上图。那么为什么 RNN 的并行计算能力不行呢?问题就出在这里。因为 T 时刻的计算依赖 T-1 时刻的隐层计算结果,而 T-1 时刻的计算依赖 T-2 时刻的隐层计算结果…….. 这样就形成了所谓的序列依赖关系。就是说只能先把第 1 时间步的算完,才能算第 2 时间步的结果,这就造成了 RNN 在这个角度上是无法并行计算的,只能老老实实地按着时间步一个单词一个单词往后走。
而 CNN 和 Transformer 就不存在这种序列依赖问题,所以对于这两者来说并行计算能力就不是问题,每个时间步的操作可以并行一起计算。
那么能否针对性地对 RNN 改造一下,提升它的并行计算能力呢?如果可以的话,效果如何呢?下面我们讨论一下这个问题。
如何改造 RNN 使其具备并行计算能力?
上面说过,RNN 不能并行计算的症结所在,在于 T 时刻对 T-1 时刻计算结果的依赖,而这体现在隐层之间的全连接网络上。既然症结在这里,那么要想解决问题,也得在这个环节下手才行。在这个环节多做点什么事情能够增加 RNN 的并行计算能力呢?你可以想一想。
其实留给你的选项并不多,你可以有两个大的思路来改进:一种是仍然保留任意连续时间步(T-1 到 T 时刻)之间的隐层连接;而另外一种是部分地打断连续时间步(T-1 到 T 时刻)之间的隐层连接。
我们先来看第一种方法,现在我们的问题转化成了:我们仍然要保留任意连续时间步(T-1 到 T 时刻)之间的隐层连接,但是在这个前提下,我们还要能够做到并行计算,这怎么处理呢?因为只要保留连续两个时间步的隐层连接,则意味着要计算 T 时刻的隐层结果,就需要 T-1 时刻隐层结果先算完,这不又落入了序列依赖的陷阱里了吗?嗯,确实是这样,但是为什么一定要在不同时间步的输入之间并行呢?没有人说 RNN 的并行计算一定发生在不同时间步上啊,你想想,隐层是不是也是包含很多神经元?那么在隐层神经元之间并行计算行吗?如果你要是还没理解这是什么意思,那请看下图。
上面的图只显示了各个时间步的隐层节点,每个时间步的隐层包含 3 个神经元,这是个俯视图,是从上往下看 RNN 的隐层节点的。另外,连续两个时间步的隐层神经元之间仍然有连接,上图没有画出来是为了看着简洁一些。这下应该明白了吧,假设隐层神经元有 3 个,那么我们可以形成 3 路并行计算(红色箭头分隔开成了三路),而每一路因为仍然存在序列依赖问题,所以每一路内仍然是串行的。大思路应该明白了是吧?但是了解 RNN 结构的同学会发现这样还遗留一个问题:隐层神经元之间的连接是全连接,就是说 T 时刻某个隐层神经元与 T-1 时刻所有隐层神经元都有连接,如果是这样,是无法做到在神经元之间并行计算的,你可以想想为什么,这个简单,我假设你有能力想明白。那么怎么办呢?很简单,T 时刻和 T-1 时刻的隐层神经元之间的连接关系需要改造,从之前的全连接,改造成对应位置的神经元(就是上图被红箭头分隔到同一行的神经元之间)有连接,和其它神经元没有连接。这样就可以解决这个问题,在不同路的隐层神经元之间可以并行计算了。
第一种改造 RNN 并行计算能力的方法思路大致如上所述,这种方法的代表就是论文「Simple Recurrent Units for Highly Parallelizable Recurrence」中提出的 SRU 方法,它最本质的改进是把隐层之间的神经元依赖由全连接改成了哈达马乘积,这样 T 时刻隐层单元本来对 T-1 时刻所有隐层单元的依赖,改成了只是对 T-1 时刻对应单元的依赖,于是可以在隐层单元之间进行并行计算,但是收集信息仍然是按照时间序列来进行的。所以其并行性是在隐层单元之间发生的,而不是在不同时间步之间发生的。
这其实是比较巧妙的一种方法,但是它的问题在于其并行程度上限是有限的,并行程度取决于隐层神经元个数,而一般这个数值往往不会太大,再增加并行性已经不太可能。另外每一路并行线路仍然需要序列计算,这也会拖慢整体速度。SRU 的测试速度为:在文本分类上和原始 CNN(Kim 2014)的速度相当,论文没有说 CNN 是否采取了并行训练方法。其它在复杂任务阅读理解及 MT 任务上只做了效果评估,没有和 CNN 进行速度比较,我估计这是有原因的,因为复杂任务往往需要深层网络,其它的就不妄作猜测了。
第二种改进典型的思路是:为了能够在不同时间步输入之间进行并行计算,那么只有一种做法,那就是打断隐层之间的连接,但是又不能全打断,因为这样基本就无法捕获组合特征了,所以唯一能选的策略就是部分打断,比如每隔 2 个时间步打断一次,但是距离稍微远点的特征如何捕获呢?只能加深层深,通过层深来建立远距离特征之间的联系。代表性模型比如上图展示的 Sliced RNN。我当初看到这个模型的时候,心里忍不住发出杠铃般的笑声,情不自禁地走上前跟他打了个招呼:你好呀,CNN 模型,想不到你这个糙汉子有一天也会穿上粉色裙装,装扮成 RNN 的样子出现在我面前啊,哈哈。了解 CNN 模型的同学看到我上面这句话估计会莞尔会心一笑:这不就是简化版本的 CNN 吗?不了解 CNN 的同学建议看完后面 CNN 部分再回头来看看是不是这个意思。
那经过这种改造的 RNN 速度改进如何呢?论文给出了速度对比实验,归纳起来,SRNN 速度比 GRU 模型快 5 到 15 倍,嗯,效果不错,但是跟对比模型 DC-CNN 模型速度比较起来,比 CNN 模型仍然平均慢了大约 3 倍。这很正常但是又有点说不太过去,说正常是因为本来这就是把 RNN 改头换面成类似 CNN 的结构,而片段里仍然采取 RNN 序列模型,所以必然会拉慢速度,比 CNN 慢再正常不过了。说「说不过去」是指的是:既然本质上是 CNN,速度又比 CNN 慢,那么这么改的意义在哪里?为什么不直接用 CNN 呢?是不是?前面那位因为吃亏吃的少所以爱抬杠的同学又会说了:也许人家效果特别好呢。嗯,从这个结构的作用机制上看,可能性不太大。你说论文实验部分证明了这一点呀,我认为实验部分对比试验做的不充分,需要补充除了 DC-CNN 外的其他 CNN 模型进行对比。当然这点纯属个人意见,别当真,因为我讲起话来的时候经常摇头晃脑,此时一般会有人惊奇地跟我反馈说:为什么你一讲话我就听到了水声?
上面列举了两种大的改进 RNN 并行计算能力的思路,我个人对于 RNN 的并行计算能力持悲观态度,主要因为 RNN 本质特性决定了我们能做的选择太少。无非就是选择打断还是不打断隐层连接的问题。如果选择打断,就会面临上面的问题,你会发现它可能已经不是 RNN 模型了,为了让它看上去还像是 RNN,所以在打断片段里仍然采取 RNN 结构,这样无疑会拉慢速度,所以这是个两难的选择,与其这样不如直接换成其它模型;如果我们选择不打断,貌似只能在隐层神经元之间进行并行,而这样做的缺点是:一方面并行能力上限很低;另外一方面里面依然存在的序列依赖估计仍然是个问题。这是为何悲观的原因,主要是看不到大的希望。
在一年多前,CNN 是自然语言处理中除了 RNN 外最常见的深度学习模型,这里介绍下 CNN 特征抽取器,会比 RNN 说得详细些,主要考虑到大家对它的熟悉程度可能没有 RNN 那么高。
NLP 中早期的怀旧版 CNN 模型
最早将 CNN 引入 NLP 的是 Kim 在 2014 年做的工作,论文和网络结构参考上图。一般而言,输入的字或者词用 Word Embedding 的方式表达,这样本来一维的文本信息输入就转换成了二维的输入结构,假设输入 X 包含 n 个字符,而每个字符的 Word Embedding 的长度为 d,那么输入就是 d*n 的二维向量。
卷积层本质上是个特征抽取层,可以设定超参数 F 来指定卷积层包含多少个卷积核(Filter)。对于某个 Filter 来说,可以想象有一个 d*k 大小的移动窗口从输入矩阵的第一个字开始不断往后移动,其中 k 是 Filter 指定的窗口大小,d 是 Word Embedding 长度。对于某个时刻的窗口,通过神经网络的非线性变换,将这个窗口内的输入值转换为某个特征值,随着窗口不断往后移动,这个 Filter 对应的特征值不断产生,形成这个 Filter 的特征向量。这就是卷积核抽取特征的过程。卷积层内每个 Filter 都如此操作,就形成了不同的特征序列。Pooling 层则对 Filter 的特征进行降维操作,形成最终的特征。一般在 Pooling 层之后连接全联接层神经网络,形成最后的分类过程。
这就是最早应用在 NLP 领域 CNN 模型的工作机制,用来解决 NLP 中的句子分类任务,看起来还是很简洁的,之后陆续出现了在此基础上的改进模型。这些怀旧版 CNN 模型在一些任务上也能和当时怀旧版本的 RNN 模型效果相当,所以在 NLP 若干领域也能野蛮生长,但是在更多的 NLP 领域,还是处于被 RNN 模型压制到抑郁症早期的尴尬局面。那为什么在图像领域打遍天下无敌手的 CNN,一旦跑到 NLP 的地盘,就被 RNN 这个地头蛇压制得无颜见图像领域江东父老呢?这说明这个版本的 CNN 还是有很多问题的,其实最根本的症结所在还是老革命遇到了新问题,主要是到了新环境没有针对新环境的特性做出针对性的改变,所以面临水土不服的问题。
CNN 能在 RNN 纵横的各种 NLP 任务环境下生存下来吗?谜底即将揭晓。
CNN 的进化:物竞天择的模型斗兽场
下面我们先看看怀旧版 CNN 存在哪些问题,然后看看我们的 NLP 专家们是如何改造 CNN,一直改到目前看上去还算效果不错的现代版本 CNN 的。
首先,我们先要明确一点:CNN 捕获到的是什么特征呢?从上述怀旧版本 CNN 卷积层的运作机制你大概看出来了,关键在于卷积核覆盖的那个滑动窗口,CNN 能捕获到的特征基本都体现在这个滑动窗口里了。大小为 k 的滑动窗口轻轻的穿过句子的一个个单词,荡起阵阵涟漪,那么它捕获了什么? 其实它捕获到的是单词的 k-gram 片段信息,这些 k-gram 片段就是 CNN 捕获到的特征,k 的大小决定了能捕获多远距离的特征。
说完这个,我们来看 Kim 版 CNN 的第一个问题:它只有一个卷积层。表面看上去好像是深度不够的问题是吧?我会反问你说:为什么要把 CNN 作深呢?其实把深度做起来是手段,不是目的。只有一个卷积层带来的问题是:对于远距离特征,单层 CNN 是无法捕获到的,如果滑动窗口 k 最大为 2,而如果有个远距离特征距离是 5,那么无论上多少个卷积核,都无法覆盖到长度为 5 的距离的输入,所以它是无法捕获长距离特征的。
那么怎样才能捕获到长距离的特征呢?有两种典型的改进方法:一种是假设我们仍然用单个卷积层,滑动窗口大小 k 假设为 3,就是只接收三个输入单词,但是我们想捕获距离为 5 的特征,怎么做才行?显然,如果卷积核窗口仍然覆盖连续区域,这肯定是完不成任务的。提示一下:你玩过跳一跳是吧?能采取类似策略吗?对,你可以跳着覆盖呀,是吧?这就是 Dilated 卷积的基本思想,确实也是一种解决方法。
第二种方法是把深度做起来。第一层卷积层,假设滑动窗口大小 k 是 3,如果再往上叠一层卷积层,假设滑动窗口大小也是 3,但是第二层窗口覆盖的是第一层窗口的输出特征,所以它其实能覆盖输入的距离达到了 5。如果继续往上叠加卷积层,可以继续增大卷积核覆盖输入的长度。
上面是两种典型的解决 CNN 远距离特征捕获能力的方案,Dilated CNN 偏技巧一些,而且叠加卷积层时超参如何设置有些学问,因为连续跳接可能会错过一些特征组合,所以需要精心调节参数搭配,保证所有可能组合都被覆盖到。相对而言,把 CNN 作深是主流发展方向。上面这个道理好理解,其实自从 CNN 一出现,人们就想各种办法试图把 CNN 的深度做起来,但是现实往往是无情的,发现怎么折腾,CNN 做 NLP 问题就是做不深,做到 2 到 3 层卷积层就做不上去了,网络更深对任务效果没什么帮助(请不要拿 CharCNN 来做反例,后来研究表明使用单词的 2 层 CNN 效果超过 CharCNN)。目前看来,还是深层网络参数优化手段不足导致的这个问题,而不是层深没有用。后来 Resnet 等图像领域的新技术出现后,很自然地,人们会考虑把 Skip Connection 及各种 Norm 等参数优化技术引入,这才能慢慢把 CNN 的网络深度做起来。
上面说的是 Kim 版本 CNN 的第一个问题,无法捕获远距离特征的问题,以及后面科研人员提出的主要解决方案。回头看 Kim 版本 CNN 还有一个问题,就是那个 Max Pooling 层,这块其实与 CNN 能否保持输入句子中单词的位置信息有关系。首先我想问个问题:RNN 因为是线性序列结构,所以很自然它天然就会把位置信息编码进去;那么,CNN 是否能够保留原始输入的相对位置信息呢?我们前面说过对于 NLP 问题来说,位置信息是很有用的。其实 CNN 的卷积核是能保留特征之间的相对位置的,道理很简单,滑动窗口从左到右滑动,捕获到的特征也是如此顺序排列,所以它在结构上已经记录了相对位置信息了。但是如果卷积层后面立即接上 Pooling 层的话,Max Pooling 的操作逻辑是:从一个卷积核获得的特征向量里只选中并保留最强的那一个特征,所以到了 Pooling 层,位置信息就被扔掉了,这在 NLP 里其实是有信息损失的。所以在 NLP 领域里,目前 CNN 的一个发展趋势是抛弃 Pooling 层,靠全卷积层来叠加网络深度,这背后是有原因的(当然图像领域也是这个趋势)。
上图展示了在 NLP 领域能够施展身手的摩登 CNN 的主体结构,通常由 1-D 卷积层来叠加深度,使用 Skip Connection 来辅助优化,也可以引入 Dilated CNN 等手段。比如 ConvS2S 主体就是上图所示结构,Encoder 包含 15 个卷积层,卷积核 kernel size=3,覆盖输入长度为 25。当然对于 ConvS2S 来说,卷积核里引入 GLU 门控非线性函数也有重要帮助,限于篇幅,这里不展开说了,GLU 貌似是 NLP 里 CNN 模型必备的构件,值得掌握。再比如 TCN(论文:An Empirical Evaluation of Generic Convolutional and Recurrent Networks for Sequence Modeling),集成了几项技术:利用 Dilated CNN 拓展单层卷积层的输入覆盖长度,利用全卷积层堆叠层深,使用 Skip Connection 辅助优化,引入 Casual CNN 让网络结构看不到 T 时间步后的数据。不过 TCN 的实验做得有两个明显问题:一个问题是任务除了语言模型外都不是典型的 NLP 任务,而是合成数据任务,所以论文结论很难直接说就适合 NLP 领域;另外一点,它用来进行效果比较的对比方法,没有用当时效果很好的模型来对比,比较基准低。所以 TCN 的模型效果说服力不太够。其实它该引入的元素也基本引入了,实验说服力不够,我觉得可能是它命中缺 GLU 吧。
除此外,简单谈一下 CNN 的位置编码问题和并行计算能力问题。上面说了,CNN 的卷积层其实是保留了相对位置信息的,只要你在设计模型的时候别手贱,中间层不要随手瞎插入 Pooling 层,问题就不大,不专门在输入部分对 position 进行编码也行。但是也可以类似 ConvS2S 那样,专门在输入部分给每个单词增加一个 position embedding,将单词的 position embedding 和词向量 embedding 叠加起来形成单词输入,这样也可以,也是常规做法。
至于 CNN 的并行计算能力,那是非常强的,这其实很好理解。我们考虑单层卷积层,首先对于某个卷积核来说,每个滑动窗口位置之间没有依赖关系,所以完全可以并行计算;另外,不同的卷积核之间也没什么相互影响,所以也可以并行计算。CNN 的并行度是非常自由也非常高的,这是 CNN 的一个非常好的优点。
以上内容介绍了怀旧版 CNN 是如何在 NLP 修罗场一步步通过自我进化生存到今天的。CNN 的进化方向,如果千言万语一句话归纳的话,那就是:想方设法把 CNN 的深度做起来,随着深度的增加,很多看似无关的问题就随之解决了。就跟我们国家最近 40 年的主旋律是发展经济一样,经济发展好了,很多问题就不是问题了。最近几年之所以大家感到各方面很困难,症结就在于经济不行了,所以很多问题无法通过经济带动来解决,于是看似各种花样的困难就冒出来,这是一个道理。
那么介绍了这么多,摩登版 CNN 效果如何呢?与 RNN 及 Transforme 比起来怎样?别着急,后面会专门谈这个问题。
Transformer 是谷歌在 17 年做机器翻译任务的「Attention is all you need」的论文中提出的,引起了相当大的反响。每一位从事 NLP 研发的同仁都应该透彻搞明白 Transformer,它的重要性毫无疑问,尤其是你在看完我这篇文章之后,我相信你的紧迫感会更迫切,我就是这么一位善于制造焦虑的能手。不过这里没打算重点介绍它,想要入门 Transformer 的可以参考以下三篇文章:一个是 Jay Alammar 可视化地介绍 Transformer 的博客文章 The Illustrated Transformer,非常容易理解整个机制,建议先从这篇看起,这是中文翻译版本;第二篇是 Calvo 的博客:Dissecting BERT Part 1: The Encoder,尽管说是解析 Bert,但是因为 Bert 的 Encoder 就是 Transformer,所以其实它是在解析 Transformer,里面举的例子很好;再然后可以进阶一下,参考哈佛大学 NLP 研究组写的「The Annotated Transformer.」,代码原理双管齐下,讲得也很清楚。
下面只说跟本文主题有关的内容。
这里要澄清一下,本文所说的 Transformer 特征抽取器并非原始论文所指。我们知道,「Attention is all you need」论文中说的的 Transformer 指的是完整的 Encoder-Decoder 框架,而我这里是从特征提取器角度来说的,你可以简单理解为论文中的 Encoder 部分。因为 Encoder 部分目的比较单纯,就是从原始句子中提取特征,而 Decoder 部分则功能相对比较多,除了特征提取功能外,还包含语言模型功能,以及用 attention 机制表达的翻译模型功能。所以这里请注意,避免后续理解概念产生混淆。
Transformer 的 Encoder 部分(不是上图一个一个的标为 encoder 的模块,而是红框内的整体,上图来自 The Illustrated Transformer,Jay Alammar 把每个 Block 称为 Encoder 不太符合常规叫法)是由若干个相同的 Transformer Block 堆叠成的。这个 Transformer Block 其实才是 Transformer 最关键的地方,核心配方就在这里。那么它长什么样子呢?
它的照片见上图,看上去是不是很可爱,有点像安卓机器人是吧?这里需要强调一下,尽管 Transformer 原始论文一直重点在说 Self Attention,但是目前来看,能让 Transformer 效果好的,不仅仅是 Self attention,这个 Block 里所有元素,包括 Multi-head self attention,Skip connection,LayerNorm,FF 一起在发挥作用。为什么这么说?你看到后面会体会到这一点。
我们针对 NLP 任务的特点来说下 Transformer 的对应解决方案。首先,自然语言一般是个不定长的句子,那么这个不定长问题怎么解决呢?Transformer 做法跟 CNN 是类似的,一般设定输入的最大长度,如果句子没那么长,则用 Padding 填充,这样整个模型输入起码看起来是定长的了。另外,NLP 句子中单词之间的相对位置是包含很多信息的,上面提过,RNN 因为结构就是线性序列的,所以天然会将位置信息编码进模型;而 CNN 的卷积层其实也是保留了位置相对信息的,所以什么也不做问题也不大。但是对于 Transformer 来说,为了能够保留输入句子单词之间的相对位置信息,必须要做点什么。为啥它必须要做点什么呢?因为输入的第一层网络是 Muli-head self attention 层,我们知道,Self attention 会让当前输入单词和句子中任意单词发生关系,然后集成到一个 embedding 向量里,但是当所有信息到了 embedding 后,位置信息并没有被编码进去。所以,Transformer 不像 RNN 或 CNN,必须明确的在输入端将 Positon 信息编码,Transformer 是用位置函数来进行位置编码的,而 Bert 等模型则给每个单词一个 Position embedding,将单词 embedding 和单词对应的 position embedding 加起来形成单词的输入 embedding,类似上文讲的 ConvS2S 的做法。而关于 NLP 句子中长距离依赖特征的问题,Self attention 天然就能解决这个问题,因为在集成信息的时候,当前单词和句子中任意单词都发生了联系,所以一步到位就把这个事情做掉了。不像 RNN 需要通过隐层节点序列往后传,也不像 CNN 需要通过增加网络深度来捕获远距离特征,Transformer 在这点上明显方案是相对简单直观的。说这些是为了单独介绍下 Transformer 是怎样解决 NLP 任务几个关键点的。
Transformer 有两个版本:Transformer base 和 Transformer Big。两者结构其实是一样的,主要区别是包含的 Transformer Block 数量不同,Transformer base 包含 12 个 Block 叠加,而 Transformer Big 则扩张一倍,包含 24 个 Block。无疑 Transformer Big 在网络深度,参数量以及计算量相对 Transformer base 翻倍,所以是相对重的一个模型,但是效果也最好。
(上篇)
雷锋网 AI 科技评论经作者许可转载。