首页 > 其他分享 >昇思25天学习打卡营第19天|ResNet50 图像分类案例:数据集、训练与预测可视化

昇思25天学习打卡营第19天|ResNet50 图像分类案例:数据集、训练与预测可视化

时间:2024-07-30 08:57:34浏览次数:11  
标签:25 ResNet50 nn self num 打卡 data channel out

目录

环境配置

数据集加载

数据集可视化

Building Block

Bottleneck

构建ResNet50网络

模型训练与评估

可视化模型预测


环境配置


        首先指出实验环境预装的 mindspore 版本以及更换版本的方法。然后,它卸载了已安装的 mindspore 并重新安装指定的 2.3.0rc1 版本,并查看了当前安装的 mindspore 版本。接着,通过自定义的 download 函数从给定的 url 下载一个压缩文件(cifar-10-binary.tar.gz)到指定的本地路径(./datasets-cifar10-bin),同时设置了覆盖已存在文件的选项。

        代码如下:

%%capture captured_output
# 实验环境已经预装了mindspore==2.3.0rc1,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.3.0rc1
# 查看当前 mindspore 版本
!pip show mindspore
from download import download

url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"

download(url, "./datasets-cifar10-bin", kind="tar.gz", replace=True)

数据集加载


        首先,定义了一些常量,如数据集根目录、批量大小、图像大小、并行线程个数和分类数量。

        然后,定义了一个名为 create_dataset_cifar10 的函数,用于根据指定的参数创建 Cifar10Dataset 。在函数内部,根据数据的使用类型(训练或测试)添加不同的数据增强操作,如随机裁剪、水平翻转、调整大小、缩放、归一化等,并对标签进行类型转换。然后进行数据映射和批量处理操作。

        最后,使用上述函数分别创建了训练数据集 dataset_train 和测试数据集 dataset_val ,并获取了它们各自的批量数量。

        代码如下:

import mindspore as ms
import mindspore.dataset as ds
import mindspore.dataset.vision as vision
import mindspore.dataset.transforms as transforms
from mindspore import dtype as mstype

data_dir = "./datasets-cifar10-bin/cifar-10-batches-bin"  # 数据集根目录
batch_size = 64  # 批量大小
image_size = 32  # 训练图像空间大小
workers = 1  # 并行线程个数
num_classes = 10  # 分类数量


def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):

    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,
                                 usage=usage,
                                 num_samples=10000,
                                 num_parallel_workers=workers,
                                 shuffle=True)

    trans = []
    if usage == "train":
        trans += [
            vision.RandomCrop((32, 32), (4, 4, 4, 4)),
            vision.RandomHorizontalFlip(prob=0.5)
        ]

    trans += [
        vision.Resize(resize),
        vision.Rescale(1.0 / 255.0, 0.0),
        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),
        vision.HWC2CHW()
    ]

    target_trans = transforms.TypeCast(mstype.int32)

    # 数据映射操作
    data_set = data_set.map(operations=trans,
                            input_columns='image',
                            num_parallel_workers=workers)

    data_set = data_set.map(operations=target_trans,
                            input_columns='label',
                            num_parallel_workers=workers)

    # 批量操作
    data_set = data_set.batch(batch_size)

    return data_set


# 获取处理后的训练与测试数据集

dataset_train = create_dataset_cifar10(dataset_dir=data_dir,
                                       usage="train",
                                       resize=image_size,
                                       batch_size=batch_size,
                                       workers=workers)
step_size_train = dataset_train.get_dataset_size()

dataset_val = create_dataset_cifar10(dataset_dir=data_dir,
                                     usage="test",
                                     resize=image_size,
                                     batch_size=batch_size,
                                     workers=workers)
step_size_val = dataset_val.get_dataset_size()

数据集可视化


        首先,从训练数据集 dataset_train 中获取一个批次的数据,并将其中的图像数据 images 和标签数据 labels 转换为 numpy 数组,并打印出它们的形状和前 6 个标签。

        然后,读取一个文件获取类别名称,并存储在 classes 列表中。

        最后,使用 matplotlib.pyplot 绘制训练数据集中的前 6 张图片。对每张图片进行了数据处理,包括转置维度、反标准化等操作,并为每个子图添加了对应的类别名称标题,同时关闭了坐标轴显示,最后展示了绘制的图像。

        代码如下:

import matplotlib.pyplot as plt
import numpy as np

data_iter = next(dataset_train.create_dict_iterator())

images = data_iter["image"].asnumpy()
labels = data_iter["label"].asnumpy()
print(f"Image shape: {images.shape}, Label shape: {labels.shape}")

