Pytorch从零构建ResNet

第一章 从零构建ResNet18
第二章 从零构建ResNet50


文章目录

  • Pytorch从零构建ResNet
  • 前言
  • 一、ResNet是什么?
    • 1. 残差学习
    • 2. ResNet具体结构
  • 二、ResNet分步骤实现
  • 三、完整例子+测试
  • 总结

前言

  • ResNet 目前是应用很广的网络基础框架,所以有必要了解一下,并且resnet结构清晰,适合练手

  • pytorch就更不用多说了。(坑自坑 ) 懂自懂

  • 本文使用以下环境构筑

    torch 1.11
    torchvision 0.12.0
    python 3.9
    

一、ResNet是什么?

深度残差网络(Deep residual network, ResNet)的提出是CNN图像史上的一件里程碑事件,具体多牛,大家自己某度咯。ResNet的作者何恺明也因此摘得CVPR2016最佳论文奖,当然何博士的成就远不止于此,感兴趣的也可以去搜一下他后来的辉煌战绩。下面简单讲述ResNet的理论及实现。

1. 残差学习

深度网络的退化问题至少说明深度网络不容易训练。但是我们考虑这样一个事实:现在你有一个浅层网络,你想通过向上堆积新层来建立深层网络,一个极端情况是这些增加的层什么也不学习,仅仅复制浅层网络的特征,即这样新层是恒等映射(Identity mapping)。在这种情况下,深层网络应该至少和浅层网络性能一样,也不应该出现退化现象。

这个有趣的假设让何博士灵感爆发,他提出了残差学习来解决退化问题。对于一个堆积层结构(几层堆积而成)。对于一个堆积层结构(几层堆积而成)当输入为 x时其学习到的特征记为F(x), 现在再加一条分支,直接跳到堆积层的输出,则此时最终输出H(x) = F(x) + x

如下图

这种跳跃连接就叫做shortcut connection(类似电路中的短路)。具体原理这边不展开叙说,感兴趣的可以去看原论文。上面这种两层结构的叫BasicBlock,一般适用于ResNet18ResNet34,而ResNet50以后都使用下面这种三层的残差结构叫Bottleneck

2. ResNet具体结构

上面简单说了以下残差网络与普通卷积的区别,接下来用图来看一下网络的结构

可以看到,18层的网络有五个部分组成,从conv2开始,每层都有两个有残差块,并且每个残差块具有2个卷积层。接下来再具体看看18层以及50层的具体结构

其中,蓝色部分为conv2,然后往下依次按颜色划分为conv3、conv4,conv5。需要注意的是,从conv3开始,第一个残差块的第一个卷积层的stride为2,这是每层图片尺寸变化的原因。另外,stride为2的时候,每层的维度也就是channel也发生了变化,这这时候,残差与输出不是直接相连的,因为维度不匹配,需要进行升维,也就是上图中虚线连接的残差块,实线部分代表可以直接相加.

二、ResNet分步骤实现

首先实现残差块:

class BasicBlock(nn.Module):def __init__(self,in_channels,out_channels,stride=[1,1],padding=1) -> None:super(BasicBlock, self).__init__()# 残差部分self.layer = nn.Sequential(nn.Conv2d(in_channels,out_channels,kernel_size=3,stride=stride[0],padding=padding,bias=False),nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True), # 原地替换 节省内存开销nn.Conv2d(out_channels,out_channels,kernel_size=3,stride=stride[1],padding=padding,bias=False),nn.BatchNorm2d(out_channels))# shortcut 部分# 由于存在维度不一致的情况 所以分情况self.shortcut = nn.Sequential()if stride[0] != 1 or in_channels != out_channels:self.shortcut = nn.Sequential(# 卷积核为1 进行升降维# 注意跳变时 都是stride==2的时候 也就是每次输出信道升维的时候nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], bias=False),nn.BatchNorm2d(out_channels))def forward(self, x):out = self.layer(x)out += self.shortcut(x)out = F.relu(out)return out

接下来是ResNet18的具体实现

