Skip to content

手写梯度下降拟合一条直线

不用 sklearn,用梯度下降从零拟合 y=wx+b,一步步观察损失如何下降。

学完本案例你将理解:不需要闭式解,只要会求梯度,参数就能自动收敛到最优值。


生活引入

投篮练习——每次调整一点角度

你站在三分线外投篮。第一次偏左了——下次往右调一点。第二次偏右了——再往左调一点。每次投完,你看一眼偏差,朝减小偏差的方向调整姿势。

机器学习训练完全一样:每次算完预测值,看一眼和真实值的差距(损失),然后朝减小差距的方向调整参数(梯度下降)。重复几百次,姿势就定型了。


直观理解

我们的目标是找到一条直线 y=wx+b,让它尽可能穿过所有数据点。w 是斜率,b 是截距,损失 = 所有点到直线的竖直距离平方的平均值(MSE)。

如果从 w=0,b=0 开始,直线是平的,损失很大。梯度下降会告诉我们:「w 该往大调,b 也该往大调」,然后我们照做。反复 100 次,直线就会穿过数据点。


数学定义

L(w,b)=1n_i=1n(wx_i+by_i)2$$$$Lw=2n_i=1n(wx_i+by_i)x_i,Lb=2n_i=1n(wx_i+by_i)$$$$wwηLw,bbηLb

Python 动手实践

实例

python

import numpy as np

np.random.seed(0)

# 生成带噪声的线性数据:真实 y = 2x + 1

n = 60

x = np.random.uniform(-3, 3, n)

y = 2 * x + 1 + np.random.randn(60) * 0.8

# 手推梯度函数(对 w 和 b 分别求偏导)

def compute_gradients(w, b, x, y):

    y_pred = w * x + b

    error = y_pred - y          # 预测值 - 真实值

    dw = np.mean(2 * error * x) # dL/dw

    db = np.mean(2 * error)     # dL/db

    return dw, db

# 梯度下降主循环

w, b = 0.0, 0.0     # 初始参数故意设成 0

lr = 0.05

epochs = 100

print("RUNOOB 梯度下降过程 (真实值 w=2, b=1):")

for epoch in range(epochs):

    y_pred = w * x + b

    loss = np.mean((y_pred - y) ** 2)

    dw, db = compute_gradients(w, b, x, y)

    w -= lr * dw

    b -= lr * db

    if epoch % 20 == 0:

        print(f"  epoch {epoch:3d}  loss={loss:.4f}  w={w:.3f}  b={b:.3f}")

print(f"\nRUNOOB 最终: w={w:.3f}, b={b:.3f}  (真实 w=2, b=1)")

print(f"拟合直线: y = {w:.2f}x + {b:.2f}")
python
RUNOOB 梯度下降过程 (真实值 w=2, b=1):
  epoch   0  loss=13.4515  w=0.790  b=0.057
  epoch  20  loss=0.9165  w=1.868  b=0.785
  epoch  40  loss=0.7316  w=1.949  b=0.918
  epoch  60  loss=0.7083  w=1.971  b=0.956
  epoch  80  loss=0.7055  w=1.977  b=0.967
  epoch 100  loss=0.7052  w=1.979  b=0.970

RUNOOB 最终: w=1.979, b=0.970  (真实 w=2, b=1)
拟合直线: y = 1.98x + 0.97

AI 中的应用场景

这个案例的代码骨架就是所有深度学习训练的模板:前向计算预测值 → 计算损失 → 反向传播求梯度 → 更新参数 → 重复。PyTorch 中的 optimizer.step() 和 loss.backward() 就是自动化了这个过程。

AI 思考中...

用导数找函数最小值

可视化不同学习率的影响

基于 VitePress 构建,部署于 GitHub Pages