# 训练数据集中,前六张图片所对应的标签
print(f"Labels: {labels[:6]}")

classes = []

with open(data_dir + "/batches.meta.txt", "r") as f:
    for line in f:
        line = line.rstrip()
        if line:
            classes.append(line)

# 训练数据集的前六张图片
plt.figure()
for i in range(6):
    plt.subplot(2, 3, i + 1)
    image_trans = np.transpose(images[i], (1, 2, 0))
    mean = np.array([0.4914, 0.4822, 0.4465])
    std = np.array([0.2023, 0.1994, 0.2010])
    image_trans = std * image_trans + mean
    image_trans = np.clip(image_trans, 0, 1)
    plt.title(f"{classes[labels[i]]}")
    plt.imshow(image_trans)
    plt.axis("off")
plt.show()

        运行结果:

Building Block


        定义了一个名为 ResidualBlockBase 的类,它继承自 mindspore.nn.Cell 。

        首先,定义了类属性 expansion ,其值为 1 ,表示最后一个卷积核数量与第一个卷积核数量相等。

        在 __init__ 方法中,初始化了一些属性:

        如果没有传入 norm 参数,就创建一个 nn.BatchNorm2d 对象作为 norm ;否则使用传入的 norm 。

        定义了两个卷积层 conv1 和 conv2 ,并使用指定的初始化方式 weight_init 。

        定义了 ReLU 激活函数和 down_sample 。

        construct 方法定义了前向传播的计算过程:

        定义 identity 为输入 x ,作为 shortcuts 分支。

        经过主分支的卷积、归一化、激活等操作。

        如果 down_sample 存在,对输入进行处理得到新的 identity 。

        将主分支结果与 shortcuts 分支结果相加,再经过 ReLU 激活函数得到最终输出。

        代码如下:

from typing import Type, Union, List, Optional
import mindspore.nn as nn
from mindspore.common.initializer import Normal

# 初始化卷积层与BatchNorm的参数
weight_init = Normal(mean=0, sigma=0.02)
gamma_init = Normal(mean=1, sigma=0.02)

class ResidualBlockBase(nn.Cell):
    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:
        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):
        """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

Bottleneck


        定义了一个名为 ResidualBlock 的类,它也是继承自 mindspore.nn.Cell 。

        类属性 expansion 被设置为 4 ,表示最后一个卷积核的数量是第一个卷积核数量的 4 倍。

        在 __init__ 方法中:

        定义了三个卷积层 conv1 、 conv2 、 conv3 ,以及对应的三个 BatchNorm2d 层 norm1 、 norm2 、 norm3 ,并设置了权重初始化方式。

        定义了 ReLU 激活函数和 down_sample 。

        construct 方法定义了前向传播过程:

        定义 identity 为输入 x 作为 shortcuts 分支。

        主分支依次经过三层卷积、对应的归一化和激活操作。

        如果 down_sample 存在,对输入进行处理得到新的 identity 。

        将主分支结果与 shortcuts 分支结果相加,再经过 ReLU 激活函数得到最终输出。

        代码如下:

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__()

        self.conv1 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=1, weight_init=weight_init)
        self.norm1 = nn.BatchNorm2d(out_channel)
        self.conv2 = nn.Conv2d(out_channel, out_channel,
                               kernel_size=3, stride=stride,
                               weight_init=weight_init)
        self.norm2 = nn.BatchNorm2d(out_channel)
        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)

        self.relu = nn.ReLU()
        self.down_sample = down_sample

    def construct(self, 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)

        if self.down_sample is not None:
            identity = self.down_sample(x)

        out += identity  # 输出为主分支与shortcuts之和
        out = self.relu(out)

        return out

构建ResNet50网络


        make_layer 函数用于创建包含指定数量残差块的层。根据条件决定是否需要 down_sample 操作,并依次添加残差块到层中。

        ResNet 类定义了一个残差网络模型。在初始化方法中定义了一系列的卷积层、池化层、残差网络层、平均池化层、全连接层等。construct 方法定义了数据在模型中的前向传播过程。

        _resnet 函数根据给定的参数创建 ResNet 模型,并根据 pretrained 参数决定是否加载预训练模型的参数。

        resnet50 函数具体创建了一个 ResNet50 模型,并指定了预训练模型的 URL 和本地保存路径,以及是否使用预训练模型。

        代码如下:

def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],
               channel: int, block_nums: int, stride: int = 1):
    down_sample = None  # shortcuts分支

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

    return nn.SequentialCell(layers)
from mindspore import load_checkpoint, load_param_into_net


class ResNet(nn.Cell):
    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')
        # 各个残差网络结构块定义
        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
def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],
            layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,
            input_channel: int):
    model = ResNet(block, layers, num_classes, input_channel)

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

    return model


def resnet50(num_classes: int = 1000, pretrained: bool = False):
    """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"
    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,
                   pretrained, resnet50_ckpt, 2048)

