从开发视角彻底读懂机器学习核心数学概念

很多工程师一看到 导数、偏导、反向传播、自动微分、梯度下降、权重、偏置 这些词,就容易觉得机器学习门槛很高。

其实这些概念放到代码里并不神秘。本文会用一个 广告系统智能出价引擎 的例子,尽量不用复杂公式,把这组核心概念讲清楚。

一、 业务场景引入:智能广告出价引擎

为电商网站开发广告投放系统:根据用户历史活跃度(过去30天点击次数 $x$),自动算出合理的广告竞价(预测出价 $y_{pred}$)。出价太高成本爆炸,太低抢不到曝光,因此要让系统自动“学会”配置最优的出价核心参数。

二、 流程里的核心角色

可以先记住这几个最核心的角色。

  • 权重 w:决定某个特征有多重要。
  • 偏置 b:决定基础输出是多少。

它们一起组成最简单的模型: $ y = w \cdot x + b $

wb 一开始通常是随便给的,训练的过程,就是不断把这两个值调得更合适。

把输入数据送进模型,算出预测值,这一步就叫前向传播
比如 x = 3w = 2b = 1,那么: $ y_{pred} = 2 \times 3 + 1 = 7 $

但预测值 7 和真实值 10 还有差距,所以还要用损失函数来衡量“错了多少”。
比如用平方误差: $ Loss = (y_{pred} - y_{true})^2 = (7 - 10)^2 = 9 $

这里这个公式叫损失函数,算出来的 9 就是损失值。训练的目标,就是让损失越来越小。

三、 模型为什么知道该怎么改

现在的问题是:误差已经知道了,程序为什么会知道该改 wb

关键就看一句话:某个参数动一下,损失会变大还是变小。

这就是偏导在做的事。比如把 wb 稍微调大一点,损失都从 9 变小了,就说明这两个参数这一步都该往上调。

把每个参数“该往哪边改、改多少”的信息合在一起,就是梯度
反向传播做的事,就是从最后的 Loss 倒着往前算,把这些梯度算出来。PyTorch 会自动完成这一步。

四、 参数到底是怎么更新的

知道了该往哪边改,下一步就是真的改参数。这一步叫梯度下降法,意思很简单:哪个方向能让损失变小,就往那个方向走一小步。

公式通常写成: $ w = w - lr \cdot gradient $

lr 是学习率,表示一步走多大;前面的减号表示要和梯度反着走,因为梯度指向的是损失变大的方向。

在上面的例子里,如果 wb 的梯度都是负的,那它们更新后都会变大,预测值会更接近真实值,损失也会继续下降。

所以整个过程可以概括成一句话:先算预测,再算误差,再算每个参数该怎么改,最后把参数往能让误差变小的方向推一小步。

五、 实际开发中到底看什么、调什么

真实开发里,工程师一般不会手动去改 wb 这种模型参数,它们通常由优化器自动更新。真正经常调的是学习率、batch size、网络结构、正则化和数据这些超参数。

实际训练时,最常看的就是几类信号:

  • 训练损失:看模型有没有学起来
  • 验证损失:看有没有过拟合
  • 业务指标:看结果是否真的有用
  • 训练和验证的差距:判断是欠拟合还是过拟合
  • 梯度和错误样本:看训练过程是否正常、模型错在什么地方

观察这些信号时,有两个核心概念需要理解:

  • 收敛(Convergence):损失函数逐渐稳定到一个较小值、参数更新趋近于停止的状态。简单说,就是模型通过不断迭代,找到了一个"足够好"的解,继续训练也不会显著提升性能。训练损失持续下降且趋于平稳,就是收敛的表现;反之,损失不降、震荡或发散,就是没有收敛。
  • 鲁棒(Robustness):模型在面对数据扰动、噪声或分布变化时,仍能保持稳定表现的能力。鲁棒性强的模型,不会因为少量异常数据就大幅偏离正常输出。比如 Adam 优化器对初始学习率的选择更鲁棒,即使学习率设得不太精确,也能相对稳定地收敛。

看到问题后,再对症调整:

  • 损失不降或下降很慢,先看学习率、数据和模型能力
  • 训练集好但验证集差,通常是过拟合,要加强正则化、减少模型复杂度或增加数据
  • 训练集和验证集都差,通常是还没学会,要检查数据、特征或模型结构
  • 训练波动很大,通常先降学习率,必要时调大 batch size 或做梯度裁剪

所以更准确地说,工程师不是手动调模型参数,而是看训练信号,再调整超参数和训练策略。

六、 先看一次完整训练闭环

