☰
模糊综合评判Python实现:隶属度矩阵、AHP熵权法与合成算子
2026/10/24 4:38:08 网站建设 项目流程

简介:模糊综合评判方法的原理与 Python 实现构成这份资源的主线,面向需要处理不确定性评价问题的学生、研究者与工程实践者。内容从因素集、评价等级集、权重确定、隶属度向量到模糊评判矩阵的构建,再到模糊合成运算与结果归一化,覆盖了完整的方法链路,可用于项目评审、方案比选、绩效评估等场景。压缩包共 3 个文件,包含 1 份讲解合成算子选取的 PDF 文献、1 份 Python 代码文档与 1 个 fuzzy.py 脚本,整体约 250KB,轻量易取,便于边读原理边对照代码验证。已有 3621 人学习下载。读者可借助文献理解不同合成算子对评判结果的影响,再通过脚本把权重计算、矩阵构造与归一化流程跑通,直接改为自己的指标体系与隶属函数,减少从公式到可运行代码的摸索成本。

1. 选型评审里被平均分掩盖的短板,用隶属度矩阵摊开

给一张评审表收尾,最怕遇到这种情况:某个方案在稳定性上明显拉胯,但因为其他几项分数高,加权平均一摊,总分还是排第一。传统加权求和把“某一项很差”这个信息稀释掉了,评委心里有数,报告上却看不出。模糊综合评判方法(python)这套代码要解决的就是这件事——它先把每个指标对“优、良、中、差”的隶属程度算成一个矩阵,再用权重做合成,输出的是覆盖整个评语集的隶属向量,而不是一个孤零零的分数。zip 里是一套可直接跑的 Python 实现,覆盖隶属函数、AHP 与熵权法定权、四种合成算子和多级评判。适合手里一堆定性指标、又必须给出可解释结论的人。

2. 因素集、评语集与隶属度矩阵:三个集合定错,后面全白算

模糊综合评判的全部计算都压在一个矩阵 R 上,R 的每一行对应一个指标,每一列对应一个评语等级,元素 r_ij 表示“第 i 个指标被评为第 j 等的程度”。这个矩阵一旦定歪,后面权重再精细、算子再花哨,结论都是玄学。所以真正该花时间的是前面这三件事:因素集 U 里放什么、评语集 V 分几档、隶属度怎么从原始数据算出来。

2.1 因素集 U 和评语集 V 的粒度怎么定

因素集 U 就是参与评判的指标集合,通常来自一张现成的指标体系表,比如技术方案评审里的“功能覆盖度、性能余量、稳定性、可维护性、交付周期、报价”。这里最常见的翻车是没有检查指标方向——报价、交付周期、缺陷率这些是负向指标,数值越大评价越差,如果直接把原始数值扔进隶属函数,算出来的 R 会把“报价高”判成“优”。我在实际项目里一般会先给每个指标打上方向标签:正向(越大越好)、负向(越小越好)、区间型(落在某段最好,比如响应时间 200~400ms)。方向不统一,就没法放到同一个评语集下讨论。

评语集 V 是评价等级的集合,工程上最常用的是奇数级,3 级、5 级、7 级。级数不是越多越好:评语级数一多,隶属度矩阵的行会变得稀疏,相邻等级的区分度下降,最后靠最大隶属度原则挑出来的那一档,很可能只是数值噪声;级数太少(比如只有“合格/不合格”),又没法体现梯度,评审结论会变成二值判断。我的经验是,5 级(优、良、中、较差、差)能覆盖大部分评审场景,7 级适合需要精细区分的绩效考核,3 级只适合初筛。定级数的时候还要同时定锚点,也就是每个等级对应的分数中心,比如 95/85/75/65/50,锚点直接决定后面的隶属度计算,锚点之间等距是最省事也最容易解释的做法。

