☰
AHP层次分析法实战:判断矩阵、CR检验与城市竞争力评价
2026/10/28 10:34:18 网站建设 项目流程

简介:面向经济管理、统计学、城市规划及系统工程等专业学习者与政策分析人员的专题文档,围绕城市竞争力这一多因素综合评价问题,介绍如何用层次分析法(AHP)建立量化评价模型。内容从目标层、准则层、要素层和指标层四级结构展开,说明判断矩阵构造、1—9标度赋值、层次单排序与特征向量权重计算,并强调一致性比率小于0.1的检验要求;结合71项指标及实例,演示准则层、要素层、指标层权重求解和综合竞争力水平测算。整包含1个doc文档,约832KB,适合作为课程报告、论文写作或评价指标体系搭建的参考底稿。已有139人学习,读者可据此理解AHP从指标筛选、专家打分到权重合成与结果解释的完整流程,也可对照实例复现计算步骤并迁移到区域或城市比较研究。

1. 一份把 AHP 讲透的课程报告:从判断矩阵到城市竞争力排名

第一次看到这份《层次分析法在城市竞争力评价中的应用》的课程报告,我先直到下面几个具体词才明白它到底适合谁:判断矩阵、1-9 标度、特征向量、一致性比率 CR、71 项指标、四层结构。它面向的就是那类“要用 AHP 交一份能跑通的结果、却卡在权重算不对”的场景,并非泛泛介绍“AHP 是什么”的科普文章。

它是经济预测与决策课程的学习报告,正文走的是“先讲 AHP 基本思想,再搭四层指标体系,最后逐层赋权、列出计量模型”的完整路线。价值不在结论(原作者自己说数据量太大,没算出最终总指数),而在中间那套可复用的流程骨架:怎么把 71 项指标塞进目标层、准则层、要素层、指标层,怎么用专家两两比较构造判断矩阵,怎么用算术平均法求最大特征值与特征向量,怎么做一致性检验并把不合格的矩阵调回去。适合做综合评价、绩效打分、方案选型这类多准则决策的人,尤其是手里只有专家意见、缺乏干净数据的场景。

2. AHP 四层结构与 1-9 标度:先把权重这块地基夯实

这份报告最值得借鉴的地方,在于它把“模糊的多因素比较”拆成了机器能算的数值。很多人做综合评价翻车,是因为一开始指标体系就糊成一团;报告偏偏在结构上做得很扎实。

2.1 四层结构:目标、准则、要素、指标各管什么

报告把城市竞争力评价拆成四层,这个分层逻辑放到任何综合评价场景都成立:

层级作用报告中的实例落地时的判断标准
目标层最终要求解的问题中国城市综合竞争力全篇只能有一个,写成一句话
准则层分解总目标遵循的维度动力 0.4883、阻力 0.08933~7 个,太多则判断矩阵难填
要素层把准则再细化经济实力 0.4162、政府作用 0.2618每个准则下 2~5 个
指标层可采集、可量化的一层71 项统计年鉴指标必须能拿到数,否则整条链断

这个结构里有个容易被忽略的设计:报告把准则层分成“动力”和“阻力”两类。动力是加分项(经济实力、科技实力、对外开放),阻力是减分项(贫富分化)。这在 AHP 里属于把正负向指标分开建模的常见做法——正向指标越大越好,负向指标越大越差,后面无量纲化时处理方式不同。我一般会建议在结构表里直接把方向标出来,否则做到无量纲化那一步才发现某个指标是逆向的,整张表都得回头改。

要素层到指标层的映射也要能对上。报告表 1 里“经济实力”下面挂着政府作用、消费水平、人力资源等要素,这些要素再往下才是具体统计指标。层级之间必须满足“下层元素只隶属一个上层元素”,如果出现一个指标同时挂在两个要素下,判断矩阵就没法构造,权重会重复计算。

2.2 1-9 标度到底怎么打分

判断矩阵的元素取 1、2、…、9 及其倒数,含义是两两比较的相对重要性:

