ResNet18的搭建请移步:使用PyTorch搭建ResNet18网络并使用CIFAR10数据集训练测试
ResNet34的搭建请移步:使用PyTorch搭建ResNet34网络
ResNet101、ResNet152的搭建请移步:使用PyTorch搭建ResNet101、ResNet152网络

看过我之前ResNet18和ResNet34搭建的朋友可能想着可不可以把搭建18和34层的方法直接用在50层以上的ResNet的搭建中,我也尝试过。但是ResNet50以上的网络搭建不像是18到34层只要简单修改卷积单元数目就可以完成,ResNet50以上的三种网络都是一个样子,只是层数不同,所以完全可以将34到50层作为一个搭建分水岭。
加上我初学PyTorch和深度神经网络,对于采用BasicBlock和BottleNeck的高效率构建还不是很懂,所以这里给出了类似前两种ResNet的简单暴力堆叠网络层的构建方法

ResNet50网络结构

所有不同层数的ResNet:

这里给出了我认为比较详细的ResNet50网络具体参数和执行流程图:

直接上代码

model.py模型部分:

import torch
import torch.nn as nn
from torch.nn import functional as F"""
这里的ResNet50的搭建是暴力形式,直接累加完成搭建,没采用BasicBlock和BottleNeck
第一个DownSample类,用于定义shortcut的模型函数,完成两个layer之间虚线的shortcut,负责layer1虚线的升4倍channel以及其他layer虚线的升2倍channel
观察每一个layer的虚线处升channel仅仅是升channel前后的数量不同以及stride不同,对于kernel_size和padding都分别是1和0,不作为DownSample网络类的模型参数
参数in_channel即是升之前的通道数, out_channel即是升之后的通道数, stride即是每一次升channel不同的stride步长,对于layer1升通道的stride=1,其他layer升通道的stride=2,注意不同
"""
"""
运行时一定要注意:
本网络中的ResNet50类中forward函数里面:layer1_shortcut1.to('cuda:0');layer2_shortcut1.to('cuda:0')等语句,是将实例化的DownSample
网络模型放到train.py训练脚本中定义的GPU同一环境下,不加此句一般会如下报错:
Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same
"""class DownSample(nn.Module):def __init__(self, in_channel, out_channel, stride):            # 传入下采样的前后channel数以及stride步长super(DownSample, self).__init__()                          # 继承父类self.down = nn.Sequential(                                  # 定义一个模型容器downnn.Conv2d(in_channel, out_channel, kernel_size=1, stride=stride, padding=0, bias=False),    # 负责虚线shortcut的唯一且重要的一次卷积nn.BatchNorm2d(out_channel),                            # 在卷积和ReLU非线性激活之间,添加BatchNormalizationnn.ReLU(inplace=True)                                   # shortcut最后加入一个激活函数,置inplace=True原地操作,节省内存)def forward(self, x):out = self.down(x)                                          # 前向传播函数仅仅完成down这个容器的操作return out"""
第一个ResNet50类,不使用BottleNeck单元完成ResNet50层以上的搭建,直接使用forward再加上前面的DownSample模型类函数,指定ResNet50所有参数构建模型
"""class ResNet50(nn.Module):def __init__(self, classes_num):                                # ResNet50仅传一个分类数目,将涉及的所有数据写死,具体数据可以参考下面的图片super(ResNet50, self).__init__()# 在进入layer1234之间先进行预处理,主要是一次卷积一次池化,从[batch, 3, 224, 224] => [batch, 64, 56, 56]self.pre = nn.Sequential(# 卷积channel从原始数据的3通道,采用64个卷积核,升到64个channel,卷积核大小、步长、padding均固定nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),nn.BatchNorm2d(64),                                     # 卷积后紧接一次BatchNormalizationnn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3, stride=2, padding=1)        # 预处理最后的一次最大池化操作,数据固定)"""每一个layer的操作分为使用一次的first,和使用多次的next组成,first负责每个layer的第一个单元(有虚线)的三次卷积,next负责剩下单元(直连)的三次卷积"""# --------------------------------------------------------------self.layer1_first = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1, stride=1, padding=0, bias=False),      # layer1_first第一次卷积保持channel不变,和其他layer的first区别nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1, bias=False),      # layer1_first第二次卷积stride和其他layer_first的stride不同nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.Conv2d(64, 256, kernel_size=1, stride=1, padding=0, bias=False),     # layer1_first第三次卷积和其他layer一样,channel升4倍nn.BatchNorm2d(256)                                         # 注意最后一次卷积结束不加ReLU激活函数)self.layer1_next = nn.Sequential(nn.Conv2d(256, 64, kernel_size=1, stride=1, padding=0, bias=False),     # layer1_next的第一次卷积负责将channel减少,减少训练参数量nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.Conv2d(64, 256, kernel_size=1, stride=1, padding=0, bias=False),     # layer1_next的最后一次卷积负责将channel增加至可以与shortcut相加nn.BatchNorm2d(256))# --------------------------------------------------------------    # layer234操作基本相同,这里仅介绍layer2self.layer2_first = nn.Sequential(nn.Conv2d(256, 128, kernel_size=1, stride=1, padding=0, bias=False),    # 与layer1_first第一次卷积不同,需要降channel至1/2nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.Conv2d(128, 128, kernel_size=3, stride=2, padding=1, bias=False),    # 注意这里的stride=2与layer34相同,与layer1区别nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.Conv2d(128, 512, kernel_size=1, stride=1, padding=0, bias=False),    # 再次升channelnn.BatchNorm2d(512))self.layer2_next = nn.Sequential(nn.Conv2d(512, 128, kernel_size=1, stride=1, padding=0, bias=False),    # 负责循环普通的操作nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(128),nn.ReLU(inplace=True),nn.Conv2d(128, 512, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(512))# --------------------------------------------------------------self.layer3_first = nn.Sequential(nn.Conv2d(512, 256, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.Conv2d(256, 256, kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.Conv2d(256, 1024, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(1024))self.layer3_next = nn.Sequential(nn.Conv2d(1024, 256, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(256),nn.ReLU(inplace=True),nn.Conv2d(256, 1024, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(1024))# --------------------------------------------------------------self.layer4_first = nn.Sequential(nn.Conv2d(1024, 512, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 512, kernel_size=3, stride=2, padding=1, bias=False),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 2048, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(2048))self.layer4_next = nn.Sequential(nn.Conv2d(2048, 512, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Conv2d(512, 2048, kernel_size=1, stride=1, padding=0, bias=False),nn.BatchNorm2d(2048))self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))     # 经过最后的自适应均值池化为[batch, 2048, 1, 1]# 定义最后的全连接层self.fc = nn.Sequential(nn.Dropout(p=0.5),                  # 以0.5的概率失活神经元nn.Linear(2048 * 1 * 1, 1024),      # 第一个全连接层nn.ReLU(inplace=True),nn.Dropout(p=0.5),nn.Linear(1024, classes_num)        # 第二个全连接层,输出类结果)"""forward()前向传播函数负责将ResNet类中定义的网络层复用,再与上面的DownSample类完美组合"""def forward(self, x):out = self.pre(x)                                       # 对输入预处理,输出out = [batch, 64, 56, 56]"""每一层layer操作由两个部分组成,第一个是带有虚线的卷积单元,其他的是循环完成普通的shortcut为直连的卷积单元"""layer1_shortcut1 = DownSample(64, 256, 1)               # 使用DownSample实例化一个网络模型layer1_shortcut1,参数即是虚线处升channel数据,注意stride=1layer1_shortcut1.to('cuda:0')layer1_identity1 = layer1_shortcut1(out)                # 调用layer1_shortcut1对卷积单元输入out计算虚线处的identity,用于后面与卷积单元输出相加out = self.layer1_first(out)                            # 调用layer1_first完成layer1的第一个特殊的卷积单元out = F.relu(out + layer1_identity1, inplace=True)      # 将identity与卷积单元输出相加,经过relu激活函数for i in range(2):                                      # 使用循环完成后面几个相同输入输出相同操作的卷积单元layer_identity = out                                # 直接直连identity等于输入out = self.layer1_next(out)                         # 输入经过普通卷积单元out = F.relu(out + layer_identity, inplace=True)    # 两路结果相加,再经过激活函数# --------------------------------------------------------------后面layer234都是类似的,这里仅介绍layer2layer2_shortcut1 = DownSample(256, 512, 2)              # 注意后面layer234输入输出channel不同,stride=2都是如此layer2_shortcut1.to('cuda:0')layer2_identity1 = layer2_shortcut1(out)out = self.layer2_first(out)out = F.relu(out + layer2_identity1, inplace=True)      # 完成layer2的第一个卷积单元for i in range(3):                                      # 循环执行layer2剩下的其他卷积单元layer_identity = outout = self.layer2_next(out)out = F.relu(out + layer_identity, inplace=True)# --------------------------------------------------------------layer3_shortcut1 = DownSample(512, 1024, 2)layer3_shortcut1.to('cuda:0')layer3_identity1 = layer3_shortcut1(out)out = self.layer3_first(out)out = F.relu(out + layer3_identity1, inplace=True)for i in range(5):layer_identity = outout = self.layer3_next(out)out = F.relu(out + layer_identity, inplace=True)# --------------------------------------------------------------layer4_shortcut1 = DownSample(1024, 2048, 2)layer4_shortcut1.to('cuda:0')layer4_identity1 = layer4_shortcut1(out)out = self.layer4_first(out)out = F.relu(out + layer4_identity1, inplace=True)for i in range(2):layer_identity = outout = self.layer4_next(out)out = F.relu(out + layer_identity, inplace=True)# 最后一个全连接层out = self.avg_pool(out)                # 经过最后的自适应均值池化为[batch, 2048, 1, 1]out = out.reshape(out.size(0), -1)      # 将卷积输入[batch, 2048, 1, 1]展平为[batch, 2048*1*1]out = self.fc(out)                  # 经过最后一个全连接单元,输出分类outreturn out

ResNet50的训练可以参照我的ResNet18搭建中的训练和测试部分:
使用PyTorch搭建ResNet18网络并使用CIFAR10数据集训练测试

经过手写ResNet50网络模型的暴力搭建,我认识到了要想把ResNet及其其他复杂网络的搭建,前提必须要把模型整个流程环节全部弄清楚
例如,ResNet50里面每一次的shortcut里面的升维操作的in_channel,out_channel,kernel_size,stride,padding的参数大小变化
每一个卷积单元具体参数都是什么样的,如何才能最大化简化代码;
还有就是搭建复杂的网络模型中,一定要做到步步为营,一步步搭建并检验,每一步都要理解有理有据,最后才能将整个网络搭建起来
还有一个意外收获就是在训练过程中,发现了这样的报错:
Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same
原来是因为输入的数据类型为torch.cuda.FloatTensor,说明输入数据在GPU中。模型参数的数据类型为torch.FloatTensor,说明模型还在CPU
故在ResNet50的forward()函数中对实例化的DownSample网络添加到和train.py对ResNet50实例化的网络模型的同一个GPU下,解决了错误

使用PyTorch搭建ResNet50网络相关推荐

  1. 使用Pytorch搭建U-Net网络并基于DRIVE数据集训练(语义分割)学习笔记

    使用Pytorch搭建U-Net网络并基于DRIVE数据集训练(语义分割)学习笔记 https://www.bilibili.com/video/BV1rq4y1w7xM?spm_id_from=33 ...

  2. 实战:使用Pytorch搭建分类网络(肺结节假阳性剔除)

    实战:使用Pytorch搭建分类网络(肺结节假阳性剔除) 阅前可看: 实战:使用yolov3完成肺结节检测(Luna16数据集)及肺实质分割 其中的脚本资源getMat.py文件是对肺结节进行切割. ...

  3. Pytorch搭建LeNet5网络

    本讲目标:   介绍Pytorch搭建LeNet5网络的流程. Pytorch八股法搭建LeNet5网络 1.LeNet5网络介绍 2.Pytorch搭建LeNet5网络 2.1搭建LeNet网络 2 ...

  4. Pytorch搭建FCN网络

    Pytorch搭建FCN网络 前言 原理 代码实现 前言 FCN 全卷积网络,用卷积层替代CNN的全连接层,最后通过转置卷积层得到一个和输入尺寸一致的预测结果: 原理 为了得到更好的分割结果,论文中提 ...

  5. pytorch 搭建 VGG 网络

    目录 1. VGG 网络介绍 2. 搭建VGG 网络 3. code 1. VGG 网络介绍 VGG16 的网络结构如图: VGG 网络是由卷积层和池化层构成基础的CNN 它的CONV卷积层的参数全部 ...

  6. pytorch搭建孪生网络比较人脸相似性

    参考文献: 神经网络学习小记录52--Pytorch搭建孪生神经网络(Siamese network)比较图片相似性_Bubbliiiing的博客-CSDN博客_神经网络图片相似性 Python - ...

  7. pytorch搭建Resnet50实现狗狗120个品种类的分类

    此项目出自Kaggle竞赛 项目介绍: 谁是好狗?谁喜欢搔耳朵?好吧,看来那些花哨的深度神经网络并没有解决所有问题.然而,也许它们能回答我们在遇到四条腿的陌生人时普遍会问的问题:这是什么样的好狗狗? ...

  8. Pytorch搭建GoogLeNet网络(奥特曼分类)

    1 爬取奥特曼 get_data.py import requests import urllib.parse as up import json import time import osmajor ...

  9. 4.2 使用pytorch搭建VGG网络

    文章目录 将VGG分成两部分 提取特征网络结构 分类网络结构 model 输入:非关键字参数或有序字典 P[ython-非关键字参数和关键字参数(*args **kw)](https://blog.c ...

最新文章

  1. python【蓝桥杯vip练习题库】ALGO-142 P1103(复数运算)
  2. BI-SqlServer
  3. 文件如何存储c语言,急求如何将下列C语言程序数据存储到文件中?
  4. C#字典转换成where条件
  5. Servlet期末复习笔记
  6. 指向类成员的指针并非指针
  7. 主键和外键举例_数据库-主键和外键及其约束
  8. VSCode 如何修改字体
  9. CRC校验算法及C++程序实现
  10. redhat官网操作文档查找
  11. java ssm 运行步骤_SSM项目整合基本步骤
  12. 机器人正向运动学和D-H参数方法
  13. springbus类是做什么用的_SpringCloud-Bus组件的使用
  14. 历届奥斯卡最佳影片及下载地址
  15. 高斯投影坐标计算例题_高斯投影坐标计算程序下载
  16. 使用weixin-java-miniapp配置进行单个小程序的配置
  17. Nginx支持url不区分大小写
  18. JS逆向之人口流动态势
  19. 关系;关系模式;关系数据库
  20. Android仿英雄联盟/斗鱼波形加载动画

热门文章

  1. 西部广播电视杂志西部广播电视杂志社《西部广播电视》杂志社2023年第1期目录
  2. openjudge 雷涛的小猫
  3. js和 ts 将大数字金额转换成带单位的数字金额,万,千万,亿,格式化金额数字,格式化成带单位的金额,附ts版代码
  4. 为大家推荐4款对日常工作很有帮助的PC软件
  5. SAP HANA 平台介绍
  6. html页面按钮隐藏div显示,javascript 控制 DIV等html元素的显示和隐藏
  7. tinyproxy代理服务器配置
  8. 炒股软件“大智慧”的一些快捷键
  9. Bringing Old Photos Back to Life微软老照片修复全解析(原理、代码、训练、测试)
  10. 死锁产生的四个必要条件