因素集的另一个坑是层级。当指标超过 8 个,单级评判会因为权重被稀释而失去区分度,这时候应该先分组,比如把“功能、性能、稳定性”归到技术维度,“报价、周期、付款条件”归到商务维度,先做二级评判,再做一级合成。这件事在代码里体现为两套权重、两次合成,不是把 12 个指标硬塞进一个 12 维的 A 向量。

2.2 隶属函数的选型:三角、梯形、高斯分别管哪类指标

隶属函数负责把“指标值”翻译成“隶属度”,选型取决于指标的物理含义和数据形态。下面这张表是我这几年反复用下来的一张对照,基本能覆盖常见场景。

函数类型参数适用指标注意点
三角形(a, b, c) 左底、顶点、右底专家打分、等级明确的定性指标顶点处隶属度为 1,两侧线性衰减,解释成本最低
梯形(a, b, c, d) 两底两顶存在“合适区间”的指标,如温度、库存量平台区宽度不能拍脑袋,要来自业务约束
高斯型(c, σ) 中心、宽度测量噪声大、连续性强的物理量σ 选小会让矩阵接近 0/1,选大则区分度消失
分段线性一组断点有国标/行标分级的指标断点必须来自标准文本,不能自创

选型的判断标准其实只有一条:这个指标的“优”是一个点,还是一个区间?如果是一个点,用三角形或高斯型;如果一个区间都算优,用梯形。很多教程默认全用三角形,这在指标含有区间约束时会系统性地低估隶属度——比如“CPU 占用率 40%~60% 都算合理”,用单峰三角形就会把 50% 和 60% 判出明显差距,而实际上业务上认为它们一样好。

还有一个容易被忽略的细节是隶属函数的“腰宽”。以三角形为例,顶点在锚点 b,左右底在 b±half,half 的取值直接决定相邻等级的覆盖范围。half 取相邻锚点间距的一半,能保证相邻三角形在中间点恰好相交、隶属度各为 0.5,这是最不容易出现“某一行全零”的取法。如果指标分布明显偏斜(比如大部分方案都集中在 80 分以上),可以考虑对 half 做非对称处理,但一定要在报告里写清楚,否则复现的人对不上数。

提示:不要把隶属函数当成调参旋钮。换一个 half 就能让结论翻转,说明指标体系本身没有区分度,这时候该回去改指标,而不是继续调函数。

2.3 从专家打分表到 R 矩阵的代码落地

有了锚点和函数,R 矩阵的计算就是逐指标、逐等级地算隶属度再按行归一化。下面这段是包里最核心的一段,也是我每次接手新项目时最先改的地方——把samples换成真实数据,把half换成业务认可的腰宽,其他逻辑基本不动。

import numpy as np # 评语集锚点:优 95 / 良 80 / 中 65 / 差 45,级数和分数都按项目改 ANCHORS = np.array([95.0, 80.0, 65.0, 45.0]) def tri_mf(x, a, b, c): """三角形隶属函数:a 左底,b 顶点,c 右底""" if x <= a or x >= c: return 0.0 if x == b: return 1.0 return (x - a) / (b - a) if x < b else (c - x) / (c - b) def build_R(samples, anchors=ANCHORS, half=8.0): """samples: {指标名: [专家1打分, 专家2打分, ...]} half: 相邻锚点间距的一半,控制隶属函数的腰宽""" rows = [] for name, scores in samples.items(): row = [] for v in anchors: # 每个专家在该等级上的隶属度取均值,等价于专家组投票 row.append(np.mean([tri_mf(s, v - half, v, v + half) for s in scores])) row = np.array(row, dtype=float) # 行归一化:保证每行隶属度之和为 1;全零行退化为均匀分布 if row.sum() > 1e-12: row = row / row.sum() else: row = np.full(len(anchors), 1.0 / len(anchors)) rows.append(row) return np.array(rows) samples = { "功能覆盖度": [92, 88, 90, 85], "性能余量": [78, 82, 75, 80], "稳定性": [55, 60, 58, 62], "可维护性": [70, 72, 68, 74], } R = build_R(samples) print(np.round(R, 3))

