MindSpore快速入门——手写数字识别【昇思25天打卡营】

本笔记为昇思25天打卡营第一天,MindSpore 快速入门,本笔记主要为 baseline 代码添加了详细注释,方便理解。
文章首发自我的个人博客 www.jiewong.com

环境信息:
规格:96G Ascend910
镜像:py39-ms2.2.14-cann7.0.0
特性:预装mindspore(2.2.14)、numpy、pandas、download

处理数据集

数据集使用 mnist 数据集
在这里插入图片描述

MNIST数据集目录结构如下:

MNIST_Data
└── train
    ├── train-images-idx3-ubyte (60000个训练图片)
    ├── train-labels-idx1-ubyte (60000个训练标签)
└── test
    ├── t10k-images-idx3-ubyte (10000个测试图片)
    ├── t10k-labels-idx1-ubyte (10000个测试标签)

导入依赖

import mindspore
from mindspore import nn
from mindspore.dataset import vision, transforms
from mindspore.dataset import MnistDataset

下载数据集

from download import download

url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/" \
      "notebook/datasets/MNIST_Data.zip"
path = download(url, "./", kind="zip", replace=True)

获取数据集对象

train_dataset = MnistDataset('MNIST_Data/train')
test_dataset = MnistDataset('MNIST_Data/test')

打印数据集中包含的数据列名,用于dataset的预处理。

print(train_dataset.get_col_names())

输出:

['image', 'label']

MindSpore的dataset使用数据处理流水线(Data Processing Pipeline),需指定map、batch、shuffle等操作。这里我们使用map对图像数据及标签进行变换处理,然后将处理好的数据集打包为大小为64的batch。

def datapipe(dataset, batch_size):
    # 定义图像变换操作列表
    image_transforms = [
        vision.Rescale(1.0 / 255.0, 0),  # 将图像像素值缩放到0-1之间
        vision.Normalize(mean=(0.1307,), std=(0.3081,)),  # 对图像进行标准化处理
        vision.HWC2CHW()  # 将图像从高度x宽度x通道数的格式转换为通道数x高度x宽度的格式
    ]
    # 定义标签变换操作,将标签数据类型转换为int32
    label_transform = transforms.TypeCast(mindspore.int32)

    # 对数据集中的图像应用定义好的图像变换操作
    dataset = dataset.map(image_transforms, 'image')
    # 对数据集中的标签应用定义好的标签变换操作
    dataset = dataset.map(label_transform, 'label')
    # 将数据集分批,每批大小为batch_size
    dataset = dataset.batch(batch_size)
    # 返回处理后的数据集
    return dataset
# 使用datapipe函数对训练数据集进行变换和批处理
# 第一个参数是待处理的数据集,第二个参数是批大小
# 这里将批大小设置为64,意味着每个批次包含64个数据点
train_dataset = datapipe(train_dataset, 64)

# 对测试数据集进行相同的处理
# 这有助于保持训练和测试阶段的数据处理一致性
test_dataset = datapipe(test_dataset, 64)

可使用create_tuple_iteratorcreate_dict_iterator对数据集进行迭代访问,查看数据和标签的shape和datatype。

# 导入测试数据集
for image, label in test_dataset.create_tuple_iterator():
    # 打印图像的形状和类型,形状格式为[N, C, H, W],分别代表批次大小、通道数、高度、宽度
    print(f"Shape of image [N, C, H, W]: {image.shape} {image.dtype}")
    # 打印标签的形状和类型
    print(f"Shape of label: {label.shape} {label.dtype}")
    # 只处理第一批数据,然后退出循环
    break

输出

Shape of image [N, C, H, W]: (64, 1, 28, 28) Float32
Shape of label: (64,) Int32
# 导入测试数据集
for data in test_dataset.create_dict_iterator():
    # 打印图像的形状和数据类型。形状遵循[N, C, H, W]的格式,其中
    # N是批量大小,C是通道数,H是高度,W是宽度。
    print(f"Shape of image [N, C, H, W]: {data['image'].shape} {data['image'].dtype}")
    # 打印标签的形状和数据类型。标签的形状依赖于具体的任务,例如分类、检测等。
    print(f"Shape of label: {data['label'].shape} {data['label'].dtype}")
    # 只处理第一个数据,然后退出循环。
    break

输出

Shape of image [N, C, H, W]: (64, 1, 28, 28) Float32
Shape of label: (64,) Int32

网络构建

mindspore.nn类是构建所有网络的基类,也是网络的基本单元。当用户需要自定义网络时,可以继承nn.Cell类,并重写__init__方法和construct方法。__init__包含所有网络层的定义,construct中包含数据(Tensor)的变换过程。

# 导入MindSpore框架的神经网络模块
import mindspore.nn as nn

