news 2026/4/16 10:39:43

计算机视觉入门到实战系列(二十)基于ResNet的图像分类--算法实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
计算机视觉入门到实战系列(二十)基于ResNet的图像分类--算法实现

基于ResNet的图像分类--算法实现

    • 数据准备
    • ResNet
      • 概述
      • 核心思想
      • 1. ResNet-18/34
      • 2. ResNet-50/101/152
    • 网络架构
      • ResNet-34 示例结构:
      • 各版本详细配置:
    • ResNet34网络代码实现
    • 数据增强
    • 数据集封装Dataset类
    • 数据集封装DataLoader类

数据准备

导包

fromPILimportImageimportcv2importnumpyasnpimportosfromsklearn.model_selectionimporttrain_test_splitfromsklearn.kernel_approximationimportAdditiveChi2Samplerimportrandomimportmathfromimutilsimportpaths

读取数据

# 图像数据data=[]# 图像对应的标签labels=[]# 储存标签信息的临时变量labels_tep=[]# 数据集的地址# 请读者自行下载数据集,并将数据集放在代码的同级目录下image_paths=list(paths.list_images('E:\\jupyterNotebook\\Hands-on-CV-main\\10classify\\caltech-101'))forimage_pathinimage_paths:# 获取图像类别label=image_path.split(os.path.sep)[-2]# 读取每个类别的图像image=cv2.imread(image_path)# 将图像通道从BGR转换为RGBimage=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)# 统一输入图像的尺寸image=cv2.resize(image,(200,200),interpolation=cv2.INTER_AREA)data.append(image)labels_tep.append(label)name2label={}tep={}foridx,nameinenumerate(labels_tep):tep[name]=idxforidx,nameinenumerate(tep):name2label[name]=idxforidx,image_pathinenumerate(image_paths):labels.append(name2label[image_path.split(os.path.sep)[-2]])data=np.array(data)labels=np.array(labels)

数据集划分

(x_train,X,y_train,Y)=train_test_split(data,labels,test_size=0.4,stratify=labels,random_state=42)(x_val,x_test,y_val,y_test)=train_test_split(X,Y,test_size=0.5,random_state=42)print(f"x_train examples:{x_train.shape}\n\ x_test examples:{x_test.shape}\n\ x_val examples:{x_val.shape}")

上面数据获取部分和前面一节是一致的,不再赘述

ResNet

概述

ResNet(Residual Network,残差网络)是2015年由微软研究院的何恺明等人提出的一种深度卷积神经网络架构。它通过引入残差学习(Residual Learning)解决了深度神经网络中的梯度消失/爆炸和退化问题,使得可以训练非常深的网络(超过100层)。

核心思想

  1. 残差块(Residual Block)
    ResNet的核心创新是残差块,它通过"跳跃连接"(skip connection)将输入直接传递到输出:
输出 = F(x) + x

其中:

  • x是输入
  • F(x)是需要学习的残差映射
  • F(x) + x通过跳跃连接实现
  1. 数学表达
    传统网络:H(x) = F(x)
    残差网络:H(x) = F(x) + x
    目标学习:F(x) = H(x) - x(残差)

1. ResNet-18/34

使用基本的残差块:

Conv 3×3 ReLU Conv 3×3

跳跃连接:如果输入输出维度相同,直接相加;如果维度不同,使用1×1卷积调整维度。

2. ResNet-50/101/152

使用瓶颈结构(Bottleneck):

Conv 1×1, 减少通道数 Conv 3×3, 主要计算 Conv 1×1, 恢复通道数

网络架构

ResNet-34 示例结构:

输入 → Conv 7×7 → MaxPool ↓ [Residual Block ×3] 通道数:64 ↓ [Residual Block ×4] 通道数:128 ↓ [Residual Block ×6] 通道数:256 ↓ [Residual Block ×3] 通道数:512 ↓ 全局平均池化 → 全连接层 → 输出

各版本详细配置:

