My Blog
Agent 学习

Agent 学习第 3 周:深度学习常用组件

Adam、Embedding、CNN、RNN/LSTM、池化、归一化、Dropout 逐个跑一遍,看清每个组件的输入输出形状。

发布于 2026/9/11更新于 2026/9/11阅读约 13 分钟
只问这篇文章 →

第三周是配工具箱——把深度学习里最常用的那几个组件挨个过一遍:Adam、Embedding、CNN、RNN/LSTM、池化、归一化、Dropout,最后串成一个完整模型。

这周写代码最多,六个脚本 + 一个作业。每个组件单独跑一遍看输出形状,比看图理解快得多。


一、Adam:为什么不用 SGD 了

先说 SGD 的四个毛病:

  • 学习率难调(全局固定,大则震荡小则慢)
  • 各参数同等对待,忽略梯度尺度差异
  • 容易在鞍点/平坦区停滞
  • 无动量导致方向抖动

Adam 用三个机制解决:

机制作用
一阶矩(动量)累积梯度方向,平滑震荡
二阶矩(平方)按参数梯度量级自动缩放学习率
偏差修正消除训练初期动量被低估的问题
θ ← θ − η · m̂ / (√v̂ + ε)

第二条是关键——不同参数拿到的实际学习率是不一样的。梯度一直很大的参数,步子自动变小;梯度很小的参数,步子自动变大。SGD 做不到这件事。


二、Embedding:把词变成向量

本质就是一张查找表:

E ∈ ℝ^(V×d)     V = 词表大小,d = 向量维度(通常 64~512)
输入词 id  →  输出对应的 d 维稠密向量

形状变化是这样的:

[batch, seq_len]  →  Embedding  →  [batch, seq_len, embed_dim]

初始化怎么选:

  • 数据量充足 → 随机初始化,让模型自学
  • 数据量少 → 用预训练词向量(如 Word2Vec),加速收敛、迁移泛化

三、CNN:局部感受野 + 权重共享

两个核心思想:

  • 局部感受野:每次只看一个小窗口,不看全局
  • 权重共享:同一个卷积核在整个序列上滑动,参数量不随输入长度增加

文本用 Conv1d(核在序列长度这一维滑),图像用 Conv2d(核在 H×W 二维滑)。

输出长度公式:

L_out = ⌊(L_in + 2p − k) / s⌋ + 1

out_channels 是卷积核数量,每个核学一种局部模式——比如某个核专门识别「不太好」这种否定搭配。


四、RNN / LSTM:处理长距离依赖

为什么需要 RNN

CNN 的卷积核大小固定,只能看 k 个相邻 token,处理不了长距离依赖。

RNN 的思路是让隐状态当「记忆」,一路往下传:

hₜ = tanh(W_h·hₜ₋₁ + W_x·xₜ + b)

简单 RNN 的致命问题

梯度消失。 序列很长时,早期信息经过多次 tanh 传递后接近 0,模型「忘记」了很早之前的内容。

LSTM 的三个门

门控制什么
遗忘门丢弃多少旧信息
输入门写入多少新信息
输出门读出多少作为隐状态

关键在于 细胞状态 cₜ 是直接传播的——它的梯度通道比隐状态顺畅得多,所以能支持更长的序列。这是 LSTM 相比 RNN 的本质改进,不是「门多了几个」那么简单。

双向 LSTM 则是从左到右、从右到左各跑一遍再拼接,让每个位置同时看到前后文。


五、池化:降维 + 平移不变性

两大作用:

  • 下采样降维:压缩长序列,减少计算量,保留最重要的特征
  • 平移不变性:关键特征不管出现在哪个位置都能被捕捉到

两种方式:MaxPool 取窗口最大值(保留最显著特征)、AvgPool 取平均(反映整体分布)。

最实用的是全局最大池化——不限定窗口,直接对整个序列维度取最大值:

[batch, seq_len, hidden]  →  全局最大池化  →  [batch, hidden]

它把变长序列压成了固定维度向量,后面才能接全连接层。这一步是文本分类模型的标准操作。


六、归一化:BatchNorm vs LayerNorm

要解决的问题

内部协变量偏移——每次参数更新后,各层的输入分布都在漂移,导致训练不稳定、只能用很小的学习率。

归一化把每层输入拉回均值 0、方差 1 附近,加速收敛 + 允许更大学习率。

两者的区别

这个我一开始老记反,后来找到个简单办法:

BatchNorm  按「列」统计  同一特征跨样本   → 图像常用
LayerNorm  按「行」统计  同一样本跨特征   → 文本 / Transformer / BERT 标准选择

NLP 用 LayerNorm 的原因是:文本的 batch 内样本长度不一,按 batch 方向统计不稳定;而按样本自己的特征维度统计就没这个问题。


七、Dropout:训练时随机「装死」

训练时随机让一部分神经元输出置 0,强迫网络不能依赖某几个特定神经元,学习更分散、更鲁棒的特征。

正则化的解释是:相当于同时训练了 2ⁿ 个子网络的集成,防止神经元「共同适应」。

训练和推理的行为不一样,这点很关键:

  • 训练时:按概率 p 屏蔽部分节点,并放大补偿其余节点
  • 推理时(model.eval()):全部节点激活,不做屏蔽

忘记调用 model.eval() 是新手最常见的 bug 之一——推理结果会莫名其妙地不稳定,因为 Dropout 还在随机丢神经元。

常见摆放与取值:

p = 0.1~0.2   放 Embedding 层后
p = 0.3~0.5   放全连接层前(最常用最有效)
p > 0.5       一般不用,信息损失太多

八、串起来:一个完整模型

课程最后把所有组件串成一条完整链路:

Embedding      →  词转向量
    ↓
RNN            →  捕捉序列信息
    ↓
池化            →  压缩为定长向量
    ↓
LayerNorm      →  归一化
    ↓
Dropout        →  防过拟合
    ↓
Linear         →  维度对齐到类别数
    ↓
Sigmoid        →  输出
    ↓
Adam 优化器

这周的作业(week3/task/文本位置多分类任务.py)就是用这套结构做一个「判断特定字符出现在第几个位置」的多分类任务。


这周的收获

1. 每个组件都在解决一个具体的问题。

这周最大的收获是想清楚了「为什么需要它」这条线:

CNN 看不了长距离      →  RNN 用隐状态传递记忆
RNN 梯度消失          →  LSTM 用细胞状态开一条梯度通道
序列变长              →  池化压成定长
训练不稳定            →  归一化
过拟合                →  Dropout
学习率难调            →  Adam

不是「深度学习有这些组件」,而是「遇到了这些问题,所以有了这些组件」。 按后者理解,记忆负担小很多。

2. 形状变化比公式更值得盯。

我这周每写一个组件都会 print 一遍输入输出的 shape。[batch, seq_len] → [batch, seq_len, embed] → [batch, hidden] → [batch, num_classes] 这条链路走通了,模型结构就不再是抽象的方块图。

3. 训练态和推理态是两套行为。

Dropout 和 BatchNorm 在 train() 和 eval() 下行为不同。这是从「写业务代码」转到「写模型代码」时最容易忽略的一点——同一段代码在两种模式下跑出来的结果本来就该不一样。

下周进入语言模型,从 N-gram 一路讲到 BERT 和 Transformer。

相关文章