ARTICLE DETAIL

资讯详情

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

AHP城市竞争力评价复现:四层指标、权重计算与一致性检验

AHP城市竞争力评价复现:四层指标、权重计算与一致性检验

简介:这份文档围绕层次分析法在城市竞争力评价中的应用展开,面向经济学、统计学、城市规划与系统工程等专业的学生、研究者及政策分析人员,帮助解决多指标、多维度城市竞争力难以量化比较的问题。内容从AHP基本思想入手,讲解目标层、准则层、要素层与指标层的四层结构,说明如何以1—9标度构造判断矩阵、计算特征向量获得权重,并通过一致性比率CR小于0.1检验判断可靠性;同时结合中国城市竞争力评价实例,展示准则层、要素层和指标层权重的逐级赋值与综合评分思路。压缩包内含1个doc文件,大小约832KB,适合作为课程报告、方法学习或评价模型搭建的参考材料。已有140人学习,读者可从中获取AHP建模流程、指标体系组织方式、判断矩阵与一致性检验的完整示例,以及将权重结果用于城市竞争力分析的基本路径。

1. 拿到一份 AHP 城市竞争力评价报告,先别翻结论页

那份课程报告躺在硬盘里很久了,封面、目录、四章正文,最后停在"由于数据量庞大,暂不计算城市排名"。多数人扫一眼就走,我拆的时候先翻附件——附件里那张 AHP 赋权表才是整份资源唯一不可替代的东西。层次分析法用在城市竞争力评价上,公式在教科书里到处都是,真正稀缺的是有人把 71 项候选指标压成目标层、准则层、要素层、指标层四层结构,又逐层把判断矩阵填完、把一致性检验跑过、把权重算到小数点后四位。这套链路对做城市评估、园区打分、区域对比的人来说可以直接照搬;对只是想学 AHP 的人,它是一个比课本例子大一号、又没大到做不完的完整样本。下面按我复现的顺序拆:结构怎么搭、矩阵怎么填、权重怎么解、CR 卡住怎么办、哪几个细节最容易把权重算废。

2. 四层指标体系怎么搭:从 71 项候选指标到可计算的层次结构

指标体系这一步决定后面所有计算有没有意义。AHP 不负责帮你选指标,它只负责在你选完之后给出权重,所以结构搭歪了,后面矩阵填得再漂亮也是白算。报告里给的 71 项候选指标是原始素材,真正要落地的是把它们分桶、定层、挂到正确的父节点下面。

2.1 四个层级各自管什么,以及为什么不能只做三层

目标层只有一个元素,就是最终那个标量——城市综合竞争力指数,所有计算最后都收敛到它身上。准则层是分解总目标的第一刀,报告切成动力、阻力、相互影响、环境水平四块。要素层是对准则的再解释,比如"动力"下面拆成经济实力、政府作用、消费水平、人力资源、科技实力五项。指标层是可以直接查到数的基层条目,人均 GDP、财政支出占比、每万人专利数这类。

层级数量的选择有个很实在的算术约束。一个 n 阶判断矩阵需要做 n(n−1)/2 次两两比较。如果把 71 项指标直接挂在目标层下面做一个扁平结构,比较次数是 71×70/2 = 2485 次,没有任何专家团队能保质保量填完,而且一致性必然崩盘。做成四层之后,最大的矩阵是动力层下的 5×5,只有 10 次比较;准则层的 4×4 是 6 次;要素层的 2×2 和 3×3 更少。全表加起来一百多次比较,一个下午能开完一场专家会。

这就是分层的第一性理由:不是为了让报告好看,是为了把人类判断力的规模控制在可执行区间内。每层元素个数建议压在 3 到 7 之间,超过 9 就超出 1-9 标度的表达能力,那时候该做的不是继续填矩阵,而是回去重新分桶。

提示:判断矩阵的阶数上限不是软件限制,是 1-9 标度的语义边界。标度只区分到"极端重要",一旦元素超过 9 个,专家在"明显重要"和"极端重要"之间就分不出层次了。

