03 函数式自动微分

📅 2026/7/2 20:44:10
03 函数式自动微分
函数式自动微分神经网络的训练主要使用反向传播算法模型预测值logits与正确标签label送入损失函数loss function获得loss然后进行反向传播计算求得梯度gradients最终更新至模型参数parameters。自动微分能够计算可导函数在某点处的导数值是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算该功能对用户屏蔽了大量的求导细节和过程大大降低了框架的使用门槛。MindSpore使用函数式自动微分的设计理念提供更接近于数学语义的自动微分接口mindspore.grad和mindspore.value_and_grad。下面我们使用一个简单的单层线性变换模型进行介绍。importnumpyasnpimportmindsporefrommindsporeimportops,nn,Tensor,Parameter函数与计算图计算图是用图论语言表示数学函数的一种方式也是深度学习框架表达神经网络模型的统一方法。我们将根据下面的计算图构造计算函数和神经网络。在这个模型中x xx为输入y yy为正确值w ww和b bb是我们需要优化的参数。np.random.seed(42)xops.ones(5,mindspore.float32)# input tensoryops.zeros(3,mindspore.float32)# expected outputwParameter(Tensor(np.random.randn(5,3),mindspore.float32),namew)# weightbParameter(Tensor(np.random.randn(3,),mindspore.float32),nameb)# bias我们根据计算图描述的计算过程构造计算函数。其中binary_cross_entropy_with_logits 是一个损失函数计算预测值和目标值之间的二值交叉熵损失。deffunction(x,y,w,b):zops.matmul(x,w)b lossops.binary_cross_entropy_with_logits(z,y,ops.ones_like(z),ops.ones_like(z))returnloss执行计算函数可以获得计算的loss值。lossfunction(x,y,w,b)print(loss)1.3423151微分函数与梯度计算为了优化模型参数需要求参数对loss的导数∂ loss ⁡ ∂ w \frac{\partial \operatorname{loss}}{\partial w}∂w∂loss​和∂ loss ⁡ ∂ b \frac{\partial \operatorname{loss}}{\partial b}∂b∂loss​此时我们调用mindspore.grad函数来获得function的微分函数。这里使用了grad函数的两个入参分别为fn待求导的函数。grad_position指定求导输入位置的索引。由于我们对w ww和b bb求导因此配置其在function入参对应的位置(2, 3)。使用grad获得微分函数是一种函数变换即输入为函数输出也为函数。grad_fnmindspore.grad(function,(2,3))执行微分函数即可获得w ww、b bb对应的梯度。gradsgrad_fn(x,y,w,b)print(grads)(Tensor(shape[5, 3], dtypeFloat32, value [[ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]]), Tensor(shape[3], dtypeFloat32, value [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]))Stop Gradient通常情况下求导时会求loss对参数的导数因此函数的输出只有loss一项。当我们希望函数输出多项时微分函数会求所有输出项对参数的导数。此时如果想实现对某个输出项的梯度截断或消除某个Tensor对梯度的影响需要用到Stop Gradient操作。这里我们将function改为同时输出loss和z的function_with_logits获得微分函数并执行。deffunction_with_logits(x,y,w,b):zops.matmul(x,w)b lossops.binary_cross_entropy_with_logits(z,y,ops.ones_like(z),ops.ones_like(z))returnloss,zgrad_fnmindspore.grad(function_with_logits,(2,3))gradsgrad_fn(x,y,w,b)print(grads)(Tensor(shape[5, 3], dtypeFloat32, value [[ 1.32618928e00, 1.01589143e00, 1.04216456e00], [ 1.32618928e00, 1.01589143e00, 1.04216456e00], [ 1.32618928e00, 1.01589143e00, 1.04216456e00], [ 1.32618928e00, 1.01589143e00, 1.04216456e00], [ 1.32618928e00, 1.01589143e00, 1.04216456e00]]), Tensor(shape[3], dtypeFloat32, value [ 1.32618928e00, 1.01589143e00, 1.04216456e00]))可以看到求得w ww、b bb对应的梯度值发生了变化。此时如果想要屏蔽掉z对梯度的影响即仍只求参数对loss的导数可以使用mindspore.ops.stop_gradient接口将梯度在此处截断。我们将function实现加入stop_gradient并执行。deffunction_stop_gradient(x,y,w,b):zops.matmul(x,w)b lossops.binary_cross_entropy_with_logits(z,y,ops.ones_like(z),ops.ones_like(z))returnloss,ops.stop_gradient(z)grad_fnmindspore.grad(function_stop_gradient,(2,3))gradsgrad_fn(x,y,w,b)print(grads)(Tensor(shape[5, 3], dtypeFloat32, value [[ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]]), Tensor(shape[3], dtypeFloat32, value [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]))可以看到求得w ww、b bb对应的梯度值与初始function求得的梯度值一致。Auxiliary dataAuxiliary data意为辅助数据是函数除第一个输出项外的其他输出。通常我们会将函数的loss设置为函数的第一个输出其他的输出即为辅助数据。grad和value_and_grad提供has_aux参数当其设置为True时可以自动实现前文手动添加stop_gradient的功能满足返回辅助数据的同时不影响梯度计算的效果。下面仍使用function_with_logits配置has_auxTrue并执行。grad_fnmindspore.grad(function_with_logits,(2,3),has_auxTrue)grads,(z,)grad_fn(x,y,w,b)print(grads,z)(Tensor(shape[5, 3], dtypeFloat32, value [[ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]]), Tensor(shape[3], dtypeFloat32, value [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02])) [ 3.8211915 -2.994512 -1.932323 ]可以看到求得w ww、b bb对应的梯度值与初始function求得的梯度值一致同时z能够作为微分函数的输出返回。神经网络梯度计算前述章节主要根据计算图对应的函数介绍了MindSpore的函数式自动微分但我们的神经网络构造是继承自面向对象编程范式的nn.Cell。接下来我们通过Cell构造同样的神经网络利用函数式自动微分来实现反向传播。首先我们继承nn.Cell构造单层线性变换神经网络。这里我们直接使用前文的w ww、b bb作为模型参数使用mindspore.Parameter进行包装后作为内部属性并在construct内实现相同的Tensor操作。# Define modelclassNetwork(nn.Cell):def__init__(self):super().__init__()self.ww self.bbdefconstruct(self,x):zops.matmul(x,self.w)self.breturnz接下来我们实例化模型和损失函数。# Instantiate modelmodelNetwork()# Instantiate loss functionloss_fnnn.BCEWithLogitsLoss()完成后为使用函数式自动微分需要将神经网络和损失函数的调用封装为前向计算函数。# Define forward functiondefforward_fn(x,y):zmodel(x)lossloss_fn(z,y)returnloss完成后我们使用value_and_grad接口获得微分函数用于计算梯度。由于使用Cell封装神经网络模型模型参数为Cell的内部属性此时无需使用grad_position指定对函数输入求导因此将其配置为None。对模型参数求导时使用weights参数通过model.trainable_params()方法从Cell中获取可求导参数。grad_fnmindspore.value_and_grad(forward_fn,None,weightsmodel.trainable_params())loss,gradsgrad_fn(x,y)print(grads)(Tensor(shape[5, 3], dtypeFloat32, value [[ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02], [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]]), Tensor(shape[3], dtypeFloat32, value [ 3.26189250e-01, 1.58914644e-02, 4.21645455e-02]))执行微分函数可以看到梯度值和前文function求得的梯度值一致。