神经网络构建基础
虽然现在有了AI生成工具,但是我们是研究者,还是需要了解一个基本的神经网络是如何构建的。
接下来以pytorch框架的一个简单网络为例,来说明神经网络的构建过程。
整体结构预览
一般来说,一个神经网络的代码结构大致如下所示(以”从零到可训练”为主线,把数据、模型、训练、评估、配置拆开):
1 | project/ |
上面的结构并非唯一,但它体现了研究代码里常见的分层:数据、模型、训练、评估、配置与复现。
从一个最小可训练例子开始
为了让”构建神经网络”这件事更具体,我们先用 MNIST 的一个 MLP(多层感知机)跑通训练与评估。这个例子足够简单,但覆盖了工程里最核心的模块。
任务设定:分类问题
MNIST 是手写数字分类数据集,输入是一张 $28\times 28$ 的灰度图(可视为向量 $\mathbf{x}\in \mathbb{R}^{784}$),输出类别 $y\in{0,\dots,9}$。
我们的目标是学习一个函数
$$
f_\theta:\mathbb{R}^{784}\rightarrow \mathbb{R}^{10},
$$
输出 10 类的 logits(未归一化分数),再用 softmax 得到概率:
$$
p_\theta(y=k\mid \mathbf{x})=\frac{\exp(f_\theta(\mathbf{x})k)}{\sum{j=0}^{9}\exp(f_\theta(\mathbf{x})_j)}.
$$
损失函数:交叉熵
对单样本 $(\mathbf{x},y)$,分类常用交叉熵损失:
$$
\mathcal{L}(\theta;\mathbf{x},y)=-\log p_\theta(y\mid \mathbf{x}).
$$
对一个 batch ${(\mathbf{x}i,y_i)}{i=1}^{B}$,取平均:
$$
\mathcal{L}{\text{batch}}(\theta)=\frac{1}{B}\sum{i=1}^{B}-\log p_\theta(y_i\mid \mathbf{x}_i).
$$
模块 1:数据(Dataset & DataLoader)
为什么要拆出数据模块?
数据模块负责:
- 读取与预处理:标准化、数据增强、padding/裁剪等;
- 批处理:构造 mini-batch,支持 shuffle;
- 复现性:随机种子与 worker 初始化。
例子:MNIST 的输入输出形状
以 MNIST 为例,DataLoader 每次返回一个 batch:
$$
\mathbf{X}\in \mathbb{R}^{B\times 1\times 28\times 28},\qquad
\mathbf{y}\in {0,\dots,9}^{B}.
$$
如果我们使用 MLP,需要把图像摊平成向量:
$$
\text{flatten}(\mathbf{X})\rightarrow \mathbb{R}^{B\times 784}.
$$
常见坑:dtype 与归一化
- 图像输入通常是
float32;标签是int64(分类损失常要求 long)。 - 归一化:像素 $\in[0,1]$,可再做标准化 $\frac{x-\mu}{\sigma}$。
模块 2:模型(nn.Module 的最小闭环)
一个最简 MLP
MLP 可以写成两层线性变换加非线性:
$$
\mathbf{h}=\phi(\mathbf{W}_1\mathbf{x}+\mathbf{b}_1),\qquad
\mathbf{z}=\mathbf{W}_2\mathbf{h}+\mathbf{b}_2,
$$
其中 $\phi$ 可以选 ReLU:$\phi(t)=\max(0,t)$。
设隐藏层维度为 $d$,则参数维度为:
$$
\mathbf{W}_1\in\mathbb{R}^{d\times 784},\ \mathbf{b}_1\in\mathbb{R}^{d},\
\mathbf{W}_2\in\mathbb{R}^{10\times d},\ \mathbf{b}_2\in\mathbb{R}^{10}.
$$
例子:形状流动(shape flow)
假设 batch size $B=64$,隐藏维度 $d=256$:
$$
\mathbf{X}\in\mathbb{R}^{64\times 1\times 28\times 28}
\rightarrow
\mathbb{R}^{64\times 784}
\rightarrow
\mathbb{R}^{64\times 256}
\rightarrow
\mathbb{R}^{64\times 10}.
$$
这一步”形状检查”是排错最有效的方法之一:一旦 shape 不对,后续损失与梯度都不可信。
加入一个正则化例子:Dropout
训练时 Dropout 对隐藏层随机置零:
$$
\tilde{\mathbf{h}}=\mathbf{m}\odot \mathbf{h},\qquad m_i\sim \text{Bernoulli}(1-p),
$$
推理时不再随机置零(或使用等效缩放)。它的直觉是减少 co-adaptation,提高泛化。
模块 3:训练循环(Training Loop)
训练循环的四个基本动作
一次迭代(一个 batch)通常包含:
- 前向传播:$\mathbf{z}=f_\theta(\mathbf{x})$;
- 计算损失:$\mathcal{L}(\theta)$;
- 反向传播:$\nabla_\theta \mathcal{L}$;
- 参数更新:$\theta\leftarrow \theta-\eta \nabla_\theta \mathcal{L}$(以 SGD 为例)。
例子:学习率对收敛的影响(直观版)
假设在某一步梯度范数较大 $|\nabla_\theta \mathcal{L}|$:
- 学习率 $\eta$ 太大:更新步长过大,可能在最优点附近”来回震荡”甚至发散;
- 学习率 $\eta$ 太小:每步更新很微弱,训练极慢,可能停在次优区域很久。
一个实用做法:先用较大学习率短跑(warmup / 试探),观察 loss 曲线是否单调下降,再微调。
例子:梯度爆炸与梯度裁剪
在某些任务(如 RNN 或非常深的网络)可能出现梯度爆炸:
$$
|\nabla_\theta \mathcal{L}|\rightarrow \infty.
$$
常用的梯度裁剪(按范数)是:
$$
\nabla_\theta \mathcal{L}\leftarrow \nabla_\theta \mathcal{L}\cdot
\min\left(1,\frac{c}{|\nabla_\theta \mathcal{L}|}\right),
$$
其中 $c$ 是阈值。这在训练不稳定时很常见、也很有效。
例子:训练/验证模式切换的重要性
很多层在训练与评估时行为不同:
- Dropout:训练时随机置零,评估时关闭;
- BatchNorm:训练时用 batch 统计量,评估时用滑动平均。
因此必须区分:
$$
\text{train mode}\quad vs\quad \text{eval mode}.
$$
模块 4:评估与指标(Metrics)
例子:分类准确率
最常见指标是 accuracy:
$$
\text{acc}=\frac{1}{N}\sum_{i=1}^{N}\mathbb{I}\left(\arg\max_k z_{i,k}=y_i\right).
$$
例子:混淆矩阵能告诉你什么?
如果模型总把”9”预测成”4”,accuracy 可能还行,但错误结构会很明显。混淆矩阵 $C\in\mathbb{R}^{10\times 10}$:
$$
C_{a,b}=#{i: y_i=a,\ \hat{y}_i=b}.
$$
它能帮助你定位:哪些类别之间最容易混淆、是否数据偏斜、是否需要更强的特征提取网络。
模块 5:保存、加载与复现
例子:为什么要保存 best checkpoint?
训练 loss 下降不代表泛化一定更好;验证集指标通常先升后降(过拟合)。因此常见策略:
- 保存 last checkpoint:方便中断后继续训练;
- 保存 best checkpoint:以验证集最优指标为准,用于最终测试/部署。
例子:复现性最容易忽略的点
即使固定随机种子,不同硬件/并行策略也可能导致差异。实践中建议记录:
- 代码版本(git commit);
- 超参数(lr, batch size, weight decay, scheduler);
- 环境信息(CUDA/cuDNN/PyTorch 版本);
- 数据划分方式与随机种子。
扩展练习:把 MLP 换成一个小 CNN
如果想让例子更贴近”神经网络真正擅长的视觉归纳偏置”,可以把 MLP 换成 CNN:
$$
\mathbf{X}\in\mathbb{R}^{B\times 1\times 28\times 28}
\rightarrow
\text{Conv} \rightarrow \text{ReLU} \rightarrow \text{Pool}
\rightarrow
\text{Conv} \rightarrow \text{ReLU} \rightarrow \text{Pool}
\rightarrow
\text{Flatten} \rightarrow \text{Linear} \rightarrow \mathbb{R}^{B\times 10}.
$$
思考题:
- 为什么 CNN 通常比 MLP 在图像上更强?
- 卷积核大小、通道数、池化会如何影响表达能力与计算量?
小结
一个”可训练”的神经网络工程最少需要:数据管道、模型定义、训练循环、评估指标、保存与复现。
建议先用最小例子(MNIST MLP)跑通闭环,再逐步替换模块(CNN、优化器、调度器、增强策略),这样改动可控、结果可解释。