模型训练与评估


        实现了使用预训练的 ResNet50 模型进行微调训练,并在训练过程中进行验证和保存最优模型。

        首先,定义了 ResNet50 网络,并重置了全连接层,设置了学习率、优化器和损失函数。然后创建了训练集和验证集的迭代器,并设置了最佳模型的存储路径。

        定义了 train 函数用于模型的训练,在每个训练批次中计算损失并打印部分批次的损失信息,返回平均损失。evaluate 函数用于模型的验证,计算准确率。

        在训练循环中,对每个 epoch 进行训练和验证,打印当前 epoch 的平均损失和准确率。如果当前准确率高于之前的最优准确率,就保存当前模型为最优模型。

        最后,打印出验证结束后的最优准确率和保存最优模型的路径。

        代码如下:

# 定义ResNet50网络
network = resnet50(pretrained=True)

# 全连接层输入层的大小
in_channel = network.fc.in_channels
fc = nn.Dense(in_channels=in_channel, out_channels=10)
# 重置全连接层
network.fc = fc
# 设置学习率
num_epochs = 1
lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,
                        step_per_epoch=step_size_train, decay_epoch=num_epochs)
# 定义优化器和损失函数
opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)
loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')


def forward_fn(inputs, targets):
    logits = network(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
import os

# 创建迭代器
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_acc = 0
best_ckpt_dir = "./BestCheckpoint"
best_ckpt_path = "./BestCheckpoint/resnet50-best.ckpt"

if not os.path.exists(best_ckpt_dir):
    os.mkdir(best_ckpt_dir)
import mindspore.ops as ops


def train(data_loader, epoch):
    """模型训练"""
    losses = []
    network.set_train(True)

    for i, (images, labels) in enumerate(data_loader):
        loss = train_step(images, labels)
        if i % 100 == 0 or i == step_size_train - 1:
            print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %
                  (epoch + 1, num_epochs, i + 1, step_size_train, loss))
        losses.append(loss)

    return sum(losses) / len(losses)


def evaluate(data_loader):
    """模型验证"""
    network.set_train(False)

    correct_num = 0.0  # 预测正确个数
    total_num = 0.0  # 预测总数

    for images, labels in data_loader:
        logits = network(images)
        pred = logits.argmax(axis=1)  # 预测结果
        correct = ops.equal(pred, labels).reshape((-1, ))
        correct_num += correct.sum().asnumpy()
        total_num += correct.shape[0]

    acc = correct_num / total_num  # 准确率

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

for epoch in range(num_epochs):
    curr_loss = train(data_loader_train, epoch)
    curr_acc = evaluate(data_loader_val)

    print("-" * 50)
    print("Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]" % (
        epoch+1, num_epochs, curr_loss, curr_acc
    ))
    print("-" * 50)

    # 保存当前预测准确率最高的模型
    if curr_acc > best_acc:
        best_acc = curr_acc
        ms.save_checkpoint(network, 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)

        运行结果:

可视化模型预测


        定义了一个名为 visualize_model 的函数,用于可视化模型在验证集上的预测结果。

        函数首先创建了一个 ResNet50 模型用于对狼和狗图像进行二分类,然后加载最优模型的参数。接着从验证集中获取一批数据,包括图像和标签,并使用模型对这批图像进行预测。通过读取文件获取类别名称,然后绘制图像进行展示。对于每幅图像,如果预测正确,标题显示为蓝色;如果预测错误,标题显示为红色。同时展示了经过处理后的图像,并关闭了坐标轴。最后,调用 visualize_model 函数并传入最优检查点路径和验证集来执行可视化操作。

        代码如下:

import matplotlib.pyplot as plt


