mindspore 学习笔记
初学入门
基本介绍
昇思MindSpore介绍
昇思MindSpore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标。其中,易开发表现为API友好、调试难度低;高效执行包括计算效率、数据预处理效率和分布式训练效率;全场景则指框架同时支持云、边缘以及端侧场景。
昇思MindSpore总体架构如下图所示:
CPU、GPU、TPU、NPU等到底是什么? - 知乎 (zhihu.com)
ModelZoo(模型库):ModelZoo提供可用的深度学习算法网络,也欢迎更多开发者贡献新的网络(ModelZoo地址)。
Science(科学计算):MindScience是基于昇思MindSpore融合架构打造的科学计算行业套件,包含了业界领先的数据集、基础模型、预置高精度模型和前后处理工具,加速了科学行业应用开发。Compiler(AI编译器):图层的核心编译器,主要基于端云统一的MindIR实现三大功能,包括硬件无关的优化(类型推导、自动微分、表达式化简等)、硬件相关优化(自动并行、内存优化、图算融合、流水线执行等)、部署推理相关的优化(量化、剪枝等)。
执行流程
左边蓝色方框的是MindSpore主体框架,主要提供神经网络在训练、验证相关的基础API功能,另外还会默认提供自动微分、自动并行等功能。
蓝色方框往下是MindSpore Data模块,可以利用该模块进行数据预处理,包括数据采样、数据迭代、数据格式转换等不同的数据操作。在训练的过程会遇到很多调试调优的问题,因此有MindSpore Insight模块对loss曲线、算子执行情况、权重参数变量等调试调优相关的数据进行可视化,方便用户在训练过程中进行调试调优。蓝色方框往上的内容跟算法开发相关的用户更加贴近,包括存放大量的AI算法模型库ModelZoo,提供面向不同领域的开发工具套件MindSpore DevKit,另外还有高阶拓展库MindSpore Extend,这里面值得一提的就是MindSpore Extend中的科学计算套件MindSciences,MindSpore首次探索将科学计算与深度学习结合,将数值计算与深度学习相结合,通过深度学习来支持电磁仿真、药物分子仿真等等。神经网络模型训练完后,可以导出模型或者加载存放在MindSpore Hub中已经训练好的模型。接着有MindIR提供端云统一的IR格式,通过统一IR定义了网络的逻辑结构和算子的属性,将MindIR格式的模型文件 与硬件平台解耦,实现一次训练多次部署。因此如图所示,通过IR把模型导出到不同的模块执行推理。
设计理念
-
采用函数式可微分编程架构,使用户聚焦于模型算法的数学原生表达
神经网络模型通常基于梯度下降算法进行训练,但手动求导过程复杂,结果容易出错。昇思MindSpore的基于源码转换(Source Code Transformation,SCT)的自动微分(Automatic Differentiation)机制采用函数式可微分编程架构,在接口层提供Python编程接口,包括控制流的表达。用户可聚焦于模型算法的数学原生表达,无需手动进行求导。
-
统一单机和分布式训练的编码方式
随着神经网络模型和数据集的规模不断增大,分布式并行训练成为了神经网络训练的常见做法,但分布式并行训练的策略选择和编写十分复杂,这严重制约着深度学习模型的训练效率,阻碍深度学习的发展。MindSpore统一了单机和分布式训练的编码方式,开发者无需编写复杂的分布式策略,在单机代码中添加少量代码即可实现分布式训练,提高神经网络训练效率,大大降低了AI开发门槛,使用户能够快速实现想要的模型。例如设置
set_auto_parallel_context(parallel_mode=ParallelMode.AUTO_PARALLEL)
便可自动建立代价模型,为用户选择一种较优的并行模式。
层次结构
-
High-Level Python API
第一层为高阶API,其在中阶API的基础上又提供了训练推理的管理、混合精度训练、调试调优等高级接口,方便用户控制整网的执行流程和实现神经网络的训练推理及调优。例如用户使用Model接口,指定要训练的神经网络模型和相关的训练设置,对神经网络模型进行训练。
-
Medium-Level Python API
第二层为中阶API,其封装了低阶API,提供网络层、优化器、损失函数等模块,用户可通过中阶API灵活构建神经网络和控制执行流程,快速实现模型算法逻辑。例如用户可调用Cell接口构建神经网络模型和计算逻辑,通过使用Loss模块和Optimizer接口为神经网络模型添加损失函数和优化方式,利用Dataset模块对数据进行处理以供模型的训练和推导使用。
-
Low-Level Python API
第三层为低阶API,主要包括张量定义、基础算子、自动微分等模块,用户可使用低阶API轻松实现张量定义和求导计算。例如用户可通过Tensor接口自定义张量,使用grad接口计算函数在指定处的导数。
华为昇腾AI全栈介绍
昇腾计算,是基于昇腾系列处理器构建的全栈AI计算基础设施及应用,包括昇腾Ascend系列芯片、Atlas系列硬件、CANN芯片使能、MindSpore AI框架、ModelArts、MindX应用使能等。
昇腾AI全栈如下图所示:
下面简单介绍每个模块的作用:
-
Atlas系列产品:提供AI训练、推理卡及训练服务器(了解更多)。
-
CANN(异构计算架构):芯片使能、驱动层(了解更多)。
-
MindSpore(AI框架):全场景AI框架(了解更多)。
-
MindX SDK(昇腾SDK):行业SDK和应用解决方案(了解更多)。
-
ModelArts(AI开发平台):华为云AI开发平台(了解更多)。
-
MindStudio(全流程开发工具链):AI全流程开发IDE(了解更多)。
详细信息请点击华为昇腾官网。
张量 Tensor
张量是一种特殊的数据结构,与数组和矩阵非常相似。张量(Tensor)是MindSpore网络运算中的基本数据结构。
import numpy as np import mindspore from mindspore import ops from mindspore import Tensor, CSRTensor, COOTensor # 根据数据直接生成 # 可以根据数据创建张量,数据类型可以设置或者通过框架自动推断。 data = [1, 0, 1, 0] x_data = Tensor(data) # 从NumPy数组生成 np_array = np.array(data) x_np = Tensor(np_array) # 使用init初始化器构造张量 from mindspore.common.initializer import One, Normal # Initialize a tensor with ones tensor1 = mindspore.Tensor(shape=(2, 2), dtype=mindspore.float32, init=One()) # Initialize a tensor from normal distribution tensor2 = mindspore.Tensor(shape=(2, 2), dtype=mindspore.float32, init=Normal()) # init主要用于并行模式下的延后初始化,在正常情况下不建议使用init对参数进行初始化。 print("tensor1:\n", tensor1) print("tensor2:\n", tensor2) # 继承另一个张量的属性,形成新的张量 from mindspore import ops x_ones = ops.ones_like(x_data) print(f"Ones Tensor: \n {x_ones} \n") x_zeros = ops.zeros_like(x_data) print(f"Zeros Tensor: \n {x_zeros} \n") # 张量的属性 # 张量的属性包括形状、数据类型、转置张量、单个元素大小、占用字节数量、维数、元素个数和每一维步长。 # 形状(shape):Tensor的shape,是一个tuple。 # 数据类型(dtype):Tensor的dtype,是MindSpore的一个数据类型。 # 单个元素大小(itemsize): Tensor中每一个元素占用字节数,是一个整数。 # 占用字节数量(nbytes): Tensor占用的总字节数,是一个整数。 # 维数(ndim): Tensor的秩,也就是len(tensor.shape),是一个整数。 # 元素个数(size): Tensor中所有元素的个数,是一个整数。 # 每一维步长(strides): Tensor每一维所需要的字节数,是一个tuple。 x = Tensor(np.array([[1, 2], [3, 4]]), mindspore.int32) print("x_shape:", x.shape) print("x_dtype:", x.dtype) print("x_itemsize:", x.itemsize) print("x_nbytes:", x.nbytes) print("x_ndim:", x.ndim) print("x_size:", x.size) print("x_strides:", x.strides)
张量索引
Tensor索引与Numpy索引类似,索引从0开始编制,负索引表示按倒序编制,冒号:
和 ...
用于对数据进行切片。
张量运算
张量之间有很多运算,包括算术、线性代数、矩阵处理(转置、标引、切片)、采样等,张量运算和NumPy的使用方式类似,下面介绍其中几种操作。
普通算术运算有:加(+)、减(-)、乘(*)、除(/)、取模(%)、整除(//)。
稀疏张量
稀疏张量是一种特殊张量,其中绝大部分元素的值为零。
在某些应用场景中(比如推荐系统、分子动力学、图神经网络等),数据的特征是稀疏的,若使用普通张量表征这些数据会引入大量不必要的计算、存储和通讯开销。这时就可以使用稀疏张量来表征这些数据。
MindSpore现在已经支持最常用的CSR
和COO
两种稀疏数据格式。
常用稀疏张量的表达形式是<indices:Tensor, values:Tensor, shape:Tensor>
。其中,indices
表示非零下标元素, values
表示非零元素的值,shape表示的是被压缩的稀疏张量的形状。在这个结构下,我们定义了三种稀疏张量结构:CSRTensor
、COOTensor
和RowTensor
。
数据集 Dataset
MindSpore提供基于Pipeline的数据引擎,通过数据集(Dataset)和数据变换(Transforms)实现高效的数据预处理。其中Dataset是Pipeline的起始,用于加载原始数据。mindspore.dataset
提供了内置的文本、图像、音频等数据集加载接口,并提供了自定义数据集加载接口。此外MindSpore的领域开发库也提供了大量的预加载数据集,可以使用API一键下载使用。
数据集加载
我们使用Mnist数据集作为样例,介绍使用mindspore.dataset
进行加载的方法。mindspore.dataset
提供的接口仅支持解压后的数据文件,因此我们使用download
库下载数据集并解压。压缩文件删除后,直接加载,可以看到其数据类型为MnistDataset。
train_dataset = MnistDataset("MNIST_Data/train", shuffle=False) print(type(train_dataset))
数据集迭代
数据集加载后,一般以迭代方式获取数据,然后送入神经网络中进行训练。我们可以用create_tuple_iterator
或create_dict_iterator
接口创建数据迭代器,迭代访问数据。访问的数据类型默认为Tensor
;若设置output_numpy=True
,访问的数据类型为Numpy
。
数据集常用操作
Pipeline的设计理念使得数据集的常用操作采用dataset = dataset.operation()
的异步执行方式,执行操作返回新的Dataset,此时不执行具体操作,而是在Pipeline中加入节点,最终进行迭代时,并行执行整个Pipeline。
下面分别介绍几种常见的数据集操作。
shuffle
数据集随机shuffle
可以消除数据排列造成的分布不均问题。
map
map
操作是数据预处理的关键操作,可以针对数据集指定列(column)添加数据变换(Transforms),将数据变换应用于该列数据的每个元素,并返回包含变换后元素的新数据集。
batch
将数据集打包为固定大小的batch
是在有限硬件资源下使用梯度下降进行模型优化的折中方法,可以保证梯度下降的随机性和优化计算量。
自定义数据集
mindspore.dataset
提供了部分常用数据集和标准格式数据集的加载接口。对于MindSpore暂不支持直接加载的数据集,可以通过构造自定义数据集类或自定义数据集生成函数的方式来生成数据集,然后通过GeneratorDataset
接口实现自定义方式的数据集加载。GeneratorDataset
支持通过可迭代对象、迭代器和生成函数构造自定义数据集。
可迭代对象
Python中可以使用for循环遍历出所有元素的,都可以称为可迭代对象(Iterable),我们可以通过实现__getitem__
方法来构造可迭代对象,并将其加载至GeneratorDataset
。
迭代器
Python中内置有__iter__
和__next__
方法的对象,称为迭代器(Iterator)。下面构造一个简单迭代器,并将其加载至GeneratorDataset
。
数据变换 Transforms
通常情况下,直接加载的原始数据并不能直接送入神经网络进行训练,此时我们需要对其进行数据预处理。MindSpore提供不同种类的数据变换(Transforms),配合数据处理Pipeline来实现数据预处理。所有的Transforms均可通过map
方法传入,实现对指定数据列的处理。mindspore.dataset
提供了面向图像、文本、音频等不同数据类型的Transforms,同时也支持使用Lambda函数。
网络构建
神经网络模型是由神经网络层和Tensor操作构成的,mindspore.nn
提供了常见神经网络层的实现,在MindSpore中,Cell
类是构建所有网络的基类,也是网络的基本单元。一个神经网络模型表示为一个Cell
,它由不同的子Cell
构成。使用这样的嵌套结构,可以简单地使用面向对象编程的思维,对神经网络结构进行构建和管理。
定义模型类
当我们定义神经网络时,可以继承nn.Cell
类,在__init__
方法中进行子Cell的实例化和状态管理,在construct
方法中实现Tensor操作。
class Network(nn.Cell): def __init__(self): super().__init__() self.flatten = nn.Flatten() self.dense_relu_sequential = nn.SequentialCell( nn.Dense(28*28, 512), nn.ReLU(), nn.Dense(512, 512), nn.ReLU(), nn.Dense(512, 10) ) def construct(self, x): x = self.flatten(x) logits = self.dense_relu_sequential(x) return logits #构建完成后,实例化Network对象,并查看其结构。 model = Network() print(model) #我们构造一个输入数据,直接调用模型,可以获得一个10维的Tensor输出,其包含每个类别的原始预测值。在此基础上,我们通过一个nn.Softmax层实例来获得预测概率。 pred_probab = nn.Softmax(axis=1)(logits) y_pred = pred_probab.argmax(1) print(f"Predicted class: {y_pred}")
模型层
本节中我们分解上节构造的神经网络模型中的每一层。首先我们构造一个shape为(3, 28, 28)的随机数据(3个28x28的图像),依次通过每一个神经网络层来观察其效果。
input_image = ops.ones((3, 28, 28), mindspore.float32) print(input_image.shape) #实例化nn.Flatten层,将28x28的2D张量转换为784大小的连续数组。 flatten = nn.Flatten() flat_image = flatten(input_image) print(flat_image.shape) #nn.Dense为全连接层,其使用权重和偏差对输入进行线性变换。 layer1 = nn.Dense(in_channels=28*28, out_channels=20) hidden1 = layer1(flat_image) print(hidden1.shape) # nn.ReLU层给网络中加入非线性的激活函数,帮助神经网络学习各种复杂的特征。 print(f"Before ReLU: {hidden1}\n\n") hidden1 = nn.ReLU()(hidden1) print(f"After ReLU: {hidden1}") #nn.SequentialCell是一个有序的Cell容器。输入Tensor将按照定义的顺序通过所有Cell。我们可以使用SequentialCell来快速组合构造一个神经网络模型。 seq_modules = nn.SequentialCell( flatten, layer1, nn.ReLU(), nn.Dense(20, 10) ) logits = seq_modules(input_image) print(logits.shape) #最后使用nn.Softmax将神经网络最后一个全连接层返回的logits的值缩放为[0, 1],表示每个类别的预测概率。axis指定的维度数值和为1。 softmax = nn.Softmax(axis=1) pred_probab = softmax(logits)
模型参数
网络内部神经网络层具有权重参数和偏置参数(如nn.Dense
),这些参数会在训练过程中不断进行优化,可通过 model.parameters_and_names()
来获取参数名及对应的参数详情。
函数式自动微分
神经网络的训练主要使用反向传播算法,模型预测值(logits)与正确标签(label)送入损失函数(loss function)获得loss,然后进行反向传播计算,求得梯度(gradients),最终更新至模型参数(parameters)。自动微分能够计算可导函数在某点处的导数值,是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算,该功能对用户屏蔽了大量的求导细节和过程,大大降低了框架的使用门槛。MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口grad
和value_and_grad
。
为了优化模型参数,需要求参数对loss的导数,此时我们调用mindspore.grad函数,来获得function
的微分函数。
这里使用了grad
函数的两个入参,分别为:
-
fn
:待求导的函数。 -
grad_position
:指定求导输入位置的索引。
由于我们对w 和 b求导,因此配置其在function
入参对应的位置(2, 3)
。
使用
grad
获得微分函数是一种函数变换,即输入为函数,输出也为函数。
def function(x, y, w, b): z = ops.matmul(x, w) + b loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z)) return loss grad_fn = mindspore.grad(function, (2, 3)) grads = grad_fn(x, y, w, b) print(grads)
模型训练
模型训练一般分为四个步骤:
-
构建数据集。
-
定义神经网络模型。
-
定义超参、损失函数及优化器。
-
输入数据集进行训练与评估。
超参
超参(Hyperparameters)是可以调整的参数,可以控制模型训练优化的过程,不同的超参数值可能会影响模型训练和收敛速度。目前深度学习模型多采用批量随机梯度下降算法进行优化,随机梯度下降算法的原理如下:
公式中,&amp;amp;amp;lt;span id="MathJax-Element-2-Frame" class="MathJax" data-mathml="&amp;amp;amp;lt;math xmlns=&amp;amp;amp;amp;quot;http://www.w3.org/1998/Math/MathML&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;mi&amp;amp;amp;gt;n&amp;amp;amp;lt;/mi&amp;amp;amp;gt;&amp;amp;amp;lt;/math&amp;amp;amp;gt;"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-43" class="math"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-44" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-45" class="mi"&amp;amp;amp;gt;n是批量大小(batch size),&amp;amp;amp;lt;span id="MathJax-Element-3-Frame" class="MathJax" data-mathml="&amp;amp;amp;lt;math xmlns=&amp;amp;amp;amp;quot;http://www.w3.org/1998/Math/MathML&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;mrow class=&amp;amp;amp;amp;quot;MJX-TeXAtom-ORD&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;mo&amp;amp;amp;gt;&amp;amp;amp;amp;amp;#x3B7;&amp;amp;amp;lt;/mo&amp;amp;amp;gt;&amp;amp;amp;lt;/mrow&amp;amp;amp;gt;&amp;amp;amp;lt;/math&amp;amp;amp;gt;"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-46" class="math"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-47" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-48" class="texatom"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-49" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-50" class="mo"&amp;amp;amp;gt;&amp;amp;eta;η是学习率(learning rate)。另外,&amp;amp;amp;lt;span id="MathJax-Element-4-Frame" class="MathJax" data-mathml="&amp;amp;amp;lt;math xmlns=&amp;amp;amp;amp;quot;http://www.w3.org/1998/Math/MathML&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;msub&amp;amp;amp;gt;&amp;amp;amp;lt;mi&amp;amp;amp;gt;w&amp;amp;amp;lt;/mi&amp;amp;amp;gt;&amp;amp;amp;lt;mrow class=&amp;amp;amp;amp;quot;MJX-TeXAtom-ORD&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;mi&amp;amp;amp;gt;t&amp;amp;amp;lt;/mi&amp;amp;amp;gt;&amp;amp;amp;lt;/mrow&amp;amp;amp;gt;&amp;amp;amp;lt;/msub&amp;amp;amp;gt;&amp;amp;amp;lt;/math&amp;amp;amp;gt;"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-51" class="math"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-52" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-53" class="msubsup"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-54" class="mi"&amp;amp;amp;gt;w&amp;amp;amp;lt;span id="MathJax-Span-55" class="texatom"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-56" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-57" class="mi"&amp;amp;amp;gt;t为训练轮次&amp;amp;amp;lt;span id="MathJax-Element-5-Frame" class="MathJax" data-mathml="&amp;amp;amp;lt;math xmlns=&amp;amp;amp;amp;quot;http://www.w3.org/1998/Math/MathML&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;mi&amp;amp;amp;gt;t&amp;amp;amp;lt;/mi&amp;amp;amp;gt;&amp;amp;amp;lt;/math&amp;amp;amp;gt;"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-58" class="math"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-59" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-60" class="mi"&amp;amp;amp;gt;t中的权重参数,&amp;amp;amp;lt;span id="MathJax-Element-6-Frame" class="MathJax" data-mathml="&amp;amp;amp;lt;math xmlns=&amp;amp;amp;amp;quot;http://www.w3.org/1998/Math/MathML&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;lt;mi mathvariant=&amp;amp;amp;amp;quot;normal&amp;amp;amp;amp;quot;&amp;amp;amp;gt;&amp;amp;amp;amp;amp;#x2207;&amp;amp;amp;lt;/mi&amp;amp;amp;gt;&amp;amp;amp;lt;mi&amp;amp;amp;gt;l&amp;amp;amp;lt;/mi&amp;amp;amp;gt;&amp;amp;amp;lt;/math&amp;amp;amp;gt;"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-61" class="math"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-62" class="mrow"&amp;amp;amp;gt;&amp;amp;amp;lt;span id="MathJax-Span-63" class="mi"&amp;amp;amp;gt;&amp;amp;nabla;&amp;amp;amp;lt;span id="MathJax-Span-64" class="mi"&amp;amp;amp;gt;l∇为损失函数的导数。除了梯度本身,这两个因子直接决定了模型的权重更新,从优化本身来看,它们是影响模型性能收敛最重要的参数。一般会定义以下超参用于训练:
-
训练轮次(epoch):训练时遍历数据集的次数。
-
批次大小(batch size):数据集进行分批读取训练,设定每个批次数据的大小。batch size过小,花费时间多,同时梯度震荡严重,不利于收敛;batch size过大,不同batch的梯度方向没有任何变化,容易陷入局部极小值,因此需要选择合适的batch size,可以有效提高模型精度、全局收敛。
-
学习率(learning rate):如果学习率偏小,会导致收敛的速度变慢,如果学习率偏大,则可能会导致训练不收敛等不可预测的结果。梯度下降法被广泛应用在最小化模型误差的参数优化算法上。梯度下降法通过多次迭代,并在每一步中最小化损失函数来预估模型的参数。学习率就是在迭代过程中,会控制模型的学习进度。
损失函数
损失函数(loss function)用于评估模型的预测值(logits)和目标值(targets)之间的误差。训练模型时,随机初始化的神经网络模型开始时会预测出错误的结果。损失函数会评估预测结果与目标值的相异程度,模型训练的目标即为降低损失函数求得的误差。
常见的损失函数包括用于回归任务的nn.MSELoss
(均方误差)和用于分类的nn.NLLLoss
(负对数似然)等。 nn.CrossEntropyLoss
结合了nn.LogSoftmax
和nn.NLLLoss
,可以对logits 进行归一化并计算预测误差。
优化器
模型优化(Optimization)是在每个训练步骤中调整模型参数以减少模型误差的过程。MindSpore提供多种优化算法的实现,称之为优化器(Optimizer)。优化器内部定义了模型的参数优化过程(即梯度如何更新至模型参数),所有优化逻辑都封装在优化器对象中。在这里,我们使用SGD(Stochastic Gradient Descent)优化器。
我们通过model.trainable_params()
方法获得模型的可训练参数,并传入学习率超参来初始化优化器。
optimizer = nn.SGD(model.trainable_params(), learning_rate=learning_rate)
在训练过程中,通过微分函数可计算获得参数对应的梯度,将其传入优化器中即可实现参数优化,具体形态如下:
grads = grad_fn(inputs)
optimizer(grads)
训练与评估实现
接下来我们定义用于训练的train_loop
函数和用于测试的test_loop
函数。使用函数式自动微分,需先定义正向函数forward_fn
,使用mindspore.value_and_grad
获得微分函数grad_fn
。然后,我们将微分函数和优化器的执行封装为train_step
函数,接下来循环迭代数据集进行训练即可。
def train_loop(model, dataset, loss_fn, optimizer): # Define forward function def forward_fn(data, label): logits = model(data) loss = loss_fn(logits, label) return loss, logits # Get gradient function grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True) # Define function of one-step training def train_step(data, label): (loss, _), grads = grad_fn(data, label) loss = ops.depend(loss, optimizer(grads)) return loss size = dataset.get_dataset_size() model.set_train() for batch, (data, label) in enumerate(dataset.create_tuple_iterator()): loss = train_step(data, label) if batch % 100 == 0: loss, current = loss.asnumpy(), batch print(f"loss: {loss:>7f} [{current:>3d}/{size:>3d}]")
保存与加载
保存和加载模型权重
保存模型使用save_checkpoint
接口,传入网络和指定的保存路径:要加载模型权重,需要先创建相同模型的实例,然后使用load_checkpoint
和load_param_into_net
方法加载参数。
model = network() mindspore.save_checkpoint(model, "model.ckpt")
model = network()
param_dict = mindspore.load_checkpoint("model.ckpt")
param_not_load = mindspore.load_param_into_net(model, param_dict)
print(param_not_load)
保存和加载MindIR
除Checkpoint外,MindSpore提供了云侧(训练)和端侧(推理)统一的中间表示(Intermediate Representation,IR)。可使用export
接口直接将模型保存为MindIR。
model = network() inputs = Tensor(np.ones([1, 1, 28, 28]).astype(np.float32)) mindspore.export(model, inputs, file_name="model", file_format="MINDIR")
MindIR同时保存了Checkpoint和模型结构,因此需要定义输入Tensor来获取输入shape。
已有的MindIR模型可以方便地通过load
接口加载,传入nn.GraphCell
即可进行推理。
nn.GraphCell
仅支持图模式。
mindspore.set_context(mode=mindspore.GRAPH_MODE) graph = mindspore.load("model.mindir") model = nn.GraphCell(graph) outputs = model(inputs) print(outputs.shape)