一个使用Mindspore进行简单线性函数拟合的例子

转载地址:https://bbs.huaweicloud.com/forum/thread-69090-1-1.html

作者:Yesterday

最近在学习使用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模式下写了一个分步骤过程进行训练的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

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 in range(num):

        x  = np.random.uniform(-5.0,5.0)

        np_x[i]=x

        noise = np.random.normal(0,0.01)

        y  = * + + 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 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])

输出结果:

1

2

3

4

5

6

7

8

9

10

11

12

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的代码其实非常简单:

1

2

3

4

5

6

7

8

9

10

11

12

13

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)函数进行输入的。而在训练过程中:

1

grads = train_network(data_x,data_y)

这一步data_x和data_y分别通过construct函数的两个形参data和label传到了GradWrap,从而进一步传到了WithLossCell。

上面我们在PyNative模式下,一步一步实现了训练过程。其实Mindspore本身有很多设计好的集成化模型,很多内容无需单独写代码。在理解了上述Mindspore训练过程之后,下面我们就利用Mindspore的dataset数据类型和Model类实现上述的功能:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

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 in range(num):

        x  = np.random.uniform(-5.0,5.0)

        noise = np.random.normal(0,0.01)

        y  = * + + 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)

输出结果为:

1

2

3

4

5

6

7

8

9

10

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的设置有几个需要注意的点:

  1. numpy生成的数组默认是float64类型,而Dense类中的参数默认是float32,两者需要统一。在这里我将numpy生成的数组用astype转换成了float32

  2. r0.5版Mindpsore,用生成器产生的dataset类型的dataset_size是空的,所以需要在生成之后用set_dataset_size(num)设置数据的大小。据说r0.6版本会改进,不再需要手工设置。

  3. dataset类型在向loss function输入数据的时候,是按照设置的顺序进行输入的目前版本的自带的输入接口(TrainOneStepCell)有且只有两个,即data和label,所以生成器中必须data在前,label在后。据说r0.7将支持灵活的数据输入模式。

注释2:自带的TrainOneStepCell类的作用类似于上面代码中的GradWrap,但除了计算梯度和提供接口之外,也提供了优化器的接口。

以上就是我的使用mindspore实现线性函数拟合的代码以及我对于Mindpsore一些功能的个人理解,希望对大家有帮助。如果有什么解释不正确的地方也欢迎大家指正。

你可能感兴趣的:(一个使用Mindspore进行简单线性函数拟合的例子)