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

资讯详情

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

极大似然估计:从概率原理到损失函数与梯度下降实战

极大似然估计:从概率原理到损失函数与梯度下降实战 做AI这一行迟早要撞上“极大似然估计”这堵墙。不管是看线性回归、逻辑回归还是看深度学习的损失函数、概率图模型绕来绕去最后都会回到这个统计学概念上。很多人一开始被“似然”“估计”这些词唬住觉得是高不可攀的数学理论其实它背后的逻辑极其朴素假设有一堆数据我们想知道是哪个参数最有可能生成出这堆数据那就把所有候选参数挨个摆出来看谁让“这堆数据出现的概率”最大谁就是答案。这篇文章我会从最直观的例子讲起把极大似然估计的数学原理拆开揉碎然后带着你过一遍它在AI里的落地场景最后给出一份可以直接跑通的Python实现。无论你是刚入门AI的学生还是工作几年想补数学底子的工程师这篇文章的目标就一个让你看完之后能用自己的话把极大似然估计讲明白并且知道在写代码的时候它到底站在哪里。1. 先把“极大似然估计”翻译成人话1.1 从一个猜硬币的例子说起想象一个场景你手里有一枚硬币但你不确定它是不是均匀的——也就是说抛一次硬币正面朝上的概率p可能是0.5可能是0.3也可能是0.8。现在你抛了10次结果是7次正面、3次反面。这个时候让你猜这枚硬币的p是多少你大概率会说“0.7左右”——为什么因为如果p真的是0.7那么10次里出现7次正面这件事发生的概率最大。这个直觉就是极大似然估计的全部内核。用术语说就是在已知观测数据的情况下我们反过来去推断“哪个参数最有可能产生这组数据”。说话的顺序很关键——先有数据后有参数推断方向跟“先知道概率然后预测结果”正好相反。这个猜硬币的例子虽然简单但它包含了极大似然估计的所有要素一个未知参数p一组观测数据10次抛硬币的结果以及一个用来衡量“数据在某个参数下出现的可能性”的函数。极大似然估计做的事情就是把这个函数最大化把对应的参数找出来。1.2 概率和似然一对容易搞混的双胞胎学极大似然估计的时候第一个拦路虎就是“似然”这个词。它跟“概率”太像了但本质上站的角度不同。概率是“已知参数预测结果”。比如已知硬币正面概率是0.5问你抛10次出7次正面的概率是多少——这是概率问题答案是一个具体的数值比如C(10,7) * 0.5^7 * 0.5^3 ≈ 0.117。似然是“已知结果推断参数”。现在结果已经发生了——10次里出了7次正面——问你p等于多少时这个结果出现的可能性最大。这里p是变量结果固定我们关心的是“给定结果不同参数下的概率值”。一个帮助记忆的方式是概率是P(数据|参数)似然是L(参数|数据)它们看起来只是把条件和结果调换了位置但含义完全不同。实际计算中两者的函数形式往往相同——都是p^k * (1-p)^(n-k)这种形式——但变量不同概率把p当常数、数据当变量似然把数据当常数、p当变量。这个区分不是咬文嚼字它直接决定了你后面怎么理解损失函数、怎么理解模型训练。逻辑回归里那个交叉熵损失函数本质上就是负的似然函数对数——这一点后面我会详细展开。2. 数学推导从似然函数到参数估计2.1 搭建似然函数把猜硬币的例子数学化。假设我们做了n次独立重复实验观测到k次正面。每次抛硬币的结果服从伯努利分布——正面概率为p反面概率为1-p。如果我们把每次实验的结果记作x_i正面取1反面取0那么在一次实验中观测到某个结果的概率是p^(x_i) * (1-p)^(1-x_i)因为每次实验是独立的整个观测数据出现的概率就是每次实验概率的乘积——这就是似然函数L(p) Π p^(x_i) * (1-p)^(1-x_i)如果n10、k7展开就是L(p) p^7 * (1-p)^3。现在的问题变成p取什么值时这个函数最大这里有个容易掉进去的坑连乘运算在计算机里很不友好数字太小会下溢求导也很麻烦。所以标准做法是对似然函数取对数把连乘变成连加得到对数似然函数ℓ(p) k * log(p) (n-k) * log(1-p)因为对数函数是单调递增的所以最大化L(p)和最大化ℓ(p)得到的最优参数完全相同。取对数不会改变极值点的位置但计算上却方便了一大截。2.2 取对数不只是一个计算技巧很多人以为取对数纯粹是为了计算方便但它的意义比这更深。首先从数值角度说连乘的项数一多结果会极度趋近于0。比如你有一万个样本每个样本的概率密度在0.5左右连乘之后的结果小到浮点数根本存不下来。取对数之后累加的数字通常在几十到几百的量级稳定得多。其次从数学角度说指数族分布——包括正态分布、伯努利分布、泊松分布等——取对数之后形式非常规整通常能化简成“某个统计量的线性函数”的形式。这给后续求导、求解析解提供了极大的便利。第三从优化角度说很多AI框架里定义的损失函数都是“负对数似然”——比如分类问题的交叉熵。神经网络训练的过程本质上就是最小化这个负对数似然也就是最大化对数似然。所以取对数这个操作不只是数学家的洁癖它直接关系到了现代深度学习框架的设计。2.3 求极值解析解与数值解有了对数似然函数下一步就是找最大值。经典的数学方法是对p求导令导数等于0解出参数。拿ℓ(p) k * log(p) (n-k) * log(1-p)来说对p求导得到dℓ/dp k/p - (n-k)/(1-p)令它等于0得到k/p (n-k)/(1-p)交叉相乘后解得p k/n。这就是极大似然估计的解析解——抛10次硬币出7次正面p的估计值是0.7。这个结果跟直觉完全吻合但它的意义在于这不是猜的而是推导出来的。但在真实场景里解析解往往是奢侈品。复杂的模型比如深度神经网络参数动辄百万千万级别根本不可能用“令导数等于0”的方式求解。这时候就得上数值优化方法——最典型的就是梯度下降。核心思想是先给参数一个初始值然后沿着梯度方向迭代更新一步一步走到极值点。这个思路在后面的Python实战里会完整跑一遍。这里还有一个细节值得注意解析解和数值解求出来的结果可能并不完全一样因为数值优化依赖初始值和步长可能收敛到局部最优而非全局最优。但是在凸问题——比如逻辑回归——里局部最优就是全局最优所以问题不大。非凸问题里极大似然估计可能会陷入局部最优这也是深度学习训练为什么需要多次尝试初始化的原因之一。3. 极大似然估计在AI中的落地场景3.1 线性回归最小二乘法其实是MLE的特例很多人在学线性回归的时候用的是最小二乘法最小化预测值和真实值之间的平方误差和。但鲜有人告诉你这个做法本身可以从极大似然估计推导出来。假设线性模型是y wx b ε其中ε是噪声项服从均值为0、方差为σ²的正态分布。那么给定x时y的条件分布服从均值为wx b、方差为σ²的正态分布。现在有一堆观测数据(x_i, y_i)我们要估计参数w和b。把所有样本的似然函数写出来取对数最大化你会惊奇地发现最大化对数似然等价于最小化Σ(y_i - wx_i - b)²——这正是最小二乘法的目标函数。这个推导我认为是理解统计学习和机器学习之间关系的最重要桥梁。它揭示了一个本质**最小二乘法不是“天经地义”的它是“在噪声服从正态分布”这个假设下推导出来的最优方案。**如果你的噪声分布不是正态最小二乘法就不一定是最优的了。这也是为什么我反复强调学AI一定要懂极大似然估计——它让你看透很多“默认配置”背后的前提条件。3.2 逻辑回归交叉熵损失从哪来如果说线性回归是回归问题的基础那逻辑回归就是分类问题的基础。它的输出是“样本属于某个类别的概率”这就需要把线性输出映射到0到1之间——通常用sigmoid函数。但问题是训练的时候用什么损失函数最直觉的想法可能是“直接用平方误差”但实际训练逻辑回归的时候大家用的都是交叉熵。为什么答案还是极大似然估计。逻辑回归的模型假设是给定x样本属于类别1的概率是sigmoid(wx b)属于类别0的概率是1 - sigmoid(wx b)。这本质上是一个伯努利分布。把整个训练集上的似然函数写出来取对数你会得到ℓ Σ [y_i * log(p_i) (1-y_i) * log(1-p_i)]这个式子就是交叉熵损失函数的原型加个负号变成损失函数。也就是说用交叉熵损失训练逻辑回归就是在做极大似然估计。这个认识的实操价值是当你不在“概率分布”的框架下思考问题你就很难理解为什么某个任务要用交叉熵而不是平方误差也很难在自定义模型时选对损失函数。3.3 训练神经网络的本质最大化对数似然把视角再拉大一点。深度学习中90%以上的监督学习任务本质上都在做一个事最大化训练数据上的对数似然。你可以这样理解神经网络不是一个“函数拟合器”而是一个“条件概率分布建模器”。它的最后一层输出在经过合适的激活函数softmax用于分类、identity用于回归之后可以被解读为P(y|x, θ)——在给定输入x和网络参数θ的条件下输出y的概率。训练过程就是调整θ使得整个训练集上所有(x_i, y_i)的对数似然最大化。交叉熵损失对应分类任务均方误差对应“高斯噪声假设下的回归任务”它们都是负对数似然的不同实例。这个视角的巨大好处是它统一了你对深度学习各种任务的理解。无论你在做图像分类、机器翻译还是语音识别本质上都是在做极大似然估计只是条件概率分布的建模方式不同罢了。理解了这一层你再看那些五花八门的损失函数就不会觉得它们是凭空冒出来的了。4. 从零实现用Python跑通一个MLE4.1 数据生成与模型假设理论讲再多不如亲手跑一遍。下面我们用Python实现一个完整的极大似然估计流程。首先构造场景假设真实世界中有一个线性关系y 3x 5但我们能观测到的数据被高斯噪声污染了。我们拿到一批(x, y)数据现在要通过极大似然估计把这“3”和“5”还原出来。import numpy as np import matplotlib.pyplot as plt # 固定随机种子保证结果可复现 np.random.seed(42) # 生成100个x值均匀分布在0到10之间 x np.linspace(0, 10, 100) # 真实的参数 true_w 3.0 true_b 5.0 # 生成带噪声的观测数据 noise np.random.normal(0, 2.0, sizex.shape) y true_w * x true_b noise这里的关键假设是“噪声服从均值为0的正态分布”。这个假设可能对可能错但在极大似然估计的框架下你必须先做这个假设后面的一切推导都建立在它之上。如果你怀疑噪声不是正态分布的那极大似然估计得到的“最优”参数可能就不是你真正想要的。数据的分布情况可以画个散点图确认一下。你会看到点大致分布在一根直线附近但有一定的上下波动这就是高斯噪声的效果。4.2 解析解实现从公式到代码根据第2节的推导在线性回归且噪声服从高斯分布的假设下极大似然估计等价于最小化均方误差。而均方误差的极小值有解析解被称为“正规方程”θ (X^T X)^(-1) X^T y其中X是设计矩阵——第一列是1对应偏置项第二列是x值θ是我们要估计的参数向量[b, w]^T。# 构建设计矩阵 X_design np.column_stack([np.ones_like(x), x]) # 正规方程求解 theta_hat np.linalg.inv(X_design.T X_design) X_design.T y b_hat, w_hat theta_hat print(f估计的截距 b {b_hat:.4f}) print(f估计的斜率 w {w_hat:.4f}) print(f真实值: b {true_b}, w {true_w})跑完这段代码你大概率会得到接近b ≈ 4.x、w ≈ 3.x的结果。因为噪声的存在估计值不可能精确等于真实值但只要数据量够大估计值就会越来越接近真实值——这是极大似然估计的重要性质叫作“一致性”。这里有个实操细节值得注意直接用np.linalg.inv()求逆在数据量小的时候没问题但特征多的时候计算量会爆炸而且矩阵可能接近奇异。更稳妥的做法是用np.linalg.lstsq()或者np.linalg.solve()。# 更稳定的做法 theta_hat_stable, _, _, _ np.linalg.lstsq(X_design, y, rcondNone)4.3 数值优化实现梯度下降解析解虽然漂亮但它只存在于线性模型等少数场景。为了展示极大似然估计在复杂模型中的落地方式我再带你看一遍用梯度下降求解的完整流程。我们需要做的有下面四步。第一步定义对数似然函数。在高斯噪声假设下单个样本的负对数似然是(y - (wx b))²的常数倍。所以我们要最小化的是均方误差。第二步求梯度。对w和b分别求偏导。第三步初始化参数——比如w 0b 0。第四步迭代更新w w - 学习率 * 梯度b b - 学习率 * 梯度。# 初始化参数 w 0.0 b 0.0 learning_rate 0.01 iterations 1000 n len(x) loss_history [] for i in range(iterations): # 预测值 y_pred w * x b # 计算梯度 dw (-2/n) * np.sum(x * (y - y_pred)) db (-2/n) * np.sum(y - y_pred) # 更新参数 w - learning_rate * dw b - learning_rate * db # 记录损失 loss np.mean((y - y_pred) ** 2) loss_history.append(loss) if i % 100 0: print(f迭代 {i}: w {w:.4f}, b {b:.4f}, loss {loss:.4f})跑完之后你会看到w和b逐渐收敛到接近真实值。这跟解析解的结论一致但走的路完全不同——它是从初始值出发一步一步沿着梯度的反方向走到极值点的。这里有三个调参的关键点学习率是最大的坑。太小的话收敛极慢半天跑不到极值点太大的话会震荡甚至直接发散。实践中可以从0.01开始试看loss曲线再调整。迭代次数要配合学习率。学习率小就得多跑几轮学习率大就可以少跑几轮。关键是看loss是不是已经平稳了。梯度计算要用整个批量batch的均值而不是单个样本。因为单个样本的梯度可能指错方向平均之后更稳定。5. 常见问题与避坑指南5.1 为什么一定要取对数而不是直接用似然函数这个问题我在教别人的时候被问到过很多次。最直接的回答是连乘在数值计算中极易下溢。比如你有10000个样本每个概率密度在0.5左右连乘的结果是0.5^10000这个数字小到任何浮点数都存不下。取对数之后加法的数值范围就合理多了。而且对数函数是单调的不改变极值位置数学性质还好——梯度就等于各因子梯度之和计算简单。一个补充的细节在推导极大似然估计的解析解时取对数还能把指数族分布里的指数项“拉”下来让表达式变成线性的处理起来非常顺手。5.2 极大似然估计一定会过拟合吗这是把极大似然估计用到机器学习时最需要警惕的问题。严格来说极大似然估计本身不一定会过拟合但它的确在某些情况下特别容易过拟合——尤其是模型复杂、数据量不足的时候。原因是极大似然估计的终极目标是最大化训练数据上的似然。如果模型足够复杂它可以“记住”训练数据中的每一个点——包括噪声——从而让似然值达到极大。但这样学到的模型泛化能力很差。解决办法有几条路选择更简单的模型限制模型容量。比如线性回归里用L1或L2正则化——正则化本质上是在似然函数后面加上参数的先验约束这已经进入了最大后验估计MAP的范畴。增加数据量让模型没有机会“死记硬背”。早停——在训练过程中监控验证集上的表现一旦验证集loss开始上升就停止训练。理解MLE和过拟合的关系能帮你更清醒地看待“loss越低越好”这个直觉。在训练集上做文章容易在泛化能力上下功夫才是真功夫。5.3 MLE、MAP与贝叶斯估计三者到底什么关系这个三角关系是很多人的知识盲区。我帮你一句话理清MLE只看数据认为参数是一个固定的未知常量我们的任务是找到那个让数据出现概率最大的固定值。MAP在MLE的基础上加上了参数的先验分布。比如我事先认为w更可能靠近0那么我在优化的时候不光是最大化似然还要考虑这个先验——这相当于加了一个正则化项。如果你用L2正则化那你其实就是在做“参数服从高斯分布”假设下的MAP估计。贝叶斯估计则更进一步它不给一个固定的参数点而是计算参数的后验分布——得到一个概率分布而不是一个点估计。这个成本很高但信息也最完整。MLE: θ_MLE argmax P(data | θ) MAP: θ_MAP argmax P(data | θ) * P(θ) 贝叶斯: P(θ | data) ∝ P(data | θ) * P(θ)理解这个递进关系之后很多论文里的方法你就能一眼看穿加正则化就是在做MAP用dropout在贝叶斯视角下相当于对参数分布做近似采样。5.4 数值稳定性从log(x)到log-sum-exp实操中还有一个很隐蔽但极其常见的坑直接计算对数似然时如果涉及到概率归一化——比如softmax——你可能会写出log(exp(a_i) / Σ exp(a_j))这种表达式。当某个a_j特别大的时候exp(a_j)会溢出成无穷大结果直接变成NaN。解决办法是使用log-sum-exp技巧提出最大值m把表达式改写为a_i - m - log(Σ exp(a_j - m))这样所有指数项的输入都被限制在[-∞, 0]的范围内不会溢出。在实际编码中很多框架如PyTorch和TensorFlow都已经内置了数值稳定的log-softmax和cross-entropy实现。但如果你自己写底层代码——比如从零实现一个softmax回归——这个坑就一定绕不过去。还有一个类似的数值稳定问题当p接近0或1时log(p)或log(1-p)会变成负无穷。这时候可以考虑对概率值做裁剪clip保证落在[1e-15, 1-1e-15]区间内。6. 学习极大似然估计的一些个人体会写到最后分享一点我自己的学习经验。第一次学极大似然估计的时候我最大的困惑是“这东西到底有什么实际用途”。当时课本上只讲了猜硬币的例子感觉就是一个很trivial的推导跟AI没什么关系。后来真正上手做项目才发现它的影子无处不在损失函数怎么设计、正则化怎么理解、模型不确定性怎么评估全都跟极大似然估计有关。如果你现在也处于“公式能看懂但不知道往哪用”的阶段我的建议是不要孤立地学它而是把它跟具体的模型绑在一起学。你学一个线性回归就从头推导一遍“为什么损失是均方误差”你学一个逻辑回归就推导一遍“为什么损失是交叉熵”。每推导一遍极大似然估计的烙印就深一层。另外动手写代码非常重要。哪怕只是用numpy写一个简单的线性回归梯度下降你也会对“似然函数”“梯度”“学习率”这些概念产生真实的体感。纸上得来终觉浅这句老话放在数学和代码上同样成立。还有一个小技巧当你看不懂某个损失函数时先停下来问自己三个问题——这个模型的概率解释是什么噪声服从什么分布最大化对数似然之后得到了什么绝大多数损失函数都可以顺着这三个问题推出答案。把这个基础打牢了后面学EM算法、变分推断、贝叶斯神经网络这些进阶内容都会顺畅很多。数学基础这东西短期内看不出效果但它决定的是你技术天花板的高度。
返回列表