这次我们来看“极大似然估计”这个概念。不是泛泛讲定义,而是从零开始,把似然函数、对数似然、求导取最大值的每一步,配合 manim 动画一起讲透。很多人在学机器学习时被极大似然估计卡住,不是公式看不懂,而是没有一个直观图景:为什么取对数?为什么要求导?为什么最后得到的参数是样本均值?用 manim 做一个教学动画,这些问题是可以被“看见”的。
manim 是 3Blue1Brown 开源的数学动画引擎,核心价值是让数学推导过程变成动画。它用 Python 编写,通过编写场景类,可以生成视频文件。对于“极大似然估计”这种带函数曲线、参数变化、极值点的内容,manim 特别合适。本文会先讲清楚极大似然估计的数学原理,再给出一套 manim 可视化实现方案,包括环境准备、代码示例、渲染命令和常见问题排错。
读这篇文章的人,可能是正在学机器学习的学生、需要做数学讲解视频的内容创作者,或者想用程序化动画提升表达力的开发者。只要会一点 Python,能跑命令行,就能跟着把场景渲染出来。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 数学概念讲解 + 程序化动画可视化 |
| 核心知识点 | 极大似然估计(Maximum Likelihood Estimation,MLE) |
| 可视化工具 | manim(数学动画引擎) |
| 运行环境 | Python 3 环境,建议使用虚拟环境或 Conda 管理 |
| 关键依赖 | manim、FFmpeg(视频输出)、LaTeX(可选,用于公式渲染) |
| 启动方式 | 命令行渲染:manim -pql 文件名.py 场景类名 |
| 批量渲染 | 支持多场景连续渲染,也可通过脚本循环处理多个文件 |
| GPU/显存需求 | 不是必须项,通常 CPU 即可完成渲染;如启用 GPU 加速需按本机环境单独验证 |
| 适合场景 | 数学网课、论文讲解、机器学习概念科普、个人知识复盘 |
| 不适合场景 | 追求影视级特效、需要二维矢量动画之外复杂粒子效果的场景 |
manim 的定位不是通用视频剪辑软件,而是“数学语言的可视化工具”。它适合把公式推导、函数变化、几何关系做成清晰连贯的动画。极大似然估计里最关键的就是“似然函数曲线”和“最大值对应的参数位置”,这两点用 manim 画出来,理解成本会降低一大截。
2. 适用场景与使用边界
2.1 这个方案适合谁
第一类读者是正在学习统计学或机器学习的同学。极大似然估计是朴素贝叶斯、逻辑回归、线性回归参数求解的共同基础,但课本上常直接给公式推导,缺少直观展示。如果你自己手写一个 manim 动画,把曲线画出来,比干看公式更有效。
第二类读者是做技术分享或教学内容的人。用 manim 生成的视频可以嵌入 CSDN 博客、B 站、知识付费课程或内部培训材料。它的代码化制作方式也方便后续维护和迭代,改一个参数重新渲染,就能得到新版本动画。
第三类读者是想把数学问题“可视化”的工程师。manim 的场景代码就是一套动画逻辑,可以复用、组合,适合做个人技术笔记和演示工具。
2.2 能解决什么问题
- 把“似然函数随参数变化”直观呈现,避免停留在公式层面。
- 把“最大化似然函数”的过程表现为曲线上的点移动,突出极值点。
- 通过动画对比不同样本量下的似然曲线,理解估计的一致性。
- 将推导步骤拆成多个动画片段,方便控制教学节奏。
2.3 不适合什么场景
如果只是想在文章里放一张静态图,不需要动画,那么用 matplotlib 画图更快捷。如果要做实景拍摄或访谈类视频,manim 也不是合适工具。另外,manim 对复杂 3D 场景支持有限,虽然也有 3D 相机功能,但学习成本较高,不建议一上来就挑战复杂场景。
2.4 版权、隐私与合规边界
使用 manim 时,要遵循所选发行版本的开源许可要求。如果要在公开课程或商业课程中使用,需要检查对应依赖库和字体资源的授权情况。如果在教学中使用到具体数据或人脸素材,需要确保有合法来源和授权。涉及模型参数、内部业务数据时,先脱敏再演示。边界很清楚:工具可以开源使用,但内容不能侵权。
3. manim 本地部署环境准备
3.1 操作系统与 Python 环境
manim 支持 Windows、macOS、Linux,使用前提是能安装 Python 3 环境。建议用 Conda 或 venv 创建独立环境,避免和已有项目依赖冲突。
# 创建虚拟环境(macOS/Linux) python3 -m venv manim_env source manim_env/bin/activate # Windows PowerShell python -m venv manim_env .\manim_env\Scripts\Activate.ps13.2 安装 manim 主库
当前主流的社区版 manim 安装方式比较简单,核心依赖会自动拉取。安装命令如下:
pip install manim安装完成后,验证是否成功:
manim --version如果能输出版本号,说明安装成功。如果提示找不到命令,通常是当前虚拟环境没有激活,或者 pip 安装路径不在系统 PATH 中。
3.3 安装 FFmpeg
manim 渲染视频需要 FFmpeg 进行编码。没有 FFmpeg 时,渲染任务会卡在最后一步,或者直接报错。
- Windows:可以下载 FFmpeg 并解压,把 bin 目录加入系统 PATH。
- macOS:
brew install ffmpeg。 - Linux:
sudo apt install ffmpeg或按发行版包管理器安装。
验证方式:
ffmpeg -version3.4 安装 LaTeX(可选但推荐)
manim 中的Tex和MathTex对象需要 LaTeX 来渲染数学公式。如果只是用Text显示普通文字,可以不装。但要展示似然函数公式,强烈建议安装 TeX 发行版:
- Windows:MiKTeX。
- macOS:MacTeX(体积较大,也可以装 BasicTeX)。
- Linux:TeX Live。
安装完成后,manim 渲染MathTex时才能正常生成公式动画。
3.5 硬件与磁盘空间
manim 的渲染瓶颈主要在 CPU,多数场景 CPU 就能跑。若场景非常复杂,渲染时间会明显拉长,此时可通过降低分辨率、降低帧率来缓解。磁盘方面,一个中等质量动画视频通常几十 MB,但如果生成 4K 长视频,占用会显著增加。建议在工作目录保留至少 2GB 可用空间,避免输出中断。
4. manim 启动与项目结构
manim 的启动方式不是双击图标,而是通过命令行执行场景文件。一个典型工作目录如下:
mle_animation/ ├── scene.py ├── media/ │ ├── videos/ │ └── images/ └── assets/4.1 编写第一个场景文件
创建一个scene.py,内容如下:
from manim import Scene, Text class HelloMLE(Scene): def construct(self): text = Text("极大似然估计") self.play(Create(text)) self.wait(1)4.2 命令行渲染
终端进入scene.py所在目录,执行:
manim -pql scene.py HelloMLE参数说明:
-p:渲染完成后自动打开预览窗口。-q:指定视频质量,l为低质量,m为中等质量,h为高质量,k为 4K。- 后面的
HelloMLE是场景类的类名。
渲染完成后,视频文件会生成在media/videos/scene/目录下,文件名为HelloMLE.mp4。
4.3 使用低质量快速测试
写动画场景时,建议先用-ql渲染,确认内容无误后再用-qh出正式版本。这样可以节省大量时间。对于只有简单文字和图形的场景,-ql几秒就能出结果。
5. 极大似然估计核心概念:从零讲透
5.1 一个抛硬币例子
假设我们拿到一枚硬币,不知道正面朝上的概率是多少。设正面概率为 (p),反面概率为 (1-p)。现在抛了 10 次,结果有 7 次正面、3 次反面。那么“当前这组结果出现的可能性”可以写成:
[ L(p) = p^7(1-p)^3 ]
这个函数叫做似然函数,它的自变量是未知参数 (p),因变量是在该参数下当前样本出现的概率。注意这里和概率密度函数的视角不同:概率视角是固定参数,问样本的概率;似然视角是固定样本,问哪个参数让这个样本“看起来最合理”。
极大似然估计的核心思想就是:找到使 (L(p)) 最大的 (p),把它作为真实参数的估计值。
5.2 为什么要取对数
直接对 (L(p) = p^7(1-p)^3) 求导也可以,但当样本数量变大时,连乘会导致数值下溢,而且导数表达式会很复杂。取对数不会改变最大值点,因为对数函数是单调递增的。可以得到对数似然函数:
[ \ln L(p) = 7\ln p + 3\ln(1-p) ]
取对数后,乘法变成加法,求导变得容易,数值稳定性也更好。
5.3 求导并解方程
对 (p) 求导:
[ \frac{d}{dp} \ln L(p) = \frac{7}{p} - \frac{3}{1-p} ]
令导数为 0:
[ \frac{7}{p} - \frac{3}{1-p} = 0 ]
解得:
[ p = \frac{7}{10} ]
也就是说,10 次试验中 7 次正面,极大似然估计的结果就是正面频率 (7/10)。这个结论很直观,但背后的推导逻辑值得讲清楚。
5.4 正态分布均值估计
再看一个更常用的情况:假设数据 (x_1, x_2, \dots, x_n) 来自正态分布 (N(\mu, \sigma^2)),其中 (\sigma^2) 已知,需要估计均值 (\mu)。
似然函数为:
[ L(\mu) = \prod_{i=1}^n \frac{1}{\sqrt{2\pi\sigma^2}} \exp \left( -\frac{(x_i - \mu)^2}{2\sigma^2} \right) ]
取对数后:
[ \ln L(\mu) = -\frac{n}{2}\ln(2\pi\sigma^2) - \sum_{i=1}^n \frac{(x_i - \mu)^2}{2\sigma^2} ]
对 (\mu) 求导并令导数为 0:
[ \sum_{i=1}^n (x_i - \mu) = 0 ]
得到:
[ \hat{\mu} = \frac{1}{n}\sum_{i=1}^n x_i ]
所以正态分布均值的极大似然估计就是样本均值。这个结论在数理统计和机器学习中反复出现。
5.5 理解“极大似然”的直观意义
极大似然估计本质上是在“给定数据”的前提下,去寻找让数据出现概率最大的参数。它解决的是反问题:我们都知道数据是从某个分布里抽出来的,但分布参数未知,于是用数据反过来推断最可能的参数。这个“反过来”的过程,如果只靠文字描述,容易绕晕。manim 的价值就在这里:把似然函数曲线画出来,让观众看到一个点沿着曲线移动,在最高点停下,这个点对应的横坐标就是参数估计值。
6. 用 manim 实现极大似然估计可视化
6.1 场景设计思路
针对抛硬币例子,动画可以分成三个片段:
- 显示 10 次抛硬币结果,其中 7 个正面,3 个反面。
- 绘制似然函数曲线 (L(p) = p^7(1-p)^3),横轴为 (p),纵轴为似然值。
- 在曲线最高点放置一个红色圆点,显示对应的 (p = 0.7)。
这样的节奏符合“从零讲透”的目标:先有数据,再画函数,最后找极值。
6.2 场景代码示例
下面是一个可运行的 manim 场景,绘制似然函数曲线并标记极大值点。
from manim import * import numpy as np class LikelihoodCurve(Scene): def construct(self): # 坐标系 axes = Axes( x_range=[0, 1, 0.1], y_range=[0, 0.003, 0.001], axis_config={"include_numbers": True}, ) labels = axes.get_axis_labels(x_label="p", y_label="L(p)") # 显示数据 result_text = Text("10 次抛硬币:7 正 3 反") result_text.to_edge(UP) self.play(Write(result_text)) # 似然函数 def likelihood(p): return p**7 * (1 - p)**3 curve = axes.plot(likelihood, color=YELLOW) curve_label = MathTex("L(p) = p^7(1-p)^3").set_color(YELLOW) curve_label.to_corner(UL) self.play(Create(axes), Write(labels)) self.play(Create(curve), Write(curve_label)) # 最大值点 max_p = 0.7 max_likelihood = likelihood(max_p) dot = Dot(axes.coords_to_point(max_p, max_likelihood), color=RED) dot_label = MathTex("p=0.7").next_to(dot, DOWN) self.play(FadeIn(dot), Write(dot_label)) self.wait(2)代码注释已经说明各部分用途。需要根据实际渲染效果调整坐标轴范围,因为 (L(p)) 的最大值约等于 0.0022,如果 y 轴范围过大,曲线会显得很平。
6.3 渲染这个场景
将代码保存为likelihood_scene.py,执行:
manim -pql likelihood_scene.py LikelihoodCurve等待渲染完成后,会打开一个预览窗口。预期结果是:黄色曲线呈单峰形状,红色圆点标记在峰值位置,左下角显示似然函数公式。如果曲线过平或超出坐标范围,调整y_range即可。
6.4 扩展:动态展示不同样本量的影响
可以通过ValueTracker控制抛硬币次数,观察似然曲线随样本量变化而变尖的过程。核心逻辑是:样本量越大,似然曲线越集中,参数估计越稳定。这个动态效果比静态曲线更有教学冲击力。
from manim import * class MLEConvergence(Scene): def construct(self): axes = Axes( x_range=[0, 1, 0.1], y_range=[0, 0.05, 0.01], axis_config={"include_numbers": True}, ) labels = axes.get_axis_labels(x_label="p", y_label="L(p)") self.add(axes, labels) n = 10 k = 7 def likelihood(p): return p**k * (1 - p)**(n - k) curve = axes.plot(likelihood, color=YELLOW) self.play(Create(curve), run_time=2) # 这里可以结合 updater 实现参数变化,但要注意 y 轴范围需要同步调整 self.wait(1)这个示例只展示了静态版本。完整动态版本需要根据n的变化重新计算曲线,并保持最大值点位置可见。实际渲染时需要思考坐标轴是否自动适配,或固定范围后观察曲线形态变化。
6.5 判断动画是否成功
- 曲线形状是否正确:抛硬币似然函数是单峰函数,峰值接近 (p=0.7)。
- 坐标刻度是否可读:横轴 (p) 在 0 到 1 之间,纵轴数值不能溢出或过小。
- 动画节奏是否合适:关键步骤之间停顿至少 1 秒,方便观看者理解。
- 公式对象是否正常显示:如果
MathTex渲染失败,需要检查 LaTeX 安装情况。
7. 批量渲染与脚本集成
manim 不是传统 API 服务,但可以通过命令行和脚本实现批量渲染。当你有多个场景文件或一个文件中有多个场景类时,可以逐个渲染。
7.1 单个文件多个场景
在同一个.py文件中定义多个 Scene 类:
class SceneA(Scene): def construct(self): # ... pass class SceneB(Scene): def construct(self): # ... pass然后通过命令行指定类名渲染:
manim -qm likelihood_scene.py SceneA manim -qm likelihood_scene.py SceneB7.2 shell 批量渲染脚本
如果需要批量渲染一个目录下的所有场景,可以写一个简单的 shell 脚本:
#!/bin/bash scenes=("SceneA" "SceneB" "SceneC") for scene in "${scenes[@]}" do manim -qm likelihood_scene.py "$scene" done在 Windows PowerShell 中类似:
$scenes = @("SceneA", "SceneB", "SceneC") foreach ($scene in $scenes) { manim -qm likelihood_scene.py $scene }批量渲染时要注意:如果某个场景出错,脚本默认会中断。可以加上错误捕获,例如在 bash 中使用set -e前先导出日志,或者把每个场景的渲染结果记录到独立日志文件,方便定位问题。
7.3 将 manim 渲染集成到自动化流程
如果你有 CI/CD 环境,可以在代码变更后自动渲染教学视频。思路是:
- 使用一条安装命令安装依赖。
- 使用一条命令渲染全部场景。
- 将
media/videos目录作为工作产物输出。
示例 GitHub Actions 伪代码:
- name: Install dependencies run: pip install manim - name: Render all scenes run: | manim -qm likelihood_scene.py SceneA manim -qm likelihood_scene.py SceneB需要注意具体路径和命令需按项目实际结构调整。manim 渲染属于重 CPU 任务,CI 中要预留充足时间。
8. 资源占用与性能观察
8.1 渲染时看哪些资源
运行 manim 渲染时,可以在后台打开任务管理器或top命令观察 CPU 占用。manim 的主要计算量来自图形绘制和帧合成,因此 CPU 使用率通常较高,内存占用则和场景复杂度、画面尺寸相关。
如果场景里大量使用MathTex,第一次渲染公式需要调用 LaTeX,耗时较长,后续再渲染同场景时可能走缓存。这个过程中 CPU 和内存占用都会明显增加。
8.2 影响渲染时间的因素
- 视频质量:
-qk4K 渲染耗时远高于-ql。 - 帧率:默认帧率一般是 60fps,降低帧率可以缩短渲染时间。
- 场景复杂度:对象数量、动画时长、3D 变换都会增加计算量。
- 公式数量:每次 LaTeX 编译都会消耗时间。
- 硬件性能:CPU 单核性能影响最大,多核并行对 manim 帮助有限。
8.3 降低资源占用的方法
- 开发阶段使用
-ql低质量渲染。 - 控制单个场景动画时长,不必要的过程尽量缩短。
- 避免使用过多像素级特效,例如大量粒子和高分辨率贴图。
- 合理拆分场景,把复杂内容拆成多个短视频,避免单次长视频渲染失败后返工。
- 使用
--disable_caching并非总是好事,开启缓存可以在二次渲染时节省时间。
8.4 端口冲突与进程残留
manim 的预览功能可能会调用本地播放器,但一般不会监听网络端口。如果项目由 Jupyter 或 Web 服务拉起,要关注端口冲突。渲染异常退出后,检查残留的 Python 进程,及时清理,避免占用大量 CPU 和内存。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
执行manim命令提示找不到命令 | 虚拟环境未激活或安装失败 | 检查pip list是否有 manim | 激活虚拟环境,重新执行pip install manim |
| 渲染视频没有画面或黑屏 | 场景类名错误,或construct内未添加任何对象 | 查看控制台输出报错信息 | 确认类名一致,确认场景中至少self.add或self.play一个对象 |
| 公式显示为乱码或空白 | LaTeX 未正确安装 | 运行latex --version检查 | 安装 TeX Live 或 MiKTeX,重启终端 |
| 提示缺少 ffmpeg | FFmpeg 未安装或未加入 PATH | 运行ffmpeg -version | 安装 FFmpeg,并把可执行文件目录加入环境变量 |
| 渲染速度很慢 | 使用高质量参数或场景过于复杂 | 观察 CPU 占用和渲染日志 | 改用-ql快速测试,优化动画结构 |
| 视频文件没有自动打开 | 未使用-p参数,或播放器不支持 | 检查media/videos目录 | 手动打开输出文件,或加上-p参数 |
MathTex渲染报错 | LaTeX 宏包缺失或语法错误 | 查看窗口中的完整 LaTeX 日志 | 检查公式语法,必要时使用Text代替临时过渡 |
| 色块闪烁或对象位置不对 | 坐标范围与实际函数值不匹配 | 打印函数关键点值 | 调整 Axes 的 x_range 和 y_range |
9.1 处理 LaTeX 公式渲染问题
在使用MathTex时,如果出现红色报错框,最稳妥的办法是先在文档中单独渲染一个简单公式,确认 LaTeX 环境可用。如果简单公式能渲染,复杂公式报错,则多半是公式语法或未安装宏包。例如\(和\)并不是 LaTeX 的数学环境,manim 的MathTex中直接写p^7(1-p)^3即可。
9.2 处理运行内存不足
manim 在渲染高分辨率视频时可能会占用大量内存。如果遇到内存不足,应降低分辨率和动画复杂度,同时关闭其他大型应用。如果仍然经常卡死,可能是机器性能不足以支持该质量的渲染,需要更换设备或调整预期。
10. 最佳实践与使用建议
10.1 先做静态数学验证再写动画
在设计动画之前,先用 Python 画一份静态似然函数曲线,确认坐标轴范围和关键点位置。推荐用 matplotlib 快速验证数值,再用 manim 做动画。这能避免在 manim 调试阶段反复猜测参数。
示例静态验证代码:
import numpy as np import matplotlib.pyplot as plt p = np.linspace(0, 1, 200) L = p**7 * (1 - p)**3 plt.plot(p, L) plt.xlabel("p") plt.ylabel("L(p)") plt.title("Likelihood function") plt.show()确认最大值在 0.7 附近后,再把这个信息搬到 manim 场景中,曲线和坐标范围都是现成的。
10.2 保持动画节奏
教学动画不是越炫越好。每个关键点出现后,至少保留 1 到 2 秒停顿,让观众消化信息。文字、公式、图形不要同时全部铺开,分开出现效果更好。如果要在视频中配音,动画时长还需考虑口播速度。
10.3 分模块管理场景代码
不要把全部动画写在一个巨型文件里。建议按知识点拆成多个场景类,并抽离共用的数学函数和坐标配置。这样修改抛硬币次数时,不用动绘制代码。
示例目录结构:
mle_animation/ ├── common.py ├── scene_intro.py ├── scene_likelihood.py └── scene_maximize.py10.4 做好素材和输出目录管理
不要在工作目录堆一堆.mp4。利用 manim 的media目录特性,把输出文件自动归入media/videos/。如果要长期使用,可以在外部磁盘或网盘做备份。对于中间产物和临时测试脚本,也要定期清理。
10.5 教学数据与隐私合规
如果使用真实实验数据或业务数据,在公开演示前先脱敏。抛硬币、正态分布随机数这类合成数据没有版权问题,适合作为默认教学案例。如果需要用他人视频或图片作为背景素材,确保有授权。manim 本身能生成背景,通常不需要引入外部素材。
10.6 发布前进行全量回归
一次大的改动可能会影响之前正常渲染的场景。建议在正式发布前,跑一次批量渲染脚本,检查所有场景都能通过。重点检查公式渲染是否正常、坐标是否越界、动画是否卡顿。如果中途有场景失败,根据日志修复后重新渲染。
11. 总结与下一步
这次内容的关键是两条线:一条是极大似然估计的数学原理,另一条是 manim 动画实现。数学上,从抛硬币例子出发,理解了似然函数、对数似然、求导解方程这三步;动画上,写了一个完整的LikelihoodCurve场景,并知道怎么调整坐标、渲染、排错。
最先应该验证的场景是LikelihoodCurve,命令是最低质量的manim -pql likelihood_scene.py LikelihoodCurve。最容易踩的坑是缺少 FFmpeg 或 LaTeX,导致渲染流程中断。
如果这一步跑通了,后续可以继续扩展的方向有很多:比如给场景加上语音讲解脚本、把正态分布均值估计做成第二个动画、把多个场景合并成一个完整教学视频,再做一份字幕。manim 的学习曲线不算陡,但很值得投入,尤其适合把数学概念变成可复用的视觉资产。
建议从今天这个场景开始,动手跑一遍。渲染出来的 mp4 不大,但把抽象概念变成可见曲线的那几秒钟,价值很高。