def visualize_model(best_ckpt_path, dataset_val):
    num_class = 10  # 对狼和狗图像进行二分类
    net = resnet50(num_class)
    # 加载模型参数
    param_dict = ms.load_checkpoint(best_ckpt_path)
    ms.load_param_into_net(net, param_dict)
    # 加载验证集的数据进行验证
    data = next(dataset_val.create_dict_iterator())
    images = data["image"]
    labels = data["label"]
    # 预测图像类别
    output = net(data['image'])
    pred = np.argmax(output.asnumpy(), axis=1)

    # 图像分类
    classes = []

    with open(data_dir + "/batches.meta.txt", "r") as f:
        for line in f:
            line = line.rstrip()
            if line:
                classes.append(line)

    # 显示图像及图像的预测值
    plt.figure()
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        # 若预测正确,显示为蓝色;若预测错误,显示为红色
        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'
        plt.title('predict:{}'.format(classes[pred[i]]), color=color)
        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))
        mean = np.array([0.4914, 0.4822, 0.4465])
        std = np.array([0.2023, 0.1994, 0.2010])
        picture_show = std * picture_show + mean
        picture_show = np.clip(picture_show, 0, 1)
        plt.imshow(picture_show)
        plt.axis('off')

    plt.show()


# 使用测试数据集进行验证
visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)

        运行结果:

        打印时间:

标签:25,ResNet50,nn,self,num,打卡,data,channel,out
From: https://blog.csdn.net/chinayun_6401/article/details/140770595

相关文章

  • day23-back tracking-part02-7.25
    tasksfortoday:1.39.组合总和2.40.组合总和II3.131.分割回文串----------------------------------------------------------1.39.组合总和INthispractice,thekeychangeistherequirementthattheelementcanberepetativelyused,whichcanbeachievedby......
  • 2024.7.25 模拟赛总结
    T1icanStatement:给定一个有\(n(1\len\le10^7)\)个元素的序列\(a(a_i\le10^9)\),求满足\(i<j\)且\(a_i<a_j\)的点对\((i,j)\)中\(j-i\)的最大值。Solution:考虑什么样的\(a_i\)可能作为点对中较靠左边的元素出现。显然对于一个\(k>i\)且\(a_k......
  • 【2024-07-25】连岳摘抄
    23:59我们从来不搞一鸣惊人的事情,我们什么事情都慢慢来,实际上很快。                                                 ——XXX人的记忆,倾向于记住不愉快的事,而忽视愉......
  • 海贼王25
           ......
  • 【FMC155】基于VITA57.1标准的2路500MSPS/1GSPS/1.25GSPS 14位直流耦合AD采集FMC子卡
     板卡概述       FMC155是一款基于VITA57.1标准的,实现2路14-bit、500MSPS/1GSPS/1.25GSPS直流耦合ADC同步采集FMC子卡模块。该模块遵循VITA57.1规范,可直接与FPGA载卡配合使用,板卡ADC器件采用ADI的AD9680芯片,该芯片具有两个模拟输入通道和两个JESD204B输出数据通道对,可......
  • 昇思25天学习打卡营第16天|GAN 图像生成指南:数据集和模型训练手册
    目录MindSpore环境配置、MNIST数据集下载及处理展开。数据集可视化隐码构造模型构建模型训练效果展示模型推理MindSpore环境配置、MNIST数据集下载及处理展开。        首先,通过命令行操作安装特定版本的MindSpore库,并查看其版本。接着,从指定URL......
  • 昇思25天学习打卡营第24天|生成式-Diffusion扩散模型
    打卡目录打卡理解扩散模型环境设置DiffusionModel简介扩散模型实现原理Diffusion前向过程Diffusion逆向过程训练算法总结U-Net神经网络预测噪声构建Diffusion模型准备函数和类位置向量ResNet/ConvNeXT块Attention模块组归一化条件U-Net正向扩散(core)......
  • CVE-2015-5254
    目录漏洞描述漏洞利用流程如下复现过程漏洞利用思路总结漏洞描述ApacheActiveMQ是由美国Pachitea(Apache)软件基金会开发的开源消息中间件,支持Java消息服务、集群、Spring框架等。影响版本:ApacheActiveMQ5.13.0之前5.x版本,该程序导致的漏洞并不限制可以在......
  • error C2589: “(”:“::”右边的非法标记错误的处理
    问题:errorC2589:“(”:“::”右边的非法标记错误的处理标准库在<algorithm>头中定义了两个模板函数std::min()和std::max(),通常用它可以计算某个值对的最小值和最大值。而在VisualC++使用会发生名字min和max与<windows.h>中传统的min/max宏定义有冲突的现象。可以用如下方......
  • 【代码随想录训练营第42期 Day10打卡 LeetCode 232.用栈实现队列 225. 用队列实现栈 2
    目录一、做题心得二、题目与题解题目一:232.用栈实现队列题目链接题解题目二:225.用队列实现栈题目链接题解题目三:20.有效的括号题目链接题解题目四:1047.删除字符串中的所有相邻重复项 题目链接题解三、小结一、做题心得今天是代码随想录训练营打卡的第1......