ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

最大似然估计实战:MATLAB、Python、R三语言实现与原理详解

最大似然估计实战:MATLAB、Python、R三语言实现与原理详解 1. 从“猜硬币”到参数估计为什么最大似然估计是建模的基石做数据分析或者建模的朋友估计都听过“最大似然估计”这个词英文叫Maximum Likelihood Estimation简称MLE。听起来挺学术但它的核心思想其实特别朴素甚至有点“赌徒心理”。想象一下你手里有一枚硬币你不知道它是不是公平的即正面朝上的概率p是不是0.5。你抛了10次结果有7次正面3次反面。这时候你猜这枚硬币的p是多少直觉上你肯定会猜p0.7因为观测到的数据7正3反在这个概率下“看起来最可能发生”。这个“让观测数据看起来最可能发生”的思路就是最大似然估计的灵魂。在正式的统计建模中我们面对的模型比如一个正态分布、一个逻辑回归模型通常包含一些未知的参数比如正态分布的均值μ和标准差σ。我们手头有一堆观测数据。最大似然估计要做的就是找到一组参数值使得在这组参数下我们观测到的这堆数据出现的“可能性”Likelihood达到最大。它不是去计算某个事件发生的概率而是反过来在给定数据的情况下去评估不同参数取值的“合理程度”。所以MLE是参数估计领域最核心、最经典的方法之一从简单的线性回归到复杂的深度学习模型底层都能看到它的身影。今天我们就抛开复杂的公式推导从实际应用和代码实现的角度把手弄脏彻底搞懂怎么用MATLAB、Python和R这三门数据分析的利器来玩转最大似然估计。2. 最大似然估计的核心逻辑拆解似然函数与优化问题要理解MLE必须抓住两个关键概念似然函数和对数似然函数。很多人在这里被公式吓退其实我们一步步拆开看。2.1 似然函数连接数据与参数的桥梁首先我们有一个概率模型。比如我们假设数据服从正态分布那么这个模型就是N(μ, σ²)其中μ和σ是未知参数。假设我们观测到n个独立的数据点x1, x2, ..., xn。对于单个数据点xi在参数θ这里θ代表μ和σ下的概率密度是f(xi | θ)。因为数据点是独立的所以所有数据点同时被观测到的“可能性”就是每个数据点概率密度的乘积。这个乘积就是似然函数 L(θ)L(θ) f(x1 | θ) * f(x2 | θ) * ... * f(xn | θ) ∏ f(xi | θ)注意似然函数是关于参数θ的函数数据x是已知的、固定的。我们的目标是调整θ让这个乘积L(θ)尽可能大。为什么是乘积因为独立事件同时发生的概率是各自概率的乘积这里我们用概率密度来类比“可能性”。2.2 为什么要取对数从连乘到连加直接对似然函数L(θ)求最大化在数学和计算上都很麻烦。因为它是很多项可能小于1的连乘结果可能会是一个非常接近于0的极小数字在计算机里容易造成下溢Underflow。更麻烦的是乘积形式求导数很复杂。数学上有一个非常巧妙的转换对似然函数取自然对数。因为对数函数是单调递增的所以让L(θ)最大化的θ同样也是让ln L(θ)最大化的θ。取对数后连乘变成了连加ln L(θ) ∑ ln f(xi | θ)这个函数被称为对数似然函数。求和形式在数学上求导、在计算机上计算都方便得多。这也是为什么你在所有关于MLE的文献和代码里看到的都是在优化对数似然函数。2.3 优化求解找到那个“最可能”的点现在问题转化了我们有了一个关于参数θ的函数ln L(θ)我们需要找到一组θ值使得这个函数值最大。这本质上是一个无约束优化问题。对于某些简单的模型比如我们前面说的正态分布我们可以通过令对数似然函数的一阶导数梯度等于0解出参数的解析解。例如正态分布的MLE估计结果就是样本均值和样本方差注意分母是n不是n-1。但是绝大多数现实中的模型比如混合模型、复杂的回归模型其对数似然函数关于参数的导数方程似然方程没有简单的解析解。这时候我们就必须依靠数值优化算法来寻找最大值点。这就是MATLAB、Python、R等工具大显身手的地方。它们内置了强大的优化器如fminsearch,fminunc,optim,nlm等我们只需要定义好对数似然函数然后把它喂给优化器告诉它“帮我找到让这个函数值最大的参数”。注意这里有一个非常重要的思维转换。在频率学派的框架下参数θ被认为是固定的、未知的常数而不是随机变量。MLE寻找的是那个“最可能产生当前数据”的常数值。这与贝叶斯估计将参数视为随机变量有其先验分布有哲学上的根本区别。理解这一点有助于你分清不同方法的应用场景。3. 实战案例一估计正态分布的参数MATLAB实现我们用一个最简单的例子来热身用MLE估计一组数据的均值和标准差。假设我们有一组数据我们知道它来自一个正态分布但不知道具体的μ和σ。3.1 数据准备与可视化首先我们生成一些模拟数据。这样我们就知道了真实的参数可以验证MLE估计的效果。% 设置随机种子确保结果可复现 rng(123); % 设定真实的分布参数 true_mu 5; true_sigma 2; % 生成100个服从正态分布 N(5, 2^2) 的样本数据 n_samples 100; data normrnd(true_mu, true_sigma, n_samples, 1); % 绘制数据直方图直观感受分布 figure; histogram(data, 20, Normalization, pdf, EdgeColor, none, FaceColor, [0.2, 0.6, 0.8], FaceAlpha, 0.7); hold on; % 绘制真实分布的概率密度曲线 x_range linspace(min(data)-3, max(data)3, 1000); true_pdf normpdf(x_range, true_mu, true_sigma); plot(x_range, true_pdf, r-, LineWidth, 2); xlabel(数据值); ylabel(概率密度); title(样本数据直方图与真实分布对比); legend(样本数据直方图, 真实分布N(5, 2^2)); grid on; hold off;运行这段代码你会看到一个直方图其形状大致围绕5均值对称宽度由标准差2决定。红色的曲线是真实的分布。我们的目标是从蓝色的柱子样本数据中反推出红线的位置参数μ和σ。3.2 构建负对数似然函数MATLAB的优化工具箱如fminsearch,fminunc默认是求解最小值问题。而我们需要的是最大化对数似然函数。一个标准的技巧是定义负对数似然函数作为目标函数然后求它的最小值。这样在数学上是等价的。对于正态分布N(μ, σ)其概率密度函数为f(x | μ, σ) (1/(σ√(2π))) * exp(-(x-μ)²/(2σ²))那么对数似然函数为ln L(μ, σ) ∑ [ -ln(σ) - 0.5*ln(2π) - 0.5*((xi-μ)/σ)² ]去掉常数项-0.5*ln(2π)不影响优化我们得到负对数似然函数NLL(μ, σ) ∑ [ ln(σ) 0.5*((xi-μ)/σ)² ]在MATLAB中我们将其写成一个函数function nll normal_nll(params, data) % params: 参数向量params(1)mu, params(2)sigma % data: 观测数据向量 % nll: 负对数似然值 mu params(1); sigma params(2); % 确保标准差为正数 if sigma 0 nll inf; % 如果sigma非正返回无穷大惩罚无效参数 return; end n length(data); % 计算负对数似然 nll n * log(sigma) 0.5 * sum(((data - mu) ./ sigma).^2); end这里有一个关键细节我们对参数sigma加了约束sigma 0。在优化中如果优化器试探了一个负的sigma值我们的函数会返回Inf无穷大这相当于告诉优化器“此路不通”引导它去寻找正的sigma值。这是一种处理简单边界约束的常用技巧。3.3 调用优化器进行估计有了目标函数和数据我们就可以开始优化了。fminsearch函数使用Nelder-Mead单纯形法是一种稳健的、不需要计算导数的直接搜索方法非常适合这个简单问题。% 使用样本矩估计量作为优化的初始值这是一个很好的起点 initial_mu mean(data); initial_sigma std(data); % 注意这是样本标准差分母n-1MLE估计会是分母n initial_params [initial_mu, initial_sigma]; % 设置优化选项显示迭代过程提高精度 options optimset(Display, iter, TolX, 1e-8, TolFun, 1e-8); % 调用fminsearch最小化负对数似然函数 [estimated_params, nll_value, exitflag] fminsearch((p) normal_nll(p, data), initial_params, options); % 提取估计结果 mu_hat estimated_params(1); sigma_hat estimated_params(2); fprintf(真实参数: mu %.4f, sigma %.4f\n, true_mu, true_sigma); fprintf(MLE估计: mu_hat %.4f, sigma_hat %.4f\n, mu_hat, sigma_hat); fprintf(样本均值 %.4f 样本标准差分母n-1 %.4f\n, mean(data), std(data)); fprintf(样本标准差分母n即MLE公式 %.4f\n, sqrt(mean((data - mean(data)).^2)));运行这段代码你会看到优化器的迭代过程并最终输出结果。你会发现mu_hat会非常接近mean(data)。对于正态分布均值的MLE估计就是样本均值。sigma_hat会非常接近sqrt(mean((data - mean(data)).^2))即用n做分母的样本标准差而不是常用的std函数默认分母n-1计算的无偏估计。这是MLE的一个重要性质它求的是最可能产生数据的参数这个估计量不一定无偏。对于方差MLE估计量是有偏的偏小尤其是在小样本时。在实际应用中如果需要无偏估计有时会采用贝塞尔校正即除以n-1。3.4 结果可视化与对比让我们把估计出的分布和真实分布画在一起看看拟合效果。% 绘制对比图 figure; histogram(data, 20, Normalization, pdf, EdgeColor, none, FaceColor, [0.2, 0.6, 0.8], FaceAlpha, 0.5); hold on; plot(x_range, true_pdf, r-, LineWidth, 2, DisplayName, 真实分布); % 绘制MLE估计的分布 estimated_pdf normpdf(x_range, mu_hat, sigma_hat); plot(x_range, estimated_pdf, b--, LineWidth, 2, DisplayName, MLE估计分布); xlabel(数据值); ylabel(概率密度); title(最大似然估计MLE拟合效果对比); legend(show); grid on; hold off;蓝色的虚线就是我们通过MLE估计出的正态分布曲线。它应该与数据的直方图轮廓以及红色真实曲线都非常吻合。这个简单的例子完整演示了MLE的整个工作流定义模型、写出负对数似然函数、利用优化器求解。实操心得使用fminsearch时初始值的选择很重要。虽然对于像正态分布这样的凸问题最终结果对初始值不敏感但对于更复杂的模型如混合模型糟糕的初始值可能导致优化器陷入局部最优得到错误的结果。用样本统计量如均值、方差作为初始值通常是一个安全且高效的选择。4. 实战案例二逻辑回归的MLE视角Python实现逻辑回归是分类问题中的经典模型。很多人是从“用sigmoid函数做概率映射”和“最小化交叉熵损失”的角度理解它。实际上逻辑回归的参数正是通过最大似然估计得到的。理解这一点能让你对逻辑回归的认识更深一层。4.1 逻辑回归的似然函数推导假设我们有二分类数据标签y_i ∈ {0, 1}。逻辑回归模型假设给定特征x_iy_i1的概率服从伯努利分布P(y_i1 | x_i) p_i 1 / (1 exp(-(w^T x_i b)))其中w是权重向量b是偏置项合起来就是我们要求的参数θ。对于单个样本其概率似然可以写为P(y_i | x_i; θ) (p_i)^{y_i} * (1-p_i)^{1-y_i}这个式子很巧妙当y_i1时取p_i当y_i0时取1-p_i。对于n个独立同分布的样本似然函数是它们的乘积L(θ) ∏ [ (p_i)^{y_i} * (1-p_i)^{1-y_i} ]取对数得到对数似然函数ln L(θ) ∑ [ y_i * ln(p_i) (1-y_i) * ln(1-p_i) ]最大化这个对数似然函数等价于最小化负对数似然而负对数似然正是我们熟知的二元交叉熵损失。所以用梯度下降法优化交叉熵损失本质上就是在做逻辑回归的MLE。4.2 使用Python的Scipy进行MLE估计我们将使用scipy.optimize.minimize这个强大的优化器手动实现逻辑回归的MLE拟合并与sklearn的结果进行对比。import numpy as np import pandas as pd from scipy.optimize import minimize from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt # 1. 生成模拟数据 np.random.seed(42) n_samples 300 # 生成两个二维正态分布的点作为两个类别 X_class1 np.random.multivariate_normal(mean[2, 2], cov[[1, 0.5], [0.5, 1]], sizen_samples//2) X_class0 np.random.multivariate_normal(mean[-2, -2], cov[[1, -0.3], [-0.3, 1]], sizen_samples//2) X np.vstack([X_class0, X_class1]) y np.hstack([np.zeros(n_samples//2), np.ones(n_samples//2)]) # 为特征矩阵添加一列1用于偏置项b即 intercept X_with_intercept np.hstack([np.ones((X.shape[0], 1)), X]) # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split(X_with_intercept, y, test_size0.3, random_state42) # 2. 定义负对数似然函数即交叉熵损失 def neg_log_likelihood(theta, X, y): theta: 参数向量包含偏置项和权重 [b, w1, w2, ...] X: 特征矩阵第一列全为1 y: 标签向量 # 线性部分 linear X.dot(theta) # 计算sigmoid概率防止数值溢出 # 对线性部分进行裁剪保证exp计算稳定 linear_clipped np.clip(linear, -500, 500) p 1 / (1 np.exp(-linear_clipped)) # 计算负对数似然交叉熵 # 避免log(0)的情况给一个极小值epsilon epsilon 1e-15 p np.clip(p, epsilon, 1 - epsilon) nll -np.sum(y * np.log(p) (1 - y) * np.log(1 - p)) return nll # 3. 定义梯度函数提供给优化器加速收敛 def gradient(theta, X, y): linear X.dot(theta) p 1 / (1 np.exp(-np.clip(linear, -500, 500))) grad X.T.dot(p - y) # 这是交叉熵损失对theta的梯度 return grad # 4. 使用Scipy进行优化MLE估计 initial_theta np.zeros(X_train.shape[1]) # 初始参数设为0 # 使用L-BFGS-B算法它能利用梯度信息收敛更快 result minimize(funneg_log_likelihood, x0initial_theta, args(X_train, y_train), methodL-BFGS-B, jacgradient, options{disp: True, maxiter: 1000}) theta_mle result.x print(\n--- 手动MLE估计结果 ---) print(f估计的参数 (b, w1, w2): {theta_mle}) print(f优化是否成功: {result.success}) print(f最终负对数似然值: {result.fun}) # 5. 使用sklearn的LogisticRegression作为基准 # 注意sklearn默认使用L2正则化C1为了公平比较我们设置一个很大的C值来近似无正则化 logreg_sklearn LogisticRegression(fit_interceptFalse, C1e10, solverlbfgs, max_iter1000) logreg_sklearn.fit(X_train, y_train) theta_sklearn np.hstack([logreg_sklearn.intercept_, logreg_sklearn.coef_.flatten()]) if logreg_sklearn.fit_intercept else logreg_sklearn.coef_.flatten() # 因为我们已经在X中添加了截距列且设置了fit_interceptFalse所以coef_就包含了所有参数 theta_sklearn logreg_sklearn.coef_.flatten() print(\n--- sklearn逻辑回归结果 ---) print(f估计的参数 (b, w1, w2): {theta_sklearn})运行这段代码你会发现手动MLE估计出的参数theta_mle和sklearn逻辑回归在关闭正则化或使用极大C值的情况下估计出的参数theta_sklearn几乎完全一致。这有力地证明了逻辑回归的标准训练过程就是在执行最大似然估计。4.3 模型评估与决策边界可视化参数估计出来了我们还需要看看模型在测试集上的表现并画出它的决策边界。# 6. 评估模型性能 def predict_proba(X, theta): linear X.dot(theta) p 1 / (1 np.exp(-np.clip(linear, -500, 500))) return p def predict(X, theta, threshold0.5): proba predict_proba(X, theta) return (proba threshold).astype(int) # 在测试集上预测 y_pred_proba_mle predict_proba(X_test, theta_mle) y_pred_mle predict(X_test, theta_mle) y_pred_sklearn logreg_sklearn.predict(X_test) from sklearn.metrics import accuracy_score, roc_auc_score print(\n--- 测试集性能对比 ---) print(fMLE模型准确率: {accuracy_score(y_test, y_pred_mle):.4f}) print(fSklearn模型准确率: {accuracy_score(y_test, y_pred_sklearn):.4f}) print(fMLE模型AUC: {roc_auc_score(y_test, y_pred_proba_mle):.4f}) # 7. 绘制决策边界 def plot_decision_boundary(X, y, theta, ax, title): # 创建网格点 x1_min, x1_max X[:, 1].min() - 1, X[:, 1].max() 1 x2_min, x2_max X[:, 2].min() - 1, X[:, 2].max() 1 xx1, xx2 np.meshgrid(np.arange(x1_min, x1_max, 0.1), np.arange(x2_min, x2_max, 0.1)) # 计算网格上每个点的预测概率 grid_points np.c_[np.ones(xx1.ravel().shape[0]), xx1.ravel(), xx2.ravel()] Z predict_proba(grid_points, theta) Z Z.reshape(xx1.shape) # 绘制等高线决策边界为p0.5的等高线 contour ax.contour(xx1, xx2, Z, levels[0.5], colorsred, linewidths2) # 绘制概率填充色 contourf ax.contourf(xx1, xx2, Z, alpha0.3, cmapRdBu_r) # 绘制原始数据点 scatter ax.scatter(X[:, 1], X[:, 2], cy, edgecolorsk, cmapbwr) ax.set_xlabel(Feature 1) ax.set_ylabel(Feature 2) ax.set_title(title) plt.colorbar(contourf, axax) fig, axes plt.subplots(1, 2, figsize(14, 6)) plot_decision_boundary(X_train, y_train, theta_mle, axes[0], Decision Boundary (Manual MLE) - Train Set) plot_decision_boundary(X_train, y_train, theta_sklearn, axes[1], Decision Boundary (Sklearn) - Train Set) plt.tight_layout() plt.show()通过决策边界图你可以直观地看到两个模型手动MLE和sklearn的边界是完全重合的。这再次验证了我们的MLE实现是正确的。踩坑实录在实现sigmoid函数1/(1exp(-z))时如果z是一个很大的负数比如-1000exp(-z)会变成一个极大的数导致计算溢出inf。如果z是一个很大的正数exp(-z)会下溢为0可能导致除零错误。因此对线性部分的输出进行数值裁剪np.clip是稳定计算的关键。这也是很多开源库如sklearn, tensorflow内部的常见做法。5. 实战案例三拟合自定义分布与模型选择R语言实现现实中的数据往往不服从标准的正态分布或伯努利分布。MLE的强大之处在于只要你能定义出概率模型写出概率密度/质量函数你就能估计它的参数。我们用一个自定义的指数衰减分布来演示这个过程并引入模型选择的概念。5.1 定义问题与生成数据假设我们观测到一些事件发生的时间间隔比如网站用户的访问间隔、设备的故障间隔。我们怀疑这些时间间隔服从一个指数衰减分布其概率密度函数为f(t | λ) λ * exp(-λ * t), for t 0其中λ 0 是衰减率参数它的倒数1/λ就是平均时间间隔。我们生成一些模拟数据# 设置随机种子 set.seed(2024) # 定义真实的衰减率参数 true_lambda - 0.8 # 平均间隔为 1/0.8 1.25 个单位时间 # 生成100个服从指数分布的数据点 n - 100 data_t - rexp(n, rate true_lambda) # 查看数据摘要 summary(data_t) hist(data_t, breaks30, collightblue, main模拟指数分布数据直方图, xlab时间间隔)你会看到数据是右偏的大部分值较小少数值很大这是指数分布的典型特征。5.2 编写自定义的负对数似然函数并进行MLE估计在R中我们使用stats4包中的mle函数或者更通用的optim函数。这里我们用optim。# 定义负对数似然函数 neg_log_lik_exp - function(lambda, data) { # lambda: 待估参数 # data: 观测数据向量 # 参数约束lambda必须为正 if (lambda 0) { return(Inf) # 返回无穷大作为惩罚 } n - length(data) # 指数分布的对数似然sum(log(lambda) - lambda * data_i) # 负对数似然 -sum(log(lambda) - lambda * data_i) -n*log(lambda) lambda * sum(data) nll - -n * log(lambda) lambda * sum(data) return(nll) } # 使用optim进行优化 # 初始值用矩估计量指数分布的均值是1/lambda所以lambda的矩估计是1/mean(data) initial_lambda - 1 / mean(data_t) result - optim(par initial_lambda, fn neg_log_lik_exp, data data_t, method Brent, # 对于单参数优化Brent方法很高效 lower 1e-6, # 参数下界 upper 10) # 参数上界 lambda_hat - result$par nll_min - result$value cat(sprintf(真实lambda: %.4f\n, true_lambda)) cat(sprintf(MLE估计lambda_hat: %.4f\n, lambda_hat)) cat(sprintf(对应的平均间隔 (1/lambda_hat): %.4f\n, 1/lambda_hat)) cat(sprintf(样本均值 (矩估计的1/lambda): %.4f\n, mean(data_t)))对于指数分布MLE有一个漂亮的解析解λ_hat 1 / (样本均值)。你会发现optim估计出的lambda_hat和直接用1/mean(data_t)计算的结果几乎一模一样。这验证了我们代码的正确性。5.3 模型诊断我们选对分布了吗仅仅估计出参数还不够。我们假设数据服从指数分布这个假设合理吗我们需要进行模型诊断。一个简单有效的方法是分位数-分位数图。# Q-Q图比较样本分位数与理论分位数 # 如果数据确实来自指数分布点应该大致在一条直线上 qqplot_data - function(data, rate) { n - length(data) # 理论分位数指数分布 theoretical_quantiles - qexp(ppoints(n), rate rate) # 样本分位数排序后的数据 sample_quantiles - sort(data) plot(theoretical_quantiles, sample_quantiles, main Q-Q Plot for Exponential Distribution, xlab Theoretical Quantiles (Exp), ylab Sample Quantiles, pch 19, col darkblue) # 添加参考线 yx abline(a 0, b 1, col red, lwd 2) # 计算并显示相关系数作为拟合优度的粗略指标 cor_coef - cor(theoretical_quantiles, sample_quantiles) legend(topleft, legend sprintf(Correlation: %.4f, cor_coef), bty n) } qqplot_data(data_t, lambda_hat)观察Q-Q图如果点紧密分布在红色对角线附近说明指数分布假设是合理的。如果出现系统性的弯曲例如S形则说明数据可能来自其他分布比如韦伯分布或伽马分布。5.4 引入竞争模型韦伯分布假设Q-Q图显示尾部有偏差我们考虑一个更灵活的模型韦伯分布。它有两个参数形状参数k和尺度参数λ其概率密度函数为f(t | k, λ) (k/λ) * (t/λ)^{k-1} * exp(-(t/λ)^k)当k1时韦伯分布退化为指数分布。因此韦伯分布是包含指数分布作为特例的更一般模型。# 定义韦伯分布的负对数似然函数 neg_log_lik_weibull - function(params, data) { # params[1] k (shape), params[2] lambda (scale) k - params[1] lambda - params[2] if (k 0 || lambda 0) { return(Inf) } n - length(data) # 韦伯分布的对数似然 # sum( log(k) - k*log(lambda) (k-1)*log(t) - (t/lambda)^k ) # 负对数似然 term1 - n * (log(k) - k*log(lambda)) term2 - (k-1) * sum(log(data)) term3 - sum((data / lambda)^k) nll - -(term1 term2 - term3) return(nll) } # 初始值令k1即指数分布lambda用之前的估计 initial_params_weibull - c(1, 1/lambda_hat) result_weibull - optim(par initial_params_weibull, fn neg_log_lik_weibull, data data_t, method L-BFGS-B, lower c(1e-6, 1e-6), upper c(Inf, Inf)) params_weibull_hat - result_weibull$par nll_weibull_min - result_weibull$value cat(sprintf(\n--- 韦伯分布MLE估计 ---\n)) cat(sprintf(形状参数 k_hat: %.4f\n, params_weibull_hat[1])) cat(sprintf(尺度参数 lambda_hat: %.4f\n, params_weibull_hat[2])) cat(sprintf(韦伯分布最小负对数似然值: %.4f\n, nll_weibull_min)) cat(sprintf(指数分布最小负对数似然值: %.4f\n, nll_min))5.5 模型比较AIC准则现在我们有兩個模型简单的指数分布1个参数和复杂的韦伯分布2个参数。韦伯分布的拟合似然值肯定更高负对数似然值更低因为它有更多参数更灵活。但这是否意味着它更好我们可能过拟合了。我们需要一个权衡模型拟合优度和复杂度的准则。赤池信息量准则就是这样一个工具AIC 2k - 2ln(L)其中k是模型参数个数L是最大似然值。AIC值越小模型相对越好。它惩罚了参数数量防止过度拟合。# 计算两个模型的AIC # AIC 2k - 2*log(Likelihood_max) 2k 2*NLL_min # 因为我们的函数返回的是负对数似然NLL所以 AIC 2k 2*NLL_min aic_exp - 2*1 2*nll_min # 指数分布有1个参数 (lambda) aic_weibull - 2*2 2*nll_weibull_min # 韦伯分布有2个参数 (k, lambda) cat(sprintf(\n--- 模型比较 (AIC) ---\n)) cat(sprintf(指数分布 AIC: %.4f\n, aic_exp)) cat(sprintf(韦伯分布 AIC: %.4f\n, aic_weibull)) if (aic_exp aic_weibull) { cat(根据AIC准则指数分布是更优模型。\n) } else { cat(根据AIC准则韦伯分布是更优模型。\n) } # 绘制两个拟合分布与数据直方图的对比 hist(data_t, breaks30, probability TRUE, collightgrey, main模型拟合对比, xlab时间间隔, ylimc(0, 1)) # 绘制指数分布拟合曲线 curve(dexp(x, rate lambda_hat), addTRUE, colblue, lwd2, lty1) # 绘制韦伯分布拟合曲线 curve(dweibull(x, shape params_weibull_hat[1], scale params_weibull_hat[2]), addTRUE, colred, lwd2, lty2) legend(topright, legendc(数据直方图, 指数分布拟合, 韦伯分布拟合), colc(lightgrey, blue, red), ltyc(NA, 1, 2), lwd2, pchc(15, NA, NA), btyn)通过比较AIC你可以判断在考虑了复杂度之后哪个模型对数据的描述更“高效”。如果数据确实是指数分布的那么韦伯分布的AIC可能不会比指数分布低太多甚至可能更高因为多了一个参数却没有显著提升拟合度。这个完整的流程——从定义模型、MLE估计、模型诊断到模型比较——展示了MLE在实际数据分析中的完整应用链条。经验技巧使用optim时method的选择很重要。对于无约束或边界约束问题L-BFGS-B是一个很好的通用选择它利用了梯度信息收敛速度快。对于单变量问题Brent方法是最优选择。如果无法提供梯度函数gr参数可以使用Nelder-Mead方法但它可能较慢。始终检查result$convergence是否为0以确保优化成功收敛。6. 进阶话题与常见陷阱掌握了MLE的基本应用后在实际操作中你还会遇到一些更复杂的情况和容易踩的坑。6.1 存在缺失数据或隐变量EM算法很多时候我们无法观测到完整的数据。例如在混合高斯模型中我们知道数据来自几个不同的组但不知道每个数据点具体属于哪个组。这时每个数据点的“组别”就是一个隐变量。直接对包含隐变量的似然函数求最大化非常困难。期望最大化算法就是为解决这类问题而生的。它通过迭代执行两步来逼近MLE解E步基于当前参数估计计算隐变量的期望或后验概率。M步将E步得到的期望代入似然函数然后最大化它更新参数估计。EM算法保证每次迭代都能增加似然函数的值最终收敛到一个局部最优解。sklearn.mixture.GaussianMixture和R语言中的mclust包其底层都是用EM算法来拟合混合模型的。6.2 似然函数非凸与局部最优很多复杂模型如神经网络、深层混合模型的对数似然函数是非凸的这意味着它有多个“山峰”局部极大值。优化算法从不同的初始点出发可能会收敛到不同的解。应对策略多随机初始值这是最常用也最有效的方法。从不同的随机起点多次运行优化算法选择似然值最高的那个结果作为最终估计。使用全局优化算法如模拟退火、遗传算法等但它们通常计算成本很高。利用领域知识如果对参数的可能范围有先验了解可以设置合理的初始值避免糟糕的局部最优。6.3 参数可识别性问题有时候不同的参数组合可能产生完全相同的概率分布。例如在一个简单的线性回归模型y β0 β1*x ε中如果x是一个常数那么β0和β1就无法唯一确定存在无穷多解使似然函数最大。这称为不可识别。更隐蔽的情况发生在潜类别模型中。例如在一个两成分的混合高斯模型中如果你不约束两个成分的均值或方差那么“成分A”和“成分B”的标签是可以互换的这会导致两种等价的参数估计称为标签切换问题。虽然这不影响整体的分布拟合但会给单个成分的解释带来麻烦。解决方法对参数施加约束。例如在混合模型中可以强制要求成分按均值从小到大排序或者要求第一个成分的权重大于0.5。6.4 标准误与置信区间MLE的“不确定性”MLE给出了一个点估计但我们往往还想知道这个估计的精度如何即参数的标准误。在样本量较大、满足一定正则条件下MLE估计量具有优良的渐近性质它近似服从正态分布其协方差矩阵可以由观测信息矩阵的逆来估计。在R中使用maxLik包可以方便地得到参数估计及其标准误。在Python的statsmodels库中许多模型拟合后也会自动输出标准误和置信区间。# 示例使用R的maxLik包获取标准误 library(maxLik) # 重新定义对数似然函数注意maxLik需要的是对数似然函数不是负的 log_lik_exp - function(lambda) { if (lambda 0) return(-Inf) n - length(data_t) ll - n * log(lambda) - lambda * sum(data_t) return(ll) } # 使用maxLik进行MLE估计 mle_result - maxLik(logLik log_lik_exp, start c(lambda initial_lambda)) summary(mle_result)运行summary(mle_result)你不仅能看到参数估计值还能看到它的标准误、t值和p值从而可以构建参数的近似置信区间。6.5 计算效率与数值稳定性当数据量很大或模型很复杂时计算对数似然函数及其梯度可能成为瓶颈。向量化操作在MATLAB、Python (NumPy)、R中尽量使用向量和矩阵运算避免循环可以极大提升速度。对数域计算如前所述始终在对数域进行计算避免概率连乘导致的下溢。自动微分对于极其复杂的模型手动推导梯度非常困难且容易出错。可以考虑使用支持自动微分的框架如Python的JAX、PyTorch或TensorFlow它们可以自动计算梯度让你只需定义对数似然函数本身。最大似然估计是一个强大而优美的框架它将许多统计建模问题统一为优化问题。从最简单的分布拟合到最复杂的深度学习模型其核心思想一脉相承。理解并熟练运用MLE能让你在数据分析中拥有更深刻的洞察力和更灵活的建模能力。希望这三个不同工具MATLAB, Python, R的实战案例能帮你打通从理论到实践的任督二脉。在实际项目中不妨多问自己一句“这个模型的参数如果用MLE来估计我该怎么做” 这往往是深入理解模型本质的最佳路径。
返回列表