写点什么

用 Python 从零开始构建 ResNet

  • 2021-06-09
  • 本文字数:3473 字

    阅读完需:约 11 分钟

用Python从零开始构建ResNet

近年来,深度学习和计算机视觉领域取得了一系列突破。特别是行业引入了非常深的卷积神经网络后,在这些模型的帮助下,图像识别和图像分类等问题取得了非常好的成果。


因此这些年来,深度学习架构变得越来越深(层越来越多)以解决越来越复杂的任务,这也有助于提高分类和识别任务的性能,并让它们表现稳健。


但当我们继续向神经网络添加更多层时,模型训练起来也越来越困难,模型的准确度开始饱和,然后还会下降。于是 ResNet 诞生了,让我们摆脱了这种窘境,并能帮助解决这个问题。

什么是 ResNet?

残差网络(ResNet)是著名的深度学习模型之一,由任少清、何开明、孙健和张翔宇在他们的论文中引入。这篇 2015 年的论文全名叫“Deep Residual Learning for Image Recognition”[1]。ResNet 模型是迄今为止广泛流行和最成功的深度学习模型之一。

残差块

随着这些残差(Residual)块的引入,训练非常深的网络时面临的问题得到了缓解,ResNet 模型由这些块组成。



来源:“图像识别的深度残差学习”论文


随着这些残差块的引入,训练非常深的网络时面临的问题得到了缓解,ResNet 模型由这些块组成。


在上图中,我们可以注意到的第一件事是跳过模型的某些层的直接连接。这种连接称为“跳过连接”,是残差块的核心。由于存在这种跳过连接,输出是不相同的。如果没有跳过连接,输入‘X 将乘以层的权重,然后添加一个偏置项。


然后是激活函数 f(),我们得到输出为 H(x)。


H(x)=f(wx+b)或 H(x)=f(x)


现在引入了新的跳过连接技术,输出 H(x)更改为


H(x)=f(x)+x


但是输入的维度可能与输出的维度不同,这可能发生在卷积层或池化层中。因此,这个问题可以用这两种方法来处理:


  • 用跳过连接填充零以增加其维度。

  • 1×1 卷积层被添加到输入以匹配维度。在这种情况下,输出为:


H(x)=f(x)+w1.x


这里添加了一个额外的参数 w1,而在使用第一种方法时没有添加额外的参数。


ResNet 中的这些跳过连接技术通过梯度流经的替代快捷路径来解决深度 CNN 中梯度消失的问题。此外,如果有任何层损害了架构的性能,跳过连接也能起作用,它将被正则化跳过。

ResNet 的架构

架构中有一个 34 层的普通网络,其灵感来自 VGG-19,其中添加了快捷连接或跳过连接。这些跳过连接或残差块将架构转换为残差网络,如下图所示。



来源:“图像识别的深度残差学习”论文

将 ResNet 与 Keras 结合使用:

Keras 是一个开源深度学习库,能够在 TensorFlow 上运行。Keras Applications 提供以下 ResNet 版本。


  • ResNet50

  • ResNet50V2

  • ResNet101

  • ResNet101V2

  • ResNet152

  • ResNet152V2

让我们从零开始构建 ResNet:


来源:“图像识别的深度残差学习”论文


我们将上图作为参考,开始构建网络。


ResNet 架构多次使用 CNN 块,因此我们为 CNN 块创建一个类,它接受输入通道和输出通道。每个 conv 层之后都有一个 batchnorm2d。


import torchimport torch.nn as nn
复制代码