2.2 动力与阻力分开:正负向指标不能混在一个准则里

报告把"贫富分化"单独拎出来放进阻力层,这个处理值得说一句。城市竞争力不是一堆指标的简单加总,有些指标越高越好,有些越高越糟。AHP 本身只输出权重,不识别方向,方向问题要到无量纲化阶段才处理。如果一开始就把贫富分化塞进"经济实力"里,权重会把它当成一个正向贡献项,后面再怎么调无量纲化公式都掰不回来。

分开之后有两个好处。第一,权重可解释——阻力层的权重直接回答"负面因素在这个评价体系里占多大分量",这个数在写政策建议时很好用。第二,敏感性分析时能量化杠杆,把阻力层权重从 0.0893 调到 0.15,看排名怎么动,这比笼统说"负面因素也重要"要有说服力。

代价也要说清楚。阻力层下面只挂了贫富分化一个要素,单元素层的判断矩阵是 1×1,RI 等于 0,一致性检验退化成形式检查,算出来的 CR 没有意义。这种情况正确做法是跳过检验并在报告里注明,而不是硬凑一个 0。报告权重表里贫富分化那一格空着,多半就是卡在这儿——单元素层算不出权重,填表的人索性留白,结果组合权重链断了一节。这个坑在第 5 章展开。

2.3 用一份嵌套字典把层次结构固化下来

指标体系一旦定稿,第一件事是把它写成代码里的数据结构,而不是继续留在 Word 表格里。后面所有权重计算、组合权重、排名输出都从这里取数,改一处全链路跟着动。

# 层次结构骨架:只保留层级关系与节点名,指标数据另外用 CSV 挂 # 指标层条目在实际项目里从附件表导入,这里用占位名演示结构 HIERARCHY = { "城市综合竞争力": { # 目标层,唯一根节点 "动力": { # 准则层 "经济实力": ["地区生产总值", "人均地区生产总值", "固定资产投资额"], "政府作用": ["财政支出占GDP比重", "行政审批事项数"], "消费水平": ["社会消费品零售总额", "居民人均消费支出"], "人力资源": ["每万人高等教育在校生数", "从业人员占比"], "科技实力": ["R&D经费投入强度", "每万人发明专利授权量"], }, "阻力": { # 负向准则,单独成层 "贫富分化": ["城乡收入比", "基尼系数估算值"], }, "相互影响": { "对外开放": ["实际利用外资额", "进出口总额占GDP比重"], "依存度": ["外贸依存度", "外资企业产值占比"], }, "环境水平": { "基础设施": ["人均道路面积", "万人拥有公共交通车辆"], "环境水平": ["建成区绿化覆盖率", "单位GDP能耗"], }, } } def flatten_leaves(node, path=()): """递归展开到指标层,返回 [(完整路径, 指标名), ...]""" out = [] for key, val in node.items(): if isinstance(val, dict): out.extend(flatten_leaves(val, path + (key,))) elif isinstance(val, list): for item in val: out.append((path + (key,), item)) return out leaves = flatten_leaves(HIERARCHY) print(len(leaves)) # 指标层条目总数 print(leaves[0]) # (('动力', '经济实力'), '地区生产总值')

HIERARCHY里字典的深度就是层级深度,键名是节点名,列表是叶子。flatten_leaves用递归把所有叶子拉平,返回的元组前半段是完整的父节点路径,后半段是指标名——这个路径在计算组合权重时直接当键用,避免后面靠字符串拼接出错。参数path是递归累加器,初始为空元组,不需要手动传。

把结构写进代码的另一个好处是能立刻做完整性检查:遍历一遍,凡是空列表的节点说明指标没挂满,凡是有字典又有列表的节点说明层级混了。这类错误在 Word 表格里肉眼看不出来,在代码里一行断言就能拦住。

3. 判断矩阵与权重求解:1-9 标度、算术平均法与特征向量

层次结构定了,接下来是整份资源里最花时间的部分:把每一层元素两两比较,填成判断矩阵,再解出权重向量。报告里准则层、要素层、指标层一共十几张矩阵,全都用手工填表加算术平均法求解。

3.1 1-9 标度到底在标什么

判断矩阵 A 是个正互反矩阵,元素 a_ij 表示"对上层某个目标而言,元素 i 相对元素 j 的重要程度",满足 a_ij > 0、a_ji = 1/a_ij、a_ii = 1。标度取值限制在 1 到 9 及其倒数。

标度含义反向取值
1i 与 j 同等重要1
3i 比 j 稍微重要1/3
5i 比 j 明显重要1/5
7i 比 j 强烈重要1/7
9i 比 j 极端重要1/9
2,4,6,8上述相邻判断的中间值1/2,1/4,1/6,1/8

实操上别直接问专家"经济实力比科技实力重要几倍",人对倍数不敏感,问出来全是拍脑袋。我一般改成两段式提问:先问"这两个哪个更重要",再问"是稍微重要、明显重要还是强烈重要",最后映射到 3、5、7。多轮下来同一批专家的结果稳定性明显好于直接报倍数。

多个专家打分时,聚合方式要用几何平均而不是算术平均。原因是标度是比率尺度,1/9 和 9 表达的是同一对关系的两个方向,算术平均会得到 (9 + 1/9)/2 ≈ 4.5,把"极端重要"稀释成"接近明显重要",语义直接崩掉。几何平均 √(9 × 1/9) = 1,正好还原成"同等重要",这才是比率尺度该有的聚合方式。

注意:几何平均只对同一位置的多个专家值做聚合,聚合完仍然要检查互反性。工程上更稳的做法是只让专家填上三角,下三角由程序取倒数,从源头消灭手填不一致。

3.2 算术平均法:列归一化、行求和、归一化

报告用的是算术平均法,也叫和积法。三步:判断矩阵按列归一化,归一化后按行求平均得到权重向量 w,再用 A w 和 w 的逐元素比值求最大特征值 λ_max。

import numpy as np RI_TABLE = {1: 0.00, 2: 0.00, 3: 0.58, 4: 0.90, 5: 1.12, 6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45} def ahp_weights(A): """算术平均法求权重、最大特征值、CI、CR""" A = np.asarray(A, dtype=float) n = A.shape[0] assert np.allclose(A * A.T, np.ones((n, n))), "矩阵不是正互反矩阵" B = A / A.sum(axis=0) # 1) 按列归一化 w = B.mean(axis=1) # 2) 行平均得到权重 Aw = A @ w lam_max = float((Aw / w).mean()) # 3) 最大特征值 CI = (lam_max - n) / (n - 1) if n > 1 else 0.0 RI = RI_TABLE.get(n, 1.49) CR = CI / RI if RI > 0 else 0.0 return w, lam_max, CI, CR

A.sum(axis=0)是列求和,A / ...广播成逐元素除法,得到的 B 每列和为 1。B.mean(axis=1)按行取平均,正好是每个元素的权重。Aw / w逐元素相除后取均值,就是 λ_max 的近似——完全一致时这个比值恒等于 n。

assert那一行是必须的。手填矩阵最常见的错误是 a_ij 填了 3,a_ji 忘了填 1/3 而填了 0.3,这种矩阵算出来的权重会出现负值或量级错乱,不加断言很难第一时间发现。RI_TABLE.get(n, 1.49)在阶数超过 9 时回退到 1.49,这是个近似值,实际项目里遇到 10 阶以上应该回去重新分层。

3.3 幂法交叉验证:两套算法结果对不上就是有 bug

算术平均法给的是 λ_max 的近似,幂法给的是精确迭代解。两套方法算同一张矩阵,如果 λ_max 差超过 1e-6,说明中间某一步实现错了,而不是"方法本身有误差"。

