240622_昇思学习打卡-Day4-5-ResNet50迁移学习

240622_昇思学习打卡-Day4-5-ResNet50迁移学习

我们对事物的认知都是一点一点积累出来的,往往借助已经认识过的东西,可以更好地理解和认识新的有关联的东西。比如一个人会骑自行车,我们让他去骑摩托车他也很快就能学会,比如已经学会C++,现在让他去学python他也很容易就能理解。这种情况我们一般称为举一反三。反言之,我们从原始部落找出来一个人(仅作举例),指着摩托车让他骑,可能是一件特别难的事,因为他对这个领域没有丝毫的认知和理解,在实现这件事上就会特别困难。

映射到神经网络上也是一样的道理,如果我们在训练时不导入预训练权重,他就像一个没有见过现代社会的原始人,学任何东西都特别慢,学习成本特别高,但如果我们导入了相似模型结构下针对别的任务的训练权重(比如训练识别自行车),用来训练识别摩托车,我们只需要改变网络最后的分类层,即可得到比较好的训练效果,可以大大缩小模型训练的时间。

原理是我在这么多层神经网络的训练下,已经明白了轱辘(车轮)长什么样,把手长什么样,最后的分类层只是区分出来什么是自行车,你现在给我一堆摩托的照片,我就可以去寻找两者的相似处,我对轱辘和把手的认知就不用从0开始重新学习,只需要进行微调,比如摩托车的轱辘比自行车大一点,摩托车的车把手比自行车大一点。基于以前已经学习到的东西,可以大大缩小训练成本。

迁移学习就是这个道理。我们在神经网络技术的发展中,针对不同的任务,不可能每个网络都从0开始训练,那样需要的数据集及成本都是不可承受的。

本文以ResNet50迁移学习为例展开讲解,在MindSpore架构下运行。

文章目录

数据准备

下载数据集

本文用到狗与狼分类数据集,使用download接口下载,也可自行下载放在项目当前目录下

python 复制代码
from download import download

dataset_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/intermediate/Canidae_data.zip"

download(dataset_url, "./datasets-Canidae", kind="zip", replace=True)

数据目录结构如下:

text 复制代码
text
datasets-Canidae/data/
└── Canidae
    ├── train
    │   ├── dogs
    │   └── wolves
    └── val
        ├── dogs
        └── wolves

首先定义一些超参数:

python 复制代码
batch_size = 18                             # 批量大小
image_size = 224                            # 训练图像空间大小
num_epochs = 5                             # 训练周期数
lr = 0.001                                  # 学习率
momentum = 0.9                              # 动量
workers = 4                                 # 并行线程个数

加载数据集以及做一些数据增强(本文所用的狼狗数据集属于ImageNet数据集,其典型mean和std值分别为[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225],所以代码中直接使用):

python 复制代码
# 导入MindSpore库,用于深度学习框架
import mindspore as ms
import mindspore.dataset as ds
import mindspore.dataset.vision as vision

# 定义训练数据集和验证数据集的路径
# 数据集目录路径
data_path_train = "./datasets-Canidae/data/Canidae/train/"
data_path_val = "./datasets-Canidae/data/Canidae/val/"

