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)

效果:

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

相关推荐
@我们的天空8 分钟前
【机器学习】python之人工智能应用篇——3D生成技术
人工智能·python·深度学习·神经网络·机器学习·3d
82782093716 分钟前
Django中,update_or_create()
后端·python·django
U盘失踪了21 分钟前
Django 定义模型执行迁移
python·django
万俟淋曦22 分钟前
[ROS 系列学习教程] 建模与仿真 - 使用 ros_control 控制差速轮式机器人
人工智能·ai·机器人·ros·ros_control
Daydreamer .25 分钟前
django后台登录:Forbidden (403) CSRF verification failed.
python·django·csrf
aikude202427 分钟前
送物机器人电子方案定制
人工智能
昉钰29 分钟前
CSS基础学习记录(6)
css·学习·html·动画
B.D.S.29 分钟前
计网之IP
网络·tcp/ip·智能路由器
Xinstall渠道统计34 分钟前
社交App广告优化新篇章:Xinstall引领用户体验升级,助力买量效果提升
大数据·人工智能·科技·ux
天天代码码天天35 分钟前
C# YoloV8 OpenVINO 视频抽帧 自动标注 预标注工具
人工智能·yolo·openvino