def power_method(A, eps=1e-10, max_iter=2000): """幂法迭代求主特征向量和最大特征值""" A = np.asarray(A, dtype=float) n = A.shape[0] v = np.ones(n) / n # 正规化初始向量 for _ in range(max_iter): u = A @ v v_new = u / u.sum() # 每步归一化,防止数值溢出 if np.abs(v_new - v).max() < eps: v = v_new break v = v_new lam = float((A @ v / v).mean()) # 收敛后用瑞利商近似 return v, lam A_demo = np.array([ [1, 3, 5, 7], [1/3, 1, 3, 5], [1/5, 1/3, 1, 3], [1/7, 1/5, 1/3, 1], ], dtype=float) w1, lam1, ci, cr = ahp_weights(A_demo) w2, lam2 = power_method(A_demo) print(w1.round(4), lam1.round(6), round(cr, 4)) print(w2.round(4), lam2.round(6)) print("两法偏差:", abs(lam1 - lam2))

eps控制收敛精度,1e-10 对 9 阶以内的矩阵通常几十次迭代就够。max_iter是保险丝,正常不会触发。归一化那一步不能省——不归一化的话 v 的模会随迭代指数增长,十几轮就溢出成 inf。

这套交叉验证我在每个项目里都会跑一遍。血泪经验是:算术平均法的 bug 往往不体现在权重上(权重看起来还是归一化的、还是正数),只体现在 λ_max 上,而 λ_max 又直接决定 CR,最后表现为"明明矩阵填得挺合理,CR 死活过不了 0.1"。先排除实现错误,再去改矩阵,顺序不能反。

3.4 组合权重:从单层排序推到总目标排序

单层排序只回答"在父节点下各元素占多少",最终要的是每个要素对总目标的贡献,这就要逐层相乘。报告的准则层权重是动力 0.4883、阻力 0.0893、相互影响 0.2328、环境水平 0.1896;动力层下经济实力 0.4162、政府作用 0.2618、消费水平 0.0624、人力资源 0.0986、科技实力 0.1611。

def combine_weights(layer_weights, parent_weight=1.0, prefix=""): """递归计算组合权重,layer_weights 是嵌套 dict,叶子为 float""" result = {} for name, val in layer_weights.items(): full = f"{prefix}/{name}" if prefix else name if isinstance(val, dict): result.update(combine_weights(val, parent_weight * 1.0, full)) else: result[full] = round(parent_weight * val, 6) return result # 以准则层 -> 要素层两级为例 criteria = {"动力": 0.4883, "阻力": 0.0893, "相互影响": 0.2328, "环境水平": 0.1896} factors = { "动力": {"经济实力": 0.4162, "政府作用": 0.2618, "消费水平": 0.0624, "人力资源": 0.0986, "科技实力": 0.1611}, "相互影响": {"对外开放": 0.6667, "依存度": 0.3333}, "环境水平": {"基础设施": 0.5, "环境水平": 0.5}, } combined = {} for c, wc in criteria.items(): if c in factors: for f, wf in factors[c].items(): combined[f"{c}/{f}"] = round(wc * wf, 6) else: combined[c] = wc # 阻力层下无要素细分,直接挂准则权重 print(combined) print("合计:", round(sum(combined.values()), 6))

跑出来的要素层组合权重是:经济实力 0.20323、政府作用 0.127837、消费水平 0.03047、人力资源 0.048146、科技实力 0.078665、对外开放 0.155194、依存度 0.077596、基础设施 0.0948、环境水平 0.0948、阻力 0.0893,合计 1.000038。

最后那个 0.000038 的小尾巴不是打印误差,是上游权重只保留四位小数导致的累积舍入。它在单个城市打分时影响可以忽略,但在做城市间排名、尤其是几个城市分数咬得很近的时候,这 3.8e-5 有可能改变名次。组合权重算完必须做一次归一化除以总和,这是流程里最容易被跳过的一步。

4. 一致性检验:CR 逼近 0.1 时的三条修正路径

