赞
踩
最近接到一些秋招面试,发现自己对于好多网络结构都模糊了,刚好最近在调研模型,就趁这个机会把之前的常见模型知识梳理一下。
主要参考文档:
https://jalammar.github.io/illustrated-transformer/
https://blog.csdn.net/jojozhangju/article/details/51982254
循环神经网络(RNN)指的是一个序列当前的输出与之前的输出也有关。具体的表现形式为网络会对前面的信息进行记忆,保存在网络的内部状态中,并应用于当前输出的计算中,即隐含层之间的节点不再无连接而是有链接的,并且隐含层的输入不仅包含输入层的输出还包含上一时刻隐含层的输出。
经典RNN结构在时间上进行展开:
计算过程:
需要注意的是:
隐含层状态
这里体现出和传统神经网络的区别:
在传统的神经网络中,每一个网络层的参数是不共享的。而在RNN中,每输入一步,每一层各自都共享参数U,V,W,其反映着RNN每一步都在做相同的事情,只是输入不同。因此,这大大降低了网络中需要学习的参数。具体的说是,将RNN进行展开,这样变成了多层的网络,如果这是一个多层的传统神经网络,那么到之间的U矩阵与到之间的U是不同的,但是RNN中却是一样的,同理对于隐含层与隐含层之间的W、隐含层与输出层之间的V也是一样的。
图中每一步都会有输出,但是每一步都要有输出并不是必须的。比如,我们需要预测一条语句所表达的情绪,我们仅仅需要关系最后一个单词输入后的输出,而不需要知道每个单词输入后的输出。同理,每步都需要输入也不是必须的。循环神经网络(RNN)的关键之处在于隐含层,隐含层能够捕捉序列的信息。
如果能像访问过去的上下文信息一样,访问未来的上下文,这样对于许多序列标注任务是非常有益的。例如,在最特殊字符分类的时候,如果能像知道这个字母之前的字母一样,知道将要来的字母,这将非常有帮助。同样,对于句子中的音素分类也是如此。
双向循环网络的基本思想是提出每一个训练序列向前和向后分别是两个循环神经网络,而且这两个都连接着一个输出层。这个结构提供给输出层输入序列中每一个点的完整的过去和未来的上下文信息。下图展示的是一个沿着时间展开的双向循环神经网络。六个独特的权值在每一个时步被重复的利用,六个权值分别对应:输入到向前和向后隐含层(w1, w3),隐含层到隐含层自己(w2, w5),向前和向后隐含层到输出层(w4, w6)。值得注意的是:向前和向后隐含层之间没有信息流,这保证了展开图是非循环的。
具体计算过程如下:
Forward pass:
对于BRNN的隐含层,向前推算跟单向的RNN一样,除了输入序列对于两个隐含层是相反方向的,
输出层直到两个隐含层处理完所有的全部输入序列才更新:
Backward pass:
双向循环神经网络(BRNN)的向后推算与标准的循环神经网络(RNN)通过时间反向传播相似,除了所有的输出层δ项首先被计算,然后返回给两个不同方向的隐含层:
简单来说双向LSTM就是把BRNN中隐含层的小圆圈换成了长短时记忆的模块。与其说长短时记忆是一种循环神经网络,倒不如说是一个加强版的组件被放在了循环神经网络中。这个模块的样子如下图所示:
计算公式:
一个LSTM的参数数量:
假设LSTM输入维度为x_dim,输出维度为y_dim,那么参数个数n为:
解释:
Transformer完整架构:
http://nlp.seas.harvard.edu/images/the-annotated-transformer_14_0.png
我们这里只关注encoder。
首先输入的是word embedding,这里是直接输入一整句话的所有embedding(而RNN是一个单词一个单词输入)。假如我们的输入是Thinking Machine,每个词有一个embedding,那么就有2个embedding。输入embedding需要加上位置编码,为什么要加之后说。然后经过一个Multi-Head Attention结构,这个结构是算法单元中最重要的部分,之后详细说。之后做了一个shortcut的处理,就是把输入和输出按照对应位置加起来,就是残差连接,这个操作有利于加速训练。然后经过一个归一化normalization的操作。接着经过一个两层的全连接网络,最后同样是shortcut和normalization的操作。可以看到,除了Multi-Head Attention,都是常规操作,没有什么难理解的。这里需要注意的是,每个小模块的输入和输出向量,维度都是相等的,比如,Multi-Head Attention的输入和输出向量维度是相等的,否则无法进行shortcut操作;Feed Forward的输入和输出向量维度也是相等的;最终的输出和输入向量维度也是相等的。但是Multi-Head Attention和Feed Forward内部,向量维度会发生变化。
现在详细看一下Multi-Head Attention的结构。
这个Multi-Head表示多头的意思,先从最简单的看起,看看单头Attention是如何操作的。从结构图的橙色方块可以看到,embedding在进入到Attention之前,有3个分叉,那表示说从1个向量,变成了3个向量。具体是怎么算的呢?我们看下图,定义一个矩阵(这个矩阵随机初始化,通过训练得到),将embedding和矩阵做乘法,得到查询向量q,假设输入embedding是512维,在图3中我们用4个小方格表示,输出的查询向量是64维,下图中用3个小方格以示不同。然后类似地,定义和矩阵,将embedding和做矩阵乘法,得到键向量k;将embeding和做矩阵乘法,得到值向量v。对每一个embedding做同样的操作,那么每个输入就得到了3个向量,查询向量,键向量和值向量。需要注意的是,查询向量和键向量要有相同的维度,值向量的维度可以相同,也可以不同,但一般也是相同的。
接下来计算每个embedding的输出,以第一次Thinking为例。q1与k1、k2做点积(这也是为什么前文提到查询向量和键向量的维度必须要一致,否则无法做点积)➡️除以常数8(这个常数8是键向量的维度的开方,键向量和查询向量的维度都是64,开方后是8),做这个尺度上的调整的目的是为了易于训练➡️ 然后softmax归一化,得到一组和为1的系数权重➡️将权重和相应的向量做加权求和,就得到Thinking的输出向量z1。类似的,可以算出Machine的输出z2。如果一句话中包含更多的词,也是相同的计算方法。
通过这样一系列的计算,可以看到,现在每个词的输出z都包含了其他词的信息,每个词都不再是孤立的了,这就是transformer的独到之处,词与词之间的“距离”永远为1,无论你的句子有多长,这就解决了RNN结构处理长序列信息缺失的问题。。而且每个位置中,词与词的相关程度,可以通过softmax输出的权重进行分析。如下图所示,这是某一次计算的权重,其中线条颜色的深浅反映了权重的大小,可以看到it中权重最大的两个词是The和animal,表示it跟这两个词关联最大。这就是attention的含义,输出跟哪个词关联比较强,就放比较多的注意力在上面。
上面我们把每一步计算都拆开了看,实际计算的时候,可以通过矩阵来计算,如下图所示。
讲完了attention,再来讲Multi-Head。对于同一组输入embedding,我们可以并行做若干组上面的操作(这也是transformer优于RNN的一大特点),而这个若干组就是multi-head的head数。例如,我们可以进行8组这样的运算,每一组都有WQ,WK,WV矩阵,并且不同组的矩阵也不相同。这样最终会计算出8组输出,我们把8组的输出连接起来,并且乘以矩阵WO做一次线性变换得到输出,WO也是随机初始化,通过训练得到,计算过程如下图所示。这样的好处,一是多个组可以并行计算,二是不同的组可以捕获不同的子空间的信息。
到这里Transformer encoder的结构基本讲完了,现在和RNN做个对比。当一个RNN计算长序列的第4个隐向量时,用到了输入x4和上一步的隐向量h3,h4包含最多的信息是当前输入x4,越往前的输入随着距离的增加,信息衰减得越多(就算是LSTM也一样),但是transformer这个结构就不存在这个问题,不管当前词和其他词的空间距离有多远,包含其他词的信息不取决于距离,而是取决于两者的相关性,这是Transformer的第一个优势。第二个优势在于,对于Transformer来说,在对当前词进行计算的时候,不仅可以用到前面的词,也可以用到后面的词。而RNN只能用到前面的词,这并不是个严重的问题,因为这可以通过双向RNN来解决。第三个优势,RNN是一个顺序结构,必须算出一个隐向量才能计算后一个,那么这就意味着隐向量无法同时并行计算,导致RNN计算效率低。而Transformer一次输入一整个句子的所有embedding计算,不存在这个问题。
关于上面的第三点优势,可能有人会不认可,RNN的结构包含了序列的时序信息,而Transformer却完全把时序信息给丢掉了。为了解决时序的问题,Transformer的作者用了一个绝妙的办法,这就是在前文提到的位置编码(Positional Encoding)。位置编码是和word embedding同样维度的向量,将位置embedding和词embedding加在一起,作为输入embedding,如下图所示。位置编码可以通过学习得到,也可以通过设置一个跟位置或者时序相关的函数得到,比如设置一个正弦或者余弦函数(原文),这里不再多说。
我们把Transformer encoder的结构作为一个基本单元,把N个这样的基本单元顺序连起来,就是BERT的算法模型,如下图:
BERT并不是第一个提出预训练+微调的方案,此前还有一套方案叫GPT。GPT是一个典型的language model,它在一个8亿单词的语料库上做训练,给出前文,不断地预测下一个单词。比如这句话,Winter is coming,当给出第一个词Winter之后,预测下一个词is,之后再预测下一个词coming。不需要标注数据,通过这种无监督训练的方式,得到一个预训练模型。
而BERT是怎么训练的呢?BERT来自于Bidirectional Encoder Representations from Transformers首字母缩写,这里提到了一个双向(Bidirectional)的概念。BERT在一个33亿单词的语料库上做预训练,预训练包括两个任务,第一个任务是随机的扣掉15%的单词(为什么是15%作者没说,很tricky),用一个掩码MASK代替,让模型去预测这个单词;第二个任务是每个训练样本是一个上下句,有50%的样本,下句和上句是真实的,另外50%的样本,下句和上句是无关的,模型需要判断两句的关系。这两个任务各有一个loss,将这两个loss加起来作为总的loss进行优化。
下面两行是一个小栗子,用括号标注的是扣掉的词,用[MASK]来代替。
正样本:我[MASK](是)个算法工程师,我服务于WiFi万能钥匙这家[MASK](公司)。
负样本:我[MASK](是)个算法工程师,今天[MASK](股票)又跌了。
可以看到,相比于GPT,BERT是预测文中扣掉的词,可以充分利用到上下文的信息,这使得模型有更强的表达能力,这也是BERT中Bidirectional的含义。在一些NLP任务中需要判断句子关系,比如判断两句话是否有相同的含义。BERT有了第二个任务,就能够很好的捕捉句子之间的关系。
讲完了这两个任务,我们再来看看,如何表达这么复杂的一个训练样本,让计算机能够明白。图3.2表示“my dog is cute, he likes playing.”的输入形式。每个符号的输入由三个部分构成,一个是词本身的embedding;第二个是表示上下句的embedding,如果是上句,就用A embedding,如果是下句,就用B embedding;最后,根据Transformer模型的特点,还要加上位置embedding,这里的位置embedding是通过学习的方式得到的,BERT设计一个样本最多支持512个位置;将3个embedding相加,作为输入。需要注意的是,在每个句子的开头,需要加一个Classification(CLS)符号,后文中会进行介绍,其他细节省略。
完成预训练之后,就要针对特定任务进行微调了,这里描述一下论文中的4个例子,看下图。
首先说下分类任务,分类任务包括对单句子的分类任务,比如判断电影评论是喜欢还是讨厌;多句子分类,比如判断两句话是否表示相同的含义。(a)(b)是对这类任务的一个示例,左边表示两个句子的分类,右边是单句子分类。在输出的隐向量中,取出CLS对应的向量C,加一层网络W,并丢给softmax进行分类,得到预测结果P,计算过程如下图的计算公式。在特定任务数据集中对Transformer模型的所有参数和网络W共同训练,直到收敛。新增加的网络W是HxK维,H表示隐向量的维度,K表示分类数量,W的参数数量相比预训练模型的参数少得可怜。
我们再来看问答任务,如图3.4(c),以SQuAD v1.1为例,给出一个问题Question,并且给出一个段落Paragraph,然后从段落中标出答案的具体位置。需要学习一个开始向量S,维度和输出隐向量维度相同,然后和所有的隐向量做点积,取值最大的词作为开始位置;另外再学一个结束向量E,做同样的运算,得到结束位置。附加一个条件,结束位置一定要大于开始位置。最后再看NER任务,实体命名识别,比如给出一句话,对每个词进行标注,判断属于人名,地名,机构名,还是其他。如图3.4(d)所示,加一层分类网络,对每个输出隐向量都做一次判断。可以看到,这些任务,都只需要新增少量的参数,然后在特定数据集上进行训练即可。从实验结果来看,即便是很小的数据集,也能取得不错的效果。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。