K同学[365天深度学习训练营]第十一周记录J2:ResNet50V2算法实战与解析

 系统环境:WIN10-WSL2-Ubuntu22.04

- 语言环境:Python3.9.18

编译器vscode+jupyter notebook

- 深度学习环境:Pytorch2.1.2

- 显卡:NVIDIA Tesla P40

本周是介绍残差神经网络的一个变种ResNet50V2

与基础的ResNet50的区别是在残差结构上,v2先进行BN标准化和ReLU预激活,如下图右侧的结构

以下是对于残差神经网络模型的构建
 

''' 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)

设置训练

# 训练循环
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小
    num_batches = len(dataloader)   # 批次数目, (size/batch_size,向上取整)
 
    train_loss, train_acc = 0, 0  # 初始化训练损失和正确率
    
    for X, y in dataloader:  # 获取图片及其标签
        X, y = X.to(device), y.to(device)
        
        # 计算预测误差
        pred = model(X)          # 网络输出
        loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
        
        # 反向传播
        optimizer.zero_grad()  # grad属性归零
        loss.backward()        # 反向传播
        optimizer.step()       # 每一步自动更新
        
        # 记录acc与loss
        train_acc  += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()
            
    train_acc  /= size
    train_loss /= num_batches
 
    return train_acc, train_loss

def test (dataloader, model, loss_fn):
    size        = len(dataloader.dataset)  # 测试集的大小
    num_batches = len(dataloader)          # 批次数目
    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

import copy
 
optimizer  = torch.optim.Adam(model.parameters(), lr= 1e-4) 
loss_fn    = nn.CrossEntropyLoss() # 创建损失函数
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=0.9)  # 定义学习率调度器

epochs = 100
patience = 10  # 早停的耐心值,即如果模型连续10个周期没有准确率提升,则跳出训练

train_loss = []
train_acc = []
test_loss = []
test_acc = []
 
best_acc = 0  # 设置一个最佳准确率,作为最佳模型的判别指标
no_improve_epoch = 0  # 用于跟踪准确率是否提升的计数器

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
    
    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)
        no_improve_epoch = 0  # 重置计数器
        # 保存最佳模型的检查点
        PATH = './best_model.pth'
        torch.save({
            'epoch': epoch,
            'model_state_dict': best_model.state_dict(),
            'optimizer_state_dict': optimizer.state_dict(),
            'loss': epoch_test_loss,
        }, PATH)
    else:
        no_improve_epoch += 1
    
    if no_improve_epoch >= patience:
        print(f"Early stopping triggered at epoch {epoch+1}")
        break  # 早停
    
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    scheduler.step()  # 更新学习率
    
    lr = optimizer.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('Training complete.')

训练结果为

Epoch: 1, Train_acc:38.3%, Train_loss:5.374, Test_acc:18.6%, Test_loss:5.786, Lr:9.00E-05
Epoch: 2, Train_acc:67.5%, Train_loss:2.085, Test_acc:40.7%, Test_loss:3.037, Lr:8.10E-05
Epoch: 3, Train_acc:82.5%, Train_loss:0.842, Test_acc:67.3%, Test_loss:1.399, Lr:7.29E-05
Epoch: 4, Train_acc:88.1%, Train_loss:0.432, Test_acc:77.0%, Test_loss:0.913, Lr:6.56E-05
Epoch: 5, Train_acc:93.4%, Train_loss:0.398, Test_acc:73.5%, Test_loss:0.981, Lr:5.90E-05
Epoch: 6, Train_acc:93.1%, Train_loss:0.356, Test_acc:80.5%, Test_loss:0.681, Lr:5.31E-05
Epoch: 7, Train_acc:95.8%, Train_loss:0.205, Test_acc:76.1%, Test_loss:0.980, Lr:4.78E-05
Epoch: 8, Train_acc:96.2%, Train_loss:0.185, Test_acc:77.0%, Test_loss:0.928, Lr:4.30E-05
Epoch: 9, Train_acc:98.2%, Train_loss:0.138, Test_acc:82.3%, Test_loss:0.774, Lr:3.87E-05
Epoch:10, Train_acc:97.6%, Train_loss:0.172, Test_acc:79.6%, Test_loss:0.981, Lr:3.49E-05
Epoch:11, Train_acc:98.0%, Train_loss:0.112, Test_acc:79.6%, Test_loss:0.835, Lr:3.14E-05
Epoch:12, Train_acc:98.5%, Train_loss:0.076, Test_acc:86.7%, Test_loss:0.482, Lr:2.82E-05
Epoch:13, Train_acc:98.7%, Train_loss:0.145, Test_acc:84.1%, Test_loss:0.579, Lr:2.54E-05
Epoch:14, Train_acc:99.3%, Train_loss:0.114, Test_acc:78.8%, Test_loss:0.842, Lr:2.29E-05
Epoch:15, Train_acc:99.6%, Train_loss:0.105, Test_acc:82.3%, Test_loss:0.801, Lr:2.06E-05
Epoch:16, Train_acc:98.9%, Train_loss:0.118, Test_acc:84.1%, Test_loss:0.572, Lr:1.85E-05
Epoch:17, Train_acc:100.0%, Train_loss:0.038, Test_acc:85.0%, Test_loss:0.543, Lr:1.67E-05
Epoch:18, Train_acc:99.3%, Train_loss:0.076, Test_acc:82.3%, Test_loss:0.691, Lr:1.50E-05
Epoch:19, Train_acc:99.3%, Train_loss:0.218, Test_acc:80.5%, Test_loss:0.817, Lr:1.35E-05
Epoch:20, Train_acc:98.7%, Train_loss:0.207, Test_acc:79.6%, Test_loss:0.717, Lr:1.22E-05
Epoch:21, Train_acc:99.6%, Train_loss:0.045, Test_acc:81.4%, Test_loss:0.649, Lr:1.09E-05
Early stopping triggered at epoch 22
Training complete.

测试准确率最高为86.7%

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

54afive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值