判断矩阵是人填的,人对多因素相对重要性的判断天然不自洽——觉得 A 比 B 重要、B 比 C 重要,但填 A 对 C 的时候又手抖写了"稍微重要",三元关系就崩了。一致性检验的作用是把这种不自洽量化出来,超过阈值就退回去改,不带着错误往下算。

4.1 CI、RI、CR 三个量的计算与查表

完全一致的正互反矩阵满足 a_ik = a_ij · a_jk,此时 λ_max 恰好等于 n。实际矩阵 λ_max 总是大于 n,超出的部分就是不一致的量。CI 定义为 (λ_max − n)/(n − 1),再用同阶随机矩阵的平均 CI 做归一化,得到 CR = CI / RI。

阶数 n123456789
RI000.580.901.121.241.321.411.45

判定规则是 CR < 0.1 认为一致性可接受。n 等于 1 或 2 时 RI 为 0,CR 无定义,这两种情况直接跳过检验——1 阶矩阵恒一致,2 阶正互反矩阵也恒一致,检查没有信息量。代码里常见的翻车是没判 RI > 0 就做除法,n=2 时直接抛 ZeroDivisionError 或者返回 nan,然后这个 nan 一路传到权重表里,最后整张表全是 nan。

报告里准则层那张 4×4 矩阵算出来 λ_max 略大于 4,CI 是个小正数,除以 RI = 0.90 之后 CR 落在 0.1 以内,判定为"满意的一致性"。原文把这一步写得很短,实际操作时这一步最花时间——报告里那几张矩阵之所以能一次过,是因为填表的人在正式定稿前已经反复调过。

4.2 定位最矛盾的三元组:别整张表重打

CR 超了 0.1,最常见的错误反应是把整张矩阵推倒重填。这么做的问题是会把专家的真实偏好一起抹掉——原本经济实力确实比消费水平重要得多,重填之后这个判断被稀释,权重的业务含义就没了。

正确做法是定位到具体哪几个元素在打架。不一致的数学来源就是三元组违反传递性,也就是 a_ik 和 a_ij · a_jk 偏离最大的那一组。

def worst_triplet(A): """找出违反传递性最严重的 (i, j, k) 三元组""" A = np.asarray(A, float) n = A.shape[0] best = None for i in range(n): for j in range(n): for k in range(n): if len({i, j, k}) < 3: continue pred = A[i, j] * A[j, k] # 传递性预测值 err = abs(pred - A[i, k]) / A[i, k] if best is None or err > best[0]: best = (err, i, j, k, pred, A[i, k]) return best err, i, j, k, pred, actual = worst_triplet(A_demo) print(f"最不一致三元组 ({i},{j},{k}) 误差 {err:.3f}," f"传递性预测 {pred:.2f},实际填写 {actual:.2f}")

pred是沿着 i→j→k 两步走出来的间接判断,actual是直接填的 i→k。两者比值偏离 1 越远,这一组越该改。改的时候只动 a_ik 一个元素,同步把 a_ki 取倒数,重算 CR,再找下一组,循环到 CR 达标为止。

改哪个方向有讲究。如果 pred 远大于 actual,说明间接路径认为 i 比 k 重要得多,而直接填的比较保守,通常是把 a_ik 往上调,调到接近 pred 的平方根附近比较稳——直接调到 pred 往往矫枉过正,把另一组三元组又搞崩了。我一般的习惯是一次只调一档标度(比如从 3 调到 5),然后重算,宁可多迭代两轮。

提示:调整前先记下原始的 a_ik 和调整理由。AHP 的一致性修正是有主观成分的,报告里如果只写"调整后 CR < 0.1"而不写调了哪个元素、为什么调,审阅的人没法判断权重还是不是专家原本的意思。

4.3 把检验做成流水线里的断言

十几张矩阵靠人工一张张检查 CR,出错是迟早的事。最省心的办法是写成一个装饰器或者断言函数,任何一张矩阵进来先过一遍,不达标直接抛异常并打印三元组。

