技术背景

在MindSpore深度学习框架中,我们可以使用mindspore.grad对函数式编程的函数直接计算自动微分,也可以使用mindspore.ops.GradOperation求解Cell类的梯度dout。本文所介绍的mindspore.ops.InsertGradientOf是一个对dout进一步进行处理的算子,类似于在Cell类中自定义一个bprop函数,不改变前向传播输出的结果,只改变反向传播的结果。

测试场景

我们使用一个简单的函数\(f(x,y)=xy^2,\frac{\partial f}{\partial x}=y^2\)来测试一下MindSpore中的自动微分,以及InsertGradientOf算子对梯度的操作。

import numpy as np
from mindspore import Tensor, ops, grad
# 定义Clip函数的上下界
a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32))
# Clip反向传播dx结果
def clip_gradient(dx):
ret = dx
if ret > a:
ret = a
if ret < b:
ret = b
return ret
# 生成一个计算图的节点
clip = ops.InsertGradientOf(clip_gradient)
# 主函数
func = lambda x, y: x * y ** 2
# 带Clip的主函数
def clip_func(x, y):
x = clip(x)
c = func(x, y)
return c
# 带Clip主函数的前向传播
def f(x, y):
return clip_func(x, y)
# 带Clip主函数的反向传播
def fd(x, y):
return grad(clip_func)(x, y)
# 给定x,y的数值
x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32))
# 分别计算Clip前后的主函数的前向传播与反向传播
print("forward: ", func(x, y))
print("backward: ", grad(func)(x, y))
print("clip forward: ", f(x, y))
print("clip backward:", fd(x, y))

输出结果为:

forward:  [-8.] # x*y**2
backward: [4.] # y**2
clip forward: [-8.] # x*y**2
clip backward: [1.1] # clip(y**2)

需要注意的是,虽然我们最终clip的时候操作的是\(\frac{\partial f}{\partial x}=y^2\),但是在函数实现时,clip函数应该施加在\(x\)上面,而不是\(y\)上面,这表示对\(x\)的反向传播进行操作。

InsertGradientOf成员函数

bprop是MindSpore框架中Cell类的一个关于计算反向传播的函数,可以用于计算和处理梯度值。但是有一个比较偏的问题是,bprop的函数输入与construct函数的输入要求要一致,如果参数数量对不上,就会报错。关于这一点,其实torch里面处理的方案会更直观一些,可以参考这篇博客中的两个Issue。而MindSpore中要实现类似的功能,就需要依赖于这个InsertGradientOf算子。先看一个使用bprop处理Clip梯度的示例:

import numpy as np
from mindspore import Tensor, grad, nn
# 定义Clip参数
a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32))
# Clip函数
def clip_gradient(dx):
ret = dx
if ret > a:
ret = a
if ret < b:
ret = b
return ret
# 需要被Clip的Cell类
class Net(nn.Cell):
# 使用bprop处理梯度
def bprop(self, x, y, out, dout):
return clip_gradient(y**2)
def construct(self, x, y):
return x * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32))
net = Net()
print (net(x, y))
print (grad(net)(x, y))

输出结果为:

[-8.]
[1.1]

这里还是比较容易理解的,我们手动推导了一个\(\frac{\partial f}{\partial x}=y^2\),那么就可以把\(y\)参数传给bprop函数,然后计算\(y^2\),最后再计算clip。但是这个方案要求传入到bprop函数的参数是完整的,如果参数匹配不上就会报错:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) def clip_gradient(dx):
ret = dx
if ret > a:
ret = a
if ret < b:
ret = b
return ret clip = ops.InsertGradientOf(clip_gradient) class Net(nn.Cell):
def bprop(self, y, out, dout):
return clip_gradient(y ** 2)
def construct(self, x, y):
return x * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32)) net = Net()
print (net(x, y))
print (grad(net)(x, y))

这里给bprop函数传入的参数跟construct函数是对不齐的,那么计算梯度时就会出现这样的报错:

[-8.]
Traceback (most recent call last):
File "test_insert_gradient.py", line 83, in <module>
print (grad(net)(x, y))
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/ops/composite/base.py", line 622, in after_grad
return grad_(fn_, weights, grad_position)(*args, **kwargs)
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/common/api.py", line 131, in wrapper
results = fn(*arg, **kwargs)
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/ops/composite/base.py", line 601, in after_grad
res = self._pynative_forward_run(fn, grad_, weights, args, kwargs)
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/ops/composite/base.py", line 658, in _pynative_forward_run
outputs = fn(*args, **new_kwargs)
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/nn/cell.py", line 693, in __call__
raise err
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/nn/cell.py", line 690, in __call__
_pynative_executor.end_graph(self, output, *args, **kwargs)
File "/home/dechin/anaconda3/envs/mindspore-latest/lib/python3.7/site-packages/mindspore/common/api.py", line 1264, in end_graph
self._executor.end_graph(obj, output, *args, *(kwargs.values()))
TypeError: Size of bprop func inputs[1] is not equal to the size of cell inputs[2] ----------------------------------------------------
- C++ Call Stack: (For framework developers)
----------------------------------------------------
mindspore/ccsrc/pipeline/pynative/grad/grad.cc:837 GetCustomBpropPrim

但是我们知道,\(\frac{\partial f}{\partial x}=g(y)\)是一个只跟\(y\)有关的函数,其实不用传入\(x\)参数也应该要可以计算其梯度值。

接下来考虑,如果在Cell类外定义一个InsertGradientOf算子构建的函数,那么也可以在Cell类里面使用:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) def clip_gradient(dx):
ret = dx
if ret > a:
ret = a
if ret < b:
ret = b
return ret clip = ops.InsertGradientOf(clip_gradient) class Net(nn.Cell):
def construct(self, x, y):
return clip(x) * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32)) net = Net()
print (net(x, y))
print (grad(net)(x, y))

输出结果为:

[-8.]
[1.1]

这个计算结果是对的,不过我们需要的是这个clip函数最好也能够调用到类本身的一些属性和成员变量,而InsertGradientOf算子也支持对成员函数进行处理:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) class Net(nn.Cell):
def __init__(self):
super().__init__()
self.clip = ops.InsertGradientOf(self.back)
# 把Clip定义成一个成员函数
def back(self, y):
ret = y
if ret > a:
ret = a
if ret < b:
ret = b
return ret
def construct(self, x, y):
return self.clip(x) * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([0.8]).astype(np.float32)) net = Net()
print (net(x, y))
print (grad(net)(x, y))

这里输出的结果为:

[-1.2800001]
[0.64000005]

因为\(y^2=0.64\),未触发边界Clip的条件,因此这里正常输出\(\frac{\partial f}{\partial x}=y^2\),如果稍微调整下输入的\(y\),触发了边界条件,那么梯度就会被Clip:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) class Net(nn.Cell):
def __init__(self):
super().__init__()
self.clip = ops.InsertGradientOf(self.back)
def back(self, y):
ret = y
if ret > a:
ret = a
if ret < b:
ret = b
return ret
def construct(self, x, y):
return self.clip(x) * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32)) net = Net()
print (net(x, y))
print (grad(net)(x, y))

计算结果为:

[-8.]
[1.1]

当然了,如果我们直接返回一个跟\(x\)、\(y\)都无关的参数作为梯度也是可以的:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) class Net(nn.Cell):
def __init__(self):
super().__init__()
self.clip = ops.InsertGradientOf(self.back)
def back(self, dx):
return 100.
def construct(self, x, y):
return self.clip(x) * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32)) net = Net()
print (net(x, y))
print (grad(net)(x, y))

输出结果为:

[-8.]
100.0

