系列回顾:前三篇我们把概率基础、条件概率与贝叶斯定理、常见概率分布都讲透了。现在有一个自然的问题:分布的参数是怎么从数据里估计出来的?比如语言模型的参数 $\theta$,到底是按什么标准"学"出来的?这篇文章回答这个问题——最大似然估计(MLE) 是连接"概率模型"和"训练目标"的桥梁,是理解为什么交叉熵是分类损失、为什么 MSE 是回归损失的统一框架。
我们已经知道:
但一个问题始终没有正面回答:我们用什么标准来确定参数 $\theta$ 的值?
直觉上,好的参数应该让模型"解释"训练数据的能力越强越好——即在这组参数下,观测到训练数据的概率应该尽可能大。
这就是最大似然估计(Maximum Likelihood Estimation,MLE) 的核心思想。
它的重要性在于:只要你对数据的概率分布做了一个假设,MLE 自动给出对应的损失函数。 分类分布假设 → 交叉熵损失;高斯噪声假设 → MSE 损失;拉普拉斯噪声假设 → MAE 损失。损失函数不是经验配方,是概率假设的数学推论。
同一个函数 $P(x; \theta)$,有两种理解方式:
概率视角:$\theta$ 固定,$x$ 是变量。给定参数,某个数据出现的概率是多少?
似然视角:$x$ 固定(已经观测到的数据),$\theta$ 是变量。给定数据,某组参数有多"合理"?
似然函数(Likelihood Function) $L(\theta)$ 就是用似然视角看同一个函数:
$$L(\theta) = P(\mathcal{D}; \theta)$$
$\mathcal{D}$ 是观测到的数据集,$L(\theta)$ 衡量"在参数 $\theta$ 下,观测到这批数据的概率"。
设训练数据 $\mathcal{D} = \{x^{(1)}, x^{(2)}, \ldots, x^{(n)}\}$,假设样本独立同分布(i.i.d.)——每个样本独立从同一个分布里采样。
联合概率等于各样本概率的乘积:
$$L(\theta) = P(\mathcal{D}; \theta) = \prod_{i=1}^n P(x^{(i)}; \theta)$$
最大似然估计:找让似然函数最大的参数:
$$\hat{\theta}_{MLE} = \arg\max_\theta L(\theta) = \arg\max_\theta \prod_{i=1}^n P(x^{(i)}; \theta)$$
翻译成大白话:找一组参数,让训练数据"最有可能"出现。
连乘在数值上会迅速下溢(很多小于 1 的概率相乘,结果趋向于 0),而且对乘积求导比对加法复杂。
标准做法:取对数,把连乘变成求和:
$$\ell(\theta) = \log L(\theta) = \sum_{i=1}^n \log P(x^{(i)}; \theta)$$
因为 $\log$ 是单调递增函数,$\arg\max_\theta L(\theta) = \arg\max_\theta \ell(\theta)$,两者给出同样的 $\hat{\theta}_{MLE}$。
实际训练中,最小化负对数似然(Negative Log-Likelihood,NLL):
$$\text{NLL}(\theta) = -\ell(\theta) = -\sum_{i=1}^n \log P(x^{(i)}; \theta)$$
最小化 NLL = 最大化对数似然 = 最大化似然 = MLE。
多分类任务:输入 $x$,标签 $y \in \{1, 2, \ldots, K\}$。
模型输出 Softmax 概率 $\hat{p}_k = P(y=k \mid x; \theta)$,满足 $\sum_k \hat{p}_k = 1$。
条件似然——给定输入 $x$,标签 $y$ 的概率:
$$P(y \mid x; \theta) = \prod_{k=1}^K \hat{p}_k^{\mathbb{1}[y=k]}$$
其中 $\mathbb{1}[y=k]$ 是指示函数($y=k$ 时为 1,否则为 0)。这个乘积只有一项非平凡:当 $k$ 等于真实标签时,贡献 $\hat{p}_y^1 = \hat{p}_y$;其余项是 $\hat{p}_k^0 = 1$。
所以 $P(y \mid x; \theta) = \hat{p}_y$——就是模型对真实类别的预测概率。
对 $n$ 个样本的对数似然:
$$\ell(\theta) = \sum_{i=1}^n \log P(y^{(i)} \mid x^{(i)}; \theta) = \sum_{i=1}^n \log \hat{p}_{y^{(i)}}$$
用 one-hot 标签向量 $\vec{y}^{(i)}$(真实类别位置为 1,其余为 0)重写:
$$\ell(\theta) = \sum_{i=1}^n \sum_{k=1}^K y_k^{(i)} \log \hat{p}_k^{(i)}$$
负对数似然(除以 $n$ 取均值):
$$\text{NLL}(\theta) = -\frac{1}{n}\sum_{i=1}^n \sum_{k=1}^K y_k^{(i)} \log \hat{p}_k^{(i)}$$
这正是交叉熵损失!
$$\text{NLL} = \frac{1}{n}\sum_{i=1}^n H(\vec{y}^{(i)}, \hat{\vec{p}}^{(i)}) = \text{Cross-Entropy Loss}$$
结论:最小化分类任务的交叉熵损失,在数学上等价于对分类分布做最大似然估计。 这不是经验总结,是必然的数学推论。
三分类,真实标签是"猫"(类别 0):
模型A(好):预测 $\hat{p} = [0.9, 0.07, 0.03]$
$$\text{Loss}_A = -\log 0.9 \approx 0.105$$
模型B(差):预测 $\hat{p} = [0.3, 0.5, 0.2]$
$$\text{Loss}_B = -\log 0.3 \approx 1.204$$
模型A对真实类别更有把握,损失更小。当 $\hat{p}_{true} \to 1$,损失 $\to 0$;当 $\hat{p}_{true} \to 0$,损失 $\to \infty$——这正是我们想要的行为。
回归任务:输入 $x$,目标值 $y \in \mathbb{R}$。
核心假设:观测值 $y$ 等于模型预测值 $f(x; \theta)$ 加上高斯噪声:
$$y = f(x; \theta) + \varepsilon, \quad \varepsilon \sim \mathcal{N}(0, \sigma^2)$$
等价于:给定 $x$,$y$ 服从以预测值为中心的高斯分布:
$$P(y \mid x; \theta) = \mathcal{N}(y; f(x;\theta), \sigma^2) = \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(y - f(x;\theta))^2}{2\sigma^2}\right)$$
对数似然($\sigma^2$ 假设已知,视为常数):
$$\ell(\theta) = \sum_{i=1}^n \log P(y^{(i)} \mid x^{(i)}; \theta)$$
$$= \sum_{i=1}^n \left[-\frac{1}{2}\log(2\pi\sigma^2) - \frac{(y^{(i)} - f(x^{(i)};\theta))^2}{2\sigma^2}\right]$$
$$= -\frac{n}{2}\log(2\pi\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^n (y^{(i)} - f(x^{(i)};\theta))^2$$
最大化 $\ell(\theta)$ 关于 $\theta$(第一项是常数,与 $\theta$ 无关):
$$\hat{\theta}_{MLE} = \arg\min_\theta \sum_{i=1}^n (y^{(i)} - f(x^{(i)};\theta))^2 = \arg\min_\theta \text{MSE}(\theta)$$
这正是均方误差损失!
结论:回归任务用 MSE 损失,等价于假设残差服从高斯分布的最大似然估计。
不同的噪声假设,对应不同的 MLE 损失:
MAE 的推导:拉普拉斯分布的对数:$\log P(y \mid x) \propto -\frac{|y - f(x)|}{b}$,最大化等价于最小化 $\sum |y^{(i)} - f(x^{(i)})|$,即 MAE。
关键启示:损失函数的选择,本质上是对噪声分布的假设。高斯噪声对大误差更敏感(因为平方),所以 MSE 对离群点敏感。拉普拉斯噪声对大误差"宽容",所以 MAE 对离群点更鲁棒。
语言模型的训练数据是文本序列 $\mathbf{x} = (x_1, x_2, \ldots, x_T)$。
用链式法则分解联合概率(第12篇推导过):
$$P(\mathbf{x}; \theta) = \prod_{t=1}^T P(x_t \mid x_1, \ldots, x_{t-1}; \theta)$$
MLE 最大化训练语料 $\mathcal{D}$ 的对数似然:
$$\hat{\theta}_{MLE} = \arg\max_\theta \sum_{\mathbf{x} \in \mathcal{D}} \log P(\mathbf{x}; \theta) = \arg\max_\theta \sum_{\mathbf{x} \in \mathcal{D}} \sum_{t=1}^{T} \log P(x_t \mid x_{ 取负号除以总 token 数,得到训练损失: $$L(\theta) = -\frac{1}{|\mathcal{D}|}\sum_{\mathbf{x} \in \mathcal{D}} \sum_{t=1}^{T} \log P(x_t \mid x_{ 这就是 DeepSeek V3 的预训练损失函数。 每一步在 128K 个 token 的分类分布上做 MLE,让真实 token 的对数概率尽量大。 困惑度:MLE 损失的直觉度量 困惑度(Perplexity,PPL) 是语言模型最常用的评估指标,定义为 NLL 的指数: $$\text{PPL} = e^{\text{NLL}}$$ 直觉:PPL 等于"模型平均在多少个等可能选项里选"——PPL=10 意味着模型平均只在 10 个候选 token 里犹豫,PPL=100 意味着更不确定。 完全随机猜:PPL ≈ 128000(词表大小) 优秀语言模型:PPL 在个位数到几十之间 完美预测(每步 100% 确定):PPL = 1 MLE 训练让 NLL 下降,等价于让 PPL 下降——模型越来越"确定"下一个 token 是什么。 第五部分:MLE 与 KL 散度的关系 MLE 等价于最小化 KL 散度 设真实数据分布为 $P_{data}$,模型分布为 $P_\theta$。 KL 散度衡量两个分布的差异(第15篇会详细讲,这里先用结论): $$D_{KL}(P_{data} \| P_\theta) = \mathbb{E}_{x \sim P_{data}}\left[\log \frac{P_{data}(x)}{P_\theta(x)}\right] = \mathbb{E}_{x \sim P_{data}}[\log P_{data}(x)] - \mathbb{E}_{x \sim P_{data}}[\log P_\theta(x)]$$ 第一项 $\mathbb{E}[\log P_{data}(x)]$ 是数据分布的熵,和 $\theta$ 无关。最小化 KL 散度等价于最大化: $$\mathbb{E}_{x \sim P_{data}}[\log P_\theta(x)] \approx \frac{1}{n}\sum_{i=1}^n \log P_\theta(x^{(i)})$$ 用样本均值近似期望,这正是对数似然! MLE = 最小化模型分布和数据分布之间的 KL 散度。 训练语言模型,就是让 $P_\theta$ 尽量接近真实的语言分布 $P_{data}$,用 KL 散度衡量"接近程度",用 MLE(NLL 损失)来优化。 第六部分:MLE 的统计性质 一致性 一致性(Consistency):当样本量 $n \to \infty$,MLE 估计收敛到真实参数: $$\hat{\theta}_{MLE} \xrightarrow{P} \theta^* \quad (n \to \infty)$$ 对大模型的含义:训练数据越多,MLE 估计越准确。DeepSeek V3 用 14.8 万亿 token 训练,一致性保证了这么大的数据量确实能让参数收敛到更好的值,而不是随机游走。 渐近效率:Cramér-Rao 下界 渐近效率(Asymptotic Efficiency):在所有无偏估计量里,MLE 的方差最小——它是"最精确"的估计方法。 Cramér-Rao 下界:任何无偏估计量 $\hat{\theta}$ 的方差满足: $$\text{Var}[\hat{\theta}] \geq \frac{1}{\mathcal{I}(\theta)}$$ 其中 $\mathcal{I}(\theta)$ 是 Fisher 信息矩阵: $$\mathcal{I}(\theta) = \mathbb{E}\left[\left(\frac{\partial \log P(x;\theta)}{\partial \theta}\right)^2\right] = \mathbb{E}[g^2]$$ MLE 的渐近方差恰好达到这个下界——没有其他方法能做得更好(在大样本极限下)。 Fisher 信息的直觉:$\mathcal{I}(\theta)$ 越大,说明对数似然对参数越敏感,从数据里能提取的参数信息越多,估计越精确。MLE 充分利用了数据里的所有参数信息。 对深度学习优化器的启示:Adam 优化器使用梯度平方的移动平均 $v_t \approx \mathbb{E}[g^2]$,这正是 Fisher 信息矩阵的对角近似。Adam 本质上是自然梯度(Fisher 信息加权梯度)的一种高效近似——这解释了为什么 Adam 比朴素 SGD 收敛更快更稳定。 MLE 的局限性 MLE 很强大,但不是万能的: 局限一:有限样本时容易过拟合 数据少时,MLE 会"记住"训练数据的噪声。 极端例子:只有 3 个样本,其中 2 个正例、1 个负例,MLE 估计 $\hat{p} = 2/3$,但真实概率可能是 0.5。 解决方法:加入先验(MAP 估计),等价于正则化(第12篇讲过)。 局限二:模型设定错误 MLE 只在模型族包含真实分布时有理论保证。如果模型本身就是错的,MLE 找到的只是"最好的错误近似"。 局限三:计算复杂度 完整 MLE 需要遍历所有数据,计算代价极高。实践中用 mini-batch 随机近似(SGD),以无偏性换计算效率。 第七部分:从 MLE 到完整训练流程 DeepSeek V3 的三阶段训练目标 阶段一:预训练(主体是 MLE) $$L_{PT}(\theta) = -\frac{1}{|\mathcal{D}|}\sum_{\mathbf{x} \in \mathcal{D}} \sum_t \log P_\theta(x_t \mid x_{ 在 14.8T token 上最大化语言模型的对数似然。核心 MLE 目标。 阶段二:监督微调 SFT(依然是 MLE) $$L_{SFT}(\theta) = -\frac{1}{|\mathcal{D}_{SFT}|}\sum_{\mathbf{x} \in \mathcal{D}_{SFT}} \sum_t \log P_\theta(x_t \mid x_{ 在高质量标注数据上继续 MLE,让模型学习特定风格和格式。 阶段三:GRPO 强化学习(带权重的 MLE) $$L_{GRPO}(\theta) = -\mathbb{E}\left[\sum_i A_i \log \pi_\theta(o_i \mid q)\right] + \beta D_{KL}(\pi_\theta \| \pi_{ref})$$ 第一项是带权重的 NLL:高奖励的输出权重大,低奖励的权重小(甚至为负)。这是 MLE 的推广——不是对所有数据等权重,而是按奖励加权。 三个阶段都在 MLE 框架里,区别在于"用什么数据"和"每个样本的权重是什么"。 梯度的形式 MLE 的梯度: $$\nabla_\theta \text{NLL} = -\frac{1}{n}\sum_i \nabla_\theta \log P_\theta(x^{(i)})$$ 对于分类任务(Softmax + 交叉熵),梯度有优雅的形式: $$\frac{\partial \text{NLL}}{\partial z_k} = \hat{p}_k - y_k$$ 预测概率减去真实标签——当模型预测正确时梯度趋向 0,预测错误时梯度推动参数向正确方向调整。 这个形式的简洁性不是偶然——是 Softmax 和交叉熵联合设计的结果,使得梯度信号既直观又数值稳定(不像 Sigmoid + MSE 那样在接近 0/1 时梯度消失)。 第八部分:完整代码——从 MLE 到损失函数推导 import numpy as np import torch import torch.nn.functional as F np.random.seed(42) torch.manual_seed(42) # ===== 1. MLE 推导交叉熵损失的验证 ===== print("===== MLE ↔ 交叉熵损失(验证等价性)=====\n") # 分类任务:3个类别,4个样本 logits = torch.tensor([ [2.0, 1.0, -1.0], # 样本1:预测类别0 [-1.0, 3.0, 0.5], # 样本2:预测类别1 [0.5, 0.2, 2.5], # 样本3:预测类别2 [1.5, 1.0, 0.5], # 样本4:预测类别0 ]) labels = torch.tensor([0, 1, 2, 0]) # 方法一:PyTorch 内置交叉熵 ce_loss = F.cross_entropy(logits, labels) # 方法二:手动计算 NLL(MLE 视角) probs = F.softmax(logits, dim=1) nll_manual = 0.0 for i, y in enumerate(labels): nll_manual -= torch.log(probs[i, y]) nll_manual /= len(labels) print(f"PyTorch 交叉熵损失: {ce_loss.item():.6f}") print(f"手动计算 NLL: {nll_manual.item():.6f}") print(f"两者完全相等: {torch.isclose(ce_loss, nll_manual)}\n") # 展示每个样本的贡献 print("逐样本分解:") print(f"{'样本':>4} | {'真实类别':>8} | {'预测概率':>10} | {'NLL贡献':>10}") print("-" * 45) for i, (y, p) in enumerate(zip(labels, probs)): true_prob = p[y].item() nll_contrib = -np.log(true_prob) print(f"{i+1:>4} | {y.item():>8} | {true_prob:>10.4f} | {nll_contrib:>10.4f}") print(f"{'平均':>4} | {'':>8} | {'':>10} | {nll_manual.item():>10.4f}") # ===== 2. MLE 推导 MSE 损失的验证(高斯噪声假设)===== print("\n===== MLE ↔ MSE 损失(高斯噪声假设)=====\n") # 回归任务:4个样本,真实值和预测值 y_true = torch.tensor([2.5, 1.0, 3.8, 2.1]) y_pred = torch.tensor([2.3, 1.4, 3.5, 2.4]) sigma = 1.0 # 假设噪声标准差为 1 # 方法一:MSE 损失 mse = F.mse_loss(y_pred, y_true) # 方法二:高斯分布的负对数似然 log2pi_sigma2 = np.log(2 * np.pi * sigma**2) gaussian_nll = 0.5 * log2pi_sigma2 + (y_true - y_pred)**2 / (2 * sigma**2) gaussian_nll_mean = gaussian_nll.mean() print(f"MSE 损失: {mse.item():.6f}") print(f"高斯 NLL(含常数项): {gaussian_nll_mean.item():.6f}") print(f"常数项(与θ无关): {0.5 * log2pi_sigma2:.6f}") print(f"NLL 去掉常数: {(gaussian_nll_mean - 0.5 * log2pi_sigma2).item():.6f}") print(f"MSE = NLL 去掉常数/2: {mse.item():.6f}") print(f"最优化时等价(相差常数和系数,不影响 argmin)\n") # ===== 3. 不同噪声假设:MSE vs MAE ===== print("===== 不同噪声假设对应不同损失 =====\n") # 含离群点的数据 y_true_outlier = torch.tensor([1.0, 2.0, 3.0, 4.0, 20.0]) # 最后一个是离群点 y_pred_const = torch.tensor([2.0, 2.0, 2.0, 2.0, 2.0]) # 预测常数(均值) # 调整预测以最小化 MSE 和 MAE # MSE最优预测 = 均值,MAE最优预测 = 中位数 mean_pred = y_true_outlier.mean() median_pred = y_true_outlier.median() mse_mean = F.mse_loss(mean_pred.expand(5), y_true_outlier) mse_median = F.mse_loss(median_pred.expand(5), y_true_outlier) mae_mean = F.l1_loss(mean_pred.expand(5), y_true_outlier) mae_median = F.l1_loss(median_pred.expand(5), y_true_outlier) print(f"数据: {y_true_outlier.tolist()} (含离群点20.0)") print(f"均值: {mean_pred.item():.2f}, 中位数: {median_pred.item():.2f}\n") print(f"MSE损失 - 预测均值{mean_pred.item():.1f}: {mse_mean.item():.4f} ← 更优") print(f"MSE损失 - 预测中位数{median_pred.item():.1f}: {mse_median.item():.4f}") print(f"MAE损失 - 预测均值{mean_pred.item():.1f}: {mae_mean.item():.4f}") print(f"MAE损失 - 预测中位数{median_pred.item():.1f}: {mae_median.item():.4f} ← 更优") print(f"\n→ MSE(高斯假设)的最优预测是均值,受离群点影响大") print(f"→ MAE(拉普拉斯假设)的最优预测是中位数,对离群点鲁棒") # ===== 4. 语言模型的 NLL 和困惑度 ===== print("\n===== 语言模型 NLL 与困惑度 =====\n") # 模拟不同质量的语言模型在同一段文本上的表现 # 文本:"猫 在 吃 鱼"(4个token) # 模型A(好):每步都相当确定 log_probs_good = torch.tensor([-0.1, -0.2, -0.15, -0.1]) # 高概率:e^{-0.1}≈0.90 # 模型B(中):有些步骤不确定 log_probs_mid = torch.tensor([-0.5, -1.0, -0.8, -0.6]) # 中等概率 # 模型C(差/随机):接近均匀分布 vocab_size = 128000 log_probs_bad = torch.tensor([-np.log(vocab_size)] * 4) # 随机猜测 for name, lps in [("好模型", log_probs_good), ("中等模型", log_probs_mid), ("随机猜测", log_probs_bad)]: nll = -lps.mean().item() ppl = np.exp(nll) avg_p = np.exp(-nll) print(f"{name}: NLL={nll:.3f}, PPL={ppl:.1f}, 平均预测概率={avg_p:.3f}") print(f"\n随机猜测的PPL={np.exp(np.log(vocab_size)):.0f}(=词表大小{vocab_size})") # ===== 5. MLE 的一致性:样本量越大越准 ===== print("\n===== MLE 的一致性验证 =====\n") # 真实参数:硬币正面概率 p=0.35 true_p = 0.35 print(f"真实参数: p = {true_p}") print(f"{'样本量':>8} | {'MLE估计':>10} | {'误差':>8} | {'95%置信区间'}") print("-" * 50) for n in [10, 100, 1000, 10000, 100000]: samples = np.random.binomial(1, true_p, n) mle_p = samples.mean() # 伯努利MLE就是样本均值 error = abs(mle_p - true_p) # 置信区间(正态近似) se = np.sqrt(mle_p * (1-mle_p) / n) ci_l, ci_h = mle_p - 1.96*se, mle_p + 1.96*se print(f"{n:>8,} | {mle_p:>10.4f} | {error:>8.4f} | [{ci_l:.4f}, {ci_h:.4f}]") # ===== 6. 交叉熵梯度的简洁形式 ===== print("\n===== 交叉熵梯度:预测概率 - 真实标签 =====\n") # 手动验证 ∂NLL/∂z_k = p̂_k - y_k z = torch.tensor([2.0, 1.0, 0.5], requires_grad=True) y = torch.tensor([1, 0, 0], dtype=torch.float32) # 真实类别是0 loss = F.cross_entropy(z.unsqueeze(0), torch.tensor([0])) loss.backward() p_hat = F.softmax(z.detach(), dim=0) grad_manual = p_hat - y print(f"logits z: {z.detach().numpy().round(4)}") print(f"Softmax p̂: {p_hat.numpy().round(4)}") print(f"真实标签 y (one-hot): {y.numpy()}") print(f"\n自动求导梯度 ∂L/∂z: {z.grad.numpy().round(4)}") print(f"手动计算 p̂ - y: {grad_manual.numpy().round(4)}") print(f"完全相等: {torch.allclose(z.grad, grad_manual)}") print(f"\n→ 梯度 = 预测概率 - 真实标签,直观且数值稳定") 总结 这篇文章把最大似然估计从基本定义推导到深度学习的所有主要损失函数,并联系到 DeepSeek 的完整训练流程。 第一,MLE 的核心思想:找让训练数据出现概率最大的参数。似然是概率的"逆向视角"——数据固定,参数是变量。取对数把乘积变加法,最小化 NLL 等价于最大化似然。 第二,MLE 推导分类损失:假设标签服从分类分布,NLL 推导出交叉熵损失。最小化交叉熵 = 最大化真实类别的预测概率。梯度形式为 $\hat{p}_k - y_k$,简洁优雅且数值稳定。 第三,MLE 推导回归损失:假设残差服从高斯分布,NLL 推导出 MSE 损失。假设拉普拉斯分布,推导出 MAE 损失。损失函数的选择是噪声分布假设的反映——高斯假设对离群点敏感,拉普拉斯假设对离群点鲁棒。 第四,语言模型的训练目标是自回归 NLL——对每个位置的条件概率做 MLE,链式法则把文本概率分解为逐 token 条件概率之积。困惑度(PPL)是 NLL 的指数,直觉上表示模型"在多少个候选里犹豫"。 第五,MLE 等价于最小化模型分布和数据分布之间的 KL 散度。Fisher 信息矩阵的对角近似正是 Adam 优化器的二阶矩,解释了 Adam 比 SGD 更高效的理论原因。 第六,DeepSeek 的三阶段训练都在 MLE 框架里:预训练和 SFT 是标准 NLL,GRPO 是带奖励权重的 NLL 加 KL 正则。强化学习不是另一套体系,是 MLE 的加权推广。 下一篇预告: 第15篇,我们讲信息熵与交叉熵:语言模型损失函数的数学根源。 MLE 告诉我们"用什么标准估计参数",但交叉熵和熵本身有更深的信息论含义——熵是信息量的度量,交叉熵是"用错误的分布编码信息付出的代价"。第15篇从信息论角度重新理解训练损失,并深入 KL 散度——它不只出现在 GRPO 里,它是衡量两个分布距离的通用工具。 MLE 是一种哲学:用手头的证据(数据),找到最能"解释"这些证据的假设(参数)。它简单、合理、有理论保证。深度学习的训练,从头到尾都是在做 MLE——预训练在做,微调在做,强化学习也在做,只是数据和权重不一样。理解了 MLE,你就理解了为什么模型会"学习"。
取负号除以总 token 数,得到训练损失:
$$L(\theta) = -\frac{1}{|\mathcal{D}|}\sum_{\mathbf{x} \in \mathcal{D}} \sum_{t=1}^{T} \log P(x_t \mid x_{ 这就是 DeepSeek V3 的预训练损失函数。 每一步在 128K 个 token 的分类分布上做 MLE,让真实 token 的对数概率尽量大。 困惑度:MLE 损失的直觉度量 困惑度(Perplexity,PPL) 是语言模型最常用的评估指标,定义为 NLL 的指数: $$\text{PPL} = e^{\text{NLL}}$$ 直觉:PPL 等于"模型平均在多少个等可能选项里选"——PPL=10 意味着模型平均只在 10 个候选 token 里犹豫,PPL=100 意味着更不确定。 完全随机猜:PPL ≈ 128000(词表大小) 优秀语言模型:PPL 在个位数到几十之间 完美预测(每步 100% 确定):PPL = 1 MLE 训练让 NLL 下降,等价于让 PPL 下降——模型越来越"确定"下一个 token 是什么。 第五部分:MLE 与 KL 散度的关系 MLE 等价于最小化 KL 散度 设真实数据分布为 $P_{data}$,模型分布为 $P_\theta$。 KL 散度衡量两个分布的差异(第15篇会详细讲,这里先用结论): $$D_{KL}(P_{data} \| P_\theta) = \mathbb{E}_{x \sim P_{data}}\left[\log \frac{P_{data}(x)}{P_\theta(x)}\right] = \mathbb{E}_{x \sim P_{data}}[\log P_{data}(x)] - \mathbb{E}_{x \sim P_{data}}[\log P_\theta(x)]$$ 第一项 $\mathbb{E}[\log P_{data}(x)]$ 是数据分布的熵,和 $\theta$ 无关。最小化 KL 散度等价于最大化: $$\mathbb{E}_{x \sim P_{data}}[\log P_\theta(x)] \approx \frac{1}{n}\sum_{i=1}^n \log P_\theta(x^{(i)})$$ 用样本均值近似期望,这正是对数似然! MLE = 最小化模型分布和数据分布之间的 KL 散度。 训练语言模型,就是让 $P_\theta$ 尽量接近真实的语言分布 $P_{data}$,用 KL 散度衡量"接近程度",用 MLE(NLL 损失)来优化。 第六部分:MLE 的统计性质 一致性 一致性(Consistency):当样本量 $n \to \infty$,MLE 估计收敛到真实参数: $$\hat{\theta}_{MLE} \xrightarrow{P} \theta^* \quad (n \to \infty)$$ 对大模型的含义:训练数据越多,MLE 估计越准确。DeepSeek V3 用 14.8 万亿 token 训练,一致性保证了这么大的数据量确实能让参数收敛到更好的值,而不是随机游走。 渐近效率:Cramér-Rao 下界 渐近效率(Asymptotic Efficiency):在所有无偏估计量里,MLE 的方差最小——它是"最精确"的估计方法。 Cramér-Rao 下界:任何无偏估计量 $\hat{\theta}$ 的方差满足: $$\text{Var}[\hat{\theta}] \geq \frac{1}{\mathcal{I}(\theta)}$$ 其中 $\mathcal{I}(\theta)$ 是 Fisher 信息矩阵: $$\mathcal{I}(\theta) = \mathbb{E}\left[\left(\frac{\partial \log P(x;\theta)}{\partial \theta}\right)^2\right] = \mathbb{E}[g^2]$$ MLE 的渐近方差恰好达到这个下界——没有其他方法能做得更好(在大样本极限下)。 Fisher 信息的直觉:$\mathcal{I}(\theta)$ 越大,说明对数似然对参数越敏感,从数据里能提取的参数信息越多,估计越精确。MLE 充分利用了数据里的所有参数信息。 对深度学习优化器的启示:Adam 优化器使用梯度平方的移动平均 $v_t \approx \mathbb{E}[g^2]$,这正是 Fisher 信息矩阵的对角近似。Adam 本质上是自然梯度(Fisher 信息加权梯度)的一种高效近似——这解释了为什么 Adam 比朴素 SGD 收敛更快更稳定。 MLE 的局限性 MLE 很强大,但不是万能的: 局限一:有限样本时容易过拟合 数据少时,MLE 会"记住"训练数据的噪声。 极端例子:只有 3 个样本,其中 2 个正例、1 个负例,MLE 估计 $\hat{p} = 2/3$,但真实概率可能是 0.5。 解决方法:加入先验(MAP 估计),等价于正则化(第12篇讲过)。 局限二:模型设定错误 MLE 只在模型族包含真实分布时有理论保证。如果模型本身就是错的,MLE 找到的只是"最好的错误近似"。 局限三:计算复杂度 完整 MLE 需要遍历所有数据,计算代价极高。实践中用 mini-batch 随机近似(SGD),以无偏性换计算效率。 第七部分:从 MLE 到完整训练流程 DeepSeek V3 的三阶段训练目标 阶段一:预训练(主体是 MLE) $$L_{PT}(\theta) = -\frac{1}{|\mathcal{D}|}\sum_{\mathbf{x} \in \mathcal{D}} \sum_t \log P_\theta(x_t \mid x_{ 在 14.8T token 上最大化语言模型的对数似然。核心 MLE 目标。 阶段二:监督微调 SFT(依然是 MLE) $$L_{SFT}(\theta) = -\frac{1}{|\mathcal{D}_{SFT}|}\sum_{\mathbf{x} \in \mathcal{D}_{SFT}} \sum_t \log P_\theta(x_t \mid x_{ 在高质量标注数据上继续 MLE,让模型学习特定风格和格式。 阶段三:GRPO 强化学习(带权重的 MLE) $$L_{GRPO}(\theta) = -\mathbb{E}\left[\sum_i A_i \log \pi_\theta(o_i \mid q)\right] + \beta D_{KL}(\pi_\theta \| \pi_{ref})$$ 第一项是带权重的 NLL:高奖励的输出权重大,低奖励的权重小(甚至为负)。这是 MLE 的推广——不是对所有数据等权重,而是按奖励加权。 三个阶段都在 MLE 框架里,区别在于"用什么数据"和"每个样本的权重是什么"。 梯度的形式 MLE 的梯度: $$\nabla_\theta \text{NLL} = -\frac{1}{n}\sum_i \nabla_\theta \log P_\theta(x^{(i)})$$ 对于分类任务(Softmax + 交叉熵),梯度有优雅的形式: $$\frac{\partial \text{NLL}}{\partial z_k} = \hat{p}_k - y_k$$ 预测概率减去真实标签——当模型预测正确时梯度趋向 0,预测错误时梯度推动参数向正确方向调整。 这个形式的简洁性不是偶然——是 Softmax 和交叉熵联合设计的结果,使得梯度信号既直观又数值稳定(不像 Sigmoid + MSE 那样在接近 0/1 时梯度消失)。 第八部分:完整代码——从 MLE 到损失函数推导 import numpy as np import torch import torch.nn.functional as F np.random.seed(42) torch.manual_seed(42) # ===== 1. MLE 推导交叉熵损失的验证 ===== print("===== MLE ↔ 交叉熵损失(验证等价性)=====\n") # 分类任务:3个类别,4个样本 logits = torch.tensor([ [2.0, 1.0, -1.0], # 样本1:预测类别0 [-1.0, 3.0, 0.5], # 样本2:预测类别1 [0.5, 0.2, 2.5], # 样本3:预测类别2 [1.5, 1.0, 0.5], # 样本4:预测类别0 ]) labels = torch.tensor([0, 1, 2, 0]) # 方法一:PyTorch 内置交叉熵 ce_loss = F.cross_entropy(logits, labels) # 方法二:手动计算 NLL(MLE 视角) probs = F.softmax(logits, dim=1) nll_manual = 0.0 for i, y in enumerate(labels): nll_manual -= torch.log(probs[i, y]) nll_manual /= len(labels) print(f"PyTorch 交叉熵损失: {ce_loss.item():.6f}") print(f"手动计算 NLL: {nll_manual.item():.6f}") print(f"两者完全相等: {torch.isclose(ce_loss, nll_manual)}\n") # 展示每个样本的贡献 print("逐样本分解:") print(f"{'样本':>4} | {'真实类别':>8} | {'预测概率':>10} | {'NLL贡献':>10}") print("-" * 45) for i, (y, p) in enumerate(zip(labels, probs)): true_prob = p[y].item() nll_contrib = -np.log(true_prob) print(f"{i+1:>4} | {y.item():>8} | {true_prob:>10.4f} | {nll_contrib:>10.4f}") print(f"{'平均':>4} | {'':>8} | {'':>10} | {nll_manual.item():>10.4f}") # ===== 2. MLE 推导 MSE 损失的验证(高斯噪声假设)===== print("\n===== MLE ↔ MSE 损失(高斯噪声假设)=====\n") # 回归任务:4个样本,真实值和预测值 y_true = torch.tensor([2.5, 1.0, 3.8, 2.1]) y_pred = torch.tensor([2.3, 1.4, 3.5, 2.4]) sigma = 1.0 # 假设噪声标准差为 1 # 方法一:MSE 损失 mse = F.mse_loss(y_pred, y_true) # 方法二:高斯分布的负对数似然 log2pi_sigma2 = np.log(2 * np.pi * sigma**2) gaussian_nll = 0.5 * log2pi_sigma2 + (y_true - y_pred)**2 / (2 * sigma**2) gaussian_nll_mean = gaussian_nll.mean() print(f"MSE 损失: {mse.item():.6f}") print(f"高斯 NLL(含常数项): {gaussian_nll_mean.item():.6f}") print(f"常数项(与θ无关): {0.5 * log2pi_sigma2:.6f}") print(f"NLL 去掉常数: {(gaussian_nll_mean - 0.5 * log2pi_sigma2).item():.6f}") print(f"MSE = NLL 去掉常数/2: {mse.item():.6f}") print(f"最优化时等价(相差常数和系数,不影响 argmin)\n") # ===== 3. 不同噪声假设:MSE vs MAE ===== print("===== 不同噪声假设对应不同损失 =====\n") # 含离群点的数据 y_true_outlier = torch.tensor([1.0, 2.0, 3.0, 4.0, 20.0]) # 最后一个是离群点 y_pred_const = torch.tensor([2.0, 2.0, 2.0, 2.0, 2.0]) # 预测常数(均值) # 调整预测以最小化 MSE 和 MAE # MSE最优预测 = 均值,MAE最优预测 = 中位数 mean_pred = y_true_outlier.mean() median_pred = y_true_outlier.median() mse_mean = F.mse_loss(mean_pred.expand(5), y_true_outlier) mse_median = F.mse_loss(median_pred.expand(5), y_true_outlier) mae_mean = F.l1_loss(mean_pred.expand(5), y_true_outlier) mae_median = F.l1_loss(median_pred.expand(5), y_true_outlier) print(f"数据: {y_true_outlier.tolist()} (含离群点20.0)") print(f"均值: {mean_pred.item():.2f}, 中位数: {median_pred.item():.2f}\n") print(f"MSE损失 - 预测均值{mean_pred.item():.1f}: {mse_mean.item():.4f} ← 更优") print(f"MSE损失 - 预测中位数{median_pred.item():.1f}: {mse_median.item():.4f}") print(f"MAE损失 - 预测均值{mean_pred.item():.1f}: {mae_mean.item():.4f}") print(f"MAE损失 - 预测中位数{median_pred.item():.1f}: {mae_median.item():.4f} ← 更优") print(f"\n→ MSE(高斯假设)的最优预测是均值,受离群点影响大") print(f"→ MAE(拉普拉斯假设)的最优预测是中位数,对离群点鲁棒") # ===== 4. 语言模型的 NLL 和困惑度 ===== print("\n===== 语言模型 NLL 与困惑度 =====\n") # 模拟不同质量的语言模型在同一段文本上的表现 # 文本:"猫 在 吃 鱼"(4个token) # 模型A(好):每步都相当确定 log_probs_good = torch.tensor([-0.1, -0.2, -0.15, -0.1]) # 高概率:e^{-0.1}≈0.90 # 模型B(中):有些步骤不确定 log_probs_mid = torch.tensor([-0.5, -1.0, -0.8, -0.6]) # 中等概率 # 模型C(差/随机):接近均匀分布 vocab_size = 128000 log_probs_bad = torch.tensor([-np.log(vocab_size)] * 4) # 随机猜测 for name, lps in [("好模型", log_probs_good), ("中等模型", log_probs_mid), ("随机猜测", log_probs_bad)]: nll = -lps.mean().item() ppl = np.exp(nll) avg_p = np.exp(-nll) print(f"{name}: NLL={nll:.3f}, PPL={ppl:.1f}, 平均预测概率={avg_p:.3f}") print(f"\n随机猜测的PPL={np.exp(np.log(vocab_size)):.0f}(=词表大小{vocab_size})") # ===== 5. MLE 的一致性:样本量越大越准 ===== print("\n===== MLE 的一致性验证 =====\n") # 真实参数:硬币正面概率 p=0.35 true_p = 0.35 print(f"真实参数: p = {true_p}") print(f"{'样本量':>8} | {'MLE估计':>10} | {'误差':>8} | {'95%置信区间'}") print("-" * 50) for n in [10, 100, 1000, 10000, 100000]: samples = np.random.binomial(1, true_p, n) mle_p = samples.mean() # 伯努利MLE就是样本均值 error = abs(mle_p - true_p) # 置信区间(正态近似) se = np.sqrt(mle_p * (1-mle_p) / n) ci_l, ci_h = mle_p - 1.96*se, mle_p + 1.96*se print(f"{n:>8,} | {mle_p:>10.4f} | {error:>8.4f} | [{ci_l:.4f}, {ci_h:.4f}]") # ===== 6. 交叉熵梯度的简洁形式 ===== print("\n===== 交叉熵梯度:预测概率 - 真实标签 =====\n") # 手动验证 ∂NLL/∂z_k = p̂_k - y_k z = torch.tensor([2.0, 1.0, 0.5], requires_grad=True) y = torch.tensor([1, 0, 0], dtype=torch.float32) # 真实类别是0 loss = F.cross_entropy(z.unsqueeze(0), torch.tensor([0])) loss.backward() p_hat = F.softmax(z.detach(), dim=0) grad_manual = p_hat - y print(f"logits z: {z.detach().numpy().round(4)}") print(f"Softmax p̂: {p_hat.numpy().round(4)}") print(f"真实标签 y (one-hot): {y.numpy()}") print(f"\n自动求导梯度 ∂L/∂z: {z.grad.numpy().round(4)}") print(f"手动计算 p̂ - y: {grad_manual.numpy().round(4)}") print(f"完全相等: {torch.allclose(z.grad, grad_manual)}") print(f"\n→ 梯度 = 预测概率 - 真实标签,直观且数值稳定") 总结 这篇文章把最大似然估计从基本定义推导到深度学习的所有主要损失函数,并联系到 DeepSeek 的完整训练流程。 第一,MLE 的核心思想:找让训练数据出现概率最大的参数。似然是概率的"逆向视角"——数据固定,参数是变量。取对数把乘积变加法,最小化 NLL 等价于最大化似然。 第二,MLE 推导分类损失:假设标签服从分类分布,NLL 推导出交叉熵损失。最小化交叉熵 = 最大化真实类别的预测概率。梯度形式为 $\hat{p}_k - y_k$,简洁优雅且数值稳定。 第三,MLE 推导回归损失:假设残差服从高斯分布,NLL 推导出 MSE 损失。假设拉普拉斯分布,推导出 MAE 损失。损失函数的选择是噪声分布假设的反映——高斯假设对离群点敏感,拉普拉斯假设对离群点鲁棒。 第四,语言模型的训练目标是自回归 NLL——对每个位置的条件概率做 MLE,链式法则把文本概率分解为逐 token 条件概率之积。困惑度(PPL)是 NLL 的指数,直觉上表示模型"在多少个候选里犹豫"。 第五,MLE 等价于最小化模型分布和数据分布之间的 KL 散度。Fisher 信息矩阵的对角近似正是 Adam 优化器的二阶矩,解释了 Adam 比 SGD 更高效的理论原因。 第六,DeepSeek 的三阶段训练都在 MLE 框架里:预训练和 SFT 是标准 NLL,GRPO 是带奖励权重的 NLL 加 KL 正则。强化学习不是另一套体系,是 MLE 的加权推广。 下一篇预告: 第15篇,我们讲信息熵与交叉熵:语言模型损失函数的数学根源。 MLE 告诉我们"用什么标准估计参数",但交叉熵和熵本身有更深的信息论含义——熵是信息量的度量,交叉熵是"用错误的分布编码信息付出的代价"。第15篇从信息论角度重新理解训练损失,并深入 KL 散度——它不只出现在 GRPO 里,它是衡量两个分布距离的通用工具。 MLE 是一种哲学:用手头的证据(数据),找到最能"解释"这些证据的假设(参数)。它简单、合理、有理论保证。深度学习的训练,从头到尾都是在做 MLE——预训练在做,微调在做,强化学习也在做,只是数据和权重不一样。理解了 MLE,你就理解了为什么模型会"学习"。
这就是 DeepSeek V3 的预训练损失函数。 每一步在 128K 个 token 的分类分布上做 MLE,让真实 token 的对数概率尽量大。
困惑度(Perplexity,PPL) 是语言模型最常用的评估指标,定义为 NLL 的指数:
$$\text{PPL} = e^{\text{NLL}}$$
直觉:PPL 等于"模型平均在多少个等可能选项里选"——PPL=10 意味着模型平均只在 10 个候选 token 里犹豫,PPL=100 意味着更不确定。
MLE 训练让 NLL 下降,等价于让 PPL 下降——模型越来越"确定"下一个 token 是什么。
设真实数据分布为 $P_{data}$,模型分布为 $P_\theta$。
KL 散度衡量两个分布的差异(第15篇会详细讲,这里先用结论):
$$D_{KL}(P_{data} \| P_\theta) = \mathbb{E}_{x \sim P_{data}}\left[\log \frac{P_{data}(x)}{P_\theta(x)}\right] = \mathbb{E}_{x \sim P_{data}}[\log P_{data}(x)] - \mathbb{E}_{x \sim P_{data}}[\log P_\theta(x)]$$
第一项 $\mathbb{E}[\log P_{data}(x)]$ 是数据分布的熵,和 $\theta$ 无关。最小化 KL 散度等价于最大化:
$$\mathbb{E}_{x \sim P_{data}}[\log P_\theta(x)] \approx \frac{1}{n}\sum_{i=1}^n \log P_\theta(x^{(i)})$$
用样本均值近似期望,这正是对数似然!
MLE = 最小化模型分布和数据分布之间的 KL 散度。
训练语言模型,就是让 $P_\theta$ 尽量接近真实的语言分布 $P_{data}$,用 KL 散度衡量"接近程度",用 MLE(NLL 损失)来优化。
一致性(Consistency):当样本量 $n \to \infty$,MLE 估计收敛到真实参数:
$$\hat{\theta}_{MLE} \xrightarrow{P} \theta^* \quad (n \to \infty)$$
对大模型的含义:训练数据越多,MLE 估计越准确。DeepSeek V3 用 14.8 万亿 token 训练,一致性保证了这么大的数据量确实能让参数收敛到更好的值,而不是随机游走。
渐近效率(Asymptotic Efficiency):在所有无偏估计量里,MLE 的方差最小——它是"最精确"的估计方法。
Cramér-Rao 下界:任何无偏估计量 $\hat{\theta}$ 的方差满足:
$$\text{Var}[\hat{\theta}] \geq \frac{1}{\mathcal{I}(\theta)}$$
其中 $\mathcal{I}(\theta)$ 是 Fisher 信息矩阵:
$$\mathcal{I}(\theta) = \mathbb{E}\left[\left(\frac{\partial \log P(x;\theta)}{\partial \theta}\right)^2\right] = \mathbb{E}[g^2]$$
MLE 的渐近方差恰好达到这个下界——没有其他方法能做得更好(在大样本极限下)。
Fisher 信息的直觉:$\mathcal{I}(\theta)$ 越大,说明对数似然对参数越敏感,从数据里能提取的参数信息越多,估计越精确。MLE 充分利用了数据里的所有参数信息。
对深度学习优化器的启示:Adam 优化器使用梯度平方的移动平均 $v_t \approx \mathbb{E}[g^2]$,这正是 Fisher 信息矩阵的对角近似。Adam 本质上是自然梯度(Fisher 信息加权梯度)的一种高效近似——这解释了为什么 Adam 比朴素 SGD 收敛更快更稳定。
MLE 很强大,但不是万能的:
局限一:有限样本时容易过拟合
数据少时,MLE 会"记住"训练数据的噪声。
极端例子:只有 3 个样本,其中 2 个正例、1 个负例,MLE 估计 $\hat{p} = 2/3$,但真实概率可能是 0.5。
解决方法:加入先验(MAP 估计),等价于正则化(第12篇讲过)。
局限二:模型设定错误
MLE 只在模型族包含真实分布时有理论保证。如果模型本身就是错的,MLE 找到的只是"最好的错误近似"。
局限三:计算复杂度
完整 MLE 需要遍历所有数据,计算代价极高。实践中用 mini-batch 随机近似(SGD),以无偏性换计算效率。
阶段一:预训练(主体是 MLE)
$$L_{PT}(\theta) = -\frac{1}{|\mathcal{D}|}\sum_{\mathbf{x} \in \mathcal{D}} \sum_t \log P_\theta(x_t \mid x_{ 在 14.8T token 上最大化语言模型的对数似然。核心 MLE 目标。 阶段二:监督微调 SFT(依然是 MLE) $$L_{SFT}(\theta) = -\frac{1}{|\mathcal{D}_{SFT}|}\sum_{\mathbf{x} \in \mathcal{D}_{SFT}} \sum_t \log P_\theta(x_t \mid x_{ 在高质量标注数据上继续 MLE,让模型学习特定风格和格式。 阶段三:GRPO 强化学习(带权重的 MLE) $$L_{GRPO}(\theta) = -\mathbb{E}\left[\sum_i A_i \log \pi_\theta(o_i \mid q)\right] + \beta D_{KL}(\pi_\theta \| \pi_{ref})$$ 第一项是带权重的 NLL:高奖励的输出权重大,低奖励的权重小(甚至为负)。这是 MLE 的推广——不是对所有数据等权重,而是按奖励加权。 三个阶段都在 MLE 框架里,区别在于"用什么数据"和"每个样本的权重是什么"。 梯度的形式 MLE 的梯度: $$\nabla_\theta \text{NLL} = -\frac{1}{n}\sum_i \nabla_\theta \log P_\theta(x^{(i)})$$ 对于分类任务(Softmax + 交叉熵),梯度有优雅的形式: $$\frac{\partial \text{NLL}}{\partial z_k} = \hat{p}_k - y_k$$ 预测概率减去真实标签——当模型预测正确时梯度趋向 0,预测错误时梯度推动参数向正确方向调整。 这个形式的简洁性不是偶然——是 Softmax 和交叉熵联合设计的结果,使得梯度信号既直观又数值稳定(不像 Sigmoid + MSE 那样在接近 0/1 时梯度消失)。 第八部分:完整代码——从 MLE 到损失函数推导 import numpy as np import torch import torch.nn.functional as F np.random.seed(42) torch.manual_seed(42) # ===== 1. MLE 推导交叉熵损失的验证 ===== print("===== MLE ↔ 交叉熵损失(验证等价性)=====\n") # 分类任务:3个类别,4个样本 logits = torch.tensor([ [2.0, 1.0, -1.0], # 样本1:预测类别0 [-1.0, 3.0, 0.5], # 样本2:预测类别1 [0.5, 0.2, 2.5], # 样本3:预测类别2 [1.5, 1.0, 0.5], # 样本4:预测类别0 ]) labels = torch.tensor([0, 1, 2, 0]) # 方法一:PyTorch 内置交叉熵 ce_loss = F.cross_entropy(logits, labels) # 方法二:手动计算 NLL(MLE 视角) probs = F.softmax(logits, dim=1) nll_manual = 0.0 for i, y in enumerate(labels): nll_manual -= torch.log(probs[i, y]) nll_manual /= len(labels) print(f"PyTorch 交叉熵损失: {ce_loss.item():.6f}") print(f"手动计算 NLL: {nll_manual.item():.6f}") print(f"两者完全相等: {torch.isclose(ce_loss, nll_manual)}\n") # 展示每个样本的贡献 print("逐样本分解:") print(f"{'样本':>4} | {'真实类别':>8} | {'预测概率':>10} | {'NLL贡献':>10}") print("-" * 45) for i, (y, p) in enumerate(zip(labels, probs)): true_prob = p[y].item() nll_contrib = -np.log(true_prob) print(f"{i+1:>4} | {y.item():>8} | {true_prob:>10.4f} | {nll_contrib:>10.4f}") print(f"{'平均':>4} | {'':>8} | {'':>10} | {nll_manual.item():>10.4f}") # ===== 2. MLE 推导 MSE 损失的验证(高斯噪声假设)===== print("\n===== MLE ↔ MSE 损失(高斯噪声假设)=====\n") # 回归任务:4个样本,真实值和预测值 y_true = torch.tensor([2.5, 1.0, 3.8, 2.1]) y_pred = torch.tensor([2.3, 1.4, 3.5, 2.4]) sigma = 1.0 # 假设噪声标准差为 1 # 方法一:MSE 损失 mse = F.mse_loss(y_pred, y_true) # 方法二:高斯分布的负对数似然 log2pi_sigma2 = np.log(2 * np.pi * sigma**2) gaussian_nll = 0.5 * log2pi_sigma2 + (y_true - y_pred)**2 / (2 * sigma**2) gaussian_nll_mean = gaussian_nll.mean() print(f"MSE 损失: {mse.item():.6f}") print(f"高斯 NLL(含常数项): {gaussian_nll_mean.item():.6f}") print(f"常数项(与θ无关): {0.5 * log2pi_sigma2:.6f}") print(f"NLL 去掉常数: {(gaussian_nll_mean - 0.5 * log2pi_sigma2).item():.6f}") print(f"MSE = NLL 去掉常数/2: {mse.item():.6f}") print(f"最优化时等价(相差常数和系数,不影响 argmin)\n") # ===== 3. 不同噪声假设:MSE vs MAE ===== print("===== 不同噪声假设对应不同损失 =====\n") # 含离群点的数据 y_true_outlier = torch.tensor([1.0, 2.0, 3.0, 4.0, 20.0]) # 最后一个是离群点 y_pred_const = torch.tensor([2.0, 2.0, 2.0, 2.0, 2.0]) # 预测常数(均值) # 调整预测以最小化 MSE 和 MAE # MSE最优预测 = 均值,MAE最优预测 = 中位数 mean_pred = y_true_outlier.mean() median_pred = y_true_outlier.median() mse_mean = F.mse_loss(mean_pred.expand(5), y_true_outlier) mse_median = F.mse_loss(median_pred.expand(5), y_true_outlier) mae_mean = F.l1_loss(mean_pred.expand(5), y_true_outlier) mae_median = F.l1_loss(median_pred.expand(5), y_true_outlier) print(f"数据: {y_true_outlier.tolist()} (含离群点20.0)") print(f"均值: {mean_pred.item():.2f}, 中位数: {median_pred.item():.2f}\n") print(f"MSE损失 - 预测均值{mean_pred.item():.1f}: {mse_mean.item():.4f} ← 更优") print(f"MSE损失 - 预测中位数{median_pred.item():.1f}: {mse_median.item():.4f}") print(f"MAE损失 - 预测均值{mean_pred.item():.1f}: {mae_mean.item():.4f}") print(f"MAE损失 - 预测中位数{median_pred.item():.1f}: {mae_median.item():.4f} ← 更优") print(f"\n→ MSE(高斯假设)的最优预测是均值,受离群点影响大") print(f"→ MAE(拉普拉斯假设)的最优预测是中位数,对离群点鲁棒") # ===== 4. 语言模型的 NLL 和困惑度 ===== print("\n===== 语言模型 NLL 与困惑度 =====\n") # 模拟不同质量的语言模型在同一段文本上的表现 # 文本:"猫 在 吃 鱼"(4个token) # 模型A(好):每步都相当确定 log_probs_good = torch.tensor([-0.1, -0.2, -0.15, -0.1]) # 高概率:e^{-0.1}≈0.90 # 模型B(中):有些步骤不确定 log_probs_mid = torch.tensor([-0.5, -1.0, -0.8, -0.6]) # 中等概率 # 模型C(差/随机):接近均匀分布 vocab_size = 128000 log_probs_bad = torch.tensor([-np.log(vocab_size)] * 4) # 随机猜测 for name, lps in [("好模型", log_probs_good), ("中等模型", log_probs_mid), ("随机猜测", log_probs_bad)]: nll = -lps.mean().item() ppl = np.exp(nll) avg_p = np.exp(-nll) print(f"{name}: NLL={nll:.3f}, PPL={ppl:.1f}, 平均预测概率={avg_p:.3f}") print(f"\n随机猜测的PPL={np.exp(np.log(vocab_size)):.0f}(=词表大小{vocab_size})") # ===== 5. MLE 的一致性:样本量越大越准 ===== print("\n===== MLE 的一致性验证 =====\n") # 真实参数:硬币正面概率 p=0.35 true_p = 0.35 print(f"真实参数: p = {true_p}") print(f"{'样本量':>8} | {'MLE估计':>10} | {'误差':>8} | {'95%置信区间'}") print("-" * 50) for n in [10, 100, 1000, 10000, 100000]: samples = np.random.binomial(1, true_p, n) mle_p = samples.mean() # 伯努利MLE就是样本均值 error = abs(mle_p - true_p) # 置信区间(正态近似) se = np.sqrt(mle_p * (1-mle_p) / n) ci_l, ci_h = mle_p - 1.96*se, mle_p + 1.96*se print(f"{n:>8,} | {mle_p:>10.4f} | {error:>8.4f} | [{ci_l:.4f}, {ci_h:.4f}]") # ===== 6. 交叉熵梯度的简洁形式 ===== print("\n===== 交叉熵梯度:预测概率 - 真实标签 =====\n") # 手动验证 ∂NLL/∂z_k = p̂_k - y_k z = torch.tensor([2.0, 1.0, 0.5], requires_grad=True) y = torch.tensor([1, 0, 0], dtype=torch.float32) # 真实类别是0 loss = F.cross_entropy(z.unsqueeze(0), torch.tensor([0])) loss.backward() p_hat = F.softmax(z.detach(), dim=0) grad_manual = p_hat - y print(f"logits z: {z.detach().numpy().round(4)}") print(f"Softmax p̂: {p_hat.numpy().round(4)}") print(f"真实标签 y (one-hot): {y.numpy()}") print(f"\n自动求导梯度 ∂L/∂z: {z.grad.numpy().round(4)}") print(f"手动计算 p̂ - y: {grad_manual.numpy().round(4)}") print(f"完全相等: {torch.allclose(z.grad, grad_manual)}") print(f"\n→ 梯度 = 预测概率 - 真实标签,直观且数值稳定") 总结 这篇文章把最大似然估计从基本定义推导到深度学习的所有主要损失函数,并联系到 DeepSeek 的完整训练流程。 第一,MLE 的核心思想:找让训练数据出现概率最大的参数。似然是概率的"逆向视角"——数据固定,参数是变量。取对数把乘积变加法,最小化 NLL 等价于最大化似然。 第二,MLE 推导分类损失:假设标签服从分类分布,NLL 推导出交叉熵损失。最小化交叉熵 = 最大化真实类别的预测概率。梯度形式为 $\hat{p}_k - y_k$,简洁优雅且数值稳定。 第三,MLE 推导回归损失:假设残差服从高斯分布,NLL 推导出 MSE 损失。假设拉普拉斯分布,推导出 MAE 损失。损失函数的选择是噪声分布假设的反映——高斯假设对离群点敏感,拉普拉斯假设对离群点鲁棒。 第四,语言模型的训练目标是自回归 NLL——对每个位置的条件概率做 MLE,链式法则把文本概率分解为逐 token 条件概率之积。困惑度(PPL)是 NLL 的指数,直觉上表示模型"在多少个候选里犹豫"。 第五,MLE 等价于最小化模型分布和数据分布之间的 KL 散度。Fisher 信息矩阵的对角近似正是 Adam 优化器的二阶矩,解释了 Adam 比 SGD 更高效的理论原因。 第六,DeepSeek 的三阶段训练都在 MLE 框架里:预训练和 SFT 是标准 NLL,GRPO 是带奖励权重的 NLL 加 KL 正则。强化学习不是另一套体系,是 MLE 的加权推广。 下一篇预告: 第15篇,我们讲信息熵与交叉熵:语言模型损失函数的数学根源。 MLE 告诉我们"用什么标准估计参数",但交叉熵和熵本身有更深的信息论含义——熵是信息量的度量,交叉熵是"用错误的分布编码信息付出的代价"。第15篇从信息论角度重新理解训练损失,并深入 KL 散度——它不只出现在 GRPO 里,它是衡量两个分布距离的通用工具。 MLE 是一种哲学:用手头的证据(数据),找到最能"解释"这些证据的假设(参数)。它简单、合理、有理论保证。深度学习的训练,从头到尾都是在做 MLE——预训练在做,微调在做,强化学习也在做,只是数据和权重不一样。理解了 MLE,你就理解了为什么模型会"学习"。
在 14.8T token 上最大化语言模型的对数似然。核心 MLE 目标。
阶段二:监督微调 SFT(依然是 MLE)
$$L_{SFT}(\theta) = -\frac{1}{|\mathcal{D}_{SFT}|}\sum_{\mathbf{x} \in \mathcal{D}_{SFT}} \sum_t \log P_\theta(x_t \mid x_{ 在高质量标注数据上继续 MLE,让模型学习特定风格和格式。 阶段三:GRPO 强化学习(带权重的 MLE) $$L_{GRPO}(\theta) = -\mathbb{E}\left[\sum_i A_i \log \pi_\theta(o_i \mid q)\right] + \beta D_{KL}(\pi_\theta \| \pi_{ref})$$ 第一项是带权重的 NLL:高奖励的输出权重大,低奖励的权重小(甚至为负)。这是 MLE 的推广——不是对所有数据等权重,而是按奖励加权。 三个阶段都在 MLE 框架里,区别在于"用什么数据"和"每个样本的权重是什么"。 梯度的形式 MLE 的梯度: $$\nabla_\theta \text{NLL} = -\frac{1}{n}\sum_i \nabla_\theta \log P_\theta(x^{(i)})$$ 对于分类任务(Softmax + 交叉熵),梯度有优雅的形式: $$\frac{\partial \text{NLL}}{\partial z_k} = \hat{p}_k - y_k$$ 预测概率减去真实标签——当模型预测正确时梯度趋向 0,预测错误时梯度推动参数向正确方向调整。 这个形式的简洁性不是偶然——是 Softmax 和交叉熵联合设计的结果,使得梯度信号既直观又数值稳定(不像 Sigmoid + MSE 那样在接近 0/1 时梯度消失)。 第八部分:完整代码——从 MLE 到损失函数推导 import numpy as np import torch import torch.nn.functional as F np.random.seed(42) torch.manual_seed(42) # ===== 1. MLE 推导交叉熵损失的验证 ===== print("===== MLE ↔ 交叉熵损失(验证等价性)=====\n") # 分类任务:3个类别,4个样本 logits = torch.tensor([ [2.0, 1.0, -1.0], # 样本1:预测类别0 [-1.0, 3.0, 0.5], # 样本2:预测类别1 [0.5, 0.2, 2.5], # 样本3:预测类别2 [1.5, 1.0, 0.5], # 样本4:预测类别0 ]) labels = torch.tensor([0, 1, 2, 0]) # 方法一:PyTorch 内置交叉熵 ce_loss = F.cross_entropy(logits, labels) # 方法二:手动计算 NLL(MLE 视角) probs = F.softmax(logits, dim=1) nll_manual = 0.0 for i, y in enumerate(labels): nll_manual -= torch.log(probs[i, y]) nll_manual /= len(labels) print(f"PyTorch 交叉熵损失: {ce_loss.item():.6f}") print(f"手动计算 NLL: {nll_manual.item():.6f}") print(f"两者完全相等: {torch.isclose(ce_loss, nll_manual)}\n") # 展示每个样本的贡献 print("逐样本分解:") print(f"{'样本':>4} | {'真实类别':>8} | {'预测概率':>10} | {'NLL贡献':>10}") print("-" * 45) for i, (y, p) in enumerate(zip(labels, probs)): true_prob = p[y].item() nll_contrib = -np.log(true_prob) print(f"{i+1:>4} | {y.item():>8} | {true_prob:>10.4f} | {nll_contrib:>10.4f}") print(f"{'平均':>4} | {'':>8} | {'':>10} | {nll_manual.item():>10.4f}") # ===== 2. MLE 推导 MSE 损失的验证(高斯噪声假设)===== print("\n===== MLE ↔ MSE 损失(高斯噪声假设)=====\n") # 回归任务:4个样本,真实值和预测值 y_true = torch.tensor([2.5, 1.0, 3.8, 2.1]) y_pred = torch.tensor([2.3, 1.4, 3.5, 2.4]) sigma = 1.0 # 假设噪声标准差为 1 # 方法一:MSE 损失 mse = F.mse_loss(y_pred, y_true) # 方法二:高斯分布的负对数似然 log2pi_sigma2 = np.log(2 * np.pi * sigma**2) gaussian_nll = 0.5 * log2pi_sigma2 + (y_true - y_pred)**2 / (2 * sigma**2) gaussian_nll_mean = gaussian_nll.mean() print(f"MSE 损失: {mse.item():.6f}") print(f"高斯 NLL(含常数项): {gaussian_nll_mean.item():.6f}") print(f"常数项(与θ无关): {0.5 * log2pi_sigma2:.6f}") print(f"NLL 去掉常数: {(gaussian_nll_mean - 0.5 * log2pi_sigma2).item():.6f}") print(f"MSE = NLL 去掉常数/2: {mse.item():.6f}") print(f"最优化时等价(相差常数和系数,不影响 argmin)\n") # ===== 3. 不同噪声假设:MSE vs MAE ===== print("===== 不同噪声假设对应不同损失 =====\n") # 含离群点的数据 y_true_outlier = torch.tensor([1.0, 2.0, 3.0, 4.0, 20.0]) # 最后一个是离群点 y_pred_const = torch.tensor([2.0, 2.0, 2.0, 2.0, 2.0]) # 预测常数(均值) # 调整预测以最小化 MSE 和 MAE # MSE最优预测 = 均值,MAE最优预测 = 中位数 mean_pred = y_true_outlier.mean() median_pred = y_true_outlier.median() mse_mean = F.mse_loss(mean_pred.expand(5), y_true_outlier) mse_median = F.mse_loss(median_pred.expand(5), y_true_outlier) mae_mean = F.l1_loss(mean_pred.expand(5), y_true_outlier) mae_median = F.l1_loss(median_pred.expand(5), y_true_outlier) print(f"数据: {y_true_outlier.tolist()} (含离群点20.0)") print(f"均值: {mean_pred.item():.2f}, 中位数: {median_pred.item():.2f}\n") print(f"MSE损失 - 预测均值{mean_pred.item():.1f}: {mse_mean.item():.4f} ← 更优") print(f"MSE损失 - 预测中位数{median_pred.item():.1f}: {mse_median.item():.4f}") print(f"MAE损失 - 预测均值{mean_pred.item():.1f}: {mae_mean.item():.4f}") print(f"MAE损失 - 预测中位数{median_pred.item():.1f}: {mae_median.item():.4f} ← 更优") print(f"\n→ MSE(高斯假设)的最优预测是均值,受离群点影响大") print(f"→ MAE(拉普拉斯假设)的最优预测是中位数,对离群点鲁棒") # ===== 4. 语言模型的 NLL 和困惑度 ===== print("\n===== 语言模型 NLL 与困惑度 =====\n") # 模拟不同质量的语言模型在同一段文本上的表现 # 文本:"猫 在 吃 鱼"(4个token) # 模型A(好):每步都相当确定 log_probs_good = torch.tensor([-0.1, -0.2, -0.15, -0.1]) # 高概率:e^{-0.1}≈0.90 # 模型B(中):有些步骤不确定 log_probs_mid = torch.tensor([-0.5, -1.0, -0.8, -0.6]) # 中等概率 # 模型C(差/随机):接近均匀分布 vocab_size = 128000 log_probs_bad = torch.tensor([-np.log(vocab_size)] * 4) # 随机猜测 for name, lps in [("好模型", log_probs_good), ("中等模型", log_probs_mid), ("随机猜测", log_probs_bad)]: nll = -lps.mean().item() ppl = np.exp(nll) avg_p = np.exp(-nll) print(f"{name}: NLL={nll:.3f}, PPL={ppl:.1f}, 平均预测概率={avg_p:.3f}") print(f"\n随机猜测的PPL={np.exp(np.log(vocab_size)):.0f}(=词表大小{vocab_size})") # ===== 5. MLE 的一致性:样本量越大越准 ===== print("\n===== MLE 的一致性验证 =====\n") # 真实参数:硬币正面概率 p=0.35 true_p = 0.35 print(f"真实参数: p = {true_p}") print(f"{'样本量':>8} | {'MLE估计':>10} | {'误差':>8} | {'95%置信区间'}") print("-" * 50) for n in [10, 100, 1000, 10000, 100000]: samples = np.random.binomial(1, true_p, n) mle_p = samples.mean() # 伯努利MLE就是样本均值 error = abs(mle_p - true_p) # 置信区间(正态近似) se = np.sqrt(mle_p * (1-mle_p) / n) ci_l, ci_h = mle_p - 1.96*se, mle_p + 1.96*se print(f"{n:>8,} | {mle_p:>10.4f} | {error:>8.4f} | [{ci_l:.4f}, {ci_h:.4f}]") # ===== 6. 交叉熵梯度的简洁形式 ===== print("\n===== 交叉熵梯度:预测概率 - 真实标签 =====\n") # 手动验证 ∂NLL/∂z_k = p̂_k - y_k z = torch.tensor([2.0, 1.0, 0.5], requires_grad=True) y = torch.tensor([1, 0, 0], dtype=torch.float32) # 真实类别是0 loss = F.cross_entropy(z.unsqueeze(0), torch.tensor([0])) loss.backward() p_hat = F.softmax(z.detach(), dim=0) grad_manual = p_hat - y print(f"logits z: {z.detach().numpy().round(4)}") print(f"Softmax p̂: {p_hat.numpy().round(4)}") print(f"真实标签 y (one-hot): {y.numpy()}") print(f"\n自动求导梯度 ∂L/∂z: {z.grad.numpy().round(4)}") print(f"手动计算 p̂ - y: {grad_manual.numpy().round(4)}") print(f"完全相等: {torch.allclose(z.grad, grad_manual)}") print(f"\n→ 梯度 = 预测概率 - 真实标签,直观且数值稳定") 总结 这篇文章把最大似然估计从基本定义推导到深度学习的所有主要损失函数,并联系到 DeepSeek 的完整训练流程。 第一,MLE 的核心思想:找让训练数据出现概率最大的参数。似然是概率的"逆向视角"——数据固定,参数是变量。取对数把乘积变加法,最小化 NLL 等价于最大化似然。 第二,MLE 推导分类损失:假设标签服从分类分布,NLL 推导出交叉熵损失。最小化交叉熵 = 最大化真实类别的预测概率。梯度形式为 $\hat{p}_k - y_k$,简洁优雅且数值稳定。 第三,MLE 推导回归损失:假设残差服从高斯分布,NLL 推导出 MSE 损失。假设拉普拉斯分布,推导出 MAE 损失。损失函数的选择是噪声分布假设的反映——高斯假设对离群点敏感,拉普拉斯假设对离群点鲁棒。 第四,语言模型的训练目标是自回归 NLL——对每个位置的条件概率做 MLE,链式法则把文本概率分解为逐 token 条件概率之积。困惑度(PPL)是 NLL 的指数,直觉上表示模型"在多少个候选里犹豫"。 第五,MLE 等价于最小化模型分布和数据分布之间的 KL 散度。Fisher 信息矩阵的对角近似正是 Adam 优化器的二阶矩,解释了 Adam 比 SGD 更高效的理论原因。 第六,DeepSeek 的三阶段训练都在 MLE 框架里:预训练和 SFT 是标准 NLL,GRPO 是带奖励权重的 NLL 加 KL 正则。强化学习不是另一套体系,是 MLE 的加权推广。 下一篇预告: 第15篇,我们讲信息熵与交叉熵:语言模型损失函数的数学根源。 MLE 告诉我们"用什么标准估计参数",但交叉熵和熵本身有更深的信息论含义——熵是信息量的度量,交叉熵是"用错误的分布编码信息付出的代价"。第15篇从信息论角度重新理解训练损失,并深入 KL 散度——它不只出现在 GRPO 里,它是衡量两个分布距离的通用工具。 MLE 是一种哲学:用手头的证据(数据),找到最能"解释"这些证据的假设(参数)。它简单、合理、有理论保证。深度学习的训练,从头到尾都是在做 MLE——预训练在做,微调在做,强化学习也在做,只是数据和权重不一样。理解了 MLE,你就理解了为什么模型会"学习"。
在高质量标注数据上继续 MLE,让模型学习特定风格和格式。
阶段三:GRPO 强化学习(带权重的 MLE)
$$L_{GRPO}(\theta) = -\mathbb{E}\left[\sum_i A_i \log \pi_\theta(o_i \mid q)\right] + \beta D_{KL}(\pi_\theta \| \pi_{ref})$$
第一项是带权重的 NLL:高奖励的输出权重大,低奖励的权重小(甚至为负)。这是 MLE 的推广——不是对所有数据等权重,而是按奖励加权。
三个阶段都在 MLE 框架里,区别在于"用什么数据"和"每个样本的权重是什么"。
MLE 的梯度:
$$\nabla_\theta \text{NLL} = -\frac{1}{n}\sum_i \nabla_\theta \log P_\theta(x^{(i)})$$
对于分类任务(Softmax + 交叉熵),梯度有优雅的形式:
$$\frac{\partial \text{NLL}}{\partial z_k} = \hat{p}_k - y_k$$
预测概率减去真实标签——当模型预测正确时梯度趋向 0,预测错误时梯度推动参数向正确方向调整。
这个形式的简洁性不是偶然——是 Softmax 和交叉熵联合设计的结果,使得梯度信号既直观又数值稳定(不像 Sigmoid + MSE 那样在接近 0/1 时梯度消失)。
import numpy as np import torch import torch.nn.functional as F np.random.seed(42) torch.manual_seed(42) # ===== 1. MLE 推导交叉熵损失的验证 ===== print("===== MLE ↔ 交叉熵损失(验证等价性)=====\n") # 分类任务:3个类别,4个样本 logits = torch.tensor([ [2.0, 1.0, -1.0], # 样本1:预测类别0 [-1.0, 3.0, 0.5], # 样本2:预测类别1 [0.5, 0.2, 2.5], # 样本3:预测类别2 [1.5, 1.0, 0.5], # 样本4:预测类别0 ]) labels = torch.tensor([0, 1, 2, 0]) # 方法一:PyTorch 内置交叉熵 ce_loss = F.cross_entropy(logits, labels) # 方法二:手动计算 NLL(MLE 视角) probs = F.softmax(logits, dim=1) nll_manual = 0.0 for i, y in enumerate(labels): nll_manual -= torch.log(probs[i, y]) nll_manual /= len(labels) print(f"PyTorch 交叉熵损失: {ce_loss.item():.6f}") print(f"手动计算 NLL: {nll_manual.item():.6f}") print(f"两者完全相等: {torch.isclose(ce_loss, nll_manual)}\n") # 展示每个样本的贡献 print("逐样本分解:") print(f"{'样本':>4} | {'真实类别':>8} | {'预测概率':>10} | {'NLL贡献':>10}") print("-" * 45) for i, (y, p) in enumerate(zip(labels, probs)): true_prob = p[y].item() nll_contrib = -np.log(true_prob) print(f"{i+1:>4} | {y.item():>8} | {true_prob:>10.4f} | {nll_contrib:>10.4f}") print(f"{'平均':>4} | {'':>8} | {'':>10} | {nll_manual.item():>10.4f}") # ===== 2. MLE 推导 MSE 损失的验证(高斯噪声假设)===== print("\n===== MLE ↔ MSE 损失(高斯噪声假设)=====\n") # 回归任务:4个样本,真实值和预测值 y_true = torch.tensor([2.5, 1.0, 3.8, 2.1]) y_pred = torch.tensor([2.3, 1.4, 3.5, 2.4]) sigma = 1.0 # 假设噪声标准差为 1 # 方法一:MSE 损失 mse = F.mse_loss(y_pred, y_true) # 方法二:高斯分布的负对数似然 log2pi_sigma2 = np.log(2 * np.pi * sigma**2) gaussian_nll = 0.5 * log2pi_sigma2 + (y_true - y_pred)**2 / (2 * sigma**2) gaussian_nll_mean = gaussian_nll.mean() print(f"MSE 损失: {mse.item():.6f}") print(f"高斯 NLL(含常数项): {gaussian_nll_mean.item():.6f}") print(f"常数项(与θ无关): {0.5 * log2pi_sigma2:.6f}") print(f"NLL 去掉常数: {(gaussian_nll_mean - 0.5 * log2pi_sigma2).item():.6f}") print(f"MSE = NLL 去掉常数/2: {mse.item():.6f}") print(f"最优化时等价(相差常数和系数,不影响 argmin)\n") # ===== 3. 不同噪声假设:MSE vs MAE ===== print("===== 不同噪声假设对应不同损失 =====\n") # 含离群点的数据 y_true_outlier = torch.tensor([1.0, 2.0, 3.0, 4.0, 20.0]) # 最后一个是离群点 y_pred_const = torch.tensor([2.0, 2.0, 2.0, 2.0, 2.0]) # 预测常数(均值) # 调整预测以最小化 MSE 和 MAE # MSE最优预测 = 均值,MAE最优预测 = 中位数 mean_pred = y_true_outlier.mean() median_pred = y_true_outlier.median() mse_mean = F.mse_loss(mean_pred.expand(5), y_true_outlier) mse_median = F.mse_loss(median_pred.expand(5), y_true_outlier) mae_mean = F.l1_loss(mean_pred.expand(5), y_true_outlier) mae_median = F.l1_loss(median_pred.expand(5), y_true_outlier) print(f"数据: {y_true_outlier.tolist()} (含离群点20.0)") print(f"均值: {mean_pred.item():.2f}, 中位数: {median_pred.item():.2f}\n") print(f"MSE损失 - 预测均值{mean_pred.item():.1f}: {mse_mean.item():.4f} ← 更优") print(f"MSE损失 - 预测中位数{median_pred.item():.1f}: {mse_median.item():.4f}") print(f"MAE损失 - 预测均值{mean_pred.item():.1f}: {mae_mean.item():.4f}") print(f"MAE损失 - 预测中位数{median_pred.item():.1f}: {mae_median.item():.4f} ← 更优") print(f"\n→ MSE(高斯假设)的最优预测是均值,受离群点影响大") print(f"→ MAE(拉普拉斯假设)的最优预测是中位数,对离群点鲁棒") # ===== 4. 语言模型的 NLL 和困惑度 ===== print("\n===== 语言模型 NLL 与困惑度 =====\n") # 模拟不同质量的语言模型在同一段文本上的表现 # 文本:"猫 在 吃 鱼"(4个token) # 模型A(好):每步都相当确定 log_probs_good = torch.tensor([-0.1, -0.2, -0.15, -0.1]) # 高概率:e^{-0.1}≈0.90 # 模型B(中):有些步骤不确定 log_probs_mid = torch.tensor([-0.5, -1.0, -0.8, -0.6]) # 中等概率 # 模型C(差/随机):接近均匀分布 vocab_size = 128000 log_probs_bad = torch.tensor([-np.log(vocab_size)] * 4) # 随机猜测 for name, lps in [("好模型", log_probs_good), ("中等模型", log_probs_mid), ("随机猜测", log_probs_bad)]: nll = -lps.mean().item() ppl = np.exp(nll) avg_p = np.exp(-nll) print(f"{name}: NLL={nll:.3f}, PPL={ppl:.1f}, 平均预测概率={avg_p:.3f}") print(f"\n随机猜测的PPL={np.exp(np.log(vocab_size)):.0f}(=词表大小{vocab_size})") # ===== 5. MLE 的一致性:样本量越大越准 ===== print("\n===== MLE 的一致性验证 =====\n") # 真实参数:硬币正面概率 p=0.35 true_p = 0.35 print(f"真实参数: p = {true_p}") print(f"{'样本量':>8} | {'MLE估计':>10} | {'误差':>8} | {'95%置信区间'}") print("-" * 50) for n in [10, 100, 1000, 10000, 100000]: samples = np.random.binomial(1, true_p, n) mle_p = samples.mean() # 伯努利MLE就是样本均值 error = abs(mle_p - true_p) # 置信区间(正态近似) se = np.sqrt(mle_p * (1-mle_p) / n) ci_l, ci_h = mle_p - 1.96*se, mle_p + 1.96*se print(f"{n:>8,} | {mle_p:>10.4f} | {error:>8.4f} | [{ci_l:.4f}, {ci_h:.4f}]") # ===== 6. 交叉熵梯度的简洁形式 ===== print("\n===== 交叉熵梯度:预测概率 - 真实标签 =====\n") # 手动验证 ∂NLL/∂z_k = p̂_k - y_k z = torch.tensor([2.0, 1.0, 0.5], requires_grad=True) y = torch.tensor([1, 0, 0], dtype=torch.float32) # 真实类别是0 loss = F.cross_entropy(z.unsqueeze(0), torch.tensor([0])) loss.backward() p_hat = F.softmax(z.detach(), dim=0) grad_manual = p_hat - y print(f"logits z: {z.detach().numpy().round(4)}") print(f"Softmax p̂: {p_hat.numpy().round(4)}") print(f"真实标签 y (one-hot): {y.numpy()}") print(f"\n自动求导梯度 ∂L/∂z: {z.grad.numpy().round(4)}") print(f"手动计算 p̂ - y: {grad_manual.numpy().round(4)}") print(f"完全相等: {torch.allclose(z.grad, grad_manual)}") print(f"\n→ 梯度 = 预测概率 - 真实标签,直观且数值稳定")
这篇文章把最大似然估计从基本定义推导到深度学习的所有主要损失函数,并联系到 DeepSeek 的完整训练流程。
第一,MLE 的核心思想:找让训练数据出现概率最大的参数。似然是概率的"逆向视角"——数据固定,参数是变量。取对数把乘积变加法,最小化 NLL 等价于最大化似然。
第二,MLE 推导分类损失:假设标签服从分类分布,NLL 推导出交叉熵损失。最小化交叉熵 = 最大化真实类别的预测概率。梯度形式为 $\hat{p}_k - y_k$,简洁优雅且数值稳定。
第三,MLE 推导回归损失:假设残差服从高斯分布,NLL 推导出 MSE 损失。假设拉普拉斯分布,推导出 MAE 损失。损失函数的选择是噪声分布假设的反映——高斯假设对离群点敏感,拉普拉斯假设对离群点鲁棒。
第四,语言模型的训练目标是自回归 NLL——对每个位置的条件概率做 MLE,链式法则把文本概率分解为逐 token 条件概率之积。困惑度(PPL)是 NLL 的指数,直觉上表示模型"在多少个候选里犹豫"。
第五,MLE 等价于最小化模型分布和数据分布之间的 KL 散度。Fisher 信息矩阵的对角近似正是 Adam 优化器的二阶矩,解释了 Adam 比 SGD 更高效的理论原因。
第六,DeepSeek 的三阶段训练都在 MLE 框架里:预训练和 SFT 是标准 NLL,GRPO 是带奖励权重的 NLL 加 KL 正则。强化学习不是另一套体系,是 MLE 的加权推广。
下一篇预告:
第15篇,我们讲信息熵与交叉熵:语言模型损失函数的数学根源。
MLE 告诉我们"用什么标准估计参数",但交叉熵和熵本身有更深的信息论含义——熵是信息量的度量,交叉熵是"用错误的分布编码信息付出的代价"。第15篇从信息论角度重新理解训练损失,并深入 KL 散度——它不只出现在 GRPO 里,它是衡量两个分布距离的通用工具。
MLE 是一种哲学:用手头的证据(数据),找到最能"解释"这些证据的假设(参数)。它简单、合理、有理论保证。深度学习的训练,从头到尾都是在做 MLE——预训练在做,微调在做,强化学习也在做,只是数据和权重不一样。理解了 MLE,你就理解了为什么模型会"学习"。
还没有评论,来抢沙发吧!
博客管理员
40 篇文章
还没有评论,来抢沙发吧!