這篇文章主要介紹了mindspore深度學(xué)習(xí)框架中基于InsertGradientOf算子的進(jìn)階梯度操作。InsertGradientOf算子的功能跟此前介紹過的bprop功能有些類似,也是自定義梯度,但bprop更傾向于計算梯度,而InsertGradientOf算子更傾向于修改梯度,這里介紹了一
在MindSpore深度學(xué)習(xí)框架中,我們可以使用
mindspore.grad
對函數(shù)式編程的函數(shù)直接計算自動微分,也可以使用
mindspore.ops.GradOperation
求解Cell類的梯度dout。本文所介紹的
mindspore.ops.InsertGradientOf
是一個對dout進(jìn)一步進(jìn)行處理的算子,類似于
在Cell類中自定義一個bprop函數(shù)
,不改變前向傳播輸出的結(jié)果,只改變反向傳播的結(jié)果。
我們使用一個簡單的函數(shù) \(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函數(shù)的上下界
a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32))
# Clip反向傳播dx結(jié)果
def clip_gradient(dx):
ret = dx
if ret > a:
ret = a
if ret < b:
ret = b
return ret
# 生成一個計算圖的節(jié)點
clip = ops.InsertGradientOf(clip_gradient)
# 主函數(shù)
func = lambda x, y: x * y ** 2
# 帶Clip的主函數(shù)
def clip_func(x, y):
x = clip(x)
c = func(x, y)
return c
# 帶Clip主函數(shù)的前向傳播
def f(x, y):
return clip_func(x, y)
# 帶Clip主函數(shù)的反向傳播
def fd(x, y):
return grad(clip_func)(x, y)
# 給定x,y的數(shù)值
x = Tensor(np.array([-2]).astype(np.float32))
y = Tensor(np.array([2]).astype(np.float32))
# 分別計算Clip前后的主函數(shù)的前向傳播與反向傳播
print("forward: ", func(x, y))
print("backward: ", grad(func)(x, y))
print("clip forward: ", f(x, y))
print("clip backward:", fd(x, y))
輸出結(jié)果為:
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\) ,但是在函數(shù)實現(xiàn)時,clip函數(shù)應(yīng)該施加在 \(x\) 上面,而不是 \(y\) 上面,這表示對 \(x\) 的反向傳播進(jìn)行操作。
bprop是MindSpore框架中Cell類的一個關(guān)于計算反向傳播的函數(shù),可以用于計算和處理梯度值。但是有一個比較偏的問題是,bprop的函數(shù)輸入與construct函數(shù)的輸入要求要一致,如果參數(shù)數(shù)量對不上,就會報錯。關(guān)于這一點,其實torch里面處理的方案會更直觀一些,可以參考 這篇博客 中的兩個Issue。而MindSpore中要實現(xiàn)類似的功能,就需要依賴于這個InsertGradientOf算子。先看一個使用bprop處理Clip梯度的示例:
import numpy as np
from mindspore import Tensor, grad, nn
# 定義Clip參數(shù)
a = Tensor(np.array([1.1]).astype(np.float32))
b = Tensor(np.array([0.1]).astype(np.float32))
# Clip函數(shù)
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))
輸出結(jié)果為:
[-8.]
[1.1]
這里還是比較容易理解的,我們手動推導(dǎo)了一個 \(\frac{\partial f}{\partial x}=y^2\) ,那么就可以把 \(y\) 參數(shù)傳給bprop函數(shù),然后計算 \(y^2\) ,最后再計算clip。但是這個方案要求傳入到bprop函數(shù)的參數(shù)是完整的,如果參數(shù)匹配不上就會報錯:
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函數(shù)傳入的參數(shù)跟construct函數(shù)是對不齊的,那么計算梯度時就會出現(xiàn)這樣的報錯:
[-8.]
Traceback (most recent call last):
File "test_insert_gradient.py", line 83, in
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\) 有關(guān)的函數(shù),其實不用傳入 \(x\) 參數(shù)也應(yīng)該要可以計算其梯度值。
接下來考慮,如果在Cell類外定義一個InsertGradientOf算子構(gòu)建的函數(shù),那么也可以在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))
輸出結(jié)果為:
[-8.]
[1.1]
這個計算結(jié)果是對的,不過我們需要的是這個clip函數(shù)最好也能夠調(diào)用到類本身的一些屬性和成員變量,而InsertGradientOf算子也支持對成員函數(shù)進(jìn)行處理:
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定義成一個成員函數(shù)
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))
這里輸出的結(jié)果為:
[-1.2800001]
[0.64000005]
因為 \(y^2=0.64\) ,未觸發(fā)邊界Clip的條件,因此這里正常輸出 \(\frac{\partial f}{\partial x}=y^2\) ,如果稍微調(diào)整下輸入的 \(y\) ,觸發(fā)了邊界條件,那么梯度就會被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))
計算結(jié)果為:
[-8.]
[1.1]
當(dāng)然了,如果我們直接返回一個跟 \(x\) 、 \(y\) 都無關(guān)的參數(shù)作為梯度也是可以的:
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))
輸出結(jié)果為:
[-8.]
100.0
如果要再傳一些偏置參數(shù)到 \(x\) 的梯度中,例如令 \(g=\frac{\partial f}{\partial x}+z\) ,而這個參數(shù) \(z\) 一般都是通過construct函數(shù)直接傳進(jìn)Cell類的。此時可用的思路是,把這些額外的變量存到類的屬性里面,通過讀取成員變量再加載到梯度操作函數(shù)中:
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))
輸出結(jié)果為:
[-8.]
[0.10000002]
這就實現(xiàn)了給梯度修飾函數(shù)傳參的功能。
凡是有沖突的操作,就必然有一個優(yōu)先級的順序。bprop函數(shù)是用本地的方法去計算一個梯度值,而InsertGradientOf算子是對某一個變量的梯度值進(jìn)行處理。因此當(dāng)這兩個函數(shù)同時被用于處理一個梯度值時,就需要看看誰的優(yōu)先級更高:
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函數(shù)還是對梯度做一個截斷,而bprop函數(shù)則是直接返回一個梯度值。那么最終執(zhí)行的輸出結(jié)果為:
[-8.]
100.0
這個結(jié)果表明,bprop函數(shù)的執(zhí)行優(yōu)先級要高于InsertGradientOf算子。
這篇文章主要介紹了mindspore深度學(xué)習(xí)框架中基于InsertGradientOf算子的進(jìn)階梯度操作。InsertGradientOf算子的功能跟此前介紹過的bprop功能有些類似,也是自定義梯度,但bprop更傾向于計算梯度,而InsertGradientOf算子更傾向于修改梯度,這里介紹了一些比較詳細(xì)的測試案例。
本文首發(fā)鏈接為: https://www.cnblogs.com/dechinphy/p/InsertGradientOf.html
作者ID:DechinPhy
更多原著文章: https://www.cnblogs.com/dechinphy/
請博主喝咖啡: https://www.cnblogs.com/dechinphy/gallery/image/379634.html
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
閱讀華為Mate品牌盛典:HarmonyOS NEXT加持下游戲性能得到充分釋放
閱讀實現(xiàn)對象集合與DataTable的相互轉(zhuǎn)換
閱讀算法與數(shù)據(jù)結(jié)構(gòu) 1 - 模擬
閱讀5. Spring Cloud OpenFeign 聲明式 WebService 客戶端的超詳細(xì)使用
閱讀Java代理模式:靜態(tài)代理和動態(tài)代理的對比分析
閱讀Win11筆記本“自動管理應(yīng)用的顏色”顯示規(guī)則
閱讀本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請發(fā)郵件[email protected]
湘ICP備2022002427號-10 湘公網(wǎng)安備:43070202000427號© 2013~2025 haote.com 好特網(wǎng)