反向传播与梯度下降:神经网络如何真正学会分类

回顾上章:MLP 在引入非线性后,可以形成弯曲的边界 ,但问题:它是怎么知道应该弯成什么样?
 **答案:通过 损失函数衡量差距 + 反向传播更新参数。

二、损失函数(Loss Function)的角色
• 定义:衡量模型预测与真实结果的差异。
• 分类任务常用:
• 二分类 → 二元交叉熵 (Binary Cross Entropy, BCE)
• 多分类 → 交叉熵 (Cross Entropy)
• 数学公式示例(二元交叉熵):

\[L = -\frac{1}{N}\sum_{i=1}^{N} \big[y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)\big] \]

•	直观类比:像导航地图里的“距离差”,我们要不断缩短它。

三、梯度与参数更新
• 目标:找到能让损失函数最小的参数。
• 梯度:损失函数对参数的偏导,告诉我们“往哪边调”。
• 参数更新公式(梯度下降):

\[\theta \leftarrow \theta - \eta \cdot \frac{\partial L}{\partial \theta} \]

其中:
\(\theta\) = 参数 (weights, bias)
\(\eta\) = 学习率 (learning rate)
\(\frac{\partial L}{\partial \theta}\) = 梯度
• 类比:像在山谷里往下走,梯度是“坡度”,学习率是“步长”。

四、反向传播(Backpropagation)原理
• 前向传播:输入 → 线性变换 → 激活函数 → 输出 → 损失。
• 反向传播:从损失往回推,链式法则分解每一层的梯度。
• 链式法则公式:

\[\frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial x} \]

•	直观解释:像接力赛,误差从最后一层“传递”回去。

五、PyTorch Demo 实践

👉 用之前的圆形数据集继续演示,增加 loss 曲线可视化,展示训练过程中:
1. 损失值逐渐下降
2. 决策边界逐渐变弯曲

# -*- coding: utf-8 -*-
# 反向传播与梯度下降:神经网络如何真正学会分类

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt

# 1) 生成非线性可分数据:同心圆
def make_concentric_circles(n_samples=300, inner_r=0.5, outer_r=1.0, noise=0.08, seed=42):
    rng = np.random.default_rng(seed)
    n_inner = n_samples // 2
    n_outer = n_samples - n_inner

    # 内圈(类0)
    angles_inner = rng.uniform(0, 2*np.pi, size=n_inner)
    r_inner = rng.normal(inner_r, noise, size=n_inner)
    x_inner = np.stack([r_inner * np.cos(angles_inner), r_inner * np.sin(angles_inner)], axis=1)
    y_inner = np.zeros((n_inner, 1), dtype=np.float32)

    # 外环(类1)
    angles_outer = rng.uniform(0, 2*np.pi, size=n_outer)
    r_outer = rng.normal(outer_r, noise, size=n_outer)
    x_outer = np.stack([r_outer * np.cos(angles_outer), r_outer * np.sin(angles_outer)], axis=1)
    y_outer = np.ones((n_outer, 1), dtype=np.float32)

    X = np.vstack([x_inner, x_outer]).astype(np.float32)
    y = np.vstack([y_inner, y_outer]).astype(np.float32)

    # 打乱
    idx = rng.permutation(n_samples)
    return X[idx], y[idx]

# 数据准备
X_np, y_np = make_concentric_circles(
    n_samples=300, inner_r=0.45, outer_r=1.0, noise=0.07, seed=7
)
X = torch.tensor(X_np)
y = torch.tensor(y_np)

# 2) 定义一个 MLP 模型
model = nn.Sequential(
    nn.Linear(2, 8),
    nn.ReLU(),
    nn.Linear(8, 1),
    nn.Sigmoid()
)

# 3) 定义损失函数和优化器
criterion = nn.BCELoss()  # 二元交叉熵
optimizer = optim.SGD(model.parameters(), lr=0.1)  # 梯度下降

# 4) 训练过程
losses = []
for epoch in range(200):
    optimizer.zero_grad()        # 清空梯度
    y_pred = model(X)            # 前向传播
    loss = criterion(y_pred, y)  # 计算损失
    loss.backward()              # 反向传播
    optimizer.step()             # 参数更新
    losses.append(loss.item())

# 5) 可视化 Loss 曲线
plt.plot(losses)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("Loss Curve during Training")
plt.show()

通过上面的计算和图形我们得到的结论是在定义好model,这个model我们可以理解为函数建模,也就是输入和输出之间建立了函数之间的关系,函数的关系通过y = mx + b也就是偏置和权重以及隐藏的网络层不断的进行计算,这个model会给权重和偏置一个随机的初始化值,我们输入好x之后得到的一个y_pred的值,然后使用这个值和真实的值y做个对比,通过调整偏置和权重值让其不断的缩小,最好找到了根据数据训练出来的表现最好的一组函数关系,然后就可以使用这组函数关系对后续的数据进行预测,这就是机器学习的核心。

结论和机器学习专业术语的对应关系:
1. 模型 = 函数建模
• 我们定义的 model(例如 MLP)其实就是一个复杂函数 \(f_\theta(x)\),其中 \(\theta\) 代表参数(权重 \(w\) 和偏置 \(b\))。
• 这相当于我们在寻找一个能描述 输入 → 输出 关系的函数。
2. 参数初始化 = 起点
• 在训练开始时,权重和偏置是 随机初始化 的。
• 随机意味着一开始模型的预测几乎是“瞎猜”,没有实际规律。
3. 前向传播 = 函数计算
• 输入 \(x\) 经过网络层层计算(线性变换 + 激活函数),得到预测值 \(\hat{y}\)(即 y_pred)。
• 数学类比:像是把原始输入放进“函数工厂”,得到一个输出。
4. 损失函数 = 误差度量
• 将预测值 \(\hat{y}\) 与真实值 \(y\) 对比,得到误差(loss)。
• 这个误差告诉我们:当前这组函数参数 \(\theta\) 表现好不好。
5. 反向传播 + 参数更新 = 学习过程
• 通过反向传播算法,计算损失对每个参数的梯度。
• 然后用梯度下降调整参数:

\[\theta \leftarrow \theta - \eta \cdot \frac{\partial L}{\partial \theta} \]

•	直观类比:在“山谷”中顺着斜坡往下走,直到找到最低点。
6.	收敛后的参数 = 最优函数关系
•	训练过程中,权重和偏置不断更新,最终收敛到一个较优解。
•	这组参数对应的就是一个能 泛化数据规律 的函数。

🌱 一句话总结

机器学习的核心就是:用数据来“调教”函数的参数,让这个函数能尽可能准确地映射输入与输出的关系。

换个直白的类比:
• 模型(MLP)像是一台“函数机器”,
• 权重和偏置是机器的“旋钮”,
• 损失函数是“性能检测仪”,
• 梯度下降是“调节旋钮的方法”。
• 训练完成后,这台机器就能根据新输入给出合理预测。

posted @ 2025-10-06 10:37  方子敬  阅读(7)  评论(0)    收藏  举报