# 定义模型类Network,继承自nn.Cell
class Network(nn.Cell):
    def __init__(self):
        # 调用父类的构造函数
        super().__init__()
        # 添加一个Flatten层,用于将输入的多维数据展平成一维数据
        self.flatten = nn.Flatten()
        # 使用SequentialCell创建一个序列模型,包含三个Dense全连接层和两个ReLU激活层
        self.dense_relu_sequential = nn.SequentialCell(
            nn.Dense(28*28, 512),  # 第一个Dense层,输入特征数为28*28,输出特征数为512
            nn.ReLU(),  # 第一个ReLU激活层
            nn.Dense(512, 512),  # 第二个Dense层,输入输出特征数均为512
            nn.ReLU(),  # 第二个ReLU激活层
            nn.Dense(512, 10)  # 第三个Dense层,输入特征数为512,输出特征数为10(假设为分类任务的类别数)
        )

    def construct(self, x):
        # 在construct方法中定义前向传播逻辑
        x = self.flatten(x)  # 首先将输入的x展平
        logits = self.dense_relu_sequential(x)  # 然后通过序列模型处理展平后的数据
        return logits  # 返回模型的输出

# 实例化Network类的对象
model = Network()
# 打印模型结构
print(model)

输出

Network<
  (flatten): Flatten<>
  (dense_relu_sequential): SequentialCell<
    (0): Dense<input_channels=784, output_channels=512, has_bias=True>
    (1): ReLU<>
    (2): Dense<input_channels=512, output_channels=512, has_bias=True>
    (3): ReLU<>
    (4): Dense<input_channels=512, output_channels=10, has_bias=True>
    >
  >

模型训练

在模型训练中,一个完整的训练过程(step)需要实现以下三步:

  1. 正向计算:模型预测结果(logits),并与正确标签(label)求预测损失(loss)。
  2. 反向传播:利用自动微分机制,自动求模型参数(parameters)对于loss的梯度(gradients)。
  3. 参数优化:将梯度更新到参数上。

MindSpore使用函数式自动微分机制,因此针对上述步骤需要实现:

  1. 定义正向计算函数。
  2. 使用value_and_grad通过函数变换获得梯度计算函数。
  3. 定义训练函数,使用set_train设置为训练模式,执行正向计算、反向传播和参数优化。
# 导入MindSpore的nn模块,用于构建模型和优化器
import mindspore.nn as nn

# 实例化损失函数和优化器
# 使用交叉熵损失函数,适用于多分类问题
loss_fn = nn.CrossEntropyLoss()
# 使用随机梯度下降(SGD)优化器,model.trainable_params()获取模型的可训练参数
optimizer = nn.SGD(model.trainable_params(), 1e-2)

# 定义前向传播函数
# data: 输入数据, label: 真实标签
def forward_fn(data, label):
    # 通过模型获取预测结果(logits)
    logits = model(data)
    # 计算损失值
    loss = loss_fn(logits, label)
    # 返回损失值和预测结果
    return loss, logits

# 获取梯度函数
# mindspore.value_and_grad函数用于计算函数的值和梯度
# forward_fn为需要计算梯度的函数, None表示不对输入数据求梯度
# optimizer.parameters获取优化器参数, has_aux=True表示函数返回额外的辅助数据(在这里是logits)
grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)

# 定义单步训练函数
# data: 输入数据, label: 真实标签
def train_step(data, label):
    # 使用grad_fn计算损失值和梯度
    (loss, _), grads = grad_fn(data, label)
    # 使用优化器更新模型参数
    optimizer(grads)
    # 返回损失值
    return loss

# 定义训练函数
# model: 训练的模型, dataset: 训练数据集
def train(model, dataset):
    # 获取数据集大小
    size = dataset.get_dataset_size()
    # 设置模型为训练模式
    model.set_train()
    # 遍历数据集
    for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):
        # 执行单步训练
        loss = train_step(data, label)

        # 每100个批次打印一次损失值
        if batch % 100 == 0:
            # 将损失值从Tensor转换为numpy数组,并打印
            loss, current = loss.asnumpy(), batch
            print(f"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]")

定义测试函数,用来评估模型的性能。

