【深度学习基础】PyTorch实现DPN亲身实践
1 论文关键信息
1.1 DPN block
论文指出了ResNet可以复用特征,DenseNet的结构可以帮助发现新的特征。于是作者结合二者的优点,提出了一种DPN网络结构。该结构实际上是ResNeXt与DenseNet的一种结合。
在分析的过程中论文给出了一些公式说明,ResNet,DenseNet与RNN之间的关系,如下图:
然后,又给出了一些公式说明它们之间的关系。这些应该是DPN网络形成的过程,讲道理,由于每台接触过RNN,上面这些图和论文中的一些公式我都没太看懂。反正作者们牛逼,我们直接来看它的结果吧:
我们来好好分析一下上图:首先,图中这些水管一样的图案是网络的输入输出特征,其宽度代表的是它的通道数量。
(a)图是ResNet的Bottleneck的结构,如果你了解之前的backbone就应该很清楚;(b)图是DenseNet block的结构,但是它比Dense多了一个1x1的卷积层,为什么呢? 因为它是要和ResNet结合的,加一层卷积可以让二者耦合起来;(c)然后c图就是share DenseNet block中的第一层1x1卷积,发现这样做其中会包含ResNet结构;于是论文基于这个特点,提出了DPN的结构,如(d)(e),这两个图是等效的。 我们来看一下(e)图的结构,首先上一层特征输入到1x1的卷积层,然后紧跟着一个3x3的卷积层;之后,1x1的卷积层处理的结果分成两部分——一部分结果作为残差与输入相加,另一部分与相加后的结果进行拼接。需要注意的是,这里的3x3卷积沿用ResNeXt的思想,采用分组卷积的方式形成inception结构。
1.2 网络结构
2 PyTorch代码
就像论文说的,DPN在ResNeXt的基础上,适当调整结构即可。
2.1 Dual Path Block
代码:
class DPN_Block(nn.Module):
"""
Dual Path block
"""
def __init__(self, in_chnls, add_chnl, cat_chnl, cardinality, d, stride):
super(DPN_Block, self).__init__()
self.add = add_chnl
self.cat = cat_chnl
self.chnl = cardinality * d
self.conv1 = BN_Conv2d(in_chnls, self.chnl, 1, 1, 0)
self.conv2 = BN_Conv2d(self.chnl, self.chnl, 3, stride, 1, groups=cardinality)
self.conv3 = nn.Conv2d(self.chnl, add_chnl + cat_chnl, 1, 1, 0)
self.bn = nn.BatchNorm2d(add_chnl + cat_chnl)
self.shortcut = nn.Sequential()
if add_chnl != in_chnls:
self.shortcut = nn.Sequential(
nn.Conv2d(in_chnls, add_chnl, 1, stride, 0),
nn.BatchNorm2d(add_chnl)
)
def forward(self, x):
out = self.conv1(x)
out = self.conv2(out)
out = self.bn(self.conv3(out))
add = out[:, :self.add, :, :] + self.shortcut(x)
out = torch.cat((add, out[:, self.add:, :, :]), dim=1)
return F.relu(out)
2.2 网络结构实现和测试
class DPN(nn.Module):
def __init__(self, blocks: object, add_chnls: object, cat_chnls: object,
conv1_chnl, cardinality, d, num_classes) -> object:
super(DPN, self).__init__()
self.cdty = cardinality
self.chnl = conv1_chnl
self.conv1 = BN_Conv2d(3, self.chnl, 7, 2, 3)
d1 = d
self.conv2 = self.__make_layers(blocks[0], add_chnls[0], cat_chnls[0], d1, 1)
d2 = 2 * d1
self.conv3 = self.__make_layers(blocks[1], add_chnls[1], cat_chnls[1], d2, 2)
d3 = 2 * d2
self.conv4 = self.__make_layers(blocks[2], add_chnls[2], cat_chnls[2], d3, 2)
d4 = 2 * d3
self.conv5 = self.__make_layers(blocks[3], add_chnls[3], cat_chnls[3], d4, 2)
self.fc = nn.Linear(self.chnl, num_classes)
def __make_layers(self, block, add_chnl, cat_chnl, d, stride):
layers = []
strides = [stride] + [1] * (block-1)
for i, s in enumerate(strides):
layers.append(DPN_Block(self.chnl, add_chnl, cat_chnl, self.cdty, d, s))
self.chnl = add_chnl + cat_chnl
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv1(x)
out = F.max_pool2d(out, 3, 2, 1)
print("shape 1---->", out.shape)
out = self.conv2(out)
print("shape 2---->", out.shape)
out = self.conv3(out)
print("shape 3---->", out.shape)
out = self.conv4(out)
print("shape 4---->", out.shape)
out = self.conv5(out)
print("shape 5---->", out.shape)
out = F.avg_pool2d(out, 7)
out = out.view(out.size(0), -1)
out = self.fc(out)
return F.softmax(out)
def dpn_92_32x3d(num_classes=1000):
return DPN(blocks=[3, 4, 20, 3],
add_chnls=[256, 512, 1024, 2048],
cat_chnls=[16, 32, 24, 128],
conv1_chnl=64,
cardinality=32,
d=3,
num_classes=num_classes)
def dpn_98_40x4d(num_classes=1000):
return DPN(blocks=[3, 6, 20, 3],
add_chnls=[256, 512, 1024, 2048],
cat_chnls=[16, 32, 32, 128],
conv1_chnl=96,
cardinality=40,
d=5,
num_classes=num_classes)
def dpn_131_40_4d(num_classes=1000):
return DPN(blocks=[4, 8, 28, 3],
add_chnls=[256, 512, 1024, 2048],
cat_chnls=[16, 32, 32, 128],
conv1_chnl=128,
cardinality=40,
d=5,
num_classes=num_classes)
def test():
# net = dpn_92_32x3d()
net = dpn_98_40x4d()
# net = dpn_131_40_4d()
summary(net, (3, 224, 224))
test()
DPN_98(40x4d)网络的测试输出如下: