j1:基于resnet的鸟类图片分类实验(pytorch版本)

基于resnet的鸟类图片分类实验(pytorch版本)

Ⅰ Ⅰ Introduction:
  • 本文为机器学习使用resnet实现鸟类图片分类的实验,素材来自网络。
  • 学习目标:
    • 学习和理解resnet基本原理
    • 基于tensorflow代码写出pytroch版本并跑通
Ⅱ Ⅱ Experiment:
  1. 数据准备与任务分析:
    数据通过网络下载完成
    resnet介绍:
    一、ResNet的介绍

  2. ResNet的背景与发展
    ResNet(Residual Network)是由微软研究院提出的深度卷积神经网络,在2015年的ImageNet竞赛中以显著的优势赢得了图像分类、检测和定位任务的第一名。ResNet的提出解决了深层神经网络中存在的梯度消失和梯度爆炸问题,使得构建非常深的神经网络成为可能,网络的深度从传统的几十层增加到了上百层甚至更深。

  3. ResNet的核心思想:残差学习
    传统的深度神经网络随着层数的加深,容易出现模型的退化问题,即随着网络层数的增加,模型的性能不仅没有提升,反而可能下降。ResNet的核心创新在于引入了残差学习的概念。具体来说,它通过引入“残差块”(Residual Block),使得网络的某一层不仅学习当前层的输出,还通过跳跃连接(shortcut connection)直接将输入传递给后面的层,这种直接连接被称为“跳跃连接”或“捷径”。
    即使某一层的输出值很小(表示这层没有学到什么新的特征),整个残差块的输出也可以依靠直接的跳跃连接保持较好的表现。这种机制大大缓解了深度网络中的梯度消失问题,使得网络能够更深。

  4. ResNet50的架构
    ResNet家族有不同的变种,如ResNet18、ResNet34、ResNet50、ResNet101等,其中数字表示网络层数。以ResNet50为例,整个网络由一个卷积层和4个残差块(每个块由多个残差单元构成)组成。每个残差单元中,主要的操作包括1×1卷积、3×3卷积和另一个1×1卷积,这种设计可以同时减少计算量和保持特征的丰富性。

在ResNet50中,有以下几个主要部分:

初始卷积层:7×7卷积核的卷积层,紧接着是批量归一化(Batch Normalization)和最大池化层(Max Pooling)。
四个残差块:每个残差块内包含若干个残差单元,其中包含卷积、批量归一化和ReLU激活函数等。
全局平均池化层:将特征图通过平均池化层缩小到1×1的尺寸。
全连接层:最后通过一个全连接层进行分类。
这种结构能够有效地提取图像特征,且由于残差块的引入,使得网络可以堆叠更多层而不会出现显著的退化问题。

二、

  1. 实验目的
    本实验的主要目的是通过构建和训练ResNet50模型来对特定的数据集(如鸟类图片数据集)进行分类。通过使用PyTorch框架,实验展示了如何在实际应用中加载数据、预处理数据、构建ResNet模型、训练和测试模型,并最终评估模型的性能。

  2. 实验步骤
    数据加载与预处理:首先从指定的数据文件夹中加载图像数据,并进行必要的预处理,如调整图像尺寸、归一化等。数据集被划分为训练集和测试集,以80%的数据用于训练,20%用于测试。

ResNet50模型构建:实验中自定义了一个ResNet50模型,其中包括了基础的卷积层、残差块、池化层和全连接层。自定义的ResNet50模型结构和原始ResNet50一致。

训练过程:使用交叉熵损失函数和Adam优化器对模型进行训练。模型训练的过程中,会跟踪并记录训练集和测试集的准确率和损失值。每个训练周期(epoch)结束后,模型会在测试集上进行评估,并保存最佳的模型。

结果分析与可视化:训练完成后,通过绘制训练和测试集的损失曲线和准确率曲线来分析模型的表现。可以从曲线中观察到模型的收敛情况,以及是否存在过拟合或欠拟合现象。

  1. 配置环境:
    语言环境:python 3.8
    编译器: pycharm
    深度学习环境:
    torch2.11
    cuda12.1
    torchvision
    0.15.2a0
    导入一切需要的包:
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os, PIL, pathlib, warnings
import torch.nn.functional as F
import matplotlib.pyplot as plt
import pandas as pd
from torchvision.io import read_image
from torch.utils.data import Dataset
import torch.utils.data as data
from PIL import Image
import copy
import numpy as np

查看数据:

def count_images(folder):
    """递归计算文件夹中的图像数量。"""
    count = 0
    for item in folder.iterdir():
        if item.is_file():
            count += 1
        if item.is_dir():
            count += count_images(item)
    return count


def load_data(data_dir, train_transforms):
    """加载图像数据并进行训练集和测试集的划分。"""
    total_data = datasets.ImageFolder(data_dir, transform=train_transforms)
    print(total_data)
    print(total_data.class_to_idx)

    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, test_dataset)

    return train_dataset, test_dataset, total_data


def create_data_loaders(train_dataset, test_dataset, batch_size):
    """创建训练和测试数据加载器。"""
    train_dl = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True,
                                           num_workers=0)
    test_dl = torch.utils.data.DataLoader(test_dataset,
                                          batch_size=batch_size,
                                          shuffle=True,
                                          num_workers=0)
    return train_dl, test_dl