网络层ResNet-18ResNet-34ResNet-50ResNet-101ResNet-152
卷积层17×7,64,stride27×7,64,stride27×7,64,stride27×7,64,stride27×7,64,stride2
池化层3×3 max pool, stride23×3 max pool, stride23×3 max pool, stride23×3 max pool, stride23×3 max pool, stride2
卷积层2[3×3,64]×2[3×3,64]×3[1×1,64
3×3,64
1×1,256]×3
[1×1,64
3×3,64
1×1,256]×3
[1×1,64
3×3,64
1×1,256]×3
卷积层3[3×3,128]×2[3×3,128]×4[1×1,128
3×3,128
1×1,512]×4
[1×1,128
3×3,128
1×1,512]×4
[1×1,128
3×3,128
1×1,512]×8
卷积层4[3×3,256]×2[3×3,256]×6[1×1,256
3×3,256
1×1,1024]×6
[1×1,256
3×3,256
1×1,1024]×23
[1×1,256
3×3,256
1×1,1024]×36
卷积层5[3×3,512]×2[3×3,512]×3[1×1,512
3×3,512
1×1,2048]×3
[1×1,512
3×3,512
1×1,2048]×3
[1×1,512
3×3,512
1×1,2048]×3
参数数量11.7M21.8M25.6M44.5M60.2M

ResNet34网络代码实现


左图为传统网络结构,右图为残差网络结构

importtorchastfromtorchimportnnfromtorch.nnimportfunctionalasF# 残差结构classResidualBlock(nn.Module):# 深度学习中的图像和利用模型提取的特征图往往有很多通道(channel)# 比如RGB图像的通道为3,即R通道、B通道与G通道def__init__(self,inchannel,outchannel,stride=1,shortcut=None):super(ResidualBlock,self).__init__()# 观察图 ,不难发现残差结构可大致分为左右两部分# 左边是一系列的网络层级,右边是一个跳跃连接# 定义左边self.left=nn.Sequential(# 对应图 中第一个权重层nn.Conv2d(inchannel,outchannel,3,stride,1,bias=False),nn.BatchNorm2d(outchannel),# 对应图中的 RELUnn.ReLU(inplace=True),# 对应图中第二个权重层nn.Conv2d(outchannel,outchannel,3,1,1,bias=False),nn.BatchNorm2d(outchannel))# 定义右边self.right=shortcut# forward()函数在网络结构中起到举足轻重的作用,它决定着网络如何对数据进行传播defforward(self,x):out=self.left(x)# 构建残差结构residual=xifself.rightisNoneelseself.right(x)out+=residualreturnF.relu(out)# 在这一模块中,将实现 ResNet34classResNet(nn.Module):def__init__(self,num_classes=102):super(ResNet,self).__init__()# 前几层图像转换self.pre=nn.Sequential(nn.Conv2d(3,64,7,2,3,bias=False),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(3,2,1),)# 重复的网络层分别有3、4、6、3个残差块self.layer1=self._make_layer(64,128,3)self.layer2=self._make_layer(128,256,4,stride=2)self.layer3=self._make_layer(256,512,6,stride=2)self.layer4=self._make_layer(512,512,3,stride=2)# 分类用的全连接层,将一个多通道的特征图映射到一个维度为类别数目的向量self.fc=nn.Linear(512,num_classes)def_make_layer(self,inchannel,outchannel,block_num,stride=1):# 定义shortcut连接shortcut=nn.Sequential(nn.Conv2d(inchannel,outchannel,1,stride,bias=False),nn.BatchNorm2d(outchannel))layers=[]# 给当前网络层级添加残差块layers.append(ResidualBlock(inchannel,outchannel,stride,shortcut))foriinrange(1,block_num):layers.append(ResidualBlock(outchannel,outchannel))returnnn.Sequential(*layers)defforward(self,x):x=self.pre(x)x=self.layer1(x)x=self.layer2(x)x=self.layer3(x)x=self.layer4(x)x=F.avg_pool2d(x,7)x=x.view(x.size(0),-1)returnself.fc(x)