# 定义函数,用于创建Canidae分类任务的训练集或验证集
# 参数dataset_path: 数据集路径,usage: 数据集的用途,"train"或"val"
# 返回处理后的数据集
# 创建训练数据集
def create_dataset_canidae(dataset_path, usage):
    """数据加载"""
    # 初始化ImageFolderDataset,使用多线程并打乱数据顺序
    # 使用mindspore.dataset.ImageFolderDataset接口来加载数据集
    data_set = ds.ImageFolderDataset(dataset_path,
                                     num_parallel_workers=workers,
                                     shuffle=True,)

    # 定义数据预处理的参数
    mean = [0.485 * 255, 0.456 * 255, 0.406 * 255]
    std = [0.229 * 255, 0.224 * 255, 0.225 * 255]
    scale = 32

    # 根据数据集的用途(训练或验证)选择不同的数据增强操作
    if usage == "train":
        # 训练集的数据增强操作,包括随机裁剪、水平翻转、归一化等
        # Define map operations for training dataset
        trans = [
            vision.RandomCropDecodeResize(size=image_size, scale=(0.08, 1.0), ratio=(0.75, 1.333)),
            vision.RandomHorizontalFlip(prob=0.5),
            vision.Normalize(mean=mean, std=std),
            vision.HWC2CHW()
        ]
    else:
        # 验证集的数据增强操作,主要包括解码、缩放、中心裁剪、归一化等
        # Define map operations for inference dataset
        trans = [
            vision.Decode(),
            vision.Resize(image_size + scale),
            vision.CenterCrop(image_size),
            vision.Normalize(mean=mean, std=std),
            vision.HWC2CHW()
        ]

    # 对数据集应用预处理操作
    # 数据映射操作
    data_set = data_set.map(
        operations=trans,
        input_columns='image',
        num_parallel_workers=workers)

    # 将数据集分批处理,指定批大小
    # 批量操作
    data_set = data_set.batch(batch_size)

    return data_set

# 创建训练数据集和验证数据集,并获取每个数据集的步长(即数据集的大小)
dataset_train = create_dataset_canidae(data_path_train, "train")
step_size_train = dataset_train.get_dataset_size()

dataset_val = create_dataset_canidae(data_path_val, "val")
step_size_val = dataset_val.get_dataset_size()

数据集可视化

mindspore.dataset.ImageFolderDataset接口中加载的训练数据集返回值为字典,用户可通过 create_dict_iterator 接口创建数据迭代器,使用 next 迭代访问数据集。前面 batch_size 设为18,所以使用 next 一次可获取18个图像及标签数据。

python 复制代码
# 获取训练数据集的第一个批次数据,是18张图像及标签数据。
data = next(dataset_train.create_dict_iterator())
images = data["image"]
labels = data["label"]

print("Tensor of image", images.shape)
print("Labels:", labels)

'''
执行结果为
Tensor of image (18, 3, 224, 224)    # 意思是这一批有18张3通道(RGB通道)的长224宽224的图像
Labels: [1 1 0 1 1 0 1 0 0 0 0 0 0 0 0 1 0 1]    # 因为该任务是一个二分类任务,所以类别只有简单的0和1
'''

目前拿到的数据我们可以先看看长什么样,展示图像及标题,标题为对应的label名称

python 复制代码
# 导入matplotlib.pyplot库用于绘图
import matplotlib.pyplot as plt
# 导入numpy库用于处理数组
import numpy as np

# 定义一个字典,映射类别编号到类别名称
# class_name对应label,按文件夹字符串从小到大的顺序标记label
class_name = {0: "dogs", 1: "wolves"}

# 创建一个5x5大小的画布
plt.figure(figsize=(5, 5))
# 循环遍历4个图像
for i in range(4):
    # 获取当前图像的数据和标签
    # 获取图像及其对应的label
    data_image = images[i].asnumpy()
    data_label = labels[i]
    # 将图像数据从HWC格式转换为RGB格式
    # 处理图像供展示使用
    data_image = np.transpose(data_image, (1, 2, 0))
    # 对图像进行预处理,包括归一化和颜色空间转换
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    data_image = std * data_image + mean
    data_image = np.clip(data_image, 0, 1)
    # 在画布上创建子图,并显示当前图像
    # 显示图像
    plt.subplot(2, 2, i+1)
    plt.imshow(data_image)
    # 设置子图标题为图像的类别名称
    plt.title(class_name[int(labels[i].asnumpy())])
    # 关闭子图的坐标轴显示
    plt.axis("off")

# 显示画布上的所有图像
plt.show()

训练模型

本文使用ResNet50模型进行训练,传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的,每一层都是从前一层提取特征,所以随着层数增加一般会出现退化等问题。结构比如

ResNet网络提出了残差网络结构(Residual Network)来减轻退化问题,每一层的输入不仅会传递给下一层,还会通过跳跃连接(skip connection)直接传递给更深的层次。这就涉及到一个残差块的概念:

