简介:Matlab实现CRITIC赋权法的完整代码,面向从事多属性决策分析(MCDM)的学生、教师与工程技术人员,用于在数据存在不确定性时客观确定指标权重,避免主观赋值偏差。压缩包内为单个m文件,大小仅586B,但流程完备,涵盖数据预处理、属性归一化、基于Cronbach's α的一致性检验、属性间相关系数分析、变异度强度计算、CRITIC权重求解以及对象综合评分等关键环节。已有1657人学习浏览。用户只需将内置随机示例替换为实际决策矩阵,即可直接获得各属性权重与对象评分;代码结构紧凑、逻辑清晰,便于二次开发,可补充权重可视化、评分排名展示,或与其他多属性决策方法结合使用,提升决策可靠性。适用于项目评估、供应商选择、产品设计等场景,是学习算法原理和开展实际应用的实用脚本。
1. 多指标评分总卡在权重上:把CRITIC赋权法跑通的完整姿势
做过多指标综合评价项目的开发者一定绕不开这个痛点:权重到底怎么定。CRITIC赋权法一词进了我的工具箱之后,多指标评分里"权重拍脑袋"这个问题才算真正解决。这套Matlab工程完整实现了CRITIC(Criteria Importance Through Intercriteria Correlation)的算法链路——从极差标准化、标准差计算、相关系数矩阵构建,到对比强度与冲突性合成信息量、归一化输出权重向量,整个过程不依赖任何人工打分。适合做教学质量评估、供应链绩效打分、图像质量指标权重分配这类任务的开发者,输入一张二维数据表,输出一行权重系数,省去反复开会讨论权重的玄学流程。
2. 原理与工程结构:CRITIC的三要素在Matlab里怎么落
2.1 数学原理:对比强度、冲突性与信息量
CRITIC法的核心逻辑可以用一句话说清楚:一个指标的权重,取决于它能不能帮我们把样本区分开,以及它提供的增量信息有多少。这个方法最早是在多指标决策问题的研究中被提出的,全称直译过来就是"通过指标间相关关系确定指标重要性"。
区分能力用标准差σ_j来量化。标准差大,说明各样本在这个指标上的差异大,这个指标的"话语权"就高。增量信息用指标间的冲突性来量化,具体做法是计算指标间的相关系数矩阵R,然后对每个指标求它与所有其他指标的冲突系数之和:
conflict_j = Σ(1 - r_ij), i ≠ j
这里的r_ij表示指标i和指标j之间的Pearson相关系数。r_ij越大,说明指标i和指标j越步调一致,信息冗余越严重。如果某两个指标相关系数接近0.95以上,它们在评价里作用高度重复,权重自然要被压低;反之如果两个指标负相关,说明一个涨一个跌,它们各自带来的增量信息就很大。
把两者乘起来就得到信息量C_j:
C_j = σ_j × conflict_j
最后对C做归一化得到权重:
W_j = C_j / ΣC_j
整个过程没有任何人工参与,也不依赖先验知识,输入数据直接决定权重输出。和熵权法只关注信息熵相比,CRITIC额外考虑了指标间的相关性,这在评价指标多、指标又常常彼此耦合的场景下更有优势。举个例子,供应商评估中"交货准时率"和"订单完成率"相关性很高,用熵权法时这两个指标会因为熵值接近而拿到接近的权重,评价结果被重复信息带偏;CRITIC会自动把冗余指标的权重压下来,这个特性在实际项目里帮了大忙。
2.2 工程文件结构与调用关系
这个Matlab工程按功能拆成了几个文件,我拆解项目时习惯用三个核心函数加一个入口脚本的方式来组织。这种拆分方式的好处是每个文件职责单一,后续要替换标准化方法或者改进相关系数计算方式,只需要动对应文件,不用把整个逻辑翻出来重写。
| 文件 | 作用 | 依赖 |
|---|---|---|
| minmax_normalize.m | 极差标准化,把每列映射到[0,1] | 无 |
| conflict_intensity.m | 计算标准差与冲突系数 | corrcoef |
| critic_weight.m | 主函数,合成信息量与权重 | 前两个函数 |
| run_demo.m | 示例脚本,演示调用方式 | critic_weight.m |
主调关系是run_demo加载一份评价数据(Excel或.mat格式),交给critic_weight去处理;critic_weight内部先调用minmax_normalize消除量纲,再调用conflict_intensity计算标准差和冲突系数,最后合成权重并输出。整个链路下来输入输出都很干净,调用方只需要关心最外层的critic_weight一个函数就够了。
2.3 数据预处理的边界:哪些情况不能直接跑
CRITIC自带极差标准化作为预处理,理论上丢进一个矩阵就能算。但我在实际使用中遇到过几个边界情况,处理不好权重就废了。
第一个是样本量。相关系数矩阵的稳定性依赖样本量,如果样本数少于指标数,corrcoef计算出来的相关系数矩阵不一定满秩,权重结果不可信。我一般要求样本数至少是指标数的3倍以上,低于这个比例宁可多收集数据或者先做指标筛选。
第二个是零方差列。如果所有样本在某个指标上取值完全一样,这个指标的对比强度为零,信息量为零,权重直接被分配为0。这其实是一种有用的信号——说明这个指标在当前评估里没有区分能力,留着反而稀释其他指标的权重。但要注意不能直接在标准化之前参与计算,否则会出现除零错误。
第三个是指标方向不统一。标准化前要确认所有指标都是正向的(越大越好),若有负向指标需要先做方向转换,否则算出来的权重符号和直觉相反。判断方向的方法很简单:业务上明确这个指标的优秀方向,然后把数据朝着"越大越好"的方向重排。
第四个是缺失值。Matlab的readmatrix读入含NaN的数据后,std和corrcoef的行为取决于数据分布,有的场景直接返回NaN,有的则自动忽略。保险做法是提前用rmmissing或者手动按行删除,别把缺失值带进CRITIC流程。
3. 核心代码拆解:从原始矩阵到权重向量的完整链路
3.1 极差标准化:预处理里的三个细节
function X_norm = minmax_normalize(X) % 极差标准化:把每个指标映射到 [0, 1] % 输入X: n行m列,n为样本数,m为指标数 % 输出X_norm: 同维度的标准化矩阵 % 注意:本函数假设所有指标均为正向指标(越大越好) [n, m] = size(X); X_norm = zeros(n, m); for j = 1:m x_min = min(X(:, j)); x_max = max(X(:, j)); if x_max == x_min % 零方差列:全部置0 % 该指标没有区分度,后续权重为0 X_norm(:, j) = 0; else X_norm(:, j) = (X(:, j) - x_min) / (x_max - x_min); end end end这段代码三个关键细节。第一个是零方差分支——如果某列数据全相等,直接套公式会出现除零,Matlab返回Inf或NaN,后面corrcoef算出来的相关系数矩阵全是NaN,权重结果直接废掉。第一版代码没处理这个分支,跑真实数据时某个指标全部是满分,结果weights全变成NaN,排查了很久才意识到是这个原因。
第二个细节是方向问题。注释里明确写了"假设所有指标均为正向指标"。如果你的数据里有"故障率""投诉量"这类负向指标,在调用本函数之前需要先转换方向,最常见做法是做反向极差变换:X(:, j) = (x_max - X(:, j)) / (x_max - x_min)。
第三个细节是分母选择。这里用的是(x_max - x_min)也就是极差,而不是标准差或方差。CRITIC后续会单独计算标准差来做对比强度,如果标准化时用z-score,反而会破坏后续的冲突性计算逻辑,所以极差标准化是CRITIC体系里的标准做法。
3.2 对比强度与冲突系数:corrcoef背后的两个坑
function [sigma, conflict] = conflict_intensity(X_norm) % 计算CRITIC三要素中的对比强度sigma和冲突性conflict % 输入X_norm: 已经完成极差标准化的数据矩阵 % 输出sigma: 1行m列,每列的标准差 % 输出conflict: 1行m列,每列与其余指标的冲突性系数 % 1. 对比强度:极差标准化后每列的标准差 sigma = std(X_norm, 0, 1); % 样本标准差,分母为n-1 % 2. 冲突性:基于Pearson相关系数矩阵 R = corrcoef(X_norm); % m x m 相关系数矩阵 m = size(X_norm, 2); % 3. 累加冲突系数 % 对角线元素为1,1-1=0,对累加无贡献,无需特殊排除 conflict = sum(1 - R, 1); % 1 x m end这里有两个值得展开的点。第一个是std的第二个参数。std(X_norm, 0, 1)中标志0表示计算样本标准差,分母是n-1;如果改成1就是总体标准差,分母是n。在样本量较大时两者几乎没有差别,但样本量小于30时差异会影响到最终权重的排序。
第二个是corrcoef的行为。corrcoef默认计算Pearson线性相关系数,如果你的指标数据之间存在明显的非线性关系,这个方法会低估相关性。极端情况下两个指标完全非线性相关,Pearson相关系数可能趋近于0,但实际上它们的冗余度很高。遇到这种情况,可以把冲突性计算部分换成Spearman相关系数:R = corr(X_norm, 'Type', 'Spearman')。corr函数支持指定系数类型,比corrcoef更灵活。
3.3 权重合成与结果校验:一个error挡住了多少脏数据
function weights = critic_weight(X) % CRITIC赋权法主函数 % 输入X: n行m列数值矩阵,n为样本数,m为指标数 % 输出weights: 1行m列权重向量,所有元素和为1 % 使用方法: % X = readmatrix('data.xlsx'); % w = critic_weight(X); % 1. 极差标准化 X_norm = minmax_normalize(X); % 2. 对比强度与冲突性 [sigma, conflict] = conflict_intensity(X_norm); % 3. 信息量合成 C = sigma .* conflict; % 4. 归一化得到权重 total_C = sum(C); if total_C == 0 error('信息量总和为零:请检查数据是否存在全零方差列或常数列'); end weights = C / total_C; % 5. 合理性校验:权重非负且和为1 if any(weights < 0) || abs(sum(weights) - 1) > 1e-10 error('权重结果异常,请检查输入数据'); end end这个主函数没有花哨的技巧,但第4步的error和第5步的校验是血泪经验换来的。第一次跑真实项目数据时,某列数据因为传感器故障全是同一个数值,算出来的weights里出现了NaN,后续综合评分全是NaN,排查了很久才定位到是零方差列的问题。加了这层校验之后,数据有问题会在入口处直接报错,而不是带着脏数据算出看似正常的结果。
第5步的校验通常不会被触发,因为极差标准化后的标准差和1-相关系数都是非负的。但如果标准化函数出了bug,或者输入矩阵本身有NaN导致corrcoef返回NaN,这步能兜住最后一层错误。三个函数的输入输出关系可以用一个表汇总:
| 函数 | 输入 | 输出 | 关键参数 |
|---|---|---|---|
| minmax_normalize(X) | n×m原始数据矩阵 | n×m标准化矩阵 | 正向指标假设,零方差处理 |
| conflict_intensity(X_norm) | n×m标准化矩阵 | sigma(1×m), conflict(1×m) | std标志0,corrcoef类型 |
| critic_weight(X) | n×m原始数据矩阵 | weights(1×m) | sum(C)==0时报错 |
4. 运行实战:一个demo跑通加批量处理
4.1 快速跑通:写一个demo脚本
% run_demo.m % 使用CRITIC赋权法计算模拟数据的指标权重 % 生成一份模拟数据:12个样本,5个评价指标 rng(42); % 固定随机种子,保证结果可复现 X = rand(12, 5) * 100; % 12行5列,取值范围0~100 % 调用CRITIC赋权主函数 weights = critic_weight(X); % 打印每个指标的权重 fprintf('CRITIC赋权结果:\n'); for j = 1:length(weights) fprintf('指标%d: 权重 = %.4f\n', j, weights(j)); end % 验证权重和为1 fprintf('权重总和 = %.6f\n', sum(weights));运行后会看到类似这样的输出(因为随机种子固定,每次跑结果都一样):
CRITIC赋权结果: 指标1: 权重 = 0.2341 指标2: 权重 = 0.1876 指标3: 权重 = 0.2658 指标4: 权重 = 0.1493 指标5: 权重 = 0.1632 权重总和 = 1.000000因为rand生成的是均匀分布数据,每个指标的对比强度接近,权重差异主要来自冲突性。实际项目数据不会这么均匀,权重拉开差距会明显得多。
4.2 批量处理多组数据
开发中经常遇到的情况是要对多个数据集分别计算权重,比如不同年份的数据、不同分组的评价。逐个文件手动运行不现实,写个循环统一处理更靠谱:
% batch_critic.m % 批量处理多个Excel文件,统一计算CRITIC权重 files = {'group1.xlsx', 'group2.xlsx', 'group3.xlsx'}; n_files = length(files); % 预分配结果矩阵(注意:每个文件指标数需要一致) m = 5; weights_all = zeros(n_files, m); for i = 1:n_files % 读取数据 X = readmatrix(files{i}); % 缺失值处理:直接删除含NaN的行 X = rmmissing(X); % 计算权重 weights_all(i, :) = critic_weight(X); fprintf('已处理 %s\n', files{i}); end % 写入汇总表,每行对应一个文件的权重 writematrix(weights_all, 'weights_summary.xlsx');批量处理有个容易忽略的点:每个文件的指标列数必须一致。如果group1有5列指标、group2只有4列,weights_all预分配的m=5会直接报维度不匹配错误。更稳妥的做法是每次循环实时读取指标列数,动态扩展矩阵,或者提前用size检查所有文件的列数。
rmmissing是个很方便的函数,它按行删除含NaN的记录。但要注意,如果删除后剩余样本数太少,相关系数矩阵的稳定性会受影响,所以删除完最好检查一下size(X, 1)是否还在安全范围。
4.3 结果可视化:权重条形图让汇报不再干巴巴
光输出一组数字,汇报时说服力不够。我一般会加一个条形图,把权重分布直观地展示出来:
% visualize_weights.m X = readmatrix('data.xlsx'); weights = critic_weight(X); figure; bar(weights, 'FaceColor', [0.3 0.6 0.8]); xticks(1:length(weights)); xlabel('指标编号'); ylabel('权重值'); title('CRITIC赋权法权重分布'); grid on; % 在柱子上方标注具体权重值 for j = 1:length(weights) text(j, weights(j) + 0.01, sprintf('%.3f', weights(j)), ... 'HorizontalAlignment', 'center'); end这段代码运行后会生成一个带权重标注的图窗。text函数加0.01的偏移是为了让文字不贴着柱顶,数据点密集时可以调整这个偏移量。条形图能一眼看出哪些指标起主导作用,项目汇报时配合权重表一起展示,比列一大段计算过程直观得多。
5. 常见问题与避坑指南:权重异常时先查这四个地方
5.1 现象:算出来的权重和业务认知完全相反
原因:数据里有负向指标,比如"故障率""投诉率"这种越小越好的指标,直接参与极差标准化后方向反了,标准差和相关系数都失真。标准化后某个负向指标变成"越大越差"的形态,跟其他正向指标自然呈现负相关,CRITIC会把这种负相关当成"高冲突性",权重反而被抬高,真正重要的正向指标被压低。
解决:标准化前先判断指标方向。负向指标用(x_max - x) / (x_max - x_min)转换为正向形态后,再进入CRITIC流程。如果你不想改数据,也可以在minmax_normalize里加一个方向参数,让它内部处理反向指标。
5.2 现象:结果全是NaN,代码也没任何报错
原因:数据中有零方差列,或者输入矩阵含NaN。虽然标准化函数对零方差列返回0,但corrcoef在处理相关矩阵时遇到奇异矩阵,输出的相关系数变成NaN,一路传染到权重结果。
解决:在minmax_normalize里对x_max == x_min的情况返回0,同时在主函数critic_weight里加isnan校验。建议直接在标准化函数内部检测到任何NaN输入时抛错,不要等到权重算完才发现结果已经脏了。
5.3 现象:某个指标权重异常大,标准差却很普通
原因:相关系数矩阵里出现了多个负相关。CRITIC的冲突性系数公式是Σ(1 - r),负相关时r < 0,1 - r > 1,冲突性会被放大。多个负相关叠加时,权重很容易异常突出。这在数学上不算错,因为负相关指标确实贡献了较大的增量信息,但实践中要确认负相关是不是数据录入错误造成的虚假现象。
解决:先打印corrcoef(X_norm)查看相关系数矩阵。如果发现某个指标和其他多个指标负相关,先回到数据源头核对,确认无问题后再承认这个结果是合理的。如果数据本身是排名类或等级类数据,建议改用Spearman相关系数计算冲突性,避免线性相关系数对序数数据失效。
5.4 现象:换了一组数据后,权重排序完全变了
原因:CRITIC是数据驱动的客观赋权法,权重完全取决于当前数据集的分布特性。数据一变,标准差、相关系数都变,权重自然跟着变。这属于正常现象,不是bug。
解决:在业务场景里做跨期对比时,要固定同一份基准数据计算权重,再把这份权重应用到不同时期的样本上做评分。如果基准数据集每次都要更新,可以用历史数据训练权重,新数据定期回填重新校准。从管理角度讲,权重稳定性和数据代表性往往要做一个权衡,建议在项目启动时就跟业务方明确这一点。
5.5 现象:样本量只有8个,指标有10个,权重结果非常不稳定
原因:样本数小于指标数时,相关系数矩阵的自由度不足,corrcoef算出来的相关关系很大程度是噪声,CRITIC权重没有统计意义。这是CRITIC这类基于相关矩阵的方法的通病。
解决:这类问题没有代码层面的解,最有效的做法是回到业务层:要么扩充样本量到指标数的3倍以上,要么先做主成分分析或相关性聚类减少指标数量。我在一个实验室评估项目里用过聚类合并指标的方式,把10个指标压缩到4个主成分后再跑CRITIC,权重稳定性明显改善。
6. 二次开发:把CRITIC嵌进你自己的评价系统
6.1 函数接口设计建议
把critic_weight作为核心函数,输入统一为数值矩阵,输出统一为权重行向量。上层模块不管是GUI还是Web接口调用,接住这个向量往下计算综合得分即可,不需要关心内部实现。
6.2 扩展一:与熵权法交叉验证
计算CRITIC权重的同时,用熵权法算一遍,对比两组权重排序。如果排序相关系数低于0.7,说明数据里可能有异常样本或者指标方向没处理好。
% 交叉验证脚本 w_critic = critic_weight(X); w_entropy = entropy_weight(X); % 需要另一个熵权法函数 [S, P] = corr(w_critic', w_entropy', 'Type', 'Spearman');6.3 扩展二:把标准化矩阵暴露给上层
CRITIC的标准化结果是现成的综合评价基础数据,建议在critic_weight里把X_norm作为第二个返回值暴露出来,上层模块直接拿着它计算综合得分,不需要再写一遍标准化逻辑。我当时就是把X_norm和weights一起返回,下游的综合评分模块直接使用,省掉了一次重复计算。
从那以后,我每次跑CRITIC之前都强制走一遍方向检查、方差检查、相关系数矩阵目检这三步,再去看权重结果,再也没有翻过车。希望这个流程也能帮你在项目里少踩几个坑。
本文还有配套的精品资源,点击获取