动手学深度学习笔记(二)

ryluo 2020-06-14 01:29:22
动手学深度学习系列

过拟合欠拟合及解决方案循环神经网络进阶机器翻译及相关技术注意力机制与Seq2seq模型Transformer卷积神经网络基础leNet卷积神经网络进阶

过拟合欠拟合及解决方案

训练误差与泛化误差

模型在训练集上的误差称为训练误差,在任意数据集上的误差叫做泛化误差,一般会使用测试集的误差来近似泛化误差,而机器学习模型主要是降低泛化误差,而不是训练误差,所以这里就可以通过训练误差与测试误差来判断模型是过拟合还是属于欠拟合。如下图所示:

欠拟合:模型无法得到较低的训练误差(模型太简单)

过拟合:训练误差远小于测试数据集上的误差(模型太简单,训练数据太少)

解决办法:权重衰减($L_2$范数),丢弃法(Dropout)


循环神经网络进阶

对于循环神经网络基础没有弄懂的推荐观看同济子豪兄的B站视频,非常的通俗易懂。

LSTM知乎高赞文章

GRU知乎高赞文章

首先需要对RNN的基础部分有深刻的理解(主要是RNN的整体结构,因为后续高级的RNN的基本结构也是一样的),至少要知道RNN的参数是哪些,参数在模型训练的时候怎么更新的,更新的时候可能会出现什么问题,有个大致的了解。在本节的RNN进阶中主要内容有:GRU(门控循环神经网络)、LSTM(长短期记忆网络)、深度循环神经网络以及双向循环神经网络


LSTM

LSTM和GRU主要是用来解决循环神经网络中梯度消失和梯度爆炸问题提出来,并且还具有保留长的历史信息的功能。它们都是基于门控的RNN,而门控可以简单的理解为对本来没有门控的输入每个元素乘上了一个0-1的权重,进行有选择性的忘记和记忆,这样就可以在有限的记忆容量(我们可以认为参数的最大容量)下记住更加重要的信息,而忘记不重要的信息,虽然GRU没有和LSTM一样的遗忘门和输入门,但是它的重置门和更新门也可以起到选择性的忘记与记忆的功能。

首先回顾一下普通的RNN的结构,如下图所示(台大李宏毅的PPT中的内容),从图中可以看出,普通的RNN的输入部分有上一时间步的隐藏状态$h$和当前时间步的输入$x$两部分组成,而输出部分由当前时间步的隐藏状态h’和输出y组成,具体与参数及激活函数的关系可以从图中直接看出,对普通的RNN结构就先放这里,用于对LSTM和GRU的结构进行对比的

Naive RNN


下图是LSTM与普通的RNN结构图的对比,直接放眼望去好像LSTM的结构非常的复杂,但是如果我们把$c^{t-1}$和$c^{t-1}$上下摞起来,以及$c^t$和$h_t$也摞起来,这样看起来结构是不是就一样了呢。也就是说LSTM的隐藏状态与普通RNN隐藏状态不同的是它由两部分组成,分别是$c^t$和$h^t$,前者我们也把他称为长期记忆,后者称为短期记忆,这是对LSTM一个直观上的理解。

LSTM



为什么称为短期记忆和长期记忆呢?

那就得看一下LSTM内部的结构了,如下图是LSTM的内部结构图(这里将课程中的图和李宏毅老师的PPT中的图结合起来了),在这里我们主要看上面的两个图,并且强烈建议在看这篇文章的时候出现公式及变量的地方一定要结合图,找到图中的位置。从上面的介绍我们已经知道了$c^{t}$表示的是长期记忆,而$h^{t}$表示的短期记忆。从图中可以看出LSTM的输入部分首先将上一时间步的短期记忆$h^{t-1}$与当前的时间步的输入$x^t$拼起来然后分别乘以一个权重矩阵再经过激活函数得到$z,z^i,z^f,z^o$这四个部分虽然是通过相同的方式得到的但是由于权重矩阵的不同加上神经网络的强大之处,在训练的过程中就会知道各自的任务是什么,所以这四个部分各司其职。

其中$z^f$被称为遗忘门,它是一个0-1之间的向量,并且大小形状与$c^{t-1}$是一样的,因为它们之间要进行对应元素相乘,那么就是说对于上一时刻得到的长期记忆我们选择性的去忘记(也可以理解为就是选择性的记住了某些东西),我们在想一下遗忘门是怎么来的,它是由短期记忆和当前的输入得到的,也就是说对于长期记忆的记忆与遗忘取决于上一时刻的短期记忆和当前的输入,这么听起来好像挺有道理(就是感觉有道理,但是说不出来的那种),这就是遗忘门。