标度含义
1两个元素同等重要
3前者比后者稍重要
5前者比后者明显重要
7前者比后者强烈重要
9前者比后者极端重要
2、4、6、8上述相邻判断的中间值

实操里最容易出问题的是标度用得太随意。常见做法是让专家只回答“谁更重要、重要到什么程度”,把 1-9 的语义做成卡片或问卷,避免专家直接拍数字。报告采用的就是专家评审法:对同一层次的 n 个因素两两比较,得到 n×n 的判断矩阵 A,其中 a_ij 表示因素 i 对因素 j 的相对重要性,且满足 a_ji = 1/a_ij,对角线为 1。

这一步有个隐性约束:判断矩阵是正互反矩阵,填的时候只需要填上三角,下三角自动取倒数。手工填表时如果上下三角都填,十有八九会出现 a_ij × a_ji ≠ 1 的低级错误,后面一致性检验会因为这种录入错误被误判成“专家判断矛盾”。

2.3 用 NumPy 从判断矩阵算出权重

报告正文用的是算术平均法(列和归一化),并给出了幂法迭代求特征向量的步骤。手工算 5 阶以上矩阵基本不现实,我一般直接落到代码里,同时算算术平均法和特征向量法,两者结果接近就说明矩阵没有大问题。

import numpy as np # 准则层判断矩阵(示例:动力/阻力下的 5 个准则两两比较) # 注意:只需填上三角,下三角取倒数,对角线为 1 A = np.array([ [1, 3, 5, 7, 9], [1/3, 1, 3, 5, 7], [1/5, 1/3, 1, 3, 5], [1/7, 1/5, 1/3, 1, 3], [1/9, 1/7, 1/5, 1/3, 1] ], dtype=float) n = A.shape[0] # 方法一:算术平均法(列归一化后按行求均值) col_sum = A.sum(axis=0) # 每列之和 A_norm = A / col_sum # 列归一化 w_arithmetic = A_norm.mean(axis=1) # 按行平均,得到权重 # 方法二:特征向量法 eigvals, eigvecs = np.linalg.eig(A) max_idx = np.argmax(eigvals.real) lambda_max = eigvals[max_idx].real w_eigen = eigvecs[:, max_idx].real w_eigen = w_eigen / w_eigen.sum() # 归一化 # 一致性检验 CI = (lambda_max - n) / (n - 1) RI_TABLE = {1: 0, 2: 0, 3: 0.58, 4: 0.90, 5: 1.12, 6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45} CR = CI / RI_TABLE[n] if RI_TABLE[n] > 0 else 0 print("算术平均法权重:", np.round(w_arithmetic, 4)) print("特征向量法权重:", np.round(w_eigen, 4)) print(f"lambda_max={lambda_max:.4f}, CI={CI:.4f}, CR={CR:.4f}") print("一致性是否可接受:", "是" if CR < 0.1 else "否,需要调整矩阵")

代码里几个点值得说清。col_sum是列和,A / col_sum完成列归一化,这一步对应报告里的“列和归一化”思路;.mean(axis=1)按行求均值得到权重向量,这就是算术平均法的全部。特征向量法的关键是np.argmax(eigvals.real),因为 NumPy 对实矩阵也可能返回复数特征值,取实部再取最大,避免复数比较报错。CR 判断用的RI_TABLE是随机一致性指标,阶数 1 和 2 时 RI 为 0,此时矩阵天然完全一致,代码里做了除零保护。

跑出来两组权重接近,且 CR 小于 0.1,这个判断矩阵才算能用。如果两组权重差得远,往往说明矩阵内部矛盾较大,先别急着调 CR,回头检查录入。

提示:n=2 时 CR 恒为 0,因为二阶正互反矩阵必然完全一致。如果你看到某份材料对 2 阶矩阵报出非零 CR,那是计算方式有问题,不是矩阵有问题。

3. 一致性检验:判断矩阵不合格怎么改回去

报告里三层判断矩阵都出现了“不完全一致”的表述,然后靠一致性检验过关。这一段是整个报告最有工程价值的部分,也是新手最容易糊弄过去的地方。

