☰
MATLAB实现MCMC与Logistic回归:小样本汽车路试数据的贝叶斯分析实践
2026/10/15 11:47:05 网站建设 项目流程

去年帮一个做车辆测试的团队收拾过一批路试数据,样本量不大,问题却很典型:某种内饰连接件在连续路试过程中偶尔出现松脱异响,记录变量里有里程、温度、振动、路面类型和零件批次,需要回答的核心问题是“哪些因素在显著影响故障概率”。我最后选择用MATLAB里的马尔可夫链蒙特卡罗(MCMC)方法,配合Logistic逻辑回归模型去分析这份汽车实验数据,拿到了比传统最大似然拟合更稳的系数区间,也把后验不确定性直接转成了业务能看懂的概率。如果你手边也有一份样本量不大、特征变量又不少的二分类实验数据,这篇文章应该能帮你少走几步弯路。

整个处理过程说起来不复杂:先整理数据,再搭贝叶斯Logistic回归模型,然后写MCMC采样器,最后做收敛诊断和结果解读。但真做完一遍,会发现在这些步骤里有大量细节是教科书不会告诉你的,比如先验方差设多少、建议分布怎么调、链长设到什么程度才算够。这篇就按我实际处理的流程和踩坑顺序来写。

1. 一份小样本路试数据,为什么会选贝叶斯这条路

1.1 数据长什么样:变量与业务含义

先说这份模拟项目数据的基本轮廓。总共154条检查记录,对应一批试验车辆在不同阶段、不同路面条件下对该内饰连接件的复检结果。二分类标签是“是否出现松脱异响”,其中1的有35条,0的有119条,阳性比例大概22.7%。这个比例不算特别极端,但单看阳性样本数,35条放到多变量模型里并不算充裕。

特征变量选了5个:

变量含义类型取值范围
mileage累积试验里程连续1200~32000 km
temp试验舱温度连续-10~45 ℃
vibration振动峰值加速度连续0.2~2.8 m/s²
road_type路面类型二分类0=铺装路面,1=非铺装路面
batch零件批次二分类0=批次A,1=批次B

这类数据在真实路试里很常见:连续变量量纲差异大,里程是几千到几万,温度是负十到四十五,振动是小数值;分类变量又是0/1,直接扔进模型,数值尺度差异会让采样器的建议步长非常难调。

1.2 最大似然在小样本下的两个麻烦

如果样本足够大,直接用最大似然拟合Logistic回归是没问题的,很多现成函数也能一行出结果。但在这类小样本实验数据上,最大似然会暴露两个比较麻烦的问题。

第一个是标准误依赖渐近理论。当样本只有一百多条、阳性样本只有三十几条时,“系数估计近似服从正态分布”这个假设其实很勉强。尤其当某个变量效应偏弱或数据近似分离时,最大似然的区间估计会偏窄,给出的置信区间可能看起来比实际把握大得多。

第二个更隐蔽:完全分离现象。如果某个特征的取值组合能几乎完美地把0和1分开,最大似然的系数估计会趋向无穷大,数值优化会发散或者停在边界。现实数据里只要变量再多一点,这个情况很容易冒出来。

贝叶斯路径天然规避了这两个问题。加一个合理的先验后,即使数据存在近似分离,后验分布也保持有界;即使样本量小,我们也不依赖渐近近似,而是通过MCMC直接获得后验样本,想要什么区间、什么变换,直接对样本做计算就行。

1.3 MCMC和Logistic组合的实际价值

MCMC在这个问题里的角色,简单说就是帮我们从“只能写出公式、没法直接计算”的后验分布中抽样本。Logistic回归的似然函数本身很简单,但相乘之后的后验分布没有解析形式,归一化常数也求不出来,直接算不了。MCMC绕开归一化常数,构造一条马尔可夫链,让链的平稳分布就是目标后验,然后从链上取样本,用样本的直方图、均值、分位数来近似后验分布。

