技术背景

在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算子对梯度的操作。

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad
  3. # 定义Clip函数的上下界
  4. a = Tensor(np.array([1.1]).astype(np.float32))
  5. b = Tensor(np.array([0.1]).astype(np.float32))
  6. # Clip反向传播dx结果
  7. def clip_gradient(dx):
  8. ret = dx
  9. if ret > a:
  10. ret = a
  11. if ret < b:
  12. ret = b
  13. return ret
  14. # 生成一个计算图的节点
  15. clip = ops.InsertGradientOf(clip_gradient)
  16. # 主函数
  17. func = lambda x, y: x * y ** 2
  18. # 带Clip的主函数
  19. def clip_func(x, y):
  20. x = clip(x)
  21. c = func(x, y)
  22. return c
  23. # 带Clip主函数的前向传播
  24. def f(x, y):
  25. return clip_func(x, y)
  26. # 带Clip主函数的反向传播
  27. def fd(x, y):
  28. return grad(clip_func)(x, y)
  29. # 给定x,y的数值
  30. x = Tensor(np.array([-2]).astype(np.float32))
  31. y = Tensor(np.array([2]).astype(np.float32))
  32. # 分别计算Clip前后的主函数的前向传播与反向传播
  33. print("forward: ", func(x, y))
  34. print("backward: ", grad(func)(x, y))
  35. print("clip forward: ", f(x, y))
  36. print("clip backward:", fd(x, y))

输出结果为:

  1. forward: [-8.] # x*y**2
  2. backward: [4.] # y**2
  3. clip forward: [-8.] # x*y**2
  4. 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梯度的示例:

  1. import numpy as np
  2. from mindspore import Tensor, grad, nn
  3. # 定义Clip参数
  4. a = Tensor(np.array([1.1]).astype(np.float32))
  5. b = Tensor(np.array([0.1]).astype(np.float32))
  6. # Clip函数
  7. def clip_gradient(dx):
  8. ret = dx
  9. if ret > a:
  10. ret = a
  11. if ret < b:
  12. ret = b
  13. return ret
  14. # 需要被Clip的Cell类
  15. class Net(nn.Cell):
  16. # 使用bprop处理梯度
  17. def bprop(self, x, y, out, dout):
  18. return clip_gradient(y**2)
  19. def construct(self, x, y):
  20. return x * y ** 2
  21. x = Tensor(np.array([-2]).astype(np.float32))
  22. y = Tensor(np.array([2]).astype(np.float32))
  23. net = Net()
  24. print (net(x, y))
  25. print (grad(net)(x, y))

输出结果为:

  1. [-8.]
  2. [1.1]

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. def clip_gradient(dx):
  6. ret = dx
  7. if ret > a:
  8. ret = a
  9. if ret < b:
  10. ret = b
  11. return ret
  12. clip = ops.InsertGradientOf(clip_gradient)
  13. class Net(nn.Cell):
  14. def bprop(self, y, out, dout):
  15. return clip_gradient(y ** 2)
  16. def construct(self, x, y):
  17. return x * y ** 2
  18. x = Tensor(np.array([-2]).astype(np.float32))
  19. y = Tensor(np.array([2]).astype(np.float32))
  20. net = Net()
  21. print (net(x, y))
  22. print (grad(net)(x, y))

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

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

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

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. def clip_gradient(dx):
  6. ret = dx
  7. if ret > a:
  8. ret = a
  9. if ret < b:
  10. ret = b
  11. return ret
  12. clip = ops.InsertGradientOf(clip_gradient)
  13. class Net(nn.Cell):
  14. def construct(self, x, y):
  15. return clip(x) * y ** 2
  16. x = Tensor(np.array([-2]).astype(np.float32))
  17. y = Tensor(np.array([2]).astype(np.float32))
  18. net = Net()
  19. print (net(x, y))
  20. print (grad(net)(x, y))

输出结果为:

  1. [-8.]
  2. [1.1]

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. class Net(nn.Cell):
  6. def __init__(self):
  7. super().__init__()
  8. self.clip = ops.InsertGradientOf(self.back)
  9. # 把Clip定义成一个成员函数
  10. def back(self, y):
  11. ret = y
  12. if ret > a:
  13. ret = a
  14. if ret < b:
  15. ret = b
  16. return ret
  17. def construct(self, x, y):
  18. return self.clip(x) * y ** 2
  19. x = Tensor(np.array([-2]).astype(np.float32))
  20. y = Tensor(np.array([0.8]).astype(np.float32))
  21. net = Net()
  22. print (net(x, y))
  23. print (grad(net)(x, y))

这里输出的结果为:

  1. [-1.2800001]
  2. [0.64000005]

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. class Net(nn.Cell):
  6. def __init__(self):
  7. super().__init__()
  8. self.clip = ops.InsertGradientOf(self.back)
  9. def back(self, y):
  10. ret = y
  11. if ret > a:
  12. ret = a
  13. if ret < b:
  14. ret = b
  15. return ret
  16. def construct(self, x, y):
  17. return self.clip(x) * y ** 2
  18. x = Tensor(np.array([-2]).astype(np.float32))
  19. y = Tensor(np.array([2]).astype(np.float32))
  20. net = Net()
  21. print (net(x, y))
  22. print (grad(net)(x, y))

