为什么使用CNN?
如果只是简单的用全连接层做模型的话,那么它的参数会巨大
随着参数的增加,我们可以增加他的弹性/能力,但也增加了过拟合的风险
但实际上,做图像识别是不需要这么多参数的,所以从两个方面考虑简化:
(1)Receptive field
如同人类识别鸟是通过鸟的特征,而不是整张图片一样,一个neuron也并不需要观察整个图片,而只用观察到鸟的pattern。patten在的地方,就是receptive filed。
把这一块333的receptive field,变成一个27维列向量,传入Neural
当然,不同的Neural可以守卫任意receptive field。receptive field也可以只包含一个channel,但是CNN一般不这样搞。当然receptive field也不一定非要正方形
Receptive field存在的意义是抓pattern,所以,对于不同的目标,可以自己设计receptive field 的size,3×3,5×5都可以。size的形状也可以自己设定。
虽然receptive field size可以设置的比较小,但是它能学习到比较大的pattern
我们是希望两个receptive field是有重叠的,如果pattern正好出现在两个感受野交界上面。
如果stride后超过边界了怎么办?做padding,也就是补零
特征不会说固定出现在一个地方,比如鸟嘴不会总在左上方出现,而是有可能出现在图片的中央、右上方等,但位置的改变没有改变本质,鸟嘴还是鸟嘴。如何利用这个特性简化模型呢?
不同的neuron计算时共享相同的参数,但是由于input不同,所以output也会不同。负责同一个receptive field 的neuron不会共享参数。
假设一共有64组不同的参数,(实际上,一个filter就是其中的一组参数),对于同一个 receptive field,64个filter都会参与计算。而到了下一个receptive field, 还是这64组参数作为w ,与作为x 的input计算产生output。
这时,一个CNN初步成型了。
参考
从上面我们可以知道,原图像在经过filter卷积之后,变小了,从(8,8)变成了(6,6)。假设我们再卷一次,那大小就变成了(4,4)了。
这样有啥问题呢?
主要有两个问题:
每次卷积,图像都缩小,这样卷不了几次就没了;
相比于图片中间的点,图片边缘的点在卷积中被计算的次数很少。这样的话,边缘的信息就易于丢失。
为了解决这个问题,我们可以采用padding的方法。我们每次卷积前,先给图片周围都补一圈空白,让卷积之后图片跟原来一样大,同时,原来的边缘也被计算了更多次。通过填充的方法,当卷积核扫描输入数据时,它能延伸到边缘以外的伪像素,从而使输出和输入size相同。
比如,我们把(8,8)的图片给补成(10,10),那么经过(3,3)的filter之后,就是(8,8),没有变。
我们把上面这种“让卷积之后的大小不变”的padding方式,称为 “Same” 方式,(进行填充,允许卷积核超出原始图像边界,并使得卷积后结果的大小与原来的一致)
把不经过任何填白的,称为 “Valid”方式。这个是我们在使用一些框架的时候,需要设置的超参数。(不进行任何处理,只使用原始图像,不允许卷积核超出原始图像边界)
比如max pooling, 不会学习任何参数,只是减少计算量的手段
pooling的主要作用就是减少运算量
这个pooling,是为了提取一定区域的主要特征,并减少参数数量,防止模型过拟合。
比如下面的MaxPooling,采用了一个2×2的窗口,并取stride=2:
除了MaxPooling,还有AveragePooling,顾名思义就是取那个区域的平均值。
QA1:pooling层的作用:
池化层的一般作用是对特征图进行下采样,它本身没有参数权重,计算也简单,但它可达到降维特征(将低层次组合为高层次的特征)、突显特征、减少参数量、减少计算量、增加非线性、防止过拟合及提升模型泛化能力等作用
QA2:pooling层的反向传播:
池化层的前向传播我们都比较好理解,但是其是如何参与反向传播的呢?
池化层在反向传播时,它是不可导的,因为它是对特征图进行下采样会导致特征图变小,比如一个2x2的池化,在L+1层输出的特征图是16个神经元,那么对应L层就会有64个神经元,两层之间经过池化操作后,特征图尺寸改变,无法一一对应,这使得梯度无法按位传播。那么如何解决池化层不可导但却要参与反向传播呢?
在反向传播时,梯度是按位传播的,那么,一个解决方法,就是如何构造按位的问题,但一定要遵守传播梯度总和保持不变的原则。
对于平均池化,其前向传播是取某特征区域的平均值进行输出,这个区域的每一个神经元都是有参与前向传播了的,因此,在反向传播时,框架需要将梯度平均分配给每一个神经元再进行反向传播,相关的操作示意如下图所示。
对于最大池化,其前向传播是取某特征区域的最大值进行输出,这个区域仅有最大值神经元参与了前向传播,因此,在反向传播时,框架仅需要将该区域的梯度直接分配到最大值神经元即可,其他神经元的梯度被分配为0且是被舍弃不参与反向传播的,但如何确认最大值神经元,这个还得框架在进行前向传播时记录下最大值神经元的Max ID位置,这是最大池化与平均池化差异的地方,相关的操作示意如下图所示。
其中,上述表格中,前向传播时,每个单元格表示特征图神经元值,而在反向传播时,每个单元格表示的是分配给对应神经元的梯度值。
针对于全连接层:
参数个数: FC前层 × FC后层
计算量/乘法计算次数/times : FC前层 × FC后层
感受野( R e c e p t i v e Receptive Receptive F i e l d Field Field)的定义是卷积神经网络每一层输出的特征图( f e a t u r e feature feature m a p map map)上的像素点在原始输入图片上映射的区域大小。再通俗点的解释是,特征图上的一个点对应原始输入图片上的区域,如下图所示。
感受野的作用
(1)一般 t a s k task task 要求感受野越大越好,如图像分类中最后卷积层的感受野要大于输入图像,网络深度越深感受野越大性能越好;
(2)密集预测 t a s k task task要求输出像素的感受野足够的大,确保做出决策时没有忽略重要信息,一般也是越深越好;
(3)目标检测 t a s k task task中设置 a n c h o r anchor anchor要严格对应感受野, a n c h o r anchor anchor太大或偏离感受野都会严重影响检测性能。
感受野计算:
在计算感受野时有下面几点需要说明:
(1)第一层卷积层的输出特征图像素的感受野的大小等于卷积核的大小。
(2)深层卷积层的感受野大小和它之前所有层的滤波器大小和步长有关系。
(3)计算感受野大小时,忽略了图像边缘的影响,即不考虑padding的大小。
下面给出计算感受野大小的计算公式:
R F l + 1 = ( R F l − 1 ) ∗ s t r i d e + k e r n e l RF_{l+1} = (RF_{l}-1)*stride + kernel RFl+1=(RFl−1)∗stride+kernel
其中 R F l + 1 RF_{l+1} RFl+1为当前特征图对应的感受野的大小,也就是要计算的目标感受野, R F l RF_{l} RFl为上一层特征图对应的感受野大小, f l + 1 f_{l+1} fl+1为当前卷积层卷积核的大小,累乘项 s t r i d e s strides strides表示当前卷积层之前所有卷积层的步长乘积。
举一个例子,7 * 7的输入图经过三层卷积核为3 * 3的卷积操作后得到 O u t 3 Out3 Out3的感受野为7 * 7,也就是 O u t 3 Out3 Out3中的值是由 I n p u t Input Input所有区域的值经过卷积计算得到,其中卷积核( f i l t e r filter filter)的步长( s t r i d e stride stride)为1、 p a d d i n g padding padding为0,,如下图所示:
以上面举的 s a m p l e sample sample为例:
O u t 1 Out1 Out1层由于是第一层卷积输出,即其感受野等于其卷积核的大小,即第一层卷积层输出的特征图的感受野为3, R F 1 RF1 RF1=3;
O u t 2 Out2 Out2层的感受野 R F 2 RF2 RF2 = 3 + (3 - 1) * 1 = 5,即第二层卷积层输出的特征图的感受野为5;
O u t 3 Out3 Out3层的感受野 R F 3 RF3 RF3 = 3 + (5 - 1) * 1 = 7,即第三层卷积层输出的特征图的感受野为7;
记得之前说过,CNN限制了模型的弹性,具体如下,越内部就越限制弹性
FC是自己决定是看全局还是局部范围(将一些weight设置为0),而加了感受野的概念之后就只能看一个小范围,所以model弹性变小
所以,CNN的model bias比较大。但对CV任务不是问题
Filter:
另一种理解CNN的方式是直接从filter的角度看
那么我们先给 定不同的Filter,每一个filter都会抓图片里面的pattern(33channel)
以黑白图片为例子
filter看到image里面出现什么东西的时候他的值最大(这里出现对角线1的时候最大)
如果说我们设置64个filter,那就会得到一个channel为64的 Image,每一个矩阵就是相应的feature map
我们再根据Image-1进行Convolution,构造3364的filter
那么33是否太小而无法看到更大范围的pattern呢??其实根本不会,在第二层33我们看见的其实在原图是5*5,根据网络的不断叠加,我们会越看越大。
其实现在回过头来看,CNN跟我们之前学习的神经网络,也没有很大的差别。传统的神经网络,其实就是多个FC层叠加起来。
CNN,无非就是把FC改成了CONV和POOL,就是把传统的由一个个神经元组成的layer,变成了由filters组成的layer。
那么,为什么要这样变?有什么好处?
具体说来有两点:
我们对比一下传统神经网络的层和由filters构成的CONV层:
假设我们的图像是8×8大小,也就是64个像素,假设我们用一个有9个单元的全连接层:
那这一层我们需要多少个参数呢?需要 64×9 = 576个参数(先不考虑偏置项b)。因为每一个链接都需要一个权重w。
那我们看看 同样有9个单元的filter是怎么样的:
其实不用看就知道,有几个单元就几个参数,所以总共就9个参数!
(1)因为,对于不同的区域,我们都共享同一个filter,因此就共享这同一组参数。这也是有道理的,通过前面的讲解我们知道,filter是用来检测特征的,那一个特征一般情况下很可能在不止一个地方出现,比如“竖直边界”,就可能在一幅图中多出出现,那么 我们共享同一个filter不仅是合理的,而且是应该这么做的。
(2)由此可见,参数共享机制,让我们的网络的参数数量大大地减少。这样,我们可以用较少的参数,训练出更加好的模型,典型的事半功倍,而且可以有效地 避免过拟合。
(3)同样,由于filter的参数共享,即使图片进行了一定的平移操作,我们照样可以识别出特征,这叫做 “平移不变性”。因此,模型就更加稳健了。
补充:
1. 在欧几里得几何中,平移是一种几何变换,表示把一幅图像或一个空间中的每一个点在相同方向移动相同距离。比如对图像分类任务来说,图像中的目标不管被移动到图片的哪个位置,得到的结果(标签)应该是相同的,这就是卷积神经网络中的平移不变性。
2. 池化:比如最大池化,它返回感受野中的最大值,如果最大值被移动了,但是仍然在这个感受野中,那么池化层也仍然会输出相同的最大值。这就有点平移不变的意思了。
所以这两种操作共同提供了一些平移不变性,即使图像被平移,卷积保证仍然能检测到它的特征,池化则尽可能地保持一致的表达。
由卷积的操作可知,输出图像中的任何一个单元,只跟输入图像的一部分有关系:
而传统神经网络中,由于都是全连接,所以输出的任何一个单元,都要受输入的所有的单元的影响。这样无形中会对图像的识别效果大打折扣。比较,每一个区域都有自己的专属特征,我们不希望它受到其他区域的影响。
以下内容部分参考李宏毅笔记
基于RNN(LSTM)的序列模型来说,计算每个cell的输出无法进行并行化。而且单向的RNN无法很好的利用全局的信息。其中下面中的a和b均为向量。
CNN是可以平行化的,不需要第一个filter计算完毕才计算第二个filter,可以同时计算所有的filter。但是一个缺点是要叠许多层才可以看到长期的信息,如果第一个filter就需要长时间的信息那么是做不到的,所以有了自注意力。
self-attention取代RNN:
自注意力模型输入:
如下图所示,左侧的变长的输入序列即是自注意力模型的输入数据,注意这个向量序列的长度不是固定的。
因为要建立输入向量序列的长依赖关系,所以模型要考虑整个向量序列的信息。如下图所示,Self-Attention的输出序列长度是和输入序列的长度一样的,对应的输出向量考虑了整个输入序列的信息。然后将输出向量输入到Fully-Connected网络中,做后续处理。
当然Self-Attention可以和Fully-Connected交替使用多次以提高网络的性能,如下图所示。
上边我们提到,Self-Attention可以使用多次,那么输入可能是整个网络的输入,也可能是前一个隐藏层的输出,这里我们使用[a1,a2,a3,a4]来表示输入,使用b1表示输出为例。
首先Self-Attention会计算a1分别与[a1,a2,a3,a4]的相关性[α1,1,α1,2,α1,3,α1,4]。相关性表示了输入[a1,a2,a3,a4]对a1是哪一个label的影响大小。而相关性α的计算方式共有Dot-product和Additive两种。
Dot-product:
在Dot-product中,输入的两个向量,左边的向量乘上矩阵Wq得到矩阵q,右边的向量乘上矩阵Wk得到矩阵k,然后将矩阵q和矩阵k对应元素相乘并求和得到相关性α。
Additive:
在Additive中同样也是先将两个输入向量与矩阵Wq和矩阵Wk相乘得到矩阵q和k。然后将矩阵q和k串起来输入到一个激活函数(tanh)中,在经过Transform得到相关性α。
为了提高模型能力,自注意力模型经常采用查询-键-值(Query-Key-Value,QKV)模型,其计算过程如下所示:
由上边计算过程可知,每个输入a都会产生q、k和v三个矩阵。这里还是以[a1,a2,a3,a4]为输入为例:
Self-Attention有一个使用非常广泛的的进阶版Multi-head Self-Attention,具体使用多少个head,是一个需要我们自己调节的超参数(hyper parameter)。
在后续的计算中,我们只将属于相同相关性的矩阵进行运算,如下图所示:
通过前边的了解,可以发现对于每一个input是出现在sequence的最前面,还是最后面这样的位置信息,Self-Attention是无法获取到的。这样子可能会出现一些问题,比如在做词性标记(POS tagging)的时候,像动词一般不会出现在句首的位置这样的位置信息还是非常重要的。
我们可以使用positional encoding的技术,将位置信息加入到Self-Attention中。
如上图所示,我们可以为每个位置设定一个专属的positional vector,用ei表示,上标i代表位置。我们先将ei和ai相加,然后再进行后续的计算就可以了。ei向量既可以人工设置,也可以通过某些function或model来生成,具体可以查阅相关论文。
self-attention比较flexible,比较flexible的model比较需要更多的data,如果data不够就有可能过拟合。而小的model,比较有限制的model适合在data少的时候,不容易过拟合
self-attention的弹性比较大,所以需要更多的训练资料,训练资料少的时候就会overfitting。而CNN的弹性比较小,训练资料比较少的时候效果比较好,但是训练资料多的时候无法从更大量的训练资料得到好处
Transformer是一个sequence to sequence(seq2seq)的模型,输入一个序列,输出一个序列。这两个序列的长度、关系是由模型自己确定的。相关场景包括:语音识别(一段语音转换成的文字的长度是由模型决定的)、机器翻译(翻译后的语句的长度是由机器决定的)、语音翻译(直接对语音进行翻译)、聊天机器人(输入是语句、输出也是语句)。还有很多看起来和seq2seq完全没关系的问题也可以用seq2seq硬做。
Seq2seq模型包括一个encoder和decoder
Encoder:
Decoder:
Decoder的很常见的一种是autoregressive(AT)。以语音识别为例来介绍AT。Decoder的输入是encoder的输出向量(但不完全是,看了后面就会知道原因)。Decoder的输入中会有一个标记开始的token。在中文语音识别的模型中,Decoder的输出就是一个个的汉字。那么,每个输出位可能的输出是所有汉字(不是真正意义上的所有汉字,而是训练中的用到的汉字)。对于每个输出位,可以做一个softmax,来决定这个输出位到底输出什么汉字
有趣的部分是,当decoder决定在第一个字的位置输出“机”后,这个“机”会被当成decoder的输入,然后decoder输出“器”;同样地,“器”也被当成decoder的输入。这样进行下去,逐渐输出“机器学习”四个字。也就是说,这种decoder(具体来说,AT)的输入是它在上一个时间点的输出。那么如果decoder的上一个输出是错的该怎么办呢?即便decoder努力输出正确的结果,但也可能会造成连环错误:从一个字错了之后,后面的结果接连出错。我们暂时不考虑这个问题。
(下面一排token都是onehot编码)
Encoder和decoder的构成对比:
如果把灰色的部分遮起来,其实decoder和encoder的构成没有太大差别。细小的差别在于,decoder中的multi-head attention前面加了一个“masked”。这一部分涉及到self attention的知识,这里只是简单提一下。
对于self attention,输出向量的每一位和输入向量的每一位是有关系的。即b1与a1~a4都有关系。
而masked self attention中,b1只与a1有关系,b2只与a1a2有关系,b3只与a1a3有关系…
具体来说:原有的:
masked:
那么为什么在decoder里面要用这种masked的形式呢?其实这和decoder的特征很符合。在decoder中,每一位的输出会被当成输入用来产生下一个输出,也就是说,decoder的输入是随着输出的过程一个一个产生的,而不是一下子就有一个完整的输入送给decoder。所以,比如,计算b2的时候,此时还没有b3和b4,所以当然只能根据a1~a2来计算b2。
这里还有一个问题没有提及,就是:到底模型要产生多长的输出?当模型产生“习”之后,就应该停止了,但模型如果不知道该停了,也许会继续把“习”当成输入继续产生输出。
怎么办?我们可以在每一位的输出列表中加一个stop token。每一位的输出列表是汉字列表,因此要在汉字组成的列表中加一个表示结束的stop token:
与AT相对的是NAT(non-autoregressive)。NAT的结果一般比AT差。
NAT的好处是平行化,因为AT的decoder是逐个产生的输出,如果你句子长度为100,就要做100次decoder。所以在速度上,NAT更快
这一部分从decoder的训练开始。训练的过程如下:
训练的时候,对于输出的每一个字,我们都给它一个独热向量,希望它输出的各汉字的分布和独热向量越接近越好。其实,对于输出的每一位,相当于在做分类问题。如果汉字列表中有4000个汉字,那么每一个汉字的输出过程就都是一个4000类的分类问题 。这里“机器学习”四个字相当于是做了4个4000类的分类问题。
虽然decoder的输入是随着输出的产生而逐渐产生的,但在训练的时候我们会直接一次性告诉decoder正确答案:
我们希望机器可以在有begin的情况下输出“机”,在有“begin”和“机”的情况下输出“器”……在有“begin机器学习”的情况下输出“end”。这个叫做teacher foring:把ground truth(真实值)作为输入。
接下来讲一些关于训练transformer(或者不一定是transformer,也可以是其他的seq2seq模型)的小技巧。
Copy Mechanism:
在聊天机器人中,可以采用copy mechanism:
在第一段对话中,模型应该回答“库洛洛你好”,但是库洛洛对模型来说是很奇怪的词汇,也许训练数据中根本没有出现过这个词。这是,模型其实没有必要真的去生成“库洛洛”,而是可以学会在见到“我是…”这样的句式时,原封不动地输出“我是”后面的内容。这是一种从输入内容进行copy的方式。另一种情况是,模型遇到了一些不懂的内容。“不能使用念能力”是模型没有见过的内容,那么这时机器可以直接把这部分语句复制过来直接输出,而不是试图去产生这个语句。
另一种情况是做摘要的时候:
摘要本身不需要产生新的词汇,而是直接从文章里选词汇就可以。当然,真实生活中,我们可能会用到一些原文中没有的词来更好的概括内容,但这是很高级的能力,在这里我们不涉及。
Guided Attention:
模型可能会犯一些低级错误。以TTS(语音合成,text to speech)为例,如果让模型重复念一个词很多次,它的读音可能会不一样。比如,让某个模型念“发财”4遍,模型念的这4遍“发财”是有区别的;只念一遍“发财”时,甚至不能完整念出“发财”二字。苹果的Siri、papago翻译都有这样的问题。这样的例子不多,但确实是存在的。
要求机器做attention的时候是有固定方式的。与此问题相关的训练技巧是强迫模型的输出的顺序和输入的顺序之间是保持单调的。比如,在语音识别,语音合成的过程中,模型必须从单调地左向右处理输入(关键词:monotonic attention, location-aware attention):
beam search(集束搜索):
假设世界上只有两种token:A和B。模型每次生成A或B的抉择过程可以用一棵树来表示。每次输出时,模型都用一个分数来决定到底输出什么。图中的红色路径是贪心的结果,其实绿色路径是最好的选择。如果在刚开始选择了输出A,结果可能会错过更好的结果。一种简单但幼稚不可行的方案是暴力搜索这棵树来发现最好的路径,因为计算量大到无法想象:以中文语音识别为例,如果汉字列表中有4000个字,那么树的每个节点的分叉就有4000个,这是不可能进行暴力搜索的。所以要用到beam search的方法。这个方法具体是怎么进行的,这里不提及。这个方法有时候有用,有时候没用(至少两种情况都有不少大佬的论文来证明)。对于那些有确定唯一答案的任务,比如语音识别,集束搜索很有用;而对于需要发挥一些创造力的任务,比如,根据上文续写下文,集束搜索的表现不好。
这里提到一个很神奇的现象。在进行语音合成的测试时,需要加入一些随机的噪声。这是很违反常理的,我们往往在训练的时候使用噪声来让模型可以接触到更多的可能性,让模型更强健。但在语音合成的技术中,在测试的时候也要加入一些噪声,这样反而合成的语音质量更好。
Optimizing Evaluation Metric:
有一种评估标准叫blue score:把模型产生的句子和真实的句子进行比较。但是问题在于,我们训练的时候并不是这样做的。对于识别“机器学习”这四个字的语音识别任务来说,训练的过程实际上是做了4个4000类(4000是假设的汉字总数量)的分类问题,对于每个字的产生,我们用了交叉熵。但是在评估的时候,我们用的是blue score。交叉熵和blue score之间并没有什么直接的联系。那么我们可不可以在训练的时候就用blue score进行优化呢?不行,因为对两个句子之间计算的blue score是不可微分的。那么怎么办呢?一个方法是。遇到用optimization无法解决的问题时,采用强化学习硬训练一次就行了。即便有人成功过,这依然是一个很难的方法。
schedule sampling:
训练的过程中,decoder可以看到正确答案,但在测试的时候,decoder只能看到自己的输出,因此会出现一步错步步错的情况,这种情况被称为exposure bias。解决办法是训练的过程中,在给decoder展示的ground truth中故意加入一些错误,decoder反而会学得更好。
但是这一招会伤害到transformer平行化的能力
论文第一张图
深度神经网络好在可以加很多层把网络变得特别深,然后不同程度的层会得到不同等级的feature,比如低级的视觉特征或者是高级的语义特征
提出问题:随着网络越来越深,梯度就会出现爆炸或者消失
深入讲述了深度增加了之后精度也会变差
考虑一个比较浅一点的网络和他对应的比较深的版本(在浅的网络中再多加一些层进去),如果浅的网络效果还不错的话,深的网络是不应该变差的:深的网络新加的那些层,总是可以把这些层学习的变成一个identity mapping(恒等映射)(输入是x,输出也是x,等价于可以把一些权重学成比如说简单的n分之一,是的输入和输出是一一对应的),但是实际情况是,虽然理论上权重是可以学习成这样,但是实际上做不到:假设让SGD去优化,深层学到一个跟那些浅层网络精度比较好的一样的结果,上面的层变成identity(相对于浅层神经网络,深层神经网络中多加的那些层全部变成identity),这样的话精度不应该会变差,应该是跟浅层神经网络是一样的,但是实际上SGD找不到这种最优解
这篇文章提出显式地构造出一个identity mapping,使得深层的神经网络不会变的比相对较浅的神经网络更差,它将其称为deep residual learning framework
要学的东西叫做H(x),假设现在已经有了一个浅的神经网络,他的输出是x,然后要在这个浅的神经网络上面再新加一些层,让它变得更深。新加的那些层不要直接去学H(x),而是应该去学H(x)-x,x是原始的浅层神经网络已经学到的一些东西,新加的层不要重新去学习,而是去学习学到的东西和真实的东西之间的残差,最后整个神经网络的输出等价于浅层神经网络的输出x和新加的神经网络学习残差的输出之和,将优化目标从H(x)转变成为了H(x)-x
上图中最下面的红色方框表示所要学习的H(x)
蓝色方框表示原始的浅层神经网络
红色阴影方框表示新加的层
o表示最终整个神经网络的输出
这样的好处是:只是加了一个东西进来,没有任何可以学的参数,不会增加任何的模型复杂度,也不会使计算变得更加复杂,而且这个网络跟之前一样,也是可以训练的,没有任何改变
非常深的residual nets非常容易优化,但是如果不添加残差连接的话,效果就会很差。越深的网络,精度就越高
introduction是摘要的扩充版本,也是对整个工作比较完整的描述
一篇文章要成为经典,不见得一定要提出原创性的东西,很可能就是把之前的一些东西很巧妙的放在一起,能解决一个现在大家比较关心难的问题
残差连接如何处理输入和输出的形状是不同的情况:
implementation中讲了实验的一些细节
首先阐述了ImageNet
描述了plain networks
输入输出形状不一样的时候怎样做残差连接
对比以上三种方案
怎样构建更深的ResNet
如果要做50或者50层以上的,会引入bottleneck design
ImageNet标号的错误率本来挺高的,估计有1%
CIFAR-10是一个很小的数据集,跑起来相对来说比较容易,32*32,五万个样本,10类的数据集
在整个残差连接,如果后面新加上的层不能让模型变得更好的时候,因为有残差连接的存在,新加的那些层应该是不会学到任何东西,应该都是靠近0的,这样就等价于就算是训练了1000层的ResNet,但是可能就前100层有用,后面的900层基本上因为没有什么东西可以学的,基本上就不会动了
这篇文章没有结论
mAP:目标检测上最常见的一个精度,锚框的平均精度,越高越好
为什么ResNet训练起来比较快?
一方面是因为梯度上保持的比较好,新加一些层的话,加的层越多,梯度的乘法就越多,因为梯度比较小,一般是在0附近的高斯分布,所以就会导致在很深的时候就会比较小(梯度消失)。虽然batch normalization或者其他东西能够对这种状况进行改善,但是实际上相对来说还是比较小,但是如果加了一个ResNet的话,它的好处就是在原有的基础上加上了浅层网络的梯度,深层的网络梯度很小没有关系,浅层网络可以进行训练,变成了加法,一个小的数加上一个大的数,相对来说梯度还是会比较大的。也就是说,不管后面新加的层数有多少,前面浅层网络的梯度始终是有用的,这就是从误差反向传播的角度来解释为什么训练的比较快
在CIFAR上面加到了1000层以上,没有做任何特别的regularization,然后效果很好,overfitting有一点点但是不大。SGD收敛是没有意义的,SGD的收敛就是训练不动了,收敛是最好收敛在比较好的地方。做深的时候,用简单的机器训练根本就跑不动,根本就不会得到比较好的结果,所以只看收敛的话意义不大,但是在加了残差连接的情况下,因为梯度比较大,所以就没那么容易收敛,所以导致一直能够往前(SGD的精髓就是能够一直能跑的动,如果哪一天跑不动了,梯度没了就完了,就会卡在一个地方出不去了,所以它的精髓就在于需要梯度够大,要一直能够跑,因为有噪音的存在,所以慢慢的他总是会收敛的,所以只要保证梯度一直够大,其实到最后的结果就会比较好)
为什么ResNet在CIFAR-10那么小的数据集上他的过拟合不那么明显?
open question
虽然模型很深,参数很多,但是因为模型是这么构造的,所以使得他内在的模型复杂度其实不是很高,也就是说,很有可能加了残差链接之后,使得模型的复杂度降低了,一旦模型的复杂度降低了,其实过拟合就没那么严重了
这篇文章的residual和gradient boosting是不一样的
1. gradient boosting是在标号上做residual
2. 这篇文章是在feature维度上
随着我们设计越来越深的网络,深刻理解“新添加的层如何提升神经网络的性能”变得至关重要。更重要的是设计网络的能力,在ResNet这种网络中,添加层会使网络更具表现力
虽然模型更大,但是可能学偏了,所谓的模型偏差。如果更大的模型里面包含小模型,我学到的模型就会严格的比前面更大,所以至少不会变差
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Residual(nn.Module):
def __init__(self, input_channels, num_channels,
use_1x1conv=False, strides=1):
super().__init__()
# 第一个卷积层
self.conv1 = nn.Conv2d(input_channels, num_channels,
kernel_size=3, padding=1, stride=strides)
# 第二个卷积层
self.conv2 = nn.Conv2d(num_channels, num_channels,
kernel_size=3, padding=1)
# 如果使用1 x 1卷积以使得输入变换成需要的形状
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
# 对应第一个卷积层的批量规范化层
self.bn1 = nn.BatchNorm2d(num_channels)
# 对应第二个卷积层的批量规范化层
self.bn2 = nn.BatchNorm2d(num_channels)
def forward(self, X):
# 第一层:卷积 -> 规范化 -> relu激活
Y = F.relu(self.bn1(self.conv1(X)))
# 第二层:卷积 -> 规范化
Y = self.bn2(self.conv2(Y))
# 如果要让输入变换成需要的形状
if self.conv3:
# 对X使用1 x 1卷积,以使输出成为需要的形状
X = self.conv3(X)
# 嵌套模型的实现,即对上一次训练后的模型进行嵌套
Y += X
# relu激活并输出
return F.relu(Y)
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
def resnet_block(input_channels, num_channels, num_residuals,
first_block=False):
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(Residual(input_channels, num_channels,
use_1x1conv=True, strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))
net = nn.Sequential(b1, b2, b3, b4, b5,
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten(), nn.Linear(512, 10))
进一步,在下图可以多个模板中同时存在conv-block 和 identity-block两种模式。本质区别就是有没有下采样downsample和通道转换
如果g(x)很难训练或者训练没有什么好处的话他就会在梯度反传拿不到梯度,所以他的权重就不会被更新了
从梯度的角度: 梯度连乘变加法,最下面的层(靠近数据层)也能拿到更大的梯度,不管网络多深,我都能拿到比较大的梯度做高效的更新
1.恒等映射 2. 梯度消失,梯度连乘变加法3.梯度相关性