美文网首页
Resnet-Deep residual network

Resnet-Deep residual network

作者: 踩坑第某人 | 来源:发表于2018-11-24 12:15 被阅读0次

    摘要

    Resnet(残差网络)在ILSVRC2015比赛中取得冠军,并取得了5项第一:

    • ImageNet分类第一
    • ImageNet检测第一
    • ImageNet定位第一
    • COCO检测第一
    • COCO分割第一

    作者是来自微软亚洲研究院的何凯明等人。主要贡献在于解决了深度CNN模型难训练的问题,提出恒等映射和残差网络的结构,并使得网络深度有了更大的突破(从2014年VGG的19层,Googlenet22层发展到Resnet的50层,152层),是CNN图像史上的一件里程碑事件。

    网络由来

    • 深度网络的退化问题
      从经验来看,当卷积神经网络的层数增加时,网络可以进行更加复杂的特征模式的提取,理论上可以取得更好的结果(Alexnet8->VGG19->Google22)。然而实践发现深度网络出现了退化问题(Degradation problem):随着网络深度的增加,模型的准确度会出现饱和,甚至下降。如下图所示:56层的网络比20层网络效果还要差。这不会是过拟合问题,因为56层网络的训练误差同样高。我们知道深层网络存在着梯度消失或者爆炸的问题,这使得深度学习模型很难训练。但是现在已经存在一些技术手段如BatchNorm来缓解这个问题。因此,出现深度网络的退化问题是非常令人诧异的。
    模型精度vs 网络深度
    • 恒等映射(Identity Mapping)
      深度网络的退化问题至少说明深度网络不容易训练。但是我们考虑这样一个事实:现在你有一个浅层网络,你想通过向上堆积新层来建立深层网络,一个极端情况是这些增加的层什么也不学习,仅仅复制浅层网络的特征,即这样新层是恒等映射(Identity mapping)。在这种情况下,深层网络应该至少和浅层网络性能一样,也不应该出现退化现象。针对这个退化问题,resnet作者提出了残差学习来解决退化问题。对于一个堆积层结构(几层堆积而成)当输入为时其学习到的特征记为,现在我们希望其可以学习到残差,这样其实原始的学习特征是。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。残差学习的结构如图4所示。这有点类似与电路中的“短路”,所以是一种短路连接(shortcutconnection)。


      Identity Mapping

    网络结构

    Resnet网络是参考了VGG19网络,在其基础上进行了修改,并通过短路机制加入了残差单元,Resnet34如下图所示。不同点在于除了第一层resnet采用7x7卷积并连接pool外,中间层都直接在采用stride=2的卷积进行下采样,在最后用global average pool替换了全连接层。

    Resnet网络以一个残差块为基础单元,多个单元在深度上进行形成一组,同一组中每个残差块的输出通道数相同,不同组的输出通道以256为基础,并以2倍递增。从下图中可以看到,ResNet相比普通网络每两层间增加了短路机制,这就形成了残差学习,其中实现部分表示常规的identity mapping(输入输出通道数相同),虚线表示输入输出通道数不同时在shortcut上加了1x1卷积来改变输入的通道数。其中Resnet34 和Resnet50除第一层7x7卷积和最后一层全连接外,共有4组残差组,每组各有3,4,6,3个残差单元,而resnet34中一个残差单元含有2个3x3卷积,因此层数为1+(3+4+6+3)x2+1 = 34,同理resnet50的层数为:1+(3+4+6+3)x3+1=50

    resnet34 vs VGG19
    • 浅层残差单元vs深层残差单元
      ResNet使用两种残差单元,如下图所示。左图对应的是浅层网络(34层及以下),而右图对应的是深层网络(50层及以上)。对于短路连接,当输入和输出维度一致时,可以直接将输入加到输出上。但是当维度不一致时(对应的是维度增加一倍),这就不能直接相加。有两种策略:
      (1)采用zero-padding增加维度,此时一般要先做一个downsamp,可以采用strde=2的pooling,这样不会增加参数;
      (2)采用新的映射(projection shortcut),一般采用1x1的卷积,这样会增加参数,也会增加计算量。短路连接除了直接使用恒等映射,当然都可以采用projection shortcut。
    残差单元

    代码实现

    本文采用tensorflow.contrib.layers 模块来构建Mobilenet网络结构,关于tf.nn,tf.layers等api的构建方式参见VGG网络中的相关代码。

    # --------------------------Method 1 --------------------------------------------
    import tensorflow as tf
    import tensorflow.contrib.layers as tcl
    from tensorflow.contrib.framework import arg_scope
    class ResNet50:
        def __init__(self, resolution_inp=224, channel=3, name='resnet50'):
            self.name = name
            self.channel = channel
            self.resolution_inp = resolution_inp
    
        def __call__(self, x, dropout=0.5, is_training=True):
            with tf.variable_scope(self.name) as scope:
                with arg_scope([tcl.batch_norm], is_training=is_training, scale=True):
                    with arg_scope([tcl.conv2d],
                                   activation_fn=tf.nn.relu,
                                   normalizer_fn=tcl.batch_norm,
                                   padding="SAME"):
                        conv1 = tcl.conv2d(x, 64, 7, stride=2)
                        conv1 = tcl.max_pool2d(conv1, kernel_size=3, stride=2)
    
                        conv2 = self._res_blk(conv1, 256, 3, stride=1)
                        conv2 = self._res_blk(conv2, 256, 3, stride=1)
                        conv2 = self._res_blk(conv2, 256, 3, stride=1)
    
                        conv3 = self._res_blk(conv2, 512, 3, stride=2)
                        conv3 = self._res_blk(conv3, 512, 3, stride=1)
                        conv3 = self._res_blk(conv3, 512, 3, stride=1)
                        conv3 = self._res_blk(conv3, 512, 3, stride=1)
    
                        conv4 = self._res_blk(conv3, 1024, 3, stride=2)
                        conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                        conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                        conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                        conv4 = self._res_blk(conv4, 1024, 3, stride=1)
                        conv4 = self._res_blk(conv4, 1024, 3, stride=1)
    
                        conv5 = self._res_blk(conv4, 2048, 3, stride=2)
                        conv5 = self._res_blk(conv5, 2048, 3, stride=1)
                        conv5 = self._res_blk(conv5, 2048, 3, stride=1)
    
                        avg_pool = tf.nn.avg_pool(conv5, [1, 7, 7, 1], strides=[1, 1, 1, 1], padding="VALID")
                        flatten = tf.layers.flatten(avg_pool)
    
                        self.fc6 = tf.layers.dense(flatten, units=1000, activation=tf.nn.relu)
                        # dropout = tf.nn.dropout(fc6, keep_prob=0.5)
                        predictions = tf.nn.softmax(self.fc6)
                        return predictions
    
        def _res_blk(self, x, num_outputs, kernel_size, stride=1, scope=None):
            with tf.variable_scope(scope, "resBlk"):
                small_ch = num_outputs // 4
    
                conv1 = tcl.conv2d(x, small_ch, kernel_size=1, stride=stride, padding="SAME")
                conv2 = tcl.conv2d(conv1, small_ch, kernel_size=kernel_size, stride=1, padding="SAME")
                conv3 = tcl.conv2d(conv2, num_outputs, kernel_size=1, stride=1, padding="SAME")
    
                shortcut = x
                if stride != 1 or x.get_shape()[-1] != num_outputs:
                    shortcut = tcl.conv2d(x, num_outputs, kernel_size=1, stride=stride, padding="SAME",scope="shortcut")
    
                out = tf.add(conv3, shortcut)
                out = tf.nn.relu(out)
                return out
    
    

    运行

    该部分代码包含2部分:计时函数time_tensorflow_run接受一个tf.Session变量和待计算的tensor以及相应的参数字典和打印信息, 统计执行该tensor100次所需要的时间(平均值和方差);主函数 run_benchmark中初始化了vgg16的3种调用方式,分别统计3中网络在推理(predict) 和梯度计算(后向传递)的时间消耗,详细代码如下:

    # -------------------------- Demo and Test -------------------------------------------
    from datetime import datetime
    import math
    import time
    batch_size = 16
    num_batches = 100
    
    
    def time_tensorflow_run(session, target, feed, info_string):
        """
        calculate time for each session run
        :param session: tf.Session
        :param target: opterator or tensor need to run with session
        :param feed: feed dict for session
        :param info_string: info message for print
        :return: 
        """
        num_steps_burn_in = 10  # 预热轮数
        total_duration = 0.0  # 总时间
        total_duration_squared = 0.0  # 总时间的平方和用以计算方差
        for i in range(num_batches + num_steps_burn_in):
            start_time = time.time()
            _ = session.run(target, feed_dict=feed)
    
            duration = time.time() - start_time
    
            if i >= num_steps_burn_in:  # 只考虑预热轮数之后的时间
                if not i % 10:
                    print('[%s] step %d, duration = %.3f' % (datetime.now(), i - num_steps_burn_in, duration))
                total_duration += duration
                total_duration_squared += duration * duration
    
        mn = total_duration / num_batches  # 平均每个batch的时间
        vr = total_duration_squared / num_batches - mn * mn  # 方差
        sd = math.sqrt(vr)  # 标准差
        print('[%s] %s across %d steps, %.3f +/- %.3f sec/batch' % (datetime.now(), info_string, num_batches, mn, sd))
    
    
    # test demo
    def run_benchmark():
        """
        main function for test or demo
        :return: 
        """
        with tf.Graph().as_default():
            image_size = 224  # 输入图像尺寸
            images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3], dtype=tf.float32, stddev=1e-1))
    
            # method 0
            # prediction, fc = resnet50(images, training=True)
            model = ResNet50(224, 3)
            prediction = model(images, is_training=True)
            fc = model.fc6
    
            params = tf.trainable_variables()
    
            for v in params:
                print(v)
            init = tf.global_variables_initializer()
    
            print("out shape ", prediction)
            sess = tf.Session()
            print("init...")
            sess.run(init)
    
            print("predict..")
            writer = tf.summary.FileWriter("./logs")
            writer.add_graph(sess.graph)
            time_tensorflow_run(sess, prediction, {}, "Forward")
    
            # 用以模拟训练的过程
            objective = tf.nn.l2_loss(fc)  # 给一个loss
            grad = tf.gradients(objective, params)  # 相对于loss的 所有模型参数的梯度
    
            print('grad backword')
            time_tensorflow_run(sess, grad, {}, "Forward-backward")
            writer.close()
    
    if __name__ == '__main__':
        run_benchmark()
    

    注: 完整代码可参见个人github工程

    参数量

    时间效率

    参考

    https://blog.csdn.net/u013709270/article/details/78838875

    相关文章

      网友评论

          本文标题:Resnet-Deep residual network

          本文链接:https://www.haomeiwen.com/subject/egknpftx.html