def check_consistency(A, name="matrix", threshold=0.1): """一致性守卫:CR 超阈值直接抛异常,附带定位信息""" w, lam, ci, cr = ahp_weights(A) if A.shape[0] <= 2: return w, cr # 1、2 阶矩阵无需检验 if cr >= threshold: err, i, j, k, pred, actual = worst_triplet(A) raise ValueError( f"[{name}] CR={cr:.4f} 超阈值," f"最不一致三元组索引 ({i},{j},{k}),预测 {pred:.2f} 实际 {actual:.2f}" ) return w, cr # 批量校验所有层级的矩阵 matrices = {"准则层": A_demo} for name, mat in matrices.items(): w, cr = check_consistency(mat, name) print(f"{name}: CR={cr:.4f} 通过,权重 {w.round(4)}")

把threshold做成参数是有必要的,探索阶段可以放宽到 0.15 先把链路跑通,定稿再收紧到 0.1。异常信息里带索引和预测值,是因为改矩阵的时候人需要知道去第几行第几列动刀,光看一个 CR 数字没有任何指导意义。

这套断言在批量处理几十张矩阵时价值最大。曾经有一次跑一个 12 层的评估体系,前 11 张全过,最后一张 CR = 0.31,日志里直接打出三元组索引,五分钟定位到是某个专家把 1/5 填成了 0.2 的倒数关系搞反。没有这个日志,靠肉眼在一张 5×5 表里找错,能找一下午。

5. 避坑:五个让权重白算的细节

这一章是踩过的坑汇总,每一条都真实发生过,而且都不是公式错,是流程和数据结构上的疏漏。公式错反而好查,因为结果会明显离谱;这几类错误算出来的数看着都挺正常,直到排名出来才发现不对劲。

5.1 权重表里有一格是空的,组合权重链断在中间

现象:逐层乘到要素层时,某个分支算不出数,或者算出来之后所有组合权重加起来只有 0.91 左右,怎么都对不上 1。

原因:单元素层的判断矩阵是 1×1,RI = 0,一致性检验没法做,填表的人索性把这一格留空。报告表里"贫富分化"下面没有数值,就是这种情况。空值在乘法里会变成 NaN 或者被当成 0,整条链从这儿断掉。

解决:在代码里显式处理单元素层,元素权重直接赋 1.0,不参与矩阵计算,也不做一致性检验。同时加一条断言:所有叶子权重非 None 且组合权重总和与 1 的偏差小于 1e-4。这条断言我后来加进了所有 AHP 脚本的模板里,跑一次只要几毫秒,能省掉半天的排查。

5.2 RI 表按错误的阶数索引,CR 系统性偏小

现象:所有矩阵的 CR 都很漂亮,普遍在 0.02 以下,但把某个明显矛盾的矩阵故意塞进去,CR 还是只有 0.05。

原因:查 RI 的时候用了 n−1 而不是 n 去索引。3 阶矩阵查了 2 阶的 RI = 0,除法跳过直接返回 0;4 阶矩阵查了 3 阶的 0.58 而不是 0.90,CR 被系统性放大约 1.55 倍的分母缩小,判定变得过于宽松。

解决:RI 表用字典按阶数直接索引,不要用列表加偏移量。字典的键就是 n,取不到时显式报错而不是回退默认值。另外加一个自检用例:构造一个已知 CR 的矩阵(比如故意填一组强不一致的 4 阶矩阵),验证函数返回的 CR 落在预期区间,这个用例每次改代码都跑一遍。

5.3 手填矩阵没保证互反,权重出现负值

现象:权重向量里出现负数,或者某个元素权重超过 0.9,其余元素加起来不到 0.1。

原因:手工填表时只填了上三角,下三角凭印象填,出现 a_ij = 3 而 a_ji = 0.5 这种组合。矩阵不再是正互反矩阵,列归一化之后行平均不再具有权重含义,算术平均法的收敛前提被破坏。

