深度学习-一篇入门

【模型】一篇入门之-ResNet卷积神经网络

作者 : 老饼 发表日期 : 2024-01-18 11:19:30 更新日期 : 2024-11-01 22:24:01
本站原创文章,转载请说明来自《老饼讲解-深度学习》www.bbbdata.com



ResNet(ResidualNetwork,残差神经网络)是2015年提出的一种带有残差快捷连接的卷积神经网络

本文讲解ResNet解决网络退化问题的原理,以及ResNet的模型结构,并展示ResNet的代码实现

通过本文,可以快速了解ResNet是什么,解决了什么问题,以及如何使用ResNet来解决图片类别识别问题




     01. 什么是ResNet神经网络     




本节介绍什么是ResNet神经网络,以及ResNet模块的意义





     什么是ResNet    


ResNet是2015年提出的一种卷积神经网络,它的核心是通过残差快捷连接来解决网络退化问题
 ResNet神经网络原文:《Deep Residual Learning for Image Recognition
  网络退化是指神经网络层数加深到一定程度后,继续加深网络不但不会降低误差,反而使网络效果更差
ResNet最大的贡献就是解决了网络退化问题, 如下所示,56层网络的误差比20层的误差还要高
 什么是网络退化
网络退化使得深度学习的层数不能太深,在ResNet出现之前,一般最多也只有100层
而ResNet引入ResNet模块来解决网络退化问题,使用ResNet模块,即使模型深度达到1000层也没问题
 什么是ResNet模块
 ResNet模块就是在原来的神经网络层添加一个快捷连接(shortcut),把输入添加到输出上,如下所示
 
什么是ResNet 
  为什么ResNet可以有效地解决网络退化问题呢?可如下理解:
1. 快捷连接减轻了该层神经网络的拟合复杂度,它只拟合增量(残差),从而解决网络退化的问题     
2. 这种快捷连接使该层易于成为恒等网络(即输入与输出一致),从而解决网络退化的问题               
           因为易于成为恒等网络,所以增加层时,即使不能降低误差,也不致于增加误差,所以网络不会退化
 
什么是ResNet模型
 
ResNet原文在ImageNet和CIFAR-10数据集上展开了一系列的关于ResNet模块相关的实验与探索
 其中,在面向ImageNet数据做相关实验时提出了ResNet-18、34、50、101、152 共5个模型
在面向CIFAR-10数据做相关实验时提出了ResNet-20、32、44、56、110、1202 共6个模型
 
狭义上,ResNet神经网络就是指以上11个模型,但更广义地,使用了ResNet模块的都可以称为ResNet模型





     ResNet模块与快捷连接方式      


ResNet模块
ResNet原文提供了普通ResNet模块和Bottleneck-ResNet模块
  普通ResNet模块和Bottleneck-ResNet模块如下:
 普通ResNet模块和Bottleneck-ResNet模块 
普通ResNet模块就是直接添加了一个快捷连接,使得被快捷连接跨越的层的意义变为拟合残差
  Bottleneck-ResNet模块就是在普通ResNet模块首、尾层加入了1×1卷积,用于控制输入输出的降、升维
ResNet的快捷连接方式与连接方案  
在ResNet模块中,最终的输出由输入与层输出相加得到
为了使的维度必须相同,一般需要先对进行转换,使得它与的维度对齐
即: 

其中
是一个简单的、旨在与维度layer(x)对齐的映射
 
ResNet原文中提供了三种连接方式,如下:
 
ResNet的三种连接方式
1. 恒等连接                                                                      
                                       
恒等连接只有x与layer(x)维度一致时才能使用      
 
2. 零填充连接                                                                    
 为"用0将x填充到与layer(x)一样的维度"     
 3.
投影连接                                                                       
                                     
假设layer(x)有m个通道,x有n个通道,      
       
          则投影快捷连接可以理解为将x的n个维度重新线性组合成m个维度

恒等连接与零填充连接都不需要额外的参数,而投影连接则需要引入新的、待训练的参数w 
由于整个深度神经网络中可能会有多个ResNet模块,有的x与layer(x)维度一致,而有的维度不一致
  ResNet原文共提供了三种连接方案供整个网络选用:
A:升维时用零填充快捷链接,其他为恒等快捷链接         
B:升维时用投影快捷链接,其他为恒等快捷链接            
C:所有链接都用投影快捷链接                         







     02. ResNet网络结构介绍      




