赞
踩
神经网络的训练主要使用反向传播算法,模型预测值(logits)与正确标签(label)送入损失函数(loss function)获得loss,然后进行反向传播计算,求得梯度(gradients),最终更新至模型参数(parameters)。自动微分能够计算可导函数在某点处的导数值,是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算,该功能对用户屏蔽了大量的求导细节和过程,大大降低了框架的使用门槛。
恩,作为一个小白来说,我觉得有必要补充一下梯度的概念
在深度学习中,梯度(Gradient)是一个非常重要的概念,它表示函数在某一点上的局部变化率。在多变量函数的情况下,梯度是一个向量,它的每个分量都表示函数在相应维度上的变化率。
具体来说,对于一个多变量函数 f(x1, x2, …, xn),其梯度∇f(x) 是一个向量,其每个分量 ∂ x ∂ f \frac{∂x}{∂f} ∂f∂x表示函数在 xi 方向上的变化率。梯度的大小表示函数在该点的局部斜率,而梯度的方向表示函数在该点上升最快的方向。
梯度的计算通常遵循以下步骤:
1、对每个变量求偏导数:对于函数 f(x),对每个变量 ∂ f ∂ x i \frac{∂f}{∂xi} ∂xi∂f 。
2、将这些偏导数组合成梯度向量:将所有偏导数组合起来,形成一个向量,这就是梯度向量。在多变量函数的情况下,梯度向量是所有偏导数的集合。
例如,对于一个简单的函数 f(x, y) = x2 + y2 ,其梯度向量是:∇f(x,y)=[ ∂ f ∂ x \frac{∂f}{∂x} ∂x∂f, ∂ f ∂ x \frac{∂f}{∂x} ∂x∂f]=[2x,2y]
在实际应用中,梯度的计算通常使用链式法则,这涉及到多个变量的偏导数,用于指导模型的参数更新。
MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口grad
和value_and_grad
。下面我们使用一个简单的单层线性变换模型进行介绍。
# 导入numpy库,并重命名为np,以便在代码中方便地使用numpy的功能。
import numpy as np
# 导入mindspore库,这是华为推出的一个开源深度学习框架。
import mindspore
# 从mindspore库中导入nn模块,该模块提供了构建神经网络所需的层和函数。
from mindspore import nn
# 从mindspore库中导入ops模块,该模块提供了神经网络操作的各种运算符。
from mindspore import ops
# 从mindspore库中导入Tensor和Parameter类,这两个类分别用于创建张量对象和参数对象,它们是构建神经网络的基础。
from mindspore import Tensor, Parameter
计算图是用图论语言表示数学函数的一种方式,也是深度学习框架表达神经网络模型的统一方法。我们将根据下面的计算图构造计算函数和神经网络。
在这个模型中,
x
x
x为输入,
y
y
y为正确值,
w
w
w和
b
b
b是我们需要优化的参数。
# 创建输入张量 # 使用ops模块中的ones函数创建一个形状为(5,)的一维张量,所有元素都初始化为1,数据类型为float32。 x = ops.ones(5, mindspore.float32) # 创建预期输出张量 # 使用ops模块中的zeros函数创建一个形状为(3,)的一维张量,所有元素都初始化为0,数据类型为float32。 y = ops.zeros(3, mindspore.float32) # 创建一个参数对象w,其值为一个5x3的二维张量,元素值从标准正态分布中随机采样,数据类型为float32。 # 这个张量将作为神经网络中的权重(weights)。 w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w') # 创建一个参数对象b,其值为一个形状为(3,)的一维张量,元素值从标准正态分布中随机采样,数据类型为float32。 # 这个张量将作为神经网络中的偏置(biases)。 b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b')
我们根据计算图描述的计算过程,构造计算函数。
其中,binary_cross_entropy_with_logits 是一个损失函数,计算预测值和目标值之间的二值交叉熵损失。
# 定义一个函数function,它接受四个参数:x(输入数据),y(标签),w(权重),b(偏置)。 def function(x, y, w, b): # 使用ops模块中的matmul函数计算x和w的矩阵乘法,然后加上偏置b。 # 这个操作实现了线性变换,z是模型的预测值。 z = ops.matmul(x, w) + b # 调用ops模块中的binary_cross_entropy_with_logits函数计算二进制交叉熵损失。 # 这个损失函数适用于二分类问题,其中z是模型的预测值(在计算损失之前通常不需要经过激活函数)。 # y是真实的标签值,它应该是一个与z相同形状的张量,包含0和1。 # ops.ones_like(z)创建了一个与z形状相同的张量,所有元素都是1,用作损失函数的参数。 # 这个函数的返回值是损失,一个标量值,表示模型预测值和真实值之间的差异。 # 损失越小,表示模型的预测越接近真实标签。 # 在训练过程中,我们通常使用梯度下降等优化算法来最小化这个损失函数,从而提高模型的性能。 loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z)) # 返回计算得到的损失值。 return loss # 调用定义的function函数,传入x、y、w和b参数,计算二进制交叉熵损失。 loss = function(x, y, w, b) # 使用print函数将计算得到的损失值输出。 print(loss)
输出:
0.17323041
为了优化模型参数,需要求参数对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
w
w和
b
b
b求导,因此配置其在function
入参对应的位置(2, 3)
。
使用
grad
获得微分函数是一种函数变换,即输入为函数,输出也为函数。
# 使用mindspore.grad函数来创建一个梯度函数grad_fn,它将计算function函数关于其第三个和第四个参数(w和b)的梯度。
# 参数function是我们之前定义的函数,其计算二进制交叉熵损失。
# 参数(2, 3)是一个元组,表示我们想要计算的是function函数关于其参数列表中的第二和第三个参数的梯度。
# 因此,grad_fn将会返回一个函数,当我们调用这个返回的函数并传入相应的参数时,它将计算并返回function函数关于w和b的梯度。
grad_fn = mindspore.grad(function, (2, 3))
# 调用创建的梯度函数grad_fn,传入x、y、w和b参数,计算function函数关于w和b的梯度。
# grads将是一个包含两个元素的元组,第一个元素是关于w的梯度,第二个元素是关于b的梯度。
grads = grad_fn(x, y, w, b)
# 使用print函数将计算得到的梯度输出到控制台。
print(grads)
输出:
(Tensor(shape=[5, 3], dtype=Float32, value=
[[ 5.55104874e-02, 8.84492472e-02, 9.58411861e-03],
[ 5.55104874e-02, 8.84492472e-02, 9.58411861e-03],
[ 5.55104874e-02, 8.84492472e-02, 9.58411861e-03],
[ 5.55104874e-02, 8.84492472e-02, 9.58411861e-03],
[ 5.55104874e-02, 8.84492472e-02, 9.58411861e-03]]),
Tensor(shape=[3], dtype=Float32, value=
[ 5.55104874e-02, 8.84492472e-02, 9.58411861e-03]))
输出的梯度包含两个Tensor对象,分别对应于权重 w 和偏置 b 的梯度。这些梯度表示在当前的参数值和给定的输入 x 和标签 y 下,损失函数对于每个参数的变化率。换句话说,它们指示了为了减少损失,每个参数应该移动的方向和幅度。
对于权重 w 的梯度,它是一个形状为 [5, 3] 的二维张量,这意味着你有5个输入和3个输出。每个元素 w[i, j] 的梯度表示如果稍微改变 w[i, j] 的值,损失函数的值会如何变化。正的梯度意味着增加 w[i, j] 的值可以减少损失,而负的梯度意味着减少 w[i, j] 的值可以减少损失。
对于偏置 b 的梯度,它是一个形状为 [3] 的一维张量,每个元素 b[j] 的梯度表示如果稍微改变 b[j] 的值,损失函数的值会如何变化。同样,正的梯度意味着增加 b[j] 的值可以减少损失,而负的梯度意味着减少 b[j] 的值可以减少损失。
此处,所有的梯度都是正的,这意味着增加权重和偏置的值可以减少损失函数的值。然而,需要注意的是,这些梯度只是在这个特定点的局部信息,它们指示了在这个点附近如何优化参数。在实际的优化过程中,你需要结合学习率来更新参数,以在多次迭代中逐步减少损失。
此外,权重 w 的梯度矩阵中的所有行都是相同的,这可能表明输入 x 是相同的,或者模型在当前状态下对于所有输入有相同的预测。如果输入 x 是不同的,这可能是模型结构的一个特点,比如在批处理中所有输入都被视为相同的情况。
在深度学习框架中,Stop Gradient(在某些框架中可能被称为 detach 或其他类似名称)是一个操作,用于阻止梯度在计算图中的传播。当你对某个张量应用 Stop Gradient 操作后,该张量的梯度将不会在反向传播过程中被计算,即该张量在计算图中的梯度将被视为0。
这个操作的目的是在某些情况下冻结模型的一部分,使其在训练过程中不被更新。这可以在以下几种情况下使用:
- 特征提取:如果你想在预训练的模型上添加自定义层,并只训练这些新添加的层,你可以将预训练层的输出与 Stop Gradient 结合使用,这样预训练层的参数就不会在训练过程中被更新。
- 固定某些参数:在某些模型架构中,你可能想要固定某些参数,只训练其他参数。通过在这些参数上应用 Stop Gradient,可以确保它们在训练过程中保持不变。
- 调试和实验:在调试模型或进行实验时,你可能想要检查模型中某一部分的输出对损失的影响,而不想影响其他部分的训练。在这种情况下,可以在特定部分应用 Stop Gradient 来隔离其影响。
- 避免梯度消失或爆炸:在某些情况下,应用 Stop Gradient 可以帮助防止梯度消失或爆炸问题,特别是在非常深的网络中。
在MindSpore中,可以使用 ops.stop_gradient 函数来实现这个操作。
通常情况下,求导时会求loss对参数的导数,因此函数的输出只有loss一项。当我们希望函数输出多项时,微分函数会求所有输出项对参数的导数。此时如果想实现对某个输出项的梯度截断,或消除某个Tensor对梯度的影响,需要用到Stop Gradient操作。
这里我们将function
改为同时输出loss和z的function_with_logits
,获得微分函数并执行。
# 定义一个函数function_with_logits,它接受四个参数:x(输入数据),y(标签),w(权重),b(偏置)。 def function_with_logits(x, y, w, b): # 使用ops模块中的matmul函数计算x和w的矩阵乘法,然后加上偏置b。 # 这个操作实现了线性变换,z是模型的预测值(logits)。 z = ops.matmul(x, w) + b # 调用ops模块中的binary_cross_entropy_with_logits函数计算二进制交叉熵损失。 # 这个损失函数适用于二分类问题,其中z是模型的预测值(在计算损失之前通常不需要经过激活函数)。 # y是真实的标签值,它应该是一个与z相同形状的张量,包含0和1。 # ops.ones_like(z)创建了一个与z形状相同的张量,所有元素都是1,用作损失函数的参数。 # 这个函数的返回值是损失,一个标量值,表示模型预测值和真实值之间的差异。 loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z)) # 返回计算得到的损失值和logits。 return loss, z # 使用mindspore.grad函数来创建一个梯度函数grad_fn,它将计算function_with_logits函数关于其第二个和第三个参数(w和b)的梯度。 # 参数function_with_logits是我们之前定义的函数,它返回损失和logits。 # 参数(2, 3)是一个元组,表示我们想要计算的是function_with_logits函数关于其参数列表中的第三和第四个参数的梯度。 grad_fn = mindspore.grad(function_with_logits, (2, 3)) # 调用之前创建的梯度函数grad_fn,传入x、y、w和b参数,计算function_with_logits函数关于w和b的梯度。 # grads将是一个包含两个元素的元组,第一个元素是关于w的梯度,第二个元素是关于b的梯度。 grads = grad_fn(x, y, w, b) # 使用print函数将计算得到的梯度输出到控制台。 print(grads)
输出:
(Tensor(shape=[5, 3], dtype=Float32, value=
[[ 1.05551052e+00, 1.08844924e+00, 1.00958407e+00],
[ 1.05551052e+00, 1.08844924e+00, 1.00958407e+00],
[ 1.05551052e+00, 1.08844924e+00, 1.00958407e+00],
[ 1.05551052e+00, 1.08844924e+00, 1.00958407e+00],
[ 1.05551052e+00, 1.08844924e+00, 1.00958407e+00]]),
Tensor(shape=[3], dtype=Float32, value=
[ 1.05551052e+00, 1.08844924e+00, 1.00958407e+00]))
可以看到求得
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/786093
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。