一个使用Mindspore进行简单线性函数拟合的例子
最近在学习使用Mindpsore,经过这几个星期的学习,并同很多华为的朋友进行咨询以后,总算基本上理解了Mindpsore的基本操作。我在学习中发现,官网教程一上来就用一个图片分类的例子作为基础教程,对于初学者来说非常不友好。所以在这里我给大家分享一个我自己参照官网教程编写的,比图片分类更简单的线性函数拟合的例子,希望能对大家学习使用Mindpsore有所帮助。
我们希望实现一个对 y = w * x + b 这么一个简单函数进行参数拟合,这里我们用 y = 2x + 3 + noise 的方程产生随机数据,并使用线性回归函数 l = \sum_i 1/2 * ( y_i - y'_i )^2 作为损失函数。建模方面我们直接用Mindspore的Dense类生成一个1*1维的网络,优化算法使用了目前GPU版支持的RMSProp算子作为优化器。为了便于大家理解Mindspore的工作原理,首先我在PyNative模式下写了一个分步骤过程进行训练的代码:
import numpy as np import mindspore as ms from mindspore.ops import composite as C from mindspore.ops import operations as P from mindspore import Tensor from mindspore import context from mindspore.common.initializer import TruncatedNormal from mindspore import nn from mindspore.train import Model context.set_context(mode=context.PYNATIVE_MODE, device_target="GPU") # 生成训练数据 def get_data(num,w=2.0,b=3.0): np_x=np.ones([num,1]) np_y=np.ones([num,1]) for i in range(num): x = np.random.uniform(-5.0,5.0) np_x[i]=x noise = np.random.normal(0,0.01) y = x * w + b + noise np_y[i]=y return Tensor(np_x,ms.float32),Tensor(np_y,ms.float32) # 自定义损失函数的形式: 1/2 * (y - y')^2 class MyLoss(nn.loss.loss._Loss): def __init__(self,reduction='mean'): super().__init__(reduction) self.square=P.Square() def construct(self,data,label): x=self.square(data-label)*0.5 return self.get_loss(x) # 生成参数梯度 class GradWrap(nn.Cell): """ GradWrap definition """ def __init__(self, network): super().__init__(auto_prefix=False) self.network = network self.weights = ms.ParameterTuple(filter(lambda x: x.requires_grad, network.get_parameters())) # 注释2 def construct(self, data, label): weights = self.weights return C.GradOperation('get_by_list', get_by_list=True) \ (self.network, weights)(data, label) #设置网络结构,并将参数初始化 net=nn.Dense(1,1,TruncatedNormal(0.02),TruncatedNormal(0.02)) #设置损失函数 criterion=MyLoss() loss_opeartion = nn.WithLossCell(net, criterion) # 注释1 train_network = GradWrap(loss_opeartion) # 注释2 train_network.set_train() #设置优化算法 optim = nn.RMSProp(params=net.trainable_params(),learning_rate=0.1) #进行优化训练 epoch_size=100 batch_size=16 for i in range(epoch_size): data_x,data_y=get_data(batch_size) grads = train_network(data_x,data_y) #读取训练参数,注释2 optim(grads) # 进行单次训练 #打印损失函数的值 if i%10 == 0: output=net(data_x) loss_output = criterion(output, data_y) print(loss_output.asnumpy()) #打印参数 print([net.weight.default_input,net.bias.default_input])
输出结果:
already has forward run before grad by user 11.686171 2.5589511 0.18780856 0.018050613 0.00046021695 3.4643475e-05 5.043277e-05 0.00011456126 0.03616023 0.00042373774 [[[1.9692531]], [3.0024445]]
可见训练后的w和b的参数基本上已经非常接近预设的 w = 2 , b = 3 了。
上述代码是参照官网《使用PyNative模式调试》写的。下面对上面的几个地方做一下说明:
注释1:Mindspore自带的WithLossCell的代码其实非常简单:
class WithLossCell(Cell): def __init__(self, backbone, loss_fn): super(WithLossCell, self).__init__(auto_prefix=False) self._backbone = backbone self._loss_fn = loss_fn def construct(self, data, label): out = self._backbone(data) return self._loss_fn(out, label) @property def backbone_network(self): return self._backbone
可以看到,其WithLossCell的作用是将网络模型和损失函数的表达式联系起来,在construct函数中才真正定义了损失函数作用形式。换句话说,其实这里WithLossCell才是真正意义上的“损失函数”,之前定义的“MyLoss”(也包括Mindspore自带的那些Loss function)本质上只是“损失函数的表达式”而以。因此,如果想要自定义复杂形式的损失函数,只需要自己定义一个类似WithLossCell的类即可。
注释2:GrapWrap这个类,除了计算参数的梯度之外,还有一个重要的作用就是为训练数据的输入提供接口。注意到WithLossCell类的construct函数的两个输入参数(data,label),是通过GradWrap类中的construct(data,label)函数进行输入的。而在训练过程中:
grads = train_network(data_x,data_y)
这一步data_x和data_y分别通过construct函数的两个形参data和label传到了GradWrap,从而进一步传到了WithLossCell。
上面我们在PyNative模式下,一步一步实现了训练过程。其实Mindspore本身有很多设计好的集成化模型,很多内容无需单独写代码。在理解了上述Mindspore训练过程之后,下面我们就利用Mindspore的dataset数据类型和Model类实现上述的功能:
import numpy as np import mindspore as ms from mindspore.ops import composite as C from mindspore.ops import operations as P from mindspore import Tensor from mindspore import context from mindspore import dataset as ds from mindspore.common.initializer import TruncatedNormal from mindspore import nn from mindspore.train import Model from mindspore.common.api import ms_function from mindspore.train.callback import LossMonitor context.set_context(mode=context.GRAPH_MODE, device_target="GPU") # 生成训练数据的产生器,注释1 def get_data(num,w=2.0,b=3.0): for i in range(num): x = np.random.uniform(-5.0,5.0) noise = np.random.normal(0,0.01) y = x * w + b + noise yield np.array([x]).astype(np.float32),np.array([y]).astype(np.float32) # 自定义损失函数的形式: 1/2 * (y - y')^2 class MyLoss(nn.loss.loss._Loss): def __init__(self,reduction='mean'): super().__init__(reduction) self.square=P.Square() @ms_function def construct(self,data,label): x=self.square(data-label)*0.5 return self.get_loss(x) #设置网络结构,并将参数初始化 net=nn.Dense(1,1,TruncatedNormal(0.02),TruncatedNormal(0.02)) #准备训练数据 num=1600 batch_size=16 repeat_size=1 input_data = ds.GeneratorDataset(get_data(num),column_names=['data','label'], num_samples=num,shuffle=False) # 注释1 input_data.set_dataset_size(num) # Mindspore r0.5 及以前的版本必须 input_data = input_data.batch(batch_size) input_data = input_data.repeat(repeat_size) #设置损失函数 criterion=MyLoss() loss_opeartion = nn.WithLossCell(net, criterion) #设置优化算法 optim = nn.RMSProp(params=net.trainable_params(),learning_rate=0.1) #设置训练模式 train_net=nn.TrainOneStepCell(loss_opeartion,optim) # 注释2 model = Model(train_net) #进行训练,并打印中间过程的损失函数值(使用LossMonitor) model.train(repeat_size,input_data,callbacks=LossMonitor(),dataset_sink_mode=False) #打印参数 print(net.weight.default_input,net.bias.default_input)
输出结果为:
epoch: 1 step 1, loss is 85.78379821777344 epoch: 1 step 2, loss is 55.65464782714844 epoch: 1 step 3, loss is 34.5796012878418 epoch: 1 step 4, loss is 38.71565246582031 epoch: 1 step 5, loss is 34.45740509033203 ... epoch: 1 step 100, loss is 0.051551684737205505 Epoch time: 1481.641, per step time: 14.816, avg loss: 3.774 ************************************************************ [[2.0914884]] [2.9273274]
上述代码参照了官网《实现一个图片分类应用》教程。此代码同样使用了Dense作为网络模型、RMSProp作为优化器,并且同样使用了自定义的损失函数形式。但与上面代码不同的是,这里用了mindspore自带的dataset类型作为训练数据的输入,并且训练过程是在mindspore的Model类中自动完成的。
下面对代码的部分内容进行说明:
注释1:使用dataset类作为训练数据的存储类型可以很方便的在mindspore的训练过程在进行数据输入,mindspore本身自带了很多dataset类型,比如官网《实现一个图片分类应用》教程就使用了了MnistDataset类型。其实我们完全可以使用GeneratorDataset产生自己的数据类型。具体方式有两种,一种是使用生成器(generator),另一种则是含有__getitem__的自定义类,具体方法请参见官网《加载数据集》教程和GeneratorDataset的使用说明。在这里我们使用了生成器的形式产生了自己的dataset类。关于dataset的设置有几个需要注意的点:
-
numpy生成的数组默认是float64类型,而Dense类中的参数默认是float32,两者需要统一。在这里我将numpy生成的数组用astype转换成了float32
-
r0.5版Mindpsore,用生成器产生的dataset类型的dataset_size是空的,所以需要在生成之后用set_dataset_size(num)设置数据的大小。据说r0.6版本会改进,不再需要手工设置。
-
dataset类型在向loss function输入数据的时候,是按照设置的顺序进行输入的。目前版本的自带的输入接口(TrainOneStepCell)有且只有两个,即data和label,所以生成器中必须data在前,label在后。据说r0.7将支持灵活的数据输入模式。
注释2:自带的TrainOneStepCell类的作用类似于上面代码中的GradWrap,但除了计算梯度和提供接口之外,也提供了优化器的接口。
以上就是我的使用mindspore实现线性函数拟合的代码以及我对于Mindpsore一些功能的个人理解,希望对大家有帮助。如果有什么解释不正确的地方也欢迎大家指正。