详解MindSpore自定义模型损失函数


Posted in Python onJune 30, 2021

一、技术背景

损失函数是机器学习中直接决定训练结果好坏的一个模块,该函数用于定义计算出来的结果或者是神经网络给出的推测结论与正确结果的偏差程度,偏差的越多,就表明对应的参数越差。而损失函数的另一个重要性在于会影响到优化函数的收敛性,如果损失函数的指数定义的太高,稍有参数波动就导致结果的巨大波动的话,那么训练和优化就很难收敛。一般我们常用的损失函数是MSE(均方误差)和MAE(平均标准差)等。那么这里我们尝试在MindSpore中去自定义一些损失函数,可用于适应自己的特殊场景。

二、MindSpore内置的损失函数

刚才提到的MSE和MAE等常见损失函数,MindSpore中是有内置的,通过net_loss = nn.loss.MSELoss()即可调用,再传入Model中进行训练,具体使用方法可以参考如下拟合一个非线性函数的案例:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())
net_loss = nn.loss.MSELoss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, net_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

训练的结果如下:

epoch: 1 step: 160, loss is 2.5267093

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[1.0694231  0.12706374]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [5.186701]

The total time cost is: 8.412306308746338s

最终优化出来的loss值是2.5,不过在损失函数定义不同的情况下,单纯只看loss值是没有意义的。所以通常是大家统一定一个测试的标准,比如大家都用MAE来衡量最终训练出来的模型的好坏,但是中间训练的过程不一定采用MAE来作为损失函数。

三、自定义损失函数

由于python语言的灵活性,使得我们可以继承基本类和函数,只要使用mindspore允许范围内的算子,就可以实现自定义的损失函数。我们先看一个简单的案例,暂时将我们自定义的损失函数命名为L1Loss:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.abs = ops.Abs()

    def construct(self, base, target):
        x = self.abs(base - target)
        return self.get_loss(x)

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

这里自己定义的内容实际上有两个部分,一个是construct函数中的计算结果的函数,比如这里使用的是求绝对值。另外一个定义的部分是reduction参数,我们从mindspore的源码中可以看到,这个reduction函数可以决定调用哪一种计算方法,定义好的有平均值、求和、保持不变三种策略。

详解MindSpore自定义模型损失函数

那么最后看下自定义的这个损失函数的运行结果:

epoch: 1 step: 160, loss is 1.8300734

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[ 1.2687287  -0.09565887]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [3.7297544]

The total time cost is: 7.0749146938323975s

这里不必太在乎loss的值,因为前面也提到了,不同的损失函数框架下,计算出来的值就是不一样的,小一点大一点并没有太大意义,最终还是需要大家统一一个标准才能够进行很好的衡量和对比。

四、自定义其他算子

这里我们仅仅是替换了一个abs的算子为square的算子,从求绝对值变化到求均方误差,这里只是修改了一个算子,内容较为简单:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.square = ops.Square()

    def construct(self, base, target):
        x = self.square(base - target)
        return self.get_loss(x)

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

关于更多的算子内容,可以参考下这个链接

