01_机器如何自动学习?线性模型与梯度下降

本章目标:不使用任何深度学习框架,仅用 Python 原生代码手动实现线性回归。从"暴力穷举"到"梯度下降",理解机器"学习"的本质。


📖 目录 (Table of Contents)

  1. 问题定义:从一组考试分数说起
  2. 直觉:我们在寻找什么?
  3. 方法一:暴力穷举 (Exhaustive Search)
  4. 方法二:梯度下降 (Gradient Descent)
  5. 进阶:Batch GD vs Stochastic GD
  6. 总结与预告

1. 问题定义:从一组考试分数说起

假设我们有一组简单的学习时间 ( x x x) 和考试分数 ( y y y) 的数据:

学习时间 ( x x x)考试分数 ( y y y)
1 小时2 分
2 小时4 分
3 小时6 分

核心任务:让机器找到 x x x 和 y y y 之间的关系,并预测:学习 4 小时能得多少分?

作为人类,你一眼就能看出规律是 y = 2 x y = 2x y=2x。但在机器的眼中,数据只是一堆数字。它最开始可能会瞎猜 y = 0.5 x y = 0.5x y=0.5x 或者 y = 100 x y = 100x y=100x。

所谓的"机器学习",就是让计算机通过不断试错,自动把参数校准到 2.0 2.0 2.0 的过程。


2. 直觉:我们在寻找什么?

为了简化问题,我们假设模型是一个简单的线性函数(不带偏置 b b b):

y ^ = x ⋅ ω \hat{y} = x \cdot \omega y^​=x⋅ω

  • x x x: 输入数据 (Input)
  • ω \omega ω: 权重 (Weight) —— 这就是我们要找的"魔法数字"。
  • y ^ \hat{y} y^​: 模型的预测值 (Prediction)

我们需要一个标准来衡量模型猜得"准不准"。这就引入了 损失函数 (Loss Function)。在线性回归中,最常用的是 均方误差 (MSE, Mean Square Error):

L o s s = ( y ^ − y ) 2 = ( x ⋅ ω − y ) 2 Loss = (\hat{y} - y)^2 = (x \cdot \omega - y)^2 Loss=(y^​−y)2=(x⋅ω−y)2

看看下面这张图,理解"残差"(预测值与真实值的距离)是如何构成 Loss 的:

在这里插入图片描述

图解说明:

  • 蓝点:真实数据 (1,2), (2,4), (3,6)。
  • 黄线:当前的猜测模型 y = 1.2 x y = 1.2x y=1.2x。
  • 红虚线:残差 (Residuals)。
    • x = 1 x=1 x=1: 预测 1.2 1.2 1.2,真实 2.0 2.0 2.0,误差 0.8 0.8 0.8。
    • x = 2 x=2 x=2: 预测 2.4 2.4 2.4,真实 4.0 4.0 4.0,误差 1.6 1.6 1.6。
    • x = 3 x=3 x=3: 预测 3.6 3.6 3.6,真实 6.0 6.0 6.0,误差 2.4 2.4 2.4。
  • Loss: 就是这三根红线的长度平方和 (MSE)。

3. 方法一:暴力穷举 (Exhaustive Search)

在不知道怎么"智能"优化之前,最简单粗暴的方法就是:把所有可能的 w w w 都试一遍!

让我们画出 w w w 从 0.0 0.0 0.0 到 4.0 4.0 4.0 变化时,Loss 的变化曲线。

🐍 代码实现 (Exhaustive Search)

import numpy as np
import matplotlib.pyplot as plt

# 1. 准备数据 (Training Set)
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

# 2. 定义模型 (Model)
def forward(x, w):
    return x * w

# 3. 定义损失函数 (Loss Function)
def loss(x, y, w):
    y_pred = forward(x, w)
    return (y_pred - y) ** 2

# 4. 穷举过程
w_list = []
mse_list = []

print("开始穷举...")
for w in np.arange(0.0, 4.1, 0.1):
    print(f"w={w:.1f}")
    l_sum = 0
    for x_val, y_val in zip(x_data, y_data):
        y_pred_val = forward(x_val, w)
        l = loss(x_val, y_val, w)
        l_sum += l
        print(f"\tval: {x_val}, {y_val}, pred: {y_pred_val:.2f}, loss: {l:.2f}")

    mse = l_sum / 3  # 计算平均损失
    w_list.append(w)
    mse_list.append(mse)

# 5. 画图 (Visualization)
# plt.plot(w_list, mse_list)
# plt.show()