每个残差块包含两到三个卷积层,以及一个跨层的直接连接,比如

输入x,经过卷积a,卷积b,然后得到一个输出x1,此时不把x1直接给下一层,而是和x统筹学习一下,再交给下一层。

以下是代码示例:

python 复制代码
from typing import Type, Union, List, Optional
from mindspore import nn, train
from mindspore.common.initializer import Normal

# 初始化卷积层得权重
weight_init = Normal(mean=0, sigma=0.02)
gamma_init = Normal(mean=1, sigma=0.02)
python 复制代码
class ResidualBlockBase(nn.Cell):
    """
    Residual Block基类。

    该类定义了残差块的基本结构,包括两个卷积层、批量归一化和激活函数。子类可以通过重写来扩展其功能。
    
    Attributes:
        expansion: int,通道扩张率,默认为1,表示输出通道数与输入通道数相等。
    """

    expansion: int = 1  # 最后一个卷积核数量与第一个卷积核数量相等

    def __init__(self, in_channel: int, out_channel: int,
                 stride: int = 1, norm: Optional[nn.Cell] = None,
                 down_sample: Optional[nn.Cell] = None) -> None:
        """
        初始化ResidualBlockBase。

        参数:
            in_channel(int): 输入通道数。
            out_channel(int): 输出通道数。
            stride(int): 步长,默认为1。
            norm(Optional[nn.Cell]): 批量归一化层,默认为None,如果None,则使用默认的批量归一化层。
            down_sample(Optional[nn.Cell]): 下采样层,默认为None,如果需要下采样,则通过该参数指定下采样层。
        """
        super(ResidualBlockBase, self).__init__()
        if not norm:
            self.norm = nn.BatchNorm2d(out_channel)
        else:
            self.norm = norm

        self.conv1 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=3, stride=stride,
                               weight_init=weight_init)
        self.conv2 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=3, weight_init=weight_init)
        self.relu = nn.ReLU()
        self.down_sample = down_sample

    def construct(self, x):
        """
        构建残差块的前向传播。

        参数:
            x: 输入数据。

        返回:
            经过残差块处理后的输出数据。
        """
        """ResidualBlockBase construct."""
        identity = x  # shortcuts分支

        out = self.conv1(x)  # 主分支第一层:3*3卷积层
        out = self.norm(out)
        out = self.relu(out)
        out = self.conv2(out)  # 主分支第二层:3*3卷积层
        out = self.norm(out)

        if self.down_sample is not None:
            identity = self.down_sample(x)
        out += identity  # 输出为主分支与shortcuts之和
        out = self.relu(out)

        return out
python 复制代码
# 定义ResidualBlock类,用于实现残差块
class ResidualBlock(nn.Cell):
    # 定义扩张率,用于计算输出通道数
    expansion = 4  # 最后一个卷积核的数量是第一个卷积核数量的4倍

    # 初始化函数,设置输入通道数、输出通道数、步长和下采样函数
    def __init__(self, in_channel: int, out_channel: int,
                 stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:
        super(ResidualBlock, self).__init__()

        # 初始化第一个1x1卷积层,用于减少输入通道数
        self.conv1 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=1, weight_init=weight_init)
        # 初始化第一个批量归一化层
        self.norm1 = nn.BatchNorm2d(out_channel)
        # 初始化第二个3x3卷积层,用于增大特征图尺寸
        self.conv2 = nn.Conv2d(out_channel, out_channel,
                               kernel_size=3, stride=stride,
                               weight_init=weight_init)
        # 初始化第二个批量归一化层
        self.norm2 = nn.BatchNorm2d(out_channel)
        # 初始化第三个1x1卷积层,用于增加输出通道数
        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,
                               kernel_size=1, weight_init=weight_init)
        # 初始化第三个批量归一化层
        self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)

        # 初始化激活函数为ReLU
        self.relu = nn.ReLU()
        # 初始化下采样函数,用于当输入和输出尺寸不同时进行下采样
        self.down_sample = down_sample

    # 构造函数,输入特征图x,输出残差学习后的特征图
    def construct(self, x):

        # 初始化identity为输入x,用于残差学习
        identity = x  # shortscuts分支

        # 经过第一个卷积层和批量归一化层
        out = self.conv1(x)  # 主分支第一层:1*1卷积层
        out = self.norm1(out)
        out = self.relu(out)
        # 经过第二个卷积层和批量归一化层
        out = self.conv2(out)  # 主分支第二层:3*3卷积层
        out = self.norm2(out)
        out = self.relu(out)
        # 经过第三个卷积层和批量归一化层
        out = self.conv3(out)  # 主分支第三层:1*1卷积层
        out = self.norm3(out)

        # 如果存在下采样函数,则对输入x进行下采样
        if self.down_sample is not None:
            identity = self.down_sample(x)

        # 将主分支输出和identity相加,实现残差学习
        out += identity  # 输出为主分支与shortcuts之和
        out = self.relu(out)

        # 返回残差学习后的特征图
        return out

