【ResNet】Pytorch从零构建ResNet18

article/2025/11/10 15:35:11

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

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

2. ResNet具体结构

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

image-20220521105221093.png

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

res18&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: 此博客同时更新于个人博客


http://chatgpt.dhexx.cn/article/l6d8nIKG.shtml

相关文章

HTML+CSS 简单的顶部导航栏菜单制作

导航栏的制作: 技术要求: CSSHTML各类标签 实现目的: 制作导航栏菜单 代码分析: 基本样式清除无序列原点删除下划线删除文字默认居中a标签设置块级元素伪类选择器对a状态修饰 分步实现: 分三栏布局:…

WEB前端(7)—— 简单的 HTML+CSS 导航栏案例

适合每个新手的导航栏&#xff1a; 代码与运行效果如图&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>导航栏</title><style type"text/css">ul{/*设置导航栏的框框*/margin: 30px…

CSS — 导航栏篇(一)

Navigation Bar Navigation Bar 是什么&#xff1f;这就是每个网站都会有的导航栏&#xff0c;本文将会带你接触导航栏的世界。首先我们需要了解导航栏的作用——它能快速帮助用户进行需求选择。一个清晰的导航栏能让用户第一时间了解网站的基本模块功能&#xff0c;而且作为网…

CSS + HTML导航栏效果

今天写了一个导航栏&#xff0c;需要的效果如下&#xff1a; 实现的代码思路如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>导航栏</title><style type"text/css&qu…

html+css创建侧边导航栏

效果&#xff1a; 代码&#xff1a; .left{position: fixed;width: 250px;height: 2000px;background-color: rgb(100, 93, 93);float: left;text-align: center; } .nav a{display: block;width: 247px;height: 70px;background-color: rgb(100, 93, 93);color: rgb(254, 254…

HTML5+CSS3制作底部导航栏

目录 前言 一、底部导航栏示例图 二、HTML框架 1.一号盒子 2.二号盒子 总结 ​​​​​​ 前言 在日常的网上冲浪中&#xff0c;我们常常在网页最底部&#xff0c;看到一大堆链接&#xff0c;非常整齐&#xff0c;一目了然&#xff0c;那么是如何实现的呢&#xff1f;..…

网页制作之侧边导航栏(只用HTML实现)

话不多说&#xff0c;上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" con…

CSS【导航栏】

导航栏链接列表 作为标准的HTML基础一个导航栏是必须的。在我们的例子中我们将建立一个标准的HTML列表导航栏。导航条基本上是一个链接列表&#xff0c;所以使用 <ul> 和 <li>元素非常有意义&#xff1a; <!DOCTYPE html><html><head><meta…

html中网页导航栏设置

以下内容是摘抄博客&#xff1a;https://www.runoob.com/css/css-navbar.html 设计导航窗口在左侧的显示如下&#xff1a; 代码部分则如下&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>菜鸟教程(runoo…

html左侧导航栏右侧显示内容

效果图 代码 复制下来直接运行就可以 <!doctype html> <html lang "en"><head><meta charset "UTF-8"><meta name "viewport"content "widthdevice-width, user-scalableno, initial-scale1.0, maximum-s…

导航栏的HTML的布局方式

1.利用浮动完成布局 以小米导航栏为例 <style>* {padding: 0;margin: 0;/* 通配符全选&#xff0c;取消内外边距的小缝隙 // 不建议使用通配符 */}header {width: 100%;background-color: #333333;/* 设置背景 */}div {width: 1226px;height: 40px;margin: auto;/* 设…

html里制作简单导航栏

今天简单的做了一下网页里的导航栏。 效果如下&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>实验3</title><style type"text/css">ul{/*设置导航栏的框框*/margi…

html中关于侧边导航栏和导航栏的编写

侧边导航栏 <style>.box{width: 50px;height: 50px;background-color: #483957;transition: width .5s,background-color .2s;}.box:hover{background-color: #004FCB;width: 200px;cursor: pointer;}.a1{position: fixed;right: 40px;top: 200px;float: right;}</st…

Html顶部导航栏实现

顶部导航nav栏实现&#xff08;包括一级菜单&#xff0c;二级菜单&#xff09; 实现效果&#xff1a; 代码如下~ Html部分&#xff1a; <!doctype html> <html> <head> <meta charset"utf-8"> <title>顶部导航栏</title> <…

CSS+HTML 顶部导航栏实现

导航栏的实现、固定顶部导航栏、二级菜单实现 效果图&#xff1a; 2018/11/16更新&#xff1a; 最近在使用这个导航栏的时候&#xff0c;发现页面在放大和缩小的情况下&#xff0c;导航栏的布局和显示都有些小问题&#xff0c;所以重新改了一下css部分的代码&#xff0c;重新贴…

导航栏html代码

效果如下 html 代码 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>导航制作</title><link rel"stylesheet" href"css/style.css"><link rel"stylesheet" href"css…

html+css实现页面顶部导航栏

最终效果如下&#xff1a; 接下来&#xff0c;我将从html和css两大部分&#xff0c;逐步为您讲解制作过程 目录 Html 实现布局 创建父栏目 创建子栏目 插入外部样式表&#xff0c;为接下来的css编辑做准备 Css 实现样式 排布文本&#xff0c;设置背景 交互效果的实现 …

HTML侧边导航栏

HTML侧边导航栏 简介&#xff1a;本文用最简洁的语言&#xff0c;来教会读者&#xff0c;如果用htmlcss来制作&#xff0c;侧边导航栏&#xff0c;本案例以手机商城中的部分为例子来制作。 第一步&#xff1a;构建框架 <body><!-- 首先确定导航栏中的内容 每个内容…

用HTML来做导航栏

首先建一个大盒子&#xff0c;名为 box 用于装导航栏里面的部分。代码如下&#xff1a; <div id"box"> <div> 然后再给这个大盒子加一点定义&#xff0c;给他一个长和宽 #box {width: 100%;height: 20px;background-color: rgb(227, 228, 229);font-size…

HTML导航栏的四种制作方法

1.首先&#xff0c;大家可以直接使用html5中的导航栏标签<nav></nav> 具体代码如下 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>导航栏</title></head><body><nav><a href&q…