对于输入门$z^i$,其得到的方法与遗忘门是一样的,并且其值也是在0-1之间,输入门没有像遗忘门一样直接作于长期记忆,而是对$z$进行了对应元素的乘积,$z$也是通过上一时刻的隐藏状态$h^{t-1}$和当前的输入$x^t$通过一个权重再激活的来的(注意:这里的激活函数没有使用sigmoid而是使用tanh,意思是他不是作为门控来使用,而是对输入的信息作用,并且是通过权重矩阵学习之后的信息进行压缩)。而输入门是直接作用于$z$,和它对应元素进行相乘,这个也很好理解,就是说我们输入的信息,尽管是通过权重矩阵学习过的信息还是需要进行筛选,才可以为模型做出相应的贡献。

当前时间步的长期记忆$c^t$,结合左边和右边的图我们可以看出当前时间步的长期记忆是当前时刻信息最丰富的部分,因为他结合了上一时间步经过选择性遗忘而筛选的信息与当前时间步经过筛选的输入信息,总的来说就是过去的信息以及当前的信息都有,让他作为下一时间步的长期信息也是有道理的。

当前的短期记忆$h^t$,它是记录短时间内比较重要的信息,所以它先将当前的长期记忆$c^t$通过tanh进行压缩(注意:他这里使用的是tanh激活函数是对信息压缩,而不是sigmoid对信息进行筛选),然后与输出门$z^o$进行对应元素进行对应元素相乘,得到当前的短期记忆。在生成短期记忆的时候使用输出们进行筛选,也是有道理的,输出门是对当前输入信息学习之后的筛选器,用来决定当前的短期记忆当然是可以理解的。

最后来说一下当前时间步的输出$y^t$,既然是当前时间步的输出,那么直接对当前的短期记忆进行学习即可(前面已经对当前短期记忆进行了说明,到这里应该可以理解了)

LSTM
LSTM


建议:先把机器学习课程中的LSTM相关的东西学完,如果对与普通的RNN理解还不是很透彻的一定要弄明白,最快的方法就是看代码学习

循环神经网络基础推荐视频同济子豪兄的B站视频

推荐学习资料(知乎高赞):LSTM知乎高赞文章


深度循环神经网络

深度循环神经网络与单层的循环神经网络最根本的区别在于,对于单层的循环神经网络,每个时间步的隐含状态$H_t$只传递给下一个时间步,而在深度循环神经网络中,每一个时间步获得的隐含状态不仅要传递给下一个时间步还要输出给当前时间步的下一层(依次传递给更高的层),知道最后一个输出层,如下图所示:


双向循环神经网络

前面介绍的单向循环神经网络的信息是从前往后传,也就是序列后面面的信息决定于其前面的信息,但有时候序列前面的信息也有可能用到序列后面的信息,这时候就需要序列的信息从后往前传,而双向循环神经网络就是结合了从前往后传的信息,以及从后往前穿的信息,如下图所示是一个单个隐层的双向循环神经网络,网络中每个时间步的输出取决于从前往后得到的隐藏状态以及从后往前传的隐藏状态(具体操作的时候可以将两个方向在同一时间步得到的隐藏状态拼起来使用)。


机器翻译及相关技术

机器翻译任务代码实现的全过程总结,这里不涉及代码,而是将代码的思路,对于理解机器翻译任务有帮助。

机器翻译任务代码总结如下

数据预处理

  1. 读取数据,处理数据中的编码问题,并将无效的字符串删除
  2. 分词,分词的目的就是将字符串转换成单词组成的列表。目前有很多现成的分词工具可以直接使用,也可以直接按照空格进行分词(不推荐,因为分词不是很准确)
  3. 建立词典,将单词组成的列表编程单词id组成的列表,这里会得到如下几样东西
    1. 去重后词典,及其中单词对应的索引列表
    2. 还可以得到给定索引找到其对应的单词的列表,以及给定单词得到对应索引的字典。
    3. 原始语料所有词对应的词典索引的列表
  4. 对数据进行padding操作。因为机器翻译模型本质上是一个固定输入长度的Seq2Sqe模型,所以我们需要设置最大的数据长度,如果超出了设定的长度直接把后面的截断,少了的,根据需要进行不同的padding
  5. 制作数据生成器,但是需要注意的是对于翻译任务的数据格式,机器翻译的输入是一段文本序列,输出也是一段文本序列。


