深度学习核心机制
本文覆盖深度学习基础核心机制,以面试题 Q&A 格式呈现,难度标记:⭐基础 ⭐⭐进阶 ⭐⭐⭐高级
一、神经网络基础
Q1:什么是感知机(Perceptron)?它和现代神经网络有什么关系?⭐
A: 感知机是 Frank Rosenblatt 于 1958 年提出的最简单的神经网络模型,是现代深度学习的基石。
感知机的数学定义:
$$ y = f\left(\sum_{i=1}^{n} w_i x_i + b\right) = f(\mathbf{w}^T \mathbf{x} + b) $$
其中 $f$ 是阶跃函数(Heaviside step function):
$$ f(z) = \begin{cases} 1 & z \geq 0 \ 0 & z < 0 \end{cases} $$
感知机的局限性:
- 只能解决线性可分问题
- 无法解决 XOR 问题(Minsky & Papert, 1969 年证明)
- 阶跃函数不可导,无法使用梯度下降
从感知机到现代神经网络的演进:
| 特征 | 感知机 | 现代神经网络 |
|---|---|---|
| 层数 | 单层 | 多层(深度) |
| 激活函数 | 阶跃函数 | 可微函数(ReLU等) |
| 学习算法 | 感知机学习规则 | 反向传播+梯度下降 |
| 能力 | 线性分类 | 逼近任意函数 |
关键突破: 1986 年 Rumelhart 等人提出反向传播算法,使得训练多层网络成为可能,解决了 XOR 等非线性问题。
Q2:什么是多层感知机(MLP)?它的结构是怎样的?⭐
A: 多层感知机(Multi-Layer Perceptron, MLP)是最基本的深度学习架构,由输入层、隐藏层和输出层组成。
MLP 的数学表达:
对于一个 L 层的 MLP,第 $l$ 层的计算为:
$$ \mathbf{z}^{(l)} = \mathbf{W}^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} $$
$$ \mathbf{a}^{(l)} = f^{(l)}(\mathbf{z}^{(l)}) $$
其中:
- $\mathbf{a}^{(0)} = \mathbf{x}$ 是输入
- $\mathbf{W}^{(l)}$ 是第 $l$ 层的权重矩阵,形状为 $(n_l, n_{l-1})$
- $\mathbf{b}^{(l)}$ 是偏置向量,形状为 $(n_l, 1)$
- $f^{(l)}$ 是第 $l$ 层的激活函数
完整前向传播过程(3层示例):
输入 x → [线性变换 W1*x + b1] → [激活函数 σ] → h1
→ [线性变换 W2*h1 + b2] → [激活函数 σ] → h2
→ [线性变换 W3*h2 + b3] → [softmax] → 输出 y参数量计算:
$$ \text{参数量} = \sum_{l=1}^{L} (n_l \times n_{l-1} + n_l) $$
例如:输入 784 → 隐藏层 256 → 隐藏层 128 → 输出 10
$$ (784 \times 256 + 256) + (256 \times 128 + 128) + (128 \times 10 + 10) = 235,146 $$
Q3:万能近似定理(Universal Approximation Theorem)说了什么?有什么实际意义?⭐⭐
A: 万能近似定理是深度学习理论的重要基石。
定理陈述(Cybenko 1989, Hornik 1991):
一个包含有限个神经元的单隐藏层前馈神经网络,使用挤压性质的激活函数(如 sigmoid),可以以任意精度逼近 $\mathbb{R}^n$ 上任意连续函数在紧集上的值。
数学表述:
对于任意连续函数 $f: [0,1]^n \to \mathbb{R}$ 和任意 $\epsilon > 0$,存在一个单隐藏层网络 $g$,使得:
$$ \sup_{x \in [0,1]^n} |f(x) - g(x)| < \epsilon $$
关键要点:
- 存在性定理:只说明"存在"这样的网络,不说明如何找到
- 宽度 vs 深度:定理说的是宽度(神经元数量),实际中深度网络更高效
- 不保证泛化:能逼近训练集不代表泛化能力好
- 不保证可训练:存在不等于能通过梯度下降找到
实际意义:
- 理论上证明了神经网络的强大表达能力
- 但实践中,深度网络比宽网络更高效(参数量更少)
- 这就是为什么现代深度学习强调"深度"而非"宽度"
深度 vs 宽度的效率对比:
某些函数:
- 深度网络:O(n) 个参数即可表示
- 宽度网络:需要 O(2^n) 个参数
例如:计算 x₁ AND x₂ AND ... AND xₙ
- 深度网络:O(n) 参数,O(log n) 层
- 单隐藏层:需要 2^n 个神经元Q4:前向传播(Forward Propagation)的完整过程是什么?⭐
A: 前向传播是神经网络从输入到输出的计算过程,是反向传播的基础。
完整前向传播流程:
# 伪代码
def forward(x, params):
# 第1层
z1 = W1 @ x + b1
a1 = relu(z1)
# 第2层
z2 = W2 @ a1 + b2
a2 = relu(z2)
# 输出层
z3 = W3 @ a2 + b3
y_hat = softmax(z3)
return y_hat矩阵维度追踪(Batch Size = B):
输入: x ∈ R^(B × d_in)
Layer 1: z1 = x @ W1^T + b1, z1 ∈ R^(B × h1)
a1 = relu(z1), a1 ∈ R^(B × h1)
Layer 2: z2 = a1 @ W2^T + b2, z2 ∈ R^(B × h2)
a2 = relu(z2), a2 ∈ R^(B × h2)
Output: z3 = a2 @ W3^T + b3, z3 ∈ R^(B × d_out)
y = softmax(z3), y ∈ R^(B × d_out)关键概念——计算图:
前向传播构建了一个有向无环图(DAG),每个节点保存了:
- 该节点的输出值
- 该节点的梯度函数(用于反向传播)
# PyTorch 中的计算图构建
x = torch.randn(3, requires_grad=True)
h = x @ W1 + b1 # 计算图节点
a = torch.relu(h) # 计算图节点
y = a @ W2 + b2 # 计算图节点
loss = criterion(y, target)
# 此时计算图已构建完毕
loss.backward() # 沿计算图反向传播Q5:激活函数是如何演进的?从 Sigmoid 到 SwiGLU 的发展历程是什么?⭐⭐
A: 激活函数的演进是深度学习发展的核心线索之一。
1. Sigmoid 函数
$$ \sigma(z) = \frac{1}{1 + e^{-z}}, \quad \sigma'(z) = \sigma(z)(1 - \sigma(z)) $$
- 输出范围:(0, 1)
- 导数最大值:0.25(在 z=0 处)
- 问题:梯度消失(多层相乘后梯度趋近于零)、非零中心化、指数计算慢
2. Tanh 函数
$$ \tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}}, \quad \tanh'(z) = 1 - \tanh^2(z) $$
- 输出范围:(-1, 1)
- 零中心化
- 导数最大值:1(在 z=0 处)
- 问题:仍有梯度消失问题(饱和区导数趋近于0)
3. ReLU 函数(革命性突破)
$$ \text{ReLU}(z) = \max(0, z), \quad \text{ReLU}'(z) = \begin{cases} 1 & z > 0 \ 0 & z \leq 0 \end{cases} $$
- 解决了梯度消失问题(正区间梯度恒为1)
- 计算极其简单
- 引入稀疏性
- 问题:Dead ReLU(神经元永久死亡)
4. Leaky ReLU / PReLU
$$ \text{LeakyReLU}(z) = \begin{cases} z & z > 0 \ \alpha z & z \leq 0 \end{cases} $$
- $\alpha$ 通常取 0.01(Leaky ReLU)或可学习(PReLU)
- 解决了 Dead ReLU 问题
5. GELU(Transformer 标配)
$$ \text{GELU}(z) = z \cdot \Phi(z) = z \cdot \frac{1}{2}\left[1 + \text{erf}\left(\frac{z}{\sqrt{2}}\right)\right] $$
近似形式:
$$ \text{GELU}(z) \approx 0.5z\left(1 + \tanh\left[\sqrt{\frac{2}{\pi}}(z + 0.044715z^3)\right]\right) $$
为什么 GELU 是 Transformer 标配:
- 概率性解释:可以看作是对输入的随机正则化(Dropout 的确定性版本)
- 平滑可微:在零点处是光滑的,不像 ReLU 有不可导点
- 非单调:在负区间有轻微的负值,保留了部分负值信息
- 实验效果好:BERT、GPT 系列均采用 GELU
6. SwiGLU(LLM 新标准)
$$ \text{SwiGLU}(x, W, V, b, c) = \text{Swish}(xW + b) \otimes (xV + c) $$
其中 $\text{Swish}(z) = z \cdot \sigma(z)$($\sigma$ 是 sigmoid)
SwiGLU 在 LLaMA/Qwen 中流行的原因:
- 门控机制:通过门控(GLU)选择性地传递信息
- 更好的梯度流:乘法结构提供了更丰富的梯度信号
- 实验效果显著:Google 的论文(Shazeer 2020)表明,在相同参数量下,SwiGLU 的性能优于 ReLU/GELU
- 计算效率:虽然参数量增加(FFN 从 2 个矩阵变为 3 个),但效果提升足以补偿
# LLaMA 中的 SwiGLU FFN
class SwiGLU(nn.Module):
def forward(self, x):
return F.silu(x @ self.w1) * (x @ self.w3) # 门控激活函数演进总结:
Sigmoid (1986) → Tanh → ReLU (2010) → LeakyReLU → GELU (2016) → SwiGLU (2020)
↓ ↓ ↓
梯度消失 Transformer标配 LLM新标准Q6:不同层应该用什么激活函数?⭐⭐
A: 这取决于任务类型和网络位置。
输出层激活函数选择:
| 任务类型 | 激活函数 | 原因 |
|---|---|---|
| 二分类 | Sigmoid | 输出概率 [0,1] |
| 多分类 | Softmax | 输出概率分布,和为1 |
| 回归 | 无(线性) | 输出任意实数 |
隐藏层激活函数选择:
| 场景 | 推荐激活函数 | 原因 |
|---|---|---|
| CNN | ReLU / GELU | 计算快,效果好 |
| Transformer | GELU / SwiGLU | 平滑,实验效果好 |
| RNN/LSTM | Tanh | 输出范围 [-1,1],适合门控 |
| 生成模型输出 | Tanh | 输出范围 [-1,1] |
Softmax 函数详解:
$$ \text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} $$
数值稳定性实现:
$$ \text{Softmax}(z_i) = \frac{e^{z_i - \max(\mathbf{z})}}{\sum_{j=1}^{K} e^{z_j - \max(\mathbf{z})}} $$
减去最大值防止指数溢出。
二、反向传播与自动微分
Q7:反向传播算法的核心思想是什么?⭐
A: 反向传播(Backpropagation)是计算神经网络损失函数对所有参数梯度的高效算法。
核心思想:链式法则 + 动态规划
对于复合函数 $L = f(g(h(x)))$,链式法则给出:
$$ \frac{\partial L}{\partial x} = \frac{\partial f}{\partial g} \cdot \frac{\partial g}{\partial h} \cdot \frac{\partial h}{\partial x} $$
反向传播的两个阶段:
- 前向传播:计算每一层的输出并保存(用于反向传播)
- 反向传播:从损失函数开始,逐层计算梯度并利用链式法则传递
为什么叫"反向":
前向:x → h1 → h2 → ... → y → L
反向:∂L/∂y → ∂L/∂h_n → ... → ∂L/∂h1 → ∂L/∂x梯度从输出层"反向"流回输入层。
Q8:用计算图来解释反向传播 ⭐⭐
A: 计算图是理解反向传播最直观的工具。
示例:计算 $L = (x \cdot w + b - y)^2$
前向传播(从左到右):
x=2, w=3, b=1, y=5
x ──→ [×] ──→ [+] ──→ [-] ──→ [²] ──→ L
w ──↗ b ──↗ y──↗
计算过程:
m = x·w = 6
n = m+b = 7
o = n-y = 2
L = o² = 4反向传播(从右到左):
∂L/∂L = 1
∂L/∂o = 2o = 4
∂L/∂n = ∂L/∂o · ∂o/∂n = 4 · 1 = 4
∂L/∂y = ∂L/∂o · ∂o/∂y = 4 · (-1) = -4
∂L/∂m = ∂L/∂n · ∂n/∂m = 4 · 1 = 4
∂L/∂b = ∂L/∂n · ∂n/∂b = 4 · 1 = 4
∂L/∂x = ∂L/∂m · ∂m/∂x = 4 · 3 = 12
∂L/∂w = ∂L/∂m · ∂m/∂w = 4 · 2 = 8计算图的关键优势:
- 路径上的梯度相乘:如果有多个路径,梯度相加
- 中间结果复用:每个节点只需保存局部梯度
- 自动处理复杂拓扑:任意 DAG 结构都可以处理
# PyTorch 自动构建计算图
x = torch.tensor(2.0, requires_grad=True)
w = torch.tensor(3.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
y = 5.0
m = x * w # 计算图节点
n = m + b # 计算图节点
o = n - y # 计算图节点
L = o ** 2 # 计算图节点
L.backward() # 自动反向传播
print(x.grad) # tensor(12.)
print(w.grad) # tensor(8.)
print(b.grad) # tensor(4.)Q9:两层 MLP 的反向传播完整推导 ⭐⭐
A: 这是面试中常见的手推题,以下是完整推导。
设定:
- 输入:$\mathbf{x} \in \mathbb{R}^d$
- 隐藏层:$\mathbf{h} = \sigma(\mathbf{W}_1 \mathbf{x} + \mathbf{b}_1)$,$\mathbf{h} \in \mathbb{R}^p$
- 输出层:$\hat{y} = \mathbf{W}_2 \mathbf{h} + \mathbf{b}_2$(回归任务)
- 损失函数:$L = \frac{1}{2}(\hat{y} - y)^2$
前向传播:
$$ \mathbf{z}_1 = \mathbf{W}_1 \mathbf{x} + \mathbf{b}_1 $$
$$ \mathbf{h} = \sigma(\mathbf{z}_1) $$
$$ \hat{y} = \mathbf{W}_2 \mathbf{h} + \mathbf{b}_2 $$
$$ L = \frac{1}{2}(\hat{y} - y)^2 $$
反向传播:
Step 1:损失对输出的梯度
$$ \frac{\partial L}{\partial \hat{y}} = \hat{y} - y $$
Step 2:损失对输出层参数的梯度
$$ \frac{\partial L}{\partial \mathbf{W}_2} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial \mathbf{W}_2} = (\hat{y} - y) \cdot \mathbf{h}^T $$
$$ \frac{\partial L}{\partial \mathbf{b}_2} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial \mathbf{b}_2} = (\hat{y} - y) $$
Step 3:损失对隐藏层输出的梯度
$$ \frac{\partial L}{\partial \mathbf{h}} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial \mathbf{h}} = \mathbf{W}_2^T (\hat{y} - y) $$
Step 4:损失对隐藏层激活前的梯度
$$ \frac{\partial L}{\partial \mathbf{z}_1} = \frac{\partial L}{\partial \mathbf{h}} \odot \sigma'(\mathbf{z}_1) $$
其中 $\odot$ 是逐元素乘法(Hadamard积)。
Step 5:损失对隐藏层参数的梯度
$$ \frac{\partial L}{\partial \mathbf{W}_1} = \frac{\partial L}{\partial \mathbf{z}_1} \cdot \mathbf{x}^T $$
$$ \frac{\partial L}{\partial \mathbf{b}_1} = \frac{\partial L}{\partial \mathbf{z}_1} $$
完整流程图:
前向: x → [W1,b1] → z1 → [σ] → h → [W2,b2] → ŷ → [L]
反向: ∂L/∂W2 ← ∂L/∂ŷ ← ∂L/∂h ← ∂L/∂z1 ← ∂L/∂W1Q10:前向模式 vs 反向模式自动微分有什么区别?⭐⭐⭐
A: 自动微分有两种模式,理解它们的区别对于理解深度学习框架至关重要。
前向模式(Forward Mode):
- 从输入向输出传播导数
- 一次前向传播计算一个输入方向的导数
- 计算量:$O(1)$ 次前向传播 per 输入维度
反向模式(Reverse Mode):
- 从输出向输入传播导数
- 一次反向传播计算所有输入的导数
- 计算量:$O(1)$ 次反向传播 for all 输入维度
对比:
| 特性 | 前向模式 | 反向模式 |
|---|---|---|
| 传播方向 | 输入 → 输出 | 输出 → 输入 |
| 计算次数 | O(n) 次(n=输入维度) | O(1) 次 |
| 内存需求 | O(1) | O(m)(m=计算图节点数) |
| 适用场景 | 输入少、输出多 | 输入多、输出少 |
| 深度学习适用性 | ❌ 不适合 | ✅ 适合 |
为什么深度学习用反向模式:
神经网络通常有:
- 大量输入(参数):百万到万亿级
- 少量输出(损失):通常 1 个标量
这恰好是反向模式的优势场景:一次反向传播即可计算所有参数的梯度。
具体例子:
# f: R^1000000 → R^1(100万参数,1个损失)
# 前向模式:需要 1,000,000 次前向传播
# 反向模式:只需要 1 次反向传播
# 计算量比:1,000,000 : 1前向模式的实现方式(Dual Numbers):
$$ f(x + \epsilon) = f(x) + \epsilon f'(x) + O(\epsilon^2) $$
在计算中保留 $\epsilon$ 项即可得到导数。
Q11:PyTorch 的 autograd 机制是如何工作的?⭐⭐
A: PyTorch 的自动微分系统是其核心特性之一。
核心概念:
- Tensor:多维数组,支持自动微分
- 计算图:动态构建的 DAG(Dynamic Acyclic Graph)
- 梯度累积:梯度默认累积,需要手动清零
autograd 的关键属性:
x = torch.randn(3, requires_grad=True)
# requires_grad: 是否需要计算梯度
# grad: 存储梯度值
# grad_fn: 梯度函数(指向创建该tensor的操作)
# is_leaf: 是否是叶子节点(用户创建的tensor)动态计算图 vs 静态计算图:
| 特性 | 动态图(PyTorch) | 静态图(TF1.x) |
|---|---|---|
| 构建时机 | 运行时 | 编译时 |
| 调试 | 方便(标准Python) | 困难 |
| 灵活性 | 高(支持控制流) | 低 |
| 优化 | 难以全局优化 | 可以全局优化 |
| 序列化 | 不直接支持 | 支持 |
PyTorch 反向传播示例:
import torch
# 创建tensor
x = torch.randn(2, 2, requires_grad=True)
y = torch.randn(2, 2, requires_grad=True)
# 前向传播(构建计算图)
z = x * y
out = z.sum()
# 反向传播
out.backward()
# 梯度
print(x.grad) # ∂out/∂x = y
print(y.grad) # ∂out/∂y = x梯度累积与清零:
# PyTorch 默认累积梯度
for i in range(10):
loss = model(x)
loss.backward() # 梯度累积!
optimizer.step()
optimizer.zero_grad() # 必须手动清零
# 为什么累积?—— 用于梯度累积(小显存训练大batch)
for i in range(4):
loss = model(x_batch[i])
loss.backward() # 累积4个mini-batch的梯度
optimizer.step()
optimizer.zero_grad()detach() 和 no_grad():
# detach: 从计算图中分离,不计算梯度
a = x * 2
b = a.detach() # b 不再追踪梯度
# no_grad: 上下文管理器,禁用梯度计算
with torch.no_grad():
y = model(x) # 推理时不计算梯度,节省内存Q12:梯度检查(Gradient Checking)是什么?怎么做?⭐⭐
A: 梯度检查用于验证反向传播实现的正确性。
数值梯度近似:
$$ \frac{\partial L}{\partial \theta_i} \approx \frac{L(\theta_i + \epsilon) - L(\theta_i - \epsilon)}{2\epsilon} $$
其中 $\epsilon$ 是一个很小的值(如 $10^{-7}$)。
相对误差:
$$ \text{相对误差} = \frac{|g_{\text{analytic}} - g_{\text{numerical}}|}{|g_{\text{analytic}}| + |g_{\text{numerical}}|} $$
- 相对误差 $< 10^{-7}$:✅ 很好
- 相对误差 $< 10^{-5}$:✅ 通常可以接受
- 相对误差 $> 10^{-3}$:❌ 可能有bug
def gradient_check(model, x, y, epsilon=1e-7):
# 计算解析梯度
loss = model(x, y)
loss.backward()
analytic_grads = {name: p.grad.clone() for name, p in model.named_parameters()}
# 计算数值梯度
for name, param in model.named_parameters():
numeric_grad = torch.zeros_like(param)
flat = param.data.view(-1)
for i in range(len(flat)):
old_val = flat[i].item()
flat[i] = old_val + epsilon
loss_plus = model(x, y)
flat[i] = old_val - epsilon
loss_minus = model(x, y)
numeric_grad.view(-1)[i] = (loss_plus - loss_minus) / (2 * epsilon)
flat[i] = old_val
# 计算相对误差
rel_error = torch.abs(analytic_grads[name] - numeric_grad) / \
(torch.abs(analytic_grads[name]) + torch.abs(numeric_grad) + 1e-8)
print(f"{name}: max relative error = {rel_error.max().item()}")三、梯度问题
Q13:梯度消失(Vanishing Gradient)是怎么产生的?⭐
A: 梯度消失是深度网络训练的核心难题之一。
产生原因:链式法则中的连乘效应
对于 L 层网络,反向传播时:
$$ \frac{\partial L}{\partial \mathbf{W}_1} = \frac{\partial L}{\partial \mathbf{a}L} \cdot \prod^{L} \frac{\partial \mathbf{a}l}{\partial \mathbf{a}{l-1}} \cdot \frac{\partial \mathbf{a}_1}{\partial \mathbf{W}_1} $$
如果每层的 $\frac{\partial \mathbf{a}l}{\partial \mathbf{a}{l-1}} < 1$(例如 Sigmoid 的最大导数为 0.25),那么:
$$ \prod_{l=2}^{L} \frac{\partial \mathbf{a}l}{\partial \mathbf{a}{l-1}} < (0.25)^{L-1} \to 0 \quad (\text{当 } L \text{ 很大时}) $$
Sigmoid 导致梯度消失的具体分析:
$$ \sigma'(z) = \sigma(z)(1 - \sigma(z)) \leq 0.25 $$
对于 10 层网络:
$$ \text{梯度衰减} \approx (0.25)^{10} \approx 10^{-6} $$
数值示例:
层L的梯度: 1.0
层L-1的梯度: 1.0 × 0.25 = 0.25
层L-2的梯度: 0.25 × 0.25 = 0.0625
层L-3的梯度: 0.0625 × 0.25 = 0.015625
...
层1的梯度: ≈ 0 (几乎为零)Q14:梯度爆炸(Exploding Gradient)是怎么产生的?⭐
A: 梯度消失的"镜像"问题。
产生原因:
当每层的梯度放大因子 > 1 时:
$$ \prod_{l=2}^{L} \frac{\partial \mathbf{a}l}{\partial \mathbf{a}{l-1}} > 1^{L-1} \to \infty $$
常见场景:
- 权重矩阵特征值 > 1:反复相乘导致指数增长
- 学习率过大:参数更新幅度过大
- RNN 中的长序列:相同权重矩阵的多次相乘
RNN 中的梯度爆炸:
$$ \frac{\partial \mathbf{h}t}{\partial \mathbf{h}k} = \prod^{t} \frac{\partial \mathbf{h}i}{\partial \mathbf{h}{i-1}} = \prod^{t} \mathbf{W}^T \text{diag}(\sigma'(\mathbf{z}_i)) $$
如果 $\mathbf{W}$ 的最大特征值 > 1,梯度会指数增长。
梯度爆炸的表现:
训练loss突然变为NaN
参数值变得非常大
模型输出极端值Q15:解决梯度消失和梯度爆炸有哪些方法?⭐⭐
A: 以下是系统性的解决方案。
1. 使用 ReLU 激活函数
- 正区间梯度恒为 1,不会衰减
- 但需注意 Dead ReLU 问题
2. 残差连接(Residual Connection)
$$ \mathbf{y} = F(\mathbf{x}) + \mathbf{x} $$
$$ \frac{\partial \mathbf{y}}{\partial \mathbf{x}} = \frac{\partial F}{\partial \mathbf{x}} + \mathbf{I} $$
梯度可以直接流过恒等映射,不会消失。
3. Batch Normalization(BN)
$$ \hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} $$
- 使每层输入分布稳定
- 减少 Internal Covariate Shift
4. Layer Normalization(LN)
$$ \hat{x}_i = \frac{x_i - \mu_L}{\sqrt{\sigma_L^2 + \epsilon}} $$
- 对单个样本的所有特征归一化
- 不依赖 batch size
5. 合理的权重初始化
- Xavier 初始化:$\text{Var}(W) = \frac{2}{n_{in} + n_{out}}$
- He 初始化:$\text{Var}(W) = \frac{2}{n_{in}}$
6. LSTM 的门控机制
$$ \mathbf{c}_t = \mathbf{f}t \odot \mathbf{c} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t $$
- 遗忘门 $\mathbf{f}_t$ 控制信息保留
- 加法结构允许梯度直接流过
7. 梯度裁剪(Gradient Clipping)
# 按范数裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 按值裁剪
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=0.5)解决方案总结:
| 问题 | 解决方案 |
|---|---|
| 梯度消失 | ReLU、残差连接、BN/LN、LSTM门控 |
| 梯度爆炸 | 梯度裁剪、合理初始化、BN/LN |
| 通用 | 合理学习率、正则化 |
Q16:为什么 Transformer 用 LayerNorm 而不用 BatchNorm?⭐⭐⭐
A: 这是深度学习面试中的经典问题。
BN 在 Transformer 中的问题:
- 序列长度不一致:NLP 中不同样本的序列长度不同,BN 沿序列维度计算统计量不合理
- Batch 依赖:BN 需要 batch 统计量,推理时使用 running mean/var,训练和推理行为不一致
- 小 batch size:Transformer 训练时 batch size 通常较小(按样本数算),BN 统计量不稳定
- 位置信息丢失:BN 对同一 batch 内所有位置做归一化,会破坏位置信息
LN 的优势:
# BN: 对 batch 维度归一化
# 输入: (batch, seq_len, hidden)
# 对 batch 维度计算 mean, var → (1, seq_len, hidden)
# LN: 对 hidden 维度归一化
# 输入: (batch, seq_len, hidden)
# 对 hidden 维度计算 mean, var → (batch, seq_len, 1)| 特性 | BatchNorm | LayerNorm |
|---|---|---|
| 归一化维度 | batch 维度 | hidden 维度 |
| batch 依赖 | ✅ 是 | ❌ 否 |
| 序列长度 | 需要一致 | 不需要 |
| 训练/推理一致性 | ❌ 不一致 | ✅ 一致 |
| 适合 NLP | ❌ | ✅ |
| 适合 CV | ✅ | 一般 |
Transformer 中 LN 的位置:
Pre-Norm: x → LayerNorm → Attention → + → LayerNorm → FFN → +
Post-Norm: x → Attention → + → LayerNorm → FFN → + → LayerNorm现代 Transformer(如 GPT、LLaMA)通常使用 Pre-Norm,因为:
- 训练更稳定
- 不需要 warmup
- 梯度流更好
Q17:Internal Covariate Shift 是什么?BN 真的能解决它吗?⭐⭐⭐
A: 这是深度学习理论中的一个有趣话题。
Internal Covariate Shift(ICS)的原始定义(Ioffe & Szegedy 2015):
在训练过程中,由于网络参数的变化,每一层的输入分布也在不断变化,这种现象称为 Internal Covariate Shift。
BN 的原始解释:
通过归一化每一层的输入,使其分布保持稳定,从而加速训练。
现代理解(Santurkar et al. 2018):
论文 "How Does Batch Normalization Help Optimization?" 指出:
- BN 并没有减少 ICS——实验表明 BN 后各层输入分布仍在变化
- BN 的真正作用是使损失函数的landscape 更平滑(更小的 Lipschitz 常数)
- 平滑的损失函数允许更大的学习率,从而加速训练
BN 为什么有效(现代理解):
- 损失曲面平滑化:使梯度更可预测
- 解耦参数尺度:权重的尺度不影响输出
- 隐式正则化:由于 batch 统计量的随机性
四、权重初始化
Q18:为什么不能用全零初始化?⭐
A: 全零初始化是神经网络训练的"禁忌"。
对称性问题(Symmetry Problem):
如果所有权重都初始化为相同的值(包括零),那么:
- 同一层的所有神经元在前向传播时计算相同的值
- 反向传播时,所有神经元接收到相同的梯度
- 因此,所有神经元的权重更新也相同
- 结果:同一层的所有神经元永远相同,失去了"多个神经元"的意义
数学证明:
假设 $W_{ij}^{(l)} = c$(常数),则对于同一层的任意两个神经元 $i$ 和 $j$:
$$ z_i^{(l)} = \sum_k W_{ik}^{(l)} a_k^{(l-1)} = c \sum_k a_k^{(l-1)} = z_j^{(l)} $$
因此 $a_i^{(l)} = a_j^{(l)}$,进而 $\frac{\partial L}{\partial W_{ik}^{(l)}} = \frac{\partial L}{\partial W_{jk}^{(l)}}$。
正确的初始化: 应该随机初始化,打破对称性。
Q19:Xavier 初始化的推导和适用条件是什么?⭐⭐
A: Xavier 初始化(Glorot 初始化)是深度学习中最经典的初始化方法之一。
核心思想: 保持前向传播和反向传播中信号的方差不变。
推导过程:
对于线性层 $z = \sum_{i=1}^{n} w_i x_i$,假设:
- $w_i$ 和 $x_i$ 独立
- $w_i$ 均值为 0
- $x_i$ 均值为 0
则:
$$ \text{Var}(z) = \sum_{i=1}^{n} \text{Var}(w_i x_i) = n \cdot \text{Var}(w) \cdot \text{Var}(x) $$
前向传播条件: $\text{Var}(z) = \text{Var}(x)$
$$ n_{in} \cdot \text{Var}(w) = 1 \Rightarrow \text{Var}(w) = \frac{1}{n_{in}} $$
反向传播条件: $\text{Var}(\frac{\partial L}{\partial x}) = \text{Var}(\frac{\partial L}{\partial z})$
$$ n_{out} \cdot \text{Var}(w) = 1 \Rightarrow \text{Var}(w) = \frac{1}{n_{out}} $$
折中方案(Xavier):
$$ \text{Var}(w) = \frac{2}{n_{in} + n_{out}} $$
均匀分布形式:
$$ W \sim U\left[-\sqrt{\frac{6}{n_{in} + n_{out}}}, \sqrt{\frac{6}{n_{in} + n_{out}}}\right] $$
适用条件:
- 线性激活函数或 Tanh/Sigmoid
- 不适用于 ReLU(因为 ReLU 会将一半的值置零)
# PyTorch 实现
nn.init.xavier_uniform_(tensor) # 均匀分布
nn.init.xavier_normal_(tensor) # 正态分布Q20:He 初始化的推导和适用条件是什么?⭐⭐
A: He 初始化是专为 ReLU 设计的初始化方法。
问题:ReLU 对 Xavier 的影响
ReLU 将一半的输入置零,因此输出的方差减半:
$$ \text{Var}(z) = n \cdot \text{Var}(w) \cdot \text{Var}(x) \cdot \frac{1}{2} $$
He 初始化推导:
为了保持方差不变:
$$ n \cdot \text{Var}(w) \cdot \frac{1}{2} = 1 $$
$$ \text{Var}(w) = \frac{2}{n} $$
正态分布形式:
$$ W \sim \mathcal{N}\left(0, \frac{2}{n_{in}}\right) $$
均匀分布形式:
$$ W \sim U\left[-\sqrt{\frac{6}{n_{in}}}, \sqrt{\frac{6}{n_{in}}}\right] $$
适用条件:
- ReLU 及其变体(Leaky ReLU 等)
- CNN 和 MLP
# PyTorch 实现
nn.init.kaiming_uniform_(tensor, mode='fan_in', nonlinearity='relu')
nn.init.kaiming_normal_(tensor, mode='fan_in', nonlinearity='relu')初始化方法选择指南:
| 激活函数 | 推荐初始化 |
|---|---|
| Sigmoid / Tanh | Xavier |
| ReLU / LeakyReLU | He (Kaiming) |
| GELU / Swish | He (近似ReLU) |
Q21:Transformer 的 Embedding 层是如何初始化的?⭐⭐
A: Transformer 的初始化对训练稳定性至关重要。
标准 Transformer(如 BERT)的初始化:
# Embedding 初始化
nn.init.normal_(embedding.weight, mean=0.0, std=0.02)
# 注意力权重初始化
nn.init.normal_(q_proj.weight, mean=0.0, std=0.02)
nn.init.normal_(k_proj.weight, mean=0.0, std=0.02)
nn.init.normal_(v_proj.weight, mean=0.0, std=0.02)
nn.init.normal_(o_proj.weight, mean=0.0, std=0.02)
# FFN 初始化
nn.init.normal_(fc1.weight, mean=0.0, std=0.02)
nn.init.normal_(fc2.weight, mean=0.0, std=0.02)为什么 std=0.02?
对于 $d_{model} = 768$:
$$ \text{He 初始化标准差} = \sqrt{\frac{2}{768}} \approx 0.051 $$
$$ \text{Xavier 初始化标准差} = \sqrt{\frac{2}{768 + 768}} \approx 0.036 $$
$0.02$ 是一个经验值,在实践中表现良好。
LLaMA 的初始化改进:
# 使用截断正态分布,避免极端值
nn.init.trunc_normal_(weight, mean=0.0, std=0.02, a=-0.04, b=0.04)Embedding 初始化的重要性:
- 过大的初始化 → 训练不稳定
- 过小的初始化 → 训练缓慢
- $0.02$ 是一个平衡点
五、BatchNorm vs LayerNorm vs RMSNorm
Q22:BatchNorm 的原理是什么?训练和推理有什么区别?⭐
A: BatchNorm 是深度学习中最重要的技术之一。
BN 的计算过程:
对于 mini-batch $\mathcal{B} = {x_1, x_2, ..., x_m}$:
Step 1:计算均值和方差
$$ \mu_\mathcal{B} = \frac{1}{m} \sum_{i=1}^{m} x_i $$
$$ \sigma_\mathcal{B}^2 = \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu_\mathcal{B})^2 $$
Step 2:归一化
$$ \hat{x}i = \frac{x_i - \mu\mathcal{B}}{\sqrt{\sigma_\mathcal{B}^2 + \epsilon}} $$
Step 3:缩放和平移(可学习参数)
$$ y_i = \gamma \hat{x}_i + \beta $$
其中 $\gamma$ 和 $\beta$ 是可学习的参数。
训练 vs 推理:
| 阶段 | 均值/方差来源 | 特点 |
|---|---|---|
| 训练 | 当前 mini-batch | 随机性,有正则化效果 |
| 推理 | 全局统计量(running mean/var) | 确定性 |
Running Mean/Var 的更新:
$$ \mu_{\text{running}} = (1 - \alpha) \cdot \mu_{\text{running}} + \alpha \cdot \mu_\mathcal{B} $$
$$ \sigma^2_{\text{running}} = (1 - \alpha) \cdot \sigma^2_{\text{running}} + \alpha \cdot \sigma^2_\mathcal{B} $$
其中 $\alpha$ 是动量(momentum),默认 0.1。
# PyTorch 中的 BN
bn = nn.BatchNorm1d(num_features=64)
# 训练时:使用 batch 统计量
bn.train()
y = bn(x) # 使用当前 batch 的 mean, var
# 推理时:使用 running 统计量
bn.eval()
y = bn(x) # 使用 running mean, varQ23:LayerNorm 的原理是什么?⭐
A: LayerNorm 对单个样本的所有特征进行归一化。
LN 的计算过程:
对于单个样本 $\mathbf{x} = (x_1, x_2, ..., x_H)$(H 是隐藏维度):
Step 1:计算均值和方差
$$ \mu = \frac{1}{H} \sum_{i=1}^{H} x_i $$
$$ \sigma^2 = \frac{1}{H} \sum_{i=1}^{H} (x_i - \mu)^2 $$
Step 2:归一化
$$ \hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}} $$
Step 3:缩放和平移
$$ y_i = \gamma \hat{x}_i + \beta $$
BN vs LN 对比:
# 输入: (batch_size, seq_len, hidden_dim)
# BatchNorm: 对 batch 和 seq_len 维度归一化
# 计算统计量的维度: (batch_size, seq_len) → 得到 (1, 1, hidden_dim)
# LayerNorm: 对 hidden_dim 维度归一化
# 计算统计量的维度: (hidden_dim) → 得到 (batch_size, seq_len, 1)LN 的优势:
- 不依赖 batch size
- 训练和推理行为一致
- 适合变长序列
- 不需要 running statistics
# PyTorch 实现
ln = nn.LayerNorm(normalized_shape=768)
y = ln(x) # x: (batch, seq_len, 768)Q24:RMSNorm 的原理是什么?为什么比 LayerNorm 更快?⭐⭐
A: RMSNorm(Root Mean Square Layer Normalization)是 LayerNorm 的简化版本,在现代 LLM 中广泛使用。
RMSNorm 的计算:
$$ \text{RMSNorm}(x_i) = \frac{x_i}{\text{RMS}(\mathbf{x})} \cdot \gamma_i $$
其中:
$$ \text{RMS}(\mathbf{x}) = \sqrt{\frac{1}{H} \sum_{i=1}^{H} x_i^2 + \epsilon} $$
与 LayerNorm 的关键区别:
- 不计算均值:RMSNorm 不减去均值
- 不计算偏置:只有缩放参数 $\gamma$,没有平移参数 $\beta$
- 计算更简单:省略了均值计算和减法操作
为什么 RMSNorm 更快:
| 操作 | LayerNorm | RMSNorm |
|---|---|---|
| 计算均值 | ✅ 需要 | ❌ 不需要 |
| 减去均值 | ✅ 需要 | ❌ 不需要 |
| 计算方差 | ✅ 需要 | ❌ 不需要 |
| 计算RMS | ❌ 不需要 | ✅ 需要 |
| 可学习参数 | $\gamma, \beta$ | $\gamma$ |
速度提升: 约 10-15%(在大规模模型中显著)
LLaMA 中的 RMSNorm:
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim))
def forward(self, x):
# 计算 RMS
rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + self.eps)
# 归一化并缩放
x_norm = x / rms
return x_norm * self.weight使用 RMSNorm 的模型:
- LLaMA / LLaMA 2 / LLaMA 3
- Qwen / Qwen 2
- Mistral
- Gemma
为什么现代 LLM 偏好 RMSNorm:
- 训练效果与 LayerNorm 相当
- 计算更快,节省显存
- 实现更简单
- 在大规模模型上,每一个百分点的效率提升都很重要
六、Dropout
Q25:Dropout 的原理是什么?⭐
A: Dropout 是最常用的正则化技术之一。
训练时的 Dropout:
随机将一部分神经元的输出置为零:
$$ \mathbf{r} \sim \text{Bernoulli}(p) $$
$$ \tilde{\mathbf{h}} = \mathbf{r} \odot \mathbf{h} $$
其中 $p$ 是保留概率(keep probability),通常取 0.5-0.9。
Inverted Dropout(标准实现):
训练时除以保留概率,推理时不做任何处理:
$$ \tilde{\mathbf{h}} = \frac{\mathbf{r} \odot \mathbf{h}}{p} $$
为什么用 Inverted Dropout:
普通 Dropout:
- 训练时:输出期望 = p * h
- 推理时:输出期望 = h
- 需要在推理时乘以 p
Inverted Dropout:
- 训练时:输出期望 = (r * h) / p = h
- 推理时:输出期望 = h
- 推理时不需要修改# PyTorch 实现
dropout = nn.Dropout(p=0.5) # p 是 drop 概率
# 训练时
y = dropout(x) # 随机置零,并除以 (1-p)
# 推理时
y = x # 直接通过注意:PyTorch 的 p 是 drop 概率,不是 keep 概率!
Q26:Dropout 为什么能防止过拟合?⭐⭐
A: Dropout 防止过拟合有多种解释。
1. 集成学习视角
每次前向传播使用不同的 dropout mask,相当于训练了 $2^n$ 个不同的子网络(n 是神经元数量)。推理时使用所有神经元相当于这些子网络的集成。
2. 减少神经元共适应(Co-adaptation)
Dropout 强迫每个神经元独立工作,不能依赖其他特定神经元,从而学到更鲁棒的特征。
3. 噪声注入视角
Dropout 可以看作是对隐藏层注入乘性噪声,增加了训练数据的多样性。
4. 贝叶斯近似视角
Gal & Ghahramani (2016) 证明 Dropout 可以看作是变分推断的近似。
Dropout 的数学性质:
- 期望不变性:$E[\tilde{h}] = h$
- 方差增大:$\text{Var}[\tilde{h}] = \frac{1-p}{p} h^2$
- 这种噪声类似于数据增强的效果
实验效果:
没有 Dropout:
- 训练精度: 99.5%
- 测试精度: 85.0%
- 过拟合差距: 14.5%
有 Dropout (p=0.5):
- 训练精度: 95.0%
- 测试精度: 90.0%
- 过拟合差距: 5.0%Q27:Transformer 中 Dropout 放在哪些位置?⭐⭐
A: Transformer 中有多个 Dropout 位置,每个都有不同作用。
标准 Transformer 的 Dropout 位置:
1. Embedding Dropout: 词嵌入后
2. Attention Dropout: 注意力权重上(softmax 后)
3. Residual Dropout: 残差连接后(子层输出)
4. FFN Dropout: FFN 中间层后具体位置示意:
Input → Embedding → Dropout(emb_dropout)
↓
LayerNorm → Multi-Head Attention → Dropout(attn_dropout)
↓
Residual Connection → Dropout(res_dropout)
↓
LayerNorm → FFN → Dropout(ffn_dropout)
↓
Residual Connection → Dropout(res_dropout)各 Dropout 的作用:
| 位置 | 典型值 | 作用 |
|---|---|---|
| Embedding Dropout | 0.1 | 正则化输入表示 |
| Attention Dropout | 0.1 | 随机丢弃注意力连接 |
| Residual Dropout | 0.1 | 正则化残差连接 |
| FFN Dropout | 0.1 | 正则化 FFN |
注意: 现代 LLM(如 LLaMA、GPT-3)通常不使用 Dropout,因为:
- 训练数据量巨大,过拟合风险低
- 更大的 batch size 本身有正则化效果
- Dropout 会略微影响训练效率
# BERT 使用 Dropout
class BertSelfOutput(nn.Module):
def __init__(self, config):
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
self.LayerNorm = nn.LayerNorm(config.hidden_size)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
def forward(self, hidden_states, input_tensor):
hidden_states = self.dense(hidden_states)
hidden_states = self.dropout(hidden_states) # 残差 Dropout
hidden_states = self.LayerNorm(hidden_states + input_tensor)
return hidden_states
# LLaMA 不使用 Dropout
class LlamaAttention(nn.Module):
def __init__(self, config):
# 没有 Dropout!
pass七、残差连接
Q28:残差连接(Residual Connection)的核心思想是什么?⭐
A: 残差连接是深度学习中最重要的架构创新之一。
核心公式:
$$ \mathbf{y} = F(\mathbf{x}) + \mathbf{x} $$
其中 $F(\mathbf{x})$ 是残差函数(通常是几层网络)。
ResNet 的历史意义:
在 ResNet(2015)之前,网络超过 20 层后性能反而下降(退化问题)。ResNet 通过残差连接成功训练了 152 层网络。
为什么叫"残差":
如果最优映射是 $H(\mathbf{x})$,那么残差函数 $F(\mathbf{x}) = H(\mathbf{x}) - \mathbf{x}$ 比直接学习 $H(\mathbf{x})$ 更容易。
直觉解释:
传统网络:学习 H(x) = 某个复杂映射
残差网络:学习 F(x) = H(x) - x,即"增量变化"
如果最优映射接近恒等映射:
- 传统网络:需要学习 H(x) ≈ x(很难精确拟合)
- 残差网络:只需学习 F(x) ≈ 0(更容易!)Q29:残差连接为什么能训练更深的网络?⭐⭐
A: 这个问题有梯度流和损失曲面两个角度的解释。
角度1:梯度高速公路
反向传播时:
$$ \frac{\partial \mathbf{y}}{\partial \mathbf{x}} = \frac{\partial F}{\partial \mathbf{x}} + \mathbf{I} $$
恒等映射 $\mathbf{I}$ 保证了梯度至少为 1,不会消失。
展开到多层:
$$ \mathbf{y}L = \mathbf{x} + \sum^{L} F_l(\mathbf{x}_l) $$
$$ \frac{\partial \mathbf{y}L}{\partial \mathbf{x}} = \mathbf{I} + \sum^{L} \frac{\partial F_l}{\partial \mathbf{x}} $$
即使 $\frac{\partial F_l}{\partial \mathbf{x}}$ 很小,梯度也不会消失。
角度2:指数级路径
从输入到输出有 $2^L$ 条路径(每层可以选择经过或不经过 $F$):
- 短路径(跳过很多层):梯度大,训练快
- 长路径(经过所有层):梯度小,训练慢
这种多路径结构使得:
- 浅层特征可以直达输出
- 深层特征可以逐步精炼
角度3:损失曲面平滑化
残差连接使损失曲面更平滑,更容易优化:
没有残差连接:损失曲面有很多"悬崖"和局部最小值
有残差连接:损失曲面更平滑,更容易找到好的解Q30:Transformer 中是如何使用残差连接的?⭐⭐
A: Transformer 的每个子层都使用残差连接。
标准 Transformer 的残差连接:
Multi-Head Attention:
output = LayerNorm(x + MultiHeadAttention(x))
FFN:
output = LayerNorm(x + FFN(x))Pre-Norm vs Post-Norm:
# Post-Norm(原始 Transformer)
def post_norm(x, sublayer):
return LayerNorm(x + sublayer(x))
# Pre-Norm(GPT、LLaMA 等)
def pre_norm(x, sublayer):
return x + sublayer(LayerNorm(x))Pre-Norm 的优势:
- 训练更稳定:梯度流更直接
- 不需要 warmup:可以直接用较大学习率
- 更容易训练深层网络
Pre-Norm 的梯度分析:
$$ \mathbf{x}_{l+1} = \mathbf{x}_l + F_l(\text{LN}(\mathbf{x}_l)) $$
$$ \frac{\partial \mathbf{x}_L}{\partial \mathbf{x}_l} = \mathbf{I} + \frac{\partial}{\partial \mathbf{x}l} \sum^{L-1} F_i(\text{LN}(\mathbf{x}_i)) $$
由于直接的恒等连接,梯度可以无衰减地流过整个网络。
LLaMA 的残差连接实现:
# LLaMA Transformer Block
class LlamaDecoderLayer(nn.Module):
def forward(self, x, ...):
# Self-Attention with Pre-Norm
residual = x
x = self.input_layernorm(x)
x = self.self_attn(x, ...)
x = residual + x
# FFN with Pre-Norm
residual = x
x = self.post_attention_layernorm(x)
x = self.mlp(x)
x = residual + x
return x八、CNN 基础
Q31:卷积操作的数学定义是什么?⭐
A: 卷积是 CNN 的核心操作。
离散卷积(2D):
$$ (I * K)(i, j) = \sum_{m} \sum_{n} I(i-m, j-n) \cdot K(m, n) $$
注意:深度学习中的"卷积"实际上是互相关(cross-correlation):
$$ (I \star K)(i, j) = \sum_{m} \sum_{n} I(i+m, j+n) \cdot K(m, n) $$
卷积的关键参数:
| 参数 | 含义 | 典型值 |
|---|---|---|
| kernel_size | 卷积核大小 | 3×3, 5×5 |
| stride | 步长 | 1, 2 |
| padding | 填充 | 0, 1, kernel_size//2 |
| dilation | 空洞卷积 | 1, 2 |
| groups | 分组卷积 | 1, groups |
输出尺寸计算:
$$ H_{out} = \frac{H_{in} + 2 \times \text{padding} - \text{dilation} \times (\text{kernel_size} - 1) - 1}{\text{stride}} + 1 $$
常用配置:
# 保持尺寸不变 (padding='same' 或 padding=kernel_size//2)
conv3x3 = nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1)
# 尺寸减半 (stride=2)
conv_down = nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=2, padding=1)卷积的三个重要性质:
- 局部连接:每个输出只依赖输入的局部区域
- 权值共享:同一卷积核在所有位置共享参数
- 平移等变性:$f(shift(x)) = shift(f(x))$
Q32:如何计算 CNN 的参数量?⭐
A: 参数量计算是面试中的高频问题。
卷积层参数量:
$$ \text{参数量} = C_{out} \times (C_{in} \times K_h \times K_w + 1) $$
其中:
- $C_{out}$:输出通道数
- $C_{in}$:输入通道数
- $K_h \times K_w$:卷积核大小
- $+1$:偏置项
示例:
# Conv2d(64, 128, kernel_size=3, padding=1)
# 参数量 = 128 * (64 * 3 * 3 + 1) = 128 * 577 = 73,856全连接层参数量:
$$ \text{参数量} = (n_{in} + 1) \times n_{out} $$
经典模型参数量:
| 模型 | 参数量 | 层数 | Top-1 准确率 |
|---|---|---|---|
| LeNet-5 | ~60K | 5 | - |
| AlexNet | ~61M | 8 | 57.1% |
| VGG-16 | ~138M | 16 | 71.5% |
| ResNet-50 | ~25.6M | 50 | 76.0% |
| ResNet-152 | ~60M | 152 | 77.8% |
参数量 vs FLOPs:
参数量:模型的存储空间
FLOPs:模型的计算量(浮点运算次数)
注意:
- 参数量大的模型不一定 FLOPs 大(如深度可分离卷积)
- 参数量小的模型不一定 FLOPs 小Q33:经典 CNN 架构是如何演进的?⭐⭐
A: CNN 架构的演进体现了深度学习的发展历程。
1. LeNet-5(1998,Yann LeCun)
Input(32×32) → Conv5×5 → Pool → Conv5×5 → Pool → FC → FC → Output- 第一个成功的 CNN 应用(手写数字识别)
- 证明了卷积的有效性
2. AlexNet(2012,Krizhevsky)
- 首次在 ImageNet 上大幅超越传统方法
- 使用 ReLU 激活函数
- 使用 Dropout
- 使用 GPU 训练
- 局部响应归一化(LRN,已过时)3. VGGNet(2014,Simonyan & Zisserman)
核心思想:用多个 3×3 卷积代替大卷积核
3×3 conv × 2 = 5×5 的感受野
3×3 conv × 3 = 7×7 的感受野
优势:更少的参数,更多的非线性4. ResNet(2015,He et al.)
核心创新:残差连接
y = F(x) + x
- 解决了深层网络的退化问题
- 训练了 152 层网络
- 至今仍是许多任务的基础架构感受野计算:
$$ RF_l = RF_{l-1} + (k_l - 1) \times \prod_{i=1}^{l-1} s_i $$
对于 3 个 3×3 卷积(stride=1):
$$ RF = 1 + (3-1) + (3-1) + (3-1) = 7 $$
Q34:1×1 卷积有什么用?⭐⭐
A: 1×1 卷积看似简单,但用途广泛。
1×1 卷积的本质:
- 对每个空间位置的通道向程做线性变换
- 等价于对每个像素位置应用一个全连接层
主要用途:
1. 通道数变换(升维/降维)
# 降维:256 → 64
conv1x1_down = nn.Conv2d(256, 64, kernel_size=1)
# 升维:64 → 256
conv1x1_up = nn.Conv2d(64, 256, kernel_size=1)2. 增加非线性(配合激活函数)
1×1 conv + ReLU → 增加非线性变换能力3. Inception 模块中的降维
Input → [1×1 conv降维] → [3×3 conv] → Output
→ [1×1 conv降维] → [5×5 conv] → Output
→ [1×1 conv] → Output
→ [max pool] → [1×1 conv] → Output1×1 卷积减少通道数,大幅降低后续大卷积核的计算量。
4. ResNet 中的 Bottleneck
输入 (256通道)
↓
1×1 conv (64通道) ← 降维
↓
3×3 conv (64通道) ← 空间卷积
↓
1×1 conv (256通道) ← 升维
↓
输出 (256通道)参数量对比:
直接 3×3 conv (256→256): 256 × 256 × 3 × 3 = 589,824
Bottleneck:
1×1: 256 × 64 × 1 × 1 = 16,384
3×3: 64 × 64 × 3 × 3 = 36,864
1×1: 64 × 256 × 1 × 1 = 16,384
总计: 69,632
节省: 589,824 / 69,632 ≈ 8.5倍Q35:深度可分离卷积(Depthwise Separable Convolution)是什么?⭐⭐
A: 深度可分离卷积是 MobileNet 的核心创新,大幅减少计算量。
标准卷积 vs 深度可分离卷积:
标准卷积:
$$ \text{计算量} = C_{out} \times C_{in} \times K_h \times K_w \times H \times W $$
深度可分离卷积 = 逐通道卷积 + 逐点卷积:
Step 1:逐通道卷积(Depthwise Conv)
每个输入通道独立卷积,不混合通道:
$$ \text{计算量} = C_{in} \times K_h \times K_w \times H \times W $$
Step 2:逐点卷积(Pointwise Conv,1×1)
1×1 卷积混合通道:
$$ \text{计算量} = C_{out} \times C_{in} \times 1 \times 1 \times H \times W $$
计算量对比:
$$ \text{压缩比} = \frac{1}{C_{out}} + \frac{1}{K^2} $$
对于 $K=3, C_{out}=256$:
$$ \text{压缩比} = \frac{1}{256} + \frac{1}{9} \approx 0.115 $$
计算量减少约 8-9 倍!
# PyTorch 实现
# 深度可分离卷积
class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_ch, out_ch, kernel_size=3, padding=1):
super().__init__()
self.depthwise = nn.Conv2d(
in_ch, in_ch, kernel_size=kernel_size,
padding=padding, groups=in_ch # 关键:groups=in_ch
)
self.pointwise = nn.Conv2d(in_ch, out_ch, kernel_size=1)
def forward(self, x):
x = self.depthwise(x)
x = self.pointwise(x)
return xQ36:CNN vs Transformer 有什么区别?⭐⭐
A: 这是现代深度学习的核心争论之一。
架构对比:
| 特性 | CNN | Transformer |
|---|---|---|
| 感受野 | 局部 → 全局(堆叠) | 一步全局 |
| 位置编码 | 隐式(卷积结构) | 显式(位置编码) |
| 参数共享 | 空间位置共享 | 序列位置共享 |
| 计算复杂度 | O(K²·C·H·W) | O(n²·d) |
| 归纳偏置 | 局部性、平移等变性 | 无(需大量数据) |
| 数据效率 | 高(小数据集) | 低(需要大数据集) |
| 可扩展性 | 有限 | 强(scaling law) |
各自的优势:
CNN 的优势:
- 数据效率高(归纳偏置)
- 计算效率高(局部连接)
- 适合小数据集
Transformer 的优势:
- 全局注意力(一步看到所有位置)
- 可扩展性强(scaling law)
- 适合大规模预训练
融合趋势:
CV: ConvNeXt(用 CNN 结构实现 Transformer 性能)
Swin Transformer(用窗口注意力实现局部性)
NLP: 几乎全面 Transformer 化
多模态: 两者结合九、RNN/LSTM/GRU
Q37:RNN 的结构和梯度问题是什么?⭐
A: RNN 是处理序列数据的经典架构。
RNN 的前向传播:
$$ \mathbf{h}t = \tanh(\mathbf{W} \mathbf{h}{t-1} + \mathbf{W} \mathbf{x}_t + \mathbf{b}_h) $$
$$ \mathbf{y}t = \mathbf{W} \mathbf{h}_t + \mathbf{b}_y $$
RNN 的梯度问题:
反向传播时,梯度需要通过时间步传播(BPTT):
$$ \frac{\partial L}{\partial \mathbf{h}_t} = \frac{\partial L}{\partial \mathbf{h}T} \prod^{T} \frac{\partial \mathbf{h}k}{\partial \mathbf{h}{k-1}} $$
其中:
$$ \frac{\partial \mathbf{h}k}{\partial \mathbf{h}{k-1}} = \text{diag}(\tanh'(\mathbf{z}k)) \cdot \mathbf{W} $$
梯度消失/爆炸的条件:
- 若 $|\mathbf{W}_{hh}|$ 的最大特征值 < 1 → 梯度消失
- 若 $|\mathbf{W}_{hh}|$ 的最大特征值 > 1 → 梯度爆炸
T=20 时:
- 特征值 0.9: 0.9^20 ≈ 0.12(梯度消失)
- 特征值 1.1: 1.1^20 ≈ 6.7(梯度爆炸)Q38:LSTM 的三个门是如何工作的?⭐⭐
A: LSTM(Long Short-Term Memory)通过门控机制解决了 RNN 的梯度问题。
LSTM 的三个门:
1. 遗忘门(Forget Gate)
$$ \mathbf{f}_t = \sigma(\mathbf{W}f [\mathbf{h}, \mathbf{x}_t] + \mathbf{b}_f) $$
决定保留多少旧的记忆:$\mathbf{f}_t \in (0, 1)$
2. 输入门(Input Gate)
$$ \mathbf{i}_t = \sigma(\mathbf{W}i [\mathbf{h}, \mathbf{x}_t] + \mathbf{b}_i) $$
$$ \tilde{\mathbf{c}}_t = \tanh(\mathbf{W}c [\mathbf{h}, \mathbf{x}_t] + \mathbf{b}_c) $$
决定写入多少新信息:$\mathbf{i}_t \in (0, 1)$
3. 输出门(Output Gate)
$$ \mathbf{o}_t = \sigma(\mathbf{W}o [\mathbf{h}, \mathbf{x}_t] + \mathbf{b}_o) $$
决定输出多少记忆:$\mathbf{o}_t \in (0, 1)$
Cell State 更新:
$$ \mathbf{c}_t = \mathbf{f}t \odot \mathbf{c} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t $$
隐藏状态输出:
$$ \mathbf{h}_t = \mathbf{o}_t \odot \tanh(\mathbf{c}_t) $$
LSTM 解决梯度消失的关键:
Cell state 的更新是加法而非乘法:
$$ \mathbf{c}_t = \mathbf{f}t \odot \mathbf{c} + \mathbf{i}_t \odot \tilde{\mathbf{c}}_t $$
$$ \frac{\partial \mathbf{c}t}{\partial \mathbf{c}{t-1}} = \mathbf{f}_t $$
如果 $\mathbf{f}_t \approx 1$,梯度可以无衰减地流过。
Q39:GRU 是如何简化 LSTM 的?⭐⭐
A: GRU(Gated Recurrent Unit)是 LSTM 的简化版本。
GRU 的两个门:
1. 更新门(Update Gate)
$$ \mathbf{z}_t = \sigma(\mathbf{W}z [\mathbf{h}, \mathbf{x}_t]) $$
2. 重置门(Reset Gate)
$$ \mathbf{r}_t = \sigma(\mathbf{W}r [\mathbf{h}, \mathbf{x}_t]) $$
候选隐藏状态:
$$ \tilde{\mathbf{h}}_t = \tanh(\mathbf{W} [\mathbf{r}t \odot \mathbf{h}, \mathbf{x}_t]) $$
隐藏状态更新:
$$ \mathbf{h}_t = (1 - \mathbf{z}t) \odot \mathbf{h} + \mathbf{z}_t \odot \tilde{\mathbf{h}}_t $$
LSTM vs GRU 对比:
| 特性 | LSTM | GRU |
|---|---|---|
| 门的数量 | 3(遗忘、输入、输出) | 2(更新、重置) |
| 状态数量 | 2(h, c) | 1(h) |
| 参数量 | 较多 | 较少(约少25%) |
| 训练速度 | 较慢 | 较快 |
| 性能 | 长序列更好 | 短序列相当 |
# PyTorch 实现
lstm = nn.LSTM(input_size=128, hidden_size=256, num_layers=2)
gru = nn.GRU(input_size=128, hidden_size=256, num_layers=2)Q40:为什么 Transformer 取代了 RNN?⭐⭐⭐
A: Transformer 取代 RNN 是深度学习发展的重要里程碑。
RNN 的核心问题:
- 串行计算:$\mathbf{h}t$ 依赖 $\mathbf{h}$,无法并行
- 长距离依赖:梯度消失导致难以捕捉长距离关系
- 计算效率:序列长度 T,计算复杂度 O(T)
Transformer 的优势:
- 并行计算:所有位置同时计算,充分利用 GPU
- 全局注意力:一步就能看到序列中的所有位置
- 可扩展性:适合大规模并行训练
效率对比:
| 方面 | RNN | Transformer |
|---|---|---|
| 并行性 | ❌ 串行 | ✅ 完全并行 |
| 长距离依赖 | 困难(梯度消失) | 容易(直接注意力) |
| 训练速度 | 慢 | 快(GPU友好) |
| 内存 | O(1) per step | O(n²) attention |
| 推理速度 | O(T) per token | O(T) 自回归 |
为什么 Transformer 更适合 GPU:
RNN:
- 每个时间步依赖前一步
- GPU 无法充分利用(串行瓶颈)
Transformer:
- 注意力矩阵计算是大矩阵乘法
- GPU 擅长矩阵运算(高度并行)RNN 仍有优势的场景:
- 超长序列:Transformer 的 O(n²) 注意力成本太高
- 流式处理:RNN 可以逐个 token 处理
- 低资源环境:RNN 参数量和计算量更小
现代趋势:
RNN 的复兴:
- RWKV:RNN 结构 + Transformer 性能
- Mamba:状态空间模型(SSM),线性复杂度
- S4/S5:结构化状态空间模型
这些模型试图结合 RNN 的效率和 Transformer 的性能十、深度学习面试高频题汇总
Q41:手推反向传播:一个两层网络的梯度计算 ⭐⭐
题目: 给定一个两层 MLP,隐藏层使用 ReLU,输出层使用 Softmax + 交叉熵损失,手推梯度。
标准答案:
网络结构:
x → [W1, b1] → z1 → [ReLU] → a1 → [W2, b2] → z2 → [Softmax] → ŷ → [CE Loss] → L
前向传播:
z1 = W1 @ x + b1
a1 = max(0, z1) # ReLU
z2 = W2 @ a1 + b2
ŷ = softmax(z2)
L = -Σ y·log(ŷ) # 交叉熵损失
反向传播:
# 输出层梯度
∂L/∂z2 = ŷ - y # Softmax + CE 的梯度简化
# W2, b2 梯度
∂L/∂W2 = ∂L/∂z2 · a1^T
∂L/∂b2 = ∂L/∂z2
# 隐藏层梯度
∂L/∂a1 = W2^T · ∂L/∂z2
∂L/∂z1 = ∂L/∂a1 ⊙ (z1 > 0) # ReLU 导数
# W1, b1 梯度
∂L/∂W1 = ∂L/∂z1 · x^T
∂L/∂b1 = ∂L/∂z1Q42:为什么 ReLU 比 Sigmoid 更受欢迎?⭐
标准答案:
- 梯度消失:Sigmoid 导数最大 0.25,多层相乘后梯度消失;ReLU 正区间梯度恒为 1
- 计算效率:ReLU 只需比较操作,Sigmoid 需要指数运算
- 稀疏性:ReLU 会使约 50% 的神经元输出为 0,产生稀疏表示
- 收敛速度:ReLU 收敛速度比 Sigmoid 快约 6 倍(Krizhevsky et al.)
Dead ReLU 问题及解决:
- 问题:学习率过大导致神经元永久死亡
- 解决:使用 Leaky ReLU 或合理设置学习率
Q43:解释 BatchNorm 的原理和训练/推理的区别 ⭐⭐
标准答案:
原理: 对每个 mini-batch 的每个特征维度进行归一化,然后通过可学习的 γ 和 β 进行缩放和平移。
训练时:
- 使用当前 mini-batch 的均值和方差
- 更新 running mean 和 running variance
- 有正则化效果(因为每个 batch 的统计量不同)
推理时:
- 使用训练期间累积的 running mean 和 running variance
- 输出是确定性的
为什么推理时不能用 batch 统计量: 推理时 batch size 可能为 1,无法计算有意义的统计量。
Q44:什么是梯度裁剪?什么时候需要?⭐
标准答案:
梯度裁剪: 当梯度的范数超过阈值时,将其缩放到阈值范围内。
两种方式:
# 按范数裁剪(推荐)
torch.nn.utils.clip_grad_norm_(parameters, max_norm=1.0)
# 按值裁剪
torch.nn.utils.clip_grad_value_(parameters, clip_value=0.5)使用场景:
- RNN 训练(梯度爆炸常见)
- Transformer 训练初期
- 强化学习
Q45:如何选择优化器?SGD vs Adam ⭐⭐
标准答案:
| 特性 | SGD | Adam |
|---|---|---|
| 收敛速度 | 慢 | 快 |
| 泛化能力 | 通常更好 | 可能稍差 |
| 超参敏感 | 学习率需调 | 默认参数通常可用 |
| 内存需求 | 低 | 高(2倍) |
| 适用场景 | CV(配合调度器) | NLP、Transformer |
Adam 的优势:
- 自适应学习率
- 动量加速
- 对超参数不敏感
SGD + Momentum 的优势:
- 泛化能力通常更好
- 内存效率高
- 适合大规模 CV 任务
现代趋势:
- AdamW(权重衰减修正的Adam)是 Transformer 的标配
- SGD + Momentum 仍是 CV 领域的主流
Q46:什么是学习率预热(Warmup)?为什么 Transformer 需要?⭐⭐
标准答案:
学习率预热: 训练初期从小学习率逐渐增加到目标学习率。
Warmup阶段: lr = lr_max * (step / warmup_steps)
衰减阶段: lr = lr_max * sqrt(warmup_steps) / sqrt(step)为什么 Transformer 需要 Warmup:
- 初始参数随机:早期梯度不稳定,大学习率会导致参数"跑飞"
- 自注意力不稳定:注意力权重初期近似均匀,需要逐渐学习
- Adam 的自适应性不足:Adam 的二阶矩估计在初期不准确
Warmup 的效果:
- 训练更稳定
- 最终性能更好
- 减少对初始学习率的敏感性
Q47:什么是学习率调度(Learning Rate Schedule)?⭐⭐
标准答案:
常见调度策略:
1. Step Decay
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
# 每30个epoch学习率乘以0.12. Cosine Annealing
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# lr = lr_min + 0.5*(lr_max - lr_min)*(1 + cos(π*t/T))3. Cosine with Warmup(Transformer 标配)
# Warmup + Cosine Decay4. OneCycleLR
scheduler = OneCycleLR(optimizer, max_lr=0.01, total_steps=1000)为什么需要学习率调度:
- 初期需要大步长快速收敛
- 后期需要小步长精细调整
- 避免在最优解附近震荡
Q48:什么是权重衰减(Weight Decay)?和 L2 正则化有什么区别?⭐⭐
标准答案:
L2 正则化: 在损失函数中加入权重的 L2 范数
$$ L_{total} = L + \frac{\lambda}{2} |\mathbf{w}|^2 $$
梯度更新:
$$ \mathbf{w} \leftarrow \mathbf{w} - \eta (\nabla L + \lambda \mathbf{w}) $$
权重衰减: 直接在更新时衰减权重
$$ \mathbf{w} \leftarrow (1 - \lambda \eta) \mathbf{w} - \eta \nabla L $$
区别:
- 对于 SGD,两者等价
- 对于 Adam,两者不等价!
- L2 正则化会受到自适应学习率的影响
- 权重衰减是独立的
AdamW(Weight Decay 修正的 Adam):
# Adam + L2 正则化(错误方式)
# 权重衰减被自适应学习率缩放
# AdamW(正确方式)
# 权重衰减独立于梯度更新
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)Q49:什么是混合精度训练(Mixed Precision Training)?⭐⭐
标准答案:
混合精度训练: 同时使用 FP32 和 FP16(或 BF16)进行训练。
核心思想:
- 前向传播和反向传播使用 FP16(计算快,内存少)
- 权重更新使用 FP32(保持精度)
为什么需要:
- FP16 计算速度是 FP32 的 2-8 倍(GPU Tensor Core)
- FP16 内存占用是 FP32 的一半
- 可以训练更大的模型或使用更大的 batch size
关键技术:
1. Loss Scaling
# 放大 loss,防止 FP16 下溢
loss = loss * 1024 # scale factor
loss.backward()
# 缩小梯度
for p in model.parameters():
p.grad /= 10242. Master Weights
# FP32 主权重用于更新
master_weights = [p.float() for p in model.parameters()]
# FP16 权重用于前向/反向
fp16_weights = [p.half() for p in model.parameters()]PyTorch 实现:
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
output = model(input)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()BF16 vs FP16:
| 格式 | 指数位 | 尾数位 | 范围 | 精度 |
|---|---|---|---|---|
| FP32 | 8 | 23 | ±3.4e38 | 高 |
| FP16 | 5 | 10 | ±6.5e4 | 低 |
| BF16 | 8 | 7 | ±3.4e38 | 更低 |
BF16 范围与 FP32 相同,不容易溢出,是现代 LLM 的首选。
Q50:什么是梯度累积(Gradient Accumulation)?⭐
标准答案:
梯度累积: 在多个 mini-batch 上累积梯度后再更新参数,等效于更大的 batch size。
动机: 显存不足以放下大 batch size
实现:
accumulation_steps = 4 # 等效 batch size = 4 * real_batch_size
for i, (inputs, labels) in enumerate(dataloader):
loss = model(inputs, labels) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()注意事项:
- loss 要除以累积步数(保持梯度尺度一致)
- BatchNorm 在小 batch 上统计量不准确
- 随机种子会影响效果(每个 mini-batch 应该不同)
Q51:解释 Attention 机制的计算过程 ⭐⭐
标准答案:
Scaled Dot-Product Attention:
$$ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V $$
计算步骤:
# Q, K, V: (batch, seq_len, d_model)
# 1. 计算注意力分数
scores = Q @ K.transpose(-2, -1) # (batch, seq_len, seq_len)
# 2. 缩放
scores = scores / math.sqrt(d_k)
# 3. Mask(可选)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# 4. Softmax
attn_weights = F.softmax(scores, dim=-1)
# 5. 加权求和
output = attn_weights @ V # (batch, seq_len, d_model)为什么要除以 $\sqrt{d_k}$:
当 $d_k$ 较大时,$QK^T$ 的方差约为 $d_k$,导致 Softmax 输出趋近于 one-hot,梯度接近于 0。
除以 $\sqrt{d_k}$ 使方差保持为 1,Softmax 输出更平滑。
Q52:什么是参数高效微调(PEFT)?⭐⭐
标准答案:
PEFT(Parameter-Efficient Fine-Tuning): 只微调模型的一小部分参数,而不是全部参数。
主要方法:
1. LoRA(Low-Rank Adaptation)
# 冻结原始权重 W
# 添加低秩更新 ΔW = A @ B
# A: (d, r), B: (r, d), r << d
W' = W + ΔW = W + A @ B2. Prefix Tuning
- 在注意力计算中添加可学习的前缀向量
3. Adapter
- 在 Transformer 层中插入小型适配器模块
4. QLoRA
- 4-bit 量化 + LoRA
为什么需要 PEFT:
- 全量微调大模型成本高
- 显存需求大
- 容易过拟合小数据集
Q53:什么是模型量化(Quantization)?⭐⭐
标准答案:
量化: 将模型权重和激活从高精度(FP32)转换为低精度(INT8/INT4)。
量化类型:
1. 训练后量化(PTQ)
# 直接将训练好的模型量化
quantized_model = torch.quantization.quantize_dynamic(model, {nn.Linear}, dtype=torch.qint8)2. 量化感知训练(QAT)
# 训练过程中模拟量化
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
model_prepared = torch.quantization.prepare_qat(model)量化公式:
$$ x_q = \text{round}\left(\frac{x}{s}\right) + z $$
其中 $s$ 是缩放因子,$z$ 是零点。
INT8 量化效果:
- 模型大小减少 4x
- 推理速度提升 2-4x
- 精度损失通常 < 1%
LLM 量化:
- GPTQ:逐层量化,保留重要权重
- AWQ:激活感知量化
- GGML/GGUF:CPU 推理友好的量化格式
Q54:什么是知识蒸馏(Knowledge Distillation)?⭐⭐
标准答案:
知识蒸馏: 用大模型(教师)的知识训练小模型(学生)。
核心思想:
教师模型的 softmax 输出包含"暗知识"(dark knowledge)——类别之间的相似性关系。
损失函数:
$$ L = \alpha \cdot L_{CE}(y, \hat{y}_s) + (1 - \alpha) \cdot T^2 \cdot KL(\hat{y}_t^T | \hat{y}_s^T) $$
其中:
- $T$ 是温度参数(通常 2-20)
- $\hat{y}_t^T$ 是教师的软标签(温度 T 的 softmax)
- $\hat{y}_s^T$ 是学生的软标签
- $\alpha$ 是平衡系数
软标签:
$$ \hat{y}_i^T = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} $$
温度 T 的作用:
- T=1:标准 softmax
- T>1:更平滑的概率分布,暴露更多类别间关系
蒸馏的应用:
- DistilBERT:BERT 的 60% 参数,97% 性能
- TinyLLaMA:小参数量的 LLaMA
- 端侧部署:大模型蒸馏到可部署的小模型
Q55:深度学习中常见的正则化方法有哪些?⭐⭐
标准答案:
1. L1 正则化(Lasso)
$$ L_{total} = L + \lambda \sum_i |w_i| $$
- 产生稀疏权重(部分权重为 0)
- 适合特征选择
2. L2 正则化(Ridge / Weight Decay)
$$ L_{total} = L + \frac{\lambda}{2} \sum_i w_i^2 $$
- 权重趋向于小值
- 防止过拟合
3. Dropout
- 随机丢弃神经元
- 隐式集成学习
4. 数据增强
- 图像:翻转、旋转、裁剪、颜色变换
- 文本:回译、同义词替换、随机删除
5. Early Stopping
- 在验证集性能不再提升时停止训练
6. Batch Normalization
- 有隐式正则化效果
7. Label Smoothing
$$ y_{smooth} = (1 - \epsilon) \cdot y + \frac{\epsilon}{K} $$
- 防止模型过于自信
8. Mixup
$$ \tilde{x} = \lambda x_i + (1 - \lambda) x_j $$ $$ \tilde{y} = \lambda y_i + (1 - \lambda) y_j $$
正则化方法选择:
| 方法 | 适用场景 | 效果 |
|---|---|---|
| L2 | 通用 | 中等 |
| Dropout | 全连接层 | 好 |
| 数据增强 | 数据少时 | 好 |
| BN/LN | 深层网络 | 中等 |
| Label Smoothing | 分类任务 | 中等 |
总结
本文档覆盖了深度学习核心机制的 55 个关键问题,包括:
- 神经网络基础:从感知机到 MLP,激活函数演进
- 反向传播:链式法则、计算图、自动微分
- 梯度问题:消失、爆炸及解决方案
- 权重初始化:Xavier、He 初始化
- 归一化技术:BN、LN、RMSNorm
- 正则化:Dropout、残差连接
- 经典架构:CNN、RNN、LSTM、GRU
- 现代技术:混合精度、量化、蒸馏
面试准备建议:
- ⭐ 基础题必须熟练掌握
- ⭐⭐ 进阶题要能详细解释
- ⭐⭐⭐ 高级题要有自己的理解
核心公式必须掌握:
- 反向传播链式法则
- BN/LN 的计算公式
- 激活函数及其导数
- 卷积输出尺寸计算
- 注意力机制计算