第J2周:ResNet50v2算法实战与解析

本文详细介绍了如何在Python环境下使用PyTorch2.0.1+cu118构建ResNet50V2模型,包括数据预处理、模型配置、训练过程、测试评估以及可视化训练曲线。作者分享了对ResNet50V2结构优化的理解,如残差块简化、预激活结构等。
摘要由CSDN通过智能技术生成

目录 

我的环境

  • 语言环境:python3.8.18
  • 编译器:jupyter notebook
  • 深度学习环境:torch==2.0.1+cu118,torchvision==0.15.2+cu118 

一、代码实现

1.配置GPU 

import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,warnings
 
warnings.filterwarnings("ignore")             #忽略警告信息
 
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
cuda

2.导入数据

data_dir = "E:/pytorch练习/训练营/J1/数据集/第8天/bird_photos"
data_dir = pathlib.Path(data_dir)
 
data_paths  = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[7] for path in data_paths]
print(classeNames)
['Bananaquit', 'Black Skimmer', 'Black Throated Bushtiti', 'Cockatoo']

3.加载数据

train_transforms = transforms.Compose([
    transforms.Resize([224,224]),
    transforms.ToTensor(),
    transforms.Normalize(
        mean = [0.485,0.456,0.406],
        std = [0.229,0.224,0.225]
    )
])
 
test_transforms = transforms.Compose([
    transforms.Resize([224,224]),
    transforms.ToTensor(),
    transforms.Normalize(
        mean = [0.485,0.456,0.406],
        std = [0.229,0.224,0.225]
    )
])
total_data = datasets.ImageFolder("E:/pytorch练习/训练营/J1/数据集/第8天/bird_photos",transform = train_transforms)
print(total_data)

4.划分数据集 

train_size = int(0.8 * len(total_data))
test_size  = len(total_data) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
print(train_dataset)
print(test_dataset)
 
batch_size = 8
 
train_dl = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True,
                                           #num_workers=1
                                       )
test_dl = torch.utils.data.DataLoader(test_dataset,
                                          batch_size=batch_size,
                                          shuffle=True,
                                          #num_workers=1
                                       )
 
for X, y in test_dl:
    print("Shape of X [N, C, H, W]: ", X.shape)
    print("Shape of y: ", y.shape, y.dtype)
    break
<torch.utils.data.dataset.Subset object at 0x000001974B7AD610>
<torch.utils.data.dataset.Subset object at 0x000001974B7AD640>
Shape of X [N, C, H, W]:  torch.Size([8, 3, 224, 224])
Shape of y:  torch.Size([8]) torch.int64
​

5.构造模型

from torch import nn
from torch.nn import functional as F
 
import torch.nn.functional as F
 
 
''' Residual Block '''
class Block2(nn.Module):
    def __init__(self, in_channel, filters, kernel_size=3, stride=1, conv_shortcut=False):
        super(Block2, self).__init__()
        self.preact = nn.Sequential(
            nn.BatchNorm2d(in_channel),
            nn.ReLU(True)
        )
 
        self.shortcut = conv_shortcut
        if self.shortcut:
            self.short = nn.Conv2d(in_channel, 4*filters, 1, stride=stride, padding=0, bias=False)
        elif stride>1:
            self.short = nn.MaxPool2d(kernel_size=1, stride=stride, padding=0)
        else:
            self.short = nn.Identity()
 
        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channel, filters, 1, stride=1, bias=False),
            nn.BatchNorm2d(filters),
            nn.ReLU(True)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(filters, filters, kernel_size, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(filters),
            nn.ReLU(True)
        )
        self.conv3 = nn.Conv2d(filters, 4*filters, 1, stride=1, bias=False)
 
    def forward(self, x):
        x1 = self.preact(x)
        if self.shortcut:
            x2 = self.short(x1)
        else:
            x2 = self.short(x)
        x1 = self.conv1(x1)
        x1 = self.conv2(x1)
        x1 = self.conv3(x1)
        x = x1 + x2
        return x
 
class Stack2(nn.Module):
    def __init__(self, in_channel, filters, blocks, stride=2):
        super(Stack2, self).__init__()
        self.conv = nn.Sequential()
        self.conv.add_module(str(0), Block2(in_channel, filters, conv_shortcut=True))
        for i in range(1, blocks-1):
            self.conv.add_module(str(i), Block2(4*filters, filters))
        self.conv.add_module(str(blocks-1), Block2(4*filters, filters, stride=stride))
 
    def forward(self, x):
        x = self.conv(x)
        return x
