日期
心得
什么是函数式自动微分,在日常的模型训练中,涉及到复杂的数学公式如何转换为机械语言,通过本次的学习,使我了解到了如何去做梯度计算,通过梯度计算,设计损失函数,有一步步优化代码。
昇思MindSpore 基础入门学习 函数式自动微分 (AI 代码解析)
函数式自动微分
神经网络的训练主要使用反向传播算法,模型预测值(logits)与正确标签(label)送入损失函数(loss function)获得loss,然后进行反向传播计算,求得梯度(gradients),最终更新至模型参数(parameters)。自动微分能够计算可导函数在某点处的导数值,是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算,该功能对用户屏蔽了大量的求导细节和过程,大大降低了框架的使用门槛。
MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口grad
和value_and_grad
。下面我们使用一个简单的单层线性变换模型进行介绍。
import numpy as np
import mindspore
from mindspore import nn
from mindspore import ops
from mindspore import Tensor, Parameter
import numpy as np
:- 这行代码导入了NumPy库,并将其命名为
np
。NumPy是一个强大的Python库,用于进行科学计算,特别是数组操作。
- 这行代码导入了NumPy库,并将其命名为
import mindspore
:- 这行代码导入了MindSpore库。MindSpore是华为开发的一个深度学习框架,旨在提供高效、灵活的AI模型开发和部署。
from mindspore import nn
:- 这行代码从MindSpore库中导入了神经网络模块
nn
。nn
模块包含了构建神经网络所需的各种层和函数。
- 这行代码从MindSpore库中导入了神经网络模块
from mindspore import ops
:- 这行代码从MindSpore库中导入了操作模块
ops
。ops
模块包含了各种用于张量操作的函数,如数学运算、逻辑运算等。
- 这行代码从MindSpore库中导入了操作模块
from mindspore import Tensor, Parameter
:- 这行代码从MindSpore库中导入了
Tensor
和Parameter
类。Tensor
是MindSpore中的基本数据结构,类似于NumPy的数组,用于存储和操作多维数据。Parameter
用于定义神经网络中的可训练参数。
- 这行代码从MindSpore库中导入了
mindspore.nn
:- 该模块提供了构建神经网络的各种组件,如层(例如全连接层、卷积层)、损失函数、优化器等。
mindspore.ops
:- 该模块提供了各种张量操作函数,包括数学运算、逻辑运算、形状操作等。
mindspore.Tensor
:- 该类用于创建和操作多维数组,是MindSpore中的基本数据结构。
mindspore.Parameter
:- 该类用于定义神经网络中的可训练参数,这些参数在训练过程中会被优化。
函数与计算图
计算图是用图论语言表示数学函数的一种方式,也是深度学习框架表达神经网络模型的统一方法。我们将根据下面的计算图构造计算函数和神经网络。
在这个模型中,x𝑥为输入,y𝑦为正确值,w𝑤和b𝑏是我们需要优化的参数。
x = ops.ones(5, mindspore.float32) # input tensor
y = ops.zeros(3, mindspore.float32) # expected output
w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w') # weight
b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b') # bias
x = ops.ones(5, mindspore.float32)
:- 这行代码使用
ops.ones
函数创建一个形状为(5,)
的全1张量,数据类型为mindspore.float32
。这个张量将作为输入数据。
- 这行代码使用
y = ops.zeros(3, mindspore.float32)
:- 这行代码使用
ops.zeros
函数创建一个形状为(3,)
的全0张量,数据类型为mindspore.float32
。这个张量表示期望的输出数据。
- 这行代码使用
w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w')
:- 这行代码创建一个权重参数
w
。首先使用np.random.randn(5, 3)
生成一个形状为(5, 3)
的随机数组,然后将其转换为MindSpore的Tensor
对象,并指定数据类型为mindspore.float32
。最后,将这个Tensor
对象包装成一个Parameter
对象,并命名为'w'
。这个参数将在训练过程中被优化。
- 这行代码创建一个权重参数
b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b')
:- 这行代码创建一个偏置参数
b
。首先使用np.random.randn(3,)
生成一个形状为(3,)
的随机数组,然后将其转换为MindSpore的Tensor
对象,并指定数据类型为mindspore.float32
。最后,将这个Tensor
对象包装成一个Parameter
对象,并命名为'b'
。这个参数也将在训练过程中被优化。
- 这行代码创建一个偏置参数
ops.ones
:- 该函数用于创建一个全1的张量,参数为形状和数据类型。
ops.zeros
:- 该函数用于创建一个全0的张量,参数为形状和数据类型。
Parameter
:- 该类用于定义神经网络中的可训练参数,这些参数在训练过程中会被优化。
Tensor
:- 该类用于创建和操作多维数组,是MindSpore中的基本数据结构。
np.random.randn
:- 该函数用于生成服从标准正态分布的随机数组。
我们根据计算图描述的计算过程,构造计算函数。 其中,binary_cross_entropy_with_logits 是一个损失函数,计算预测值和目标值之间的二值交叉熵损失。
def function(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss
z = ops.matmul(x, w) + b
:- 这行代码首先使用
ops.matmul
函数对输入张量x
和权重参数w
进行矩阵乘法运算,然后将结果与偏置参数b
相加,得到中间结果z
。这个操作模拟了一个简单的线性变换。
- 这行代码首先使用
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
:- 这行代码计算损失函数。使用
ops.binary_cross_entropy_with_logits
函数计算z
和y
之间的二元交叉熵损失。ops.ones_like(z)
用于生成与z
形状相同的张量,其元素全为1,作为正例权重和负例权重。这里假设正例和负例的权重相同。
- 这行代码计算损失函数。使用
return loss
:- 这行代码返回计算得到的损失值。
ops.matmul
:- 该函数用于执行矩阵乘法操作。
ops.binary_cross_entropy_with_logits
:- 该函数用于计算二元交叉熵损失,通常用于二分类问题。它接受预测值(logits)、目标值、正例权重和负例权重作为参数。
ops.ones_like
:- 该函数用于创建一个与输入张量形状相同的全1张量。
执行计算函数,可以获得计算的loss值。
loss = function(x, y, w, b)
print(loss)
这段代码调用了前面定义的function
函数,并传入之前定义的变量x
、y
、w
和b
。然后,它打印出函数返回的损失值。
微分函数与梯度计算
为了优化模型参数,需要求参数对loss的导数,此时我们调用mindspore.grad
函数,来获得function
的微分函数。
这里使用了grad
函数的两个入参,分别为:
fn
:待求导的函数。grad_position
:指定求导输入位置的索引。
由于我们对w𝑤和b𝑏求导,因此配置其在function
入参对应的位置(2, 3)
。
使用grad
获得微分函数是一种函数变换,即输入为函数,输出也为函数。
grad_fn = mindspore.grad(function, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
grad_fn = mindspore.grad(function, (2, 3))
:- 这行代码使用MindSpore的
grad
函数生成一个计算梯度的函数grad_fn
。grad
函数的第一个参数是目标函数function
,第二个参数(2, 3)
指定了需要计算梯度的参数索引。在这个例子中,w
(索引2)和b
(索引3)的梯度将被计算。
- 这行代码使用MindSpore的
grads = grad_fn(x, y, w, b)
:- 这行代码调用生成的梯度函数
grad_fn
,并传入输入张量x
、目标张量y
、权重参数w
和偏置参数b
。函数返回一个包含w
和b
梯度的元组grads
。
- 这行代码调用生成的梯度函数
print(grads)
:- 这行代码打印出计算得到的梯度元组
grads
。输出将显示w
和b
相对于损失函数的梯度。
- 这行代码打印出计算得到的梯度元组
mindspore.grad
:- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
function
: 目标函数,需要计算其梯度。parameters
: 一个元组,包含需要计算梯度的参数索引。
- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
# 例子输出解析
# 该输出打印了计算得到的梯度信息。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# (Tensor(shape=[5, 3], dtype=Float32, value=
# [[-0.0123, 0.0456, -0.0789],
# ...
# ]), Tensor(shape=[3], dtype=Float32, value=
# [ 0.1234, -0.5678, 0.9101]))
请注意,具体的梯度值将取决于输入张量和参数的具体值。为了查看梯度张量的数值,可以进一步将它们转换为NumPy数组:
print(grads[0].asnumpy()) # 打印w的梯度
print(grads[1].asnumpy()) # 打印b的梯度
这样可以查看梯度的数值内容。
Stop Gradient
通常情况下,求导时会求loss对参数的导数,因此函数的输出只有loss一项。当我们希望函数输出多项时,微分函数会求所有输出项对参数的导数。此时如果想实现对某个输出项的梯度截断,或消除某个Tensor对梯度的影响,需要用到Stop Gradient操作。
这里我们将function
改为同时输出loss和z的function_with_logits
,获得微分函数并执行。
def function_with_logits(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss, z
grad_fn = mindspore.grad(function_with_logits, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
def function_with_logits(x, y, w, b)
:- 定义了一个新的函数
function_with_logits
,该函数接受输入张量x
、目标张量y
、权重参数w
和偏置参数b
。
- 定义了一个新的函数
z = ops.matmul(x, w) + b
:- 计算线性变换的结果
z
,即输入张量x
与权重参数w
的矩阵乘积,再加上偏置参数b
。
- 计算线性变换的结果
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
:- 计算二元交叉熵损失
loss
,使用ops.binary_cross_entropy_with_logits
函数,输入为z
和y
,正例和负例权重均为ops.ones_like(z)
,即全1的张量。
- 计算二元交叉熵损失
return loss, z
:- 返回损失值
loss
和中间结果z
。
- 返回损失值
grad_fn = mindspore.grad(function_with_logits, (2, 3))
:- 使用
mindspore.grad
函数生成一个计算梯度的函数grad_fn
,指定需要计算梯度的参数为w
(索引2)和b
(索引3)。
- 使用
grads = grad_fn(x, y, w, b)
:- 调用生成的梯度函数
grad_fn
,传入输入张量x
、目标张量y
、权重参数w
和偏置参数b
,返回w
和b
的梯度。
- 调用生成的梯度函数
print(grads)
:- 打印计算得到的梯度元组
grads
。
- 打印计算得到的梯度元组
mindspore.grad
:- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
function
: 目标函数,需要计算其梯度。parameters
: 一个元组,包含需要计算梯度的参数索引。
- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
# 例子输出解析
# 该输出打印了计算得到的梯度信息。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# (Tensor(shape=[5, 3], dtype=Float32, value=
# [[-0.0123, 0.0456, -0.0789],
# ...
# ]), Tensor(shape=[3], dtype=Float32, value=
# [ 0.1234, -0.5678, 0.9101]))
请注意,具体的梯度值将取决于输入张量和参数的具体值。为了查看梯度张量的数值,可以进一步将它们转换为NumPy数组:
print(grads[0].asnumpy()) # 打印w的梯度
print(grads[1].asnumpy()) # 打印b的梯度
这样可以查看梯度的数值内容。
可以看到求得w𝑤、b𝑏对应的梯度值发生了变化。此时如果想要屏蔽掉z对梯度的影响,即仍只求参数对loss的导数,可以使用ops.stop_gradient
接口,将梯度在此处截断。我们将function
实现加入stop_gradient
,并执行。
def function_stop_gradient(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss, ops.stop_gradient(z)
grad_fn = mindspore.grad(function_stop_gradient, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
def function_stop_gradient(x, y, w, b):
:- 定义了一个新的函数
function_stop_gradient
,该函数接受输入张量x
、目标张量y
、权重参数w
和偏置参数b
。
- 定义了一个新的函数
z = ops.matmul(x, w) + b
:- 计算线性变换的结果
z
,即输入张量x
与权重参数w
的矩阵乘积,再加上偏置参数b
。
- 计算线性变换的结果
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
:- 计算二元交叉熵损失
loss
,使用ops.binary_cross_entropy_with_logits
函数。输入为z
和y
,正例和负例权重均设为与z
形状相同的全1张量。
- 计算二元交叉熵损失
return loss, ops.stop_gradient(z)
:- 返回损失值
loss
,以及应用了ops.stop_gradient
函数的z
。ops.stop_gradient
是用于停止梯度传播的函数,对于返回的这个z
,在自动微分中不会计算其梯度。
- 返回损失值
grad_fn = mindspore.grad(function_stop_gradient, (2, 3))
:- 使用
mindspore.grad
函数生成一个计算梯度的函数grad_fn
,指定需要计算梯度的参数为w
(索引2)和b
(索引3)。
- 使用
grads = grad_fn(x, y, w, b)
:- 调用生成的梯度函数
grad_fn
,传入输入张量x
、目标张量y
、权重参数w
和偏置参数b
,计算w
和b
的梯度。
- 调用生成的梯度函数
print(grads)
:- 打印计算得到的梯度元组
grads
。
- 打印计算得到的梯度元组
mindspore.grad
:- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
function
: 目标函数,需要计算其梯度。parameters
: 一个元组,包含需要计算梯度的参数索引。
- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
ops.stop_gradient
:- 该操作阻止梯度的反向传播。当需要计算函数的前向传播时保持操作的功能,但在反向传播时不希望计算其梯度时使用。
# 例子输出解析
# 该输出打印了计算得到的梯度信息。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# (Tensor(shape=[5, 3], dtype=Float32, value=
# [[-0.0123, 0.0456, -0.0789],
# ...
# ]), Tensor(shape=[3], dtype=Float32, value=
# [ 0.1234, -0.5678, 0.9101]))
请注意,由于z
的梯度被阻止,输出的元组仅包含w
和b
的梯度,z
的梯度不会被计算。此外,具体的梯度值将取决于输入张量和参数的具体值,上面给出的值仅为示例。 若要查看梯度张量的数值内容,可以进一步将它们转换为NumPy数组:
print(grads[0].asnumpy()) # 打印w的梯度
print(grads[1].asnumpy()) # 打印b的梯度
这样可以查看梯度的数值内容。
Auxiliary data
Auxiliary data意为辅助数据,是函数除第一个输出项外的其他输出。通常我们会将函数的loss设置为函数的第一个输出,其他的输出即为辅助数据。
grad
和value_and_grad
提供has_aux
参数,当其设置为True
时,可以自动实现前文手动添加stop_gradient
的功能,满足返回辅助数据的同时不影响梯度计算的效果。
下面仍使用function_with_logits
,配置has_aux=True
,并执行。
grad_fn = mindspore.grad(function_with_logits, (2, 3), has_aux=True)
grads, (z,) = grad_fn(x, y, w, b)
print(grads, z)
grad_fn = mindspore.grad(function_with_logits, (2, 3), has_aux=True)
:- 这行代码使用
mindspore.grad
函数生成一个计算梯度的函数grad_fn
。这里的grad
函数参数:- 第一个参数是目标函数
function_with_logits
。 - 第二个参数
(2, 3)
指定了需要计算梯度的参数索引,表示计算w
(索引2)和b
(索引3)的梯度。 has_aux=True
表示目标函数返回多个值,且需要返回附加值(auxiliary values),即function_with_logits
中的z
。
- 第一个参数是目标函数
- 这行代码使用
grads, (z,) = grad_fn(x, y, w, b)
:- 调用生成的梯度函数
grad_fn
,传入输入张量x
、目标张量y
、权重参数w
和偏置参数b
,返回两个部分:grads
:包含w
和b
的梯度值。(z,)
:从返回的附加值中解包得到z
。
- 调用生成的梯度函数
print(grads, z)
:- 打印计算得到的梯度
grads
和中间结果z
。
- 打印计算得到的梯度
mindspore.grad
:- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
function
: 目标函数,需要计算其梯度。parameters
: 一个元组,包含需要计算梯度的参数索引。has_aux
: 布尔值,如果目标函数返回多个值且需要返回附加值,则设置为True
。
- 该函数用于创建一个新的函数,此函数返回输入函数相对于指定参数的梯度。生成的函数将返回梯度作为输出。参数:
function_with_logits
:- 之前定义的函数,接受输入张量
x
、目标张量y
、权重参数w
和偏置参数b
,返回损失值loss
和线性变换结果z
。
- 之前定义的函数,接受输入张量
# 例子输出解析
# 该输出打印了计算得到的梯度信息和中间结果z。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# (Tensor(shape=[5, 3], dtype=Float32, value=
# [[-0.0123, 0.0456, -0.0789],
# ...
# ]), Tensor(shape=[3], dtype=Float32, value=
# [ 0.1234, -0.5678, 0.9101])) Tensor(shape=[N, M], dtype=Float32, value=
# [[...], # z 的值
# ...
# ])
具体的梯度值和z
的值将取决于输入张量和参数的具体值。若要查看梯度和z
张量的数值内容,可以进一步将它们转换为NumPy数组:
print(grads[0].asnumpy()) # 打印w的梯度
print(grads[1].asnumpy()) # 打印b的梯度
print(z.asnumpy()) # 打印z的值
这样可以查看梯度和z
的数值内容。
神经网络梯度计算
前述章节主要根据计算图对应的函数介绍了MindSpore的函数式自动微分,但我们的神经网络构造是继承自面向对象编程范式的nn.Cell
。接下来我们通过Cell
构造同样的神经网络,利用函数式自动微分来实现反向传播。
首先我们继承nn.Cell
构造单层线性变换神经网络。这里我们直接使用前文的w𝑤、b𝑏作为模型参数,使用mindspore.Parameter
进行包装后,作为内部属性,并在construct
内实现相同的Tensor操作。
# Define model
class Network(nn.Cell):
def __init__(self):
super().__init__()
# 初始化权重参数 w 和偏置参数 b
self.w = w
self.b = b
def construct(self, x):
# 计算线性变换的结果 z
z = ops.matmul(x, self.w) + self.b
return z
class Network(nn.Cell):
:- 定义了一个名为
Network
的神经网络类,继承自mindspore.nn.Cell
。
- 定义了一个名为
def __init__(self):
:- 初始化方法,用于初始化模型的参数。
super().__init__()
:调用父类的初始化方法。self.w = w
和self.b = b
:设置网络的权重参数w
和偏置参数b
。
def construct(self, x):
:- 构造方法,用于定义网络的前向计算。
z = ops.matmul(x, self.w) + self.b
:计算输入张量x
与权重参数w
的矩阵乘积,再加上偏置参数b
,得到线性变换的结果z
。return z
:返回计算结果z
。
mindspore.nn.Cell
:Cell
是MindSpore中所有神经网络层和模型的基类。通过继承Cell
可以创建自定义的神经网络模型。
ops.matmul
:- 这是一个矩阵乘法操作,计算两个张量的矩阵乘积。
# 使用例子
# 假设 w 和 b 已经定义为适当的张量:
# w = Tensor(np.random.randn(5, 3), mindspore.float32)
# b = Tensor(np.random.randn(3), mindspore.float32)
# 输入张量 x
# x = Tensor(np.random.randn(10, 5), mindspore.float32)
# 创建模型实例
network = Network()
# 前向计算
output = network(x)
print(output)
# 例子输出解析
# 该输出将会是一个张量,表示输入 x 通过线性变换后的结果 z。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# Tensor(shape=[10, 3], dtype=Float32, value=
# [[-0.123, 0.456, -0.789],
# ...
# ])
具体的输出值将取决于w
、b
和x
的具体值。为了查看模型输出的数值,可以进一步将它们转换为NumPy数组:
print(output.asnumpy()) # 打印模型输出的值
这样可以查看输出张量的数值内容。
# Instantiate model
model = Network()
# Instantiate loss function
loss_fn = nn.BCEWithLogitsLoss()
model = Network()
:- 创建一个
Network
类的实例,即神经网络模型。
- 创建一个
loss_fn = nn.BCEWithLogitsLoss()
:- 创建一个损失函数实例,使用
mindspore.nn.BCEWithLogitsLoss
,这是一个结合了Sigmoid层和二元交叉熵损失的损失函数。
- 创建一个损失函数实例,使用
mindspore.nn.BCEWithLogitsLoss
:- 这是一个结合了Sigmoid激活函数和二元交叉熵损失的损失函数。它适用于二分类问题,可以直接对未经过Sigmoid激活的输出(logits)计算损失,这样可以提高数值稳定性。
# 使用例子
# 假设输入张量 x 和目标张量 y 已经定义:
# x = Tensor(np.random.randn(10, 5), mindspore.float32)
# y = Tensor(np.random.randn(10, 3), mindspore.float32)
# 前向计算
output = model(x)
# 计算损失
loss = loss_fn(output, y)
print(loss)
# 例子输出解析
# 该输出将会是一个标量张量,表示计算得到的损失值。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# Tensor(shape=[], dtype=Float32, value=0.5678)
具体的损失值将取决于模型输出和目标张量的具体值。为了查看损失值的数值,可以进一步将它们转换为NumPy标量:
print(loss.asnumpy()) # 打印损失值
这样可以查看损失值的数值内容。
# Define forward function
def forward_fn(x, y):
# 使用模型进行前向计算,得到预测结果 z
z = model(x)
# 计算预测结果 z 与目标值 y 之间的损失
loss = loss_fn(z, y)
return loss
def forward_fn(x, y):
:- 定义了一个名为
forward_fn
的前向计算函数,用于计算输入张量x
和目标张量y
之间的损失。
- 定义了一个名为
z = model(x)
:- 使用神经网络模型
model
对输入张量x
进行前向计算,得到预测结果z
。
- 使用神经网络模型
loss = loss_fn(z, y)
:- 使用损失函数
loss_fn
计算预测结果z
与目标值y
之间的损失。
- 使用损失函数
return loss
:- 返回计算得到的损失值
loss
。
- 返回计算得到的损失值
假设输入张量x
和目标张量y
已经定义:
# 示例输入数据
x = Tensor(np.random.randn(10, 5), mindspore.float32)
y = Tensor(np.random.randn(10, 3), mindspore.float32)
# 调用前向函数
loss = forward_fn(x, y)
print(loss)
# 例子输出解析
# 该输出将会是一个标量张量,表示计算得到的损失值。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# Tensor(shape=[], dtype=Float32, value=0.5678)
具体的损失值将取决于模型输出和目标张量的具体值。为了查看损失值的数值,可以进一步将其转换为NumPy标量:
print(loss.asnumpy()) # 打印损失值
这样可以查看损失值的数值内容。
# Import necessary modules
import mindspore
from mindspore import nn, ops, Tensor
import numpy as np
# Define model
class Network(nn.Cell):
def __init__(self):
super().__init__()
# 初始化权重参数 w 和偏置参数 b
self.w = Tensor(np.random.randn(5, 3), mindspore.float32)
self.b = Tensor(np.random.randn(3), mindspore.float32)
def construct(self, x):
# 计算线性变换的结果 z
z = ops.matmul(x, self.w) + self.b
return z
# Instantiate model
model = Network()
# Instantiate loss function
loss_fn = nn.BCEWithLogitsLoss()
# Define forward function
def forward_fn(x, y):
# 使用模型进行前向计算,得到预测结果 z
z = model(x)
# 计算预测结果 z 与目标值 y 之间的损失
loss = loss_fn(z, y)
return loss
# Define gradient function
grad_fn = mindspore.value_and_grad(forward_fn, None, weights=model.trainable_params())
# 示例输入数据
x = Tensor(np.random.randn(10, 5), mindspore.float32)
y = Tensor(np.random.randn(10, 3), mindspore.float32)
# 计算损失和梯度
loss, grads = grad_fn(x, y)
# 打印梯度
print(grads)
import mindspore
:- 导入MindSpore模块。
from mindspore import nn, ops, Tensor
:- 导入MindSpore中的神经网络模块
nn
、操作模块ops
和张量类型Tensor
。
- 导入MindSpore中的神经网络模块
import numpy as np
:- 导入NumPy库用于生成示例数据。
- 定义模型
Network
类:- 初始化权重参数
w
和偏置参数b
。 - 定义前向计算函数
construct
,计算输入张量x
与权重参数w
的矩阵乘积,再加上偏置参数b
,得到线性变换的结果z
。
- 初始化权重参数
- **实例化模型
model
和损失函数 **loss_fn
:- 创建
Network
类的实例。 - 创建
nn.BCEWithLogitsLoss
类的实例。
- 创建
- **定义前向计算函数 **
forward_fn
:- 使用模型对输入张量
x
进行前向计算,得到预测结果z
。 - 使用损失函数计算预测结果
z
与目标值y
之间的损失。
- 使用模型对输入张量
- **定义梯度计算函数 **
grad_fn
:- 使用
mindspore.value_and_grad
函数,计算前向函数forward_fn
对模型可训练参数的梯度。 None
表示不计算forward_fn
对输入x
和y
的梯度。weights=model.trainable_params()
表示计算前向函数对模型可训练参数的梯度。
- 使用
- **示例输入数据
x
和 **y
:- 使用 NumPy 生成示例输入数据
x
和目标数据y
。
- 使用 NumPy 生成示例输入数据
- 计算损失和梯度:
- 调用梯度计算函数
grad_fn
计算输入x
和y
下的损失和梯度。
- 调用梯度计算函数
- **打印梯度 **
grads
:
打印计算得到的梯度。
mindspore.value_and_grad
:- 该函数返回一个计算函数值和梯度的函数。用于自动求解函数对指定输入的梯度。返回的函数在调用时会返回函数值和梯度。
model.trainable_params()
:- 该方法返回模型中所有可训练的参数。用于指定需要计算梯度的参数。
使用例子
假设输入张量 x
和目标张量 y
已经定义:
# 示例输入数据
x = Tensor(np.random.randn(10, 5), mindspore.float32)
y = Tensor(np.random.randn(10, 3), mindspore.float32)
# 计算损失和梯度
loss, grads = grad_fn(x, y)
# 打印梯度
print(grads)
# 例子输出解析
# 该输出将会是一个包含模型所有可训练参数梯度的列表。
# 假定在MindSpore框架环境下执行,输出将会是类似于以下内容:
# [Tensor(shape=[5, 3], dtype=Float32, value=
# [[-0.123, 0.456, -0.789],
# ...
# ]),
# Tensor(shape=[3], dtype=Float32, value=
# [ 0.123, -0.456, 0.789])]
具体的梯度值将取决于模型参数和输入数据的具体值。为了查看梯度值的数值,可以进一步将其转换为NumPy数组:
for grad in grads:
print(grad.asnumpy()) # 打印每个梯度的值
这样可以查看每个梯度张量的数值内容。
整体代码
#!/usr/bin/env python
# coding: utf-8
# [![下载Notebook](https://qingyun-test.oss-cn-hangzhou.aliyuncs.com/images/2024/07/15/image2740052684156387276.png)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.3.0rc2/tutorials/zh_cn/beginner/mindspore_autograd.ipynb) [![下载样例代码](https://qingyun-test.oss-cn-hangzhou.aliyuncs.com/images/2024/07/15/image17136930288766242513.png)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.3.0rc2/tutorials/zh_cn/beginner/mindspore_autograd.py) [![查看源文件](https://qingyun-test.oss-cn-hangzhou.aliyuncs.com/images/2024/07/15/image2788024829928156342.png)](https://gitee.com/mindspore/docs/blob/r2.3.0rc2/tutorials/source_zh_cn/beginner/autograd.ipynb)
#
# [基本介绍](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/introduction.html) || [快速入门](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/quick_start.html) || [张量 Tensor](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/tensor.html) || [数据集 Dataset](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/dataset.html) || [数据变换 Transforms](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/transforms.html) || [网络构建](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/model.html) || **函数式自动微分** || [模型训练](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/train.html) || [保存与加载](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/save_load.html) || [使用静态图加速](https://www.mindspore.cn/tutorials/zh-CN/r2.3.0rc2/beginner/accelerate_with_static_graph.html)
# # 函数式自动微分
#
# 神经网络的训练主要使用反向传播算法,模型预测值(logits)与正确标签(label)送入损失函数(loss function)获得loss,然后进行反向传播计算,求得梯度(gradients),最终更新至模型参数(parameters)。自动微分能够计算可导函数在某点处的导数值,是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算,该功能对用户屏蔽了大量的求导细节和过程,大大降低了框架的使用门槛。
#
# MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口`grad`和`value_and_grad`。下面我们使用一个简单的单层线性变换模型进行介绍。
# In[1]:
import numpy as np
import mindspore
from mindspore import nn
from mindspore import ops
from mindspore import Tensor, Parameter
# ## 函数与计算图
#
# 计算图是用图论语言表示数学函数的一种方式,也是深度学习框架表达神经网络模型的统一方法。我们将根据下面的计算图构造计算函数和神经网络。
#
# ![compute-graph](https://qingyun-test.oss-cn-hangzhou.aliyuncs.com/images/2024/07/15/image2135138656591676869.png)
# 在这个模型中,$x$为输入,$y$为正确值,$w$和$b$是我们需要优化的参数。
# In[2]:
x = ops.ones(5, mindspore.float32) # input tensor
y = ops.zeros(3, mindspore.float32) # expected output
w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w') # weight
b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b') # bias
# 我们根据计算图描述的计算过程,构造计算函数。
# 其中,[binary_cross_entropy_with_logits](https://www.mindspore.cn/docs/zh-CN/r2.3.0rc2/api_python/ops/mindspore.ops.binary_cross_entropy_with_logits.html) 是一个损失函数,计算预测值和目标值之间的二值交叉熵损失。
# In[3]:
def function(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss
# 执行计算函数,可以获得计算的loss值。
# In[4]:
loss = function(x, y, w, b)
print(loss)
# ## 微分函数与梯度计算
# 为了优化模型参数,需要求参数对loss的导数:$\frac{\partial \operatorname{loss}}{\partial w}$和$\frac{\partial \operatorname{loss}}{\partial b}$,此时我们调用`mindspore.grad`函数,来获得`function`的微分函数。
#
# 这里使用了`grad`函数的两个入参,分别为:
#
# - `fn`:待求导的函数。
# - `grad_position`:指定求导输入位置的索引。
#
# 由于我们对$w$和$b$求导,因此配置其在`function`入参对应的位置`(2, 3)`。
#
# > 使用`grad`获得微分函数是一种函数变换,即输入为函数,输出也为函数。
# In[5]:
grad_fn = mindspore.grad(function, (2, 3))
# 执行微分函数,即可获得$w$、$b$对应的梯度。
# In[6]:
grads = grad_fn(x, y, w, b)
print(grads)
# ## Stop Gradient
# 通常情况下,求导时会求loss对参数的导数,因此函数的输出只有loss一项。当我们希望函数输出多项时,微分函数会求所有输出项对参数的导数。此时如果想实现对某个输出项的梯度截断,或消除某个Tensor对梯度的影响,需要用到Stop Gradient操作。
#
# 这里我们将`function`改为同时输出loss和z的`function_with_logits`,获得微分函数并执行。
# In[7]:
def function_with_logits(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss, z
# In[8]:
grad_fn = mindspore.grad(function_with_logits, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
# 可以看到求得$w$、$b$对应的梯度值发生了变化。此时如果想要屏蔽掉z对梯度的影响,即仍只求参数对loss的导数,可以使用`ops.stop_gradient`接口,将梯度在此处截断。我们将`function`实现加入`stop_gradient`,并执行。
# In[9]:
def function_stop_gradient(x, y, w, b):
z = ops.matmul(x, w) + b
loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
return loss, ops.stop_gradient(z)
# In[10]:
grad_fn = mindspore.grad(function_stop_gradient, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)
# 可以看到,求得$w$、$b$对应的梯度值与初始`function`求得的梯度值一致。
# ## Auxiliary data
#
# Auxiliary data意为辅助数据,是函数除第一个输出项外的其他输出。通常我们会将函数的loss设置为函数的第一个输出,其他的输出即为辅助数据。
#
# `grad`和`value_and_grad`提供`has_aux`参数,当其设置为`True`时,可以自动实现前文手动添加`stop_gradient`的功能,满足返回辅助数据的同时不影响梯度计算的效果。
#
# 下面仍使用`function_with_logits`,配置`has_aux=True`,并执行。
# In[11]:
grad_fn = mindspore.grad(function_with_logits, (2, 3), has_aux=True)
# In[12]:
grads, (z,) = grad_fn(x, y, w, b)
print(grads, z)
# 可以看到,求得$w$、$b$对应的梯度值与初始`function`求得的梯度值一致,同时z能够作为微分函数的输出返回。
# ## 神经网络梯度计算
# 前述章节主要根据计算图对应的函数介绍了MindSpore的函数式自动微分,但我们的神经网络构造是继承自面向对象编程范式的`nn.Cell`。接下来我们通过`Cell`构造同样的神经网络,利用函数式自动微分来实现反向传播。
#
# 首先我们继承`nn.Cell`构造单层线性变换神经网络。这里我们直接使用前文的$w$、$b$作为模型参数,使用`mindspore.Parameter`进行包装后,作为内部属性,并在`construct`内实现相同的Tensor操作。
# In[13]:
# Define model
class Network(nn.Cell):
def __init__(self):
super().__init__()
self.w = w
self.b = b
def construct(self, x):
z = ops.matmul(x, self.w) + self.b
return z
# 接下来我们实例化模型和损失函数。
# In[14]:
# Instantiate model
model = Network()
# Instantiate loss function
loss_fn = nn.BCEWithLogitsLoss()
# 完成后,由于需要使用函数式自动微分,需要将神经网络和损失函数的调用封装为一个前向计算函数。
# In[15]:
# Define forward function
def forward_fn(x, y):
z = model(x)
loss = loss_fn(z, y)
return loss
# 完成后,我们使用`value_and_grad`接口获得微分函数,用于计算梯度。
#
# 由于使用Cell封装神经网络模型,模型参数为Cell的内部属性,此时我们不需要使用`grad_position`指定对函数输入求导,因此将其配置为`None`。对模型参数求导时,我们使用`weights`参数,使用`model.trainable_params()`方法从Cell中取出可以求导的参数。
# In[16]:
grad_fn = mindspore.value_and_grad(forward_fn, None, weights=model.trainable_params())
# In[17]:
loss, grads = grad_fn(x, y)
print(grads)
# 执行微分函数,可以看到梯度值和前文`function`求得的梯度值一致。
- 导入必要的模块:
numpy
:用于生成示例数据。mindspore
:MindSpore框架的核心模块。mindspore.nn
:包含神经网络相关的类和函数。mindspore.ops
:包含各种操作符。mindspore.Tensor
和mindspore.Parameter
:用于定义张量和参数。
- 定义输入和参数:
x
:输入张量,使用ops.ones
生成全1的张量。y
:目标输出张量,使用ops.zeros
生成全0的张量。w
和b
:权重和偏置参数,使用Parameter
包装并初始化为随机值。
- 定义计算函数:
function
:根据计算图描述的计算过程,计算线性变换后的输出z
,并使用binary_cross_entropy_with_logits
计算损失。
- 计算损失:
- 调用
function
函数,计算并打印损失值。
- 调用
- 定义微分函数:
- 使用
mindspore.grad
获得function
的微分函数grad_fn
,指定对w
和b
求导。
- 使用
- 计算梯度:
- 调用
grad_fn
函数,计算并打印w
和b
对应的梯度。
- 调用
- Stop Gradient:
- 修改
function
为function_with_logits
,同时输出loss
和z
。 - 使用
ops.stop_gradient
截断z
的梯度,确保只对loss
求导。
- 修改
- Auxiliary data:
- 使用
mindspore.grad
的has_aux
参数,自动处理辅助数据,确保不影响梯度计算。
- 使用
- 定义神经网络:
- 继承
nn.Cell
定义单层线性变换神经网络Network
。 - 实例化模型和损失函数。
- 继承
- 定义前向计算函数:
- 将神经网络和损失函数的调用封装为
forward_fn
。
- 获得微分函数:
- 使用
mindspore.value_and_grad
获得forward_fn
的微分函数grad_fn
,指定对模型参数求导。
- 计算并打印梯度:
调用
grad_fn
函数,计算并打印模型参数对应的梯度。mindspore.grad
:用于获得函数的微分函数,可以指定对哪些输入求导。mindspore.value_and_grad
:用于获得函数的微分函数,同时返回函数值和梯度。ops.stop_gradient
:用于截断某个Tensor的梯度,不影响其他Tensor的梯度计算。nn.BCEWithLogitsLoss
:二值交叉熵损失函数,结合了Sigmoid层和二值交叉熵损失。model.trainable_params()
:从模型中取出可以求导的参数。
通过这些步骤和API的使用,可以实现神经网络的自动微分和梯度计算,从而进行模型参数的优化。