如果要再传一些偏置参数到\(x\)的梯度中,例如令\(g=\frac{\partial f}{\partial x}+z\),而这个参数\(z\)一般都是通过construct函数直接传进Cell类的。此时可用的思路是,把这些额外的变量存到类的属性里面,通过读取成员变量再加载到梯度操作函数中:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) class Net(nn.Cell):
def __init__(self):
super().__init__()
self.clip = ops.InsertGradientOf(self.back)
self.z = 0.
def back(self, dx):
ret = dx
if ret > a:
ret = a
if ret < b:
ret = b
return ret + self.z
def construct(self, x, y, z=0.):
self.z = z
return self.clip(x) * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32)) net = Net()
print (net(x, y, z=-1))
print (grad(net)(x, y, z=-1))

输出结果为:

[-8.]
[0.10000002]

这就实现了给梯度修饰函数传参的功能。

优先级问题

凡是有冲突的操作,就必然有一个优先级的顺序。bprop函数是用本地的方法去计算一个梯度值,而InsertGradientOf算子是对某一个变量的梯度值进行处理。因此当这两个函数同时被用于处理一个梯度值时,就需要看看谁的优先级更高:

import numpy as np
from mindspore import Tensor, ops, grad, nn a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32)) class Net(nn.Cell):
def __init__(self):
super().__init__()
self.clip = ops.InsertGradientOf(self.back)
def back(self, y):
ret = y
if ret > a:
ret = a
if ret < b:
ret = b
return ret
def bprop(self, x, y, out, dout):
return 100.
def construct(self, x, y):
return self.clip(x) * y ** 2 x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32)) net = Net()
print (net(x, y))
print (grad(net)(x, y))

在这个案例中,clip函数还是对梯度做一个截断,而bprop函数则是直接返回一个梯度值。那么最终执行的输出结果为:

[-8.]
100.0

这个结果表明,bprop函数的执行优先级要高于InsertGradientOf算子。

总结概要

这篇文章主要介绍了mindspore深度学习框架中基于InsertGradientOf算子的进阶梯度操作。InsertGradientOf算子的功能跟此前介绍过的bprop功能有些类似,也是自定义梯度,但bprop更倾向于计算梯度,而InsertGradientOf算子更倾向于修改梯度,这里介绍了一些比较详细的测试案例。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/InsertGradientOf.html

作者ID:DechinPhy

更多原著文章:https://www.cnblogs.com/dechinphy/

请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