# 采用bn的网络中,卷积层的输出并不加偏置
class ResNet18(nn.Module):def __init__(self, BasicBlock, num_classes=10) -> None:super(ResNet18, self).__init__()self.in_channels = 64# 第一层作为单独的 因为没有残差快self.conv1 = nn.Sequential(nn.Conv2d(3,64,kernel_size=7,stride=2,padding=3,bias=False),nn.BatchNorm2d(64),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))# conv2_xself.conv2 = self._make_layer(BasicBlock,64,[[1,1],[1,1]])# conv3_xself.conv3 = self._make_layer(BasicBlock,128,[[2,1],[1,1]])# conv4_xself.conv4 = self._make_layer(BasicBlock,256,[[2,1],[1,1]])# conv5_xself.conv5 = self._make_layer(BasicBlock,512,[[2,1],[1,1]])self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512, num_classes)#这个函数主要是用来,重复同一个残差块def _make_layer(self, block, out_channels, strides):layers = []for stride in strides:layers.append(block(self.in_channels, out_channels, stride))self.in_channels = out_channelsreturn nn.Sequential(*layers)def forward(self, x):out = self.conv1(x)out = self.conv2(out)out = self.conv3(out)out = self.conv4(out)out = self.conv5(out)# out = F.avg_pool2d(out,7)out = self.avgpool(out)out = out.reshape(x.shape[0], -1)out = self.fc(out)return out

可以输出网络结构看一下

