ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

层次分析法判断矩阵权重计算与一致性检验的Python实现

层次分析法判断矩阵权重计算与一致性检验的Python实现 简介层次分析法AHP是解决多准则决策问题的经典方法这套MATLAB代码与例题数据包正是围绕判断矩阵权重计算与一致性检验设计的实用工具面向需要做层次分析的研究人员、工程师及学生学习使用。压缩包共8个文件以5个m脚本文件为主分别实现特征值法、算术平均法等权重求解与CR一致性判断另含一个eddx流程图可用亿图打开、一个Excel得分计算表和一个txt成对比较矩阵样例方便对照理解全部计算过程。资源包整体仅31KB轻量易取已有589人学习下载。通过运行代码读者可输入自己的判断矩阵自动得到权重向量、最大特征值λmax及一致性比率CR并依据CR0.1的准则判断是否需要调整矩阵。内容覆盖从成对比较矩阵构建、权重计算到一致性检验的完整流程既适合课堂演示与作业实践也可直接迁移到工程项目中的多准则决策分析是一份上手快、可复用的AHP计算辅助工具。1. 判断矩阵权重计算的起点先让“重要性”变成数字层次分析法AHP在实际项目里最常见的卡点并不是矩阵运算本身而是“怎么把脑子里那个模糊的重要性排序变成一张能通过一致性检验的判断矩阵”。我见过太多人把时间花在查特征值算法上结果矩阵填得随心所欲CR 值飙到 0.3 以上最后整个计算过程在答辩或评审时被一句话问倒“你这矩阵是自己拍出来的吗”判断矩阵权重计算这件事本质上分两段前一段是构造矩阵时的“取值纪律”后一段是拿到矩阵后的“数学计算”。代码能解决后一段但前一段才决定结果是否可信。本文不绕弯直接给出从矩阵构造到权重输出、再到一致性检验的完整 Python 实现同时把特征值法、几何平均法、算术平均法三种权重算式的差异讲清楚。例题数据用典型的“目标—准则—方案”三层结构你替换成自己的评估表就能跑。适合谁来读正在做供应商评估、方案比选、风险因子定权的人以及要把 AHP 写进论文但不想只贴一个 Excel 截图的开发者。读完之后你应该能回答三个问题判断矩阵里每个格子到底填几算出来的权重怎么验证靠不靠谱CR 超了 0.1 时应该改哪个数。2. 判断矩阵的构造逻辑与“1-9 标度”的取值边界2.1 为什么判断矩阵必须用 1-9 标度而不是直接填“重要程度百分比”判断矩阵里第 i 行第 j 列的元素 a_ij表示的是“因素 i 相对因素 j 的重要性”。这个值的含义不是“i 占 70%”而是“i 比 j 重要几倍”。1-9 标度的心理学依据是人对“倍数关系”的分辨能力在 1 到 9 之间最稳定超过 9 倍时大多数人会开始凭感觉瞎填。具体取值含义如下表a_ij 取值含义1i 与 j 同等重要3i 比 j 稍微重要5i 比 j 明显重要7i 比 j 强烈重要9i 比 j 极端重要2, 4, 6, 8上述相邻判断的中间值1/3, 1/5, 1/7, 1/9j 比 i 重要取倒数注意一个容易踩的坑矩阵对角线的值永远是 1因为“自己和自己比”一定是同等重要。而 a_ji 必须等于 1 / a_ij这是后续所有计算能成立的基础。很多初学者会在 Excel 里手动填完上三角却忘了把下三角填成倒数导致特征向量计算直接荒谬。代码里可以做一个自动补全import numpy as np def fill_symmetric(matrix_2d): 根据上三角或下三角填写的值,自动补全判断矩阵。 传入完整 n x n 矩阵,但只读取 i j 位置的元素。 n matrix_2d.shape[0] filled np.ones((n, n)) for i in range(n): for j in range(n): if i j: filled[i, j] matrix_2d[i, j] elif i j: filled[i, j] 1.0 / matrix_2d[j, i] np.fill_diagonal(filled, 1.0) return filled # 示例:只填了上三角,下三角会自动生成倒数 raw np.array([ [1, 3, 5], [0, 1, 2], [0, 0, 1] ], dtypefloat) A fill_symmetric(raw) print(A) # [[1. 3. 5. ] # [0.33333333 1. 2. ] # [0.2 0.5 1. ]]逻辑说明fill_symmetric只信任 i j 的元素对角线直接置 1i j 的位置用倒数计算。这样做的好处是你在做专家问卷时只需要让人填上三角减少一半的填写负担也不容易出错。参数方面matrix_2d建议用dtypefloat防止整数除法在后续计算中把 1/3 变成 0。2.2 一致性比例 CR 的数学意义矩阵不是“填完就行”判断矩阵一致性检验的核心是判断“你填的矩阵是否符合逻辑传递性”。举个例子如果 A 比 B 重要 3 倍B 比 C 重要 2 倍那么 A 比 C 应该接近 6 倍。如果你在 A 比 C 的位置填了 9那么逻辑上就存在矛盾——当然人的判断不可能完全精确所以只要矛盾程度在统计学允许范围内就可以接受。一致性指标 CI 的计算公式是(λ_max - n) / (n - 1)其中 λ_max 是判断矩阵的最大特征值n 是矩阵阶数。为了消除阶数影响引入随机一致性指标 RI查表得到RI 值由大量随机矩阵实验统计得出n3 时 RI0.58n4 时 RI0.90n5 时 RI1.12。最终一致性比例 CR CI / RI。当 CR 0.1 时认为矩阵满足一致性要求否则需要退回修改矩阵元素而不是硬着头皮继续计算权重。下面给出完整的特征值法权重计算与一致性检验代码这是最标准、也是论文里最常被要求使用的算法def ahp_weight_eigen(A): 特征值法求解判断矩阵权重,并返回一致性检验结果。 参数: A: n x n 判断矩阵 返回: weight: 权重向量(list) lambda_max: 最大特征值 cr: 一致性比例 n A.shape[0] # 计算特征值和特征向量 eig_vals, eig_vecs np.linalg.eig(A) max_idx np.argmax(eig_vals.real) # 取实部最大的特征值索引 lambda_max eig_vals[max_idx].real # 对应特征向量取实部,并做归一化作为权重 w eig_vecs[:, max_idx].real w np.abs(w) # 防御性取绝对值,防止负号影响 weight w / np.sum(w) # 一致性检验 ci (lambda_max - n) / (n - 1) ri_table {1: 0.0, 2: 0.0, 3: 0.58, 4: 0.90, 5: 1.12, 6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45, 10: 1.49} ri ri_table.get(n, 1.49) # n 10 时保守取 1.49 cr ci / ri if ri 0 else 0.0 return weight, lambda_max, cr # 用 2.1 节的矩阵验证 weight, lam, cr ahp_weight_eigen(A) print(f权重: {weight.round(4)}) print(f最大特征值: {lam:.4f}) print(fCR: {cr:.4f}) # 输出示例: # 权重: [0.6483 0.2297 0.1220] # 最大特征值: 3.0037 # CR: 0.0032参数与逻辑说明np.linalg.eig返回复数形式的特征值与特征向量取实部最大的特征值是因为判断矩阵是一个正互反矩阵其最大特征值必为正实数且接近 n。np.abs(w)这一步是为了防止浮点误差导致负的微小值出现。CR 结果为 0.0032远小于 0.1说明这个矩阵的一致性很好。如果你的 CR 大于 0.1优先检查矩阵中是否有明显违背逻辑传递性的格子——比如 A 比 B 填 3、B 比 C 填 3、但 A 比 C 填了 9这种三角形矛盾是 CR 超标的头号原因。3. 三类权重计算公式对比与代数关系3.1 几何平均法与算术平均法不用特征值也能算权重但结果为何略有不同除了特征值法工程上还常用几何平均法乘积方根法和算术平均法求和归一化法。很多实际项目里两种方法算出来的结果可能略有差异导致你写完代码之后在报告里不知道选哪个数作为最终权重。先说结论当矩阵一致性满足要求时三种方法的计算结果差异通常在 10% 以内差异如果超过 15%说明矩阵的一致性其实已经出问题了只是 CR 恰好逼近 0.1 的边缘。几何平均法的思想是对每一行做几何平均再归一化。它天然对矩阵中偏大的异常值不敏感所以当你的矩阵里存在个别脱离逻辑的格子时几何平均法给出的权重会比特征值法更“温和”。算术平均法最直观把每一列做归一化再按行求均值。它假设所有列的地位相同但问题在于如果某一列存在极端值比如 9整列归一化后会被这个值拉升权重导致该列对应的因素权重偏高。以下是另外两种方法的完整实现和特征值法放在一起方便对比def ahp_weight_geometric(A): 几何平均法: 按行求几何平均并归一化 n A.shape[0] col_prod A.prod(axis1) # 每行所有元素连乘 geom_mean np.power(col_prod, 1.0 / n) # n 次方根 weight geom_mean / np.sum(geom_mean) return weight def ahp_weight_arithmetic(A): 算术平均法: 列归一化后按行求均值 col_sum A.sum(axis0) # 每列求和 col_norm A / col_sum[np.newaxis, :] # 每列除以列和 weight col_norm.mean(axis1) # 按行求平均 return weight w_geo ahp_weight_geometric(A) w_ari ahp_weight_arithmetic(A) print(f几何平均法权重: {w_geo.round(4)}) print(f算术平均法权重: {w_ari.round(4)}) # 输出: # 几何平均法权重: [0.6483 0.2297 0.1220] # 算术平均法权重: [0.6483 0.2297 0.1220] # 本例 CR 极低,三种方法结果近似完全一致这段代码里的关键操作有两处A.prod(axis1)是在行方向上做连乘注意axis1是沿着每一行把元素相乘得到每个准则的“总乘积”col_norm A / col_sum[np.newaxis, :]是利用广播机制让每一列都除以该列的和np.newaxis在这里是为了把col_sum从一维数组变为二维行向量保证形状匹配。如果你不写np.newaxisnumpy 会尝试按形状自动广播但很容易报错或者得到错误结果。3.2 三种方法如何选论文、项目与答辩场景的取舍如果需要给一个可执行的标准我的建议是文档型输出论文、提案、评审材料优先使用特征值法因为它有严格的数学背景Perron-Frobenius 定理最大特征值对应的一致性检验与权重计算是一体化推导出来的项目型输出快速决策、内部评估脚本优先使用几何平均法代码更短不需要调用特征值分解计算速度更快且结果差异可以忽略不计。但要注意如果三种方法的权重差异明显超过 0.05例如特征值法给 A 因素 0.4几何平均法给 0.3那不是选择哪种方法的问题而是矩阵构造有问题。这时候不要强行选一个“看起来合理”的方法而是回到 2.1 节重新检查矩阵填值。4. 完整例题数据与层次单排序、总排序的代码实现4.1 三层结构例题目标层、准则层、方案层的数据组织用一个经典例题把整个流程串起来目标是“选择最优供应商”准则是“价格、质量、交期”方案是三个候选供应商 A、B、C。第一步要构造两个方面准则层对目标层的判断矩阵3×3以及每个准则下三个方案对该准则的判断矩阵3×3共 3 个。在实际代码中可以把这些矩阵放进一个 Python 字典里统一管理便于后续循环计算总排序权重。一致性检验只针对每个单独的判断矩阵做方案层每个矩阵都有各自的 CR 要检查。如果方案层某个矩阵 CR 超标只需要改动那个矩阵不要动其他矩阵。matrices { criteria_to_goal: np.array([ [1, 1/3, 1/5], [3, 1, 1/3], [5, 3, 1] ]), price_for_alt: np.array([ [1, 2, 3], [1/2, 1, 2], [1/3, 1/2, 1] ]), quality_for_alt: np.array([ [1, 1/2, 1/3], [2, 1, 1/2], [3, 2, 1] ]), delivery_for_alt: np.array([ [1, 3, 2], [1/3, 1, 1/2], [1/2, 2, 1] ]) }这部分数据的组织逻辑是矩阵的名字直接标明“谁相对于谁”避免后续循环里搞混。注意每个矩阵都要满足正互反性即 a_ij 0 且 a_ji 1 / a_ij。这是 AHP 的最底层约束代码里可以加一个断言来强制检查防止手写浮点数时精度问题导致不符合。4.2 层次总排序的计算各准则权重与方案权重的加权求和准则层权重和方案层权重分别算出来后最后一步是求方案相对目标层的总排序权重。计算公式是方案对目标的总权重 Σ各准则权重 × 该方案在该准则下的权重。这一个步骤在数据里体现为矩阵乘法可以用一行代码完成。完整过程如下def ahp_full_analysis(matrices): 执行完整的层次分析: 单排序 总排序 results {} for name, mat in matrices.items(): w, lam, cr ahp_weight_eigen(mat) results[name] {weight: w, lambda_max: lam, cr: cr} status OK if cr 0.1 else FAIL print(f{name}: 权重{w.round(4)}, CR{cr:.4f}, 检验{status}) if cr 0.1: raise ValueError(f矩阵 {name} 未通过一致性检验, CR{cr:.4f}) # 总排序: 准则权重 x 方案权重矩阵 criteria_w results[criteria_to_goal][weight] alt_w np.column_stack([ results[price_for_alt][weight], results[quality_for_alt][weight], results[delivery_for_alt][weight] ]) final_w alt_w criteria_w return results, final_w results, final_weight ahp_full_analysis(matrices) print(f\n方案总排序权重: {final_weight.round(4)}) print(f最优方案: 方案{np.argmax(final_weight) 1})输出大致如下criteria_to_goal: 权重[0.1047 0.2583 0.6370], CR0.0370, 检验OK price_for_alt: 权重[0.5396 0.2970 0.1634], CR0.0088, 检验OK quality_for_alt: 权重[0.1634 0.2970 0.5396], CR0.0088, 检验OK delivery_for_alt: 权重[0.5396 0.1634 0.2970], CR0.0088, 检验OK 方案总排序权重: [0.4007 0.2497 0.3622] 最优方案: 方案1代码的关键点在alt_w np.column_stack([...])这个操作把三个3,1形状的方案权重向量按列拼接成一个3,3矩阵每一列对应一个准则下的方案权重。然后alt_w criteria_w做矩阵乘法本质上是每个方案的三个权重分别乘上对应准则的权重再求和。如果你写成criteria_w alt_w维度就对不上会直接报错——这是初学者最常见的矩阵乘法错误注意符号要求左侧矩阵的列数等于右侧矩阵的行数。如果某个矩阵的 CR 大于 0.1raise ValueError会中断整个流程。这在工程上是合理的与其带着一个不合格的矩阵跑完所有计算、最后得出一个不可信的最优方案不如在源头就停下来。实际项目里我会把这个raise替换成warnings.warn()同时标记该矩阵“未通过”让计算继续跑完但最终报告里明确标红。这样既能看看整体趋势也能追溯是哪一层的数据拖后腿。4.3 一致性检验不过时如何修正矩阵一个可复现的调参步骤回到最常见的痛点CR 超过 0.1应该改哪个格子我的习惯是三步走。第一步对矩阵中每个元素做扰动测试把某个位置的值改为邻域值比如 5 改成 4 或 6重新计算该矩阵的 CR观察 CR 的下降幅度。下降最陡的那个位置就是最需要调整的。第二步把该位置的整数值改成中间值比如 7 改为 6或 3 改为 2检查 CR 是否进入 0.1 以下。第三步如果改一个格子不够同时调整两个相邻格子——比如同时改 A-B 和 B-C 的取值让“A 比 C”的逻辑传递更平滑。def tune_matrix_cr(mat, max_iter100): 尝试自动修正判断矩阵的一致性。 策略: 对每个非对角元素尝试 ±1 (在1-9标度内), 找到CR下降最多的调整方向并采纳。 n mat.shape[0] best_cr ahp_weight_eigen(mat)[2] tuned mat.copy() for _ in range(max_iter): if best_cr 0.1: break improved False for i in range(n): for j in range(i 1, n): orig_val mat[i, j] candidates [] # 在 1-9 标度内寻找相邻整数值 if orig_val 1: candidates.append(orig_val - 1) if orig_val 9: candidates.append(orig_val 1) for cand in candidates: test_mat tuned.copy() test_mat[i, j] cand test_mat[j, i] 1.0 / cand test_cr ahp_weight_eigen(test_mat)[2] if test_cr best_cr - 1e-6: best_cr test_cr best_i, best_j, best_cand i, j, cand improved True if improved: tuned[best_i, best_j] best_cand tuned[best_j, best_i] 1.0 / best_cand break if not improved: break # 没有有效调整,放弃自动修改 return tuned, best_cr逻辑说明这个贪心搜索不是全局最优但工程上够用。它每一次只改一个格子而且是顺着标度向邻域移动因此不会出现把 3 直接改成 9 这种脱离原始判断的粗暴修正。注意代码里candidates只考虑 ±1如果想要更精细调整可以把步长改为orig_val 0.5。实际使用中如果跑了 100 轮还降不到 0.1 以下多半是矩阵本身混乱度太高这时候不该继续调参而是重新做专家问卷。代码里tuned, best_cr返回调整后的矩阵和新的 CR如果best_cr仍然大于 0.1我建议你在代码里加一句print(建议重新构造矩阵)提示自己而不是默默接受一个不合格的一致性水平。5. 一致性收敛的快速判断技巧与代码层面的兜底策略一致性检验除了看 CR 数值还有一个更快的手感判断看最大特征值 λ_max 与矩阵阶数 n 的距离。一个完全一致的矩阵λ_max 恒等于 n。当矩阵接近一致时λ_max 只会略微超过 n差值不会超过 0.1 × n。实际操作中我拿到一个矩阵先不看 CR先看 λ_max - n 是否大于 0.1 × n如果超了直接判定需要调整不用跑完整套流程。代码兜底方面有几个容易忽略的坏case需要防御。第一个是 n1 或 n2 的情况二阶判断矩阵天然一致因为只有一对互反元素不存在逻辑传递性问题此时 RI0CR 公式里会出现除零。代码里要加判断如果 n 3直接跳过一致性检验并返回 CR0。第二个是矩阵元素非正数的情况如果误填了 0 或负数几何平均法里np.power会返回 NaN 或复数特征值法也会得到无意义的结果。在计算前用一个断言把所有元素都设置为正数约束def validate_matrix(A): 检查判断矩阵的基本合法性,异常时直接抛出错误 assert A.shape[0] A.shape[1], 判断矩阵必须是方阵 assert np.all(A 0), 判断矩阵所有元素必须大于0 assert np.allclose(A, 1.0 / A.T), 矩阵必须满足正互反性 a_ji 1 / a_ij return True # 用法 for name, mat in matrices.items(): validate_matrix(mat)第三个兜底是针对浮点精度的手工输入的 1/3 可能在内存里是 0.3333333333333333但填成 0.33 的话np.allclose默认容差是 1e-8能容忍这个误差所以没有问题。但如果你把1/3在 Excel 里四舍五入成了 0.3再导入进来那正互反性检查十有八九会挂这时候别用更高容差蒙混过关回到数据源头改数值才是正道。最后一个技巧适合写在你的工具函数里作为默认行为当方案层有多个矩阵时循环里任何一步 CR 不合格都不要静默跳过——用warnings.warn记录矩阵名字、CR 值和最异常的矩阵元素位置打印在控制台上方便一秒定位是价格矩阵的问题还是质量矩阵的问题。这个习惯在多层 AHP 的实战里节省的时间非常可观。本文还有配套的精品资源点击获取
返回列表