Resnet-Deep residual network

摘要

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容