计算结果为:

  1. [-8.]
  2. [1.1]

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. class Net(nn.Cell):
  6. def __init__(self):
  7. super().__init__()
  8. self.clip = ops.InsertGradientOf(self.back)
  9. def back(self, dx):
  10. return 100.
  11. def construct(self, x, y):
  12. return self.clip(x) * y ** 2
  13. x = Tensor(np.array([-2]).astype(np.float32))
  14. y = Tensor(np.array([2]).astype(np.float32))
  15. net = Net()
  16. print (net(x, y))
  17. print (grad(net)(x, y))

输出结果为:

  1. [-8.]
  2. 100.0

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. class Net(nn.Cell):
  6. def __init__(self):
  7. super().__init__()
  8. self.clip = ops.InsertGradientOf(self.back)
  9. self.z = 0.
  10. def back(self, dx):
  11. ret = dx
  12. if ret > a:
  13. ret = a
  14. if ret < b:
  15. ret = b
  16. return ret + self.z
  17. def construct(self, x, y, z=0.):
  18. self.z = z
  19. return self.clip(x) * y ** 2
  20. x = Tensor(np.array([-2]).astype(np.float32))
  21. y = Tensor(np.array([2]).astype(np.float32))
  22. net = Net()
  23. print (net(x, y, z=-1))
  24. print (grad(net)(x, y, z=-1))

输出结果为:

  1. [-8.]
  2. [0.10000002]

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

优先级问题

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

  1. import numpy as np
  2. from mindspore import Tensor, ops, grad, nn
  3. a = Tensor(np.array([1.1]).astype(np.float32))
  4. b = Tensor(np.array([0.1]).astype(np.float32))
  5. class Net(nn.Cell):
  6. def __init__(self):
  7. super().__init__()
  8. self.clip = ops.InsertGradientOf(self.back)
  9. def back(self, y):
  10. ret = y
  11. if ret > a:
  12. ret = a
  13. if ret < b:
  14. ret = b
  15. return ret
  16. def bprop(self, x, y, out, dout):
  17. return 100.
  18. def construct(self, x, y):
  19. return self.clip(x) * y ** 2
  20. x = Tensor(np.array([-2]).astype(np.float32))
  21. y = Tensor(np.array([2]).astype(np.float32))
  22. net = Net()
  23. print (net(x, y))
  24. print (grad(net)(x, y))

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

  1. [-8.]
  2. 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. 80+产品正通过兼容性测试,OpenHarmony生态蓬勃发展

    4 月 25 日,开放原子开源基金会举办了 OpenAtom OpenHarmony(以下简称"OpenHarmony")技术日活动,OpenHarmony PMC 委员代表首次对 ...

  2. 拥抱开源更省钱「GitHub 热点速览」

    免费.低成本.自托管.开源替代品...这些词就是本周的热门开源项目的关键字.常见的 AI 提升图片分辨率的工具,大多是在线服务或者调用接口的客户端,而「Upscaler」是一款下载即用的免费 AI 图 ...

  3. Spring源码 20 手写模拟

    项目地址 https://gitee.com/liao-hang/hand-write-spring.git 模拟 Spring 注解 自动装配 Autowired @Target(ElementTy ...

  4. 一个很好用的ORM库--peewee

    发现一个很好用的 ORM 库 -- peewee 以下为简单示例 from peewee import * db = SqliteDatabase('test.db') # 定义表结构 class P ...

  5. 深入理解MD5算法:原理、应用与安全

    第一章:引言 导言 在当今数字化时代,数据安全和完整性变得至关重要.消息摘要算法是一种用于验证数据完整性和安全性的重要工具.在众多消息摘要算法中,MD5(Message Digest Algorith ...

  6. 将 Github Pages 个人博客录入搜索引擎(以 Bing 为例)

    目录 关于 Bing Webmaster Tools 步骤一:登录 步骤二:添加网站 步骤三:验证网站 步骤四:添加网站地图 验证 & 总结 相关链接 笔者最近准备将 Gitee Pages ...

  7. CentOS6.5安装与配置JDK-7

    系统环境:centos-6.5 安装方式:rpm安装 软件:jdk-7-linux-i586.rpm 下载地址:http://www.oracle.com/technetwork/java/javas ...

  8. Android开发 Error:The number of method references in a .dex file cannot exceed 64K.Android开发 Error:The number of method references in a .dex file cannot exceed 64K

    前言 错误起因: 在Android系统中,一个App的所有代码都在一个Dex文件里面. Dex是一个类似Jar的存储了多有Java编译字节码的归档文件. 因为Android系统使用Dalvik虚拟机, ...

  9. Redis工具类,不用框架时备用

    redis.hostName=127.0.0.1 redis.port=6379 redis.database=3 redis.timeout=15000 redis.usePool=true red ...

  10. 力扣613(MySQL)-直线上的最近距离(简单)

    题目: 表 point 保存了一些点在 x 轴上的坐标,这些坐标都是整数. 写一个查询语句,找到这些点中最近两个点之间的距离. 最近距离显然是 '1' ,是点 '-1' 和 '0' 之间的距离.所以输 ...