2016 ImageNet Second
2017 CVPR
Aggregated ResidualTransformations for Deep Neural Networks
采用 VGG 堆叠的思想和 Inception 的 split-transform-merge 思想,把convolution进化成为group convolution。
32 paths 符合split-transform-merge的模式,每个paths 一模一样(采用相同的卷积参数)
上面三个设计是等价设计, 最后一个C的结构, 极其类似ResNet的bottleneck,但是通道数却多的多。ResNeXt引入Inception结构,通过稀疏连接来approach之前的dense连接。
a是ResNeXt基本单元,如果把输出那里的1x1合并到一起,得到等价网络b拥有和Inception-ResNet相似的结构,而进一步把输入的1x1也合并到一起,得到等价网络c则和通道分组卷积的网络有相似的结构。
ResNeXt 只能在 block 的 depth>3时使用. 如果 block 的 depth=2,则会得到宽而密集的模块.
借鉴GoogleNet Inception的模式:split-transform-merge。
ResNeXt 中每个分支一模一样(采用相同的卷积参数),分支的个数就是 cardinality。
借鉴
GoogLeNet 的 split-transform-merge
通过在大卷积核层两侧加入 1x1 的网络层,控制核个数,减少参数个数的方式,见下图
VGG/ResNets 的 repeat layer
重复相同的几层,前提条件是这几层的输出输出具有相同的维度,一般在不同的 repeat layers 之间使用 strip=2 降维,同时核函数的个数乘 2。
C:通道数
其中,
D 是 channel 输入向量; wi 是第 i 个 channel 的 filter 权重.
Inner product 可以看作是 splitting-transforming-aggregating 的组合:
(1) Splitting:输入向量 x 被分为低维 embedding,即单维空间的 xi;
(2) Transforming:变换得到低维表示,即:wixi;
(3) Aggregating: 通过相加将所有的 embeddings 变换聚合,即
其中,可以是任意函数,其将 x 投影到一个嵌入空间(一般是低维空间),并进行变换.
C 是待聚合的变换集的大小,即 Cardinality. 类似于 D.
对于变换函数的设计,采用策略是:所有的 Ti 拓扑结构相同.
参数量
计算一下便可得:假设输入特征图为256维
Resnet的参数量为:
256x64+3x3x64x64+64x256=70k
ResNeXt的参数量为:
Cx(256xd+3x3xdxd+dx256) 当C取32,d=4时,上式也等于70k。
网络结构
ResNeXt 保留 ResNet 的堆叠 block,而是对单个 building block 改进.
class Block(nn.Module):
'''Grouped convolution block.'''
expansion = 2
def __init__(self, in_planes, cardinality=32, bottleneck_width=4, stride=1):
super(Block, self).__init__()
group_width = cardinality * bottleneck_width
self.conv1 = nn.Conv2d(in_planes, group_width, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(group_width)
self.conv2 = nn.Conv2d(group_width, group_width, kernel_size=3, stride=stride, padding=1, groups=cardinality, bias=False)
self.bn2 = nn.BatchNorm2d(group_width)
self.conv3 = nn.Conv2d(group_width, self.expansion*group_width, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(self.expansion*group_width)
self.shortcut = nn.Sequential()
if stride != 1 or in_planes != self.expansion*group_width:
self.shortcut = nn.Sequential(
nn.Conv2d(in_planes, self.expansion*group_width, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion*group_width)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += self.shortcut(x)
out = F.relu(out)
return out
class ResNeXt(nn.Module):
def __init__(self, num_blocks, cardinality, bottleneck_width, num_classes=10):
super(ResNeXt, self).__init__()
self.cardinality = cardinality
self.bottleneck_width = bottleneck_width
self.in_planes = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.layer1 = self._make_layer(num_blocks[0], 1)
self.layer2 = self._make_layer(num_blocks[1], 2)
self.layer3 = self._make_layer(num_blocks[2], 2)
# self.layer4 = self._make_layer(num_blocks[3], 2)
self.linear = nn.Linear(cardinality*bottleneck_width*8, num_classes)
def _make_layer(self, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(Block(self.in_planes, self.cardinality, self.bottleneck_width, stride))
self.in_planes = Block.expansion * self.cardinality * self.bottleneck_width
# Increase bottleneck_width by 2 after each stage.
self.bottleneck_width *= 2
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
# out = self.layer4(out)
out = F.avg_pool2d(out, 8)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
def ResNeXt29_2x64d():
return ResNeXt(num_blocks=[3,3,3], cardinality=2, bottleneck_width=64)
def ResNeXt29_4x64d():
return ResNeXt(num_blocks=[3,3,3], cardinality=4, bottleneck_width=64)
def ResNeXt29_8x64d():
return ResNeXt(num_blocks=[3,3,3], cardinality=8, bottleneck_width=64)
def ResNeXt29_32x4d():
return ResNeXt(num_blocks=[3,3,3], cardinality=32, bottleneck_width=4)