Seq2Seq模型的构建

  1. Seq2Seq模型由很多钟,但是整体框架都是基于先编码后解码的框架。也就是先对输入序列使用循环神经网络对他进行编码,编码成一个向量之后,再将编码得到的向量作为一个新的解码循环神经网络的隐藏状态的输入,进行解码,一次输出一个序列的元素,再将模型训练输出的序列元素与真实标签计算损失进行学习。
  2. 词嵌入,一般情况下输入到编码网络中的数据不是一个onehot向量而是经过了编码之后的向量,比如由word2vec技术,让编码后的向量由更加丰富的含义。
  3. 在进行编码和解码的过程中数据都是以时间步展开,也就是(Seq_len,)这种形式的数据进行处理的
  4. 对于编码与解码的循环神经网络,可以通过控制隐藏层的层数及每一层隐藏层神经元的数量来控制模型的复杂度
  5. 编码部分,RNN的用0初始化隐含状态,最后的输出主要是隐藏状态,编码RNN输出的隐含状态认为是其对应的编码向量
  6. 解码器的整体形状与编码器是一样的,只不过解码器的模型的隐藏状态是由编码器的输出的隐藏状态初始化的。


损失函数

  1. 解码器的输出是一个和词典维度相同的向量,其每个值对应与向量索引位置对应词的分数,一般是选择分数最大的那个词作为最终的输出。
  2. 在计算损失函数之前,要把padding去掉,因为padding的部分不参与计算


测试

  1. 解码器在测试的时候需要将模型的输出作为下一个时间步的输入
  2. Beam Search搜索算法。
    1. 假设预测的时候词典的大小为3,内容为a,b,c. beam size为2,解码的时候过程如下
    2. 生成第一个词的时候,选择概率最大的两个词,假设为a,c.那么当前的两个序列就是a和c。
    3. 生成第二个词的时候,将当前序列a和c,分别与此表中的所有词进行组合,得到新的6个序列aa ab ac ca cb cc,计算每个序列的得分,并选择得分最高的2个序列,作为新的当前序列,假如为aa cb
    4. 后面不断重复这个过程,直到遇到结束符或者达到最大长度为止,最终输出得分最高的2个序列。


Seq2seq模型及注意力机制

Seq2Seq模型

我们知道Seq2Seq模型的结构是基于编码器-解码器,可以解决输入和输出序列不等长的问题,例如机器翻译问题。编码器和解码器本质上是两个RNN,其中编码器对输入序列进行分析编码成一个上下文向量(Context vector),解码器利用这个编码器生成的向量根据具体任务来进行解码,得到一个新的序列。

编码器

如下图所示为一个编码器的结构,就是将输入序列$x_1$至$x_4$依次输入到编码器中得到了$h_1$至$h_4$的隐含状态,而最终的上下文向量$c$,可以是编码器最后一个时间步的隐藏状态,也可以是编码器每个时间步得到的隐藏状态进行一个函数映射(就是使用某个度量函数去表示原始序列的信息),这个上下文向量后面会再解码器生成序列中。

preview
图二
#### 解码器 下图是两种比较常见的Seq2Seq模型的结构,两个图的左半部分都是上面所说的编码器部分,而右半部分就是解码器部分了。如图二所示,其直接将编码器的输出作为解码器的初始隐藏状态,然后直接进行解码。图二是直接将编码器得到的上下文向量输入到解码器的每个时间步中,并且每个时间步的上下文向量是相同,换句话说就是解码器每个时间步都使用了相同的上下文向量。这两种情况可能带来的问题是,当需要编码的句子太长的时候,由于上下文向量能够存储信息的容量是有限的,所以可能会导致,信息的丢失,此外,解码器每个时间步的上下文向量都是一个相同的对输入序列的表征,对于上面两种问题,基于注意力机制的Seq2Seq模型给了很好的解决办法。
preview
图二
preview
图三
### Attention机制的Seq2Seq 基于Attention的Seq2Seq模型本质上就是在上述的图三中的解码器部分进行了改进,在解码器的每个时间步上使用不同的上下文向量$c$如下图所示的$c_1,c_2,c_3$,但是对于解码器的初始化一般还是会使用编码器最后时间步的隐藏状态,即图中的$h'_0=c$(此处的c表示的是编码器最后时间步的隐藏状态),如何得到解码器不同时间步不同的上下文向量就是Attention要做的事情了。
preview
Attention机制生成的上下文向量可以自动的去选取与当前时间步输出最有用的信息,用有限的上下文向量的容量去表示当前时间步对输入信息最关注的那部分信息,**最简单的做法就是对编码器输出的所有时间步的隐藏状态进行一个加权平均,不同的权值所对应的隐含状态就是对不同时间步的输入信息关的注程度**,如下图就很清楚的说明了具体是怎么操作的(下图来自《动手学深度学习》第447页)。图中的$a$表示是编码器不同时间步对应的权值,而其权值又决定于编码器该时间步的隐藏状态以及解码器上一个时间步的隐藏状态(这里直接这么说比较绕,很容易看不明白,建议去看课本上的解释)。下面给出一个简单的解释:设解码器当前隐藏状态为$s_{t'} $ 则无注意力的解码器当前的隐藏状态表示为:$s_{t'} = g(y_{t'-1}, c, s_{t'-1})$ 基于注意力的解码器当前的隐藏状态表示为:$s_{t'} = g(y_{t'-1}, c_{t'}, s_{t'-1}) $ 其中: $y_{t'-1}: $解码器上一时间步的输出 $c: $编码器最后时间步(或者之前所有时间步隐藏状态的某种映射)的隐藏状态 $c_{t'}: $解码器在t'时间步通过注意力机制获得的的上下文向量 $s_{t'-1}: $解码器的$t'-1$时间步的隐藏状态
下面两个图是背景变量$c_t'$的生成过程,最后就剩下如何计算$a_{ij}$的值了。这里的a其实$a_{ij}$是注意力打分函数的输出,跟三部分东西有关系,分别是查询项$q(quary)$:解码器上一时间步的隐藏状态$s_{t'-1}$,键项k(key)和值项v(value)都是编码器的隐含状态($h_1, h_2, h_3$)。常见的注意力打分函数有: `加性模型`:$ s_{(x_i,q)}= v^Ttanh(Wx_i+Uq)$ `点积模型`: $s_{(x_i,q)}= x_i^Tq$ `缩放点积模型`:$ s_{(x_i,q)}= \frac{x_i^Tq}{\sqrt{d}}$ `双线性模型`:$ s_{(x_i,q)}= x_i^TWq$
preview