数据增强

# 定义训练图像增强(变换)的方法train_transform=transforms.Compose([transforms.ToPILImage(),# transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])])# 定义训练图像增强(变换)的方法val_transform=transforms.Compose([transforms.ToPILImage(),# transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])])

数据集封装Dataset类

# 数据集classImageDataset(Dataset):def__init__(self,images,labels=None,transforms=None):self.X=images self.y=labels self.transforms=transformsdef__len__(self):return(len(self.X))# 用于深度学习训练构建的数据类中,__getitem__()函数非常重要# __getitem__()决定着数据如何传入模型# 在下面的代码中,可以发现,当transforms非空时:# 数据将先经过transforms进行数据增强,再返回进行后续操作def__getitem__(self,i):data=self.X[i][:]ifself.transforms:data=self.transforms(data)ifself.yisnotNone:return(data,self.y[i])else:returndata

生成dataset类

# 生成不同的类用于训练、验证以及测试train_data=ImageDataset(x_train,y_train,train_transform)val_data=ImageDataset(x_val,y_val,val_transform)test_data=ImageDataset(x_test,y_test,val_transform)

数据集封装DataLoader类

BATCH_SIZE=128trainloader=DataLoader(train_data,batch_size=BATCH_SIZE,shuffle=True)valloader=DataLoader(val_data,batch_size=BATCH_SIZE,shuffle=True)testloader=DataLoader(test_data,batch_size=BATCH_SIZE,shuffle=False)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 17:21:39

模板代码安全审计

1、非修改序列算法 这些算法不会改变它们所操作的容器中的元素。 1.1 find 和 find_if find(begin, end, value):查找第一个等于 value 的元素,返回迭代器(未找到返回 end)。find_if(begin, end, predicate):查找第…

作者头像 李华
网站建设 2026/4/15 20:36:36

强烈安利专科生必用的8个AI论文网站测评

强烈安利专科生必用的8个AI论文网站测评 2026年专科生论文写作工具测评:为何需要一份权威榜单? 随着AI技术的不断普及,越来越多的专科生开始借助智能工具提升论文写作效率。然而,面对市场上琳琅满目的AI论文网站,如何选…

作者头像 李华
网站建设 2026/4/12 11:33:32

未来、趋势与软技能

SQLAlchemy是Python中最流行的ORM(对象关系映射)框架之一,它提供了高效且灵活的数据库操作方式。本文将介绍如何使用SQLAlchemy ORM进行数据库操作。目录安装SQLAlchemy核心概念连接数据库定义数据模型创建数据库表基本CRUD操作查询数据关系操…

作者头像 李华
网站建设 2026/4/11 19:35:04

Python入门:从零到一的第一个程序

SQLAlchemy是Python中最流行的ORM(对象关系映射)框架之一,它提供了高效且灵活的数据库操作方式。本文将介绍如何使用SQLAlchemy ORM进行数据库操作。 目录 安装SQLAlchemy 核心概念 连接数据库 定义数据模型 创建数据库表 基本CRUD操作…

作者头像 李华
网站建设 2026/4/13 5:46:56

TestOps的测试资产生命周期管理:从创建到归档

在敏捷和DevOps主导的软件开发时代,测试资产管理成为质量保障的核心环节。TestOps(测试运维)通过整合自动化、持续交付和智能工具,优化测试资源的全生命周期管理,从而提升效率、减少缺陷逃逸率。测试资产包括测试用例、…

作者头像 李华
网站建设 2026/4/13 4:16:19

2026 年了,还不会做 AI 大模型应用?程序员必须直面的 3 个现实

过去两年,大模型几乎重塑了整个技术圈的讨论重心。 从最初的 ChatGPT,到后来百花齐放的国产大模型,再到企业纷纷“全面接入 AI”,你几乎很难再找到一个完全不谈大模型的技术会议、产品发布或技术社区。 但在热闹之外&#xff0c…

作者头像 李华