def visualize_sample_images(image_folder):
    """从指定的文件夹中显示一些示例图像。"""
    image_files = [f for f in os.listdir(image_folder) if f.endswith((".jpg", ".png", ".jpeg"))]
    fig, axes = plt.subplots(2, 4, figsize=(16, 6))

    for ax, img_file in zip(axes.flat, image_files):
        img_path = os.path.join(image_folder, img_file)
        img = Image.open(img_path)
        ax.imshow(img)
        ax.axis('off')
    plt.tight_layout()
    plt.show()

在这里插入图片描述

  1. 构建网络:
    为了提高模型性能,选择输入为3通道,经过4层卷积2层池化以及两层全连接输出最终结果,同时训练中加入BN与dropout方法。
class ResNetblock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(ResNetblock, self).__init__()
        self.blockconv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels * 4, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels * 4)
        )
        if stride != 1 or in_channels != out_channels * 4:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels * 4, kernel_size=1, stride=stride),
                nn.BatchNorm2d(out_channels * 4)
            )

    def forward(self, x):
        residual = x
        out = self.blockconv(x)
        if hasattr(self, 'shortcut'):
            residual = self.shortcut(x)
        out += residual
        out = F.relu(out)
        return out

class ResNet50(nn.Module):
    def __init__(self, block, num_classes=1000):
        super(ResNet50, self).__init__()
        self.conv1 = nn.Sequential(
            nn.ZeroPad2d(3),
            nn.Conv2d(3, 64, kernel_size=7, stride=2),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d((3, 3), stride=2)
        )
        self.in_channels = 64
        self.layer1 = self.make_layer(ResNetblock, 64, 3, stride=1)
        self.layer2 = self.make_layer(ResNetblock, 128, 4, stride=2)
        self.layer3 = self.make_layer(ResNetblock, 256, 6, stride=2)
        self.layer4 = self.make_layer(ResNetblock, 512, 3, stride=2)

        self.avgpool = nn.AvgPool2d((7, 7))
        self.fc = nn.Linear(512 * 4, num_classes)

    def make_layer(self, block, channels, num_blocks, stride=1):
        """创建ResNet层,每一层包含多个残差块。"""
        strides = [stride] + [1] * (num_blocks - 1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_channels, channels, stride))
            self.in_channels = channels * 4
        return nn.Sequential(*layers)

    def forward(self, x):
        """定义前向传播。"""
        out = self.conv1(x)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.avgpool(out)
        out = out.view(out.size(0), -1)
        out = self.fc(out)
        return out
  1. 训练模型:
    模型的损失函数选用交叉熵,通过以下代码对模型进行更新:
def train(dataloader, model, optimizer, loss_fn, device):
    """训练模型的一个epoch。"""
    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
  1. 测试模型:
    通过以下代码完成评估:
def test(dataloader, model, loss_fn, device):
    """测试模型的性能。"""
    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)
            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
  1. 实验结果及可视化:
    主函数训练代码即绘制图像执行如下:
if __name__ == "__main__":
    # 设置设备
    device = set_device()
    
    # 配置matplotlib
    configure_plot()
    
    # 数据路径
    data_dir = 'bird_photos'
    data_dir = pathlib.Path(data_dir)
    
    # 统计图片数量
    image_count = count_images(data_dir)
    print("图片总数为:", image_count)
    
    # 获取类别名称
    data_paths = list(data_dir.glob('*'))
    classNames = [str(path).split('/')[-1] for path in data_paths]
    print("类别名称:", classNames)
    
    # 数据预处理
    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_transform = transforms.Compose([
        transforms.Resize([224, 224]),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    
    # 加载数据
    train_dataset, test_dataset, total_data = load_data(data_dir, train_transforms)
    
    # 创建数据加载器
    batch_size = 8
    train_dl, test_dl = create_data_loaders(train_dataset, test_dataset, batch_size)
    
    # 可视化部分图片
    visualize_sample_images(‘bird_photos/Black Skimmer/')
    
    # 定义ResNet模型
    model = ResNet50(block=ResNetblock, num_classes=len(classNames)).to(device)
    print(model)
    
    # 设置损失函数和优化器
    loss_fn = nn.CrossEntropyLoss()
    learn_rate = 1e-3
    opt = torch.optim.Adam(model.parameters(), lr=learn_rate)
    
    # 训练模型
    epochs = 20
    train_loss, train_acc, test_loss, test_acc = [], [], [], []
    best_acc = 0

    for epoch in range(epochs):
        model.train()
        epoch_train_acc, epoch_train_loss = train(train_dl, model, opt, loss_fn, device)
        model.eval()
        epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn, device)

        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))
    
    # 绘制结果
    plot_results(epochs, train_acc, test_acc, train_loss, test_loss)
    
    print('训练完成')

在这里插入图片描述

Ⅲ Ⅲ Conclusion:

本实验成功实现了一个基于ResNet50的图像分类任务。实验结果表明,残差网络在分类任务中具有良好的表现,能够较好地学习和分类复杂的图像数据。在实验中,模型在训练过程中逐渐收敛,并且在测试集上也表现出较高的准确率,这验证了ResNet的有效性。

然而,实验中也可能遇到一些挑战,如数据不均衡、训练时间较长等问题。对于这些问题,可以考虑在后续实验中使用更多的数据增强技术、更复杂的模型结构或者调节超参数(如学习率、批大小等)来进一步优化模型的性能。此外,还可以通过迁移学习或微调预训练模型等方法,进一步提升模型的分类效果。

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值