点积模型可视化如下:

preview

最后基于注意力的Seq2Seq模型可以用下图进行表示:

Image Name

参考链接(知乎高赞文章):完全图解RNN、RNN变体、Seq2Seq、Attention机制
参考书籍: 《动手学深度学习》、《神经网络与深度学习》
注意力的具体实现参考代码,也可以更深刻的理解。


Transformer

Self-Attention

Self-Attention是Transfomer中最核心的部分,其框架图如下图所示,主要功能是将一个输入词向量序列$a^1,a^2,a^3,a^4$(由$x^1,x^2,x^3,x^4$编码得到)输出一个新的经过加权(注意力机制的加权)之后的序列$b^1,b^2,b^3,b^4$,与RNN的功能类似但结构却不是与RNN类似的序列结构的,Self-Attention可以实现并行计算。

img


先给出Self-Attention的公式,然后再对公式进行解释:

Q: 查询(query: to match other)

k: 键(key: to be matched)

V: 值(value: information to be extracted)

从公式中可以看出,Attention是将查询与键做矩阵乘法之后经过归一化之后再进行Softmax得到注意力分数,最后于值进行加权。


如下图所示:$q,k,v$ 都是由输入$a^i$($x^i$ embedding得到的向量,后面的输入都是指的$a^i$)乘以一个可学习的权重矩阵$W$得到的(不同的$W$由于神经网络的学习,所以可以各司其职,达到注意力的效果),对于序列中的每个时间步的输入$a^i$都有$q,k,v$三个参数,所以可以将一个序列中所有的$q,k,v$分别拼接起来形成$Q,K,V$三个矩阵,这里拼成矩阵的形式就是为了后面并行化计算的。

img


其中得到了序列中每个元素的三个参数$q,k,v$之后,拿每个query $q$去对每个key $k$做attention,这里的attention的计算公式是两个向量的内积,如下图(左图)中所示,其中除以$\sqrt{d}$是为了归一化,消除向量维度的影响。然后再将计算得到的$a_{i,j}$进行归一化得到$\hat{a}_{i,j}$如下图(右图)中所示,$\hat{a}_{i,j}$表示的是注意力输出的分数。


img img


最后将输出的注意力分数与$v$进行加权平均,得到输出$b^1,b^2,b^3,b^4$


img img


因为Attention是对于序列中每个q分别对每个k做点积,而对于一个序列的q和k都可以写成向量的形式,如下图所示,两个向量点乘最后就得到了注意力矩阵,而这个矩阵的每个元素就是每个query对每个key的注意力分数,所以写成矩阵的形式如下图所示:


img img


img img


Multi-head Attention

多头注意力,本质上是多个Self-Attention,多个Attention可以提取序列中不同的注意力。如下图所示,多头注意力是再对每个输入$a^i$输入多个$q,k,v$,相当于多组$q,j,v$,而每一组之间是没有联系的,通过上面对Self-Attention的介绍我们可以知道,对于一个Self-Attention最终输出一个注意力矩阵$\hat{A}$,那么多头注意力其实就是再最后输出了多个注意力矩阵。如下图所示是一个含有两个head的注意力。