本节介绍ResNet原文中的两组ResNet网络结构





      ResNet-20、32、44、56、110、1202网络结构           


ResNet原文在面向CIFAR-10数据做相关实验时,提出了 ResNet-20、32、44、56、110、1202共6组模型
模型输入:32×32×3的图片       
模型输出:10个类别的概率向量 
 ResNet-20、32、44、56、110、1202的通用结构如下:
 ResNet-20、32、44、56、110、1202的模型通用结构  
设n设为n ={3,5,7,9,18,200}时,就可分别得到ResNet-20、32、44、56、110、1202模型 
 值得注意的是,ResNet-1202是一个超深网络,它已经超过了1000层






         ResNet-18、34、50、101、152网络结构         


ResNet原文在面向ImageNet数据做相关实验时,提出了ResNet-18、34、50、101、152共5组模型
 ResNet-18、34、101、152都是简单的卷积神经网络上添加上快捷链接
 ResNet-18的模型结构如下:
  ResNet-18的模型结构    
 可以看到,ResNet-18的架构主要借鉴自VGG,例如5大卷积层、使用多个3×3卷积等等
ResNet-18中,C1是一个大卷积,快速将输入图片信息融合,而C2-C5卷积层都由多个ResNet模块堆叠而成
值得注意的是,C2是通过maxpool来减小FeatureMap的Size,而C3、C4、C5则令首个卷积步幅为2来减小FeatureMap
 
其中,快捷连接方案使用B方法,即“升维时用投影快捷链接,其他为恒等快捷链接”
 
 ResNet-18、34、50、101、152模型
对于其它模型(ResNet-34、50、101、152)则是在18层的形式上,按以下表格修改配置得到:
 ResNet-18、34、50、101、152模型配置
可以看到,ResNet-50、101、152使用了Bottleneck-ResNet模块,
 这主要是Bottleneck-ResNet模块的性价比更优,在构建深层ResNet中,改用Bottleneck-ResNet模块的优势会更明显










     03. ResNet神经网络-代码实现     




本节展示如何实现一个ResNet用于图片类别识别





      pytorch中的ResNet模型      


pytorch中提供了ResNet模型,我们可以将pytorch中的ResNet模型结构打印出来
 以ResNet18为例,具体代码如下:
  import torchvision                                                                                                
  model = torchvision.models.resnet18()                                   # 初始化模型         
  print('\n resnet18的模块:\n',dict(model.named_children()) )    # 打印模型的模块  

运行结果如下:
 
pytorch中的ResNet模型结构 
以上就是pytorch中ResNet18的结构了







     ResNet神经网络-代码实现     


下面展示如何训练一个ResNet18卷积神经网络用于图片类别识别
 具体代码如下:
import torchvision
import torch
from   torch.utils.data   import DataLoader
import numpy as np
model = torchvision.models.resnet18(num_classes=102)                          # 初始化模型
print('\n resnet18的模块:\n',dict(model.named_children()) ) 
# 训练函数                                                                   
def train(dataloader,valLoader,model,epochs,goal,device):                    
    for epoch in range(epochs):                                              
        err_num  = 0                                                         # 本次epoch评估错误的样本
        eval_num = 0                                                         # 本次epoch已评估的样本
        print('-----------当前epoch:',str(epoch),'----------------')         
        for batch, (imgs, labels) in enumerate(dataloader):                  
		    # -----训练模型-----                                             
            x, y = imgs.to(device), labels.to(device)                        # 将数据发送到设备
            optimizer.zero_grad()                                            # 将优化器里的参数梯度清空
            py   = model(x)                                                  # 计算模型的预测值   
            loss = lossFun(py, y)                                            # 计算损失函数值
            loss.backward()                                                  # 更新参数的梯度
            optimizer.step()                                                 # 更新参数
			# ----计算错误率----                                             
            idx      = torch.argmax(py,axis=1)                               # 模型的预测类别
            eval_num = eval_num + len(idx)                                   # 更新本次epoch已评估的样本
            err_num  = err_num +sum(y != idx)                                # 更新本次epoch评估错误的样本
            if(batch%10==0):                                                 # 每10批打印一次结果
                print('err_rate:',err_num/eval_num)                          # 打印错误率
        # -----------验证数据误差---------------------------                 
        model.eval()                                                         # 将模型调整为评估状态
        val_acc_rate = calAcc(model,valLoader,device)                        # 计算验证数据集的准确率
        model.train()                                                        # 将模型调整回训练状态
        print("验证数据的准确率:",val_acc_rate)                              # 打印准确率    
        if((err_num/eval_num)<=goal):                                        # 检查退出条件
            break                                                            
    print('训练步数',str(epoch),',最终训练误差',str(err_num/eval_num))       

