论文:https://arxiv.org/abs/1904.01169
原理
Res2Net在多个尺度上表示特征并且增加了每个网络层的感受野范围。

将输入feature map划分为几个组。一组卷积核首先从一组输入feature map中提取特征。然后将输出的feature map与另一组输入feature map再通过另一组卷积核提取特征。这个过程重复几次,直到处理完所有的输入feature map。最后,将所有组输出的feature map拼接起来,再通过1x1的卷积核进行特征融合。当通过3x3的卷积核的时候,感受野也会增加。
此处没有对第一次拆分进行卷积操作,一是可以减少参数的数量,二是可以特征重利用。
Res2Net modual还添加了SE Block(squeeze and excitation),如下图

Res2Net Modual可以集成在ResNet,ResNeXt,DLA这些模型中,其性能都有所提升,只需要修改其中的bottleneck即可。
通过论文中的实验,当 s c a l e = 4 scale=4 scale=4时有很好的效果。

实现
以下代码是我对tensorflow.slim实现的ResNet_v1进行的修改(如果有什么不正确的地方,欢迎指正):
@slim.add_arg_scope
def res2net_bottleneck(inputs,
depth,
depth_bottleneck,
stride,
rate=1,
scale=4,
outputs_collections=None,
scope=None,
use_bounded_activations=False,
use_se_block=False):
with tf.variable_scope(scope, 'bottleneck_v1', [inputs]) as sc:
depth_in = slim.utils.last_dimension(inputs.get_shape(), min_rank=4)
if depth == depth_in:
shortcut = resnet_utils.subsample(inputs, stride, 'shortcut')
else:
shortcut = slim.conv2d(
inputs,
depth, [1, 1],
stride=stride,
activation_fn=tf.nn.relu6 if use_bounded_activations else None,
scope='shortcut')
residual = slim.conv2d(inputs, depth_bottleneck, [1, 1], stride, scope='conv1')
# slice_layer
single_channel = depth_bottleneck // scale
output_list = []
for i in range(scale):
out = residual[..., i * single_channel:(i + 1) * single_channel]
output_list.append(out)
sub_y = []
for i in range(scale):
slice_x = output_list[i]
if i > 1:
slice_x = tf.add(sub_y[-1], slice_x)
if i > 0:
slice_x = resnet_utils.conv2d_same(slice_x, single_channel, 3, stride=1, rate=rate, scope='conv2_' + str(i))
sub_y.append(slice_x)
residual = tf.concat(sub_y, axis=-1)
residual = slim.conv2d(residual, depth, [1, 1], stride=1, activation_fn=None, scope='conv3')
if use_se_block:
residual = se_block(residual)
if use_bounded_activations:
# Use clip_by_value to simulate bandpass activation.
residual = tf.clip_by_value(residual, -6.0, 6.0)
output = tf.nn.relu6(shortcut + residual)
else:
output = tf.nn.relu(shortcut + residual)
return slim.utils.collect_named_outputs(outputs_collections, sc.name, output)
# SE Block的实现
def se_block(inputs, c=16):
num_chnnels = int(inputs.get_shape()[-1])
x = tf.reduce_mean(inputs, [1, 2], name='se_pool', keepdims=True) # 全局平均池化
x = slim.conv2d(x, num_chnnels // c, [1, 1], padding='VALID', scope='fc1')
x = slim.conv2d(x, num_chnnels, [1, 1], activation_fn=tf.nn.sigmoid, padding='VALID', scope='fc2')
return tf.multiply(inputs, x)
论文展示效果
以下几张图片是论文中的实现效果:


