本笔记为昇思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_iterator 或create_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)需要实现以下三步:
- 正向计算:模型预测结果(logits),并与正确标签(label)求预测损失(loss)。
- 反向传播:利用自动微分机制,自动求模型参数(parameters)对于loss的梯度(gradients)。
- 参数优化:将梯度更新到参数上。
MindSpore使用函数式自动微分机制,因此针对上述步骤需要实现:
- 定义正向计算函数。
- 使用value_and_grad通过函数变换获得梯度计算函数。
- 定义训练函数,使用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")
加载模型
加载保存的权重分为两步:
- 重新实例化模型对象,构造模型。
- 加载模型参数,并将其加载至模型上。
# 导入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 数据集,查询资料后才了解了。对于一个事物,我不能总是不知道,去年不知道,今年不知道,明年还是不知道,我今年大三,还有大把的时间去学习,加油💪⛽️