这段代码的逻辑分三步。第一步,对每个指标、每个评语锚点,把该指标下所有专家的打分逐一代入三角形函数,得到单个专家的隶属度;第二步,对专家求均值,这一步等价于“专家组投票”,也是把主观打分变成模糊关系的关键——它不要求专家先商量出统一分数,只要求他们在同一把尺子上打分;第三步,按行归一化,强制每行隶属度之和为 1,避免某些指标因为落在函数重叠区导致整行和偏离 1,影响后续合成。

参数说明:anchors必须和评语集级数严格对应,改级数时同步改这里,否则会出现维度对不上;half建议按相邻锚点间距的一半起步(本例锚点间距 15,half≈7.5,取 8 是留一点重叠),如果指标分布集中在少数几档,可以适当放大 half 让分布摊开,但放大后一定要看每行是否出现多个接近的峰值,那意味着等级区分度不够;samples的每个列表长度可以不相等,但同一指标下专家数量太悬殊会让均值失真,常见做法是缺项专家不参与该指标的均值计算,而不是补一个默认分。

跑完之后我一般会做一件小事:把 R 打印出来逐行看。如果某一行四个数都在 0.25 附近,说明这个指标在专家组内部争议极大或者锚点覆盖不到,得回去核对打分尺度;如果某一行有一项超过 0.9,说明该指标的评价高度一致,这一项在合成里会起主导作用,权重上要留意别让它被过度放大。这一步花不了几分钟,却能拦住后面 80% 的“结果看着怪但说不清哪里怪”。

3. 权重从哪来:AHP 一致性检验与熵权法的分工

R 矩阵只描述了“每个指标被评成各等级的程度”,还缺一个东西回答“哪个指标更重要”,这就是权重向量 A。工程上权重来源基本两条路:一条是主观定权,用 AHP 判断矩阵把专家偏好结构化;另一条是客观定权,用熵权法从数据本身的离散程度反推。两条路各有各的失效场景,混用之前得先搞清楚它们分别在测什么。

3.1 AHP:判断矩阵不是拍脑袋填的

AHP 的核心是一张两两比较矩阵,元素 a_ij 表示“指标 i 相对指标 j 的重要程度”,常用 1~9 标度。填完之后求最大特征值对应的特征向量,归一化就是权重。AHP 真正有价值的地方不是那个特征向量,而是一致性检验——它逼着你检查自己填的比较关系有没有自相矛盾,比如 A 比 B 重要、B 比 C 重要,结果 C 又比 A 重要,这种循环在多人会签的场景里非常常见。

import numpy as np # 随机一致性指标 RI,按矩阵阶数取,1~9 阶是标准表 RI = {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} def ahp_weights(A): """A: 正互反判断矩阵,a_ij > 0 且 a_ji = 1 / a_ij""" A = np.asarray(A, dtype=float) n = A.shape[0] eigvals, eigvecs = np.linalg.eig(A) k = int(np.argmax(eigvals.real)) lam = eigvals[k].real w = np.abs(eigvecs[:, k].real) w = w / w.sum() # 特征向量归一化即权重 CI = (lam - n) / (n - 1) if n > 1 else 0.0 CR = CI / RI[n] if RI.get(n, 0) > 0 else 0.0 return w, lam, CI, CR A = np.array([ [1, 3, 2, 4], [1/3, 1, 1/2, 2], [1/2, 2, 1, 3], [1/4, 1/2, 1/3, 1], ]) w, lam, CI, CR = ahp_weights(A) print("权重:", np.round(w, 3)) print("lambda_max:", round(lam, 4), "CI:", round(CI, 4), "CR:", round(CR, 4))