3.1 CI 与 CR 的计算链路

一致性检验的设计目标是回答一个问题:专家的两两比较是否存在逻辑矛盾。比如 A 比 B 重要、B 比 C 重要,那 A 比 C 的重要性理论上应该被前两条蕴含,如果专家填了 C 比 A 重要,就矛盾了。AHP 用最大特征值偏离 n 的程度来量化这种矛盾:

  • 完全一致时,λ_max = n,CI = (λ_max − n)/(n − 1) = 0;
  • 越不一致,λ_max 偏离 n 越远,CI 越大;
  • CR = CI / RI,RI 是随机生成的同阶正互反矩阵一致性指标的平均值,相当于“随机乱填”的基准线;
  • 通常以 CR < 0.1 作为满意一致性的门槛。

报告里给出的判断是“CR 小于 0.1 则具有满意的一致性,否则需要重新审查并调整判断矩阵赋值”。这条门槛是经验值,不是定理,n 越大越难满足,实际做 7 阶以上矩阵时 CR 卡在 0.1 边缘很正常,别为了压 CR 把专家的真实判断改得面目全非。

3.2 一个能落地的调矩阵流程

矩阵不合格时的调整不能靠乱试。我一般按这个顺序走:

第一步,找出矛盾最集中的元素。计算一致性向量,看哪个元素的偏差最大。简化做法是:把每列归一化后,检查同一行内各元素的离散程度,离群的那个就是嫌疑项。

第二步,优先修“专家最不确定”的那几个格子。通常是标度 5、7、9 这种强判断,改成 3 或 4 往往就能把 CR 拉回来,而不是动 1、2 这种弱判断。

第三步,小步调整,每次只改一对,改完立刻重算 CR,不要一次改五六个格子,否则你永远不知道是哪一下起的作用。

def find_worst_pair(A, w): """定位对一致性破坏最大的元素:比较实际 a_ij 与理论 w_i/w_j 的偏离""" n = A.shape[0] worst = (0, 0) max_dev = 0 for i in range(n): for j in range(n): if i == j: continue ideal = w[i] / w[j] # 理论上应有的比值 dev = abs(np.log(A[i, j] / ideal)) # 取对数偏差,避免量级放大 if dev > max_dev: max_dev = dev worst = (i, j) return worst, max_dev worst, dev = find_worst_pair(A, w_arithmetic) print(f"偏离最大的元素位置: {worst}, 对数偏差: {dev:.4f}") print(f"当前值: {A[worst]}, 建议向 {w_arithmetic[worst[0]]/w_arithmetic[worst[1]]:.3f} 附近调整")

这段代码解决的是“往哪个方向调”的问题。思路是把实际填的 a_ij 与权重推出的理想比值 w_i/w_j 做比较,取对数偏差是因为比值本身就是对数尺度,直接做差会让大数值元素主导结果,导致总是报高标度的那几格。返回值告诉你是哪一对元素矛盾最重,以及建议值大致在哪个区间,你按这个提示改一档标度再重算,通常两三轮就能收敛。

3.3 逐层权重怎么合成到总目标

报告表 2 给出的不是单一权重,而是“相对于其上层”的权重。要得到指标层对总目标的组合权重,必须做层次总排序:把每一层的权重按隶属关系连乘再求和。

以报告的数据为例,准则层“动力”权重 0.4883,其下要素层“经济实力”权重 0.4162,那么经济实力对总目标的组合权重就是 0.4883 × 0.4162 ≈ 0.2032。指标层再往下乘一层,最终得到每个指标在总目标里的实际贡献。

