【关于生成对抗网络GAN】那些你不知道的事
一、动机
之前我们提到玻尔兹曼机(Boltzmann machine),波尔茨曼机作为一种基于能量函数的概率模型,因为能量函数比较复杂,所以存在较多的限制。虽然受限玻尔兹曼机(Restricted Boltzmann machine) 针对该问题,对能量函数进行进一步简化,即假设网络中仅有隐藏变量与观察变量的连接,而观察变量将没有连接,隐藏变量间也没有连接,且隐藏变量可用 n h n_h nh 个二进制随机变量表示,但是仍然存在限制问题。同时,该过程应用了马尔科夫链,导致计算成本较高。针对上述问题,Ian Goodfellow 于 2014 年提出了生成对抗网络(Generative Adversarial Network,GAN)模型,GAN 作为一类在无监督学习中使用的神经网络,有效地避免了马尔科夫链以及减低了波尔茨曼机所存在的限制问题,以至于在按文本生成图像、提高图片分辨率、药物匹配、检索特定模式的图片等任务中 GAN 的研究如火如荼。大牛Yann LeCun甚至评价GAN为 “adversarial training is the coolest thing since sliced bread”。
本文将通过一个简单的例子(发论文问题)向读者深入浅出的介绍 GAN 原理及其应用。
二、介绍篇
2.1 GAN 的基本思想
作为生成模型中的一种,生成对抗网络(Generative Adversarial Network,GAN)模型的训练过程可以被视为两个网络互相博弈的过程。下面我们将举一个简单的例子解释 GAN 的基本思想。
假设你是一门研究生,你想尽快地将实验结果写成一篇论文发表。
于是在每一次做完实验并写完初稿之后,都会跟你的导师进行沟通:你:boss,我实验结果出来,我想发论文
导师:(瞄了瞄你的实验结果之后) … 算了吧
(你通过跟其他论文的实验结果进行比较,发现自己的实验结果还偏低,于是,你又调整了实验参数,重新进行实验)
你:boss,我实验结果提高了,我想发论文
导师:… (瞄了瞄你的论文初稿之后)嗯 还有所欠缺
(你通过跟其他论文进行比较,发现自己写的论文初稿在表达方面还有所不足)
…
你:boss,我想发论文
导师:… (仔细看了看你的论文之后)嗯 可以试一试
(通过这样不断的修改和被拒绝,你的论文最终获得了导师的赞赏与肯定)
通过上面的例子,大家应该对 GAN 的思想有一个比较感性的认识了吧,下面我们可以进一步对 GAN 的基本结构和思想进行介绍。
2.2 GAN 基本介绍
2.2.1 GAN 的基本结构
GAN 的主要结构包括一个生成器 G(Generator)和一个判别器 D(Discriminator)。
在上面的例子中,研究生相对于生成器。在一开始的时候,他只是一个什么都不懂的初学者,为了能让该研究生发出好的 paper,需要给他裴蓓一个导师来指导他做实验写论文,并告诉他 paper 面前的质量,通过反复的修改和被拒绝,paper 最终达到了可以投稿的标准,而这个导师就相当于生成对抗网络 GAN 中的判别器。
2.2.2 GAN 的基本思想
生成对抗网络 GAN 主要包含两个模块:生成器 G(Generator)和一个判别器 D(Discriminator)。生成对抗网络 GAN 中所描述的对抗,其实就是指生成网络与判别网络之间的相互对抗。以下图为例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y4YmQ6UU-1677247913507)(img/微信截图_20200805232522.png)]
生成器模型(上图中蓝色部分 Generator)的主要工作就是学习真实图片集数据,从而使自己生成的图片更加接近与真实图片,以到达“以假乱真”,也就是“欺骗”判别器。
判别器模型(上图中红色部分 Discriminator)的主要工作就是从图片集中找出生成器所生成的图片,并区分该图片与真实图片的差异,以进行真假判别。
在整个迭代过程中,生成器不断的生成越来越逼真的图片,而判别器不断额努力鉴别出图片的真假。该过程可以视为两个网络互相博弈的过程,随着迭代次数的增加,最终两者将会趋于平衡,也就是说生成器能够生成出和真实图片一模一样的的图片,而判别器已经很难从图片集中辨别出生成器所生成的假图片了。也就是说,对于图片集中的每一张图片,判别器都给出接近 0.5 的概率认为该图片是真实的。
三、训练篇
3.1 生成器介绍
生成器模型的任务:首先需要将一个 n n n 维向量输入生成器模型,然后输出一个图片像素大小的图片(这里,生成器模型可以是任意可以输出图片的模型,如全连接神经网络,反卷积神经网络等)。
注:输入向量:携带输出的某些信息,这些信息可以是手写数字为数字几,手写的潦草程度等。由于这里我们对于输出数字的具体信息不做要求,只要求其能够最大程度与真实手写数字相似(能骗过判别器)即可。所以我们使用随机生成的向量来作为输入即可,这里面的随机输入最好是满足常见分布比如均值分布,高斯分布等。
3.2 判别器介绍
判别器模型的任务:主要能够辨别输入的图片的真假都可以作为判别器。
3.3 训练过程
前面分别介绍了生成器和判别器的任务,在这一节,我们将主要介绍生成对抗网络的训练过程,其基本流程如下:
step 1: 初始化:对判别器 D 的参数 θ d \theta_d θd 和生成器 G 的参数 θ g \theta_g θg;
step 2: 生成器“伪造”生成样本:首先,从真实样本中采样 m m m 个样本 { x 1 , x 2 , … x m } \left\{x^1, x^2, \ldots x^m\right\} {x1,x2,…xm};然后,从先验分布噪声中采样 m m m 个噪声样本 z 1 , z 2 , … , z m {z^1, z^2, \ldots, z^m} z1,z2,…,zm;接下去,利用生成器“伪造” m m m 个新样本 { x ~ 1 , x ~ 2 , … , x ~ m } \left\{\tilde{\boldsymbol{x}}^1, \tilde{\boldsymbol{x}}^2, \ldots, \tilde{\boldsymbol{x}}^m\right\} {x~1,x~2,…,x~m};最后,固定生成器 G。
step 3:判别器“鉴别”生成样本:通过对判别器 D 进行训练,以让它尽可能准确的“鉴别”出生成样本。
step 4: “欺骗”判别器:循环更新判别器 k 次之后,再利用较小的学习率来更新一次生成器的参数。使得判别器已经很难从样本集中辨别出生成器所生成的生成样本了。也就是说,对于样本集中的每一个样本,判别器都给出接近 0.5 的概率认为该样本是真实的。
注:为什么是先训练判别器再训练生成器呢?
以上面的导师和学生的例子吧,学生(生成器)要写出一篇好的 paper (生成样本),那么就需要有一个能够较好的区分好 paper (真实样本)和坏 paper (生成样本)的好导师(判别器)之后,才能指导学生(生成器)如何对 paper (生成样本)进行优化。
3.4 训练所涉及相关理论基础
前面已经对生成对抗网络进行介绍,接下去,我们将从理论基础方面介绍生成对抗网络的训练过程。
首先,需要从优化目标函数开始介绍,其表达式如下所示:
min G max D V ( G , D ) = min G max D E x ∼ p data [ log D ( x ) ] + E z ∼ p z [ log ( 1 − D ( G ( z ) ) ] \min _{G} \max _D V(G, D)=\min _G \max _D \mathbb{E}_{x \sim p_{\text { data }}}[\log D(x)]+\mathbb{E}_{z \sim p_z}[\log (1-D(G(z))] GminDmaxV(G,D)=GminDmaxEx∼p data [logD(x)]+Ez∼pz[log(1−D(G(z))]
对于判别式而言,其主要用于区别样本的真伪,所以可以视为是一个二分类问题,上式中所使用的 V ( G , D ) V(G, D) V(G,D)为二分类问题中常见的交叉熵损失。公式如下所示:
H ( p , q ) : = − ∑ i p i log q i H(p, q) :=-\sum_i p_i \log q_i H(p,q):=−i∑pilogqi
p i p_i pi 和 q i q_i qi 为真实的样本分布和生成器的生成分布。
对于生成器 G 而言,为了尽可能欺骗 D,所以需要最大化生成样本的判别概率 D ( G ( z ) ) D(G(z)) D(G(z)),即最小化 l o g ( 1 − D ( G ( z ) ) ) log(1-D(G(z))) log(1−D(G(z)))。
注意: l o g ( D ( x ) ) log(D(x)) log(D(x)) 一项与生成器 G 无关,所以可以忽略。
实际训练过程中,生成器和判别器采用交替训练的方式进行。因为对于生成器,其最小化为 max D V ( D , G ) \max _{D} V(D, G) maxDV(D,G),即最小化 $ V(D, G) $的最大值。所以为了保证 $ V(D, G) $ 取得最大值,需要对判别器迭代训练 k k k 次,然后再训练一次生成器。
当生成器 G 固定时,我们可以对 V ( D , G ) V(D,G) V(D,G) 求导,求出最优判别器 D ∗ ( x ) D*(x) D∗(x):
D ∗ ( x ) = p g ( x ) p g ( x ) + p d a t a ( x ) D^{*}(x)=\frac{p_{g}(x)}{p_{g}(x)+p_{d a t a}(x)} D∗(x)=pg(x)+pdata(x)pg(x)
把最优判别器代入上述目标函数,可以进一步求出在最优判别器下,生成器的目标函数等价于优化 p d a t a ( x ) p_{d a t a}(x) pdata(x) , p g ( x ) p_{g}(x) pg(x) 的 JS 散度(JSD, Jenson Shannon Divergence)。
可以证明,当 G,D 二者的 capacity 足够时,模型会收敛,二者将达到纳什均衡。此时, p d a t a ( x ) p_{d a t a}(x) pdata(x)= p g ( x ) p_{g}(x) pg(x),判别器不论是对于 p d a t a ( x ) p_{d a t a}(x) pdata(x) 还是 p g ( x ) p_{g}(x) pg(x) 中采样的样本,其预测概率均为 1/2,即生成样本与真实样本达到了难以区分的地步。
通过上述min max的博弈过程,理想情况下会收敛于生成分布拟合于真实分布。
四、总结
本文首先,通过以一个学生发 paper 的 example 的方式引入了生成对抗网络;然后,并进一步介绍了生成对抗网络的框架和思想,中生成器和判别器;最后,通过介绍生成对抗网络的训练过程,以引入生成对抗网络的训练公式。
参考资料
- 通俗理解生成对抗网络GAN
- 白话生成对抗网络 GAN,50 行代码玩转 GAN 模型!【附源码】
- 万字综述之生成对抗网络(GAN)
- 生成对抗网络原理与应用:GAN如何使生活更美好
- 玻尔兹曼机、生成随机网络与自回归网络——深度学习第二十章(二)
- 生成对抗网络(GAN)相比传统训练方法有什么优势?
- 火热的生成对抗网络(GAN),你究竟好在哪里
【关于 Attention 】那些你不知道的事
一、seq2seq 篇
1.1 seq2seq (Encoder-Decoder)是什么?
- 介绍:seq2seq (Encoder-Decoder)将一个句子(图片)利用一个 Encoder 编码为一个 context,然后在利用一个 Decoder 将 context 解码为 另一个句子(图片)的过程 ;
- 应用:
- 在 Image Caption 的应用中 Encoder-Decoder 就是 CNN-RNN 的编码 - 解码框架;
- 在神经网络机器翻译中 Encoder-Decoder 往往就是 LSTM-LSTM 的编码 - 解码框架,在机器翻译中也被叫做 Sequence to Sequence learning。
1.2 seq2seq 中 的 Encoder 怎么样?
- 目标:将 input 编码成一个固定长度 语义编码 context
- context 作用:
- 1、做为初始向量初始化 Decoder 的模型,做为 decoder 模型预测y1的初始向量;
- 2、做为背景向量,指导y序列中每一个step的y的产出;
- 步骤:
-
- 遍历输入的每一个Token(词),每个时刻的输入是上一个时刻的隐状态和输入
-
- 会有一个输出和新的隐状态。这个新的隐状态会作为下一个时刻的输入隐状态。每个时刻都有一个输出;
-
- 保留最后一个时刻的隐状态,认为它编码了整个句子的 语义编码 context,并把最后一个时刻的隐状态作为Decoder的初始隐状态;
-
1.3 seq2seq 中 的 Decoder 怎么样?
- 目标:将 语义编码 context 解码 为 一个 新的 output;
- 步骤:
-
- 一开始的隐状态是Encoder最后时刻的隐状态,输入是特殊的;
-
- 使用RNN计算新的隐状态,并输出第一个词;
-
- 接着用新的隐状态和第一个词计算第二个词,直到decoder产生一个 EOS token, 那么便结束输出了;
-
1.4 在 数学角度上 的 seq2seq ,你知道么?
- 场景介绍:以 机器翻译 为例,给定 一个 句子集合对 <X,Y> (X 表示 一个 英文句子集合,Y 表示 一个 中文句子集合);
- 目标:对于 X 中 的 xi,我们需要采用 seq2seq 框架 来 生成 Y 中对应 的 yi;
- 步骤:
- 编码器 encoder:将 输入 句子集合 X 进行编码,也就是将 其 通过 非线性变换 转化为 中间语义编码 Context C
- 解码器 decoder:对中间语义编码 context 进行解码,根据句子 X 的中间语义编码 Context C 和之前已经生成的历史信息 y1,y2,…,yi-1 生成 当前时刻信息 yi
1.5 seq2seq 存在 什么 问题?
- 忽略了输入序列X的长度:当输入句子长度很长,特别是比训练集中最初的句子长度还长时,模型的性能急剧下降;
- 对输入序列X缺乏区分度:输入X编码成一个固定的长度,对句子中每个词都赋予相同的权重,这样做没有区分度,往往是模型性能下降。
二、Attention 篇
2.1 什么是 Attention?
- 通俗易懂介绍:注意力机制模仿了生物观察行为的内部过程,即一种将内部经验和外部感觉对齐从而增加部分区域的观察精细度的机制。例如人的视觉在处理一张图片时,会通过快速扫描全局图像,获得需要重点关注的目标区域,也就是注意力焦点。然后对这一区域投入更多的注意力资源,以获得更多所需要关注的目标的细节信息,并抑制其它无用信息。
- Attention 介绍:帮助模型对输入的x每部分赋予不同的权重,抽取更重要的信息,使模型做出准确判断。同时,不会给模型计算与存储带来更大开销;
2.2 为什么引入 Attention机制?
根据通用近似定理,前馈网络和循环网络都有很强的能力。但为什么还要引入注意力机制呢?
- 计算能力的限制:当要记住很多“信息“,模型就要变得更复杂,然而目前计算能力依然是限制神经网络发展的瓶颈。
- 优化算法的限制:虽然局部连接、权重共享以及pooling等优化操作可以让神经网络变得简单一些,有效缓解模型复杂度和表达能力之间的矛盾;但是,如循环神经网络中的长距离以来问题,信息“记忆”能力并不高。
2.3 Attention 有什么作用?
- 让神经网络把 “ 注意力 ” 放在一部分输入上,即:区分输入的不同部分对输出的影响;
- 从增强字 / 词的语义表示这一角度介绍
- 一个字 / 词在一篇文本中表达的意思通常与它的上下文有关。光看 “ 鹄 ” 字,我们可能会觉得很陌生(甚至连读音是什幺都不记得吧),而看到它的上下文 “ 鸿鹄之志 ” 后,就对它立马熟悉了起来。因此,字 / 词的上下文信息有助于增强其语义表示。同时,上下文中的不同字 / 词对增强语义表示所起的作用往往不同。比如在上面这个例子中, “ 鸿 ” 字对理解 “ 鹄 ” 字的作用最大,而 “ 之 ” 字的作用则相对较小。为了有区分地利用上下文字信息增强目标字的语义表示,就可以用到 Attention 机制。
2.4 Attention 流程是怎么样?
步骤一 执行encoder (与 seq2seq 一致)
- 思路:将源数据依次输入Encoder,执行Encoder
- 目标:将源序列的信息,编译成语义向量,供后续decoder使用
步骤二 计算对齐系数 a
- 思路:在 decoder 的每个词,我们需要关注源序列的所有词和目标序列当前词的相关性大小,并输出相关(对齐)系数 a;
- 步骤:
-
- 在decoder输出一个预测值前,都会针对encoder的所有step,计算一个score;
-
- 将score汇总向量化后,每个decoder step能获得一个维度为[step_len,1]的score向量;
-
- 计算出score后,很自然地按惯例使用softmax进行归一化,得到对齐向量a,维度也是[step_len,1];
-
- 常用对齐函数:
步骤三 计算上下文语义向量 C
- 思路:对齐系数 a 作为权重,对 encoder 每个 step 的 output 向量进行加权求和(对齐向量a点乘outputs矩阵),得到decoder当前 step 的上下文语义向量 c
步骤四 更新decoder状态
- 思路:更新decoder状态,这个状态可以是h,也可以是 s
步骤五 计算输出预测词
- 思路:做一个语义向量到目标词表的映射(如果attention用于分类模型,那就是做一个到各个分类的映射),然后再进行softmax就可以了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G6fq7qAC-1677247913525)(img/20200916172920.png)]
2.5 Attention 的应用领域有哪些?
随着 Attention 提出 开始,就被 广泛 应用于 各个领域。比如:自然语言处理,图片识别,语音识别等不同方向深度学习任务中。随着 【Transformer 】的提出,Attention被 推向了圣坛。
三、Attention 变体篇
3.1 Soft Attention 是什么?
Soft Attention:传统的 Attention 方法,是参数化的(Parameterization),因此可导,可以被嵌入到模型中去,直接训练。梯度可以经过Attention Mechanism模块,反向传播到模型其他部分。
3.2 Hard Attention 是什么?
Hard Attention:一个随机的过程。Hard Attention不会选择整个encoder的输出做为其输入,Hard Attention会依概率Si来采样输入端的隐状态一部分来进行计算,而不是整个encoder的隐状态。为了实现梯度的反向传播,需要采用蒙特卡洛采样的方法来估计模块的梯度。
3.3 Global Attention 是什么?
- Global Attention:传统的Attention model一样。所有的hidden state都被用于计算Context vector 的权重,即变长的对齐向量at,其长度等于encoder端输入句子的长度。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kdSxU2w0-1677247913526)(img/微信截图_20210109115925.png)]
3.4 Local Attention 是什么?
- 动机:Global Attention 在做每一次 encoder 时,encoder 中的所有 hidden state 都需要参与到计算中,这种方法容易造成 计算开销增大,尤其是 句子偏长的时候。
- 介绍:Local Attention 通过结合 Soft Attention 和 Hard Attention 的一种 Attention方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5luXLMS0-1677247913527)(img/微信截图_20210109120012.png)]
3.5 self-attention 是什么?
- 核心思想:self-attention的结构在计算每个token时,总是会考虑整个序列其他token的表达;
举例:“我爱中国”这个序列,在计算"我"这个词的时候,不但会考虑词本身的embedding,也同时会考虑其他词对这个词的影响
注:具体内容可以参考 self-attention 长怎么样?
参考
- 【关于 Attention 】那些你不知道的事
- nlp中的Attention注意力机制+Transformer详解
- 模型汇总24 - 深度学习中Attention Mechanism详细介绍:原理、分类及应用
【关于 CNN】那些你不知道的事
贡献者:天骄,小猪呼噜,沐风,杨夕,芙蕖,李玲
一、动机篇
- 全连接网络:任意一对输入与输出神经元间都存在连接,现成稠密结构
- 局部特征存在相关性
二、CNN 卷积层篇
2.1 卷积层的本质是什么?
- 稀疏交互
- 动机:全连接网络,任意一对输入与输出神经元间都存在连接,现成稠密结构
- 思路:卷积核尺度远小于输入维度,每个输出神经元仅与前一层特定局部区域内的神经元存在连接
- 优点:全连接层的 参数 为 mn;卷积层为 kn (m为输入,n为输出,k 为 卷积维度)
- 参数共享
- 思路:在同一模型的不同模块中使用相同参数,为卷积运算的固有属性
- 区别
- NN:计算每层输出时,权值参数矩阵中每个元素只作用于每个输入元素一次
- CNN:卷积核中每个元素将作用于每个局部输入的特定位置上
- 物理意义:使卷积层具有平移不变性 (满足 f(g(x)) = g(f(x)) 时,称 f(x) 关于g 具有等变性)
2.2 CNN 卷积层与全连接层的联系?
- 卷积核中的权值每次滑动计算时只是局部连接,且在卷积列中的神经元共享参数——计算局部信息,而全连接层神经元的权值与所有输入相连——计算全局信息。
- 两者都是采用的矩阵点积运算,具有相似的计算形式,能够实现互相转化。
- 卷积——>全连接: 权重矩阵参数增大,权重补0,由于参数共享性质,在其中大部分块中,存在大量相等参数。
- 全连接——>全卷积: 将卷积核的尺寸设置为和输入数据体的尺寸一致(NxWxHxC)。除第一层全连接外,其他通道数都为1,N表示本层神经元的个数,为一个提前设定的超参数,结果与初始的那个全连接层一致。
- 一个深度卷积神经网络模型通常由若干卷积层叠加若干全连接层组成,中间也包含各种非线性操作以及池化操作。卷积层的作用是从输入数据中采集关键数据内容。全连接层在深度卷积神经网络中的作用是将前面经过多次卷积后高度抽象的特征进行整合。最后一层作归一化,然后输出概率。卷积层提供了一个有意义、低维度且几乎不变的特征空间,然后全连接层在这个空间里学习一个非线性的方程。通俗的说,卷积层是特征提取器,全连接层执行分类操作。
- 全连接层可以视作一种特殊的卷积。考虑下面两种情况:
- (1) 特征图和全连接层相连,AlexNet经过五次池化后得到7*7*512的特征图,下一层全连接连向4096个神经元,这个过程可以看作有4096个7*7*512的卷积核和7*7*512的特征图进行卷积操作,最终得到1*1*4096的特征图,等价于全连接得到4096个神经元。
- (2) 全连接层和全连接层相连,AlexNet的再下一层依然是4096个神经元,即4096个神经元和4096个神经元全连接,由(1)得到了1*1*4096的特征图,本次全连接过程可以看作存在4096个1*1*4096的卷积核,依次和1*1*4096的特征图进行卷积操作,等价于全连接。
2.3 channel的含义是什么?
在卷积神经网络中,channel的含义是每个卷积层中卷积核的数量。卷积层的卷积个数就等于卷积层输出的out_channels。这个值也与下一层卷积的in_channels相同。下面举例说明。
如下图,假设现有一个为 6×6×3 的图片样本,使用 3×3×3 的卷积核(filter)进行卷积操作。此时输入图片的 channels
为 3 ,而卷积核中的 in_channels
与 需要进行卷积操作的数据的 channels
一致(这里就是图片样本,为3)。
接下来,进行卷积操作,卷积核中的27个数字与分别与样本对应相乘后,再进行求和,得到第一个结果。依次进行,最终得到 4×4的结果。
上面步骤完成后,由于只有一个卷积核,所以最终得到的结果为 4×4×1, out_channels
为 1 。
在实际应用中,都会使用多个卷积核。这里如果再加一个卷积核,就会得到 4×4×2 的结果。
总结一下, channels
分为三种:
- 最初输入的图片样本的
channels
,取决于图片类型,比如RGB; - 卷积操作完成后输出的
out_channels
,取决于卷积核的数量。此时的out_channels
也会作为下一次卷积时的卷积核的in_channels
; - 卷积核中的
in_channels
,就是上一次卷积的out_channels
,如果是第一次做卷积,就是样本图片的channels
。
三、CNN 池化层篇
3.1 池化层针对区域是什么?
池化层针对区域是非重叠区域。
3.2 池化层的种类有哪些?
- 均值池化
- 思路:对领域内特征值取平均
- 优点:抑制由领域大小受限造成的估计值方差增大现象
- 特点:对背景的保留效果好
- 最大值池化
- 思路:取领域内最大值
- 优点:抑制网络参数误差造成估计均值偏移问题
- 特点:更好提取纹理信息
3.3 池化层的作用是什么?
除降低参数量外,能够保持对平移、伸缩、旋转操作的不变性
3.4 池化层 反向传播 是什么样的?
- 动机:由于 Pooling 操作容易改变 feature map 的 尺寸大小,使得 Pooling 层不可导;
- 举例说明:假定 22 的 池化,会生成 2222 = 16 个梯度,即梯度无法传递到对应的位置;
- 方法:将一个像素的loss (梯度) 传递给4个像素,且需要保证传递的 loss (梯度)总和不变。
3.5 mean pooling 池化层 反向传播 是什么样的?
- mean pooling 前向传播介绍:将 patch 中的值求平均来做 pooling;
- mean pooling 反向传播介绍:将值的梯度均等分为 n*n 份 分配给上一层,以保证 池化 前后 梯度之和保存不变;
3.6 max pooling 池化层 反向传播 是什么样的?
- max pooling 前向传播介绍:将 patch 中的值取 max 来做 pooling 结果 传递给下一层;
- max pooling 反向传播介绍:把梯度直接传给前一层 值最大的一个像素,而其他像素不接受梯度,也就是为0;
四、CNN 整体篇
4.1 CNN 的流程是什么?
- s1 输入变长的字符串或单词串
- s2 利用滑动窗口加池化的方式将原先的输入转化为固长的向量表示
4.2 CNN 的特点是什么?
CNN 能够捕获文本中的局部特征
4.3 卷积神经网络为什么会具有平移不变性?
平移不变性:即目标在空间内发生平移,但是结果(标签)不变
平移不变性=卷积+最大池化。卷积层具有平移等变性,池化层具有平移不变性。卷积神经网络的平移不变性,是池化层赋予的。
如果一个函数满足“输入改变,输出也以同样的方式进行改变”这一性质,我们就说该函数是等变的(equivariant)。形式化地,如果函数 f ( x ) f(x) f(x)与 g ( x ) g(x) g(x)满足 f ( g ( x ) ) = g ( f ( x ) ) f(g(x))=g(f(x)) f(g(x))=g(f(x)),我们就说 f ( x ) f(x) f(x)对于变换 g g g具有等变性。
卷积层具有平移等变性,也就是说卷积层对平移是敏感的,输入的平移能等价地影响输出。直观地,如果把一张输入图像先平移后卷积,其结果与先卷积后平移效果是一样的。如果我们移动输入图像中的物体,它的表示也会在输出中移动同样的量。
卷积层的参数共享(Parameter Sharing)特性使得卷积层具有平移等变性。参数共享是指在一个模型的多个函数中使用相同的参数。在传统的神经网络中,当计算一层的输出时,权重矩阵的每个元素只使用一次。当它乘以输入的一个元素后,就再也不会用到了。但是在卷积神经网络中,卷积核的每一个元素都作用在输入的每一个位置上。卷积运算中的参数共享保证了我们只需要学习一个参数集合,而不是对于每个位置都需要学习一个单独的参数集合。
如果一个函数满足“输入改变,输出不会受影响”这一性质,我们就说该函数是不变的(invariant)。形式化地,如果函数 f ( x ) f(x) f(x)与 g ( x ) g(x) g(x)满足 g ( x ) = x ′ g(x)=x' g(x)=x′且 f ( x ) = f ( x ′ ) = f ( g ( x ) ) f(x)=f(x')=f(g(x)) f(x)=f(x′)=f(g(x)),我们就说 f ( x ) f(x) f(x)对于变换 g g g具有不变性。
池化层具有(近似)平移不变性,也就是说池化层对平移不敏感。不管采用什么样的池化函数,当输入做出少量平移时,池化能够帮助输入的表示近似不变。例如我们使用最大池化,只要变换不影响到最大值,我们的池化结果不会收到影响。对于一个卷积核来说,只有一个值的变动会影响到输出, 其他的变换都不会造成扰动。平均池化的近似不变性就稍弱些。
局部平移不变性是一个很有用的性质,尤其是当我们关心某个特征是否出现而不关心它出现的具体位置时。
4.4 卷积神经网络中im2col是如何实现的?
卷积的运算量十分巨大,如果将卷积运算转化为矩阵运算,便能利用GPU来提升性能。
im2col全称image to column(从图像到矩阵),作用为加速卷积运算。即把包含批数量的4维数据转换成2维数据。也就是将输入数据降维,然后通过numpy的矩阵运算后得到结果,再将结果的形状还原,从而通过用矩阵运算来代替for循环语句。
im2col
将卷积核和卷积核扫过的区域都转化为列(行)向量。举个例子方便理解:
假设一个3*3卷积核为:
输入图像是5*5像素的单通道图像:
卷积核会锁定3*3的滑动窗口:
im2col
会将每个滑动窗口内的像素转为列向量:
就这样转化所有滑动窗口的列向量,将其拼接成9行的矩阵(行数与滑动窗口数目相关,列数则与卷积核大小相关):
im2col
还会将卷积核转化为行向量:
最后使用卷积核行向量乘以滑动窗口元素组成的矩阵:
得到1*9的列向量,将其拼接成特征图:
4.5 CNN 的局限性是什么?
- 以CNN为代表的前馈神经网络使用了条件独立假设,其特征提取范围受到限制;而循环神经网络或者Attention机制的模型能够看到序列整体的信息,因此对于序列建模任务,单纯的CNN存在先天不足。
- 卷积神经网络的核心思想是捕捉局部特征。对于序列任务,比如文本来说,局部特征就是由若干单词组成的滑动窗口(类似N-Gram)。卷积神经网络的优势在于能够自动地对N-Gram特征进行组合和筛选,获得不同抽象层次的语义信息。
- 解决方法:加入更多卷积层 -> 网络深度增加 -> 参数量增大 -> 容易过拟合 -> 引入 Dropout 正则化 -> 引入超参数 -> 网络庞大冗余,难以训练
- 使用CNN做序列任务的优点在于其共享权重的机制,使得训练速度相对较快;但是其最大的缺点在于CNN无法构建长距离依存关系。
- 有许多研究者试图改进CNN模型,使其能够应用于序列任务。诸如用于句子语义建模的动态卷积神经网络DCNN、可以在整个序列的所有窗口上进行卷积的时延神经网络TDNN等。
五、Iterated Dilated CNN 篇
5.1 什么是 Dilated CNN 空洞卷积?
- 动机:
- 正常CNN 的 filler 作用于输入矩阵连续位置,利用 卷积 和 池化 整合多尺度的上下文信息,导致分辨率损失
- pooling 会损失信息,降低精度,不加则导致感受野变小,学不到全局信息
- CNN提取特征的能力受到卷积核大小的限制,特别是对于序列问题,容易造成长距离依赖问题
- 介绍:Dilated Convolutions,翻译为扩张卷积或空洞卷积。空洞卷积与普通的卷积相比,除了卷积核的大小以外,还有一个扩张率参数,主要用来表示扩张的大小,以及去掉 pooling。
- 空洞卷积与普通卷积的相同点:卷积核的大小是一样的,在神经网络中即参数数量不变,区别在于扩张卷积具有更大的感受野。感受野指的是特征图上某个元素的计算受输入图像影响区域的范围,即特征图上神经元能够“看到”的图像范围,例如3×3卷积核的感受野大小为9。
具体地,空洞卷积通过给卷积核插入“空洞”变相增加其大小。如果在卷积核的每两个元素之间插入 D − 1 D-1 D−1个空洞,该卷积核的有效大小为 K ′ = K + ( K − 1 ) × ( D − 1 ) K'=K+(K-1)\times (D-1) K′=K+(K−1)×(D−1)。其中 K K K为原始卷积核大小, D D D称为膨胀率(扩张率,dilation rate)。当 D = 1 D=1 D=1时卷积核为普通的卷积核。
例如下图中
- (a) 普通卷积,1-dilated convolution,卷积核的感受野为3×3=9。
- (b) 空洞卷积,2-dilated convolution,卷积核的感受野为7×7=49。
- © 空洞卷积,4-dilated convolution,卷积核的感受野为15×15=225。
- 优点:dilated width 会随着层数增加而指数增加,层数 增加,参数量线性增长, 感受野 指数增加,可覆盖全部信息
例如卷积核大小为3,步长为1,膨胀率为2的空洞卷积动态示意图:
- 空洞卷积的作用
- 扩大感受野。如果希望增加输出单元的感受野,一般可以通过增加卷积核大小、增加卷积层数,或在卷积前增加池化操作来实现。但是前两者会增加参数数目,第三种方式会损失信息。空洞卷积就是一种增加感受野的同时不增加参数数量的方法。
- 捕获多尺度上下文信息:在进行空洞卷积前,通过双线性插值,对feature map进行上采样之后,再进行空洞卷积,这样可以在提高feature map的像素的同时,提取更多的全局信息
5.2 什么是 Iterated Dilated CNN?
- 思路:利用四个结构相同的 Dilated CNN 拼接起来,每个 block 里面 dilated width 为 1,1,2 的三层 DCNN,能够捕获更大感受野的信息
六、反卷积 篇
6.1 解释反卷积的原理和用途?
1.反卷积原理
反卷积=上采样=(转置卷积+微步卷积)⊆ 空洞卷积=一般意义上的广义卷积(包含上采样和下采样)。
上采样:在应用在计算机视觉的深度学习领域,由于输入图像通过卷积神经网络(CNN)提取特征后,输出的尺寸往往会变小,而有时我们需要将图像恢复到原来的尺寸以便进行进一步的计算(e.g.:图像的语义分割),这个采用扩大图像尺寸,实现图像由小分辨率到大分辨率的映射的操作,叫做上采样(Upsample)。
反卷积是一种特殊的正向卷积,先按照一定的比例通过补0来扩大输入图像的尺寸,接着旋转卷积核,再进行正向卷积。
反卷积又被称为Transposed(转置) Convolution,其实卷积层的前向传播过程就是反卷积层的反向传播过程,卷积层的反向传播过程就是反卷积层的前向传播过程。因为卷积层的前向反向计算分别为乘 C C C 和 C T C^T CT,而反卷积层的前向反向计算分别为乘 C T C^T CT 和 ( C T ) T (C^T)^T (CT)T,所以它们的前向传播和反向传播刚好交换过来。
上图展示一个反卷积的工作过程,乍看一下好像反卷积和卷积的工作过程差不多,主要的区别在于反卷积输出图片的尺寸会大于输入图片的尺寸,通过增加padding来实现这一操作,上图展示的是一个strides(步长)为1的反卷积。下面看一个strides不为1的反卷积
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVOeXpGK-1677247913540)(https://i2.kknews.cc/SIG=2stfug5/ctp-vzntr/q0r0n688rps74822ornn58958rqrr4r1.jpg)]
上图中的反卷积的stride为2,通过间隔插入padding来实现的。同样,可以根据反卷积的o、s、k、p参数来计算反卷积的输出i,也就是卷积的输入。公式如下:i=(o−1)∗s+k−2∗p。
2.作用
-
通过反卷积可以用来可视化卷积的过程
-
反卷积在GAN等领域中有着大量的应用。 比如用GANs生成图片,其中的generator和discriminator均采用深度学习,generator生成图片过程中采用的就是反卷积操作(当然discriminator采用卷积对generator生成的图片判别真伪)。
-
将一些低分辨率的图片转换为高分辨率的图片。
参考:
https://kknews.cc/code/zb2jr43.html
https://zhuanlan.zhihu.com/p/48501100
参考
- 理解为什么要将全连接层转化为卷积层
- What do the fully connected layers do in CNNs?
- CNN全连接层和卷积层的转化
- 【CNN】理解卷积神经网络中的通道 channel
- 万字长文概述NLP中的深度学习技术
【关于 RNN】那些你不知道的事
一、RNN 篇
1.2 为什么需要 RNN?
无论是 全连接网络 还是 卷积神经网络 他们的前提假设都是 元素间相互独立,也就是输入和输出的一一对应,也就是一个输入得到一个输出。不同的输入之间是没有联系的。
然而,在 序列数据(自然语言处理任务、时间序列任务)中,对于每一个输出,他不仅和他所对应的输入相关,还与前面其他词 和 词间的顺序相关。
这个时候,全连接网络 还是 卷积神经网络 将不能很好解决该问题。
RNN 之所以被称为"循环",是因为它对序列中的每个元素执行相同的任务,输出取决于先前的计算。考虑RNN的另一种方式是它们有一个“记忆”,它可以捕获到目前为止计算的信息。
1.2 RNN 结构是怎么样的?
从 上面图片中,可以看出 RNN网络 的 核心在于 s t s_t st 的值 不仅取决 xt,而且还与 s t − 1 s_{t-1} st−1 相关。
1.3 RNN 前向计算公式?
- 第一步,计算隐藏层的输出值
!
注:f 为隐藏层的激活函数,一般用 softmax 函数
- 第二步,计算输出层的输出值
注:g 为输出层的激活函数,一般用 tanh 函数 或者 ReLU 函数
1.4 RNN 存在什么问题?
- 梯度消失和梯度爆炸 问题
- 原因
误差沿时间反向传播:
矩阵的模的值取决 于 上图 红框部分:
当其大于1时,随着RNN深度的增加,矩阵的模的值呈指数函数增加,此时将出现 梯度爆炸;
当其小于1时,随着RNN深度的增加,矩阵的模的值呈指数函数减少,此时将出现 梯度消失;
- 解决方法
- 梯度消失问题解决方法:
- 合理的初始化权重值;
- 使用relu代替sigmoid和tanh作为激活函数;
- 使用其他结构的RNNs;
- 梯度爆炸问题解决方法:
- 设置一个梯度阈值,当梯度超过这个阈值的时候可以直接截取
- 梯度消失问题解决方法:
- 长距离的依赖问题
导致训练时梯度不能在较长序列中一直传递下去,从而使RNN无法捕捉到长距离的影响
二、长短时记忆网络(Long Short Term Memory Network, LSTM) 篇
2.1 为什么 需要 LSTM?
RNN 梯度消失和梯度爆炸问题
2.2 LSTM 的结构是怎么样的?
2.3 LSTM 如何缓解 RNN 梯度消失和梯度爆炸问题?
-
引用门控机制
- 遗忘门:控制继续保存长期状态c;
- 输入门:控制把即时状态输入到长期状态c;
- 输出门:控制是否把长期状态c作为当前的LSTM的输出;
-
原理:门实际上就是一层全连接层,它的输入是一个向量,输出是一个0到1之间的实数向量。
2.3 LSTM 的流程是怎么样的?
- 遗忘门计算
δ 是 sigmoid 函数
- 输入门
- 计算用于描述当前输入的 单元状态
- 计算当前时刻的单元状态
- 输出门
- 最终输出
2.4 LSTM 中激活函数区别?
- 门控的激活函数为 sigmoid;
- 输出的激活函数为tanh函数;
2.5 LSTM的复杂度?
序列长度为T,隐藏层维度为H
O(T * H^2)
2.6 LSTM 存在什么问题?
计算量大
三、GRU (Gated Recurrent Unit)
3.1 为什么 需要 GRU?
LSTM 计算量大
3.2 GRU 的结构是怎么样的?
3.3 GRU 的前向计算?
- 重置门(reset gate)的计算:
-
候选激活值(candidate activation)的计算
-
更新门(update gate)的计算
- 激活值的计算
3.4 GRU 与其他 RNN系列模型的区别?
GRU输入输出的结构与普通的RNN相似,其中的内部思想与LSTM相似。
与LSTM相比,GRU内部少了一个”门控“,参数比LSTM少,但是却也能够达到与LSTM相当的功能。考虑到硬件的计算能力和时间成本,因而很多时候我们也就会选择更加”实用“的GRU啦。
四、RNN系列模型篇
4.1 RNN系列模型 有什么特点?
- 具有记忆能力:针对文本序列,能够从头到尾阅读文章中每个单词,将前面阅读到的有用信息编码到状态变量中,从而拥有一定记忆能力,可更好理解之后的内容
参考
【关于Transformer】那些你不知道的事
一、动机篇
1.1 为什么要有 Transformer?
为什么要有 Transformer? 首先需要知道在 Transformer 之前都有哪些技术,这些技术所存在的问题:
- RNN:能够捕获长距离依赖信息,但是无法并行;
- CNN: 能够并行,无法捕获长距离依赖信息(需要通过层叠 or 扩张卷积核 来 增大感受野);
- 传统 Attention
- 方法:基于源端和目标端的隐向量计算Attention,
- 结果:源端每个词与目标端每个词间的依赖关系 【源端->目标端】
- 问题:忽略了 远端或目标端 词与词间 的依赖关系
1.2 Transformer 作用是什么?
基于Transformer的架构主要用于建模语言理解任务,它避免了在神经网络中使用递归,而是完全依赖于self-attention机制来绘制输入和输出之间的全局依赖关系。
二、整体结构篇
2.1 Transformer 整体结构是怎么样?
- Transformer 整体结构:
- encoder-decoder 结构
- 具体介绍:
- 左边是一个 Encoder;
- 右边是一个 Decoder;
Transformer 整体结构
- 整体结构放大一点
从上一张 Transformer 结构图,可以知道 Transformer 是一个 encoder-decoder 结构,但是 encoder 和 decoder 又包含什么内容呢?
- Encoder 结构:
- 内部包含6层小encoder 每一层里面有2个子层;
- Decoder 结构:
- 内部也是包含6层小decoder ,每一层里面有3个子层
Transformer encoder-decoder 结构
- 整体结构再放大一点
其中上图中每一层的内部结构如下图所求。
- 上图左边的每一层encoder都是下图左边的结构;
- 上图右边的每一层的decoder都是下图右边的结构;
具体内容,后面会逐一介绍。
Transformer encoder-decoder 内部结构
- 代码讲解【注:代码采用 tensorflow 框架编写】
class Transformer(tf.keras.Model):
def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, target_vocab_size, pe_input, pe_target, rate=0.1):
super(Transformer, self).__init__()
# Encoder 模块
self.encoder = Encoder(num_layers, d_model, num_heads, dff, input_vocab_size, pe_input, rate)
# Decoder 模块
self.decoder = Decoder(num_layers, d_model, num_heads, dff, target_vocab_size, pe_target, rate)
# 全连接层
self.final_layer = tf.keras.layers.Dense(target_vocab_size)
def call(self, inp, tar, training, enc_padding_mask, look_ahead_mask, dec_padding_mask):
# step 1: encoder
enc_output = self.encoder(inp, training, enc_padding_mask)
# step 2:decoder
dec_output, attention_weights = self.decoder(tar, enc_output, training, look_ahead_mask, dec_padding_mask)
# step 3:全连接层
final_output = self.final_layer(dec_output)
return final_output, attention_weights
2.2 Transformer-encoder 结构怎么样?
Transformer encoder 结构
- 特点:
- 与 RNN,CNN 类似,可以当成一个特征提取器;
- 组成结构介绍
- embedding 层:将 input 转化为 embedding 向量 X X X;
- Position encodding: input的位置与 input 的 embedding X X X 相加 得到 向量 X X X;
- self-attention : 将融合input的位置信息 与 input 的 embedding 信息的 X X X 输入 Self-Attention 层得到 Z Z Z;
- 残差网络: Z Z Z 与 X X X 相加后经过 layernorm 层;
- 前馈网络:经过一层前馈网络以及 Add&Normalize,(线性转换+relu+线性转换 如下式)
- 举例说明(假设序列长度固定,如100,如输入的序列是“我爱中国”):
- 首先需要 encoding:
- 将词映射成一个数字,encoding后,由于序列不足固定长度,因此需要padding,
- 然后输入 embedding层,假设embedding的维度是128,则输入的序列维度就是100*128;
- 接着是Position encodding,论文中是直接将每个位置通过cos-sin函数进行映射;
- 分析:这部分不需要在网络中进行训练,因为它是固定。但现在很多论文是将这块也embedding,如bert的模型,至于是encoding还是embedding可取决于语料的大小,语料足够大就用embedding。将位置信息也映射到128维与上一步的embedding相加,输出100*128
- 经过self-attention层:
- 操作:假设v的向量最后一维是64维(假设没有多头),该部分输出100*64;
- 经过残差网络:
- 操作:即序列的embedding向量与上一步self-attention的向量加总;
- 经过 layer-norm:
- 原因:
- 由于在self-attention里面更好操作而已;
- 真实序列的长度一直在变化;
- 原因:
- 经过 前馈网络:
- 目的:增加非线性的表达能力,毕竟之前的结构基本都是简单的矩阵乘法。若前馈网络的隐向量是512维,则结构最后输出100*512;
- 首先需要 encoding:
- Transformer Encoder 单元 代码讲解【注:代码采用 tensorflow 框架编写】
class EncoderLayer(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, dff, rate=0.1):
super(EncoderLayer, self).__init__()
self.mha = MultiHeadAttention(d_model, num_heads)
self.ffn = point_wise_feed_forward_network(d_model, dff)
self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.dropout1 = tf.keras.layers.Dropout(rate)
self.dropout2 = tf.keras.layers.Dropout(rate)
def call(self, x, training, mask):
# step 1:多头自注意力
attn_output, _ = self.mha(x, x, x, mask)
# step 2:前馈网络
attn_output = self.dropout1(attn_output, training=training)
# step 3:Layer Norml
out1 = self.layernorm1(x + attn_output)
# step 4:前馈网络
ffn_output = self.ffn(out1)
ffn_output = self.dropout2(ffn_output, training=training)
# step 5:Layer Norml
out2 = self.layernorm2(out1 + ffn_output)
return out2
- Transformer Encoder 模块代码讲解【注:代码采用 tensorflow 框架编写】
class Encoder(tf.keras.layers.Layer):
def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, maximum_position_encoding, rate=0.1):
super(Encoder, self).__init__()
self.d_model = d_model
self.num_layers = num_layers # encoder 层数
# 词嵌入
self.embedding = tf.keras.layers.Embedding(input_vocab_size, d_model)
# 位置编码
self.positional_encoding_obj = Positional_Encoding()
self.pos_encoding = self.positional_encoding_obj.positional_encoding(maximum_position_encoding, self.d_model)
# Encoder 模块构建
self.enc_layers = [EncoderLayer(d_model, num_heads, dff, rate) for _ in range(num_layers)]
self.dropout = tf.keras.layers.Dropout(rate)
def call(self, x, training, mask):
seq_len = tf.shape(x)[1]
# step 1:词嵌入
x = self.embedding(x)
x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
# step 2:位置编码
x += self.pos_encoding[:, :seq_len, :]
x = self.dropout(x, training=training)
# step 3:Encoder 模块构建
for i in range(self.num_layers):
x = self.enc_layers[i](x, training, mask)
return x
2.3 Transformer-decoder 结构怎么样?
Transformer decoder 结构
- 特点:与 encoder 类似
- 组成结构介绍
- masked 层:
- 目的:确保了位置 i 的预测仅依赖于小于 i 的位置处的已知输出;
- Linear layer:
- 目的:将由解码器堆栈产生的向量投影到一个更大的向量中,称为对数向量。这个向量对应着模型的输出词汇表;向量中的每个值,对应着词汇表中每个单词的得分;
- softmax层:
- 操作:这些分数转换为概率(所有正数,都加起来为1.0)。选择具有最高概率的单元,并且将与其相关联的单词作为该时间步的输出
- masked 层:
- Transformer Decoder 单元 代码讲解【注:代码采用 tensorflow 框架编写】
class DecoderLayer(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, dff, rate=0.1):
super(DecoderLayer, self).__init__()
self.mha1 = MultiHeadAttention(d_model, num_heads)
self.mha2 = MultiHeadAttention(d_model, num_heads)
self.ffn = point_wise_feed_forward_network(d_model, dff)
self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.layernorm3 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.dropout1 = tf.keras.layers.Dropout(rate)
self.dropout2 = tf.keras.layers.Dropout(rate)
self.dropout3 = tf.keras.layers.Dropout(rate)
def call(self, x, enc_output, training, look_ahead_mask, padding_mask):
# step 1:带 sequence mask 的 多头自注意力
attn1, attn_weights_block1 = self.mha1(x, x, x, look_ahead_mask)
attn1 = self.dropout1(attn1, training=training)
# step 2:Layer Norm
out1 = self.layernorm1(attn1 + x)
# step 3:带 padding mask 的 多头自注意力
attn2, attn_weights_block2 = self.mha2(enc_output, enc_output, out1, padding_mask)
attn2 = self.dropout2(attn2, training=training)
# step 4:Layer Norm
out2 = self.layernorm2(attn2 + out1)
# step 5:前馈网络
ffn_output = self.ffn(out2)
ffn_output = self.dropout3(ffn_output, training=training)
# step 6:Layer Norm
out3 = self.layernorm3(ffn_output + out2)
return out3, attn_weights_block1, attn_weights_block2
- Transformer Decoder 模块代码讲解【注:代码采用 tensorflow 框架编写】
class Decoder(tf.keras.layers.Layer):
def __init__(self, num_layers, d_model, num_heads, dff, target_vocab_size, maximum_position_encoding, rate=0.1):
super(Decoder, self).__init__()
self.d_model = d_model
self.num_layers = num_layers # encoder 层数
# 词嵌入
self.embedding = tf.keras.layers.Embedding(target_vocab_size, d_model)
# 位置编码
self.positional_encoding_obj = Positional_Encoding()
self.pos_encoding = self.positional_encoding_obj.positional_encoding(maximum_position_encoding, d_model)
# Dncoder 模块构建
self.dec_layers = [DecoderLayer(d_model, num_heads, dff, rate) for _ in range(num_layers)]
self.dropout = tf.keras.layers.Dropout(rate)
def call(self, x, enc_output, training, look_ahead_mask, padding_mask):
seq_len = tf.shape(x)[1]
attention_weights = {}
# step 1:词嵌入
x = self.embedding(x)
x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
# step 2:位置编码
x += self.pos_encoding[:, :seq_len, :]
x = self.dropout(x, training=training)
# step 3:Dncoder 模块构建
for i in range(self.num_layers):
x, block1, block2 = self.dec_layers[i](x, enc_output, training, look_ahead_mask, padding_mask)
attention_weights['decoder_layer{}_block1'.format(i+1)] = block1
attention_weights['decoder_layer{}_block2'.format(i+1)] = block2
return x, attention_weights
三、模块篇
3.1 self-attention 模块
3.1.1 传统 attention 是什么?
在介绍 self-attention 之前,首先需要介绍一下 传统的 attention,其结构如下图所示:
传统的 attention 结构
- 注意力机制是什么呢?
- 就是将精力集中于某一个点上
- 举个例子:
- 你在超市买东西,突然一个美女从你身边走过,这个时候你会做什么呢?
- 没错,就是将视线【也就是注意力】集中于这个美女身上,而周围环境怎么样,你都不关注。
- 思路
- 输入 给定 Target 中某个 query;
- 计算权值 Score:
- 计算 query 和 各个 Key 的相似度或相关性,得到每个 Key 对应 value 的权值系数;
- 对 权值 Score 和 value 进行加权求和
- 核心:
- Attention 机制 是对 source 中各个元素 的 value 进行加权求和,而 query 和 key 用于计算 对应 value 的权值系数
L x = ∣ ∣ S o u r c e ∣ ∣ L_x=||Source|| Lx=∣∣Source∣∣代表Source的长度
- 概念:
- attention 的核心 就是从 大量信息中 筛选出少量的 重要信息;
- 具体操作:每个 value 的 权值系数,代表 其 重要度;
传统的 attention 流程
- 具体流程介绍
-
step 1:计算权值系数
-
采用 不同的函数或计算方式,对 query 和 key 进行计算,求出相似度或相关性
-
采用的计算方法:
- 向量点积:
- Cosine 相似度计算:
- MLP 网络:
-
-
step 2: softmax 归一化
- 原因:
- score 值分布过散,将原始计算分值整理成所有元素权重之和为1 的概率分布;
- 可以通过SoftMax的内在机制更加突出重要元素的权重;
- 公式介绍
- 原因:
-
step 3: 加权求和
- 公式介绍:
- 计算结果 a i a_i ai 即为 v a l u e i value_i valuei 对应的权重系数,然后进行加权求和即可得到Attention数值
- 公式介绍:
-
- 存在问题
- 忽略了 源端或目标端 词与词间 的依赖关系【以上面栗子为例,就是把注意力集中于美女身上,而没看自己周围环境,结果可能就扑街了!】
3.1.2 为什么 会有self-attention?
- CNN 所存在的长距离依赖问题;
- RNN 所存在的无法并行化问题【虽然能够在一定长度上缓解 长距离依赖问题】;
- 传统 Attention
- 方法:基于源端和目标端的隐向量计算Attention,
- 结果:源端每个词与目标端每个词间的依赖关系 【源端->目标端】
- 问题:忽略了 远端或目标端 词与词间 的依赖关系
3.1.3 self-attention 的核心思想是什么?
- 核心思想:self-attention的结构在计算每个token时,总是会考虑整个序列其他token的表达;
- 举例:“我爱中国”这个序列,在计算"我"这个词的时候,不但会考虑词本身的embedding,也同时会考虑其他词对这个词的影响
3.1.4 self-attention 的目的是什么?
- 目的:学习句子内部的词依赖关系,捕获句子的内部结构。
3.1.5 self-attention 的怎么计算的?
self-attention 计算公式
self-attention 结构图
-
一句话概述:每个位置的embedding对应 Q,K,V 三个向量,这三个向量分别是embedding点乘 WQ,WK,WV 矩阵得来的。每个位置的Q向量去乘上所有位置的K向量,其结果经过softmax变成attention score,以此作为权重对所有V向量做加权求和即可。
-
具体步骤
- embedding层:
- 目的:将词转化成embedding向量;
- Q,K,V 向量计算:
- 根据 embedding 和权重矩阵,得到Q,K,V;
- Q:查询向量,目标字作为 Query;
- K:键向量,其上下文的各个字作为 Key;
- V:值向量,上下文各个字的 Value;
- 根据 embedding 和权重矩阵,得到Q,K,V;
- 权重 score 计算:
- 查询向量 query 点乘 key;
- 目的:计算其他词对这个词的重要性,也就是权值;
- scale 操作:
- 乘以 1 d k \frac{1}{\sqrt{d_{k}}} dk1;
- 目的:起到调节作用,使得内积不至于太大。实际上是Q,K,V的最后一个维度,当 d k d_k dk 越大, Q K T QK^T QKT 就越大,可能会将 Softmax 函数推入梯度极小的区域;
- Softmax 归一化:
- 经过 Softmax 归一化;
- Attention 的输出计算:
- 权值 score 和各个上下文字的 V 向量 的加权求和
- 目的:把上下文各个字的 V 融入目标字的原始 V 中
- embedding层:
-
举例
- 答案就是文章中的Q,K,V,这三个向量都可以表示"我"这个词,但每个向量的作用并不一样,Q 代表 query,当计算"我"这个词时,它就能代表"我"去和其他词的 K 进行点乘计算其他词对这个词的重要性,所以此时其他词(包括自己)使用 K 也就是 key 代表自己,当计算完点乘后,我们只是得到了每个词对“我”这个词的权重,需要再乘以一个其他词(包括自己)的向量,也就是V(value),才完成"我"这个词的计算,同时也是完成了用其他词来表征"我"的一个过程
-
优点
- 捕获源端和目标端词与词间的依赖关系
- 捕获源端或目标端自身词与词间的依赖关系
-
代码讲解【注:代码采用 tensorflow 框架编写】
def scaled_dot_product_attention(q, k, v, mask):
# s1:权重 score 计算:查询向量 query 点乘 key
matmul_qk = tf.matmul(q, k, transpose_b=True)
# s2:scale 操作:除以 sqrt(dk),将 Softmax 函数推入梯度极小的区域
dk = tf.cast(tf.shape(k)[-1], tf.float32)
scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)
# s3:
if mask is not None:
scaled_attention_logits += (mask * -1e9)
# s4:Softmax 归一化
attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)
# s5:加权求和
output = tf.matmul(attention_weights, v)
return output, attention_weights
3.1.6 self-attention 为什么Q和K使用不同的权重矩阵生成,为何不能使用同一个值进行自身的点乘?
因为Q、K、V的不同,可以保证在不同空间进行投影,增强了表达能力,提高了泛化能力。
3.1.7 为什么采用点积模型的 self-attention 而不采用加性模型?
主要原因:在理论上,加性模型和点积模型的复杂度差不多,但是点积模型在实现上可以更好地利用矩阵乘法,而矩阵乘法有很多加速策略,因此能加速训练。但是论文中实验表明,当维度 d d d越来越大时,加性模型的效果会略优于点积模型,原因应该是加性模型整体上还是比点积模型更复杂(有非线性因素)。
3.1.8 Transformer 中在计算 self-attention 时为什么要scaled dot product? 即 除以 d \sqrt{d} d?
一句话回答:当输入信息的维度 d 比较高,会导致 softmax 函数接近饱和区,梯度会比较小。因此,缩放点积模型可以较好地解决这一问题。
具体分析:
- 因为对于
如果某个
相对于其他元素很大的话,那么对此向量softmax后就容易得到一个onehot向量,不够“soft”了,而且反向传播时梯度为0会导致梯度消失;
- 原论文是这么解释的:假设每个 q ∈ R d \boldsymbol q\in R^d q∈Rd和 k ∈ R d \boldsymbol k\in R^d k∈Rd的每个维度都是服从均值为0方差为1的,那么二者的内积 q T k \boldsymbol q^T\boldsymbol k qTk的均值就是0,方差就是 d d d,所以内积的方差和原始方差之间的比例大约是维度值 d d d,为了降低内积各个维度值的方差(这样各个维度取值就在均值附近,不会存在某个维度偏离均值太远),所以要除以 d \sqrt{d} d(标准差)
3.1.9 self-attention 如何解决长距离依赖问题?
- 引言:
- 在上一个问题中,我们提到 CNN 和 RNN 在处理长序列时,都存在 长距离依赖问题,那么你是否会有这样 几个问题:
- 长距离依赖问题 是什么呢?
- 为什么 CNN 和 RNN 无法解决长距离依赖问题?
- 之前提出过哪些解决方法?
- self-attention 是如何 解决 长距离依赖问题的呢?
- 在上一个问题中,我们提到 CNN 和 RNN 在处理长序列时,都存在 长距离依赖问题,那么你是否会有这样 几个问题:
下面,我们将会围绕着几个问题,进行一一解答。
- 长距离依赖问题 是什么呢?
- 介绍:对于序列问题,第 t t t 时刻 的 输出 y t y_t yt 依赖于 t t t 之前的输入,也就是 说 依赖于 x t − k , k = 1 , . . . , t x_{t-k}, k=1,...,t xt−k,k=1,...,t,当间隔 k k k 逐渐增大时, x t − k x_{t-k} xt−k 的信息将难以被 y t y_t yt 所学习到,也就是说,很难建立 这种 长距离依赖关系,这个也就是 长距离依赖问题(Long-Term Dependencies Problem)。
- 为什么 CNN 和 RNN 无法解决长距离依赖问题?
- CNN:
- 捕获信息的方式:
- CNN 主要采用 卷积核 的 方式捕获 句子内的局部信息,你可以把他理解为 基于 n-gram 的局部编码方式捕获局部信息
- 问题:
- 因为是 n-gram 的局部编码方式,那么当 k k k 距离 大于 n n n 时,那么 y t y_t yt 将难以学习 x t − k x_{t-k} xt−k 信息;
- 举例:
- 其实 n-gram 类似于 人的 视觉范围,人的视觉范围 在每一时刻 只能 捕获 一定 范围内 的信息,比如,你在看前面的时候,你是不可能注意到背后发生了什么,除非你转过身往后看。
- 捕获信息的方式:
- RNN:
- 捕获信息的方式:
- RNN 主要 通过 循环 的方式学习(记忆) 之前的信息 x t x_{t} xt;
- 问题:
- 但是随着时间 t t t 的推移,你会出现梯度消失或梯度爆炸问题,这种问题使你只能建立短距离依赖信息。
- 举例:
- RNN 的学习模式好比于 人类 的记忆力,人类可能会对 短距离内发生的 事情特别清楚,但是随着时间的推移,人类开始 会对 好久之前所发生的事情变得印象模糊,比如,你对小时候发生的事情,印象模糊一样。
- 解决方法:
- 针对该问题,后期也提出了很多 RNN 变体,比如 LSTM、 GRU,这些变体 通过引入 门控的机制 来 有选择性 的记忆 一些 重要的信息,但是这种方法 也只能在 一定程度上缓解 长距离依赖问题,但是并不能 从根本上解决问题。
- 捕获信息的方式:
- CNN:
基于卷积网络和循环网络的变长序列编码
- 之前提出过哪些解决方法?
- 引言:
- 那么 之前 主要采用 什么方法 解决问题呢?
- 解决方法:
- 增加网络的层数
- 通过一个深层网络来获取远距离的信息交互
- 使用全连接网络
- 通过全连接的方法对 长距离 建模;
- 问题:
- 无法处理变长的输入序列;
- 不同的输入长度,其连接权重的大小也是不同的;
- 增加网络的层数
- 引言:
全连接模型和自注意力模型
- self-attention 是如何 解决 长距离依赖问题的呢?
- 解决方式:
- 利用注意力机制来“动态”地生成不同连接的权重,从而处理变长的信息序列
- 具体介绍:
- 对于 当前query,你需要 与 句子中 所有 key 进行点乘后再 Softmax ,以获得 句子中 所有 key 对于 当前query 的 score(可以理解为 贡献度),然后与 所有词 的 value 向量进行加权融合之后,就能使 当前 y t y_t yt 学习到句子中 其他词 x t − k x_{t-k} xt−k的信息;
- 解决方式:
3.1.10 self-attention 如何并行化?
- 引言:
- 在上一个问题中,我们主要讨论了 CNN 和 RNN 在处理长序列时,都存在 长距离依赖问题,以及 Transformer 是 如何解决 长距离依赖问题,但是对于 RNN ,还存在另外一个问题:
- 无法并行化问题
- 那么,Transformer 是如何进行并行化的呢?
- 在上一个问题中,我们主要讨论了 CNN 和 RNN 在处理长序列时,都存在 长距离依赖问题,以及 Transformer 是 如何解决 长距离依赖问题,但是对于 RNN ,还存在另外一个问题:
- Transformer 如何进行并行化?
- 核心:self-attention
- 为什么 RNN 不能并行化:
- 原因:RNN 在 计算 x i x_i xi 的时候,需要考虑到 x 1 x i − 1 x_1 ~ x_{i-1} x1 xi−1 的 信息,使得 RNN 只能 从 x 1 x_1 x1 计算到 x i x_i xi;
- 思路:
- 在 self-attention 能够 并行的 计算 句子中不同 的 query,因为每个 query 之间并不存在 先后依赖关系,也使得 transformer 能够并行化;
3.1.11 为什么用双线性点积模型(即Q,K两个向量)
双线性点积模型使用Q,K两个向量,而不是只用一个Q向量,这样引入非对称性,更具健壮性(Attention对角元素值不一定是最大的,也就是说当前位置对自身的注意力得分不一定最高)。
3.2 multi-head attention 模块
3.2.1 multi-head attention 的思路是什么样?
- 思路:
- 相当于 h h h 个 不同的 self-attention 的集成
- 就是把self-attention做 n 次,取决于 head 的个数;论文里面是做了8次。
3.2.2 multi-head attention 的步骤是什么样?
- 步骤:
- step 1 : 初始化 N 组 Q , K , V Q,K,V Q,K,V矩阵(论文为 8组);
初始化 N 组 Q , K , V Q,K,V Q,K,V矩阵
- step 2 : 每组 分别 进行 self-attention;
- step 3:
- 问题:多个 self-attention 会得到 多个 矩阵,但是前馈神经网络没法输入8个矩阵;
- 目标:把8个矩阵降为1个
- 步骤:
- 每次self-attention都会得到一个 Z 矩阵,把每个 Z 矩阵拼接起来,
- 再乘以一个Wo矩阵,
- 得到一个最终的矩阵,即 multi-head Attention 的结果;
合并 8 个矩阵
最后,让我们来看一下完整的流程:
完整的流程
换一种表现方式:
multi-head attention 表示方式
- 动图介绍
multi-head attention 动图展示
3.2.3 Transformer为何使用多头注意力机制?(为什么不使用一个头)
为了让 Transformer 能够注意到不同子空间的信息,从而捕获到跟多的特征信息。【本质:实验定律】
3.2.4 多头机制为什么有效?
类似于CNN中通过多通道机制进行特征选择。Transformer中使用切头(split)的方法,是为了在不增加复杂度( O ( n 2 d ) O(n^2 d) O(n2d))的前提下享受类似CNN中“不同卷积核”的优势。
3.2.5 为什么在进行多头注意力的时候需要对每个head进行降维?
Transformer的多头注意力看上去是借鉴了CNN中同一卷积层内使用多个卷积核的思想,原文中使用了 8 个“scaled dot-product attention”,在同一“multi-head attention”层中,输入均为“KQV”,同时进行注意力的计算,彼此之前参数不共享,最终将结果拼接起来,这样可以允许模型在不同的表示子空间里学习到相关的信息,在此之前的 A Structured Self-attentive Sentence Embedding 也有着类似的思想。简而言之,就是希望每个注意力头,只关注最终输出序列中一个子空间,互相独立。其核心思想在于,抽取到更加丰富的特征信息。
3.2.6 multi-head attention 代码介绍
- multi-head attention 模块代码讲解【注:代码采用 tensorflow 框架编写】
class MultiHeadAttention(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
self.num_heads = num_heads
self.d_model = d_model
assert d_model % self.num_heads == 0
self.depth = d_model // self.num_heads
# 初始化 Q,K,V 矩阵
self.wq = tf.keras.layers.Dense(d_model)
self.wk = tf.keras.layers.Dense(d_model)
self.wv = tf.keras.layers.Dense(d_model)
self.dense = tf.keras.layers.Dense(d_model)
def split_heads(self, x, batch_size):
x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
return tf.transpose(x, perm=[0, 2, 1, 3])
def call(self, v, k, q, mask):
batch_size = tf.shape(q)[0]
# step 1:利用矩阵计算 q,k,v
q = self.wq(q)
k = self.wk(k)
v = self.wv(v)
# step 2:
q = self.split_heads(q, batch_size)
k = self.split_heads(k, batch_size)
v = self.split_heads(v, batch_size)
# step 3:每组 分别 进行 self-attention
scaled_attention, attention_weights = scaled_dot_product_attention(
q, k, v, mask)
# step 4:矩阵拼接
scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])
concat_attention = tf.reshape(scaled_attention, (batch_size, -1, self.d_model))
# step 5:全连接层
output = self.dense(concat_attention)
return output, attention_weights
3.3 位置编码(Position encoding)模块
3.3.1 为什么要 加入 位置编码(Position encoding) ?
- 问题:
- 介绍:缺乏 一种 表示 输入序列中 单词顺序 的方法
- 说明:因为模型不包括Recurrence/Convolution,因此是无法捕捉到序列顺序信息的,例如将K、V按行进行打乱,那么Attention之后的结果是一样的。但是序列信息非常重要,代表着全局的结构,因此必须将序列的分词相对或者绝对position信息利用起来
- 目的:加入词序信息,使 Attention 能够分辨出不同位置的词
3.3.2 位置编码(Position encoding)的思路是什么 ?
- 思路:
- 在 encoder 层和 decoder 层的输入添加了一个额外的向量Positional Encoding,维度和embedding的维度一样,让模型学习到这个值
3.3.3 位置编码(Position encoding)的作用是什么 ?
- 位置向量的作用:
- 决定当前词的位置;
- 计算在一个句子中不同的词之间的距离
3.3.4 位置编码(Position encoding)的步骤是什么 ?
- 步骤:
- 将每个位置编号,
- 然后每个编号对应一个向量,
- 通过将位置向量和词向量相加,就给每个词都引入了一定的位置信息。
位置编码(Position encoding) 结构图
- 论文的位置编码是使用三角函数去计算的。好处:
- 值域只有[-1,1]
- 容易计算相对位置。
注:
pos 表示当前词在句子中的位置
i 表示向量中每个值 的 index
在偶数位置:使用 正弦编码 sin();
在奇数位置:使用 余弦编码 cos();
3.3.5 Position encoding为什么选择相加而不是拼接呢?
因为 [ W 1 W 2 ] [ e ; p ] = W 1 e + W 2 p , W ( e + p ) = W e + W p [W1 W2][e; p] = W1e + W2p,W(e+p)=We+Wp [W1W2][e;p]=W1e+W2p,W(e+p)=We+Wp,就是说求和相当于拼接的两个权重矩阵共享(W1=W2=W),但是这样权重共享是明显限制了表达能力的。
3.3.6 Position encoding和 Position embedding的区别?
- Position encoding 构造简单直接无需额外的学习参数;能兼容预训练阶段的最大文本长度和训练阶段的最大文本长度不一致;
- Position embedding 构造也简单直接但是需要额外的学习参数;训练阶段的最大文本长度不能超过预训练阶段的最大文本长度(因为没学过这么长的,不知道如何表示);但是Position embedding 的潜力在直觉上会比 Position encoding 大,因为毕竟是自己学出来的,只有自己才知道自己想要什么(前提是数据量得足够大)。
3.3.7 为何17年提出Transformer时采用的是 Position Encoder 而不是Position Embedding?而Bert却采用的是 Position Embedding ?
- Transformer 的作者在论文中对比了 Position Encoder 和 Position Embedding,在模型精度上没有明显区别。出于对序列长度限制和参数量规模的考虑,最终选择了 Encode 的形式。那么为什么Bert不这么干呢?主要原因如下:
- 模型的结构需要服务于模型的目标:Transformer最开始提出是针对机器翻译任务的,而机器翻译任务对词序特征要求不高,因此在效果差不多的情况下选择Position Encoder 足矣。但是Bert是作为通用的预训练模型,下游任务有很多对词序特征要求很高,因此选择潜力比较大的Position Embedding会更好;
- 数据量的角度:Transformer用的数据量没有Bert的数据量大,所以使用潜力无限的 Position Embedding 会有大力出奇迹的效果;
3.3.8 位置编码(Position encoding)的代码介绍
- 位置编码(Position encoding)模块代码讲解【注:代码采用 tensorflow 框架编写】
# 位置编码 类
class Positional_Encoding():
def __init__(self):
pass
# 功能:计算角度 函数
def get_angles(self, position, i, d_model):
'''
功能:计算角度 函数
input:
position 单词在句子中的位置
i 维度
d_model 向量维度
'''
angle_rates = 1 / np.power(10000, (2 * (i // 2)) / np.float32(d_model))
return position * angle_rates
# 功能:位置编码 函数
def positional_encoding(self, position, d_model):
'''
功能:位置编码 函数
input:
position 单词在句子中的位置
d_model 向量维度
'''
angle_rads = self.get_angles(
np.arange(position)[:, np.newaxis],
np.arange(d_model)[np.newaxis, :],
d_model
)
# apply sin to even indices in the array; 2i
angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])
# apply cos to odd indices in the array; 2i+1
angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])
pos_encoding = angle_rads[np.newaxis, ...]
return tf.cast(pos_encoding, dtype=tf.float32)
3.4 残差模块模块
3.4.1 为什么要 加入 残差模块?
- 动机:因为 transformer 堆叠了 很多层,容易 梯度消失或者梯度爆炸
3.5 Layer normalization 模块
3.5.1 为什么要 加入 Layer normalization 模块?
- 动机:因为 transformer 堆叠了 很多层,容易 梯度消失或者梯度爆炸;
- 原因:
- 数据经过该网络层的作用后,不再是归一化,偏差会越来越大,所以需要将 数据 重新 做归一化处理;
- 目的:
- 在数据送入激活函数之前进行normalization(归一化)之前,需要将输入的信息利用 normalization 转化成均值为0方差为1的数据,避免因输入数据落在激活函数的饱和区而出现 梯度消失或者梯度爆炸 问题
3.5.2 Layer normalization 模块的是什么?
- 介绍:
- 归一化的一种方式
- 对每一个样本介绍均值和方差【这个与 BN 有所不同,因为他是在 批方向上 计算均值和方差】
3.5.3 Batch normalization 和 Layer normalization 的区别?
- 公式
BN 计算公式
LN 计算公式
- Batch normalization — 为每一个小batch计算每一层的平均值和方差
- Layer normalization — 独立计算每一层每一个样本的均值和方差
3.5.4 Transformer 中为什么要舍弃 Batch normalization 改用 Layer normalization 呢?
原始BN是为CNN而设计的,对整个batchsize范围内的数据进行考虑;
对于RNN来说,sequence的长度是不一致的,所以用很多padding来表示无意义的信息。如果用 BN 会导致有意义的embedding 损失信息。
所以,BN一般用于CNN,而LN用于RNN。
layernorm是在hidden size的维度进行的,跟batch和seq_len无关。每个hidden state都计算自己的均值和方差,这是因为不同hidden state的量纲不一样。beta和gamma的维度都是(hidden_size,),经过白化的hidden state * beta + gamma得到最后的结果。
LN在BERT中主要起到白化的作用,增强模型稳定性(如果删除则无法收敛)
3.5.5 Layer normalization 模块代码介绍
- Layer normalization 模块代码讲解【注:代码采用 tensorflow 框架编写】
class EncoderLayer(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, dff, rate=0.1):
...
self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
...
def call(self, x, training, mask):
...
# step 3:Layer Norml
out1 = self.layernorm1(x + attn_output)
# step 4:前馈网络
ffn_output = self.ffn(out1)
ffn_output = self.dropout2(ffn_output, training=training)
# step 5:Layer Norml
out2 = self.layernorm2(out1 + ffn_output)
return out2
3.6 Mask 模块
3.6.1 什么是 Mask?
- 介绍:掩盖某些值的信息,让模型信息不到该信息;
3.6.2 Transformer 中用到 几种 Mask?
- 答案:两种
- 类别:padding mask and sequence mask
3.6.3 能不能介绍一下 Transformer 中用到几种 Mask?
- padding mask
- 作用域:每一个 scaled dot-product attention 中
- 动机:输入句子的长度不一问题
- 方法:
- 短句子:后面 采用 0 填充
- 长句子:只截取 左边 部分内容,其他的丢弃
- 原因:对于 填充 的位置,其所包含的信息量 对于 模型学习 作用不大,所以 self-attention 应该 抛弃对这些位置 进行学习;
- 做法:在这些位置上加上 一个 非常大 的负数(负无穷),使 该位置的值经过 Softmax 后,值近似 0,利用 padding mask 标记哪些值需要做处理;
- 实现:
# 功能: padding mask
def create_padding_mask(seq):
'''
功能: padding mask
input:
seq 序列
'''
seq = tf.cast(tf.math.equal(seq, 0), tf.float32)
return seq[:, tf.newaxis, tf.newaxis, :]
- sequence mask
- 作用域:只作用于 decoder 的 self-attention 中
- 动机:不可预测性;
- 目标:sequence mask 是为了使得 decoder 不能看见未来的信息。也就是对于一个序列,在 time_step 为 t 的时刻,我们的解码输出应该只能依赖于 t 时刻之前的输出,而不能依赖 t 之后的输出。因此我们需要想一个办法,把 t 之后的信息给隐藏起来。
- 做法:
- 产生一个下三角矩阵,上三角的值全为0,下三角全是 1。把这个矩阵作用在每一个序列上,就可以达到我们的目的
sequence mask 公式
注意力矩阵, 每个元素 a i j a_{ij} aij 代表 第 i 个词和第 j 个词的内积相似度
下三角矩阵,上三角的值全为0,下三角全是 1
注:
在 decoder 的 scaled dot-product attention 中,里面的 attn_mask = padding mask + sequence mask
在 encoder 的 scaled dot-product attention 中,里面的 attn_mask = padding mask
- 实现:
# 功能:sequence mask
def create_look_ahead_mask(size):
'''
功能: sequence mask
input:
seq 序列
'''
mask = 1 - tf.linalg.band_part(tf.ones((size, size)), -1, 0)
return mask
3.7 Feed forward network (FFN)
3.7.1 Feed forward network (FFN)的作用?
答:Transformer在抛弃了 LSTM 结构后,FFN 中的激活函数成为了一个主要的提供非线性变换的单元。
3.8 GELU
3.8.1 GELU原理?相比RELU的优点?
答:
- ReLU会确定性的将输入乘上一个0或者1(当x<0时乘上0,否则乘上1);
- Dropout则是随机乘上0;
- GELU虽然也是将输入乘上0或1,但是输入到底是乘以0还是1,是在取决于输入自身的情况下随机选择的。
具体说明:
我们将神经元的输入 x 乘上一个服从伯努利分布的 m 。而该伯努利分布又是依赖于 x 的:
其中, X~N(0,1),那么 φ(x) 就是标准正态分布的累积分布函数。这么做的原因是因为神经元的输入 x 往往遵循正态分布,尤其是深度网络中普遍存在Batch Normalization的情况下。当x减小时, φ(x) 的值也会减小,此时x被“丢弃”的可能性更高。所以说这是随机依赖于输入的方式。
现在,给出GELU函数的形式:
其中 φ(x) 是上文提到的标准正态分布的累积分布函数。因为这个函数没有解析解,所以要用近似函数来表示。
图像
导数
所以,GELU的优点就是在ReLU上增加随机因素,x越小越容易被mask掉。
3.9 Transformer的非线性来自于哪里?
FFN的gelu激活函数和self-attention,注意self-attention是非线性的(因为有相乘和softmax)。