class block(nn.Module):def __init__(self, in_channels, intermediate_channels, identity_downsample=None, stride=1):super(block, self).__init__()self.expansion = 4self.conv1 = nn.Conv2d(in_channels, intermediate_channels, kernel_size=1, stride=1, padding=0, bias=False)self.bn1 = nn.BatchNorm2d(intermediate_channels)self.conv2 = nn.Conv2d(intermediate_channels,intermediate_channels,kernel_size=3,stride=stride,padding=1,bias=False)self.bn2 = nn.BatchNorm2d(intermediate_channels)self.conv3 = nn.Conv2d(intermediate_channels,intermediate_channels * self.expansion,kernel_size=1,stride=1,padding=0,bias=False)self.bn3 = nn.BatchNorm2d(intermediate_channels * self.expansion)self.relu = nn.ReLU()self.identity_downsample = identity_downsampleself.stride = stridedef forward(self, x):identity = x.clone()x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.conv2(x)x = self.bn2(x)x = self.relu(x)x = self.conv3(x)x = self.bn3(x)if self.identity_downsample is not None:identity = self.identity_downsample(identity)x += identityx = self.relu(x)return x
复制代码


然后创建一个 ResNet 类,它接受许多块、层、图像通道和类数的输入。在下面的代码中,函数‘_make_layer’


创建 ResNet 层,它接受块的输入、残差块数、输出通道和步幅。