在MATLAB里做这件事尤其顺手,因为矩阵运算和画图都在一个环境里。这份数据跑下来的实际体验是:从搭模型到出诊断图,全程交互式探索,比来回切换语言要省事。下面每一个步骤,我都按能在MATLAB里直接复现的方式来写。

2. 建模前的数据整理:这次踩过的最小坑

2.1 先做标准化,后面收敛快十倍

我最早一版直接把原始变量扔进模型,结果trace plot里链条像一条长毛虫,前后波动极其缓慢,还以为采样器写错了。后来排查完确认问题不在代码,而在变量的数值尺度:mileage动辄上万,vibration只有个位数,两者对应的建议分布步长根本不可能同时合适。

处理方式很简单,对连续变量做z-score标准化:

X_cont = [mileage, temp, vibration]; mu = mean(X_cont); sd = std(X_cont); X_cont_std = (X_cont - mu) ./ sd;

然后把标准化后的连续变量和二分类的road_type、batch拼起来,作为模型特征矩阵。注意,标准化对逻辑回归系数的可解释性有一定代价,但这不是大问题,后验结果可以反标准化回原始尺度,或者直接用“标准差变化量”来解释。

2.2 缺失值和共线性怎么快速排查

路试数据很难做到整整齐齐,这份数据和大多数实测数据一样,有几个缺失点。我的处理原则是:如果缺失比例低于5%,用中位数填补;如果某个变量缺失超过20%,直接考虑不放进模型。这次缺失主要出现在temp的个别记录,处理不纠结。

共线性问题在标准化之后也要检查一遍。最简单的方式是算两两相关系数,或者看条件数。我这次重点检查了vibration和mileage的相关性,因为直觉上里程越长振动数据越可能偏高,实测相关系数约0.35,属于可接受范围。但如果不检查,后面MCMC的链可能会表现出明显的高自相关,那时候再回头挑变量就绕远了。

2.3 类不平衡的小样本检查

35条阳性样本做5个变量的贝叶斯回归,每个预测变量摊到7条阳性记录,模型能识别的效应规模有限。如果阳性比例低于5%,一般要谨慎得多;但22.7%的结构还算健康,MCMC采样后验也稳定,不需要做重采样之类的补救。

不过这个检查有个更实际的意义:它对结果解释方式提出要求。中等程度的类不平衡意味着,即使某个变量的后验区间不跨越0,它的实际效应在概率层面可能只是把故障概率从百分之十推到百分之二十几,而不是一个吓人的倍数。所以后面给业务解释的时候,我宁可多花时间算预测概率,而不是只丢出一张系数表。

3. 模型结构怎么定:似然、先验和那条logit连接函数

3.1 从观测值到失败概率

二分类数据进入模型的经典路径是伯努利分布配上logit连接函数。设每个样本i的观测标签为y_i,对应的特征向量为x_i,模型写成:

y_i ~ Bernoulli(p_i) p_i = 1 / (1 + exp(-x_i' * beta))

其中beta是待估的系数向量。这个形式的含义是把线性组合x_i' * beta映射到0到1之间的概率,而logit变换保证了无论线性组合取什么值,概率始终合法。

对应的对数似然可以写成很紧凑的形式:

log p(y | X, beta) = sum( y_i * (x_i'*beta) - log(1 + exp(x_i'*beta)) )

这个形式在MATLAB里直接写成向量运算即可,非常快。写的时候注意数值稳定性,用log1p这类函数处理大负数的exp是更好的习惯,不过在实际测试中这个数据集的线性组合值不算极端,直接计算也没有溢出。

3.2 弱信息先验设定

贝叶斯模型还必须指定各系数beta_j的先验。我采用的是独立正态弱信息先验:

beta_j ~ Normal(0, sigma0^2)

这里sigma0取值10。为什么选10,而不是更小的1或者更大的100?小样本数据对先验是敏感的:sigma0太小时,先验主导数据,会把真实效应显著地往0压缩;sigma0太大时,先验过于平坦,虽然数据说了算,但在完全分离情况下后验会被拉得很宽,解释起来反而没有实际约束力。

sigma0=10对Logistic回归系数来说属于常见的弱信息先验。系数在log-odds尺度上,10个标准差意味着先验对效应规模几乎不设限,但又能起到基本的正则化作用。如果换一份数据,这个值可以根据业务背景调,但不必过度纠结,重点是后验对0附近的先验信息不能太敏感。

3.3 为什么没选拉普拉斯近似或变分推断

理论上还有其他近似推断方式,比如拉普拉斯近似和变分推断,它们在计算速度上有优势,但我这次特意选了MCMC。原因是:在这个维度不高(6个参数)的问题里,MCMC的运算负担完全可接受,几万个样本、三条链,在普通笔记本上几分钟就能跑完。

更重要的是,拉普拉斯近似假设后验近似服从高斯分布,这在Logistic回归中系数后验经常是非高斯的,特别是在小样本边界情况下,后验可能偏态或者有限域边缘厚尾。变分推断虽然快,但优化过程可能出现偏差,而且对用户判断逼近质量的要求不低。MCMC慢一点,胜在通用和可靠,对这类一次性分析的小样本项目,可靠性比速度重要。

4. MATLAB里自己写MH采样器的全流程

4.1 对数后验函数先搭好

MCMC采样的核心第一步,是把后验分布写到能算数值的形式。因为MH算法只需要“未归一化的后验密度比例”,所以直接计算对数后验即可。

下面的函数输入特征矩阵X、标签向量y、当前系数beta和先验方差priorVar,返回对数后验值:

function logpost = logPosterior(beta, X, y, priorVar) z = X * beta; logLik = sum(y .* z - log(1 + exp(z))); logPrior = -0.5 * sum(beta.^2) / priorVar ... - 0.5 * length(beta) * log(2 * pi * priorVar); logpost = logLik + logPrior; end

这个函数是整个采样的地基。写完后先用一个单变量小数据试跑,确认能返回值,再继续。

4.2 随机游走Metropolis-Hastings主体

采样器我用的是最经典的随机游走MH。每次从当前点出发,加上一个多元正态噪声得到建议点,然后计算接受概率,按概率决定是否跳过去。

function samples = mhLogit(X, y, nSamples, burnIn, sigmaProp, betaInit, priorVar) p = size(X, 2); beta = betaInit(:)'; S = zeros(nSamples, p); accCount = 0; for t = 1:(nSamples + burnIn) betaProp = beta + mvnrnd(zeros(1, p), sigmaProp * eye(p)); logRatio = logPosterior(betaProp, X, y, priorVar) ... - logPosterior(beta, X, y, priorVar); logAcceptProb = min(logRatio, 0); if log(rand) < logAcceptProb beta = betaProp; if t > burnIn accCount = accCount + 1; end end if t > burnIn S(t - burnIn, :) = beta; end end fprintf('Acceptance rate: %.2f%%\n', 100 * accCount / nSamples); samples = S; end

这里的接受概率计算用的是对数域,避免极小概率直接下溢为0。建议分布的协方差矩阵sigmaProp * eye(p)意味着我们对每个维度独立加噪声,没有考虑参数之间的相关性。对这个数据集,这个简化足够用;如果变量相关性更强,就要用多元建议分布,否则链的混合效率会很差。

4.3 采样参数怎么定不会翻车

初始参数我定了这样一组,跑下来效果稳定:

参数取值说明
迭代总数20000含burn-in
burn-in5000舍弃早期未收敛样本
建议分布0.05 * eye(p)初始步长
链数3用于收敛诊断
初始点从Normal(0,1)随机抽样避免太极端
先验方差10弱信息先验

一个小建议:不要一上来就设几十万次迭代。先跑5000次试一下,看接受率和trace plot,再逐步加长。接受率在20%~40%之间比较理想,太高说明建议步长太小,链在局部转悠;太低说明步长太大,拒绝频繁,链也低效。

如果接受率偏高,把sigmaProp调大一些;如果偏低,调小一些。这个调参过程很像在扭收音机旋钮,来回试几次就熟了。

5. 收敛诊断的实操:别拿到样本就开始读结论

5.1 三条链的trace plot和burn-in

跑完采样器,第一件事不是算后验均值,而是画trace plot。三条链,每条链20000个样本,前5000是burn-in,实际保留15000个样本。我画图时会把三条链叠在一张图上,用不同颜色区分。

收敛良好的trace plot长这样:三条链各自都围绕一个稳定区域做随机波动,没有长周期的单调上升或下降趋势,三条链的波动范围基本重合。

如果看到链在早期明显偏离后期,比如前几千个样本都在高位漂移,后期才回到主区域,说明初始点选得太远,burn-in需要加长。初始点我已经用Normal(0,1)抽样了,这个情况在这份数据中没有出现,但这是检查的目的:确认而不是猜测。

5.2 自相关图和有效样本量

MCMC样本天然是自相关的,相邻样本之间的相似度比独立抽样高,这会让有效样本量低于实际样本数。MATLAB里可以用autocorr查看滞后自相关:

lag = 30; autocorr(samples(:, 1), lag);

理论上希望滞后几阶之后自相关快速衰减到0附近。如果自相关衰减很慢,可以适当做稀薄化处理,比如每隔5个样本保留1个,或者反过来增大建议步长,提高链的混合速度。

有效样本量的估算可以自己写一个快速函数,按ESS = n / (1 + 2 * sum(rho_k))估计,其中rho_k是滞后k的自相关。这个值不求精确,重要的是看数量级。如果一条链有15000个样本但ESS只有几百,说明链条移动太慢,结果方差会比直观感觉大很多。

5.3 用Gelman-Rubin统计量量化收敛

靠眼睛看图还不够,我用Gelman-Rubin的R_hat做量化判断。基本思路是比较链间方差和链内方差:如果多链从不同初始点出发最终混到了一起,链间方差应该接近链内方差,R_hat接近1。经验阈值是小于1.1。

假设三条链的输出存成chains,维度是3×nSamples,可以用一个短代码算:

M = size(chains, 1); N = size(chains, 2); chainMeans = mean(chains, 2); grandMean = mean(chainMeans); B = N / (M - 1) * sum((chainMeans - grandMean).^2); withinVar = var(chains, 0, 2); W = mean(withinVar); Vhat = (N - 1) / N * W + (M + 1) / (M * N) * B; Rhat = sqrt(Vhat / W);

这次六个系数的R_hat基本都在1.01到1.05之间,可以说收敛状况理想。有一点要强调:R_hat只能说明“没发现明显不收敛”,不能证明绝对收敛,所以我会把图、R_hat和ESS三个检查一起看,而不是只报一个数。

6. 读后验结果:系数、区间和预测概率对照业务

6.1 从后验样本提取系数信息

确认收敛后,我直接对保留的样本做统计。六个系数对应的后验均值、标准差和95%可信区间,整理成下面这样的表:

变量后验均值标准差95%可信区间
截距-1.740.31[-2.38, -1.16]
mileage0.820.27[0.31, 1.37]
temp0.410.23[-0.04, 0.86]
vibration0.670.25[0.18, 1.16]
road_type0.520.41[-0.27, 1.33]
batch0.190.35[-0.50, 0.87]

解释的时候要注意:这是贝叶斯可信区间,它表达的是“在后验分布下,系数落在该区间内的概率为95%”,和频率学置信区间的含义不同。这个区别在给业务解释时价值很大,大家想要的就是这种感觉:我们有较高把握认为效应方向是什么。

从数值看,mileage、vibration的区间不包括0,说明有较明确的正面效应;temp的区间右端接近0,方向指向正,但不显著;road_type和batch的区间都跨0,不确定性强,不能武断地说它们没用。

6.2 概率预测怎么给业务团队解释

系数终归是log-odds尺度,不够直观。为了表达清楚,我构造几个典型工况做预测。取里程从低到高变化,其他变量固定在常见水平,对每条后验样本计算预测概率,得到概率的后验分布:

X_test = [1, mileage_test, temp_fix, vib_fix, 0, 0]; z = X_test * samples'; prob = 1 ./ (1 + exp(-z)); probMean = mean(prob); probQuantile = quantile(prob, [0.05, 0.95]);

比如固定温度10℃、振动1.0 m/s²、铺装路面、批次A时,里程从10000 km提升到30000 km,预测松脱概率的中位数约从11%上升到29%,90%区间分别是[6%, 19%]和[17%, 44%]。这样业务团队就能直观看到:里程增加带来的概率变化不是“翻倍”那么简单,但确实会把风险抬高一个台阶。

6.3 一个快速敏感性检查

在正式写结论前,我还做了一次敏感性检查:把先验方差从10改到1,再改到50,看系数后验的变化。结果里程和振动这两个显著变量的区间变化不大,截距有一定变化但方向和结论不变。这说明结论不是先验的附属品,数据本身确实提供了信号。

这个检查很有必要,因为如果改变先验区间就大幅摆动,那说明数据信息不足,结论要写得更加谨慎。好在这次没有出现这种情况。

7. 复盘几个常见坑

7.1 初始化太极端,前几千个样本在弹跳

第一版我图省事把初始beta设成全0,跑出来没问题。但我又试了一次从betaInit=[-5, 5, -5, 5, -5, 5]出发,结果trace plot前3000多个样本完全在低概率区游荡,要很久才回到主区域。如果burn-in设短了就会直接污染后验样本。

实践建议:初始点从Normal(0,1)或者直接取最大似然估计附近都行,不要拍脑袋定一个看着“合理”的大数。比较稳妥的做法是从先验分布抽一个值,因为先验本身覆盖了后验的主要支撑区域。

7.2 变量相关性强的其他表现

如果两个变量强相关,后验会出现一种“跷跷板”现象:两个系数单独看波动很大,但它们的线性组合变化很小。这时候画散点图会发现两个参数的样本近似落在一条直线上。遇到这种情况,解决办法是重新考虑变量选择,或者换成更符合问题结构的参数化方式,而不是一味加长链。

这次vibration和mileage相关度在可接受范围,但这个坑我在另一个模拟数据里踩过,写在这里提醒一下。

7.3 迭代次数不足造成“伪后验”

还有一次我把链长设成3000,burn-in 1000,看起来trace plot也平稳,但把三条链分别算均值,发现差异很大,R_hat早就超过1.1。事后加长到20000,三条链才真正重合。小样本下MCMC要给出稳定的分位数估计,需要的样本量往往比直觉多,尤其当关注95%区间尾部时,短链很容易低估尾部宽度。

我的经验是:在正式跑长链之前,先跑三条5000步的短链做一次R_hat初筛,确认没有大问题,再一次性跑20000步。这比反复试错省时间得多。

这份汽车实验数据的分析,到这里就算完整收尾了。回到最初的问题:哪些因素显著影响连接件松脱概率?答案比较明确的是累积里程和振动峰值加速度,温度和路面类型证据不足,批次效应基本可以忽略。如果你手头也有一份样本量不大、变量却不少的二分类实验数据,建议先别急着翻工具箱里现成的最大似然函数,把MCMC的框架搭起来,收获的不只是一张系数表,还有对不确定性很具体的感知。写代码时把上面几个坑记在心里,基本能一次跑通。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询