Skip to content

深度学习核心机制

本文覆盖深度学习基础核心机制,以面试题 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 $$

关键要点:

  1. 存在性定理:只说明"存在"这样的网络,不说明如何找到
  2. 宽度 vs 深度:定理说的是宽度(神经元数量),实际中深度网络更高效
  3. 不保证泛化:能逼近训练集不代表泛化能力好
  4. 不保证可训练:存在不等于能通过梯度下降找到

实际意义:

  • 理论上证明了神经网络的强大表达能力
  • 但实践中,深度网络比网络更高效(参数量更少)
  • 这就是为什么现代深度学习强调"深度"而非"宽度"

深度 vs 宽度的效率对比:

某些函数:
- 深度网络:O(n) 个参数即可表示
- 宽度网络:需要 O(2^n) 个参数

例如:计算 x₁ AND x₂ AND ... AND xₙ
- 深度网络:O(n) 参数,O(log n) 层
- 单隐藏层:需要 2^n 个神经元

Q4:前向传播(Forward Propagation)的完整过程是什么?⭐

A: 前向传播是神经网络从输入到输出的计算过程,是反向传播的基础。

完整前向传播流程:

python
# 伪代码
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),每个节点保存了:

  • 该节点的输出值
  • 该节点的梯度函数(用于反向传播)
python
# 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 中流行的原因:

  1. 门控机制:通过门控(GLU)选择性地传递信息
  2. 更好的梯度流:乘法结构提供了更丰富的梯度信号
  3. 实验效果显著:Google 的论文(Shazeer 2020)表明,在相同参数量下,SwiGLU 的性能优于 ReLU/GELU
  4. 计算效率:虽然参数量增加(FFN 从 2 个矩阵变为 3 个),但效果提升足以补偿
python
# 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
回归无(线性)输出任意实数

隐藏层激活函数选择:

场景推荐激活函数原因
CNNReLU / GELU计算快,效果好
TransformerGELU / SwiGLU平滑,实验效果好
RNN/LSTMTanh输出范围 [-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} $$

反向传播的两个阶段:

  1. 前向传播:计算每一层的输出并保存(用于反向传播)
  2. 反向传播:从损失函数开始,逐层计算梯度并利用链式法则传递

为什么叫"反向":

前向: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 结构都可以处理
python
# 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/∂W1

Q10:前向模式 vs 反向模式自动微分有什么区别?⭐⭐⭐

A: 自动微分有两种模式,理解它们的区别对于理解深度学习框架至关重要。

前向模式(Forward Mode):

  • 从输入向输出传播导数
  • 一次前向传播计算一个输入方向的导数
  • 计算量:$O(1)$ 次前向传播 per 输入维度

反向模式(Reverse Mode):

  • 从输出向输入传播导数
  • 一次反向传播计算所有输入的导数
  • 计算量:$O(1)$ 次反向传播 for all 输入维度

对比:

特性前向模式反向模式
传播方向输入 → 输出输出 → 输入
计算次数O(n) 次(n=输入维度)O(1) 次
内存需求O(1)O(m)(m=计算图节点数)
适用场景输入少、输出多输入多、输出少
深度学习适用性❌ 不适合✅ 适合

为什么深度学习用反向模式:

神经网络通常有:

  • 大量输入(参数):百万到万亿级
  • 少量输出(损失):通常 1 个标量

这恰好是反向模式的优势场景:一次反向传播即可计算所有参数的梯度

具体例子:

python
# 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 的自动微分系统是其核心特性之一。

核心概念:

  1. Tensor:多维数组,支持自动微分
  2. 计算图:动态构建的 DAG(Dynamic Acyclic Graph)
  3. 梯度累积:梯度默认累积,需要手动清零

autograd 的关键属性:

python
x = torch.randn(3, requires_grad=True)

# requires_grad: 是否需要计算梯度
# grad: 存储梯度值
# grad_fn: 梯度函数(指向创建该tensor的操作)
# is_leaf: 是否是叶子节点(用户创建的tensor)

动态计算图 vs 静态计算图:

特性动态图(PyTorch)静态图(TF1.x)
构建时机运行时编译时
调试方便(标准Python)困难
灵活性高(支持控制流)
优化难以全局优化可以全局优化
序列化不直接支持支持

PyTorch 反向传播示例:

python
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

梯度累积与清零:

python
# 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():

python
# 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
python
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. 权重矩阵特征值 > 1:反复相乘导致指数增长
  2. 学习率过大:参数更新幅度过大
  3. 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)

python
# 按范数裁剪
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 中的问题:

  1. 序列长度不一致:NLP 中不同样本的序列长度不同,BN 沿序列维度计算统计量不合理
  2. Batch 依赖:BN 需要 batch 统计量,推理时使用 running mean/var,训练和推理行为不一致
  3. 小 batch size:Transformer 训练时 batch size 通常较小(按样本数算),BN 统计量不稳定
  4. 位置信息丢失:BN 对同一 batch 内所有位置做归一化,会破坏位置信息

LN 的优势:

python
# 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)
特性BatchNormLayerNorm
归一化维度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?" 指出:

  1. BN 并没有减少 ICS——实验表明 BN 后各层输入分布仍在变化
  2. BN 的真正作用是使损失函数的landscape 更平滑(更小的 Lipschitz 常数)
  3. 平滑的损失函数允许更大的学习率,从而加速训练

BN 为什么有效(现代理解):

  1. 损失曲面平滑化:使梯度更可预测
  2. 解耦参数尺度:权重的尺度不影响输出
  3. 隐式正则化:由于 batch 统计量的随机性

四、权重初始化

Q18:为什么不能用全零初始化?⭐

A: 全零初始化是神经网络训练的"禁忌"。

对称性问题(Symmetry Problem):

如果所有权重都初始化为相同的值(包括零),那么:

  1. 同一层的所有神经元在前向传播时计算相同的值
  2. 反向传播时,所有神经元接收到相同的梯度
  3. 因此,所有神经元的权重更新也相同
  4. 结果:同一层的所有神经元永远相同,失去了"多个神经元"的意义

数学证明:

假设 $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 会将一半的值置零)
python
# 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
python
# PyTorch 实现
nn.init.kaiming_uniform_(tensor, mode='fan_in', nonlinearity='relu')
nn.init.kaiming_normal_(tensor, mode='fan_in', nonlinearity='relu')

初始化方法选择指南:

激活函数推荐初始化
Sigmoid / TanhXavier
ReLU / LeakyReLUHe (Kaiming)
GELU / SwishHe (近似ReLU)

Q21:Transformer 的 Embedding 层是如何初始化的?⭐⭐

A: Transformer 的初始化对训练稳定性至关重要。

标准 Transformer(如 BERT)的初始化:

python
# 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 的初始化改进:

python
# 使用截断正态分布,避免极端值
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。

python
# 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, var

Q23: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 对比:

python
# 输入: (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 的优势:

  1. 不依赖 batch size
  2. 训练和推理行为一致
  3. 适合变长序列
  4. 不需要 running statistics
python
# 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 的关键区别:

  1. 不计算均值:RMSNorm 不减去均值
  2. 不计算偏置:只有缩放参数 $\gamma$,没有平移参数 $\beta$
  3. 计算更简单:省略了均值计算和减法操作

为什么 RMSNorm 更快:

操作LayerNormRMSNorm
计算均值✅ 需要❌ 不需要
减去均值✅ 需要❌ 不需要
计算方差✅ 需要❌ 不需要
计算RMS❌ 不需要✅ 需要
可学习参数$\gamma, \beta$$\gamma$

速度提升: 约 10-15%(在大规模模型中显著)

LLaMA 中的 RMSNorm:

python
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:

  1. 训练效果与 LayerNorm 相当
  2. 计算更快,节省显存
  3. 实现更简单
  4. 在大规模模型上,每一个百分点的效率提升都很重要

六、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
- 推理时不需要修改
python
# 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 Dropout0.1正则化输入表示
Attention Dropout0.1随机丢弃注意力连接
Residual Dropout0.1正则化残差连接
FFN Dropout0.1正则化 FFN

注意: 现代 LLM(如 LLaMA、GPT-3)通常不使用 Dropout,因为:

  • 训练数据量巨大,过拟合风险低
  • 更大的 batch size 本身有正则化效果
  • Dropout 会略微影响训练效率
python
# 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:

python
# 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 的优势:

  1. 训练更稳定:梯度流更直接
  2. 不需要 warmup:可以直接用较大学习率
  3. 更容易训练深层网络

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 的残差连接实现:

python
# 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 $$

常用配置:

python
# 保持尺寸不变 (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)

卷积的三个重要性质:

  1. 局部连接:每个输出只依赖输入的局部区域
  2. 权值共享:同一卷积核在所有位置共享参数
  3. 平移等变性:$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$:偏置项

示例:

python
# 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~60K5-
AlexNet~61M857.1%
VGG-16~138M1671.5%
ResNet-50~25.6M5076.0%
ResNet-152~60M15277.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. 通道数变换(升维/降维)

python
# 降维: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] → Output

1×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 倍!

python
# 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 x

Q36:CNN vs Transformer 有什么区别?⭐⭐

A: 这是现代深度学习的核心争论之一。

架构对比:

特性CNNTransformer
感受野局部 → 全局(堆叠)一步全局
位置编码隐式(卷积结构)显式(位置编码)
参数共享空间位置共享序列位置共享
计算复杂度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 对比:

特性LSTMGRU
门的数量3(遗忘、输入、输出)2(更新、重置)
状态数量2(h, c)1(h)
参数量较多较少(约少25%)
训练速度较慢较快
性能长序列更好短序列相当
python
# 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 的核心问题:

  1. 串行计算:$\mathbf{h}t$ 依赖 $\mathbf{h}$,无法并行
  2. 长距离依赖:梯度消失导致难以捕捉长距离关系
  3. 计算效率:序列长度 T,计算复杂度 O(T)

Transformer 的优势:

  1. 并行计算:所有位置同时计算,充分利用 GPU
  2. 全局注意力:一步就能看到序列中的所有位置
  3. 可扩展性:适合大规模并行训练

效率对比:

方面RNNTransformer
并行性❌ 串行✅ 完全并行
长距离依赖困难(梯度消失)容易(直接注意力)
训练速度快(GPU友好)
内存O(1) per stepO(n²) attention
推理速度O(T) per tokenO(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/∂z1

Q42:为什么 ReLU 比 Sigmoid 更受欢迎?⭐

标准答案:

  1. 梯度消失:Sigmoid 导数最大 0.25,多层相乘后梯度消失;ReLU 正区间梯度恒为 1
  2. 计算效率:ReLU 只需比较操作,Sigmoid 需要指数运算
  3. 稀疏性:ReLU 会使约 50% 的神经元输出为 0,产生稀疏表示
  4. 收敛速度: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:什么是梯度裁剪?什么时候需要?⭐

标准答案:

梯度裁剪: 当梯度的范数超过阈值时,将其缩放到阈值范围内。

两种方式:

python
# 按范数裁剪(推荐)
torch.nn.utils.clip_grad_norm_(parameters, max_norm=1.0)

# 按值裁剪
torch.nn.utils.clip_grad_value_(parameters, clip_value=0.5)

使用场景:

  1. RNN 训练(梯度爆炸常见)
  2. Transformer 训练初期
  3. 强化学习

Q45:如何选择优化器?SGD vs Adam ⭐⭐

标准答案:

特性SGDAdam
收敛速度
泛化能力通常更好可能稍差
超参敏感学习率需调默认参数通常可用
内存需求高(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:

  1. 初始参数随机:早期梯度不稳定,大学习率会导致参数"跑飞"
  2. 自注意力不稳定:注意力权重初期近似均匀,需要逐渐学习
  3. Adam 的自适应性不足:Adam 的二阶矩估计在初期不准确

Warmup 的效果:

  • 训练更稳定
  • 最终性能更好
  • 减少对初始学习率的敏感性

Q47:什么是学习率调度(Learning Rate Schedule)?⭐⭐

标准答案:

常见调度策略:

1. Step Decay

python
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
# 每30个epoch学习率乘以0.1

2. Cosine Annealing

python
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# lr = lr_min + 0.5*(lr_max - lr_min)*(1 + cos(π*t/T))

3. Cosine with Warmup(Transformer 标配)

python
# Warmup + Cosine Decay

4. OneCycleLR

python
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):

python
# 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

python
# 放大 loss,防止 FP16 下溢
loss = loss * 1024  # scale factor
loss.backward()
# 缩小梯度
for p in model.parameters():
    p.grad /= 1024

2. Master Weights

python
# FP32 主权重用于更新
master_weights = [p.float() for p in model.parameters()]
# FP16 权重用于前向/反向
fp16_weights = [p.half() for p in model.parameters()]

PyTorch 实现:

python
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:

格式指数位尾数位范围精度
FP32823±3.4e38
FP16510±6.5e4
BF1687±3.4e38更低

BF16 范围与 FP32 相同,不容易溢出,是现代 LLM 的首选。


Q50:什么是梯度累积(Gradient Accumulation)?⭐

标准答案:

梯度累积: 在多个 mini-batch 上累积梯度后再更新参数,等效于更大的 batch size。

动机: 显存不足以放下大 batch size

实现:

python
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 $$

计算步骤:

python
# 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)

python
# 冻结原始权重 W
# 添加低秩更新 ΔW = A @ B
# A: (d, r), B: (r, d), r << d

W' = W + ΔW = W + A @ B

2. Prefix Tuning

  • 在注意力计算中添加可学习的前缀向量

3. Adapter

  • 在 Transformer 层中插入小型适配器模块

4. QLoRA

  • 4-bit 量化 + LoRA

为什么需要 PEFT:

  • 全量微调大模型成本高
  • 显存需求大
  • 容易过拟合小数据集

Q53:什么是模型量化(Quantization)?⭐⭐

标准答案:

量化: 将模型权重和激活从高精度(FP32)转换为低精度(INT8/INT4)。

量化类型:

1. 训练后量化(PTQ)

python
# 直接将训练好的模型量化
quantized_model = torch.quantization.quantize_dynamic(model, {nn.Linear}, dtype=torch.qint8)

2. 量化感知训练(QAT)

python
# 训练过程中模拟量化
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 个关键问题,包括:

  1. 神经网络基础:从感知机到 MLP,激活函数演进
  2. 反向传播:链式法则、计算图、自动微分
  3. 梯度问题:消失、爆炸及解决方案
  4. 权重初始化:Xavier、He 初始化
  5. 归一化技术:BN、LN、RMSNorm
  6. 正则化:Dropout、残差连接
  7. 经典架构:CNN、RNN、LSTM、GRU
  8. 现代技术:混合精度、量化、蒸馏

面试准备建议:

  • ⭐ 基础题必须熟练掌握
  • ⭐⭐ 进阶题要能详细解释
  • ⭐⭐⭐ 高级题要有自己的理解

核心公式必须掌握:

  • 反向传播链式法则
  • BN/LN 的计算公式
  • 激活函数及其导数
  • 卷积输出尺寸计算
  • 注意力机制计算

LLM 应用 & Agent 开发面试准备