自动微分

Ascend GPU CPU 入门 模型开发

image0image1image2image3

在训练神经网络时,最常用的算法是反向传播,在该算法中,根据损失函数对于给定参数的梯度来调整参数(模型权重)。

MindSpore计算一阶导数方法mindspore.ops.GradOperation (get_all=False, get_by_list=False, sens_param=False),其中get_allFalse时,只会对第一个输入求导,为True时,会对所有输入求导;get_by_listFalse时,不会对权重求导,为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.netops.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]]

若考虑对xy输入求导,只需在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 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后, out2out1会对梯度产生相同的贡献。 所以我们可以看到,结果中每一项的值都变为了原来的两倍。