make_layer层就是把多个残差块拼起来

python 复制代码
def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],
               channel: int, block_nums: int, stride: int = 1):
    """
    创建一个由多个残差块组成的层。

    该函数根据输入参数构建一个包含指定数量残差块的层。如果当前层的步长不为1或输入通道数与输出通道数不同,
    则会添加一个下采样层。下采样层用于将输入特征图的尺寸调整到与残差块输出相同的尺寸,以保持维度匹配。

    参数:
    last_out_channel: 前一层的输出通道数。
    block: 残差块的类型。
    channel: 当前层的输出通道数。
    block_nums: 本层中残差块的数量。
    stride: 当前层的步长,默认为1。

    返回:
    一个由多个残差块组成的 SequentialCell 实例。
    """

    down_sample = None  # shortcuts分支

    # 当步长不为1或输入输出通道数不一致时,配置下采样层
    if stride != 1 or last_out_channel != channel * block.expansion:
        down_sample = nn.SequentialCell([
            nn.Conv2d(last_out_channel, channel * block.expansion,
                      kernel_size=1, stride=stride, weight_init=weight_init),
            nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)
        ])

    # 初始化第一个残差块,可能包含下采样操作
    layers = []
    layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))

    in_channel = channel * block.expansion
    # 堆叠残差网络
    for _ in range(1, block_nums):
        # 添加剩余的残差块,这些块不包含下采样
        layers.append(block(in_channel, channel))

    # 将所有残差块组合成一个 SequentialCell 返回
    return nn.SequentialCell(layers)

定义ResNet网络类

python 复制代码
from mindspore import load_checkpoint, load_param_into_net


# 定义ResNet网络类
class ResNet(nn.Cell):
    
    """
    ResNet网络结构。

    参数:
    block: 残差块的类型。这里的Type[Union[ResidualBlockBase, ResidualBlock]]意味着参数block是一个类型
    ,这个类型是ResidualBlockBase和ResidualBlock这两个类中的任何一个
    layer_nums: 每个阶段的残差块数量列表。
    num_classes: 分类的类别数。
    input_channel: 输入通道数。
    """
    def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],
                 layer_nums: List[int], num_classes: int, input_channel: int) -> None:
        super(ResNet, self).__init__()

        self.relu = nn.ReLU()
        # 初始化第一个卷积层
        # 第一个卷积层,输入channel为3(彩色图像),输出channel为64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)
        self.norm = nn.BatchNorm2d(64)
        # 初始化最大池化层
        # 最大池化层,缩小图片的尺寸
        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')
        # 通过make_layer函数构建每个阶段的残差块
        # 各个残差网络结构块定义,
        self.layer1 = make_layer(64, block, 64, layer_nums[0])
        self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)
        self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)
        self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)
        # 初始化全局平均池化层
        # 平均池化层
        self.avg_pool = nn.AvgPool2d()
        # 初始化展平层
        # flattern层
        self.flatten = nn.Flatten()
        # 初始化全连接层
        # 全连接层
        self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)

    # 定义前向传播方法
    def construct(self, x):

        x = self.conv1(x)
        x = self.norm(x)
        x = self.relu(x)
        x = self.max_pool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avg_pool(x)
        x = self.flatten(x)
        x = self.fc(x)

        return x