运行这段代码,你会得到一个完美的U型曲线(抛物线)。这就引出了深度学习中最著名的概念 —— Loss Surface (损失曲面)。


4. 方法二:梯度下降 (Gradient Descent)

穷举法在 w w w 只有一个时还能用。但现代神经网络动辄上亿个参数,穷举法会让计算机算到宇宙毁灭。我们需要一种贪心算法,能告诉我们:“别瞎试了,往右走一步就能变好!”

这就是 梯度下降。

4.1 数学推导:梯度的物理意义

想象你被蒙住双眼放在一个山谷里。你想下山(由高 Loss 走向低 Loss)。
你的脚能感觉到地面的坡度。

  • 如果左边高右边低(斜率为负),你就往右走。
  • 如果右边高左边低(斜率为正),你就往左走。

数学上,这个"坡度"就是导数(梯度):

  1. 损失函数:
    L o s s = ( x ⋅ ω − y ) 2 Loss = (x \cdot \omega - y)^2 Loss=(x⋅ω−y)2
  2. 求导(根据链式法则):
    ∂ L o s s ∂ ω = 2 ⋅ ( x ⋅ ω − y ) ⋅ x \frac{\partial Loss}{\partial \omega} = 2 \cdot (x \cdot \omega - y) \cdot x ∂ω∂Loss​=2⋅(x⋅ω−y)⋅x
  3. 权重更新公式:
    ω n e w = ω o l d − η ⋅ ∂ L o s s ∂ ω \omega_{new} = \omega_{old} - \eta \cdot \frac{\partial Loss}{\partial \omega} ωnew​=ωold​−η⋅∂ω∂Loss​

为什么要减去梯度?

  • 当 w = 0 w=0 w=0 时,斜率是负的(指向下方)。
  • − η ⋅ ( 负数 ) = 正数 -\eta \cdot (\text{负数}) = \text{正数} −η⋅(负数)=正数。所以 w w w 会增加,向右移动。
  • 反之亦然。这个负号是梯度下降的核心。

4.2 代码实战:手撕梯度下降

我们要用代码实现这个公式。

# 初始权重随便猜,比如 1.0
w = 1.0

# 前向传播
def forward(x):
    return x * w

# 【核心】梯度计算 (手动求导公式)
def gradient(x, y):
    return 2 * x * (x * w - y)

# 训练配置
learning_rate = 0.01  # 步长 (Learning Rate)

print("Predict (before training)", 4, forward(4))

# 开始训练 100 轮
for epoch in range(100):
    for x_val, y_val in zip(x_data, y_data):
        # 1. 计算梯度 (grad)
        grad = gradient(x_val, y_val)

        # 2. 更新权重 (Update)
        w = w - learning_rate * grad
        print(f"\tgrad: {x_val}, {y_val}, grad: {grad:.2f}")

    # 计算当前 Loss 仅仅为了观察
    l_sum = 0
    for x_val, y_val in zip(x_data, y_data):
        l_sum += loss(x_val, y_val, w)
    print(f"Epoch: {epoch}, w={w:.3f}, loss={l_sum/3:.3f}")

print("Predict (after training)", 4, forward(4))

运行结果:

Epoch: 99, w=1.999, loss=0.000
Predict (after training) 4 7.9999

机器成功找到了 w ≈ 2.0 w \approx 2.0 w≈2.0!


5. 进阶:Batch GD vs Stochastic GD

  • Batch Gradient Descent (BGD): 全班同学(所有样本)都发完言,取平均意见,再走一步。稳,但慢。
  • Stochastic Gradient Descent (SGD): 听一个同学发言,就走一步。快,但抖。
  • Mini-Batch SGD: 折中方案。听一组同学发言。这是深度学习的主流。

6. 总结与预告

本章我们完成了一个壮举:不依赖任何框架,从零实现了机器学习的核心逻辑。

  1. Model: y ^ = x ⋅ w \hat{y} = x \cdot w y^​=x⋅w
  2. Loss: MSE
  3. Optimization: 梯度下降

现在的痛点:
这里的梯度公式 2 * x * (x * w - y) 是我们手算的。如果模型变成了复杂的神经网络(几十层),手算梯度几乎是不可能的。

下一章预告:
我们将引入 反向传播 (Backpropagation) —— 神经网络自动求导的秘密武器。我们将看到计算图是如何像"传话游戏"一样,把梯度这一层层传回来的。

➡️ 下一章:02_神经网络如何自动求导?反向传播的数学魔法

更多推荐