系列回顾:第17篇讲了梯度下降的几何原理,第18篇讲了 SGD 的统计性质和 mini-batch 的噪声正则化效果。两篇都暗含一个缺陷:所有参数用同一个学习率。但神经网络里不同参数的梯度量级可以相差几个数量级——嵌入层参数的梯度极小,某些全连接层的梯度很大。用同一个 $\alpha$ 无法同时让所有参数收敛。这篇讲解决这个问题的核心算法:Adam——DeepSeek V3 实际使用的优化器,以及它的所有数学细节。
朴素 SGD 的更新规则:$\theta \leftarrow \theta - \alpha g$
问题一:所有参数共用一个学习率
神经网络里,不同参数的梯度量级差异极大:
同一个学习率 $\alpha$:对梯度小的参数(如低频词嵌入),步长过小,几乎不更新;对梯度大的参数,步长过大,容易振荡。
问题二:梯度方向噪声大,损失曲面条件数大
如第17篇所示,损失曲面在不同方向上的曲率差异大(高条件数),导致 SGD 走"之字形"路径,浪费很多步骤。
Adam 同时解决了这两个问题:用梯度的历史统计信息(一阶矩 + 二阶矩)自适应调整每个参数的有效学习率。
普通梯度下降像一个没有惯性的质点:每步只看当前坡度,完全不考虑之前的运动方向。在曲率大的方向上,质点来回振荡,浪费步骤。
动量法(Momentum) 引入"惯性":质点有速度,不只根据当前坡度改变方向,还会保留之前的运动趋势。
引入速度向量(一阶矩)$m_t$,它是过去梯度的指数加权移动平均(EMA):
$$m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t$$
参数更新:
$$\theta_t = \theta_{t-1} - \alpha m_t$$
在"之字形"方向上:
效果:横向振荡被抑制,纵向加速前进——类似物理上小球沿碗底滚向最低点,不是在碗壁上弹跳。
展开递推:
$$m_t = (1-\beta_1)\sum_{k=0}^{t-1} \beta_1^k g_{t-k} + \beta_1^t m_0$$
忽略初始化项($m_0 = 0$):
$$m_t = (1-\beta_1)\sum_{k=0}^{t-1} \beta_1^k g_{t-k}$$
权重 $\beta_1^k$ 随 $k$(距当前的步数)指数衰减,有效记忆长度约为:
$$\tau_{eff} = \frac{1}{1-\beta_1}$$
Adam 默认 $\beta_1 = 0.9$,DeepSeek V3 同样使用 $\beta_1 = 0.9$。
动量法减少了振荡,但所有参数的学习率仍然相同。梯度小的参数步长仍然太小。
RMSProp(Root Mean Square Propagation) 提出:用梯度的平方的指数加权移动平均来自适应调整学习率。
维护梯度平方的指数加权移动平均(二阶矩估计):
$$v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2$$
(逐元素平方,$g_t^2$ 表示 $g_t \odot g_t$)
$$\theta_t = \theta_{t-1} - \frac{\alpha}{\sqrt{v_t} + \varepsilon} \odot g_t$$
类比:调音师调节各乐器的音量——
最终,不同量级的参数都能以"合适的节奏"更新,像一个均衡器让所有声部和谐。
Adam(Adaptive Moment Estimation) = 动量法(一阶矩)+ RMSProp(二阶矩)+ 偏差修正。
初始化:$m_0 = 0$,$v_0 = 0$,$t = 0$
每步更新(给定梯度 $g_t$):
第一步:更新一阶矩(梯度的 EMA)
第二步:更新二阶矩(梯度平方的 EMA)
第三步:偏差修正
$$\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}$$
第四步:参数更新
$$\theta_t = \theta_{t-1} - \alpha \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}$$
初始化 $m_0 = v_0 = 0$,在训练初期($t$ 很小时),EMA 被初始的零值"偏置":
$$m_1 = \beta_1 \cdot 0 + (1-\beta_1) g_1 = (1-\beta_1) g_1$$
$m_1$ 比真实梯度小了 $(1-\beta_1)$ 倍。$v_1$ 同理。
偏差修正把这个系统性低估纠正回来:
$$\hat{m}_t = \frac{m_t}{1-\beta_1^t}$$
当 $t=1$:$\hat{m}_1 = \frac{(1-\beta_1)g_1}{1-\beta_1} = g_1$,完全纠正。
当 $t \to \infty$:$\beta_1^t \to 0$,$1-\beta_1^t \to 1$,修正因子趋向 1,偏差消失。
没有偏差修正,训练初期有效学习率被严重低估,参数几乎不更新,起步很慢。
合并后,每个参数 $j$ 的有效学习率:
$$\alpha_j^{eff} = \alpha \cdot \frac{\hat{m}_{t,j}}{\sqrt{\hat{v}_{t,j}} + \varepsilon}$$
当梯度稳定(方向不变)时:$\hat{m}_{t,j} \approx g_{t,j}$,$\hat{v}_{t,j} \approx g_{t,j}^2$,有效学习率趋向:
$$\alpha_j^{eff} \approx \alpha \cdot \frac{g_{t,j}}{\sqrt{g_{t,j}^2} + \varepsilon} \approx \alpha \cdot \text{sign}(g_{t,j})$$
Adam 的有效步长接近常数 $\alpha$,不依赖梯度量级!
这保证了所有参数都以近似相同的"归一化步长"更新,从根本上解决了梯度量级差异问题。
DeepSeek V3 技术报告中的优化器配置:
AdamW(Adam with Decoupled Weight Decay) 是对 Adam 的一个重要修正。
问题:原始 Adam 中,权重衰减(L2 正则化)通过在梯度上加 $\lambda\theta$ 实现:
$$g_t \leftarrow g_t + \lambda\theta_{t-1}$$
然后用这个修改后的梯度更新 $m_t$ 和 $v_t$。
问题所在:权重衰减项 $\lambda\theta$ 也被二阶矩 $v_t$ 自适应缩放了——梯度小的参数,权重衰减项被放大(自适应缩放);梯度大的参数,权重衰减项被缩小。权重衰减的实际效果和 $\theta$ 的梯度大小耦合在一起,失去了正则化的统一性。
AdamW 的解决方案:把权重衰减从梯度计算中解耦,直接作用在参数上:
$$\theta_t = \theta_{t-1} - \alpha \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon} - \alpha \lambda \theta_{t-1}$$
权重衰减项 $\alpha\lambda\theta_{t-1}$ 独立于梯度统计,每个参数以统一的比例 $\alpha\lambda$ 收缩,恢复了 L2 正则化的本来含义。
实验表明,AdamW 在大模型训练(BERT、GPT、DeepSeek)上比原始 Adam 泛化性更好,几乎已经成为 LLM 训练的标准选择。
这个选择背后有深刻的工程考量:
$\beta_2 = 0.999$ 的问题(针对大模型训练):
$\beta_2 = 0.95$ 的优势:
这是 DeepSeek 团队从 V2 到 V3 训练经验中沉淀下来的工程选择,不在原始 Adam 论文里,需要大规模实验才能验证。
定理(Kingma & Ba, 2014 修正版):在一定假设下(梯度有界、学习率递减),Adam 在非凸随机优化问题上的收敛率为:
$$\frac{1}{T}\sum_{t=1}^T \mathbb{E}[\|\nabla L(\theta_t)\|_1] = O\left(\frac{\ln T}{\sqrt{T}}\right)$$
收敛率为 $O(\ln T / \sqrt{T})$,仅比 SGD 的 $O(1/\sqrt{T})$ 多一个对数因子,但实践中收敛速度通常远快于 SGD(因为自适应学习率让每一步都更有效)。
自然梯度(Natural Gradient) 是信息几何中的最优梯度方向:用 Fisher 信息矩阵 $F$ 对梯度做预处理:
$$\tilde{g} = F^{-1} g$$
Fisher 信息矩阵的第 $j$ 个对角元素:
$$F_{jj} = \mathbb{E}\left[\left(\frac{\partial \log p(x;\theta)}{\partial \theta_j}\right)^2\right] = \mathbb{E}[g_j^2]$$
这正是 Adam 的二阶矩 $v_t$ 在期望下估计的量!
Adam 是 Fisher 信息矩阵对角近似下的自然梯度下降。
Adam 用 $\sqrt{v_t}$(梯度平方均值的平方根)近似 $\sqrt{F_{jj}}$,做对角 Fisher 预处理:
$$\theta \leftarrow \theta - \alpha \cdot \frac{g}{\sqrt{F_{diag}}} \approx \theta - \alpha \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t}}$$
直觉:Fisher 信息大的方向(梯度方差大,曲率大)应该用小步长;Fisher 信息小的方向(梯度方差小,曲率小)可以用大步长。Adam 自动做到了这一点。
问题一:泛化性有时不如 SGD
有研究表明,在某些任务上(特别是图像分类),Adam 比精调过的 SGD+Momentum 泛化性差。
原因:Adam 的强自适应性让它快速收敛,但可能收敛到泛化性较差的"尖谷"。SGD 的相对各向同性噪声更倾向宽谷。
DeepSeek 的做法:大语言模型训练中这个问题不显著(任务太复杂,SGD 根本收敛不了),Adam/AdamW 是标准选择。
问题二:内存开销
Adam 需要为每个参数维护 $m_t$ 和 $v_t$ 两个状态,内存开销是参数本身的 3 倍(参数 + 一阶矩 + 二阶矩)。
DeepSeek V3(671B 参数),BF16 参数约 $671 \times 10^9 \times 2 \text{ bytes} \approx 1.3\text{ TB}$;Adam 状态通常用 FP32,约 $671 \times 10^9 \times 4 \times 2 \text{ bytes} \approx 5.4\text{ TB}$。
这也是近年来低内存优化器(Adafactor、SM3、GaLore)被研究的动机——在参数量极大时,减少优化器状态的内存占用。
$$v_t = v_{t-1} + g_t^2, \quad \theta_t = \theta_{t-1} - \frac{\alpha}{\sqrt{v_t}+\varepsilon} g_t$$
区别:$v_t$ 是梯度平方的累积和,而非指数加权平均。
问题:$v_t$ 单调增大,有效学习率单调减小,最终趋向零——模型会"学习停滞",不适合深度学习的长期训练。
RMSProp 用指数衰减替代累积和,解决了这个问题;Adam 在此基础上加了动量。
$$m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t$$ $$\theta_t = \theta_{t-1} - \alpha \cdot \text{sign}(\beta_1 m_{t-1} + (1-\beta_1) g_t) - \alpha\lambda\theta_{t-1}$$
特点:只用符号梯度($\text{sign}$,每步走固定幅度 $\pm\alpha$),不维护二阶矩,内存比 Adam 少 1/3。
在某些视觉和语言任务上与 Adam 性能相近,但内存更省。目前在 LLM 训练中使用不如 AdamW 普遍。
2024-2025 年出现的 Muon(Momentum + Nesterov + Orthogonal update)等优化器,在特定任务上显示出潜力,但目前 DeepSeek V3 仍使用 AdamW。
import numpy as np import torch import torch.nn as nn np.random.seed(42) torch.manual_seed(42) # ===== 1. 从零实现 Adam ===== print("===== 从零实现 Adam(对比 SGD)=====\n") class AdamOptimizer: def __init__(self, params, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8, weight_decay=0.0): self.params = params self.lr = lr self.beta1 = beta1 self.beta2 = beta2 self.eps = eps self.weight_decay = weight_decay self.m = [np.zeros_like(p) for p in params] # 一阶矩 self.v = [np.zeros_like(p) for p in params] # 二阶矩 self.t = 0 def step(self, grads): self.t += 1 updated_params = [] for i, (p, g) in enumerate(zip(self.params, grads)): # 权重衰减(AdamW 风格:解耦) g_wd = g + self.weight_decay * p # 更新一阶矩 self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * g_wd # 更新二阶矩 self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * g_wd**2 # 偏差修正 m_hat = self.m[i] / (1 - self.beta1**self.t) v_hat = self.v[i] / (1 - self.beta2**self.t) # 参数更新(AdamW:权重衰减单独处理) update = self.lr * m_hat / (np.sqrt(v_hat) + self.eps) updated_params.append(p - update - self.lr * self.weight_decay * p) return updated_params # 简单二次优化问题:L(θ) = (θ₁ - 3)² + 100(θ₂ - 1)² # 第二个参数梯度比第一个大100倍 def loss_fn(theta): return (theta[0] - 3)**2 + 100*(theta[1] - 1)**2 def grad_fn(theta): return np.array([2*(theta[0] - 3), 200*(theta[1] - 1)]) theta_true = np.array([3.0, 1.0]) theta_0 = np.array([0.0, 0.0]) def run_optimizer(theta_init, opt_name, lr, n_steps=500, **kwargs): theta = theta_init.copy() if opt_name == "SGD": losses = [] for _ in range(n_steps): g = grad_fn(theta) theta = theta - lr * g losses.append(loss_fn(theta)) return losses elif opt_name == "SGD+Momentum": m = np.zeros_like(theta) losses = [] beta1 = kwargs.get("beta1", 0.9) for _ in range(n_steps): g = grad_fn(theta) m = beta1 * m + (1 - beta1) * g theta = theta - lr * m losses.append(loss_fn(theta)) return losses elif opt_name == "Adam": opt = AdamOptimizer([theta.copy()], lr=lr, beta1=kwargs.get("beta1", 0.9), beta2=kwargs.get("beta2", 0.999)) losses = [] th = [theta.copy()] for _ in range(n_steps): g = [grad_fn(th[0])] th = opt.step(g) losses.append(loss_fn(th[0])) return losses n = 500 configs = [ ("SGD", 0.001), ("SGD", 0.005), ("SGD+Momentum", 0.001), ("Adam", 0.01), ("Adam(β₂=0.95)", 0.01), ] print(f"优化目标: L(θ)=(θ₁-3)²+100(θ₂-1)²,θ₂方向梯度是θ₁的100倍") print(f"\n{'优化器':18} | {'lr':>8} | {'第10步':>10} | {'第50步':>10} | {'第200步':>10} | {'第500步':>10}") print("-" * 75) for name, lr in configs: beta2 = 0.95 if "0.95" in name else 0.999 hist = run_optimizer(theta_0.copy(), name.split("(")[0], lr, n, beta2=beta2) print(f"{name:18} | {lr:>8.3f} | {hist[9]:>10.4f} | {hist[49]:>10.4f} | " f"{hist[199]:>10.4f} | {hist[-1]:>10.6f}") print(f"\n→ SGD(lr=0.001)在θ₂方向步长太小,收敛极慢") print(f"→ SGD(lr=0.005)在θ₂方向振荡(梯度大,步长大)") print(f"→ Adam以相同lr=0.01同时照顾两个方向,快速收敛") # ===== 2. 偏差修正的重要性 ===== print("\n===== 偏差修正的效果 =====\n") class AdamNoBiasCorr: """不做偏差修正的 Adam""" def __init__(self, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8): self.lr, self.beta1, self.beta2, self.eps = lr, beta1, beta2, eps self.m = self.v = 0.0 def step(self, g): self.m = self.beta1 * self.m + (1 - self.beta1) * g self.v = self.beta2 * self.v + (1 - self.beta2) * g**2 return self.lr * self.m / (np.sqrt(self.v) + self.eps) # 无偏差修正 class AdamWithBiasCorr: """有偏差修正的 Adam""" def __init__(self, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8): self.lr, self.beta1, self.beta2, self.eps = lr, beta1, beta2, eps self.m = self.v = 0.0 self.t = 0 def step(self, g): self.t += 1 self.m = self.beta1 * self.m + (1 - self.beta1) * g self.v = self.beta2 * self.v + (1 - self.beta2) * g**2 m_hat = self.m / (1 - self.beta1**self.t) v_hat = self.v / (1 - self.beta2**self.t) return self.lr * m_hat / (np.sqrt(v_hat) + self.eps) g_const = 1.0 # 假设梯度恒为1,观察初期有效步长 opt_no_corr = AdamNoBiasCorr(lr=0.01) opt_with_corr = AdamWithBiasCorr(lr=0.01) print(f"{'步数':>5} | {'无偏差修正的步长':>16} | {'有偏差修正的步长':>16} | {'比值'}") print("-" * 55) for t in [1, 2, 3, 5, 10, 20, 50, 100]: for _ in range(t if t == 1 else 1): if t == 1: s_no = opt_no_corr.step(g_const) s_with = opt_with_corr.step(g_const) # 重新初始化并跑到第t步 opt_no = AdamNoBiasCorr(lr=0.01) opt_wi = AdamWithBiasCorr(lr=0.01) for _ in range(t): s_no = opt_no.step(g_const) s_wi = opt_wi.step(g_const) print(f"{t:>5} | {s_no:>16.6f} | {s_wi:>16.6f} | {s_no/s_wi:.4f}") print(f"\n→ 初期无偏差修正的步长远小于有修正(被零初始化拉低)") print(f"→ 经过足够多步后两者收敛(偏差消失)") # ===== 3. β₂ = 0.999 vs β₂ = 0.95 的响应速度 ===== print("\n===== β₂对有效学习率的影响 =====\n") # 模拟:前50步梯度=1,后50步梯度突然变为0.01(梯度骤减) # 观察有效学习率的响应速度 def simulate_effective_lr(beta2, steps=120, g_switch_step=50): v = 0.0 eff_lrs = [] lr, eps = 0.01, 1e-8 for t in range(1, steps+1): g = 1.0 if t <= g_switch_step else 0.01 # 梯度突变 v = beta2 * v + (1 - beta2) * g**2 v_hat = v / (1 - beta2**t) eff_lr = lr / (np.sqrt(v_hat) + eps) eff_lrs.append(eff_lr) return eff_lrs lrs_999 = simulate_effective_lr(0.999) lrs_95 = simulate_effective_lr(0.95) print("梯度在第50步从1.0骤降到0.01,有效学习率的恢复速度:") print(f"\n{'步数':>6} | {'β₂=0.999有效lr':>14} | {'β₂=0.95有效lr':>14} | {'差异倍数'}") print("-" * 52) for t in [49, 50, 55, 60, 70, 80, 100, 120]: r999, r95 = lrs_999[t-1], lrs_95[t-1] print(f"{t:>6} | {r999:>14.6f} | {r95:>14.6f} | {r95/r999:.2f}×") print(f"\n→ 梯度骤降后,β₂=0.95能在约20步内恢复大学习率") print(f"→ β₂=0.999需要约1000步才能完全恢复(记忆太长)") print(f"→ DeepSeek V3用β₂=0.95,使学习率调整更灵活") # ===== 4. AdamW vs Adam:权重衰减的解耦效果 ===== print("\n===== AdamW vs Adam:权重衰减解耦 =====\n") torch.manual_seed(0) # 构造一个简单的线性回归问题 n_samples = 100 X_data = torch.randn(n_samples, 10) true_w = torch.randn(10) * 3 # 真实参数较大 y_data = X_data @ true_w + 0.1 * torch.randn(n_samples) def train_optimizer(optimizer_class, **opt_kwargs): model = nn.Linear(10, 1, bias=False) nn.init.ones_(model.weight) # 从全1初始化(较大值,权重衰减效果明显) opt = optimizer_class(model.parameters(), **opt_kwargs) losses, weight_norms = [], [] for step in range(300): pred = model(X_data).squeeze() loss = nn.MSELoss()(pred, y_data) opt.zero_grad() loss.backward() opt.step() if step % 30 == 0: losses.append(loss.item()) weight_norms.append(model.weight.norm().item()) return losses, weight_norms wd = 0.01 losses_adam, norms_adam = train_optimizer(torch.optim.Adam, lr=0.01, betas=(0.9, 0.999), weight_decay=wd) losses_adamw, norms_adamw = train_optimizer(torch.optim.AdamW, lr=0.01, betas=(0.9, 0.999), weight_decay=wd) print(f"权重衰减系数: {wd}") print(f"{'步数':>6} | {'Adam损失':>10} | {'AdamW损失':>10} | {'Adam‖w‖':>10} | {'AdamW‖w‖':>10}") print("-" * 55) for i, step in enumerate(range(0, 300, 30)): print(f"{step:>6} | {losses_adam[i]:>10.4f} | {losses_adamw[i]:>10.4f} | " f"{norms_adam[i]:>10.4f} | {norms_adamw[i]:>10.4f}") print(f"\n→ AdamW的权重范数通常更小(正则化更彻底)") print(f"→ 在大模型训练中,AdamW泛化性系统性优于Adam") # ===== 5. DeepSeek V3 配置的 Adam 更新模拟 ===== print("\n===== DeepSeek V3 风格的 Adam 更新 =====\n") class DeepSeekAdamW: """DeepSeek V3 的 AdamW 配置""" def __init__(self, lr=4.2e-4, beta1=0.9, beta2=0.95, eps=1e-8, weight_decay=0.1): self.lr, self.beta1, self.beta2 = lr, beta1, beta2 self.eps, self.wd = eps, weight_decay self.m = self.v = 0.0 self.t = 0 def step(self, theta, g): self.t += 1 self.m = self.beta1 * self.m + (1 - self.beta1) * g self.v = self.beta2 * self.v + (1 - self.beta2) * g**2 m_hat = self.m / (1 - self.beta1**self.t) v_hat = self.v / (1 - self.beta2**self.t) # AdamW: 权重衰减解耦 theta_new = (theta - self.lr * m_hat / (np.sqrt(v_hat) + self.eps) - self.lr * self.wd * theta) return theta_new # 模拟一个参数的更新过程 opt = DeepSeekAdamW() theta = 1.0 # 初始参数值 np.random.seed(0) print(f"模拟 DeepSeek V3 风格 AdamW 更新(β₁=0.9, β₂=0.95, lr=4.2e-4, wd=0.1)") print(f"\n{'步数':>5} | {'参数θ':>12} | {'梯度g':>10} | {'一阶矩m̂':>12} | {'二阶矩v̂':>12} | {'有效lr':>10}") print("-" * 72) for t in range(1, 21): g = np.random.randn() * 0.1 # 模拟随机梯度 old_theta = theta theta = opt.step(theta, g) m_hat = opt.m / (1 - opt.beta1**opt.t) v_hat = opt.v / (1 - opt.beta2**opt.t) eff_lr = opt.lr / (np.sqrt(v_hat) + opt.eps) if t <= 10 or t == 20: print(f"{t:>5} | {old_theta:>12.6f} | {g:>10.6f} | {m_hat:>12.6f} | " f"{v_hat:>12.6f} | {eff_lr:>10.2e}")
这篇文章从朴素 SGD 的两个根本问题出发,完整推导了 Adam 的每一个组件,并深入分析了 DeepSeek V3 的优化器配置。
第一,动量法(一阶矩 EMA)解决"之字形振荡":历史梯度的指数加权平均在振荡方向上正负抵消,在持续下降方向上累积加速。有效记忆长度 $\frac{1}{1-\beta_1}$,默认 $\beta_1=0.9$ 对应记忆 10 步。
第二,RMSProp(二阶矩 EMA)解决"梯度量级不均":用梯度平方的指数加权均值做自适应分母,梯度大的参数自动缩小学习率,梯度小的参数自动放大学习率,最终有效步长接近常数 $\alpha$,与梯度量级无关。
第三,偏差修正解决"初期零初始化偏置":除以 $(1-\beta^t)$ 把 EMA 从零初始化的低估纠正回来,确保训练初期就有合理的有效学习率。
第四,AdamW 把权重衰减从梯度计算中解耦,直接作用在参数上,恢复了 L2 正则化的本来含义,泛化性系统性优于 Adam。
第五,DeepSeek V3 使用 $\beta_2=0.95$ 而非默认的 $0.999$,核心原因是大模型训练中梯度分布变化快,更短的有效记忆(约 20 步 vs 1000 步)让自适应学习率能更快响应梯度分布的变化,配合梯度裁剪保持稳定。
第六,Adam 是 Fisher 信息矩阵对角近似下的自然梯度下降,$v_t$ 近似了每个参数的 Fisher 信息,Adam 的自适应步长本质上是在参数空间的信息几何曲率下做归一化更新。
下一篇预告:
第20篇,我们讲正则化与负载均衡:L1、L2 与 MoE 专家负载均衡损失。
权重衰减(L2 正则化)我们已经提到了很多次,这篇把它和 L1 正则化一起讲透——稀疏性从哪里来、为什么 L1 能产生稀疏解但 L2 不能、Lasso 和 Ridge 回归的几何直觉。然后联系到 DeepSeek V3 的 MoE 架构:专家负载均衡损失是另一种正则化,防止某些专家被过度使用,数学形式和 L1/L2 有深刻的相似性。
Adam 不是魔法,是两个古老思想的精确结合:惯性(动量)和自适应(归一化)。把梯度的一阶矩除以二阶矩的平方根,听起来像是任意的工程选择,但背后是信息几何的深刻原理——在参数空间的"自然坐标系"里,每步走同样的距离,而不是在人工的笛卡尔坐标系里走同样的步长。这是 2014 年的一篇论文,但它训练了 2024 年最好的语言模型。
还没有评论,来抢沙发吧!
博客管理员
40 篇文章
还没有评论,来抢沙发吧!