1. 学习的本质:如何衡量“错误”并修正?
一个AI模型的学习过程,本质上是一个“猜谜-反馈-修正”的循环。
- 猜谜 (前向传播): 模型接收输入数据(如一张图片),根据其内部的参数(权重
W和偏置b),给出一个预测结果(比如“这是一只猫”)。 - 反馈 (计算损失): 我们将模型的预测结果与真实的标签(“这确实是一只猫”)进行比较。两者之间的“差距”,我们用一个叫做损失函数 (Loss Function)的东西来量化。差距越大,损失值就越高。
- 修正 (反向传播与优化): 这是最关键的一步。模型需要弄清楚:“我这次错得有多离谱?我的每一个内部参数,应该如何调整,才能让下次的预测结果更接近真相?”
“如何调整”这个问题的答案,就藏在梯度 (Gradient)之中。
2. 梯度:参数的“调整指南针”
梯度是一个数学概念,听起来很深奥,但它的直观意义非常简单:它指出了函数值增长最快的方向。
在我们的场景中,损失函数是关于模型所有参数的函数Loss = f(W, b)。我们计算出的损失函数关于某个参数(比如某个权重W_i)的梯度,就告诉我们:
如果我将参数
W_i稍微增加一点点,我的总损失值会增加多少。
- 如果梯度是一个大的正数:意味着稍微增加这个参数,损失就会急剧增大。那么为了减小损失,我们应该减小这个参数。
- 如果梯度是一个大的负数:意味着稍微增加这个参数,损失就会急剧减小。那么为了减小损失,我们应该增加这个参数。
- 如果梯度接近于0:意味着调整这个参数对损失影响不大,说明它可能已经处于一个比较理想的值了。
因此,梯度就像一个指南针,为我们模型中成千上万的参数,一一指明了“应该朝哪个方向调整才能让总损失变小”。这个根据梯度调整参数的过程,就是梯度下降 (Gradient Descent)。
3. 自动微分:解放生产力的“魔法”
一个现代神经网络的参数动辄数百万、上千万。手动去计算每一个参数对于最终损失的梯度,是完全不现实的。而自动微分 (Automatic Differentiation, AD)就是一个能自动完成这个庞大工程的绝妙机制。
MindSpore的自动微分,正是构建在我们之前学习的计算图之上的。
- 前向传播时: MindSpore记录下从输入Tensor到最终损失值的所有计算步骤,形成一张计算图。
- 反向传播时: MindSpore从最终的损失值开始,沿着计算图反向追溯,利用微积分中的链式法则,一步步地计算出损失对每一个中间变量、直至最源头的模型参数的梯度。
这个过程精确、高效,并且对用户来说几乎是透明的。我们只需要搭建好前向网络,剩下的求导工作,交给MindSpore就好。
4. 实战:mindspore.grad的使用
MindSpore提供了强大的mindspore.grad函数,让我们能亲身体验自动微分的威力。
4.1 对普通函数求导
我们先从一个简单的数学函数f(x) = 3x^2 + 4x开始。我们知道它的导数是f'(x) = 6x + 4。让我们看看MindSpore是如何计算的。
import mindspore from mindspore import grad, Tensor # 1. 定义我们的函数 def f(x): return 3 * x**2 + 4 * x # 2. 使用 grad 生成一个用于求导的新函数 # grad_position=0 表示对函数的第0个输入求导 grad_fn = grad(f, grad_position=0) # 3. 计算在 x=2 处的导数 x = Tensor(2.0, mindspore.float32) gradient = grad_fn(x) print(f"函数 f(x) = 3x^2 + 4x") print(f"在 x = 2 处的导数值是: {gradient}") # 理论值 f'(2) = 6*2 + 4 = 16输出结果将是16.0,与我们手动计算的完全一致!
4.2 对网络参数求导
现在,让我们回到神经网络的场景。我们的目标是求损失函数关于网络参数的梯度。
这里,我们需要一个更强大的工具mindspore.ops.GradOperation,它是grad针对网络训练场景的封装。
import numpy as np import mindspore from mindspore import nn, ops, Tensor # --- 复用上一章的网络和定义 --- mindspore.set_context(mode=mindspore.PYNATIVE_MODE) class MyLinearNet(nn.Cell): def __init__(self): super().__init__() self.dense = nn.Dense(in_channels=3, out_channels=4) def construct(self, x): return self.dense(x) # 1. 准备工作:网络、数据、损失函数 net = MyLinearNet() loss_fn = nn.MSELoss() # 使用均方误差损失 optimizer = nn.SGD(net.trainable_params(), learning_rate=0.01) # 定义一个优化器,后面会讲 # 2. 定义“单步训练函数” # 这个函数描述了从输入到计算出损失的完整前向过程 def forward_fn(data, label): logits = net(data) loss = loss_fn(logits, label) return loss, logits # 3. 获取梯度计算函数 # get_by_list=True 表示我们要求的是关于一个参数列表的梯度 # net.trainable_params() 返回了网络中所有需要被训练的参数(W和b) grad_fn = ops.GradOperation(get_by_list=True)(forward_fn, net.trainable_params()) # 4. 模拟一次计算 # 准备一批假数据 input_data = Tensor(np.random.rand(2, 3), mindspore.float32) target_label = Tensor(np.random.rand(2, 4), mindspore.float32) # 执行梯度计算 # grads 是一个元组,包含了损失对每个可训练参数的梯度 loss_value, grads = grad_fn(input_data, target_label) print("损失值:", loss_value) print("-" * 20) # 打印第一个参数(权重W)的梯度 print("权重(W)的梯度:\n", grads[0]) print("权重(W)的形状:", grads[0].shape) print("-" * 20) # 打印第二个参数(偏置b)的梯度 print("偏置(b)的梯度:\n", grads[1]) print("偏置(b)的形状:", grads[1].shape)代码解读:
forward_fn是我们的核心,它定义了“猜谜”和“反馈”的全过程。ops.GradOperation(...)创建了一个强大的梯度计算工具。我们告诉它,我们要对forward_fn的结果(损失)进行求导,并且求导的目标是net.trainable_params()(网络的所有权重和偏置)。- 执行
grad_fn后,它不仅返回了梯度,还返回了forward_fn的原始输出(损失值和预测值),非常方便。 - 我们成功地拿到了损失对权重
W(形状为4x3)和偏置b(形状为4)的梯度!有了这些梯度,优化器就知道该如何更新参数了。
5. 总结
自动微分是连接模型预测与模型优化的桥梁,是深度学习框架的“内功心法”。通过本文,我们理解了:
- 学习的目标: 最小化损失函数。
- 学习的依据: 损失函数关于模型参数的梯度。
- 学习的实现: MindSpore基于计算图的自动微分机制,能够自动、高效地计算出所有参数的梯度。
- 学习的工具:
mindspore.grad和ops.GradOperation是我们获取梯度的有力武器。
我们已经站在了模型训练的门槛上。我们有了数据(Tensor),有了网络(nn.Cell),现在又有了指导优化的方向(梯度)。万事俱备,只欠东风。这“东风”,就是负责执行参数更新的优化器 (Optimizer)。下一篇文章,我们将正式开始训练我们的第一个模型!