使用PyTorch搭建ResNet50网络
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网络相关推荐
- 使用Pytorch搭建U-Net网络并基于DRIVE数据集训练(语义分割)学习笔记
使用Pytorch搭建U-Net网络并基于DRIVE数据集训练(语义分割)学习笔记 https://www.bilibili.com/video/BV1rq4y1w7xM?spm_id_from=33 ...
- 实战:使用Pytorch搭建分类网络(肺结节假阳性剔除)
实战:使用Pytorch搭建分类网络(肺结节假阳性剔除) 阅前可看: 实战:使用yolov3完成肺结节检测(Luna16数据集)及肺实质分割 其中的脚本资源getMat.py文件是对肺结节进行切割. ...
- Pytorch搭建LeNet5网络
本讲目标: 介绍Pytorch搭建LeNet5网络的流程. Pytorch八股法搭建LeNet5网络 1.LeNet5网络介绍 2.Pytorch搭建LeNet5网络 2.1搭建LeNet网络 2 ...
- Pytorch搭建FCN网络
Pytorch搭建FCN网络 前言 原理 代码实现 前言 FCN 全卷积网络,用卷积层替代CNN的全连接层,最后通过转置卷积层得到一个和输入尺寸一致的预测结果: 原理 为了得到更好的分割结果,论文中提 ...
- pytorch 搭建 VGG 网络
目录 1. VGG 网络介绍 2. 搭建VGG 网络 3. code 1. VGG 网络介绍 VGG16 的网络结构如图: VGG 网络是由卷积层和池化层构成基础的CNN 它的CONV卷积层的参数全部 ...
- pytorch搭建孪生网络比较人脸相似性
参考文献: 神经网络学习小记录52--Pytorch搭建孪生神经网络(Siamese network)比较图片相似性_Bubbliiiing的博客-CSDN博客_神经网络图片相似性 Python - ...
- pytorch搭建Resnet50实现狗狗120个品种类的分类
此项目出自Kaggle竞赛 项目介绍: 谁是好狗?谁喜欢搔耳朵?好吧,看来那些花哨的深度神经网络并没有解决所有问题.然而,也许它们能回答我们在遇到四条腿的陌生人时普遍会问的问题:这是什么样的好狗狗? ...
- Pytorch搭建GoogLeNet网络(奥特曼分类)
1 爬取奥特曼 get_data.py import requests import urllib.parse as up import json import time import osmajor ...
- 4.2 使用pytorch搭建VGG网络
文章目录 将VGG分成两部分 提取特征网络结构 分类网络结构 model 输入:非关键字参数或有序字典 P[ython-非关键字参数和关键字参数(*args **kw)](https://blog.c ...
最新文章
- python【蓝桥杯vip练习题库】ALGO-142 P1103(复数运算)
- BI-SqlServer
- 文件如何存储c语言,急求如何将下列C语言程序数据存储到文件中?
- C#字典转换成where条件
- Servlet期末复习笔记
- 指向类成员的指针并非指针
- 主键和外键举例_数据库-主键和外键及其约束
- VSCode 如何修改字体
- CRC校验算法及C++程序实现
- redhat官网操作文档查找
- java ssm 运行步骤_SSM项目整合基本步骤
- 机器人正向运动学和D-H参数方法
- springbus类是做什么用的_SpringCloud-Bus组件的使用
- 历届奥斯卡最佳影片及下载地址
- 高斯投影坐标计算例题_高斯投影坐标计算程序下载
- 使用weixin-java-miniapp配置进行单个小程序的配置
- Nginx支持url不区分大小写
- JS逆向之人口流动态势
- 关系;关系模式;关系数据库
- Android仿英雄联盟/斗鱼波形加载动画
热门文章
- 西部广播电视杂志西部广播电视杂志社《西部广播电视》杂志社2023年第1期目录
- openjudge 雷涛的小猫
- js和 ts 将大数字金额转换成带单位的数字金额,万,千万,亿,格式化金额数字,格式化成带单位的金额,附ts版代码
- 为大家推荐4款对日常工作很有帮助的PC软件
- SAP HANA 平台介绍
- html页面按钮隐藏div显示,javascript 控制 DIV等html元素的显示和隐藏
- tinyproxy代理服务器配置
- 炒股软件“大智慧”的一些快捷键
- Bringing Old Photos Back to Life微软老照片修复全解析(原理、代码、训练、测试)
- 死锁产生的四个必要条件