简介:面向多准则决策分析与评价建模的学习者和研究者,这份开源 MATLAB 代码包实现贝叶斯最佳-最差方法(Bayesian BWM),可用于在成对比较中估计准则权重并给出后验推断。包内共 8 个文件,以 6 个 .m 脚本为主,另含 1 个 readme.md 与 1 个示例数据 txt,压缩后约 15KB;脚本涉及主算法、JAGS 调用接口、MCMC 链重构与收敛诊断、结果绘图以及示例运行入口,结构紧凑,便于按流程阅读和改造。使用前需在本机安装 JAGS 并配置环境变量,重启 MATLAB 后即可在 runMe.m 中替换准则名称、最优-最差比较数据等变量开展实验。已有 2174 人学习,适合希望把贝叶斯 BWM 用于权重测算、方案排序或论文复现的读者参考,也能帮助理解先验设定、后验采样与收敛判断之间的衔接。
1. 从一次评审会上的追问说起
去年帮一个做设备选型的团队看他们的评分模型,会上有人问了一句:这套权重如果换个专家重新打分,会不会整个排序都翻过来?负责模型的同事当场把最优准则的偏好值从 8 改成 7,一跑,原本排第二的方案跳到了第一。会议室安静了两秒。这件事之后我开始认真看贝叶斯 BWM 这类实现——传统 BWM(Best-Worst Method)给的是权重的点估计,而贝叶斯版本把"专家打分"当带噪观测,输出的是每个权重的后验分布。这份 MATLAB 资源BayesianBWM走的就是这条路:用 BWM 的最优/最差两两比较降低提问量,再用 MCMC 把不确定性显式采样出来。适合两类人:手上有多准则评价场景、被问过"权重稳不稳"的算法同学;以及想把 BWM 从 Excel 搬进脚本、又不想只拿一个干巴巴数字的工程开发者。
2. 项目定位与技术选型:BWM 为什么值得用贝叶斯重写一遍
2.1 BWM 的输入是一对向量,不是一张矩阵
AHP 那套要专家填完整的判断矩阵,n 个准则就是 n(n-1)/2 次两两比较,准则一多专家就开始乱填。BWM 的思路是把提问量压到 2n-3 次:先让专家指认哪个准则最重要、哪个最不重要,然后只问两件事——最优准则相对其余每个准则重要多少,其余每个准则相对最差准则重要多少。得到两个向量之后,权重就由一组带约束的优化问题解出来。
落到数据上就是三个东西,准则名列表、最优准则下标、最差准则下标,加上两条长度为 n 的整数向量。下面的结构是这类项目最常见的输入约定,看代码前先把它记住:
| 变量 | 维度 | 含义 | 取值范围 |
|---|---|---|---|
criteria | 1×n cell | 准则名称,只用于结果展示 | 任意字符串 |
idxBest | 标量 | 最优准则在列表中的位置 | 1..n |
idxWorst | 标量 | 最差准则在列表中的位置 | 1..n |
aB | 1×n | 最优准则相对各准则的偏好 | 1..9 整数,自身位置为 1 |
aW | 1×n | 各准则相对最差准则的偏好 | 1..9 整数,自身位置为 1 |
偏好刻度沿用 Saaty 的 1-9 语义,1 表示同等重要,9 表示极端重要。这两条向量里有两个位置是"占位"的:aB(idxBest)和aW(idxWorst)按定义恒为 1,不携带信息,建模时要排除,否则会把 1 当成一次真实观测喂进似然里,权重会被无端拉平。这是后面代码里第一个容易看漏的细节。
2.2 点估计的软肋,恰好是贝叶斯的长处
传统 BWM 的目标函数是最小化最大绝对偏差,形式上是 min max_j { |w_B/w_j − a_Bj|, |w_j/w_W − a_jW| },线性化之后用一个辅助变量 ξ 把它转成线性规划,解出来的 ξ* 还能当一致性指标用。这套做法成熟、快、可解释,问题在于它只回答"给定这组打分,权重是多少",不回答"这组打分本身有多可信"。
专家打分天然带噪。同一个专家隔一周再填,8 可能变成 7;换个专家,最优准则的指认都可能不同。点估计把这些波动全部吸收进一个数字里,你看不出哪个权重的估计是稳的、哪个是勉强凑出来的。贝叶斯化的做法是把观测写成 w_B/w_j 的带噪版本,给权重配一个单纯形上的先验,跑 MCMC 得到后验。这样每个权重是一个分布,可以算 95% 可信区间,可以算"方案 A 优于方案 B 的后验概率",评审会上被追问时拿得出东西。
代价也很明确:从解一个线性规划变成跑几千次迭代的采样,慢,而且要处理收敛诊断。所以这类项目一般不会把贝叶斯版当成唯一入口,常见做法是保留传统 BWM 作为快速粗筛和一致性前置检查,贝叶斯版负责在关键决策上给出不确定性刻画。
2.3 MATLAB 配 JAGS,是这条路线上最省事的组合
采样器可以自己写 Metropolis-Hastings,但在这个场景里没必要。权重要满足非负且和为 1,落在单纯形上,Dirichlet 分布正好是它的共轭先验,JAGS 直接支持ddirch;再配一个观测模型,模型文件十来行就能写完,剩下的交给 Gibbs 采样。MATLAB 侧用 matjags 这个接口把数据推过去、把链拉回来,整个流程不需要离开 MATLAB 生态。
选型上有几个现实考虑。一是团队如果本来就在 MATLAB 里做评价和画图,加一个 JAGS 比引入 Python 或 R 的整条工具链轻。二是 JAGS 的模型文件是纯文本,审稿或者交接时别人能直接读,比一堆采样器代码好解释。三是 MATLAB 的矩阵操作在处理多专家、多方案的后验汇总时确实顺手,quantile、prctile、排序这些一行就够。
提示:matjags 是第三方接口,不是 MathWorks 官方工具箱。它依赖本机已安装的 JAGS 可执行程序,装完 JAGS 之后要把它的 bin 目录加进系统 PATH,否则 MATLAB 里调用会直接报找不到可执行文件。
至于为什么用 JAGS 而不是 Stan,实际差别没那么大,两者都能写这个模型。JAGS 的语法更接近 BUGS 的老写法,模型短,调试时打印中间节点方便;Stan 的 HMC 在高维下效率更好,但这个模型维度低(n 一般不超过十几个),采样效率不是瓶颈。我一般按团队已有环境选,别为了一个十几维的模型去折腾新工具链。
3. 目录结构与数据流:从准则打分到后验权重
3.1 仓库拆开之后是四层
这类项目的目录不会太深,但分层要清楚,否则改一个观测分布要翻三个文件。下面是我按这类资源常见组织方式整理的结构,实际拿到手可能文件名略有出入,但职责划分基本一致:
BayesianBWM/ ├── data/ │ ├── criteria.csv # 准则名称,一行一个 │ └── comparisons.csv # 最优/最差下标与两条偏好向量 ├── model/ │ └── bwm.jags # JAGS 模型定义,观测分布和先验都在这里 ├── src/ │ ├── run_bwm.m # 主入口,串起整个流程 │ ├── check_consistency.m # 传统 BWM 一致性比率粗检 │ ├── prep_jags_data.m # 把表格转成 JAGS 能吃的 list │ └── summarize_posterior.m # 后验汇总、排序、出图 ├── results/ # 采样结果与图表落盘位置 └── README.md四个目录对应四个职责:data存原始输入,model存统计模型,src存流程代码,results存输出。把模型单独抽成一个.jags文件而不是硬编码在 MATLAB 字符串里,是这类项目里我认为最值得保留的习惯——换似然、换先验只动这一个文件,MATLAB 侧完全不用改。
src里四个脚本的依赖关系是单向的:run_bwm.m调用其余三个,反过来不行。这意味着你可以单独拿check_consistency.m去做批量打分筛查,不用启动采样器,这在数据量大的时候很实用。
3.2 输入文件的两个约定
criteria.csv就是一行一个准则名,读进来转成 cell 数组。comparisons.csv稍微讲究一点,它要同时承载下标和两条向量。下面是一种能用的写法,用两行分别放aB和aW,前面用一列标记类型:
type,idxBest,idxWorst,c1,c2,c3,c4,c5 aB,3,5,4,3,1,2,7 aW,3,5,2,3,9,5,1第一行说明最优准则是第 3 个、最差是第 5 个;aB行里第 3 个位置是 1(占位),表示准则 3 相对自己;aW行里第 5 个位置是 1。读的时候要做的校验至少三条:两条向量长度等于准则数、aB(idxBest)与aW(idxWorst)都为 1、所有值落在 1 到 9 的整数区间内。
校验这一步看着琐碎,但它是后面所有玄学问题的分水岭。我见过最典型的一次翻车是aB和aW放反了,程序照样跑完、照样出图,权重排序全错,直到有人拿手算结果对了一遍才发现。所以读入之后立刻断言,别等到采样完再看。
3.3 一次运行里数据经过了什么
把主脚本从头读到尾,数据流是这样的:
- 从
data/读入准则与比较向量,做合法性断言; - 调
check_consistency.m跑传统 BWM 线性规划,拿到 ξ* 和一致性比率; - 比率超阈值就打警告并中止,否则继续;
prep_jags_data.m剔除两个占位位置,把向量、下标、样本量打包成 struct;- 调 matjags 读
model/bwm.jags,传入数据和初值,跑若干条链; summarize_posterior.m把链合并、算后验均值与分位数、按均值排序,出图落盘。
这个顺序有个隐含前提:一致性检查在前,采样在后。逻辑上说得通——打分本身自相矛盾的话,后验也只是把矛盾分布化了,没有意义。但第 2 步用的是线性规划,第 5 步用的是 MCMC,两者对同一组数据的"一致性"判断未必完全同步,后面排查章会专门讲这个差异怎么处理。
4. 核心模块拆解:BWM 约束、贝叶斯模型与 MCMC 采样链路
4.1 一致性前置检查:先确认打分不是自相矛盾的
传统 BWM 的线性规划形式是:最小化 ξ,约束为 |w_B − a_Bj·w_j| ≤ ξ·a_Bj 和 |w_j − a_jW·w_W| ≤ ξ·a_jW,加上权重非负、和为 1。MATLAB 里用linprog直接写。下面这段是这类脚本的骨架:
function [w, xi, CR] = check_consistency(aB, aW, idxBest, idxWorst) % 传统 BWM 线性规划求解,返回权重、最小最大偏差和一致性比率 n = numel(aB); % 决策变量顺序:[w1..wn, xi],共 n+1 个 f = [zeros(1, n), 1]; % 不等式约束 A*x <= b,逐条展开绝对值 A = []; b = []; for j = 1:n if j == idxBest, continue; end % 占位位置不产生约束 row = zeros(1, n + 1); row(idxBest) = 1; row(j) = -aB(j); row(end) = -aB(j); % 移项后 xi 的系数 A = [A; row]; b = [b; 0]; row = zeros(1, n + 1); row(j) = 1; row(idxWorst) = -aW(j); row(end) = -aW(j); A = [A; row]; b = [b; 0]; end % 等式约束:权重和为 1 Aeq = [ones(1, n), 0]; beq = 1; lb = [zeros(1, n), 0]; ub = []; opts = optimoptions('linprog', 'Display', 'off'); [w, xi] = linprog(f, A, b, Aeq, beq, lb, ub, opts); % 一致性比率:xi* 除以同规模下的最大可能 xi CI = consistency_index(n); CR = xi / CI; end约束的展开是关键。原始式子是 |w_B − a_Bj·w_j| ≤ ξ·a_Bj,拆成两条线性不等式之后,把 ξ 当成第 n+1 个决策变量放进系数矩阵,符号要跟着移项一起翻。第 5 行的row(end) = -aB(j)就是那个容易写错的位置:如果漏了负号,linprog会给出一个 ξ 为负的解,看起来收敛了,实际毫无意义。
参数上,aB和aW是两条偏好向量,idxBest、idxWorst用来跳过占位位置。返回值CR一般跟 0.1 到 0.2 之间的阈值比,超过就提示专家重新打分。consistency_index(n)是按准则数量查表得到的最大偏差参考值,不同文献给的表略有出入,工程上按项目约定固定一张表就行,别每次换。
4.2 组装 JAGS 数据:占位位置必须剔除
JAGS 侧只认它需要的观测,所以 MATLAB 到模型的这一步是"减法":把占位的位置去掉,只留下真实比较。同时要把最优、最差准则的下标单独传过去,因为模型里的比值是围绕这两个位置组织的。
function jdata = prep_jags_data(aB, aW, idxBest, idxWorst) % 剔除 aB(idxBest) 与 aW(idxWorst) 两个占位观测 n = numel(aB); % 最优准则向量的真实观测位置 maskB = true(1, n); maskB(idxBest) = false; % 最差准则向量的真实观测位置 maskW = true(1, n); maskW(idxWorst) = false; jdata.n = n; jdata.B = idxBest; jdata.W = idxWorst; jdata.aB = aB(maskB); % 长度 n-1 jdata.aW = aW(maskW); % 长度 n-1 jdata.idxB = find(maskB); % 记录对应的是哪个准则 jdata.idxW = find(maskW); end为什么一定要保存idxB和idxW?因为剔除之后,aB的第 k 个元素对应的已经不是准则 k,而是idxB(k)。模型里要用这个映射把观测挂到正确的权重上。丢了映射,观测会被错配到相邻准则,结果看起来"差不多对",实际全错位。
aB和aW剔除后长度都是 n−1,这也解释了为什么 BWM 的提问量是 2n−3:两条向量合计 2n 个位置,减去两个占位就是 2n−2,再减去"最优准则相对最差准则"这个被两条向量重复覆盖的偏好,剩下 2n−3 次独立比较。
4.3 JAGS 模型逐段读:先验、似然、导出量
模型文件是整个项目的核心,十行左右,但每一行都有讲究。下面这版用的是对数正态观测,把aB(j)看作w_B/w_j的带噪版本:
model { # 1) 权重先验:单纯形上的均匀 Dirichlet for (i in 1:n) { alpha[i] <- 1 } w[1:n] ~ ddirch(alpha[]) # 2) 最优准则的观测:log(aB) ~ N(log(w_B/w_j), tauB) for (k in 1:(n-1)) { j <- idxB[k] log(muB[k]) <- log(w[B]) - log(w[j]) log(aB[k]) ~ dnorm(muB[k], tauB) } # 3) 最差准则的观测:log(aW) ~ N(log(w_j/w_W), tauW) for (k in 1:(n-1)) { j <- idxW[k] log(muW[k]) <- log(w[j]) - log(w[W]) log(aW[k]) ~ dnorm(muW[k], tauW) } # 4) 噪声精度先验,弱信息 tauB ~ dgamma(0.01, 0.01) tauW ~ dgamma(0.01, 0.01) }第 1 段是权重先验。ddirch要求参数向量每个分量大于 0,全取 1 就是单纯形上的均匀分布,等价于"没有任何准则被预先认为更重要"。如果你有历史数据支撑某个准则一贯更重要,把这组 alpha 改成非均匀的,模型会自动往那个方向偏,这是二次开发里最常用的一个旋钮。
第 2、3 段是似然。为什么取对数?因为aB是 1 到 9 的正整数,而w_B/w_j也是正数,直接在原尺度上建正态模型,方差会随均值一起变大,残差结构不对。取对数之后,比值变成差值,尺度统一,正态假设更站得住。muB[k]用的是<-而不是=,因为它是确定性节点,不参与采样。
第 4 段的tauB、tauW是精度参数(方差的倒数),用 Gamma 弱信息先验让数据自己决定噪声大小。这里有个坑:dgamma(0.01, 0.01)这种极小参数在低维模型里通常没问题,但如果观测数很少(比如 n=4,只有 3 个观测),后验可能贴着 0 跑,采样器会抖动。后面排查章会讲怎么判断。
注意:JAGS 里
dnorm的第二个参数是精度不是标准差。把标准差填进去是新手最常见的错误,症状是后验窄得离谱、所有链几乎重合,看着"收敛很好",其实是先验把数据压死了。
4.4 MATLAB 侧采样与后验汇总
模型写完之后,MATLAB 这边就是调接口、设参数、收结果。下面这段是主流程里的采样和汇总部分:
% 采样参数:4 条链,每条 20000 次迭代,前 5000 次预热丢弃 nChains = 4; nBurnin = 5000; nSamples = 20000; % 初值:每条链给一个不同的随机 Dirichlet 起点,帮助诊断收敛 init = cell(1, nChains); for c = 1:nChains init{c}.w = drchrnd(ones(1, jdata.n), 1)'; % 和为 1 的随机向量 init{c}.tauB = 1; init{c}.tauW = 1; end monitors = {'w', 'tauB', 'tauW'}; samples = matjags(jdata, fullfile('model', 'bwm.jags'), init, ... 'nchains', nChains, 'nburnin', nBurnin, 'nsamples', nSamples, ... 'monitorparams', monitors, 'thin', 1, 'dic', true); % 合并链,算后验均值与 95% 可信区间 W = samples.w; % 维度 [迭代数, 准则数, 链数] W = reshape(W, [], jdata.n); % 合并所有链 wMean = mean(W, 1); wCI = prctile(W, [2.5 97.5], 1); % 按后验均值排序,输出带区间的表 [~, order] = sort(wMean, 'descend'); fprintf('%-10s %10s %20s\n', '准则', '后验均值', '95% 可信区间'); for i = order fprintf('%-10s %10.4f [%.4f, %.4f]\n', ... criteria{i}, wMean(i), wCI(1,i), wCI(2,i)); end初值那一段值得单独说。ddirch采样出来的向量天然和为 1,但如果你给初值一个和为 2 的向量,JAGS 会在第一次迭代时报"节点 w 的初始值无效"。所以初始化必须走 Dirichlet 采样或者手工归一化,drchrnd是常见的一个小工具函数,没有的话用-log(rand(...))归一化也能替代。
monitors里我只留了w和两个精度参数。刚上手时容易把所有中间节点都加进去,结果内存爆掉、读回来一堆用不上的东西。建议默认只监控最终关心的量,需要排查再加。
thin设为 1 是因为迭代数不算大,保留全部样本便于算分位数。如果 nSamples 加到十万级,thin设 5 到 10 能省内存,对后验估计几乎没影响。
prctile那行算的是可信区间,注意它和置信区间的语义不同:这里说的是"权重落在这个区间的后验概率是 95%",不是频率派的重复抽样含义。汇报的时候措辞要准确,否则容易被懂统计的人挑刺。
5. 跑通与常见问题排查:环境、参数与五类翻车现场
5.1 环境准备与最小可运行闭环
跑起来需要三样东西:MATLAB(带 Optimization Toolbox,linprog要用)、JAGS 可执行程序、matjags 接口。前两个是外部依赖,第三个是一个 .m 文件集合,放进路径即可。下面这段是环境自检,我一般存成一个env_check.m,换机器先跑它:
function ok = env_check() % 检查三项依赖是否就位,返回是否可继续 ok = true; % 1) Optimization Toolbox 是否可用 if isempty(which('linprog')) warning('缺少 linprog,传统 BWM 检查无法运行'); ok = false; end % 2) matjags 是否在路径上 if isempty(which('matjags')) warning('matjags 不在路径上,检查 src 目录是否 addpath'); ok = false; end % 3) JAGS 可执行文件是否可见(靠系统调用试探) [status, ~] = system('jags --version'); if status ~= 0 warning('系统找不到 jags 命令,检查 PATH'); ok = false; end end三项检查对应三类失败。第一项挂了说明 MATLAB 装的是精简版;第二项挂了是路径问题,which直接告诉你有没有;第三项最隐蔽,因为system走的是操作系统 shell,PATH 配的是系统级环境变量而不是 MATLAB 的path,两者不是一回事。Windows 上装完 JAGS 一般要重启一次 MATLAB 才能读到新的 PATH,这一点很多说明里不写,但确实会卡人。
自检通过之后,最小闭环是:把data/里的样例数据喂给run_bwm.m,看它能否不报错地吐出results/下的后验表和图。第一次跑不要改任何参数,先确认默认路径能通,再动数据。
5.2 采样参数怎么定,别拍脑袋
nChains、nBurnin、nSamples三个参数没有放之四海皆准的值,但有一组经验起点:链数 4,预热不少于 5000,有效样本至少几千。链数少于 3 就没法算 R-hat,等于放弃了收敛诊断,不建议。预热不够的典型症状是某条链从完全不同的位置出发、还没走到后验主体就停了,合并之后分布出现双峰。
判断收敛我一般看两个量:R-hat 和有效样本量。R-hat 接近 1(经验阈值 1.1 以下)说明链之间一致;有效样本量太小说明自相关严重,需要加迭代或者加 thin。这两个量都可以从samples结构里手算,也可以装个诊断小函数。下面是一个够用的 R-hat 计算片段:
function rhat = calc_rhat(X) % X 维度:[迭代数, 参数数, 链数] [m, p, c] = size(X); chainMean = squeeze(mean(X, 1)); % [p, c] chainVar = squeeze(var(X, 0, 1)); % [p, c] W = mean(chainVar, 2); % 链内方差 B = m * var(chainMean, 0, 2); % 链间方差 varPlus = (m - 1) / m * W + B / m; % 后验方差估计 rhat = sqrt(varPlus ./ W); % [p, 1] endW是链内方差的平均,B是链均值的方差乘上迭代数,varPlus把两者加权成总体方差估计。R-hat 大于 1.1 就该回头查模型,别硬着头皮解读结果。
5.3 五类高频翻车现场
下面这几条是我在类似实现上实际遇到或者见别人踩过的,按"现象 → 原因 → 解决"写,碰上了直接对号入座。
第一类:启动就报找不到 jags 命令。现象是 MATLAB 里调matjags直接抛错,提示无法执行外部程序。原因基本是两个:JAGS 没装,或者装了但 bin 目录没进系统 PATH。解决是先单独在终端里跑jags --version确认命令行能用,能用之后重启 MATLAB,让它重新读环境变量。如果终端能用、MATLAB 还是不行,检查是不是在 IDE 里启动的 MATLAB,某些启动方式会继承旧的环境副本。
第二类:初始值无效。现象是采样器在第一轮就报某个节点的初始值不在支撑集内。原因几乎都是权重初值不满足单纯形约束,比如手工写了[0.2 0.2 0.2 0.2 0.2]但 n 是 6,和不为 1;或者写了负数。解决是统一用 Dirichlet 采样生成初值,别手写常量。另外tauB、tauW的初值必须为正,Gamma 先验的支撑集是正半轴。
第三类:后验窄得不正常。现象是所有准则的 95% 可信区间都窄到小数点后三位,看起来"结论非常确定"。原因通常是观测模型里的精度参数被填成了标准差,方差被系统性低估;也可能是tauB的先验设得太强,比如写成了dgamma(100, 1)。解决是回头确认dnorm第二个参数确实是精度,并把 Gamma 先验放弱,让数据主导。
第四类:传统 CR 通过,贝叶斯排序却翻了。现象是前置一致性检查显示 0.08、远低于阈值,但贝叶斯后验给出的排序和linprog的点估计不一致。原因是两套方法优化的目标不同:线性规划最小化最大绝对偏差,贝叶斯建模的是对数尺度的残差,尺度变换会改变"谁离谁更近"。解决不是二选一,而是把两者都作为证据看——如果某个准则在两套方法下的区间重叠严重,就老老实实说明这个位次不确定,别硬排。
第五类:改了准则数量但模型跑不动。现象是数据里加了第 6 个准则,prep_jags_data之后采样器报维度不匹配。原因是模型文件里的循环上界写成了常数,或者idxB、idxW的长度没跟着更新。解决是把所有硬编码的长度换成n或者n-1,并在prep_jags_data里加一条断言:assert(numel(jdata.aB) == jdata.n - 1)。这类错误一旦被抓到,改起来很快,但如果没有断言,它会在采样阶段以莫名其妙的维度报错出现,查起来反而绕。
6. 二次开发:换先验、扩群决策、接自己的业务
拿到这份代码之后,最值得动的三个地方我都试过,按投入产出排一下。
换先验是最轻的改动,只动alpha[]那几行。如果你手上有一批历史评审数据,统计各准则被指认为最优的频率,用频率加一个小常数当 alpha,就是一个经验贝叶斯先验。这样新一次评审的结果会向历史均值收缩,样本少的时候特别管用。下面的写法把先验强度做成可调参数:
% 传入历史最优准则频次,构造经验 Dirichlet 先验 histCount = [12 5 20 8 3]; % 各准则历史被指认为最优的次数 alpha0 = 2; % 收缩强度,越大越依赖历史 alpha = histCount + alpha0; jdata.alpha = alpha / sum(alpha) * numel(alpha); % 保持总强度稳定扩成群决策是这类方法真正的用武之地。多个专家各自给一组aB、aW,不要先平均再建模——平均会抹掉专家之间的分歧,而这部分分歧恰恰是你想知道的。更合适的做法是层次模型:每个专家有自己的权重向量,都来自一个共同的群体分布,MCMC 同时估计个体权重和群体均值。改动量不小,但 JAGS 里加一层循环就能表达,比在 MATLAB 里手工做加权平均诚实得多。
接业务数据管道是工程侧的活。现有实现的输入是 CSV,实际项目里通常从数据库或者接口取。我的习惯是把prep_jags_data的签名改成接收一个 struct 而不是两条向量,读数据的那一层单独抽出去,这样模型代码永远不需要知道数据从哪来。改完之后写一组单元测试,用固定输入对固定输出,验证重构没改变数值结果。
验证方法上,我一般做三件事:一是拿传统 BWM 的linprog结果和后验均值比,差得离谱就说明观测模型有问题;二是用已知权重的模拟数据反向生成aB、aW,看能不能把真值捞回来,这是检验模型设定最直接的办法;三是把后验样本按排序统计,输出"各方案排第一的概率",这个数比均值排序更能说服做决策的人。
有次改完观测分布忘了同步改tau的先验,跑出来的区间宽到所有准则都重叠,差点把结论写成"这几个准则重要性差不多"。从那以后我每次改模型文件,都强制先用模拟数据做一遍真值回收,过了再碰真实数据。希望这些能帮到你,少走一遍我踩过的路。
本文还有配套的精品资源,点击获取