尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

极大似然估计详解:从概率原理到Python实现与Manim可视化

极大似然估计详解:从概率原理到Python实现与Manim可视化 很多人第一次接触极大似然估计Maximum Likelihood Estimation简称 MLE是在机器学习的概率论章节里。教材通常会丢出一个公式[ \hat{\theta}{MLE} \arg\max{\theta} L(\theta) ]然后告诉你“最大化似然函数即可”。但问题在于为什么这个公式合理似然函数和概率分布到底有什么区别代码里到底怎么算如果要做动画展示又该怎么下手这篇文章的目标就是解决这几个问题。我会从零开始用抛硬币这个最小例子讲清楚 MLE 的直觉和数学原理再给出 Python 实现最后用 Manim 制作一个可视化动画把“搜索参数、观察似然值变化”的过程直观展示出来。整个过程中你不需要很强的数学背景只要会 Python 基础就能跑通全部代码。我的判断是MLE 是连接统计推断和机器学习的一座关键桥梁。学不懂它后面再看贝叶斯估计、逻辑回归、深度学习的损失函数都会有一种“知其然而不知其所以然”的虚浮感。而通过自己动手推导、编码、做动画才能真正把这座桥打牢。1. 这篇文章真正要解决的问题在学习 MLE 时大部分人都会遇到三个典型困惑。第一个困惑似然和概率有什么区别教材上会写“概率描述已知参数下事件出现的可能性似然描述已知观测结果下参数的合理性”。这句话很严谨但初学者往往听完就忘。因为没有场景没有对比没有直观感受。第二个困惑为什么要用对数很多推导过程一上来就直接写“取对数似然”但为什么不直接对原函数求导为什么取对数之后最大值点不会变如果不理解这一点后面遇到复杂模型时就会很被动。第三个困惑公式能看懂代码写不出来。考试题里的 MLE 往往有解析解用笔算能算出来。但在真实项目里数据量一大、模型一复杂根本没有解析解。这时候需要靠数值优化方法去逼近最优参数。这个过程应该怎么写代码怎么验证结果对不对这篇文章会逐一回答这些问题。最值得读这篇文章的读者有三类正在学机器学习、统计学基础被 MLE 绕晕的新手准备用 Manim 做数学可视化但不知道如何设计动画脚本的开发者技术博主或教师想找一套“能讲、能动、能跑”的 MLE 讲解方案。读完本文后你应该达到三个目标能用一句话说清 MLE 的直觉能写出正态分布参数估计的 Python 代码能手写一个 Manim 场景把似然函数随参数变化的曲线动态展示出来。2. 极大似然估计的核心概念与直觉2.1 概率与似然两个视角看同一件事要理解 MLE最关键的在于切换视角。假设我们抛一枚硬币 10 次结果是有 7 次正面。现在要估计这枚硬币正面朝上的概率 (p)。从概率的视角看问题是这样提的如果已知 (p 0.5)那么抛 10 次出现 7 次正面的概率是多少这是一个正向问题我们可以用二项分布公式直接算。从似然的视角看问题变成了如果观测到了“10 次中 7 次正面”这个结果那么 (p) 最可能是多少这是一个反向问题。我们要在 (p) 的所有可能取值中找出一个值使得“7 次正面”这件事发生的可能性最大。注意这里的措辞“可能性”而不是“概率”。因为 (p) 是参数不是随机变量严格来说不能说“(p0.5) 的概率”。但在直觉上你可以把似然理解为“给定结果参数取某个值的合理程度”。一个通俗类比假设你在一个房间里听到外面有人说话但你只听到声音、没看到人。你判断“这是不是一个成年男性”需要根据声音特征反推说话人的身份。这就像根据观测结果反推参数。MLE 做的正是这种事找到最能解释当前观测数据的那个参数。2.2 似然函数的定义假设我们有一组独立同分布的观测样本 (x_1, x_2, ..., x_n)它们来自某个概率分布族概率密度函数为 (f(x; \theta))其中 (\theta) 是待估计参数。联合概率密度可以写成[ L(\theta) \prod_{i1}^{n} f(x_i; \theta) ]这个 (L(\theta)) 就是似然函数。因为样本独立所以联合概率等于每个样本概率的乘积。注意一件事当数据固定时(L(\theta)) 是关于 (\theta) 的函数。(\theta) 的变化会影响这个函数值的大小。MLE 的目标就是找到一个 (\hat{\theta})使得[ \hat{\theta}{MLE} \arg\max{\theta} L(\theta) ]换句话说我们不是在问“哪个分布最能描述数据”而是在问“哪个参数值最能解释已经发生的数据”。2.3 为什么要取对数这是一个非常经典的问题。直接最大化 (L(\theta)) 不好吗为什么要变成最大化 (\log L(\theta))原因有三个。第一乘积变求和计算更稳定。当样本量很大时(L(\theta)) 是成千上万个概率值的乘积很容易出现数值下溢也就是计算机精度不够乘积直接变成 0。取对数后连乘变成连加数值范围小得多。第二对数函数是单调递增函数。如果 (a b)那么 (\log a \log b)。这意味着最大化 (L(\theta)) 和最大化 (\log L(\theta)) 得到的最优参数完全相同。所以我们完全可以对似然函数取对数再求导、求极值结果不会变。第三求导更方便。乘积的导数法则用起来很痛苦而求和之后每一项单独求导就舒服多了。你可能会问那为什么不取别的单调函数比如开方因为对数还有一个额外的好处它对概率的乘积结构有天然的适配性。概率的乘积本质上是信息量的叠加而对数恰好把叠加变成加法这在信息论上也有对应解释。不过作为初学者先记住计算上的三点原因就够了。2.4 频率学派视角在统计学中MLE 是频率学派的核心方法之一。频率学派认为参数是固定但未知的常数我们通过样本去估计它。与之相对的是贝叶斯学派认为参数本身也是随机变量需要引入先验分布。这个区别在工程上会引出 MAP最大后验估计等方法。理解了 MLE再学 MAP 时只需要在似然函数后面乘一个先验分布然后一起最大化即可。所以 MLE 是一块最重要的基石。3. 从抛硬币开始MLE 的最小完整例子为了建立直观理解我们用抛硬币这个最简单的问题走一遍完整推导。假设抛硬币 (n) 次正面出现 (k) 次。设正面概率为 (p)则观测到结果的概率是[ P(\text{数据}|p) C_n^k p^k (1-p)^{n-k} ]在这个表达式中如果 (n) 和 (k) 是已知观测值那么它就可以看作关于 (p) 的似然函数[ L(p) C_n^k p^k (1-p)^{n-k} ]由于组合数 (C_n^k) 与 (p) 无关在最大化时可以直接忽略。于是我们要最大化[ L(p) \propto p^k (1-p)^{n-k} ]取对数[ \ell(p) k \log p (n-k) \log(1-p) ]对 (p) 求导并令导数为零[ \frac{d\ell(p)}{dp} \frac{k}{p} - \frac{n-k}{1-p} 0 ]解这个方程[ \frac{k}{p} \frac{n-k}{1-p} ][ k(1-p) (n-k)p ][ k - kp np - kp ][ k np ][ \hat{p} \frac{k}{n} ]这个结果非常符合直觉正面出现的概率估计值就是正面出现的频率。从这个小例子可以看出 MLE 的完整流程写出观测数据的概率表达式也就是似然函数取对数简化计算对参数求导令导数等于零解方程。当然不是所有模型都能解出解析解。对于无法手算的模型就需要用数值优化方法去逼近最大值。这一部分后面会讲。4. 一般化正态分布的 MLE 推导抛硬币问题属于离散分布现在看一个连续分布的例子正态分布。假设样本 (x_1, x_2, ..., x_n) 独立同分布于正态分布 (N(\mu, \sigma^2))其中 (\mu) 和 (\sigma^2) 都是待估参数。正态分布的概率密度函数是[ f(x; \mu, \sigma^2) \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) ]似然函数为[ L(\mu, \sigma^2) \prod_{i1}^{n} \frac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\frac{(x_i-\mu)^2}{2\sigma^2}\right) ]取对数后[ \ell(\mu, \sigma^2) -\frac{n}{2} \log(2\pi) - \frac{n}{2} \log(\sigma^2) - \frac{1}{2\sigma^2} \sum_{i1}^{n} (x_i - \mu)^2 ]注意这里有两个参数我们需要分别对 (\mu) 和 (\sigma^2) 求偏导。先对 (\mu) 求偏导[ \frac{\partial \ell}{\partial \mu} \frac{1}{\sigma^2} \sum_{i1}^{n} (x_i - \mu) ]令导数为零[ \sum_{i1}^{n} (x_i - \mu) 0 ][ n\mu \sum_{i1}^{n} x_i ][ \hat{\mu} \frac{1}{n} \sum_{i1}^{n} x_i ]也就是说正态分布均值的 MLE 就是样本均值这符合直觉。再对 (\sigma^2) 求偏导[ \frac{\partial \ell}{\partial \sigma^2} -\frac{n}{2\sigma^2} \frac{1}{2\sigma^4} \sum_{i1}^{n} (x_i - \mu)^2 ]令导数为零[ -\frac{n}{2\sigma^2} \frac{1}{2\sigma^4} \sum_{i1}^{n} (x_i - \mu)^2 0 ]两边乘以 (2\sigma^4)[ -n\sigma^2 \sum_{i1}^{n} (x_i - \mu)^2 0 ][ \hat{\sigma}^2 \frac{1}{n} \sum_{i1}^{n} (x_i - \mu)^2 ]注意这里得到的是除以 (n) 的方差。而在统计学中样本方差通常使用 (n-1) 作为分母那是一个无偏估计。MLE 给出的方差是有偏的这个偏差会在小样本时比较明显。有一个小细节值得注意我们推导 MLE 时先用 (\hat{\mu}) 的表达式代入到 (\hat{\sigma}^2) 中。在实际计算时通常会用样本均值代替 (\mu)再计算方差。这个例子展示了 MLE 的完整流程而且告诉我们一个重要结论MLE 的结果很直观很多时候它就是一个“按频率或均值来估计参数”的方法。但直观背后有严格的概率推导支撑。5. 用 Python 实现极大似然估计5.1 生成实验数据我们先构造一组已知真实分布的样本数据。假设真实的正态分布参数为 (\mu3.0, \sigma1.5)生成 1000 个样本。# 文件路径ml_demo.py import numpy as np import matplotlib.pyplot as plt np.random.seed(42) mu_true 3.0 sigma_true 1.5 n 1000 data np.random.normal(mu_true, sigma_true, n) plt.hist(data, bins50, densityTrue, alpha0.6, colorsteelblue) plt.title(Histogram of Observed Data) plt.show()运行这段代码你会看到一组近似钟形的直方图。我们的目标就是通过这组样本反推出 (\mu) 和 (\sigma^2) 的估计值。5.2 手动实现解析解根据第 4 节的推导正态分布的 MLE 解析解就是样本均值和样本方差。# 继续在 ml_demo.py 中编写 mu_mle np.mean(data) sigma2_mle np.var(data) # 注意 np.var 默认除以 n对应 MLE sigma_mle np.sqrt(sigma2_mle) print(f真实参数: mu{mu_true}, sigma{sigma_true}) print(fMLE估计: mu{mu_mle:.4f}, sigma{sigma_mle:.4f}) print(f估计的方差(除以n): {sigma2_mle:.4f}) print(f无偏方差(除以n-1): {np.var(data, ddof1):.4f})输出效果类似真实参数: mu3.0, sigma1.5 MLE估计: mu3.0462, sigma1.4924 估计的方差(除以n): 2.2272 无偏方差(除以n-1): 2.2295可以看到当样本量为 1000 时MLE 结果已经非常接近真实参数。这体现了 MLE 的渐近一致性样本量越大估计越准。5.3 通用数值优化实现现实中很多模型的似然函数没有解析解我们需要通过数值优化方法最大化对数似然。Python 中可以使用scipy.optimize.minimize。核心思路是定义负对数似然函数然后用优化器最小化它。# 文件路径mle_optimize.py import numpy as np from scipy.optimize import minimize def neg_log_likelihood(params, data): mu, sigma params if sigma 0: return 1e10 # 方差必须为正 n len(data) # 正态分布负对数似然 ll -n/2 * np.log(2 * np.pi) - n * np.log(sigma) - np.sum((data - mu)**2) / (2 * sigma**2) return -ll # 因为是负对数似然 # 使用上面生成的数据也可以重新生成 np.random.seed(42) data np.random.normal(3.0, 1.5, 1000) # 初始化参数 init_params [0.0, 1.0] result minimize(neg_log_likelihood, init_params, args(data,), methodNelder-Mead) mu_opt, sigma_opt result.x print(f优化得到的 mu: {mu_opt:.4f}) print(f优化得到的 sigma: {sigma_opt:.4f}) print(f优化是否成功: {result.success}) print(f优化信息: {result.message})运行效果类似优化得到的 mu: 3.0462 优化得到的 sigma: 1.4924 优化是否成功: True 优化信息: Optimization terminated successfully.这段代码的价值在于它不依赖具体的概率分布形式。你只需要改neg_log_likelihood函数内部的计算逻辑就能估计任意分布族的参数。这也是 MLE 在工程上真正通用的原因。注意一个细节我特意检查了sigma 0的情况。因为在优化过程中算法可能会尝试访问不合法参数比如标准差为负数。如果不加约束np.log(sigma)会直接报错。6. 用 Manim 制作极大似然估计动画6.1 为什么用 Manim 做数学可视化Manim 是一个用 Python 编写数学动画的引擎最初由 3Blue1Brown 开发后来社区维护了 Manim Community Edition。它对数学公式、坐标轴、函数图像有非常好的支持特别适合用来展示“参数变化时函数如何变化”这类动态过程。MLE 正好是一个动态过程我们不断调整参数 (\mu)观察似然函数值的变化直到找到最大值。这个过程用静态图片很难讲清楚而用 Manim 可以直观呈现“搜索”的感觉。6.2 安装 Manim Community Edition推荐使用虚拟环境安装pip install manim安装完成后可以用下面的命令检查版本manim --version如果要在 Jupyter Notebook 中渲染还需要安装相关依赖。不过命令行渲染已经足够使用。6.3 动画场景设计与完整代码我们的动画目标在一个二维坐标平面中展示似然函数随参数 (\mu) 变化的动态过程。设计思路如下固定真实参数为 (\mu3, \sigma1.5)生成一组样本在遍历 (\mu) 从 -2 到 8 的过程中计算当前 (\mu) 下的负对数似然绘制似然函数曲线并用一个移动的垂直线标记当前搜索到的 (\mu)当 (\mu) 接近样本均值时负对数似然值最低也就是似然值最高。为了让代码简单易读下面使用一个简化版本先根据真实参数生成样本然后绘制负对数似然曲线再用一个小圆点沿着曲线移动直到最低点。# 文件路径mle_animation.py import numpy as np from manim import * class MLECurveScene(Scene): def construct(self): # 固定随机种子保证可复现 np.random.seed(42) data np.random.normal(3.0, 1.5, 100) # 计算负对数似然函数 def neg_log_likelihood(mu): sigma 1.5 # 固定 sigma只看 mu 的影响 n len(data) ll -n/2 * np.log(2 * np.pi) - n * np.log(sigma) - np.sum((data - mu)**2) / (2 * sigma**2) return -ll # 设置坐标轴 axes Axes( x_range[-2, 8, 1], y_range[-50, 300, 50], x_length10, y_length5, axis_config{include_numbers: True}, ) labels axes.get_axis_labels(x_labelmu, y_labelNLL) # 绘制负对数似然曲线 graph axes.plot( lambda x: neg_log_likelihood(x), x_range[-2, 8], colorBLUE, ) # 标记样本均值位置 mu_hat np.mean(data) v_line axes.get_vertical_line( axes.c2p(mu_hat, 0), colorYELLOW, line_funcLine, ) mu_label MathTex(r\hat{\mu} f{mu_hat:.2f}).next_to(v_line, UP) # 点沿曲线移动到最低点 dot Dot(axes.c2p(-2, neg_log_likelihood(-2)), colorRED) animation_point always_redraw( lambda: dot.move_to(axes.c2p(dot.get_center()[0], 0)) ) self.play(Create(axes), Write(labels)) self.play(Create(graph), run_time2) # 让点沿曲线移动 self.play( MoveAlongPath(dot, graph), run_time4, rate_funcsmooth, ) self.play(Create(v_line), Write(mu_label)) self.wait(2)这段代码有一个需要注意的地方为了演示简便我固定了 (\sigma1.5)只让 (\mu) 变化。这样做的好处是动画更简单缺点是没有体现二维参数空间的搜索过程。如果读者希望看到二维效果可以用三维曲面或热力图来扩展但代码复杂度会明显增加。6.4 渲染动画在终端中运行manim -p -ql mle_animation.py MLECurveScene参数说明-p表示渲染完成后自动播放视频-ql表示低画质快速渲染调试时用这个如果要输出高清版本可以使用-qh。首次运行 Manim 时会生成动画文件默认输出到media/videos/mle_animation/目录下。如果运行时报错ModuleNotFoundError: No module named manim说明当前虚拟环境未激活或者未安装 Manim检查一下安装步骤。6.5 动画效果解释动画中蓝线是负对数似然NLL曲线。NLL 越低说明似然越高。你会看到曲线呈现一条抛物线形状最低点位于样本均值附近。红色圆点沿曲线滑动时最终停在最低点黄色竖线标记的位置就是 (\hat{\mu})。这个动画的核心视觉冲击力在于观众能直接感受到“搜索参数”的过程。如果你在教学中使用可以先让学生猜哪个位置的 NLL 最低再播放动画验证效果会更好。7. 极大似然估计常见问题与排查方法问题现象可能原因排查方式解决方案似然函数值为 0样本量太大概率乘积下溢检查是否使用了 log 似然改用对数似然不要直接计算乘积优化器报错或收敛失败参数初始化不当或约束未加打印每次迭代的参数值设置合理的参数初值显式添加参数范围约束方差的 MLE 与样本方差不一致一个除以 n一个除以 n-1检查计算方式明确你的目标估计参数用 MLE做无偏推断用无偏方差使用scipy.optimize时函数返回 NaN对负数取对数或除零在目标函数里检查参数范围添加非法参数判断返回一个大的惩罚值Manim 渲染不出图形安装版本不对或缺少系统依赖查看完整错误栈重新安装 Manim Community Edition并安装 ffmpeg优化结果偏离解析解数值优化只找到局部最优用多个初始值测试尝试不同初值或使用全局优化方法这些坑几乎每个做 MLE 实战的人都会遇到。尤其是数值下溢和参数越界在真实项目中非常常见。建议在写目标函数时把参数合法性检查放在最前面。8. MLE 的最佳实践与工程建议8.1 优先使用对数似然无论手算还是写代码永远使用对数似然而不是原始似然。这不仅是为了数值稳定也是为了后续求导方便。在 Python 中scipy.stats模块里的大多数分布都提供了logpdf方法直接用就行。例如from scipy.stats import norm # 更稳健的写法 log_likelihood norm.logpdf(data, locmu, scalesigma).sum()这样写既清晰又不容易出现数值问题。8.2 区分 MLE 与 MAP如果引入了参数的先验分布最大化目标就变成了[ \log P(\theta | \text{data}) \propto \log P(\text{data} | \theta) \log P(\theta) ]这就是 MAP最大后验估计。MLE 可以看作先验分布为均匀分布时的 MAP。机器学习中常用的 L2 正则化就等价于给参数加了高斯先验。理解这层关系后你会发现正则化不再是一个凭空引入的“技巧”而是有概率解释的。8.3 验证 MLE 收敛性在数值优化中不能只关注最终结果还要看优化器是否真正收敛。可以打印优化成功标志、迭代次数和梯度范数。如果可能把解析解和数值解放在一起对比确认结果一致后再应用到后续流程。8.4 使用 Bootstrap 评估不确定性MLE 只给出点估计不直接给出估计的置信区间。工程上常用 Bootstrap 方法对原始样本有放回抽样多次每次重新估计参数然后得到参数估计的分布。这种做法在模型复杂度较高时比理论近似更可靠。8.5 Manim 动画的工程建议做可视化时不要一上来就画复杂的三维曲面。先画一维曲线确认逻辑正确再逐步增加维度。Manim 的调试建议用-ql低画质快速迭代把复杂的 Scene 拆成多个小 Scene 分别测试在动画中加入文本或数字方便核对参数值的变化注意always_redraw只用于需要实时更新的元素不要滥用。9. 总结与后续学习方向这篇文章从抛硬币的最小例子出发讲清了极大似然估计的直觉在给定观测数据的条件下选择最合理的参数让数据出现的可能性最高。随后推导了正态分布的解析解并给出了两种 Python 实现方式解析法和数值优化法。最后使用 Manim 制作了一个简单的曲线动画将负对数似然的最小化过程可视化。对于初学者下一步建议按这个顺序继续深入推导逻辑回归的损失函数你会发现交叉熵损失本质上就是负对数似然学习 MAP 与贝叶斯估计理解先验分布如何影响参数估计实操一个混合高斯模型GMM体会没有解析解时如何借助 EM 算法和数值优化处理复杂似然函数尝试给自己的模型写一个似然函数并用scipy.optimize或 PyTorch 做参数估计对比不同优化方法的效果。在动手实践时有几个关键点需要记住始终使用对数似然注意参数合法性约束用多种初值验证优化结果小样本时警惕 MLE 的有偏性。如果你还在学习阶段建议把本文的代码亲手敲一遍并把第 6 节的 Manim 动画改成展示不同样本量下估计值的变化这个练习会帮你把 MLE 彻底吃透。
返回列表