代码里RI表是查表用的,n是矩阵阶数,超过 9 阶时标准 RI 表不再可靠,这种规模通常应该改用 Delphi 法加分组,而不是硬算。lam是最大特征值的实部,理论上应该等于 n,实际会略大;CI是偏离程度,CR = CI / RI是相对偏离,工程上一般要求 CR < 0.1,严格一点的场景要求 < 0.05。CR 超标时不要用“取平均”或者“强行归一化”绕过,正确的做法是回到矩阵找出矛盾的三元组,请专家重新比较那一对。

注意:特征向量法在接近一致的矩阵上表现稳定,但对舍入误差敏感。填矩阵时用 1/3 而不是 0.33,用整除形式表达倒数,能让复现的人拿到一致的结果。

3.2 熵权法:让数据自己说话,但它不认“重要性”

熵权法的出发点是:某个指标在各方案之间取值差异越大,说明它携带的区分信息越多,权重应该越高;如果所有方案在该指标上几乎一样,那这个指标对排序没有贡献,权重就该低。这个逻辑和 AHP 完全不同——熵权法衡量的是“区分度”,不是“业务重要性”。一个指标可能业务上极其关键,但如果所有候选方案在这项上都差不多,熵权法会给它很低的权重,这在评审场景里往往是反直觉的。

def entropy_weights(X): """X: m 个方案 × n 个指标 的原始矩阵,传入前必须已完成同向化""" X = np.asarray(X, dtype=float) m, n = X.shape # 极差标准化,把不同量纲压到 [0, 1] Xn = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0) + 1e-12) # 平移一个极小量,避免 log(0) P = (Xn + 1e-12) / (Xn + 1e-12).sum(axis=0, keepdims=True) k = 1.0 / np.log(m) e = -k * (P * np.log(P)).sum(axis=0) # 各指标的信息熵 d = 1 - e # 差异系数 return d / d.sum()

参数说明:X的行是方案、列是指标,方向必须统一,否则标准化之后负向指标会反向拉高权重;m是方案数,熵权法在 m 很小(比如只有 2~3 个方案)时极不稳定,这时候权重的意义接近于噪声,建议改用 AHP 或等权;k = 1/ln(m)是让信息熵落在 [0,1] 的归一化系数;1e-12是为了防零,不能省略,指标某一列全相同时分子分母都会归零,这时候熵权法给出的该列权重应该是 0,退化处理即可。

3.3 组合权重:乘法合成和线性加权怎么选

只想要一份可解释的权重时,AHP 就够;想要权重反映候选方案的实际分布时,加熵权法做校正。组合方式有两种常见写法:线性加权w = α·w_ahp + (1-α)·w_entropy和乘法合成w = (w_ahp · w_entropy) / Σ(w_ahp · w_entropy)。线性加权里 α 又是一个需要拍板的参数,实操中容易引入新的主观性;乘法合成不需要额外参数,但对某一项为 0 的情况非常敏感,任何一侧给 0 都会把整个权重压没。

我一般会在评审类项目里用乘法合成,因为它保留了“业务上不重要”这个硬约束——AHP 给了低权重,熵权法再高也拉不回来;而在纯数据驱动的方案排序里用线性加权,α 取 0.5 作为起点,再根据结果稳健性微调。无论哪种方式,组合后都要重新归一化,并且在报告里把两套原始权重都列出来,方便别人判断结论是数据推出来的还是人定出来的。

4. 合成算子怎么选:M(·,+) 与 M(∧,∨) 差的不只是一个符号

R 和 A 都有了,最后一步是把它们合成一个隶属向量 B = A ∘ R。这里的“∘”不是普通矩阵乘法,而是一族合成算子,不同算子对应不同的决策语义,选错会让结论的偏向性完全变掉。

4.1 四种常用合成算子对照