解决:数据录入环节只让填上三角,下三角在代码里统一A = np.triu(A) + np.triu(A, 1).T ** -1生成。如果数据来自外部 Excel,入口处加一条np.allclose(A * A.T, 1)断言,不通过直接拒绝,不要试图"修正"——互反性错误的矩阵没有唯一正确答案,修出来的权重是不可信的。

5.4 多专家打分用算术平均聚合,语义被稀释

现象:五位专家对同一对元素的判断分别是 9、1/9、7、1/7、1,聚合后得到 3.7 左右,最终权重里这一项既不算特别重要也不算无关紧要,怎么看都不对。

原因:1-9 标度是比率尺度,9 和 1/9 是同一关系的正反表达,算术平均 (9 + 1/9)/2 ≈ 4.5 破坏了这种对称性。比率尺度的中心趋势应该用几何平均。

解决:同一位置的多专家值取几何平均np.exp(np.log(vals).mean()),聚合完再检查互反性。如果专家意见分歧极大(比如有人给 9 有人给 1/9),不要强行聚合,先开会吵清楚——这种分歧往往说明指标定义本身有歧义,不是数学能解决的。

5.5 无量纲化方向搞反,负向指标越差得分越高

现象:某城市贫富差距越大,综合得分反而越高,排名靠前。检查权重没问题,矩阵也没问题。

原因:无量纲化统一用了极差法 (x − min)/(max − min),这个公式假定所有指标都是正向的——数值越大越好。贫富分化、单位 GDP 能耗、城乡收入比这类负向指标套进去,方向完全反了。

解决:在指标元数据里加一个 direction 字段,正向用 (x − min)/(max − min),负向用 (max − x)/(max − min),中性指标用 1 − |x − target|/max|x − target|。这一步必须在权重计算之前就在数据字典里定好,不能等到算综合指数时临时判断——71 项指标靠人记方向,必错。

6. 从权重表到排名:一次敏感性验证比十页结论更有用

权重算完只是半成品,真正决定这份评价能不能拿去用的,是它对判断扰动的鲁棒性。我现在的习惯是,任何一套 AHP 权重在出排名之前,先做一轮敏感性扫描:把最关键的那几个判断矩阵元素按 ±1 档标度扰动,重算权重、重算排名,看名次会不会翻。

import numpy as np from scipy.stats import spearmanr def perturb_and_rank(A, scores, delta=1.5, idx=(0, 1)): """把 A[idx] 乘 delta,同步调整互反位,比较排名变化""" A2 = A.copy() i, j = idx A2[i, j] = np.clip(A2[i, j] * delta, 1/9, 9) A2[j, i] = 1 / A2[i, j] w1, *_ = ahp_weights(A) w2, *_ = ahp_weights(A2) r1 = np.argsort(np.argsort(-(scores @ w1))) r2 = np.argsort(np.argsort(-(scores @ w2))) return spearmanr(r1, r2).statistic, w1, w2 rho, w1, w2 = perturb_and_rank(A_demo, np.random.rand(5, 4), delta=1.5) print(f"排名秩相关 {rho:.4f}")

scores是城市 × 指标的标准化数据矩阵,delta控制扰动幅度,1.5 大致相当于在 1-9 标度上动一档。秩相关系数低于 0.9 就说明这套权重不稳,排名前五的城市里有谁上谁下全看专家的临场手感,这种结果不适合直接写进报告。

改一格判断矩阵就翻盘的指标,通常只有两三个。找出来之后有两个处理方向:要么在报告里显式标注"该结论对 X 指标权重敏感",要么回去重新审视这一格的打分依据,看是不是专家会开得太快。我一般倾向前者——敏感性本身是信息,藏起来不如写出来。

从那以后我每次做完 AHP 赋权,都会强制走一遍这个流程:组合权重归一化、单元素层显式赋值、CR 断言、负向指标方向校验、最后跑一次 ±1 档扰动看排名。五步加起来不到二十分钟,但能拦掉我见过的绝大多数翻车。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表