# 计算数据集的准确率                                                         
def calAcc(model,dataLoader,device):                                         
    py = np.empty(0)                                                         # 初始化预测结果
    y  = np.empty(0)                                                         # 初始化真实结果
    for batch, (imgs, labels) in enumerate(dataLoader):                      # 逐批预测
        cur_py =  model(imgs.to(device))                                     # 计算网络的输出
        cur_py = torch.argmax(cur_py,axis=1)                                 # 将最大者作为预测结果
        py     = np.hstack((py,cur_py.detach().cpu().numpy()))               # 记录本批预测的y
        y      = np.hstack((y,labels))                                       # 记录本批真实的y
    acc_rate = sum(y==py)/len(y)                                             # 计算测试样本的准确率
    return acc_rate                                                             

# -------模型参数初始化----------------------
def init_param(model):
    param_dict = dict(model.named_parameters())                              # 获取模型的参数字典
    for key in  param_dict:                                                  # 历遍每个参数,对其初始化
        param_name = key.split(".")[-1]                                      # 获取参数的尾缀作为名称
        if (param_name=='weight'):                                           # 如果是权重
            torch.nn.init.normal_(param_dict[key])                           # 则正态分布初始化
        elif (param_name=='bias'):                                           # 如果是阈值
            torch.nn.init.zeros_(param_dict[key])                            # 则初始化为0
            
            
#-------------主流程脚本----------------------------------
#-------------------加载数据-----------------------
trainsform =torchvision.transforms.Compose([
    torchvision.transforms.Resize([224, 224]),
    torchvision.transforms.ToTensor(),
    ]
    )
train_data = torchvision.datasets.Flowers102(                               
    root       = 'D:\pytorch\data'                                           # 路径,如果路径有,就直接从路径中加载,如果没有,就联网获取
    ,split     ='train'                                                      # 训练数据
    ,transform = trainsform                                                  # 转换数据
    ,download  = True                                                        # 是否下载,选为True,就下载到root下面
    ,target_transform= None)                                                                         
val_data = torchvision.datasets.Flowers102(                                 
    root       = 'D:\pytorch\data'                                           # 路径,如果路径有,就直接从路径中加载,如果没有,就联网获取
    ,split     ='test'                                                       # 测试数据
    ,transform = trainsform                                                  # 转换数据
    ,download  = True                                                        # 是否下载,选为True,就下载到root下面
    ,target_transform= None)                                                 
                                                                             
#-------------------模型训练--------------------------------                 
trainLoader = DataLoader(train_data, batch_size=30, shuffle=True)            # 将数据装载到DataLoader
valLoader   = DataLoader(val_data  , batch_size=30)                          # 将验证数据装载到DataLoader 
device      = torch.device('cuda' if torch.cuda.is_available() else 'cpu')   # 设置训练设备  
init_param(model)                                                            # 初始化模型参数
model       = model.to(device)                                               # 发送到设备  
lossFun     = torch.nn.CrossEntropyLoss()                                    # 定义损失函数为交叉熵损失函数
optimizer   = torch.optim.SGD(model.parameters(), lr=0.01,momentum =0.9)     # 初始化优化器
train(trainLoader,valLoader,model,1,0.01,device)                             # 训练模型,训练100步,错误低于1%时停止训练

# -----------模型效果评估--------------------------- 
model.eval()                                                                 # 将模型切换到评估状态(屏蔽Dropout)
train_acc_rate = calAcc(model,trainLoader,device)                            # 计算训练数据集的准确率
print("训练数据的准确率:",train_acc_rate)                                     # 打印准确率
val_acc_rate = calAcc(model,valLoader,device)                                # 计算验证数据集的准确率
print("验证数据的准确率:",val_acc_rate)       
备注:代码未亲测,仅供参考







好了,关于ResNet在CIFAR-10数据集上的相关实验就介绍到这了








 End 







联系老饼