自动微分
在训练神经网络时,最常用的算法是反向传播,在该算法中,根据损失函数对于给定参数的梯度来调整参数(模型权重)。
MindSpore计算一阶导数方法mindspore.ops.GradOperation (get_all=False, get_by_list=False, sens_param=False)
,其中get_all
为False
时,只会对第一个输入求导,为True
时,会对所有输入求导;get_by_list
为False
时,不会对权重求导,为True
时,会对权重求导;sens_param
对网络的输出值做缩放以改变最终梯度。下面用MatMul算子的求导做深入分析。
首先导入本文档需要的模块和接口,如下所示:
[1]:
import numpy as np
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore import Tensor
from mindspore import ParameterTuple, Parameter
from mindspore import dtype as mstype
对输入求一阶导
如果需要对输入进行求导,首先需要定义一个需要求导的网络,以一个由MatMul算子构成的网络\(f(x,y)=z * x * y\)为例。
定义网络结构如下:
[2]:
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
self.matmul = ops.MatMul()
self.z = Parameter(Tensor(np.array([1.0], np.float32)), name='z')
def construct(self, x, y):
x = x * self.z
out = self.matmul(x, y)
return out
接着定义求导网络,__init__
函数中定义需要求导的网络self.net
和ops.GradOperation
操作,construct
函数中对self.net
进行求导。
求导网络结构如下:
[3]:
class GradNetWrtX(nn.Cell):
def __init__(self, net):
super(GradNetWrtX, self).__init__()
self.net = net
self.grad_op = ops.GradOperation()
def construct(self, x, y):
gradient_function = self.grad_op(self.net)
return gradient_function(x, y)
定义输入并且打印输出:
[4]:
x = Tensor([[0.8, 0.6, 0.2], [1.8, 1.3, 1.1]], dtype=mstype.float32)
y = Tensor([[0.11, 3.3, 1.1], [1.1, 0.2, 1.4], [1.1, 2.2, 0.3]], dtype=mstype.float32)
output = GradNetWrtX(Net())(x, y)
print(output)
[[4.5099998 2.7 3.6000001]
[4.5099998 2.7 3.6000001]]
若考虑对x
、y
输入求导,只需在GradNetWrtX
中设置self.grad_op = GradOperation(get_all=True)
。
对权重求一阶导
若需要对权重的求导,将ops.GradOperation
中的get_by_list
设置为True
:
则GradNetWrtX
结构为:
[5]:
class GradNetWrtX(nn.Cell):
def __init__(self, net):
super(GradNetWrtX, self).__init__()
self.net = net
self.params = ParameterTuple(net.trainable_params())
self.grad_op = ops.GradOperation(get_by_list=True)
def construct(self, x, y):
gradient_function = self.grad_op(self.net, self.params)
return gradient_function(x, y)
运行并打印输出:
[6]:
output = GradNetWrtX(Net())(x, y)
print(output)
(Tensor(shape=[1], dtype=Float32, value= [ 2.15359993e+01]),)
若需要对某些权重不进行求导,则在定义求导网络时,对相应的权重中requires_grad
设置为False
。
self.z = Parameter(Tensor(np.array([1.0], np.float32)), name='z', requires_grad=False)
梯度值缩放
可以通过sens_param
参数对网络的输出值做缩放以改变最终梯度。首先将ops.GradOperation
中的sens_param
设置为True
,并确定缩放指数,其维度与输出维度保持一致。
缩放指数self.grad_wrt_output
可以记作如下形式:
self.grad_wrt_output = Tensor([[s1, s2, s3], [s4, s5, s6]])
则GradNetWrtX
结构为:
[7]:
class GradNetWrtX(nn.Cell):
def __init__(self, net):
super(GradNetWrtX, self).__init__()
self.net = net
self.grad_op = ops.GradOperation(sens_param=True)
self.grad_wrt_output = Tensor([[0.1, 0.6, 0.2], [0.8, 1.3, 1.1]], dtype=mstype.float32)
def construct(self, x, y):
gradient_function = self.grad_op(self.net)
return gradient_function(x, y, self.grad_wrt_output)
output = GradNetWrtX(Net())(x, y)
print(output)
[[2.211 0.51 1.49 ]
[5.588 2.68 4.07 ]]
停止计算梯度
我们可以使用stop_gradient
来禁止网络内的算子对梯度的影响,例如:
[ ]:
import numpy as np
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore import Tensor
from mindspore import ParameterTuple, Parameter
from mindspore import dtype as mstype
from mindspore.ops.functional import stop_gradient
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
self.matmul = ops.MatMul()
def construct(self, x, y):
out1 = self.matmul(x, y)
out2 = self.matmul(x, y)
out2 = stop_gradient(out2)
out = out1 + out2
return out
class GradNetWrtX(nn.Cell):
def __init__(self, net):
super(GradNetWrtX, self).__init__()
self.net = net
self.grad_op = ops.GradOperation()
def construct(self, x, y):
gradient_function = self.grad_op(self.net)
return gradient_function(x, y)
x = Tensor([[0.8, 0.6, 0.2], [1.8, 1.3, 1.1]], dtype=mstype.float32)
y = Tensor([[0.11, 3.3, 1.1], [1.1, 0.2, 1.4], [1.1, 2.2, 0.3]], dtype=mstype.float32)
output = GradNetWrtX(Net())(x, y)
print(output)
[[4.5 2.7 3.6]
[4.5 2.7 3.6]]
在这里我们对out2
设置了stop_gradient
, 所以out2
没有对梯度计算有任何的贡献。 如果我们删除out2 = stop_gradient(out2)
,那么输出值会变为:
[ ]:
output = GradNetWrtX(Net())(x, y)
print(output)
[[9.0 5.4 7.2]
[9.0 5.4 7.2]]
在我们不对out2
设置stop_gradient
后, out2
和out1
会对梯度产生相同的贡献。 所以我们可以看到,结果中每一项的值都变为了原来的两倍。