算子表达式决策语义典型场景
M(∧,∨) 主因素决定b_j = max_i min(a_i, r_ij)只要有一项特别突出就定调否决制评审、单指标一票通过
M(·,∨) 主因素突出b_j = max_i (a_i · r_ij)突出主要指标,弱化次要指标关键指标筛选
M(∧,⊕) 加权平均型变体b_j = Σ min(a_i, r_ij)兼顾主次,但有截断指标间存在明显短板约束
M(·,+) 加权平均b_j = Σ a_i · r_ij所有指标按权重线性贡献综合评审、方案排序

M(·,+)是最常用的,因为它满足“权重越大、贡献越大”的线性直觉,而且结果天然归一(A 和 R 每行都归一,乘积的和也为 1)。但它的缺点也很直接:一个指标再差,只要权重不够大,就会被其他指标的平均值掩盖,这恰恰是第 1 章里说的那个问题。所以选算子之前要先问一句:这次评判是想要“综合印象”,还是想要“短板拦截”?前者用M(·,+),后者用M(∧,∨)或带截断的算子。

4.2 主因素决定型为什么会吃掉中间信息

拿一组具体数字看差异,比看公式直观得多。设权重 A = [0.5, 0.3, 0.2],R 为三指标四等级:

A = np.array([0.5, 0.3, 0.2]) R = np.array([ [1.0, 0.0, 0.0, 0.0], [0.2, 0.6, 0.2, 0.0], [0.0, 0.3, 0.5, 0.2], ]) def compose(A, R, mode="weighted"): A = np.asarray(A, dtype=float) R = np.asarray(R, dtype=float) if mode == "weighted": # M(·,+):加权平均 B = A @ R elif mode == "minmax": # M(∧,∨):主因素决定 B = np.array([np.max(np.minimum(A, R[:, j])) for j in range(R.shape[1])]) elif mode == "product_max": # M(·,∨):主因素突出 B = np.array([np.max(A * R[:, j]) for j in range(R.shape[1])]) else: raise ValueError(mode) return B / B.sum() print("M(·,+):", np.round(compose(A, R, "weighted"), 3)) print("M(∧,∨):", np.round(compose(A, R, "minmax"), 3))

M(·,+)的结果是[0.56, 0.24, 0.16, 0.04],最大项落在“优”,且第二项“良”有 0.24 的份额,能看出“整体不错但并非压倒性”。M(∧,∨)的结果是[0.417, 0.25, 0.167, 0.167],看上去也是“优”最大,但第三、第四等级被拉平到同一个值,原本 R 里“中”比“差”高出的那部分信息被 max 操作抹掉了。这就是主因素决定型的本质:它只关心每个等级上最强的那个指标,谁在某一等级上最突出,谁就决定该等级的隶属度,其他指标的细微差异不进入结果。评审里如果出现“一票否决”的需求,它是合适的;如果只是想排序,它会系统性地放大极端项。

# 短板拦截:先把低于阈值的指标挑出来,再决定是否继续合成 def gate(A, R, threshold=0.3, level=3): """level: 关注第几个评语等级(这里取“差”),低于阈值直接返回警告""" weak = [(i, round(float(R[i, level]), 3)) for i in range(R.shape[0]) if R[i, level] > threshold] if weak: return {"gate": "blocked", "weak": weak} return {"gate": "pass", "B": compose(A, R, "weighted")}

这段gate不是标准算法,是我在实际项目里补的一道前置检查:在跑加权平均之前,先看有没有指标落在“差”这一档的隶属度超过阈值,有就单独列出来人工复核,而不是让它在平均里被冲淡。阈值取多少要看业务容忍度,常见做法是 0.3~0.4,取太高会频繁触发,取太低又拦不住问题,需要根据历史评审记录回测一遍再定。

4.3 多级模糊评判:把指标树拆成两级再合成

指标超过 8 个、且天然分成几组的时候,单级合成会让权重被稀释。这时候用两级评判:先在每个二级维度内部做一次合成,得到该维度的隶属向量,再把这些向量当作新的 R,用一级权重做第二次合成。

