CNN14. Residual Networks (ResNets)
梯度丢失和梯度爆炸统称为梯度不稳定。它们产生的原因是类似的。为了说明梯度丢失是如何产生的,我们将把问题简化,以链式法则为例来进行说明。
让我们考虑下面这个简单的深度神经网络,它的每一层都只包含一个神经元,一共有三个隐藏层:
这个等式也反映了反向传播的工作模式:它从输出层开始,逐层计算偏导数,直到输入层为止。然后,利用这些计算出来的偏导数,更新对应的权重和偏置,从而达到反向传播的目的。
但是,我们发现,随着层数的加深,梯度的计算公式会依赖于越来越多每一层的参数,这些参数的影响层层累加,最终就导致了梯度爆炸与梯度消失。
Sigmoid函数常常会引发梯度不稳定问题,所以我们以此为研究对象。 它的图像如下:
它的导函数图像如下:
该函数的导数最大值为0.25,且当取值的绝对值变大时,输出会变小。
由于我们初始化权重值的时候一般从标准正态分布中采样,所以权重w的绝对值通常小于1,因此我们可以得到:
在深度网络中,为了计算初始层的梯度,我们会累乘多个
再由于公式
最终计算结果将会呈指数级变小,这也就是梯度丢失产生的原因。
梯度爆炸产生的原因和梯度丢失正好相反。当我们选取的权重值较大时,
将大于1。当累乘这些项的时候,计算结果将呈指数级增长。
ResNet最根本的动机就是所谓的“退化”问题,即当模型的层次加深时,错误率却提高了。
自AlexNet以来,state-of-the-art的CNN结构都在不断地变深。AlexNet只有5个,而到了VGG和GoogLeNet已经有有19个和22个卷积层。
然而,我们不能通过简单地叠加层的方式来增加网络的深度。梯度消失问题的存在,使深度网络的训练变得相当困难。“梯度消失”问题指的是即当梯度在被反向传播到前面的层时,重复的相乘可能会使梯度变得无限小。因此,随着网络深度的不断增加,其性能会逐渐趋于饱和,甚至还会开始下降。
在ResNet出现之前,研究人员们发现了几个用于处理梯度消失问题的方法,比如,在中间层添加辅助损失(auxiliary loss)作为额外的监督。但没有一种方法能够一次性彻底解决这一问题。 最后,有人提出了resNet,大大改善了这一情况。
一个残差块(residual block)
ResNet的基本思想是引入了能够跳过一层或多层的“shortcut connection”,如上图所示。图中“弯弯的弧线“就是所谓的”shortcut connection“,也就是identity mapping。
resNet的作者认为:
另外,resNet还采用了reLU方式激活,reLU函数在取值变大时不会发生梯度变小的情况,所以也缓解了梯度消失。
所以说,ResNet的优越之处有两个:identity mapping和reLU激活
这两种结构分别取自ResNet34(左图)和ResNet50/101/152(右图)。
一般称整个结构为一个”building block“,而右图又称为”bottleneck design”。
右图的目的就是为了降低参数的数目:第一个1x1的卷积把256维channel降到64维,然后在最后通过1x1卷积恢复。
使用bottleneck的结构整体上用的参数数目为1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632
。然而不使用bottleneck的话就是两个3x3x256的卷积,参数数目: 3x3x256x256x2 = 1179648
,差了16.94倍。
对于常规ResNet,可以用于34层或者更少的网络中。而对于Bottleneck Design的ResNet通常用于更深的如101这样的网络中,目的是减少计算和参数量(实用目的)。
有人会问,如果F(x)和x的channel个数不同怎么办,因为F(x)和x是按照channel维度相加的,channel不同怎么相加呢? 针对channel个数是否相同,要分成两种情况考虑。我们先看下图,有实线和虚线两种连接方式:
其中W是卷积操作,用来调整x的channel维度的
上面一共提出了5种深度的ResNet,分别是18,34,50,101和152。resNet-101仅仅指卷积或者全连接层加起来有101层,而激活层或者Pooling层并没有计算在内,其它resNet都以此类推。
所有的网络都分成5部分,分别是:conv1,conv2_x,conv3_x,conv4_x。。
这里我们关注50-layer和101-layer这两列,可以发现,它们唯一的不同在于conv4_x——ResNet50有6个block,而ResNet101有23个block。这里相差了17个block,也就是17 x 3 = 51层。
参考How To Install Java with Apt-Get on Debian 8
cmd执行命令
uname -a
我得知我的系统是ubuntu,因此安装采用ubuntu的教程
参考Inception in TensorFlow 按照参考网址里的Getting Started做即可,需要事先安装bazel,而上文的1-3就是安装bazel的过程。
由于实验室虚拟机的下载速度太慢,我转而使用CIFAR作为训练数据集
由于时间有限,难度较大,我只是用了网上https://github.com/tensorflow/models/tree/master/official/resnet提供的模型跑了一遍,并记录了结果。尝试过自己修改代码,但发现官方提供的代码框架太大,难以分析。希望日后能补足这个部分......
需要把models的文件路径添加到环境变量,否则可能遇到ImportError: No module named official.resnet.
之类的问题。
export PYTHONPATH="$PYTHONPATH:/path/to/models" # 比如"$PYTHONPATH:/root/Desktop/models"
python cifar10_download_and_extract.py --data_dir ~
python cifar10_main.py --data_dir ~
训练到后期,training准确率到99%以上,由于overfitting,evaluate准确率有92%左右。
training
evaluating
可以看到,测试准确率是略低于训练准确率的。
参考 AttributeError: module 'tensorflow' has no attribute 'data'
Yes, as @blairjordan mentions,
tf.contrib.data
has been upgraded to justtf.data
in TensorFlow v1.4. So you need to make sure you're using v1.4.
原因
新旧版本的接口不同,函数调用不同
解决方案1
更改调用函数的方式 或者 安装新版的tensorflow(v1.4或v1.7)
conda create -n tf1.7 python=3.6
conda install tensorflow-gpu=1.7
为什么解决方案1可行
我最开始有疑惑,安装tensorflow-gpu要求事先安装好相应版本的cudatoolkit和cudnn。正是因为虚拟机预先安装的cuda和cudnn版本不高,我才只能安装低版本的tf。但当我运行conda install tensorflow-gpu=1.7
后,发现conda现在会自动帮你安装相应版本的cuda和cudnn依赖。如下图:
cuda和cudnn都会帮你装好
所以就可以放心地安装高版本的tensorflow了,以后也不用再纠结于cuda和cudnn的安装,只要gpu能支持,就可以顺利安装。
参考2
The arguments accepted by the Dataset.map() transformation have also changed:
dataset.map(..., output_buffer_size=B) is now dataset.map(...).prefetch(B).
I'd check that you have the latest version. In my case, I still need to use the old tf.contrib.data.
解决方案2(未证实)
采用旧的函数调用,比如data.map.prefetch的调用改为data.map 。 这个方法只是一个思路,未证实,因为我已经用解决方案1解决问题。我也不在此深究了。