目录
前言:你真的理解“可能性”吗?
在统计学和机器学习的浩瀚海洋中,有一个概念既是基石,又常常让初学者感到困惑,那就是——似然函数。你可能已经在最大似然估计(MLE)或贝叶斯推断中听说过它,但你是否真正厘清了它与“概率”的区别?
在这篇文章中,我们将深入探讨似然函数的本质。我们不仅会从数学定义上区分它,更重要的是,我们(作为数据科学从业者)将学会如何通过代码将其可视化,并将其应用于解决实际问题。我们会看到,似然函数不仅仅是冷冰冰的公式,它是连接“数据观察”与“参数推断”的桥梁。
核心概念:似然 vs 概率
首先,我们需要解决一个最常见的困惑:似然 和 概率 是一回事吗?
答案是否定的。虽然它们在数学公式上往往长得很像,但它们的视角完全不同。我们可以通过 Fisher 的经典名言来记忆:“概率”是关于参数已知时数据的预测;而“似然”是关于数据已知时参数的推断。
让我们用一个具体的场景来区分它们:
- 概率:假设我们知道这枚硬币是均匀的(参数 $p=0.5$)。我们想计算抛 10 次恰好出现 7 次正面的可能性有多大。这是计算 $P(data | p=0.5)$。
- 似然:我们不知道硬币是否均匀,但我们抛了 10 次,确实看到了 7 次正面(数据固定)。我们想知道,如果硬币正面朝上的概率是 $p$,那么这个 $p$ 最可能是多少?这是计算 $L(p | data)$。
简单来说,概率描述的是未来的不确定性,而似然解释的是过去的线索。
数学构建:从联合分布到对数似然
现在,让我们正式构建似然函数。假设我们有一组观测数据 $x1, \dots, xn$,它们独立同分布,其概率密度函数(PDF)或概率质量函数(PMF)为 $f(x \mid \theta)$,其中 $\theta$ 是我们要估计的参数向量。
似然函数的定义
似然函数 $L(\theta)$ 定义为这些观测点的联合概率密度(或概率质量)的乘积。由于样本是独立同分布的,我们将各个点的概率乘起来:
$$L(\theta; x1, \dots, xn) = \prod{i=1}^n f(xi \mid \theta)$$
为什么我们需要对数似然?
在实际计算中,直接处理连乘积通常会带来两个麻烦:
- 下溢出:当样本量 $n$ 很大时,许多小于 1 的概率相乘会导致数值极小,计算机可能无法区分它与 0。
- 求导困难:乘积的导数计算比和的导数要复杂得多。
为了解决这个问题,我们通常对似然函数取自然对数,得到对数似然函数:
$$\ell(\theta) = \log L(\theta) = \sum{i=1}^n \log f(xi \mid \theta)$$
由于对数函数是单调递增的,最大化 $L(\theta)$ 和最大化 $\ell(\theta)$ 得到的参数 $\theta$ 是完全一致的。这成为了我们后续进行优化计算的基础。
Python 实战 1:伯努利分布的似然可视化
让我们通过一个最简单的例子——抛硬币,来直观感受似然函数的形状。我们将编写 Python 代码,画出似然曲线,看看它是如何帮助我们找到最可能的参数 $p$ 的。
场景设定
假设我们有一枚硬币,抛了 10 次,结果如下:[1, 1, 1, 1, 1, 1, 1, 0, 0, 0](1 代表正面,0 代表反面)。也就是说,我们观察到了 7 次正面和 3 次反面。
代码实现
import numpy as np
import matplotlib.pyplot as plt
# 1. 准备数据:7次正面,3次反面
# 为了通用性,我们可以定义一个函数来计算似然
def bernoulli_likelihood(p_values, num_heads, num_tails):
"""
计算伯努利试验的似然值。
公式:L(p) = p^heads * (1-p)^tails
"""
# 注意:这里需要防止 p 为 0 或 1 时导致的 log(0) 问题(虽然本例未使用 log,但好习惯很重要)
return (p_values ** num_heads) * ((1 - p_values) ** num_tails)
# 2. 设定参数空间:p 从 0 到 1
p_grid = np.linspace(0, 1, 1000)
likelihood_values = bernoulli_likelihood(p_grid, num_heads=7, num_tails=3)
# 3. 寻找 MLE (Maximum Likelihood Estimate)
# 似然函数值最大时的 p
mle_estimate = p_grid[np.argmax(likelihood_values)]
# 4. 绘图
plt.figure(figsize=(10, 6))
plt.plot(p_grid, likelihood_values, label=‘Likelihood Function $L(p)$‘, color=‘royalblue‘, linewidth=2)
plt.axvline(mle_estimate, color=‘red‘, linestyle=‘--‘, label=f‘MLE Estimate $\hat{{p}} = {mle_estimate:.2f}$‘)
plt.scatter([mle_estimate], [np.max(likelihood_values)], color=‘red‘, zorder=5)
plt.title(‘Likelihood Function for Coin Toss (7 Heads, 3 Tails)‘, fontsize=14)
plt.xlabel(‘Probability of Heads ($p$)‘, fontsize=12)
plt.ylabel(‘Likelihood‘, fontsize=12)
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.ylim(0, np.max(likelihood_values) * 1.1) # 稍微留点顶部空间
plt.show()
print(f"最大似然估计结果: p = {mle_estimate}")
代码分析与洞察
运行这段代码,你会发现曲线的最高点精确地落在 $p = 0.7$ 的位置。这验证了我们的直觉:观察到 7 次正面,最可能的概率就是 7/10。
我们可以从中得到什么启发?
- 峰值位置:似然函数的峰值点就是我们要求的 MLE。
- 曲线宽度:想象一下,如果我们抛了 1000 次,得到 700 次正面。这条曲线会变得非常尖锐。这意味着我们的估计非常确定(低方差)。反之,样本量越小,曲线越平坦,我们对参数的估计就越不确定。这就是信息量的直观体现。
进阶应用:正态分布与最大似然估计 (MLE)
伯努利分布只有一维参数。让我们升级难度,看看连续分布——正态分布。我们将演示如何通过数学推导和 Python 数值计算来找到均值 $\mu$ 的估计值。
数学推导
假设 $X1, \dots, Xn \sim \mathcal{N}(\mu, \sigma^2)$。这里 $\sigma$ 已知,我们要估计 $\mu$。
对数似然函数为:
$$\ell(\mu) = -\frac{n}{2} \log(2\pi\sigma^2) – \frac{1}{2\sigma^2} \sum{i=1}^n (xi – \mu)^2$$
要最大化 $\ell(\mu)$,相当于最小化 $\sum (xi – \mu)^2$。求导并令其为 0,我们很容易解得 $\hat{\mu} = \frac{1}{n} \sum xi$(即样本均值)。
Python 实战 2:验证 MLE 的解析解
虽然我们知道答案,但在实际工程中,我们经常面对无法求出解析解的复杂模型。那时,我们就需要使用数值优化方法。让我们用 Python 来模拟这个过程。
from scipy.optimize import minimize
from scipy.stats import norm
import numpy as np
# 1. 生成模拟数据
np.random.seed(42)
true_mu = 50
true_sigma = 10
data = np.random.normal(true_mu, true_sigma, 100) # 100个样本
# 2. 定义负对数似然函数
# Scipy的minimize是求最小值,所以我们要取负号
def negative_log_likelihood(mu, data, sigma):
# 计算给定mu下,数据的联合概率密度(对数和)
# 这里我们假设sigma已知,为true_sigma
nll = -np.sum(norm.logpdf(data, loc=mu, scale=true_sigma))
return nll
# 3. 使用数值优化求解
initial_guess = [0] # 初始猜测,随便给一个
result = minimize(fun=negative_log_likelihood,
x0=initial_guess,
args=(data, true_sigma))
estimated_mu = result.x[0]
actual_mean = np.mean(data)
print(f"优化算法找到的 MLE: {estimated_mu:.4f}")
print(f"解析解 (样本均值): {actual_mean:.4f}")
深入讲解代码工作原理
-
scipy.stats.norm.logpdf:这个函数非常关键,它直接计算了正态分布的对数概率密度,避免了我们需要手动编写可能导致数值下溢的公式。 -
scipy.optimize.minimize:这是一个强大的通用优化器。在这个简单的例子中,它可能显得大材小用,但在神经网络或复杂统计模型中,这就是我们寻找参数的标准方法。
可视化进阶:二维似然表面
在现实世界中,我们往往需要同时估计多个参数。例如,同时估计正态分布的均值 $\mu$ 和标准差 $\sigma$。这时候,似然函数变成了一个三维曲面。
Python 实战 3:绘制似然热力图
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
# 数据准备
np.random.seed(42)
data = np.random.normal(loc=10, scale=2, size=50) # 真实 mu=10, sigma=2
# 定义网格
mu_range = np.linspace(8, 12, 100)
sigma_range = np.linspace(0.5, 3.5, 100)
MU, SIGMA = np.meshgrid(mu_range, sigma_range)
# 向量化计算对数似然
# 这是一个广播操作,非常高效
# logpdf 的 shape 将与 MU, SIGMA 相同
Z = np.sum(norm.logpdf(data[:, None, None], loc=MU, scale=SIGMA), axis=0)
# 绘图
plt.figure(figsize=(10, 8))
# 使用 contourf 绘制填充等高线
cp = plt.contourf(MU, SIGMA, Z, levels=20, cmap=‘viridis‘)
plt.colorbar(cp, label=‘Log-Likelihood Value‘)
# 标记真实值和估计值
mle_mu = np.mean(data)
mle_sigma = np.std(data, ddof=0) # MLE 使用的 sigma 公式分母是 n
plt.scatter([mle_mu], [mle_sigma], color=‘red‘, s=100, edgecolors=‘white‘, label=‘MLE Point‘, zorder=5)
plt.scatter([10], [2], color=‘white‘, marker=‘x‘, s=100, label=‘True Parameter‘, zorder=5)
plt.title(‘2D Log-Likelihood Surface for Normal Distribution‘, fontsize=14)
plt.xlabel(‘Mean ($\mu$)‘, fontsize=12)
plt.ylabel(‘Standard Deviation ($\sigma$)‘, fontsize=12)
plt.legend()
plt.show()
输出分析
你会发现,热力图中颜色最深(或等高线中心)的位置,恰好对应数据的均值和标准差。这个可视化展示了似然函数的几何形态:它通常是一个凸函数(对于正态分布),这意味着我们可以很容易地找到全局最大值。
实用见解:如果你在调参时发现这个曲面非常平坦,或者像山脉一样有多个高峰(多峰),那么你的模型可能就存在“不可识别性”或者陷入了局部最优。这是深度学习中非常常见的问题。
似然函数的五大关键性质
为了在工作中更专业地应用似然函数,我们需要记住以下 5 个核心性质:
- 不是概率密度:似然函数 $L(\theta)$ 关于 $\theta$ 的积分不等于 1。它是关于参数的函数,衡量的是拟合优度,而不是概率。
- 不变性:这是一个非常强大的性质。如果 $\hat{\theta}$ 是 $\theta$ 的 MLE,那么对于任何函数 $g$,$g(\hat{\theta})$ 也是 $g(\theta)$ 的 MLE。
例子*:如果你算出了正态分布方差 $\sigma^2$ 的 MLE,那么它的平方根 $\sigma$(标准差)的 MLE 直接就是前者的平方根,不需要重新求导。
- 相对尺度:似然函数的绝对值大小本身没有意义(因为它是密度函数的乘积,数值往往极小)。我们通常关心的是似然比 (Likelihood Ratio),即两个参数值下似然的比值。这告诉我们哪个参数更“可能”。
- 与 AIC/BIC 的联系:在模型选择时,我们经常听到 AIC 和 BIC。它们本质上都是基于对数似然函数构建的惩罚项指标。
* $AIC = 2k – 2\ell(\hat{\theta})$
* $BIC = k\ln(n) – 2\ell(\hat{\theta})$
* 似然越大(越好),AIC/BIC 越小(越好)。理解了这一点,你就理解了模型选择的本质。
- 连接贝叶斯推断:在贝叶斯公式中,似然函数是连接“先验”和“后验”的桥梁。
$$P(\theta \mid x) = \frac{L(\theta \mid x) \cdot \pi(\theta)}{\text{Evidence}}$$
如果没有似然函数,我们就无法根据新数据更新我们的信念。
常见陷阱与解决方案
在使用似然函数进行实际开发时,你可能会遇到以下几个坑:
1. 数值下溢
问题:当处理大量数据时,直接连乘概率会导致计算结果变为 0(Underflow)。
解决:永远使用对数似然。这在代码实现中是标准操作,务必养成习惯。
2. 局部最优
问题:对于非凸模型(如神经网络),似然函数可能有多个峰值。普通的优化算法可能卡在局部最高点,而不是全局最高点。
解决:使用随机梯度下降(SGD)、Adam 等优化器,或者尝试多次不同的初始化参数。
3. 过拟合
问题:MLE 总是倾向于选择最能完美拟合训练数据的参数,哪怕这意味着模型非常复杂。这会导致泛化能力差。
解决:引入正则化,或者在贝叶斯框架下引入先验分布来约束参数空间。
结语:下一步该如何做?
在这篇文章中,我们从硬币抛掷讲到正态分布,从数学推导讲到 Python 代码实战。你应该已经对似然函数有了坚实的理解。
给你的行动建议:
- 动手实践:尝试修改上面的代码,改变样本量 $n$,或者修改数据的噪声水平,观察似然曲线形状的变化。这种直觉的建立对于调参至关重要。
- 探索复杂模型:如果你使用 INLINECODE3938f186 或 INLINECODE27a4c87e,试着去查看文档中的损失函数定义。你会惊讶地发现,大多数分类问题的“交叉熵损失”,本质上就是负对数似然。
似然函数不仅是统计学的一个概念,它是我们数据科学工具箱中最锋利的武器之一。下次当你需要“拟合数据”时,试着从似然的角度去思考:“我在寻找什么样的参数,能让这个世界生成我眼前这些数据的可能性最大?”