MindSpore梯度进阶操作的更多相关文章

  1. [原创]Scala学习:数组的基本操作,数组进阶操作,多维数组

    1.Scala中提供了一种数据结构-数组,其中存储相同类型的元素的固定大小的连续集合.数组用于存储数据的集合,但它往往是更加有用认为数组作为相同类型的变量的集合 2 声明数组变量: 要使用的程序的数组 ...

  2. Django中的ORM进阶操作

    Django中的ORM进阶操作 Django中是通过ORM来操作数据库的,通过ORM可以很easy的实现与数据库的交互.但是仍然有几种操作是非常绕也特别容易混淆的.于是,针对这一块,来一个分类总结吧. ...

  3. Django之Models进阶操作(字段属性)

    字段属性详细介绍 一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列, ...

  4. 第11章:sed进阶操作

    第11章:sed进阶操作 sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换.删除.新增.选取等特定工作,下面先了解一下sed的用法 sed命令行格式为 ...

  5. 第10章:awk进阶操作

    第10章:awk进阶操作 在第4章:查找与替换简单的讲解了awk的使用,本章介绍详细讲解awk的使用.awk是一个强大的文本分析工具,简单的说awk就是把文件逐行的读入, 以空格为默认分隔符将每行切片 ...

  6. [学习笔记]LCT进阶操作

    LCT总结——应用篇(附题单)(LCT) 一般都是维护链的操作.split即可搞定. 进阶操作的话,处理好辅助树和原树的关系即可搞定. 其实,最大的区别就是,splay随便转,辅助树形态变了,但是原树 ...

  7. Django 之models进阶操作

    到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接数据库,并编写数据访问层代码 业务逻辑层去调用数据访问层执行数据库操作 ...

  8. Django 数据库进阶操作

    基本操作 # 增 # # models.Tb1.objects.create(c1='xx', c2='oo') 增加一条数据,可以接受字典类型数据 **kwargs # obj = models.T ...

  9. web框架-(七)Django补充---models进阶操作及modelform操作

    通过之前的课程我们可以对于Django的models进行简单的操作,今天了解下进阶操作和modelform: 1. Models进阶操作 1.1 字段操作 AutoField(Field) - int ...

  10. 03- Linux目录与文件进阶操作

    第三课 目录与文件进阶操作cat (一次性全部输出文件内容)   cat -n 文件名 显示行号   cat -b 文件名 空行不显示行号less (分页显示,可以上下翻页,光标键上下键,PageUp ...

随机推荐

  1. [一本通1677/JZOJ1217/CJOJ1101]软件开发 题解

    题目描述 一个软件开发公司同时要开发两个软件,并且要同时交付给用户,现在公司为了尽快完成这一任务,将每个软件划分成\(m\)个模块,由公司里的技术人员分工完成,每个技术人员完成同一软件的不同模块的所用 ...

  2. 第十三篇:HTML和CSS入门

    一.HTML本质以及在WEB程序中的作用 1.一套规则,浏览器认识的规则. 2.开发者: 学习html规则 开发后台程序 - 写html文件(充当模板的作用) - 数据库获取数据,然后替换到html文 ...

  3. Linux系统Mariadb初始化相关(ubuntu)

    #事先声明,此文是一边写一边操作的,中间可能有不一致的地方,大体思路就是参照windows下的目录规范,将 mysql的各目录及文件进行类比放置,然后执行重建数据库命令,也许你只是想修改下data目录 ...

  4. JVM简明笔记4:垃圾回收

    1 垃圾回收相关算法 垃圾回收器首先要做的就是,判断一个对象是存活状态还是死亡状态,死亡的对象将会被标识为垃圾数据并等待收集器进行清除. 判断一个对象是否为死亡状态的常用算法有两个:引用计数器算法 . ...

  5. 通过部署流行Web框架掌握Serverless技术

    简介: 通过学习部署流行Web框架,如SpringBoot,Express,Web IDE,让你掌握Serverless函数计算架构和技术,领略弹性并发,高可用的好处.大家好,我是霍大侠,欢迎来到我的 ...

  6. 支付宝移动端 Hybrid 解决方案探索与实践

    支付宝 Hybrid 方案建设与演进 目前支付宝有 2 套 Hybrid 方案: HTML5 容器与小程序.小程序是最近几年才出来,H5 容器已经有了很长时间的历史,所以我们就先从 H5 容器说起. ...

  7. 使用 Arthas 排查 SpringBoot 诡异耗时的 Bug

    简介: 公司有个渠道系统,专门对接三方渠道使用,没有什么业务逻辑,主要是转换报文和参数校验之类的工作,起着一个承上启下的作用.最近,在优化接口的响应时间,优化了代码之后,但是时间还是达不到要求:有一个 ...

  8. Maxcompute造数据-方法详解

    简介: 造一点模拟数据的方法 概述 造数据在一些奇怪的场合会被用到.一般我们是先有数据才有基于数据的应用场合,但是反过来如果应用拿到另外一个场景,没有数据功能是没有方法演示的.一般较为真实的数据,脱敏 ...

  9. 【ESSD技术解读-03】阿里云块存储企业级特性之异步复制

    ​简介: 在大数据时代,数据就是企业的核心资产,是企业的生命线.在现实世界中,灾难时有发生,当发生灾难时,容灾能力成为企业能否生存的关键.云上容灾服务,通常称为 DRaaS(灾难恢复即服务)不但能够省 ...

  10. 基于 MaxCompute + Hologres 的人群圈选和数据服务实践

    ​简介: 本文主要介绍如何通过 MaxCompute 进行海量人群的标签加工,通过 Hologres 进行分析建模,从而支持大规模人群复杂圈选场景下的交互式体验,以及基于API的数据服务最佳实践. 本 ...