Warning: 这是一个学习笔记及分享向的文章, 对于初学者可能不太友好
最近突然出现了一个疑问, 用了ResNet等主流主干网络这么久, 也知道每个网络最基本的原理, 比如ResNet是利用H(x)=F(x)+x恒等变换让网络易于训练, 在有downsample的层将x进行变换匹配F(x)之后的size, 但是具体是怎么实现的呢?抱着这个疑问, 在重新阅读了ResNet这篇论文之后, 把官方源码研究了一遍。花了大概一个星期的时间, 终于把源码中和ResNet这篇论文相关的部分都理解透彻了。
不得不说源码的作者是真的牛逼, 看完感觉自己写代码的能力又增加了(增加了, 但没有完全增加, 只增加了一点点)
最后写完了resnet18等几个函数后, 想找点数据来练练手。第一时间我想到了MNIST, 在印象中MNIST的图片是 28* 28* 1 的, 但是resnet18接收的是3通道的RGB图片。于是乎, 我写了以下这段代码:
model = resnet18(num_classes=10)
img = torch.Tensor(1, 3, 28, 28)
output = model(img)
然后就得到了以下的输出:
(输出部分是我在自己重写的代码里面加入了print(), 源码中没有输出每层的output.size())
>>> inputs: torch.Size([1, 3, 28, 28])
>>> conv1 -> maxpool: torch.Size([1, 64, 7, 7])
>>> layer1: torch.Size([1, 64, 7, 7])
>>> layer2: torch.Size([1, 128, 4, 4])
>>> layer3: torch.Size([1, 256, 2, 2])
>>> ValueError Traceback (most recent call last)
......
>>> ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1, 512, 1, 1])
显然 layer3 输出的[1, 256, 2, 2]的feature map太小导致后续的操作出错, 原因则是ResNet类中处理输入图片的部分
# 部分源码
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = norm_layer(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
从输出也可以看到, (1, 3, 28, 28)的inputs经过了 conv1 -> bn1 -> relu -> maxpool 之后输出为(1, 64, 7, 7)
那么就要修改处理输入图片的部分, 于是我就又去百度了, 然而看到的大部分文章都是重新写了一个网络。
能不能不重新写呢?
思考了一会儿, 我把网络进行如下修改:
model = resnet18(num_classes=10)
model.conv1 = nn.Conv2d(1, model.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
img = torch.Tensor(1, 3, 28, 28)
output = model(img)
OK, 再次报错
inputs: torch.Size([1, 1, 28, 28])
RuntimeError Traceback (most recent call last)
…
RuntimeError: running_mean should contain 512 elements not 64
哦, model.inplanes最后变成了512, 但是最开始应该是64, 再次修改
model = resnet18(num_classes=10)
model.conv1 = nn.Conv2d(1, 64, kernel_size=3, stride=1, padding=1, bias=False)
img = torch.Tensor(1, 3, 28, 28)
output = model(img)
>>> inputs: torch.Size([1, 1, 28, 28])
>>> conv1 -> maxpool: torch.Size([1, 64, 14, 14])
>>> layer1: torch.Size([1, 64, 14, 14])
>>> layer2: torch.Size([1, 128, 7, 7])
>>> layer3: torch.Size([1, 256, 4, 4])
>>> layer4: torch.Size([1, 512, 2, 2])
>>> avgpool: torch.Size([1, 512, 1, 1])
>>> flatten: torch.Size([1, 512])
>>> fc: torch.Size([1, 10])
>>> torch.Size([1, 10])
后面的数据准备和训练部分都大同小异, 就不再赘述了。
batch_size为64, 附上90轮次的loss
>>> [1, 10] loss: 0.01002
>>> [1, 20] loss: 0.00601
>>> [1, 30] loss: 0.00381
>>> [1, 40] loss: 0.00274
>>> [1, 50] loss: 0.00216
>>> [1, 60] loss: 0.00206
>>> [1, 70] loss: 0.00161
>>> [1, 80] loss: 0.00136
>>> [1, 90] loss: 0.00144