文档反馈

问题文档片段

问题文档片段包含公式时,显示为空格。

提交类型
issue

有点复杂...

找人问问吧。

PR

小问题,全程线上修改...

一键搞定!

请选择提交类型

问题类型
规范和低错类

- 规范和低错类:

- 错别字或拼写错误,标点符号使用错误、公式错误或显示异常。

- 链接错误、空单元格、格式错误。

- 英文中包含中文字符。

- 界面和描述不一致,但不影响操作。

- 表述不通顺,但不影响理解。

- 版本号不匹配:如软件包名称、界面版本号。

易用性

- 易用性:

- 关键步骤错误或缺失,无法指导用户完成任务。

- 缺少主要功能描述、关键词解释、必要前提条件、注意事项等。

- 描述内容存在歧义指代不明、上下文矛盾。

- 逻辑不清晰,该分类、分项、分步骤的没有给出。

正确性

- 正确性:

- 技术原理、功能、支持平台、参数类型、异常报错等描述和软件实现不一致。

- 原理图、架构图等存在错误。

- 命令、命令参数等错误。

- 代码片段错误。

- 命令无法完成对应功能。

- 界面错误,无法指导操作。

- 代码样例运行报错、运行结果不符。

风险提示

- 风险提示:

- 对重要数据或系统存在风险的操作,缺少安全提示。

内容合规

- 内容合规:

- 违反法律法规,涉及政治、领土主权等敏感词。

- 内容侵权。

请选择问题类型

问题描述

点击输入详细问题描述,以帮助我们快速定位问题。

下载Notebook下载样例代码查看源文件

高级自动微分

mindspore.ops模块提供的gradvalue_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接口的使用方式做简单介绍,即公式:

(1)f(x,y)=xxyz

首先定义网络模型Net、输入x和输入y

[1]:
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):

(2)fx=2xyz
(3)fy=xxz
[2]:
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:

(4)fz=xxy
[3]:
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]),)

返回辅助变量

同时对输入和权重求导,其中只有第一个输出参与求导,示例代码如下:

[4]:
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,然后看一下对输入的求导结果情况。

示例代码如下:

[5]:
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.0]

从上面的打印可以看出,由于对out2设置了stop_gradient,所以out2没有对梯度计算有任何的贡献,其输出结果与未加out2算子时一致。

下面删除out2 = stop_gradient(out2),再来看一下输出结果。示例代码为:

[6]:
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.0]

打印结果可以看出,把out2算子的梯度也计算进去之后,由于out2out1算子完全相同,因此它们产生的梯度也完全相同,所以可以看到,结果中每一项的值都变为了原来的两倍(存在精度误差)。

高阶求导

高阶微分在AI支持科学计算、二阶优化等领域均有应用。如分子动力学模拟中,利用神经网络训练势能时,损失函数中需计算神经网络输出对输入的导数,则反向传播便存在损失函数对输入、权重的二阶交叉导数。

此外,AI求解微分方程(如PINNs方法)还会存在输出对输入的二阶导数。又如二阶优化中,为了能够让神经网络快速收敛,牛顿法等需计算损失函数对权重的二阶导数。

MindSpore可通过多次求导的方式支持高阶导数,下面通过几类例子展开阐述。

单输入单输出高阶导数

例如Sin算子,其公式为:

(1)f(x)=sin(x)

其一阶导数是:

(2)f(x)=cos(x)

其二阶导数为:

(3)f(x)=cos(x)=sin(x)

其二阶导数(-Sin)实现如下:

[7]:
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.]

从上面的打印结果可以看出,sin(3.1415926)的值接近于0

单输入多输出高阶导数

对如下公式求导:

(1)f(x)=(f1(x),f2(x))

其中:

(2)f1(x)=sin(x)
(3)f2(x)=cos(x)

梯度计算时由于MindSpore采用的是反向自动微分机制,会对输出结果求和后再对输入求导。因此其一阶导数是:

(4)f(x)=cos(x)sin(x)

其二阶导数为:

(5)f(x)=sin(x)cos(x)
[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):
        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.]

从上面的打印结果可以看出,sin(3.1415926)cos(3.1415926)的值接近于1

多输入多输出高阶导数

对如下公式求导:

(1)f(x,y)=(f1(x,y),f2(x,y))

其中:

(2)f1(x,y)=sin(x)cos(y)
(3)f2(x,y)=cos(x)sin(y)

梯度计算时由于MindSpore采用的是反向自动微分机制, 会对输出结果求和后再对输入求导。

求和:

(4)output=sin(x)+cos(x)sin(y)cos(y)

输出和关于输入x的一阶导数为:

(5)doutputdx=cos(x)sin(x)

输出和关于输入x的二阶导数为:

(6)doutput2d2x=sin(x)cos(x)

输出和关于输入y的一阶导数为:

(7)doutputdy=cos(y)+sin(y)

输出和关于输入y的二阶导数为:

(8)doutput2d2y=sin(y)+cos(y)
[9]:
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的二阶导数sin(3.1415926)cos(3.1415926)的值接近于1,输出对输入y的二阶导数sin(3.1415926)+cos(3.1415926)的值接近于1

由于不同计算平台的精度可能存在差异,因此本章节中的代码在不同平台上的执行结果会存在微小的差别。