Pytorch手工实现ResNet50
用 Pytorch 手工实现 ResNet50
《吴恩达深度学习课程》第四课第二周的作业是:使用 Keras 和 Tensorflow 编写 ResNet50,用程序实现题目中描述的网络结构。由于程序填空提供了不少示例,做完后仍感觉理解不透彻,又使用 Pytorch 实现了一遍。
ResNet50 包含 49 个卷积层和 1 个全连接层,属于较大型的网络,实现起来略有难度。对于理解数据流、卷积层、残差、瓶颈层,以及对大型网络的编写和调试都有很大帮助。
使用的数据仍是第四课第二周中的手势图片识别,题目说明、Keras 例程和相关数据可从以下网址下载:https://blog.csdn.net/u013733326/article/details/80250818
Keras ResNet50 程序填空的代码可从以下网址下载:https://github.com/Kulbear/deep-learning-coursera/blob/master/Convolutional%20Neural%20Networks/Residual%20Networks%20-%20v1.ipynb
Torch 官方版本的 ResNet 实现可从以下网址下载(网络结构细节略有不同):https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py
网络结构
ResNet 网络结构如下图所示:
代码
下面使用约 100 行代码实现了 ResNet50 网络类(可缩减至 80 行左右),另外 100 行代码用于处理数据,训练和预测。
准备数据:
1 | import math |
实现 ResNet
1 | class ConvBlock(nn.Module): |
训练和预测
1 | device = 'cuda' |
实验 1000 张图片作为训练集,120 张图片作为测试集,在使用 GPU 的情况下几分钟即可完成 100 次迭代,使用 CPU 两三个小时也能训练完成,训练好的模型约 100M 左右,在测试集准确率基本稳定在 97.5%。对比简单的网络结构,ResNet50 可以较短的时间内达到较好的效果。
###瓶颈层
使用 Pytorch 实现 ResNet 时,需要注意卷积层间的对接,比如在第二层 conv2.x 中有 3 个 Block,Block 内部 3 层通道输出分别是 64,64,256,于是有 64->64->256,较容易理解;而第二层的 3 个 Block 之间,需要将 256 再转回 64,在第二层内部,通道变化是 64->64->256->64->64->256->64->64->256。
其数据流变化如下图所示:
block 中的三个卷积层:第一层,卷积核 1x1 用于实现通道数转换,第二层 3x3 实现特征提取,第三层将通道数转换成目标大小。
不同的块内结构是 Resnet50 与 Resnet34 的主要区别:
不同的结构,在 Block 块数相同,且参数规模相似的情况下,Resnet34 提取 512 个特征(输出通道数),而 ResNet50 能提取 2048 个特征。从论文中可以看到同结构的对比效果。
论文地址:https://arxiv.org/pdf/1512.03385.pdf
在图像处理中卷积核是四维的,其大小为:卷积核长 x 卷积核宽 x 输入通道数 x 输出通道数。在数据处理后期通道数越来越大,因此左图中的结构在层数多,输出特征多的情况下,参数将变得非常庞大;而右图限制了 3x3 卷积处理的通道数,1x1 的卷积操作运算量又比较小,有效地解决了这一问题。
调试方法
搭建大型网络时,数据在网络中逐层处理,常出现相邻层之间数据接口不匹配的问题。在本例中可对照官方版本的 ResNet 结构排查问题,使用下面程序可打印出 torchvision 中 ResNet 的网络结构。
1 | import torchvision |