def multi_level(tree, R_dict, mode="weighted"): """tree: {一级指标: {"weight": 一级权重, "subs": {二级指标: 二级权重}}} R_dict: {二级指标: 该指标的隶属度行向量}""" level1_vec, level1_keys = [], [] for top, cfg in tree.items(): subs = cfg["subs"] A = np.array(list(subs.values()), dtype=float) A = A / A.sum() R = np.vstack([R_dict[s] for s in subs]) level1_vec.append(compose(A, R, mode)) level1_keys.append(top) A_top = np.array([tree[t]["weight"] for t in level1_keys], dtype=float) A_top = A_top / A_top.sum() R_top = np.vstack(level1_vec) return compose(A_top, R_top, mode), dict(zip(level1_keys, level1_vec)) tree = { "技术维度": {"weight": 0.55, "subs": {"功能覆盖度": 0.4, "性能余量": 0.3, "稳定性": 0.3}}, "商务维度": {"weight": 0.45, "subs": {"报价": 0.6, "交付周期": 0.4}}, } R_dict = { "功能覆盖度": [0.7, 0.2, 0.1, 0.0], "性能余量": [0.3, 0.5, 0.2, 0.0], "稳定性": [0.1, 0.3, 0.4, 0.2], "报价": [0.4, 0.4, 0.2, 0.0], "交付周期": [0.2, 0.5, 0.3, 0.0], } B, detail = multi_level(tree, R_dict) print("一级结果:", np.round(B, 3)) print("各维度:", {k: np.round(v, 3).tolist() for k, v in detail.items()})

逻辑上,multi_level先按tree里的subs顺序取出对应行拼成子矩阵,套用同一套合成算子得到各维度的隶属向量,再把它们纵向堆叠、用一级权重合成。参数说明:tree的weight和subs里的权重各自独立归一化,不需要事先调成 1,函数里会重新归一化;R_dict的键必须和subs完全一致,拼错一个键np.vstack会直接报错;mode在两级之间可以不一致,比如二级用加权平均、一级用主因素突出,但这种情况要在报告里写清楚,否则复现的人对不上。

提示:两级评判的结果和单级评判有时会有明显差异,这不是 bug,而是权重分配结构变了。做对比实验时,把两套结果的 R 都留档,方便解释差异来源。

5. 避坑与排查:五个让评判结果不可信的具体问题

模糊综合评判的代码量不大,出问题基本都出在数据准备和参数设定上。下面五条是我在不同项目里反复碰到的翻车记录,每条按“现象 → 原因 → 解决”写,照着排查能省不少时间。

5.1 某一行隶属度全为零

现象:R 矩阵打印出来,某个指标整行都是 0.000,合成后该指标对结果完全没有贡献。原因通常有两种:一是该指标的原始打分全部落在所有三角形函数的定义域之外,比如锚点 95/80/65/45,专家打分集中在 30 分附近,所有隶属函数都返回 0;二是指标的取值方向和锚点方向相反,负向指标没做同向化。解决方式是先把这一行对应的原始打分单独打出来,看分布是否偏离锚点覆盖范围,偏离了要么调整锚点、要么检查方向标签;代码里保留全零行退化到均匀分布的分支只是兜底,不能当作正常路径。

5.2 CR 超过 0.1 还是把权重用了

现象:AHP 算出来的权重看着挺合理,但评审结论和专家直觉对不上,换个专家重填矩阵结论就大变。原因是一致性检验没过,判断矩阵内部存在循环矛盾,特征向量在这种矩阵上不稳定。解决方式不是调 RI 表,而是定位最大矛盾三元组:对每个 i、j、k 检查 a_ij · a_jk 和 a_ik 的偏离,偏离最大的那一组请专家重新比较,重填后重新算 CR,直到连续两次低于阈值。如果多个专家各自填矩阵,建议先做几何平均再算,而不是先算权重再平均。

5.3 负向指标没做同向化