# 构建并返回指定ResNet模型
def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],
            layers: List[int], num_classes: int, pretrained: bool, pretrianed_ckpt: str,
            input_channel: int):
    """
    根据给定参数构建ResNet模型。

    参数:
    model_url: 预训练模型的URL。
    block: 残差块的类型。
    layers: 每个阶段的残差块数量列表。
    num_classes: 分类的类别数。
    pretrained: 是否使用预训练模型。
    pretrianed_ckpt: 预训练模型的文件路径。
    input_channel: 输入通道数。

    返回:
    构建的ResNet模型。
    """
    model = ResNet(block, layers, num_classes, input_channel)

    if pretrained:
        # 下载并加载预训练模型
        # 加载预训练模型
        download(url=model_url, path=pretrianed_ckpt, replace=True)
        param_dict = load_checkpoint(pretrianed_ckpt)
        load_param_into_net(model, param_dict)

    return model


# 提供ResNet50模型的构造函数
def resnet50(num_classes: int = 1000, pretrained: bool = False):
    """
    构建ResNet50模型。

    参数:
    num_classes: 分类的类别数,默认为1000。
    pretrained: 是否使用预训练模型,默认为False。

    返回:
    构建的ResNet50模型。
    """
    "ResNet50模型"
    resnet50_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt"
    resnet50_ckpt = "./LoadPretrainedModel/resnet50_224_new.ckpt"
    # 调用_resnet函数构建ResNet50模型
    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,
                   pretrained, resnet50_ckpt, 2048)

固定特征进行训练

这里就是因为我们使用的是预训练模型,所以在实际投入训练时需要对模型做一点微调,以适应我们自己的分类需求

使用固定特征进行训练的时候,需要冻结除最后一层之外的所有网络层。通过设置 requires_grad== False 冻结参数,以便不在反向传播中计算梯度。

python 复制代码
# 导入MindSpore库,用于深度学习模型训练
import mindspore as ms
# 导入matplotlib库,用于数据可视化
import matplotlib.pyplot as plt
# 导入os库,用于操作系统相关功能
import os
# 导入time库,用于时间相关功能
import time

# 初始化ResNet50模型,并使用预训练的权重
net_work = resnet50(pretrained=True)

# 获取全连接层的输入通道数
# 全连接层输入层的大小
in_channels = net_work.fc.in_channels
# 定义新的全连接层,用于分类任务,输出类别数为2
# 输出通道数大小为狼狗分类数2
head = nn.Dense(in_channels, 2)
# 替换原模型的全连接层
# 重置全连接层
net_work.fc = head

# 定义平均池化层,用于对特征图进行全局平均池化
# 平均池化层kernel size为7
avg_pool = nn.AvgPool2d(kernel_size=7)
# 替换原模型的平均池化层
# 重置平均池化层
net_work.avg_pool = avg_pool

# 遍历模型参数,冻结除最后一层外的所有参数的梯度更新
# 冻结除最后一层外的所有参数
for param in net_work.get_parameters():
    if param.name not in ["fc.weight", "fc.bias"]:
        param.requires_grad = False

# 定义优化器和损失函数
# 定义优化器和损失函数
opt = nn.Momentum(params=net_work.trainable_params(), learning_rate=lr, momentum=0.5)
loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')

# 定义前向传播函数,用于计算损失
def forward_fn(inputs, targets):
    logits = net_work(inputs)
    loss = loss_fn(logits, targets)
    return loss

# 定义梯度计算函数,用于同时计算损失和梯度
grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)

# 定义训练步骤函数,用于执行一次训练迭代
def train_step(inputs, targets):
    loss, grads = grad_fn(inputs, targets)
    opt(grads)
    return loss

# 实例化训练模型
# 实例化模型
model1 = train.Model(net_work, loss_fn, opt, metrics={"Accuracy": train.Accuracy()})