res18 = ResNet18(BasicBlock)
print(res18)
ResNet18((conv1): Sequential((0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False))(conv2): Sequential((0): BasicBlock((layer): Sequential((0): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential())(1): BasicBlock((layer): Sequential((0): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential()))(conv3): Sequential((0): BasicBlock((layer): Sequential((0): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential((0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(1): BasicBlock((layer): Sequential((0): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential()))(conv4): Sequential((0): BasicBlock((layer): Sequential((0): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential((0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(1): BasicBlock((layer): Sequential((0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential()))(conv5): Sequential((0): BasicBlock((layer): Sequential((0): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential((0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)))(1): BasicBlock((layer): Sequential((0): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True))(shortcut): Sequential()))(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))(fc): Linear(in_features=512, out_features=10, bias=True)
)

至此,Resnet18就构建好了

三、完整例子+测试

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, utils
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
import numpy as np
from torch.utils.data.dataset import Dataset
from torchvision.transforms import transforms
from pathlib import Path
import cv2
from PIL import Image
import torch.nn.functional as F
%matplotlib inline
%config InlineBackend.figure_format = 'svg' # 控制显示transform = transforms.Compose([ToTensor(),transforms.Normalize(mean=[0.5,0.5,0.5],std=[0.5,0.5,0.5]),transforms.Resize((224, 224))])training_data = datasets.CIFAR10(root="data",train=True,download=True,transform=transform,
)testing_data = datasets.CIFAR10(root="data",train=False,download=True,transform=transform,
)class BasicBlock(nn.Module):def __init__(self,in_channels,out_channels,stride=[1,1],padding=1) -> None:super(BasicBlock, self).__init__()# 残差部分self.layer = nn.Sequential(nn.Conv2d(in_channels,out_channels,kernel_size=3,stride=stride[0],padding=padding,bias=False),nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True), # 原地替换 节省内存开销nn.Conv2d(out_channels,out_channels,kernel_size=3,stride=stride[1],padding=padding,bias=False),nn.BatchNorm2d(out_channels))# shortcut 部分# 由于存在维度不一致的情况 所以分情况self.shortcut = nn.Sequential()if stride != 1 or in_channels != out_channels:self.shortcut = nn.Sequential(# 卷积核为1 进行升降维nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], bias=False),nn.BatchNorm2d(out_channels))def forward(self, x):
#         print('shape of x: {}'.format(x.shape))out = self.layer(x)
#         print('shape of out: {}'.format(out.shape))
#         print('After shortcut shape of x: {}'.format(self.shortcut(x).shape))out += self.shortcut(x)out = F.relu(out)return out# 采用bn的网络中,卷积层的输出并不加偏置
class ResNet18(nn.Module):def __init__(self, BasicBlock, num_classes=10) -> None:super(ResNet18, self).__init__()self.in_channels = 64# 第一层作为单独的 因为没有残差快self.conv1 = nn.Sequential(nn.Conv2d(3,64,kernel_size=7,stride=2,padding=3,bias=False),nn.BatchNorm2d(64),nn.MaxPool2d(kernel_size=3, stride=2, padding=1))# conv2_xself.conv2 = self._make_layer(BasicBlock,64,[[1,1],[1,1]])# self.conv2_2 = self._make_layer(BasicBlock,64,[1,1])# conv3_xself.conv3 = self._make_layer(BasicBlock,128,[[2,1],[1,1]])# self.conv3_2 = self._make_layer(BasicBlock,128,[1,1])# conv4_xself.conv4 = self._make_layer(BasicBlock,256,[[2,1],[1,1]])# self.conv4_2 = self._make_layer(BasicBlock,256,[1,1])# conv5_xself.conv5 = self._make_layer(BasicBlock,512,[[2,1],[1,1]])# self.conv5_2 = self._make_layer(BasicBlock,512,[1,1])self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512, num_classes)#这个函数主要是用来,重复同一个残差块def _make_layer(self, block, out_channels, strides):layers = []for stride in strides:layers.append(block(self.in_channels, out_channels, stride))self.in_channels = out_channelsreturn nn.Sequential(*layers)def forward(self, x):out = self.conv1(x)out = self.conv2(out)out = self.conv3(out)out = self.conv4(out)out = self.conv5(out)#         out = F.avg_pool2d(out,7)out = self.avgpool(out)out = out.reshape(x.shape[0], -1)out = self.fc(out)return out# 保持数据集和测试机能完整划分
batch_size=100
train_data = DataLoader(dataset=training_data,batch_size=batch_size,shuffle=True,drop_last=True)
test_data = DataLoader(dataset=testing_data,batch_size=batch_size,shuffle=True,drop_last=True)images,labels = next(iter(train_data))
print(images.shape)
img = utils.make_grid(images)
img = img.numpy().transpose(1,2,0)
mean=[0.5,0.5,0.5]
std=[0.5,0.5,0.5]
img = img * std + mean
print([labels[i] for i in range(64)])
plt.imshow(img)device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = res18.to(device)
cost = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())print(len(train_data))
print(len(test_data))
epochs = 10
for epoch in range(epochs):running_loss = 0.0running_correct = 0.0model.train()print("Epoch {}/{}".format(epoch+1,epochs))print("-"*10)for X_train,y_train in train_data:# X_train,y_train = torch.autograd.Variable(X_train),torch.autograd.Variable(y_train)X_train,y_train = X_train.to(device), y_train.to(device)outputs = model(X_train)_,pred = torch.max(outputs.data,1)optimizer.zero_grad()loss = cost(outputs,y_train)loss.backward()optimizer.step()running_loss += loss.item()running_correct += torch.sum(pred == y_train.data)testing_correct = 0test_loss = 0model.eval()for X_test,y_test in test_data:# X_test,y_test = torch.autograd.Variable(X_test),torch.autograd.Variable(y_test)X_test,y_test = X_test.to(device), y_test.to(device)outputs = model(X_test)loss = cost(outputs,y_test)_,pred = torch.max(outputs.data,1)testing_correct += torch.sum(pred == y_test.data)test_loss += loss.item()print("Train Loss is:{:.4f}, Train Accuracy is:{:.4f}%, Test Loss is::{:.4f} Test Accuracy is:{:.4f}%".format(running_loss/len(training_data), 100*running_correct/len(training_data),test_loss/len(testing_data),100*testing_correct/len(testing_data)))

可以看看结果 还是不错的

到目前为止,pytorch构建resnet18就完成了。接下来会构建50

总结

通过手写网络,可以加深对网络的理解,同时运用pytorch更加熟练。

PS: 此博客同时更新于个人博客

【ResNet】Pytorch从零构建ResNet18相关推荐

  1. 【ResNet】Pytorch从零构建ResNet50

    Pytorch从零构建ResNet 第一章 从零构建ResNet18 第二章 从零构建ResNet50 文章目录 Pytorch从零构建ResNet 前言 一.Res50和Res18的区别? 1. 残 ...

  2. Pytorch实战2:ResNet-18实现Cifar-10图像分类(测试集分类准确率95.170%)

    版权说明:此文章为本人原创内容,转载请注明出处,谢谢合作! Pytorch实战2:ResNet-18实现Cifar-10图像分类 实验环境: Pytorch 0.4.0 torchvision 0.2 ...

  3. 基于PaddlePaddle构建ResNet18残差神经网络的食物图片分类问题

    基于PaddlePaddle构建ResNet18残差神经网络的食物图片分类问题 Introduction 本项目是在李宏毅机器学习课程的作业3进行的工作,任务是手动搭建一个CNN模型进行食物图片分类( ...

  4. 《从零构建前后分离web项目》:开篇 - 纵观WEB历史演变

    开篇 : 纵观WEB历史演变 在校学习和几年工作工作中不知不觉经历了一半的 WEB 历史演变.对近几年的发展比较了解,结合经验聊聊 WEB 发展历史. 演变不易,但也是必然,因为为人始终要进步. WE ...

  5. luatex plain 从零构建

    http://garfileo.is-programmer.com/2010/9/23/luatex-plain.21484.html http://garfileo.is-programmer.co ...

  6. 手把手教你从零构建属于自己的小linux

    从零构建属于自己的小linux 本文讲述的是利用一个宿主机一步步根据自己喜好构建属于自己的一个小型linux系统. 直接切入正题开始构建属于自己的linux系统. 准备工作: 本次制是在VMware ...

  7. 【Flutter教程】从零构建电商应用(一)

    在这个系列中,我们将学习如何使用google的移动开发框架flutter创建一个电商应用.本文是flutter框架系列教程的第一部分,将学习如何安装Flutter开发环境并创建第一个Flutter应用 ...

  8. 《从零构建前后分离的web项目》准备 - 前端了解过关了吗?

    前端基础架构和硬核介绍 技术栈的选择 首先我们构建前端架构需要对前端生态圈有一切了解,并且最好带有一定的技术前瞻性,好的技术架构可能日后会方便的扩展,减少重构的次数,即使重构也不需要大动干戈,我通常选 ...

  9. 打包node服务端_如何基于NodeJS从零构建线上自动化打包工作流?

    前言 NodeJS在前端领域正扮演着越越重要的地位,它不仅可以让前端工作者使用javascript编写后端代码,还能方便地搭建响应速度快.易于扩展的网络应用.Node.js 使用事件驱动,非阻塞I/O ...

  10. Build-Docker-Image-from-Zero: 从零构建Docker镜像

    Build-Docker-Image-from-Zero 荣涛 2022-03-11 文档修改日志 日期 修改内容 修改人 备注 2022-03-11 创建 荣涛 引言 本文档主要讲构建基础Docke ...

最新文章

  1. 调度场算法与逆波兰表达式
  2. .net core ——微服务内通信Thrift和Http客户端响应比较
  3. centos7.5 su: 无法设置组: 不允许的操作(实测补充)(这是乱获取权限导致的,要注意权限问题)以及推荐文件操作
  4. 计算机软件保护问题研究,计算机软件专利保护问题-研究.pdf
  5. 制作U盘启动的并可保持更改更新和设置的BT4最终版完全手册
  6. 抽象工厂模式---创建型
  7. Ubuntu 14.04安装LAMP(Linux,Apache,MySQL,PHP)
  8. flash mx拖拽实例_Flash MX 2004 Professional的百叶窗过渡效果
  9. Protel 99se汉化步骤
  10. ffmpeg 踩坑总结 —— 视频转码 转H264格式
  11. 《黑马程序员Android移动应用基础教程》学习笔记(1)
  12. Win11查看电脑磁盘分区格式的方法教学
  13. CodeForces - 1040B Shashlik Cooking (思维/贪心)
  14. NCIS调查表辅助工具-病案首页数据上传-病案数据统计
  15. Unity 代码帧动画
  16. C# 参考 cool edit 样式, 绘制音频波形图
  17. amoled和super amoled的区别 amoled和super amoled哪个更好
  18. TreeSet的使用方法总结、实现原理、使用示例
  19. SQL Server 与Oracle 建表语句的不同之处
  20. 深度学习数据标注工具

热门文章

  1. 用java web实现聊天室_java web实现简单聊天室
  2. 关于STM32Flash详解
  3. 盘点AI江湖中,清华人的“无问西东”
  4. 剪贴板查看器clipbrd.exe
  5. 安装配置管理 之 LumaQQ 的安装和使用
  6. 欧姆龙OMRON CP1H  PLC与台达 DOP-B触摸屏通讯
  7. mi5splus android9,小米MIUI 9.5国际版稳定版开始推送:超30款机型将获更新
  8. Windows使用快捷键
  9. android hd 输入法,Android上好用的Lime HD中文输入法03--更好的使用篇
  10. xp系统整个计算机非常慢,xp系统物理内存不足导致电脑运行速度非常缓慢的图文方法...