引言
深度学习是机器学习最耀眼的分支,而神经网络是它的地基。很多人畏惧神经网络,是因为被「反向传播」「梯度消失」这些术语吓住。但用现代框架(PyTorch/TensorFlow),训练一个网络的工程步骤只有四步:定义模型、定义损失、定义优化器、跑训练循环。剩下的是理解「它在学什么」。
本文从神经元讲起,带你用 PyTorch 从零到一构建、训练、评估一个真正能识别手写数字的网络。你不必先懂微积分——你只需要理解四个直觉:张量是数据容器、自动求导帮我们算梯度、损失衡量好坏、优化器让损失变小。跑通之后,通往 CNN/Transformer 的大门就打开了。
前置:[[ml]] 专题的评估方法(https://plumephp.com/ml-model-evaluation/)。深入原理见 [[ai-ml]] 专题的神经网络文章。
目录
- 1. 神经元与感知机:神经网络的最小单元
- 2. 张量:PyTorch 的数据容器
- 3. 自动求导:梯度从哪来
- 4. 网络结构:从输入到输出
- 5. 损失函数与优化器:训练的两个旋钮
- 6. 训练循环:epoch、batch 与反向传播
- 7. 完整实战:MNIST 手写数字识别
- 8. 评估与改进:从入门网络走向实战
- 9. 总结:神经网络的四步心法
- 延伸阅读
1. 神经元与感知机:神经网络的最小单元
1.1 单个神经元
输入 x1,x2,x3 → 加权求和 Σ(w·x + b) → 激活函数 σ → 输出
- 权重 w:每个输入的重要性。
- 偏置 b:神经元的「倾向性」。
- 激活函数 σ:加入非线性(否则多层线性叠加仍是线性)。
1.2 常见激活函数
| 函数 | 公式 | 直觉 |
|---|---|---|
| ReLU | max(0, x) | 最常用,快 |
| Sigmoid | 1/(1+e⁻ˣ) | 输出 0~1,二分类输出层 |
| Softmax | 归一化指数 | 多分类输出层,和为1 |
1.3 多层 = 逐层抽象
第一层看到原始像素,中间层学到「边缘/形状」,高层学到「眼睛/嘴巴」。层数越多,抽象层次越高(也越难训)。
2. 张量:PyTorch 的数据容器
2.1 安装与导入
pip install torch torchvision
2.2 张量基础
import torch
# 标量、向量、矩阵、高维
s = torch.tensor(3.0) # 0维
v = torch.tensor([1., 2., 3.]) # 1维
m = torch.zeros(3, 4) # 2维 (3行4列)
print(v.shape) # torch.Size([3])
print(m.shape) # torch.Size([3, 4])
print(v.dtype) # torch.float32
2.3 与 NumPy 互转
import numpy as np
arr = np.random.randn(2, 3)
t = torch.from_numpy(arr) # NumPy → Tensor
back = t.numpy() # Tensor → NumPy
神经网络的数据都装进张量,形状(shape)是你的好朋友——随时
print(x.shape)确认维度没错。
3. 自动求导:梯度从哪来
3.1 训练需要梯度
训练时我们要算「损失对每个权重 w 的偏导」,才能知道往哪调。手写链式法则很痛苦——PyTorch 的 自动求导 替你算。
3.2 一个小实验
x = torch.tensor(3.0, requires_grad=True)
y = x**2 + 2*x + 1
y.backward() # 自动算 dy/dx
print(x.grad) # 2*x + 2 = 8.0
3.3 直觉
loss.backward() → 每个参数的 .grad 被填上梯度
optimizer.step() → 参数按梯度更新一步
optimizer.zero_grad() → 清空上次梯度(否则会累加)
4. 网络结构:从输入到输出
4.1 用 nn.Sequential 搭一个全连接网络
MNIST 图片是 28×28=784 像素,输出 10 类:
import torch.nn as nn
model = nn.Sequential(
nn.Linear(784, 128), # 784 → 128
nn.ReLU(),
nn.Linear(128, 64), # 128 → 64
nn.ReLU(),
nn.Linear(64, 10), # 64 → 10 类
)
print(model)
4.2 前向传播
x = torch.randn(32, 784) # 一批 32 张图,每张 784 像素
logits = model(x) # 输出 (32, 10),10 个类的得分
print(logits.shape)
4.3 nn.Linear 内部
nn.Linear(in, out) 内含权重矩阵 W 与偏置 b,out = x @ W.T + b。训练就是学这两个参数。
5. 损失函数与优化器:训练的两个旋钮
5.1 损失函数:衡量错多少
| 任务 | 损失 | 直觉 |
|---|---|---|
| 回归 | MSELoss | 预测与真值的平方差 |
| 分类 | CrossEntropyLoss | 正确类的得分越高越好 |
import torch.nn as nn
criterion = nn.CrossEntropyLoss()
5.2 优化器:让损失变小
import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=0.01) # 梯度下降
# 或更常用的
optimizer = optim.Adam(model.parameters(), lr=0.001)
- 学习率 lr:每步走多大。太大震荡不收敛,太小龟速。
- Adam:自适应学习率,默认起点优选。
6. 训练循环:epoch、batch 与反向传播
6.1 三要素
| 概念 | 含义 |
|---|---|
| epoch | 遍历全部数据一遍 |
| batch | 每批样本数(一次前向+反向) |
| iteration | 一个 batch 的训练步骤 |
6.2 最小训练循环
for epoch in range(10):
running_loss = 0.0
for images, labels in dataloader:
optimizer.zero_grad() # 1. 清梯度
outputs = model(images) # 2. 前向
loss = criterion(outputs, labels) # 3. 算损失
loss.backward() # 4. 反向:算梯度
optimizer.step() # 5. 更新参数
running_loss += loss.item()
print(f"epoch {epoch+1} loss={running_loss/len(dataloader):.4f}")
6.3 这个循环就是深度学习的一切
从全连接到 Transformer,训练循环骨架完全一样。区别只在模型结构与数据形式。
7. 完整实战:MNIST 手写数字识别
7.1 数据加载
import torch
import torchvision
import torchvision.transforms as T
from torch.utils.data import DataLoader
transform = T.Compose([T.ToTensor(), T.Normalize((0.5,), (0.5,))])
train_set = torchvision.datasets.MNIST(
root='./data', train=True, download=True, transform=transform)
test_set = torchvision.datasets.MNIST(
root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)
7.2 定义网络
import torch.nn as nn
model = nn.Sequential(
nn.Flatten(), # 28×28 → 784
nn.Linear(784, 128),
nn.ReLU(),
nn.Linear(128, 10),
)
7.3 训练 + 评估
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(5):
model.train() # 训练模式
for images, labels in train_loader:
optimizer.zero_grad()
loss = criterion(model(images), labels)
loss.backward()
optimizer.step()
# 每轮在测试集上评估
model.eval() # 评估模式(关闭 Dropout 等)
correct = total = 0
with torch.no_grad(): # 评估不用算梯度,省内存
for images, labels in test_loader:
preds = model(images).argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
acc = correct / total
print(f"epoch {epoch+1} 测试准确率={acc:.4f}")
输出示例(CPU 训练):
epoch 1 测试准确率=0.9479
epoch 2 测试准确率=0.9614
epoch 3 测试准确率=0.9675
epoch 4 测试准确率=0.9711
epoch 5 测试准确率=0.9721
一个 5 分钟训出来的全连接网络,MNIST 就能到 ~97% 准确率。这个「四步心法」就是深度学习的入场券。
8. 评估与改进:从入门网络走向实战
8.1 看错在哪里
# 收集几个预测错的样本,画出来看看
import matplotlib.pyplot as plt
wrong = 0
for images, labels in test_loader:
preds = model(images).argmax(dim=1)
for i in range(len(images)):
if preds[i] != labels[i] and wrong < 5:
plt.imshow(images[i][0], cmap='gray')
plt.title(f"真值={labels[i].item()} 预测={preds[i].item()}")
plt.show()
wrong += 1
8.2 通往更高精度的路径
| 手段 | 效果 | 复杂度 |
|---|---|---|
| 加深加宽网络 | 中 | 低 |
| 换 CNN(卷积) | 大幅提升 | 中 |
| 数据增强(旋转/平移) | 中 | 低 |
| 调超参(lr/batch/epoch) | 中 | 低 |
| 预训练 + 微调 | 高 | 中 |
8.3 用 CNN 快速试一下
cnn = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(32*7*7, 128), nn.ReLU(),
nn.Linear(128, 10),
)
卷积能利用像素的局部空间结构,是图像任务的基本盘。
9. 总结:神经网络的四步心法
9.1 记住这四个步骤
1. 定义模型 nn.Sequential / nn.Module
2. 定义损失 CrossEntropyLoss / MSELoss
3. 定义优化器 Adam / SGD
4. 训练循环 forward → loss → backward → step
9.2 常见坑速查
| 症状 | 原因 | 解法 |
|---|---|---|
| 损失不降 | lr 太小 | 调大 lr |
| 损失 NaN | lr 太大 / 输入未归一化 | 调小 lr / Normalize |
| 维度报错 | shape 不匹配 | print(x.shape) 排查 |
| 评估要开 no_grad | 省内存 | with torch.no_grad() |
| 忘 zero_grad | 梯度累加 | 每步 optimizer.zero_grad() |
9.3 一句话心法
神经网络不是魔法,是「张量 + 自动求导 + 优化器」的工程组装。跑通最小闭环后,一切模型都是往这个骨架上加结构。
延伸阅读
- https://plumephp.com/ml-model-evaluation/ — 评估方法论(训练/验证/测试的纪律)
- https://plumephp.com/ml-python-environment-setup/ — GPU/环境配置
- [[ai-ml]] 专题的神经网络、CNN、RNN/Transformer 深度文章
- PyTorch 官方教程
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。