第二周从数学基础一路讲到反向传播。这是整个课程里公式最密集的一周,但也是最该硬啃的一周——后面所有内容都建立在这套东西上。
我这周写了四个小脚本(均方误差、交叉熵、全连接层、训练过程),最后做了一个多分类任务的作业。手敲一遍公式再看代码,比只看 PPT 强太多。
一、够用就行的数学
课上讲得很克制,只讲深度学习真正用得上的那几个:
| 概念 | 一句话 |
|---|---|
| 向量 | 有序数值列表,既有大小又有方向 |
| 内积 | `a·b = |
| 矩阵乘法 | (m×n)×(n×p)=(m×p),左矩阵列数必须等于右矩阵行数 |
| 导数 | 某点的斜率/瞬时变化率 |
| 链式法则 | dy/dx = dy/du · du/dx |
| 梯度 | 所有偏导数组合成的向量 |
内积那条后面会反复用到——余弦相似度、Attention 的 QK^T,本质都是内积。cosθ=1 是同向,0 是垂直无关,-1 是反向。
链式法则则是反向传播的全部数学基础,就这一个公式。
二、「学习」到底是什么
这一节把整件事说透了。
从找极小值说起
先看一个纯数学问题:怎么找函数的极小值?
任取一点 → 算导数 → 根据正负决定调大还是调小 → 迭代直到不再变化
然后做一个替换:
- 把
x换成模型权重 W、b - 把
f(x)换成损失函数
这就是深度学习的训练过程。 所谓「学习」,就是不断调整参数 W,让预测尽量接近真实答案。
损失函数:两种就够了
回归 → 均方误差 MSE = (ŷ - y)²
分类 → 交叉熵 = -Σ yᵢ·log(ŷᵢ)
我写 上午学习-均方误差.py 和 下午学习-交叉熵损失.py 就是把这两个手算了一遍,再和 PyTorch 的实现对比,确认数值一致。
这里有个后面会反复踩的坑:PyTorch 的
CrossEntropyLoss内部自带 softmax。如果你在模型的 forward 里已经做了 softmax,再送进这个损失函数就等于做了两次,训练会很奇怪但不报错。
梯度下降
W ← W − η·∇L(W)
沿负梯度方向更新参数。学习率 η 是这里唯一的超参数,但影响巨大:
- 太大 → 震荡甚至发散
- 太小 → 收敛极慢
- 合适 → 平稳收敛
反向传播
神经网络本质是多层复合函数,所以用链式法则从损失开始逐层往回算梯度。
前向传播:算预测和损失
反向传播:算梯度
更新参数:梯度下降
一句话总结:深度学习训练 = 梯度下降 + 反向传播。每次迭代就是这三步循环。
三、神经网络的基本构件
全连接层
y = Wx + b
多个全连接层堆叠就是多层感知机(MLP),层数越深表达能力越强。
我在 下午学习-全连接层.py 里手写了一个,和 nn.Linear 对比输出,主要是确认自己理解了矩阵的形状怎么变。
激活函数:为什么必须有
这个点我一开始没在意,后来发现很关键:
如果没有激活函数,多层线性叠加还是线性。
W₂(W₁x + b₁) + b₂ 展开之后仍然是 Wx + b 的形式,堆一百层和一层没区别。激活函数引入非线性,深度才有意义。
四个常用的:
| 函数 | 值域 | 用途 |
|---|---|---|
ReLU max(0,x) | [0, +∞) | 最常用,隐藏层默认 |
| Sigmoid | (0,1) | 二分类输出 |
| Tanh | (-1,1) | 零中心 |
| Softmax | 概率分布 | 多分类输出 |
优化器的三种变体
| 用多少数据 | 特点 | |
|---|---|---|
| BGD 批量梯度下降 | 全部 | 稳但慢 |
| SGD 随机梯度下降 | 单样本 | 快但噪声大 |
| Mini-batch SGD | 一小批 | 折中,最常用(批大小通常 32/64/128) |
学习率调度
固定学习率不总是最优的。三种常见调度:
- 阶梯衰减:每隔固定轮次衰减
- 余弦退火:按曲线平滑衰减
- Warmup:初期从小学习率线性增大,避免开局梯度爆炸
直觉是先大步探索,后期小步精调。
四、动手:多分类任务
这周的作业是写一个多分类任务(week2/task/多分类任务.py)。
结构是课上给的标准模板,后面几周的作业都沿用了这套骨架:
TorchModel(nn.Module) 模型定义
build_sample 造单条样本
build_dataset 批量造数据
evaluate 每轮评估
main 训练主循环
predict 加载模型做预测
有个设计我觉得挺巧妙,是 forward 的双模式:
def forward(self, x, y=None):
# 传了 y → 训练模式,返回 loss
# 没传 y → 推理模式,返回预测结果一个方法同时服务训练和推理,调用方不用关心内部差异。
训练完要看的两件事
- 保存权重:
torch.save(model.state_dict(), "model.bin") - 画曲线:用 matplotlib 画 acc / loss 随轮次的变化
第二件事很重要。光看最终数字看不出训练是否健康——loss 是平稳下降,还是震荡,还是早就不动了,只有画出来才知道。
这周的收获
这周内容偏理论,但有三个点让我觉得值得反复看:
-
「学习」就是求损失函数的极小值。 把这句话想明白,梯度下降、学习率、优化器这些概念就都串起来了,不再是孤立的名词。
-
激活函数的必要性。 没有它,深度就是假的。这个点在纸上推一遍展开式比任何解释都有说服力。
-
手写公式再看框架实现。 我这周四个脚本都是「先自己算一遍,再和 PyTorch 对比」。这个过程发现了好几处我以为懂了其实没懂的地方——比如交叉熵里 log 的底、以及为什么要取负号。
作为后端转过来的,最不适应的是调试方式变了。以前程序错了有栈追踪,现在是「loss 不降」,可能是学习率、可能是数据、可能是模型太小、也可能是哪里写反了符号。这周开始养成画曲线的习惯,就是为了让问题「看得见」。
下周讲深度学习的常用组件——Embedding、CNN、RNN、LSTM、池化、归一化、Dropout,一次性把工具箱配齐。