一次训练闭环的核心流程
配置阶段
初始化 权重 w偏置 b,给模型一套起始参数,并标记它们需要被自动微分(autograd)追踪。
运行阶段
输入数据后,先做 前向传播:把 x 代入 $y = w \cdot x + b$ 得到线性输出,再通过 激活函数引入非线性。常见的激活函数有 Sigmoid(把输出压到 0~1,适合二分类)、Tanh(压到 -1~1,零中心化)、ReLU(负数置零、正数原样输出,计算简单、收敛快,最常用)。本例为线性回归出价场景,不使用激活函数,$y_{pred} = w \cdot x + b$;再通过 损失函数(如平方误差 $Loss = (y_{pred} - y_{true})^2$)算出这次预测错了多少,也就是得到 损失值
分析阶段
框架通过 反向传播利用 自动微分(autograd)倒着算出每个参数的 偏导,再把这些结果组合成总 梯度
优化阶段
运行 梯度下降法:常用优化器有 SGD(基础随机梯度下降)、Momentum(带动量,加速收敛)、RMSProp(按参数自适应调节步长)、Adam(结合动量与自适应步长,最常用)。本例采用 Adam——它能根据梯度的一阶/二阶矩自适应调节每个参数的学习率,对初始学习率更鲁棒,收敛更快更稳,是绝大多数业务场景的默认选择;按 $w = w - lr \cdot \nabla w$、$b = b - lr \cdot \nabla b$ 的思路沿负梯度方向更新 wb,更新后清空梯度缓冲,进入下一轮训练。

七、 贴合实际开发的 PyTorch 代码实战

下面的代码完全模拟了上述广告出价引擎的单次优化闭环,每一个名词都在它对应的代码行上进行了精准标注:

import torch

# 1. 配置初始化:声明我们需要追踪其梯度的【权重 Weight】和【偏置 Bias】
# `requires_grad=True` 意味着张量会被纳入【自动微分】的计算图中
w = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)

# 模拟一条带标签的训练样本:x 为用户活跃度,y_true 为历史数据中对应的真实目标出价
x = torch.tensor(3.0)
y_true = torch.tensor(10.0)

print(f"--- 迭代前配置:w = {w.item():.2f}, b = {b.item():.2f} ---")

# 2. 执行【正向传播 Forward Propagation】:将数据带入业务逻辑公式中运行
# 注意:对这个线性模型来说,w 既是直线斜率,也等于模型输出 y 对输入 x 的导数,即 dy/dx = w
y_pred = w * x + b
print(f"正向传播计算结果:预测出价 y_pred = {y_pred.item():.2f} (真实目标为 {y_true.item():.2f})")

# 3. 计算【损失函数 Loss Function】以得到具体的【损失值 Loss Value】
# 这里先用单样本平方误差做教学示例;如果对一批样本求平均,才是常见的 MSE
loss = (y_pred - y_true) ** 2
print(f"定义标准评估误差:当前单样本【损失值 Loss Value】= {loss.item():.2f}")

# 4. 触发【反向传播 Backpropagation】
# 这一步会沿着前面构建好的计算图,从 `loss` 开始逆向计算每个参数对损失的影响率
loss.backward()

# 此时,各个参数的偏导已经计算完毕,并存储在各自的 `.grad` 属性中
# 注意:这里的 `w.grad` / `b.grad` 是“损失函数对参数的偏导”
# 它们对应的是 dLoss/dw、dLoss/db,不是模型里的 dy/dx
print(f"反向传播完毕,计算出 dLoss/dw = {w.grad.item():.2f}")
print(f"反向传播完毕,计算出 dLoss/db = {b.grad.item():.2f}")

# 5. 应用【梯度下降法 Gradient Descent】更新系统配置
# 学习率控制参数每次更新的步长:过大会震荡甚至不收敛,过小则下降很慢
learning_rate = 0.05

with torch.no_grad():  # 更新参数时,关闭自动微分的追踪,避免干扰计算图
    # 核心动作:沿着负梯度方向微调参数,让损失下降
    w -= learning_rate * w.grad
    b -= learning_rate * b.grad
    
    # 清空当前的梯度缓冲区,为下一次训练迭代做准备
    w.grad.zero_()
    b.grad.zero_()

print(f"--- 执行梯度下降后新配置:w = {w.item():.2f}, b = {b.item():.2f} ---")

# 验证新配置下的误差
with torch.no_grad():  # 纯验证阶段不需要继续构建计算图
    y_pred_new = w * x + b
    loss_new = (y_pred_new - y_true) ** 2
print(f"重新执行正向传播验证:新损失值 = {loss_new.item():.2f} (成功从 9.00 降低了!)")

这个示例之所以在一次参数更新后就能把损失从 9.00 直接降到 0.00,是因为这里特意选用了单样本、线性模型、且步长刚好合适的教学场景;在真实训练中,通常需要经过很多轮迭代,损失才会逐步下降。