您的位置:首頁(yè) > 軟件教程 > 教程 > MindSpore梯度進(jìn)階操作

MindSpore梯度進(jìn)階操作

來(lái)源:好特整理 | 時(shí)間:2024-05-16 11:31:22 | 閱讀:141 |  標(biāo)簽: S in   | 分享到:

這篇文章主要介紹了mindspore深度學(xué)習(xí)框架中基于InsertGradientOf算子的進(jìn)階梯度操作。InsertGradientOf算子的功能跟此前介紹過(guò)的bprop功能有些類似,也是自定義梯度,但bprop更傾向于計(jì)算梯度,而InsertGradientOf算子更傾向于修改梯度,這里介紹了一

技術(shù)背景

在MindSpore深度學(xué)習(xí)框架中,我們可以使用 mindspore.grad 對(duì)函數(shù)式編程的函數(shù)直接計(jì)算自動(dòng)微分,也可以使用 mindspore.ops.GradOperation 求解Cell類的梯度dout。本文所介紹的 mindspore.ops.InsertGradientOf 是一個(gè)對(duì)dout進(jìn)一步進(jìn)行處理的算子,類似于 在Cell類中自定義一個(gè)bprop函數(shù) ,不改變前向傳播輸出的結(jié)果,只改變反向傳播的結(jié)果。

測(cè)試場(chǎng)景

我們使用一個(gè)簡(jiǎn)單的函數(shù) \(f(x,y)=xy^2,\frac{\partial f}{\partial x}=y^2\) 來(lái)測(cè)試一下MindSpore中的自動(dòng)微分,以及InsertGradientOf算子對(duì)梯度的操作。

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
# 生成一個(gè)計(jì)算圖的節(jié)點(diǎn)
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))
# 分別計(jì)算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的時(shí)候操作的是 \(\frac{\partial f}{\partial x}=y^2\) ,但是在函數(shù)實(shí)現(xiàn)時(shí),clip函數(shù)應(yīng)該施加在 \(x\) 上面,而不是 \(y\) 上面,這表示對(duì) \(x\) 的反向傳播進(jìn)行操作。

InsertGradientOf成員函數(shù)

bprop是MindSpore框架中Cell類的一個(gè)關(guān)于計(jì)算反向傳播的函數(shù),可以用于計(jì)算和處理梯度值。但是有一個(gè)比較偏的問(wèn)題是,bprop的函數(shù)輸入與construct函數(shù)的輸入要求要一致,如果參數(shù)數(shù)量對(duì)不上,就會(huì)報(bào)錯(cuò)。關(guān)于這一點(diǎn),其實(shí)torch里面處理的方案會(huì)更直觀一些,可以參考 這篇博客 中的兩個(gè)Issue。而MindSpore中要實(shí)現(xiàn)類似的功能,就需要依賴于這個(gè)InsertGradientOf算子。先看一個(gè)使用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òng)推導(dǎo)了一個(gè) \(\frac{\partial f}{\partial x}=y^2\) ,那么就可以把 \(y\) 參數(shù)傳給bprop函數(shù),然后計(jì)算 \(y^2\) ,最后再計(jì)算clip。但是這個(gè)方案要求傳入到bprop函數(shù)的參數(shù)是完整的,如果參數(shù)匹配不上就會(huì)報(bào)錯(cuò):

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ù)是對(duì)不齊的,那么計(jì)算梯度時(shí)就會(huì)出現(xiàn)這樣的報(bào)錯(cuò):

[-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)\) 是一個(gè)只跟 \(y\) 有關(guān)的函數(shù),其實(shí)不用傳入 \(x\) 參數(shù)也應(yīng)該要可以計(jì)算其梯度值。

接下來(lái)考慮,如果在Cell類外定義一個(gè)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]

這個(gè)計(jì)算結(jié)果是對(duì)的,不過(guò)我們需要的是這個(gè)clip函數(shù)最好也能夠調(diào)用到類本身的一些屬性和成員變量,而InsertGradientOf算子也支持對(duì)成員函數(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定義成一個(gè)成員函數(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àn)? \(y^2=0.64\) ,未觸發(fā)邊界Clip的條件,因此這里正常輸出 \(\frac{\partial f}{\partial x}=y^2\) ,如果稍微調(diào)整下輸入的 \(y\) ,觸發(fā)了邊界條件,那么梯度就會(huì)被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))

計(jì)算結(jié)果為:

[-8.]
[1.1]

當(dāng)然了,如果我們直接返回一個(gè)跟 \(x\) 、 \(y\) 都無(wú)關(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\) ,而這個(gè)參數(shù) \(z\) 一般都是通過(guò)construct函數(shù)直接傳進(jìn)Cell類的。此時(shí)可用的思路是,把這些額外的變量存到類的屬性里面,通過(guò)讀取成員變量再加載到梯度操作函數(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]

這就實(shí)現(xiàn)了給梯度修飾函數(shù)傳參的功能。

優(yōu)先級(jí)問(wèn)題

凡是有沖突的操作,就必然有一個(gè)優(yōu)先級(jí)的順序。bprop函數(shù)是用本地的方法去計(jì)算一個(gè)梯度值,而InsertGradientOf算子是對(duì)某一個(gè)變量的梯度值進(jìn)行處理。因此當(dāng)這兩個(gè)函數(shù)同時(shí)被用于處理一個(gè)梯度值時(shí),就需要看看誰(shuí)的優(yōu)先級(jí)更高:

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))

在這個(gè)案例中,clip函數(shù)還是對(duì)梯度做一個(gè)截?cái)啵鴅prop函數(shù)則是直接返回一個(gè)梯度值。那么最終執(zhí)行的輸出結(jié)果為:

[-8.]
100.0

這個(gè)結(jié)果表明,bprop函數(shù)的執(zhí)行優(yōu)先級(jí)要高于InsertGradientOf算子。

總結(jié)概要

這篇文章主要介紹了mindspore深度學(xué)習(xí)框架中基于InsertGradientOf算子的進(jìn)階梯度操作。InsertGradientOf算子的功能跟此前介紹過(guò)的bprop功能有些類似,也是自定義梯度,但bprop更傾向于計(jì)算梯度,而InsertGradientOf算子更傾向于修改梯度,這里介紹了一些比較詳細(xì)的測(cè)試案例。

版權(quán)聲明

本文首發(fā)鏈接為: https://www.cnblogs.com/dechinphy/p/InsertGradientOf.html

作者ID:DechinPhy

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

請(qǐng)博主喝咖啡: https://www.cnblogs.com/dechinphy/gallery/image/379634.html

小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]

湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2024 haote.com 好特網(wǎng)