作为在ILSVRC 2015比赛中取得了冠军的网络结构,ResNet借鉴吸收了以往的网络结构的优点,完全抛弃了全连接层,使用残差学习方法解决了在信息传递时出现的信息丢失,损耗等问题,使整个网络只需要学习输入,输出差别的那一部分,简化了学习目标和学习难度。
忽略掉繁琐的解释和代码逻辑实现,我们现在简单地讲解一下RetNet的实现原理,并且利用这些原理便可以看懂相关的代码甚至写出自己的RetNet网络。
首先,介绍一下替换了全连接层的残差网络。
残差网络常见的分为两层和三层的残差学习网络:
可知,一个残差网络的结果不只是经过卷积网络计算的结果,还加上了输入的数据,两个结果经过共同作用得到了最终的结果。
一个ResNet网络可能有不同的层数,但是在对输入数据的第一次处理需要选择性地进行卷积和池化操作,例如一个50层的ResNet网络,其结构可以表示为2+48,其中2表示预处理,48则是conv卷积层的数目,采用三层的残差学习网络,由于3个卷积层为一个残差网络,故48/3=16个残差网络。
我们规定为了使数据更容易分类且更加具有特征性,我们把整个网络分为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)
网友评论