训练和评估

开始训练模型,与没有预训练模型相比,将节约一大半时间,因为此时可以不用计算部分梯度。保存评估精度最高的ckpt文件于当前路径的./BestCheckpoint/resnet50-best-freezing-param.ckpt

python 复制代码
# 导入MindSpore库,用于构建深度学习模型
import mindspore as ms
# 导入matplotlib库,用于可视化数据
import matplotlib.pyplot as plt
# 导入os库,用于操作文件系统
import os
# 导入time库,用于时间相关的操作
import time

# 加载训练数据集
dataset_train = create_dataset_canidae(data_path_train, "train")
# 获取训练数据集的步长(样本数量)
step_size_train = dataset_train.get_dataset_size()

# 加载验证数据集
dataset_val = create_dataset_canidae(data_path_val, "val")
# 获取验证数据集的步长(样本数量)
step_size_val = dataset_val.get_dataset_size()

# 定义训练的轮数
num_epochs = 5

# 创建训练数据集的迭代器,设置迭代轮数
# 创建迭代器
data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)
# 创建验证数据集的迭代器,设置迭代轮数
data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)

# 定义最佳模型检查点的保存目录
best_ckpt_dir = "./BestCheckpoint"
# 定义最佳模型检查点的文件路径
best_ckpt_path = "./BestCheckpoint/resnet50-best-freezing-param.ckpt"
python 复制代码
# 导入mindspore模块,用于构建和训练神经网络
import mindspore as ms
# 导入matplotlib.pyplot,用于绘制图像
import matplotlib.pyplot as plt
# 导入os模块,用于操作文件系统
import os
# 导入time模块,用于测量时间
import time

# 开始循环训练
print("Start Training Loop ...")

# 初始化最佳准确率变量
best_acc = 0

# 遍历每个训练轮次
for epoch in range(num_epochs):
    # 初始化训练损失列表
    losses = []
    # 设置网络为训练模式
    net_work.set_train()

    # 记录当前epoch的开始时间
    epoch_start = time.time()

    # 遍历训练数据集
    # 为每轮训练读入数据
    for i, (images, labels) in enumerate(data_loader_train):
        # 将标签转换为int32类型
        labels = labels.astype(ms.int32)
        # 执行一个训练步骤,计算损失并更新网络参数
        loss = train_step(images, labels)
        # 将当前步骤的损失添加到损失列表中
        losses.append(loss)

    # 在验证集上评估模型准确率
    # 每个epoch结束后,验证准确率

    acc = model1.eval(dataset_val)['Accuracy']

    # 记录当前epoch的结束时间
    epoch_end = time.time()
    # 计算当前epoch的总耗时(毫秒)
    epoch_seconds = (epoch_end - epoch_start) * 1000
    # 计算平均每个训练步骤的耗时(毫秒)
    step_seconds = epoch_seconds / step_size_train

    # 打印训练信息,包括epoch号、平均损失、准确率以及训练耗时
    print("-" * 20)
    print("Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]" % (
        epoch + 1, num_epochs, sum(losses) / len(losses), acc
    ))
    # 打印每个步骤的耗时
    print("epoch time: %5.3f ms, per step time: %5.3f ms" % (
        epoch_seconds, step_seconds
    ))

    # 如果当前epoch的准确率高于之前的最佳准确率,则更新最佳准确率并保存模型参数
    if acc > best_acc:
        best_acc = acc
        # 如果最佳模型保存目录不存在,则创建该目录
        if not os.path.exists(best_ckpt_dir):
            os.mkdir(best_ckpt_dir)
        # 保存模型参数到最佳模型保存路径
        ms.save_checkpoint(net_work, best_ckpt_path)

# 打印训练结束信息,包括最佳准确率和保存的模型路径
print("=" * 80)
print(f"End of validation the best Accuracy is: {best_acc: 5.3f}, "
      f"save the best ckpt file in {best_ckpt_path}", flush=True)

可视化模型预测

