使用TensorFlow实现ResNet

 

作为在ILSVRC 2015比赛中取得了冠军的网络结构,ResNet借鉴吸收了以往的网络结构的优点,完全抛弃了全连接层,使用残差学习方法解决了在信息传递时出现的信息丢失,损耗等问题,使整个网络只需要学习输入,输出差别的那一部分,简化了学习目标和学习难度。
忽略掉繁琐的解释和代码逻辑实现,我们现在简单地讲解一下RetNet的实现原理,并且利用这些原理便可以看懂相关的代码甚至写出自己的RetNet网络。
首先,介绍一下替换了全连接层的残差网络。
残差网络常见的分为两层和三层的残差学习网络:

可知,一个残差网络的结果不只是经过卷积网络计算的结果,还加上了输入的数据,两个结果经过共同作用得到了最终的结果。
一个ResNet网络可能有不同的层数,但是在对输入数据的第一次处理需要选择性地进行卷积和池化操作,例如一个50层的ResNet网络,其结构可以表示为2+48,其中2表示预处理,48则是conv卷积层的数目,采用三层的残差学习网络,由于3个卷积层为一个残差网络,故48/3=16个残差网络。

接下来的计算内容便不得而知了,16个上图所示的三层残差网络对数据进行处理,得到一个ResNet网络。

16层的RetNet网络

我们规定为了使数据更容易分类且更加具有特征性,我们把整个网络分为4个Block块,如图中的四个不同的颜色,且规定第一个block块和最后一个都只包括3个残差网络,如图50层的ResNet网络的结构为:(3+4+6+3)x 3 +2
相似地,101层的ResNet网络的结构为:(3+4+23+3)x 3 +2

所以,我们现在已知了网络的结构,就可以对网络进行编程了,首先,初始化其4个Block块:

def resnet_v2_50(inputs, num_classes = None,
                 global_pool = True, reuse = None, scope = 'resnet_v2_50'):
    blocks = [
        Block('block1', bottleneck, [(256, 64, 1)] * 2 + [(256, 64, 2)]),
        Block('block2', bottleneck, [(512, 128, 1)] * 3 + [(512, 128, 2)]),
        Block('block3', bottleneck, [(1024, 256, 1)] * 5 + [(1024, 256, 2)]),
        Block('block4', bottleneck, [(2048, 512, 1)] * 3)
    ]
    return resnet_v2(inputs, blocks, num_classes, global_pool,
                     include_root_block=True, reuse=reuse, scope=scope)

简单来说以上代码的作用就是:

  • 定义了四个Block块
  • 实现这四个Block块

在此,bottleneck为残差函数的实现,而resnet_v2函数则是将Block的内容代入环境来运算。

接下来对每一个相关的函数进行实现:

def resnet_v2(inputs, blocks, num_classes=None, global_pool=True,
          include_root_block=True, reuse=None, scope=None):
    with tf.variable_scope(scope, "resnet_v2", [inputs], reuse=reuse) as sc:
        end_points_collection = sc.original_name_scope+'_end_points'
        with slim.arg_scope([slim.conv2d, bottleneck, stack_block_dense],
                            output_collections=end_points_collection):
            net = inputs
            if include_root_block:
                with slim.arg_scope([slim.conv2d], activation_fn=None,
                                    normalizer_fn=None):
                    net = conv2d_same(net, 64, 7, stride=2, scope='conv1')
                net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')
            net = stack_blocks_dense(net, blocks)
            net = slim.batch_norm(net, activation_fn=tf.nn.relu, scope='postnorm')
            if global_pool:
                net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True)
            if num_classes is not None:
                net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,
                                  normalizer_fn=None, scope='logits')
            end_points = slim.utils.convert_collection_to_dict(end_points_collection)
            if num_classes is not None:
                end_points['predictions'] = slim.softmax(net, scope='predictions')
            return net, end_points
def bottleneck(inputs, depth, depth_bottleneck, stride, output_collections=None, scope=None):
    with tf.variable_scope(scope, 'bottleneck_v2', [input]) as sc:
        depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4)
        preact = slim.batch_norm(inputs, activation_fn=tf.nn.relu)

        if depth == depth_in:
            shortcut = subsample(inputs, stride, 'shortcut')
        else:
            shortcut = slim.conv2d(preact, depth, [1, 1], stride=stride,
                                   normalizer_fn=None, activation_fn=None,
                                   scope='shortcut')
        residual = slim.conv2d(preact, depth_bottleneck, [1, 1], stride=1, scope="conv1")
        residual = conv2d_same(residual, depth_bottleneck, 3, stride, scope="conv2")
        residual = slim.conv2d(residual, depth, [1, 1], stride=1, normalizer_fn=None, activation_fn=None,
                               scope="conv3")
        output = shortcut + residual
        return slim.utils.collect_named_outputs(output_collections,
                                                sc.name, output)

猜你喜欢

转载自blog.csdn.net/CVAIDL/article/details/88874151