前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >残差网络ResNet网络原理及实现

残差网络ResNet网络原理及实现

作者头像
石晓文
发布2018-11-30 11:26:14
2.4K0
发布2018-11-30 11:26:14
举报
文章被收录于专栏:小小挖掘机小小挖掘机

论文地址:https://arxiv.org/pdf/1512.03385.pdf

1、引言-深度网络的退化问题

在深度神经网络训练中,从经验来看,随着网络深度的增加,模型理论上可以取得更好的结果。但是实验却发现,深度神经网络中存在着退化问题(Degradation problem)。可以看到,在下图中56层的网络比20层网络效果还要差。

上面的现象与过拟合不同,过拟合的表现是训练误差小而测试误差大,而上面的图片显示训练误差和测试误差都是56层的网络较大。

深度网络的退化问题至少说明深度网络不容易训练。我们假设这样一种情况,56层的网络的前20层和20层网络参数一模一样,而后36层是一个恒等映射( identity mapping),即输入x输出也是x,那么56层的网络的效果也至少会和20层的网络效果一样,可是为什么出现了退化问题呢?因此我们在训练深层网络时,训练方法肯定存在的一定的缺陷。

正是上面的这个有趣的假设,何凯明博士发明了残差网络ResNet来解决退化问题!让我们来一探究竟!

2、ResNet网络结构

ResNet中最重要的是残差学习单元:

对于一个堆积层结构(几层堆积而成)当输入为x时其学习到的特征记为H(x),现在我们希望其可以学习到残差F(x)=H(x)-x,这样其实原始的学习特征是F(x)+x 。当残差为0时,此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。一个残差单元的公式如下:

后面的x前面也需要经过参数Ws变换,从而使得和前面部分的输出形状相同,可以进行加法运算。

在堆叠了多个残差单元后,我们的ResNet网络结构如下图所示:

3、ResNet代码实战

我们来实现一个mnist手写数字识别的程序。代码中主要使用的是tensorflow.contrib.slim中定义的函数,slim作为一种轻量级的tensorflow库,使得模型的构建,训练,测试都变得更加简单。卷积层、池化层以及全联接层都可以进行快速的定义,非常方便。这里为了方便使用,我们直接导入slim。

代码语言:javascript
复制
import tensorflow.contrib.slim as slim

我们主要来看一下我们的网络结构。首先定义两个残差结构,第一个是输入和输出形状一样的残差结构,一个是输入和输出形状不一样的残差结构。

下面是输入和输出形状相同的残差块,这里slim.conv2d函数的输入有三个,分别是输入数据、卷积核数量、卷积核的大小,默认的话padding为SAME,即卷积后形状不变,由于输入和输出形状相同,因此我们可以在计算outputs时直接将两部分相加。

代码语言:javascript
复制
def res_identity(input_tensor,conv_depth,kernel_shape,layer_name):
    with tf.variable_scope(layer_name):
        relu = tf.nn.relu(slim.conv2d(input_tensor,conv_depth,kernel_shape))
        outputs = tf.nn.relu(slim.conv2d(relu,conv_depth,kernel_shape) + input_tensor)
    return outputs

下面是输入和输出形状不同的残差块,由于输入和输出形状不同,因此我们需要对输入也进行一个卷积变化,使二者形状相同。ResNet作者建议可以用1*1的卷积层,stride=2来进行变换:

代码语言:javascript
复制
def res_change(input_tensor,conv_depth,kernel_shape,layer_name):
    with tf.variable_scope(layer_name):
        relu = tf.nn.relu(slim.conv2d(input_tensor,conv_depth,kernel_shape,stride=2))
        input_tensor_reshape = slim.conv2d(input_tensor,conv_depth,[1,1],stride=2)
        outputs = tf.nn.relu(slim.conv2d(relu,conv_depth,kernel_shape) + input_tensor_reshape)
    return outputs

最后是整个网络结构,对于x的输入,我们先进行一次卷积和池化操作,然后接入四个残差块,最后接两层全联接层得到网络的输出。

代码语言:javascript
复制
def inference(inputs):
    x = tf.reshape(inputs,[-1,28,28,1])
    conv_1 = tf.nn.relu(slim.conv2d(x,32,[3,3])) #28 * 28 * 32
    pool_1 = slim.max_pool2d(conv_1,[2,2]) # 14 * 14 * 32
    block_1 = res_identity(pool_1,32,[3,3],'layer_2')
    block_2 = res_change(block_1,64,[3,3],'layer_3')
    block_3 = res_identity(block_2,64,[3,3],'layer_4')
    block_4 = res_change(block_3,32,[3,3],'layer_5')
    net_flatten = slim.flatten(block_4,scope='flatten')
    fc_1 = slim.fully_connected(slim.dropout(net_flatten,0.8),200,activation_fn=tf.nn.tanh,scope='fc_1')
    output = slim.fully_connected(slim.dropout(fc_1,0.8),10,activation_fn=None,scope='output_layer')
    return output

完整的代码地址在:https://github.com/princewen/tensorflow_practice/tree/master/CV/ResNet

参考文献:

1、论文:https://arxiv.org/pdf/1512.03385.pdf 2、https://blog.csdn.net/kaisa158/article/details/81096588?utm_source=blogxgwz4

有关作者:

石晓文,中国人民大学信息学院在读研究生,美团外卖算法实习生

简书ID:石晓文的学习日记(https://www.jianshu.com/u/c5df9e229a67)

天善社区:https://www.hellobi.com/u/58654/articles

腾讯云:https://cloud.tencent.com/developer/user/1622140

开发者头条:https://toutiao.io/u/470599

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小小挖掘机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、引言-深度网络的退化问题
  • 2、ResNet网络结构
  • 3、ResNet代码实战
  • 参考文献:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档