使用固定特征得到的best.ckpt文件对对验证集的狼和狗图像数据进行预测。若预测字体为蓝色即为预测正确,若预测字体为红色则预测错误。这里编写一个方法直接调用

python 复制代码
# 导入matplotlib.pyplot库用于图像展示
import matplotlib.pyplot as plt
# 导入mindspore库用于深度学习模型训练和加载
import mindspore as ms

def visualize_model(best_ckpt_path, val_ds):
    """
    可视化模型预测结果。

    参数:
    best_ckpt_path: 字符串,最佳模型的检查点文件路径。
    val_ds: 数据集对象,用于从中获取验证数据。

    返回:
    无返回值,直接展示预测结果的图像。
    """
    # 加载预训练的resnet50模型
    net = resnet50()
    # 定义新的分类头部,用于从resnet的输出调整到两个类别的预测
    # 全连接层输入层的大小
    in_channels = net.fc.in_channels
    # 输出通道数大小为狼狗分类数2
    head = nn.Dense(in_channels, 2)
    # 重置全连接层
    net.fc = head
    # 调整平均池化层的设置以适应新的输出尺寸
    # 平均池化层kernel size为7
    avg_pool = nn.AvgPool2d(kernel_size=7)
    # 重置平均池化层
    net.avg_pool = avg_pool
    # 加载最佳模型的参数
    # 加载模型参数
    param_dict = ms.load_checkpoint(best_ckpt_path)
    ms.load_param_into_net(net, param_dict)
    # 创建模型对象,用于进行预测
    model = train.Model(net)
    # 从验证数据集中获取一个批次的数据
    # 加载验证集的数据进行验证
    data = next(val_ds.create_dict_iterator())
    images = data["image"].asnumpy()
    labels = data["label"].asnumpy()
    # 定义类别名称映射
    class_name = {0: "dogs", 1: "wolves"}
    # 对数据进行预测
    # 预测图像类别
    output = model.predict(ms.Tensor(data['image']))
    pred = np.argmax(output.asnumpy(), axis=1)
    # 展示预测结果
    # 显示图像及图像的预测值
    plt.figure(figsize=(5, 5))
    for i in range(4):
        plt.subplot(2, 2, i + 1)
        # 根据预测结果和真实标签设置标题颜色
        # 若预测正确,显示为蓝色;若预测错误,显示为红色
        color = 'blue' if pred[i] == labels[i] else 'red'
        plt.title('predict:{}'.format(class_name[pred[i]]), color=color)
        # 调整图像颜色空间并展示
        picture_show = np.transpose(images[i], (1, 2, 0))
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        picture_show = std * picture_show + mean
        picture_show = np.clip(picture_show, 0, 1)
        plt.imshow(picture_show)
        plt.axis('off')
    plt.show()
python 复制代码
# 调用预测可视化方法
visualize_model(best_ckpt_path, dataset_val)

效果:

打卡图片(因内容较多,分两天打卡):

相关推荐
apcipot_rain21 分钟前
【应用密码学】实验五 公钥密码2——ECC
前端·数据库·python
yu41062122 分钟前
2025年中期大语言模型实力深度剖析
人工智能·语言模型·自然语言处理
小彭律师26 分钟前
门禁人脸识别系统详细技术文档
笔记·python
鸿业远图科技1 小时前
分式注记种表达方式arcgis
python·arcgis
是孑然呀2 小时前
【小记】word批量生成准考证
笔记·学习·excel
别让别人觉得你做不到2 小时前
Python(1) 做一个随机数的游戏
python
feng995203 小时前
技术伦理双轨认证如何重构AI工程师能力评估体系——基于AAIA框架的技术解析与行业实证研究
人工智能·aaif·aaia·iaaai
2301_776681653 小时前
【用「概率思维」重新理解生活】
开发语言·人工智能·自然语言处理
{{uname}}3 小时前
利用WebSocket实现实时通知
网络·spring boot·websocket·网络协议
蜡笔小新..3 小时前
从零开始:用PyTorch构建CIFAR-10图像分类模型达到接近1的准确率
人工智能·pytorch·机器学习·分类·cifar-10