class ResNet(nn.Module):def __init__(self, block, layers, image_channels, num_classes):super(ResNet, self).__init__()self.in_channels = 64self.conv1 = nn.Conv2d(image_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU()self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
复制代码


# Essentially the entire ResNet architecture are in these 4 lines belowself.layer1 = self._make_layer(block, layers[0], intermediate_channels=64, stride=1)self.layer2 = self._make_layer(block, layers[1], intermediate_channels=128, stride=2)self.layer3 = self._make_layer(block, layers[2], intermediate_channels=256, stride=2)self.layer4 = self._make_layer(block, layers[3], intermediate_channels=512, stride=2)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.fc = nn.Linear(512 * 4, num_classes)def forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.reshape(x.shape[0], -1)x = self.fc(x)return xdef _make_layer(self, block, num_residual_blocks, intermediate_channels, stride):identity_downsample = Nonelayers = []# Either if we half the input space for ex, 56x56 -> 28x28 (stride=2), or channels changes# we need to adapt the Identity (skip connection) so it will be able to be added# to the layer that's aheadif stride != 1 or self.in_channels != intermediate_channels * 4:identity_downsample = nn.Sequential(nn.Conv2d(self.in_channels,intermediate_channels * 4,kernel_size=1,stride=stride,bias=False),nn.BatchNorm2d(intermediate_channels * 4),)layers.append(block(self.in_channels, intermediate_channels, identity_downsample, stride))# The expansion size is always 4 for ResNet 50,101,152self.in_channels = intermediate_channels * 4# For example for first resnet layer: 256 will be mapped to 64 as intermediate layer,# then finally back to 256. Hence no identity downsample is needed, since stride = 1,# and also same amount of channels.for i in range(num_residual_blocks - 1):layers.append(block(self.in_channels, intermediate_channels))
复制代码


返回 nn.Sequential(*layers)


然后定义不同版本的 ResNet


  • 对于 ResNet50,层序列为[3,4,6,3]。

  • 对于 ResNet101,层序列为[3,4,23,3]。

  • 对于 ResNet152,层序列为[3,8,36,3]。(请参阅“图像识别的深度残差学习”论文)


def ResNet50(img_channel=3, num_classes=1000):return ResNet(block, [3, 4, 6, 3], img_channel, num_classes)


```plaindef ResNet101(img_channel=3, num_classes=1000):return ResNet(block, [3, 4, 23, 3], img_channel, num_classes)def ResNet152(img_channel=3, num_classes=1000):return ResNet(block, [3, 8, 36, 3], img_channel, num_classes)
复制代码


然后编写一个小的测试来检查模型是否工作正常。


def test():net = ResNet101(img_channel=3, num_classes=1000)device = "cuda" if torch.cuda.is_available() else "cpu"y = net(torch.randn(4, 3, 224, 224)).to(device)print(y.size())
复制代码


test()
复制代码


对于上面的测试用例,输出应该是:



全部代码可以在这里访问:


https://github.com/BakingBrains/Deep_Learning_models_implementation_from-scratch_using_pytorch_/blob/main/ResNet_.py


[1]:Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun: Deep Residual Learning for Image Recognition, Dec 2015, DOI:https://arxiv.org/abs/1512.03385


原文链接:


https://www.analyticsvidhya.com/blog/2021/06/build-resnet-from-scratch-with-python/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+AnalyticsVidhya+%28Analytics+Vidhya%29

2021-06-09 10:032578
用户头像
刘燕 InfoQ高级技术编辑

发布了 1112 篇内容, 共 599.6 次阅读, 收获喜欢 1982 次。

关注

评论

发布
暂无评论
发现更多内容

技术上的过度医疗

superman

过度设计 完美方案

构建高效Presubmit卡点,落地测试左移最佳实践

大卡尔

ci 测试左移 Presubmit

Flutter 命令本质之 Flutter tools 机制源码深入分析

工匠若水

flutter android dart Gradle

Fact Table 数据表什么意思

HoneyMoose

禁止在构造函数里调用虚函数

喵叔

7月日更

web自动化测试(1):再谈UI发展史与UI、功能自动化测试

zhoulujun

大前端 自动化测试 UI自动化测试 web测试

架构实战营模块三作业

老猎人

架构实战营

架构实战营 模块8 作业

Geek_8c5f9c

#架构实战营

web自动化测试(3):web功能自动化测试selenium基础课

zhoulujun

自动化测试 selenium UI测试 界面测试

MySQL事务初始

卢卡多多

MySQL 事务 7月日更

Vite 搭建 Vue2 项目(Vue2 + vue-router + vuex)

德育处主任

JavaScript Vue 大前端 vite

在线QuartzCron定时任务表达式在线生成

入门小站

工具

Scrum Master的职责——《Scrum指南》重读有感(5)

Bruce Talk

Scrum 敏捷 随笔 Agile

没有隐私计算,区块链这个美丽的梦想就不能落地

CECBC

架构实战营 - 模块三作业

思梦乐

使用MLlib进行机器学习(十-上)

Databri_AI

机器学习 spark 线性回归

赶紧收藏!花了1万多买的软件测试教程全套,包含所有软件测试工程师全栈知识点(功能测试理论基础+接口测试+Python自动化+持续集成+性能测试+测试开发+面试简历)软件测试项目实战+训练营学习教程持

程序员阿沐

Python 软件测试 自动化测试 接口测试 测试用例

OGC标准WMTS服务概念与地图商的瓦片编号流派-web地图切片加载

zhoulujun

GIS 瓦片地图 地图瓦片服务 WMTS

网络攻防学习笔记 Day85

穿过生命散发芬芳

网络攻防 7月日更

基于 Blazor 打造一款实时字幕

newbe36524

ASP.NET Core dotnet blazor

架构实战营 - 模块三作业

李东旭

「架构实战营」

高基数数据特性是什么意思

HoneyMoose

大数据训练营一期0718作业

朱磊

在线诉讼区块链证据规则的理论逻辑与制度体系

CECBC

挑选TOP10关键时刻的九大原则

石云升

读书笔记 用户体验 商业洞察 7月日更 体验设计

究竟有没有世界上最好的编程语言?

escray

学习 极客时间 朱赟的技术管理课 7月日更

OpenCV 形态学操作之腐蚀与膨胀,开运算与闭运算,顶帽与黑帽,图像梯度运算相关知识点回顾

梦想橡皮擦

python从入门到精通 7月日更

Linux之wc命令

入门小站

Linux

大数据训练营 -0718课后作业

cc

我不会写代码,但我能做系统

明道云

web自动化测试(2):选择selenium优势?与PhantomJS/QTP/Monkey对比

zhoulujun

自动化测试 web测试 UI测试 界面测试 页面测试

用Python从零开始构建ResNet_AI&大模型_SHAKHADRI313_InfoQ精选文章