img img


从上面的分析可以知道,多头注意力机制输出了多个Attention矩阵,而每个注意力矩阵与$V$相乘在每个时间步上都会有一个输出,那么多头注意力在每个时间步上就会有多个输出,如下图所示,但是为了方便可以把多个输出拼成一个输出。

img


Positional Encoding

在上面的注意力机制中虽然对两个词向量进行了两两计算,但是却没有用到序列的顺序信息,在Transformer中使用了位置编码来表示序列的顺序信息,具体的做法就是在词向量上加上了一个与其相同形状的位置编码向量,作为新的词向量,如下图所示是一个四维的词向量加位置编码的例子。位置编码向量既可以通过数据学习的方式得到,也可以根据认为的指定规则制定

img


Transformer

img


Encoder

对于编码部分需要注意的是虚线的残差结构,目的是解决深度学习中的退化问题,如果将结构中的细节放大,则如下右边的图所示.

img img


Decoder

如下图是解码器的结构,其中需要注意的是Encoder-Decoder Attention,等于编码器最后一个block输出的$K,V$,$Q$是解码器自己的查询。

img


下图显示了Encoder和decoder一起工作的过程。

img
img


总结

由于Transform设计的非常的精妙,其包含的思想也非常的丰富,所以以上内容只是对Transformer的一个初步的理解,还需要在日后的学习中不断的更新对Transformer设计思想的理解,上面大量的图都是来自下面的两个参考资料,推荐去看原文。

参考资料:

详解Transformer (Attention Is All You Need)

The Illustrated Transformer

台大李宏毅Transformer视频


卷积神经网络基础

卷积核

卷积核是卷积神经网络的核心,而卷积核的形状是一个(输入通道,卷积核高度,卷积核宽度,输出通道数)的二维矩阵。

为什么卷积核的大小与输入通道数有关?

一个卷积核首先需要在维度上与输入特征图的数量相同,然后卷积核的每个维度与特征图的每个维度进行卷积,然后再将所有通道上得到的卷积过后的特征图进行按元素加权平均,这就完成了一个完整的卷积核的卷积操作,如下图通道数为2的卷积核的卷积操作:

img


1x1卷积

通过上面卷积核的大小与输入通道的关系可以知道,1x1的卷积本质上相当于是在通道维度上对每个像素点进行全连接,由于其输出的特性,所以可以实现通道的降维与升维。

img


卷积神经网络进阶

LeNet

LeNet主要分为卷积部分和全连接部分,卷积部分是由5x5的卷积核以及最大池化组成,卷积层用于提取图像中的空间特征,而最大池化层是用来降低卷积层对位置的敏感性。使用的激活函数是Sigmoid。在进行全连接层之前需要将feature map拉伸成一个(batch_size, )大小的二维张量,然后进行Softmax输出。

img


AlexNet

首次证明了学习到的特征可以超越⼿⼯设计的特征,从而⼀举打破计算机视觉研究的前状。

特点:

  1. 8层变换,其中有5层卷积和2层全连接隐藏层,以及1个全连接输出层。
  2. 将sigmoid激活函数改成了更加简单的ReLU激活函数。
  3. 用Dropout来控制全连接层的模型复杂度。
  4. 引入数据增强,如翻转、裁剪和颜色变化,从而进一步扩大数据集来缓解过拟合。
img


VGG

VGG:通过重复使⽤简单的基础块来构建深度模型。

Block:数个相同的填充为1、窗口形状为3×3的卷积层,接上一个步幅为2、窗口形状为2×2的最大池化层。
卷积层保持输入的高和宽不变,而池化层则对其减半。

img


NIN

LeNet、AlexNet和VGG:先以由卷积层构成的模块充分抽取 空间特征,再以由全连接层构成的模块来输出分类结果。
NiN:串联多个由卷积层和“全连接”层构成的小⽹络来构建⼀个深层⽹络。
⽤了输出通道数等于标签类别数的NiN块,然后使⽤全局平均池化层对每个通道中所有元素求平均并直接⽤于分类。

img


GoogleNet

  1. 由Inception基础块组成。
  2. Inception块相当于⼀个有4条线路的⼦⽹络。它通过不同窗口形状的卷积层和最⼤池化层来并⾏抽取信息,并使⽤1×1卷积层减少通道数从而降低模型复杂度。
  3. 可以⾃定义的超参数是每个层的输出通道数,我们以此来控制模型复杂度。
img