现象:报价越低、缺陷率越低的方案,合成结果反而越差。原因是负向指标直接用原值代入隶属函数,“报价高”被判成“优”。解决办法是在构造 R 之前统一处理:对负向指标做max - x或1/x变换,对区间型指标用距离锚点的偏离度换算,所有变换记录在代码注释里。这一步经常被跳过,因为评语集和隶属函数写得像模像样,跑起来不报错,只有对着业务常识看结果才会发现不对。

5.4 合成后 B 的各分量之和不等于 1

现象:合成得到的 B 四项加起来是 1.2 或 0.8。原因是选用了M(∧,∨)之类的算子,这类算子本身不保证归一性,尤其是当 A 和 R 没有严格按行归一的时候。解决方式是在合成之后统一做一次归一化B = B / B.sum(),同时检查 R 的每一行是否真的归一到 1——如果 R 有某行和明显偏离,先修 R 再合成,不要靠最后一步归一化掩盖问题。

5.5 最大隶属度原则在结果接近均匀时失效

现象:B = [0.28, 0.27, 0.25, 0.20],按最大隶属度原则取“优”,但四个数几乎一样,结论没有说服力。原因是评语等级之间区分度不足,或者指标本身在专家组内部分歧很大。解决办法是同时输出置信度(最大项与次大项的差),差值低于 0.05 时不要直接下结论,改为报告分布并提示“评价集中在优、良之间”;必要时回到第 2 章检查锚点和 half,把等级区分度拉开再重算。

6. 结果怎么读:最大隶属度原则失效时的一个替代做法

拿到 B 之后,直接取 argmax 是最省事的读法,但它丢掉了分布信息,也掩盖了“几个等级势均力敌”这种真实情况。我一般会同时输出三个量:最大隶属度对应的等级、该等级的置信度(最大项减次大项)、以及用评语锚点算出的加权得分。前两个回答“像哪一档、有多确定”,后一个回答“如果非要给一个分数是多少”,三者一起看,比单看 argmax 稳得多。

def read_result(B, anchors=ANCHORS, labels=("优", "良", "中", "差")): B = np.asarray(B, dtype=float) B = B / B.sum() order = np.argsort(B)[::-1] top, second = int(order[0]), int(order[1]) return { "等级": labels[top], "置信度": round(float(B[top] - B[second]), 3), "加权得分": round(float(B @ anchors), 2), "分布": dict(zip(labels, np.round(B, 3).tolist())), } print(read_result([0.56, 0.24, 0.16, 0.04])) print(read_result([0.28, 0.27, 0.25, 0.20]))

第一组输出等级“优”、置信度 0.32、加权得分约 87.4,结论可以放心用;第二组等级同样是“优”,但置信度只有 0.01,加权得分约 76.1,这时候报告里就该写“整体落在良与优之间,优的倾向不明显”,而不是简单写一个“优”。置信度的分界线没有统一标准,我在评审场景里用 0.05 作为提示阈值、0.15 作为可下结论阈值,低于前者只报分布,介于两者之间报等级加保留意见。

验证这套流程是否跑通,最省事的办法是拿一个能手工核对的小例子走一遍:两个指标、两个等级、权重各 0.5,R 取[[0.8, 0.2], [0.3, 0.7]],加权平均结果是[0.55, 0.45],和手算对得上,就说明合成链路没接错。之后再把真实数据灌进去,出错时才有对照物。

血泪经验是:不要等最后出报告才发现 R 有问题。我现在的习惯是每跑完一次,先把 R 逐行打印、把每行的和、最大值、最小值扫一遍,再跑合成;R 没问题,后面的权重和算子才有讨论的意义。从那以后我每次接新的评审数据,都强制先走一遍“打印 R → 肉眼扫行 → 小例子核对 → 再上真实数据”这四步,慢是慢一点,但省掉了大量回头解释结论为什么反常的时间。希望帮到你。

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

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

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

立即咨询