https://gitee.com/mindspore/docs/blob/master/tutorials/source_zh_cn/advanced/derivation.ipynb
高级自动微分
mindspore.ops
模块提供的grad
和value_and_grad
接口可以生成网络模型的梯度。grad
计算网络梯度,value_and_grad
同时计算网络的正向输出和梯度。本文主要介绍如何使用grad
接口的主要功能,包括一阶、二阶求导,单独对输入或网络权重求导,返回辅助变量,以及如何停止计算梯度。
更多求导接口相关信息可参考API文档。
一阶求导
计算一阶导数方法:mindspore.grad
,其中参数使用方式为:
fn
:待求导的函数或网络。grad_position
:指定求导输入位置的索引。若为int类型,表示对单个输入求导;若为tuple类型,表示对tuple内索引的位置求导,其中索引从0开始;若是None,表示不对输入求导,这种场景下,weights
非None。默认值:0。weights
:训练网络中需要返回梯度的网络变量。一般可通过weights = net.trainable_params()
获取。默认值:None。has_aux
:是否返回辅助参数的标志。若为True,fn
输出数量必须超过一个,其中只有fn
第一个输出参与求导,其他输出值将直接返回。默认值:False。
下面先构建自定义网络模型Net
,再对其进行一阶求导,通过这样一个例子对grad
接口的使用方式做简单介绍,即公式:
f ( x , y ) = x ∗ x ∗ y ∗ z (1) f(x, y)=x * x * y * z \tag{1} f(x,y)=x∗x∗y∗z(1)
首先定义网络模型Net
、输入x
和输入y
:
import numpy as np
from mindspore import ops, Tensor
import mindspore.nn as nn
import mindspore as ms
# 定义输入x和y
x = Tensor([3.0], dtype=ms.float32)
y = Tensor([5.0], dtype=ms.float32)
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
self.z = ms.Parameter(ms.Tensor(np.array([1.0], np.float32)), name='z')
def construct(self, x, y):
out = x * x * y * self.z
return out
对输入求一阶导
对输入x
, y
进行求导,需要将grad_position
设置成(0, 1):
∂ f ∂ x = 2 ∗ x ∗ y ∗ z (2) \frac{\partial f}{\partial x}=2 * x * y * z \tag{2} ∂x∂f=2∗x∗y∗z(2)
∂ f ∂ y = x ∗ x ∗ z (3) \frac{\partial f}{\partial y}=x * x * z \tag{3} ∂y∂f=x∗x∗z(3)
net = Net()
grad_fn = ms.grad(net, grad_position=(0, 1))
gradients = grad_fn(x, y)
print(gradients)
(Tensor(shape=[1], dtype=Float32, value= [ 3.00000000e+01]), Tensor(shape=[1], dtype=Float32, value= [ 9.00000000e+00]))
对权重进行求导
对权重z
进行求导,这里不需要对输入求导,将grad_position
设置成None:
∂ f ∂ z = x ∗ x ∗ y (4) \frac{\partial f}{\partial z}=x * x * y \tag{4} ∂z∂f=x∗x∗y(4)
params = ms.ParameterTuple(net.trainable_params())
output = ms.grad(net, grad_position=None, weights=params)(x, y)
print(output)
(Tensor(shape=[1], dtype=Float32, value= [ 4.50000000e+01]),)
返回辅助变量
同时对输入和权重求导,其中只有第一个输出参与求导,示例代码如下:
net = nn.Dense(10, 1)
loss_fn = nn.MSELoss()
def forward(inputs, labels):
logits = net(inputs)
loss = loss_fn(logits, labels)
return loss, logits
inputs = Tensor(np.random.randn(16, 10).astype(np.float32))
labels = Tensor(np.random.randn(16, 1).astype(np.float32))
weights = net.trainable_params()
# Aux value does not contribute to the gradient.
grad_fn = ms.grad(forward, grad_position=0, weights=None, has_aux=True)
inputs_gradient, (aux_logits,) = grad_fn(inputs, labels)
print(len(inputs_gradient), aux_logits.shape)
16 (16, 1)
停止计算梯度
可以使用stop_gradient
来停止计算指定算子的梯度,从而消除该算子对梯度的影响。
在上面一阶求导使用的矩阵相乘网络模型的基础上,再增加一个算子out2
并禁止计算其梯度,得到自定义网络Net2
,然后看一下对输入的求导结果情况。
示例代码如下:
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
def construct(self, x, y):
out1 = x * y
out2 = x * y
out2 = ops.stop_gradient(out2) # 停止计算out2算子的梯度
out = out1 + out2
return out
net = Net()
grad_fn = ms.grad(net)
output = grad_fn(x, y)
print(output)
[5.]
从上面的打印可以看出,由于对out2
设置了stop_gradient
,所以out2
没有对梯度计算有任何的贡献,其输出结果与未加out2
算子时一致。
下面删除out2 = stop_gradient(out2)
,再来看一下输出结果。示例代码为:
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
def construct(self, x, y):
out1 = x * y
out2 = x * y
# out2 = stop_gradient(out2)
out = out1 + out2
return out
net = Net()
grad_fn = ms.grad(net)
output = grad_fn(x, y)
print(output)
[10.]
打印结果可以看出,把out2
算子的梯度也计算进去之后,由于out2
和out1
算子完全相同,因此它们产生的梯度也完全相同,所以可以看到,结果中每一项的值都变为了原来的两倍(存在精度误差)。
高阶求导
高阶微分在AI支持科学计算、二阶优化等领域均有应用。如分子动力学模拟中,利用神经网络训练势能时,损失函数中需计算神经网络输出对输入的导数,则反向传播便存在损失函数对输入、权重的二阶交叉导数。
此外,AI求解微分方程(如PINNs方法)还会存在输出对输入的二阶导数。又如二阶优化中,为了能够让神经网络快速收敛,牛顿法等需计算损失函数对权重的二阶导数。
MindSpore可通过多次求导的方式支持高阶导数,下面通过几类例子展开阐述。
单输入单输出高阶导数
例如Sin算子,其公式为:
f ( x ) = s i n ( x ) (1) f(x) = sin(x) \tag{1} f(x)=sin(x)(1)
其一阶导数是:
f ′ ( x ) = c o s ( x ) (2) f'(x) = cos(x) \tag{2} f′(x)=cos(x)(2)
其二阶导数为:
f ′ ′ ( x ) = c o s ′ ( x ) = − s i n ( x ) (3) f''(x) = cos'(x) = -sin(x) \tag{3} f′′(x)=cos′(x)=−sin(x)(3)
其二阶导数(-Sin)实现如下:
import numpy as np
import mindspore.nn as nn
import mindspore.ops as ops
import mindspore as ms
class Net(nn.Cell):
"""前向网络模型"""
def __init__(self):
super(Net, self).__init__()
self.sin = ops.Sin()
def construct(self, x):
out = self.sin(x)
return out
x_train = ms.Tensor(np.array([3.1415926]), dtype=ms.float32)
net = Net()
firstgrad = ms.grad(net)
secondgrad = ms.grad(firstgrad)
output = secondgrad(x_train)
# 打印结果
result = np.around(output.asnumpy(), decimals=2)
print(result)
[-0.]
从上面的打印结果可以看出, − s i n ( 3.1415926 ) -sin(3.1415926) −sin(3.1415926)的值接近于 0 0 0。
单输入多输出高阶导数
对如下公式求导:
f ( x ) = ( f 1 ( x ) , f 2 ( x ) ) (1) f(x) = (f_1(x), f_2(x)) \tag{1} f(x)=(f1(x),f2(x))(1)
其中:
f 1 ( x ) = s i n ( x ) (2) f_1(x) = sin(x) \tag{2} f1(x)=sin(x)(2)
f 2 ( x ) = c o s ( x ) (3) f_2(x) = cos(x) \tag{3} f2(x)=cos(x)(3)
梯度计算时由于MindSpore采用的是反向自动微分机制,会对输出结果求和后再对输入求导。因此其一阶导数是:
f ′ ( x ) = c o s ( x ) − s i n ( x ) (4) f'(x) = cos(x) -sin(x) \tag{4} f′(x)=cos(x)−sin(x)(4)
其二阶导数为:
f ′ ′ ( x ) = − s i n ( x ) − c o s ( x ) (5) f''(x) = -sin(x) - cos(x) \tag{5} f′′(x)=−sin(x)−cos(x)(5)
import numpy as np
from mindspore import ops, Tensor
import mindspore.nn as nn
import mindspore as ms
class Net(nn.Cell):
"""前向网络模型"""
def __init__(self):
super(Net, self).__init__()
self.sin = ops.Sin()
self.cos = ops.Cos()
def construct(self, x):
out1 = self.sin(x)
out2 = self.cos(x)
return out1, out2
x_train = Tensor(np.array([3.1415926]), dtype=ms.float32)
net = Net()
firstgrad = ms.grad(net)
secondgrad = ms.grad(firstgrad)
output = secondgrad(x_train)
# 打印结果
result = np.around(output.asnumpy(), decimals=2)
print(result)
[1.]
从上面的打印结果可以看出, − s i n ( 3.1415926 ) − c o s ( 3.1415926 ) -sin(3.1415926) - cos(3.1415926) −sin(3.1415926)−cos(3.1415926)的值接近于 1 1 1。
多输入多输出高阶导数
对如下公式求导:
f ( x , y ) = ( f 1 ( x , y ) , f 2 ( x , y ) ) (1) f(x, y) = (f_1(x, y), f_2(x, y)) \tag{1} f(x,y)=(f1(x,y),f2(x,y))(1)
其中:
f 1 ( x , y ) = s i n ( x ) − c o s ( y ) (2) f_1(x, y) = sin(x) - cos(y) \tag{2} f1(x,y)=sin(x)−cos(y)(2)
f 2 ( x , y ) = c o s ( x ) − s i n ( y ) (3) f_2(x, y) = cos(x) - sin(y) \tag{3} f2(x,y)=cos(x)−sin(y)(3)
梯度计算时由于MindSpore采用的是反向自动微分机制, 会对输出结果求和后再对输入求导。
求和:
∑ o u t p u t = s i n ( x ) + c o s ( x ) − s i n ( y ) − c o s ( y ) (4) \sum{output} = sin(x) + cos(x) - sin(y) - cos(y) \tag{4} ∑output=sin(x)+cos(x)−sin(y)−cos(y)(4)
输出和关于输入 x x x的一阶导数为:
d ∑ o u t p u t d x = c o s ( x ) − s i n ( x ) (5) \dfrac{\mathrm{d}\sum{output}}{\mathrm{d}x} = cos(x) - sin(x) \tag{5} dxd∑output=cos(x)−sin(x)(5)
输出和关于输入 x x x的二阶导数为:
d ∑ o u t p u t 2 d 2 x = − s i n ( x ) − c o s ( x ) (6) \dfrac{\mathrm{d}\sum{output}^{2}}{\mathrm{d}^{2}x} = -sin(x) - cos(x) \tag{6} d2xd∑output2=−sin(x)−cos(x)(6)
输出和关于输入 y y y的一阶导数为:
d ∑ o u t p u t d y = − c o s ( y ) + s i n ( y ) (7) \dfrac{\mathrm{d}\sum{output}}{\mathrm{d}y} = -cos(y) + sin(y) \tag{7} dyd∑output=−cos(y)+sin(y)(7)
输出和关于输入 y y y的二阶导数为:
d ∑ o u t p u t 2 d 2 y = s i n ( y ) + c o s ( y ) (8) \dfrac{\mathrm{d}\sum{output}^{2}}{\mathrm{d}^{2}y} = sin(y) + cos(y) \tag{8} d2yd∑output2=sin(y)+cos(y)(8)
import numpy as np
from mindspore import ops, Tensor
import mindspore.nn as nn
import mindspore as ms
class Net(nn.Cell):
"""前向网络模型"""
def __init__(self):
super(Net, self).__init__()
self.sin = ops.Sin()
self.cos = ops.Cos()
def construct(self, x, y):
out1 = self.sin(x) - self.cos(y)
out2 = self.cos(x) - self.sin(y)
return out1, out2
x_train = Tensor(np.array([3.1415926]), dtype=ms.float32)
y_train = Tensor(np.array([3.1415926]), dtype=ms.float32)
net = Net()
firstgrad = ms.grad(net, grad_position=(0, 1))
secondgrad = ms.grad(firstgrad, grad_position=(0, 1))
output = secondgrad(x_train, y_train)
# 打印结果
print(np.around(output[0].asnumpy(), decimals=2))
print(np.around(output[1].asnumpy(), decimals=2))
[1.]
[-1.]
从上面的打印结果可以看出,输出对输入 x x x的二阶导数 − s i n ( 3.1415926 ) − c o s ( 3.1415926 ) -sin(3.1415926) - cos(3.1415926) −sin(3.1415926)−cos(3.1415926)的值接近于 1 1 1,输出对输入 y y y的二阶导数 s i n ( 3.1415926 ) + c o s ( 3.1415926 ) sin(3.1415926) + cos(3.1415926) sin(3.1415926)+cos(3.1415926)的值接近于 − 1 -1 −1。
由于不同计算平台的精度可能存在差异,因此本章节中的代码在不同平台上的执行结果会存在微小的差别。