手写梯度下降拟合一条直线
不用 sklearn,用梯度下降从零拟合 \(y = wx + b\),一步步观察损失如何下降。
学完本案例你将理解:不需要闭式解,只要会求梯度,参数就能自动收敛到最优值。
生活引入
投篮练习——每次调整一点角度
你站在三分线外投篮。第一次偏左了——下次往右调一点。第二次偏右了——再往左调一点。每次投完,你看一眼偏差,朝减小偏差的方向调整姿势。
机器学习训练完全一样:每次算完预测值,看一眼和真实值的差距(损失),然后朝减小差距的方向调整参数(梯度下降)。重复几百次,姿势就定型了。
直观理解
我们的目标是找到一条直线 \(y = wx + b\),让它尽可能穿过所有数据点。\(w\) 是斜率,\(b\) 是截距,损失 = 所有点到直线的竖直距离平方的平均值(MSE)。
如果从 \(w=0, b=0\) 开始,直线是平的,损失很大。梯度下降会告诉我们:「w 该往大调,b 也该往大调」,然后我们照做。反复 100 次,直线就会穿过数据点。
数学定义
\[ L(w, b) = \frac{1}{n}\sum_{i=1}^{n}(wx_i + b - y_i)^2 \] \[ \frac{\partial L}{\partial w} = \frac{2}{n}\sum_{i=1}^{n}(wx_i + b - y_i) \cdot x_i, \quad \frac{\partial L}{\partial b} = \frac{2}{n}\sum_{i=1}^{n}(wx_i + b - y_i) \] \[ w \leftarrow w - \eta \cdot \frac{\partial L}{\partial w}, \quad b \leftarrow b - \eta \cdot \frac{\partial L}{\partial b} \]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}")
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}")
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() 就是自动化了这个过程。