def test(model, dataset, loss_fn):
    # 获取数据集的批次数量
    num_batches = dataset.get_dataset_size()
    
    # 设置模型为评估模式,不进行梯度更新
    model.set_train(False)
    
    # 初始化总样本数、总损失和正确预测的数量
    total, test_loss, correct = 0, 0, 0
    
    # 遍历数据集中的所有批次
    for data, label in dataset.create_tuple_iterator():
        # 使用模型进行预测
        pred = model(data)
        
        # 更新总样本数
        total += len(data)
        
        # 计算并累加这个批次的损失
        test_loss += loss_fn(pred, label).asnumpy()
        
        # 计算并累加正确预测的数量
        correct += (pred.argmax(1) == label).asnumpy().sum()
    
    # 计算平均损失和准确率
    test_loss /= num_batches
    correct /= total
    
    # 打印测试结果
    print(f"Test: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

训练过程需多次迭代数据集,一次完整的迭代称为一轮(epoch)。在每一轮,遍历训练集进行训练,结束后使用测试集进行预测。打印每一轮的loss值和预测准确率(Accuracy),可以看到loss在不断下降,Accuracy在不断提高。

# 设置训练周期数
epochs = 3

# 对每一个训练周期进行迭代
for t in range(epochs):
    # 打印当前周期的信息
    print(f"Epoch {t+1}\n-------------------------------")
    # 调用train函数,传入模型和训练数据集,以进行模型的训练
    train(model, train_dataset)
    # 调用test函数,传入模型、测试数据集和损失函数,以评估模型在测试数据集上的性能
    test(model, test_dataset, loss_fn)

# 当所有训练周期完成后,打印完成信息
print("Done!")

输出

Epoch 1
-------------------------------
loss: 2.306157  [  0/938]
loss: 1.626934  [100/938]
loss: 0.941469  [200/938]
loss: 0.504543  [300/938]
loss: 0.484602  [400/938]
loss: 0.314964  [500/938]
loss: 0.473830  [600/938]
loss: 0.435328  [700/938]
loss: 0.209904  [800/938]
loss: 0.314501  [900/938]
Test: 
 Accuracy: 91.2%, Avg loss: 0.314961 

Epoch 2
-------------------------------
loss: 0.291560  [  0/938]
loss: 0.314653  [100/938]
loss: 0.176316  [200/938]
loss: 0.264363  [300/938]
loss: 0.273784  [400/938]
loss: 0.328052  [500/938]
loss: 0.289191  [600/938]
loss: 0.154592  [700/938]
loss: 0.350418  [800/938]
loss: 0.134797  [900/938]
Test: 
 Accuracy: 92.8%, Avg loss: 0.254814 

Epoch 3
-------------------------------
loss: 0.323751  [  0/938]
loss: 0.251007  [100/938]
loss: 0.209248  [200/938]
loss: 0.177653  [300/938]
loss: 0.257227  [400/938]
loss: 0.343036  [500/938]
loss: 0.200307  [600/938]
loss: 0.209071  [700/938]
loss: 0.251164  [800/938]
loss: 0.211159  [900/938]
Test: 
 Accuracy: 93.9%, Avg loss: 0.210028 

Done!

保存模型

# Save checkpoint
mindspore.save_checkpoint(model, "model.ckpt")
print("Saved Model to model.ckpt")

加载模型

加载保存的权重分为两步:

  1. 重新实例化模型对象,构造模型。
  2. 加载模型参数,并将其加载至模型上。
# 导入mindspore库,用于深度学习模型的构建和训练
import mindspore

# 实例化一个随机初始化的模型,这里的Network()是一个假设的模型类,需要根据实际情况替换
model = Network()

# 加载模型检查点文件,这里的"model.ckpt"是模型保存的检查点文件名,需要确保该文件在当前目录下或指定的路径下
param_dict = mindspore.load_checkpoint("model.ckpt")

# 将检查点中的参数加载到模型中,返回未成功加载的参数列表和成功加载的参数数量
# 这一步是将保存的模型参数应用到新实例化的模型中,以便进行模型的评估或继续训练
param_not_load, _ = mindspore.load_param_into_net(model, param_dict)

# 打印未成功加载的参数列表,如果列表为空,表示所有参数都成功加载
print(param_not_load)

模型推理

# 设置模型为测试模式,不进行梯度更新
model.set_train(False)
# 从测试数据集中迭代获取数据和标签
for data, label in test_dataset:
    # 使用模型对数据进行预测
    pred = model(data)
    # 获取预测结果中概率最高的类别
    predicted = pred.argmax(1)
    # 打印前10个预测值和实际标签进行比较
    print(f'Predicted: "{predicted[:10]}", Actual: "{label[:10]}"')
    # 循环一次后中断,仅展示第一批数据的预测结果
    break
print("打卡人:汪朝杰")

from datetime import datetime
import pytz
# 设置时区为北京时区
beijing_tz = pytz.timezone('Asia/shanghai')
# 获取当前时间,并转为北京时间
current_beijing_time = datetime.now(beijing_tz)
# 格式化时间输出
formatted_time = current_beijing_time.strftime('%Y-%m-%d %H:%M:%S')
print("当前北京时间:",formatted_time)

输出

Predicted: "[8 1 2 5 0 9 3 3 9 4]", Actual: "[8 1 2 5 8 9 3 3 9 4]"
打卡人:汪朝杰
当前北京时间: 2024-06-20 08:16:59

在这里插入图片描述

心得体会

通过这次打卡,我了解了 mnist 数据集,跑通了手写数字识别的 baseline 代码,值得一提的是,最开始我只是跟着 baseline 运行,后面模型推理的时候我不知道为什么是推理出那个数字,我想打开训练数据集看看,结果是二进制文件。在打卡训练营的微信交流群询问后才知道,是 mnist 数据集,查询资料后才了解了。对于一个事物,我不能总是不知道,去年不知道,今年不知道,明年还是不知道,我今年大三,还有大把的时间去学习,加油💪⛽️

花径不曾缘客扫,蓬门今始为君开。
你好,我的名字是汪朝杰,欢迎来到我的网站和数字花园🌱

  • 31
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值