def combine_weights(hierarchy): """ hierarchy: 嵌套字典,形如 {"目标": {"准则A": {"权重": 0.5, "子": {"要素1": {"权重": 0.4, "子": {...}}}}}} 这里用简化结构:每层给 (自身权重, 子字典) """ result = {} def dfs(node, prefix_w): for name, (w, children) in node.items(): total_w = prefix_w * w if children: dfs(children, total_w) else: result[name] = total_w dfs(hierarchy, 1.0) return result # 报告表2的结构切片:动力0.4883 -> 经济实力0.4162 -> 政府作用0.2618 等 tree = { "动力": (0.4883, { "经济实力": (0.4162, { "政府作用": (0.2618, {}), "消费水平": (0.0624, {}), "人力资源": (0.0986, {}), }), "科技实力": (0.1611, {}), "对外开放": (0.6667, {}), # 简化示意,实际对外开放在下层 }), "阻力": (0.0893, { "贫富分化": (1.0, {}), }), } flat = combine_weights(tree) for k, v in sorted(flat.items(), key=lambda x: -x[1]): print(f"{k}: {v:.4f}")

combine_weights用深度优先把路径上的权重连乘,走到叶子节点时把结果写进扁平字典。这个写法比手工在 Excel 里拉公式可靠,因为层级一多,漏乘某层或串行错位很难肉眼发现。注意dfs传入的是prefix_w,每一层都用父级累计权重乘以本层权重,所以叶子节点的值就是相对总目标的组合权重,全部叶子加起来应该等于 1(前提是每层权重都归一化过)。如果总和偏离 1 明显,说明某个判断矩阵的权重向量没做归一化,或者层级结构里有元素没被挂上。

注意:报告原文在要素层到指标层的赋值里留了不少占位(“同理可依次求得”后面没有具体数值),复现时不要把这些空缺当成零权重,必须自己补齐判断矩阵,否则组合权重之和永远凑不到 1。

4. 从 71 项指标到综合指数:无量纲化与计量模型串联

报告的指标层有 71 项,从统计年鉴里抠数据这一步没法在报告里完成,但它给出的计量模型骨架是完整可复现的。这一章把“拿到指标数据之后怎么算”补成可执行流程。

4.1 无量纲化的方向与公式选择

报告明确写了“首先将其无量纲化”,再逐层代公式。这一步是整个评价里最容易造成排名结果错误的环节,不同无量纲化方法能让同一批城市排出完全不同的名次。

常见做法按指标方向分两类:

指标类型典型指标无量纲化公式说明
正向(越大越好)GDP 总量、科研投入(x − min)/(max − min)归一化到 0~1
负向(越小越好)贫富分化、污染排放(max − x)/(max − min)取反后同向
适度(中间最优)某些结构比例1 −x − x0

报告把准则层分成“动力”和“阻力”,本质上就是在做方向区分,只是原文没把公式展开写。阻力类指标(贫富分化)直接参与正向加权会得出“贫富差距越大竞争力越强”的荒谬结论,必须反向处理。我一般会在数据表里加一列方向标记(+1 / −1),无量纲化时按标记分支,避免把方向判断散落在各个公式里。

还有一个坑是量纲差异:71 项指标里有的是绝对量(GDP 总量,单位亿元),有的是比率(失业率,单位 %),有的是人均量。如果直接用原始值算加权和,绝对量大的指标会彻底主导结果,权重再准也没用。所以无量纲化必须在加权之前完成,且所有指标要落到同一区间。

4.2 计量模型的三层串联

报告给出了三层公式:要素层指数由指标层加权得到,准则层指数由要素层加权得到,总目标指数由准则层加权得到。写成矩阵形式就是一条链:

import numpy as np import pandas as pd # 假设已无量纲化的指标数据:行=城市,列=指标 # 这里用随机示例数据,实际替换为统计年鉴摘录 np.random.seed(42) n_city, n_indicator = 6, 8 X_norm = np.random.rand(n_city, n_indicator) # 0~1 区间 # 指标对要素的组合权重(由上一章层次总排序得到,这里给示例值) w_indicator = np.array([0.12, 0.08, 0.15, 0.20, 0.10, 0.13, 0.11, 0.11]) assert abs(w_indicator.sum() - 1) < 1e-6, "指标权重必须归一化" # 要素层指数:指标加权求和 element_index = X_norm @ w_indicator # 假设指标到要素的分组映射(每组的指标列索引) group_map = { "经济实力": [0, 1, 2], "科技实力": [3, 4], "政府作用": [5], "环境水平": [6, 7], } # 要素层权重(来自组合权重汇总,此处示例) w_element = {"经济实力": 0.2032, "科技实力": 0.0787, "政府作用": 0.1278, "环境水平": 0.0948} criterion_index = np.zeros(n_city) for name, idxs in group_map.items(): # 组内先归一化再加权,保证组内指标贡献不被组规模稀释 sub_w = w_indicator[idxs] / w_indicator[idxs].sum() sub_index = X_norm[:, idxs] @ sub_w criterion_index += sub_index * w_element[name] # 总目标指数 total_index = criterion_index # 若要素层已覆盖全部准则,可直接用 df_result = pd.DataFrame({ "城市": [f"城市{i+1}" for i in range(n_city)], "综合指数": np.round(total_index, 4), }).sort_values("综合指数", ascending=False).reset_index(drop=True) df_result["排名"] = df_result.index + 1 print(df_result)

这段代码做的是报告里“三层计量模型”的矩阵化实现。X_norm @ w_indicator是最基本的加权求和,但直接用它会让指标多的组(比如经济实力下 3 个指标)在数值上压过指标少的组(政府作用 1 个指标),所以我在group_map循环里先做组内权重归一化,再乘组权重,这样每个要素层指数的量级只由组权重决定,不受组内指标个数影响。

assert abs(w_indicator.sum() - 1) < 1e-6这行不是装饰。层次总排序算出来的权重,只要有一层判断矩阵没归一化或某个分支漏算,总和就会偏离 1,而加权求和在总和不为 1 时结果会整体缩放,排名虽可能不变,但指数的绝对水平失去意义,后续如果要设阈值分等级就会出错。

df_result按综合指数降序排列并标名次,对应报告“指数越大,排名越靠前”的结论。实际使用时,城市的指标数据往往有缺失,缺失值怎么处理(剔除、均值填充、还是单独标记)会显著影响排名,这一点报告没有展开,但落地时绕不开。

4.3 指标数据采集的替代方案

报告的指标层来自统计年鉴,71 项指标的数据采集成本很高。如果只是为了验证方法链路,可以先用公开统计数据库的年度数据截取一个子集,或者用模拟数据把流程跑通,再逐步替换为真实指标。我一般会建议先按“每个要素下至少留 2 个能拿到的指标”来裁剪指标体系,保证判断矩阵可构造、权重可计算,跑通一轮再扩充;否则一上来铺 71 项,光数据清洗就能把整个流程拖住。报告作者也在结尾明确说了数据量太大、未能算出最终总指数,这个坦诚反而说明方法流程与数据可得性之间的落差是真实存在的。

5. 复现时的避坑清单与一个自检脚本

上面几章把流程拆完了,但真正动手复现这份报告时,翻车的点往往不在算法本身,而在数据流和矩阵录入这些细节。

5.1 五个高频踩坑记录

现象一:CR 一直算出来是 nan 或负数。原因:判断矩阵里出现了 0 或负数,或者RI_TABLE里查不到当前阶数(比如 n=10,常见 RI 表只到 9)。正互反矩阵的元素必须全为正,标度最小值是 1/9,不会出现 0。解决:录入时加校验assert (A > 0).all(),阶数超过 9 时补全 RI 表或拆分为多个低阶矩阵。

现象二:两层权重乘完,叶子节点之和等于 1.2 或 0.7。原因:某层判断矩阵计算权重后没做归一化,或者层次结构里有一个元素被两个父节点引用,组合权重被重复累加。解决:每个判断矩阵算完权重后强制w = w / w.sum();结构表里对每个节点做唯一父节点检查,用集合记录已访问节点。

现象三:正向、负向指标混在一起后,排名和常识相反。原因:阻力类指标未取反就参与加权,或者无量纲化时 max/min 顺序写反。解决:数据表加方向列,无量纲化前先按方向分支处理,处理完抽查两个城市的单项得分,确认“更好的城市该项得分更高”。