(https://www.mindspore.cn/doc/api_python/zh-CN/r1.2/mindspore/mindspore.ops.html)中的内容,

上述代码的运行结果如下:

epoch: 1 step: 160, loss is 2.5267093

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[1.0694231  0.12706374]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [5.186701]

The total time cost is: 6.87545919418335s

可以从这个结果中发现的是,计算出来的结果跟最开始使用的内置的MSELoss结果是一样的,这是因为我们自定义的这个求损失函数的形式与内置的MSE是吻合的。

五、多层算子的应用

上面的两个例子都是简单的说明了一下通过单个算子构造的损失函数,其实如果是一个复杂的损失函数,也可以通过多个算子的组合操作来进行实现:

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean"):
        super(L1Loss, self).__init__(reduction)
        self.square = ops.Square()

    def construct(self, base, target):
        x = self.square(self.square(base - target))
        return self.get_loss(x)

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

这里使用的函数是两个平方算子,也就是四次方的均方误差,运行结果如下:

epoch: 1 step: 160, loss is 16.992222

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[0.14460069 0.32045612]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [5.6676607]

The total time cost is: 7.253541946411133s

在实际的运算过程中,我们肯定不能够说提升损失函数的幂次就一定能够提升结果的优劣,但是通过多种基础算子的组合,理论上说我们在一定的误差允许范围内,是可以实现任意的一个损失函数(通过泰勒展开取截断项)的。

六、重定义reduction

方才提到这里面自定义损失函数的两个重点,一个是上面三个章节中所演示的construct函数的重写,这部分实际上是重新设计损失函数的函数表达式。另一个是reduction的自定义,这部分关系到不同的单点损失函数值之间的关系。举个例子来说,如果我们将reduction设置为求和,那么get_loss()这部分的函数内容就是把所有的单点函数值加起来返回一个最终的值,求平均值也是类似的。那么通过自定义一个新的get_loss()函数,我们就可以实现更加灵活的一些操作,比如我们可以选择将所有的结果乘起来求积而不是求和(只是举个例子,大部分情况下不会这么操作)。在python中要重写这个函数也容易,就是在继承父类的自定义类中定义一个同名函数即可,但是注意我们最好是保留原函数中的一些内容,在原内容的基础上加一些东西,冒然改模块有可能导致不好定位的运行报错。

# test_nonlinear.py

from mindspore import context
import numpy as np
from mindspore import dataset as ds
from mindspore import nn, Tensor, Model
import time
from mindspore.train.callback import Callback, LossMonitor
import mindspore as ms
import mindspore.ops as ops
from mindspore.nn.loss.loss import Loss
ms.common.set_seed(0)

def get_data(num, a=2.0, b=3.0, c=5.0):
    for _ in range(num):
        x = np.random.uniform(-1.0, 1.0)
        y = np.random.uniform(-1.0, 1.0)
        noise = np.random.normal(0, 0.03)
        z = a * x ** 2 + b * y ** 3 + c + noise
        yield np.array([[x**2], [y**3]],dtype=np.float32).reshape(1,2), np.array([z]).astype(np.float32)

def create_dataset(num_data, batch_size=16, repeat_size=1):
    input_data = ds.GeneratorDataset(list(get_data(num_data)), column_names=['xy','z'])
    input_data = input_data.batch(batch_size)
    input_data = input_data.repeat(repeat_size)
    return input_data

data_number = 160
batch_number = 10
repeat_number = 10

ds_train = create_dataset(data_number, batch_size=batch_number, repeat_size=repeat_number)

class LinearNet(nn.Cell):
    def __init__(self):
        super(LinearNet, self).__init__()
        self.fc = nn.Dense(2, 1, 0.02, 0.02)

    def construct(self, x):
        x = self.fc(x)
        return x

start_time = time.time()
net = LinearNet()
model_params = net.trainable_params()
print ('Param Shape is: {}'.format(len(model_params)))
for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

class L1Loss(Loss):
    def __init__(self, reduction="mean", config=True):
        super(L1Loss, self).__init__(reduction)
        self.square = ops.Square()
        self.config = config

    def construct(self, base, target):
        x = self.square(base - target)
        return self.get_loss(x)
    
    def get_loss(self, x, weights=1.0):
        print ('The data shape of x is: ', x.shape)
        input_dtype = x.dtype
        x = self.cast(x, ms.common.dtype.float32)
        weights = self.cast(weights, ms.common.dtype.float32)
        x = self.mul(weights, x)
        if self.reduce and self.average:
            x = self.reduce_mean(x, self.get_axis(x))
        if self.reduce and not self.average:
            x = self.reduce_sum(x, self.get_axis(x))
        if self.config:
            x = self.reduce_mean(x, self.get_axis(x))
            weights = self.cast(-1.0, ms.common.dtype.float32)
            x = self.mul(weights, x)
        x = self.cast(x, input_dtype)
        return x

user_loss = L1Loss()

optim = nn.Momentum(net.trainable_params(), learning_rate=0.01, momentum=0.6)
model = Model(net, user_loss, optim)

epoch = 1
model.train(epoch, ds_train, callbacks=[LossMonitor(10)], dataset_sink_mode=True)

for net_param in net.trainable_params():
    print(net_param, net_param.asnumpy())

print ('The total time cost is: {}s'.format(time.time() - start_time))

上述代码就是一个简单的案例,这里我们所做的操作,仅仅是把之前均方误差的求和改成了求和之后取负数。还是需要再强调一遍的是,虽然我们定义的函数是非常简单的内容,但是借用这个方法,我们可以更加灵活的去按照自己的设计定义一些定制化的损失函数。上述代码的执行结果如下:

The data shape of x is: 

(10, 10,  1)

...

The data shape of x is: 

(10, 10,  1)

epoch: 1 step: 160, loss is -310517200.0

Parameter (name=fc.weight, shape=(1, 2), dtype=Float32, requires_grad=True) [[-6154.176    667.4569]]

Parameter (name=fc.bias, shape=(1,), dtype=Float32, requires_grad=True) [-16418.32]

The total time cost is: 6.681089878082275s

一共打印了160个The data shape of x is...,这是因为我们在划分输入的数据集的时候,选择了将160个数据划分为每个batch含10个元素的模块,那么一共就有16个batch,又对这16个batch重复10次,那么就是一共有160个batch,计算损失函数时是以batch为单位的,但是如果只是计算求和或者求平均值的话,不管划分多少个batch结果都是一致的。

以上就是详解MindSpore自定义模型损失函数的详细内容,更多关于MindSpore自定义模型损失函数的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python使用循环实现批量创建文件夹示例
Mar 25 Python
Linux RedHat下安装Python2.7开发环境
May 20 Python
Python输出带颜色的字符串实例
Oct 10 Python
python:pandas合并csv文件的方法(图书数据集成)
Apr 12 Python
python3+PyQt5使用数据库窗口视图
Apr 24 Python
django缓存配置的几种方法详解
Jul 16 Python
python聚类算法解决方案(rest接口/mpp数据库/json数据/下载图片及数据)
Aug 28 Python
Python队列、进程间通信、线程案例
Oct 25 Python
python使用openpyxl操作excel的方法步骤
May 28 Python
Python 实现图片转字符画的示例(静态图片,gif皆可)
Nov 05 Python
python-图片流传输的思路及示例(url转换二维码)
Dec 21 Python
教你如何使用Python Tkinter库制作记事本
Jun 10 Python
教你用python实现12306余票查询
python实现简易自习室座位预约系统
如何用Python搭建gRPC服务
python not运算符的实例用法
Jun 30 #Python
pycharm部署django项目到云服务器的详细流程
Python快速实现一键抠图功能的全过程
总结python多进程multiprocessing的相关知识
Jun 29 #Python
You might like
完美实现GIF动画缩略图的php代码
2011/01/02 PHP
CodeIgniter框架URL路由总结
2014/09/03 PHP
字符串长度函数strlen和mb_strlen的区别示例介绍
2014/09/09 PHP
php字符串比较函数用法小结(strcmp,strcasecmp,strnatcmp及strnatcasecmp)
2016/07/18 PHP
jquery+thinkphp实现跨域抓取数据的方法
2016/10/15 PHP
PHP+Oracle本地开发环境搭建方法详解
2019/04/01 PHP
游戏人文件夹程序 ver 4.03
2006/07/14 Javascript
JQuery 弹出框定位实现方法
2010/12/02 Javascript
Ext.get() 和 Ext.query()组合使用实现最灵活的取元素方式
2011/09/26 Javascript
JavaScript高级程序设计 阅读笔记(四) ECMAScript中的类型转换
2012/02/27 Javascript
深入解析contentWindow, contentDocument
2013/07/04 Javascript
JS过滤url参数特殊字符的实现方法
2013/12/24 Javascript
JS兼容浏览器的导出Excel(CSV)文件的方法
2014/05/03 Javascript
Node.js实现JS文件合并小工具
2016/02/02 Javascript
基于jquery实现表格内容筛选功能实例解析
2016/05/09 Javascript
JavaScript实现单击网页任意位置打开新窗口与关闭窗口的方法
2017/09/21 Javascript
seajs中模块依赖的加载处理实例分析
2017/10/10 Javascript
jQuery实现下拉菜单动态添加数据点击滑出收起其他功能
2018/06/14 jQuery
详解使用VUE搭建后台管理系统(vue-cli更新至3.0)
2018/08/22 Javascript
使用 electron 实现类似新版 QQ 的登录界面效果(阴影、背景动画、窗体3D翻转)
2018/10/23 Javascript
javaScript 实现重复输出给定的字符串的常用方法小结
2020/02/20 Javascript
[02:10]DOTA2亚洲邀请赛 EG战队出场宣传片
2015/02/07 DOTA
Python编写生成验证码的脚本的教程
2015/05/04 Python
Python随机数用法实例详解【基于random模块】
2017/04/18 Python
回调函数的意义以及python实现实例
2017/06/20 Python
详解Python最长公共子串和最长公共子序列的实现
2018/07/07 Python
python取均匀不重复的随机数方式
2019/11/27 Python
屏蔽Django admin界面添加按钮的操作
2020/03/11 Python
python eventlet绿化和patch原理
2020/11/21 Python
澳大利高级泳装品牌:Bondi Born
2018/05/23 全球购物
什么是三层交换,说说和路由的区别在那里
2014/09/01 面试题
网站推广策划方案
2014/06/04 职场文书
2015年教师见习期工作总结
2015/05/20 职场文书
python 实现mysql自动增删分区的方法
2021/04/01 Python
浅谈自定义校验注解ConstraintValidator
2021/06/30 Java/Android
Python使用pyecharts控件绘制图表
2022/06/05 Python