''' 构建ResNet50V2 '''
class ResNet50V2(nn.Module):
    def __init__(self,
                 include_top=True,  # 是否包含位于网络顶部的全链接层
                 preact=True,  # 是否使用预激活
                 use_bias=True,  # 是否对卷积层使用偏置
                 input_shape=[224, 224, 3],
                 classes=1000,
                 pooling=None):  # 用于分类图像的可选类数
        super(ResNet50V2, self).__init__()
 
        self.conv1 = nn.Sequential()
        self.conv1.add_module('conv', nn.Conv2d(3, 64, 7, stride=2, padding=3, bias=use_bias, padding_mode='zeros'))
        if not preact:
            self.conv1.add_module('bn', nn.BatchNorm2d(64))
            self.conv1.add_module('relu', nn.ReLU())
        self.conv1.add_module('max_pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
 
        self.conv2 = Stack2(64, 64, 3)
        self.conv3 = Stack2(256, 128, 4)
        self.conv4 = Stack2(512, 256, 6)
        self.conv5 = Stack2(1024, 512, 3, stride=1)
 
        self.post = nn.Sequential()
        if preact:
            self.post.add_module('bn', nn.BatchNorm2d(2048))
            self.post.add_module('relu', nn.ReLU())
        if include_top:
            self.post.add_module('avg_pool', nn.AdaptiveAvgPool2d((1, 1)))
            self.post.add_module('flatten', nn.Flatten())
            self.post.add_module('fc', nn.Linear(2048, classes))
        else:
            if pooling=='avg':
                self.post.add_module('avg_pool', nn.AdaptiveAvgPool2d((1, 1)))
            elif pooling=='max':
                self.post.add_module('max_pool', nn.AdaptiveMaxPool2d((1, 1)))
 
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        x = self.post(x)
        return x
 
 
model = ResNet50V2().to(device)
model

6.定义训练和测试函数 

def train(dataloader,model,optimizer,loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
 
    train_acc,train_loss = 0,0
 
    for X,y in dataloader:
        X,y = X.to(device),y.to(device)
 
        pred = model(X)
        loss = loss_fn(pred,y)
 
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
 
        train_loss += loss.item()
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
 
    train_loss /= num_batches
    train_acc /= size
 
    return train_acc,train_loss
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)  # 测试集的大小
    num_batches = len(dataloader)  # 批次数目, (size/batch_size,向上取整)
    test_loss, test_acc = 0, 0
 
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
 
            # 计算loss
            target_pred = model(imgs)
            loss = loss_fn(target_pred, target)
 
            test_loss += loss.item()
            test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
 
    test_acc /= size
    test_loss /= num_batches
 
    return test_acc, test_loss

 7.定义一些超参数

loss_fn = nn.CrossEntropyLoss()
learn_rate = 1e-2
opt = torch.optim.SGD(model.parameters(),lr=learn_rate)
 
import copy
 
epochs = 10
 
train_loss=[]
train_acc=[]
test_loss=[]
test_acc=[]
best_acc = 0

8.开始训练

for epoch in range(epochs):
 
    model.train()
    epoch_train_acc,epoch_train_loss = train(train_dl,model,opt,loss_fn)
 
    model.eval()
    epoch_test_acc,epoch_test_loss = test(test_dl,model,loss_fn)
 
    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        best_model = copy.deepcopy(model)
 
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
 
    lr = opt.state_dict()['param_groups'][0]['lr']
 
    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss,
                          epoch_test_acc*100, epoch_test_loss, lr))
 

 
print('Done')

9.可视化

import matplotlib.pyplot as plt
#隐藏警告
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc[-10:], label='Training Accuracy')
plt.plot(epochs_range, test_acc[-10:], label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss[-10:], label='Training Loss')
plt.plot(epochs_range, test_loss[-10:], label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

二、个人总结 

本周手敲了ResNetV2算法,之前的模型框架没有发生改变,只是将模型方法进行了一个优化修改。

ResNet50V2是对原始ResNet50模型的改进版本,主要在以下几个方面进行了优化:

  1. 残差块结构的简化: ResNetV2对残差块(Residual Block)的设计进行了简化。在ResNet50中,每个残差块通常包含两个或三个卷积层,这些卷积层之间可能有批量归一化(Batch Normalization, BN)层。而在ResNet50V2中,根据“Identity Mappings in Deep Residual Networks”论文中的建议,将批量归一化层移到了激活函数(如ReLU)之前。这种改变有助于减少内部covariate shift(协变量偏移),使得网络训练更加稳定,同时有可能提高模型的表达能力。

  2. 批次归一化的顺序调整: ResNet50V2调整了BN、卷积和激活函数的顺序。原始ResNet50中,通常是在卷积之后立即进行BN,然后应用ReLU激活。但在V2版本中,BN被置于卷积层之前,即先进行BN再进行卷积操作,最后才应用ReLU。这一改动基于对BN工作原理和其与非线性激活函数相互作用的理解,旨在改善网络的训练动态。

  3. 预激活(Pre-activation)结构: ResNet50V2采用了预激活结构,即将BN和ReLU操作放置在残差块内部每一层卷积的前面。这样,输入首先经过BN和ReLU处理,然后进入卷积层。这种结构被称为“ResNet with Pre-activation”。预激活允许更容易的梯度传播,尤其是在网络深度较大时,可以缓解梯度消失或爆炸的问题,从而促进更深层次网络的训练。

  4. 初始化策略: ResNet50V2可能还考虑了不同的权重初始化策略,以适应新的残差块结构。由于网络架构的改变会影响到内部信号的分布和梯度流,因此可能需要调整初始化方法以确保模型能够从头开始有效地训练。

 

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值