现象四:算术平均法和特征向量法权重差很多,但 CR 又小于 0.1。原因:矩阵接近一致但仍有系统偏差,或者某一列数值量级异常(比如误把 9 填成 0.9)。解决:打印两种方法的结果逐项对比,差异超过 10% 的项重点核查原始标度,通常能定位到录入错误。

现象五:判断矩阵规模太大,专家填到后面开始瞎填。原因:一个准则下挂了 7 个以上要素,两两比较要填 21 个格子以上,认知负荷过高。解决:拆层。把 7 个要素按性质分成 3+4 两个子组,先组间比较再组内比较,逐层构建比一次性铺开可靠得多。

5.2 一个跑完全流程的自检脚本

把前面几章串起来,写一个自检函数,输入是层次结构和各层判断矩阵,输出是权重表与一致性报告,能在正式做评价之前把所有常见错误挡在门外:

def ahp_selfcheck(matrices, ri_table=None): """ matrices: dict, {层名: 判断矩阵 np.ndarray} 返回: {层名: {"weight": ..., "CR": ..., "pass": bool}} """ ri_table = ri_table or {1:0, 2:0, 3:0.58, 4:0.90, 5:1.12, 6:1.24, 7:1.32, 8:1.41, 9:1.45} report = {} for name, A in matrices.items(): A = np.asarray(A, dtype=float) n = A.shape[0] # 基础合法性检查 if A.shape[0] != A.shape[1]: report[name] = {"error": "非方阵"}; continue if not np.allclose(A * A.T, np.ones_like(A), atol=1e-6): report[name] = {"error": "不满足正互反 a_ij * a_ji = 1"}; continue col_sum = A.sum(axis=0) w = (A / col_sum).mean(axis=1) w = w / w.sum() eigvals = np.linalg.eigvals(A) lambda_max = np.max(eigvals.real) CI = (lambda_max - n) / (n - 1) if n > 1 else 0 RI = ri_table.get(n, None) if RI is None: report[name] = {"error": f"缺少 n={n} 的 RI 值"} continue CR = CI / RI if RI > 0 else 0.0 report[name] = { "weight": np.round(w, 4).tolist(), "lambda_max": round(float(lambda_max), 4), "CI": round(float(CI), 4), "CR": round(float(CR), 4), "pass": CR < 0.1, } return report # 使用:把各层判断矩阵放进字典,一次跑完 demo = { "准则层": np.array([ [1, 3, 5], [1/3, 1, 3], [1/5, 1/3, 1] ], dtype=float), "要素层_动力": np.array([ [1, 2, 4], [1/2, 1, 3], [1/4, 1/3, 1] ], dtype=float), } for layer, res in ahp_selfcheck(demo).items(): status = "通过" if res.get("pass") else res.get("error", "未通过") print(f"[{layer}] CR={res.get('CR', 'N/A')} 状态={status}")

这个脚本把三个检查做在前面:方阵校验、正互反校验(A * A.T是否全为 1)、RI 表覆盖校验。正互反校验这一行能挡掉大部分手工录入错误,因为上下三角填反、取倒数取错都会在这一步暴露。pass字段直接给出 CR 是否小于 0.1 的布尔结论,批量跑多层矩阵时一眼能看出哪层需要重填。RI 缺失时返回明确错误而不是抛异常,避免因为阶数不常见导致整个流程中断。

我现在的习惯是:任何一次 AHP 计算之前,先把所有判断矩阵丢进这个自检脚本跑一遍,全部pass为真才往下走无量纲化。有一次赶进度跳过这一步,结果 CR 在阈值边缘,组合权重算出来某个要素占了 0.6,排名全乱,回头查是要素层某个矩阵的上三角被填成了下三角——这种错误肉眼几乎看不出来,但正互反校验一秒就能抓到。从那以后我每次做综合评价都强制走一遍自检,宁可多花十分钟,也不愿意在排名结果里翻车。希望这个流程帮到你,把这份报告里的思路真正用到自己的评价体系上。

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

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

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

立即咨询