
简介面向数据挖掘与知识发现场景这份MATLAB代码资源围绕粗糙集理论实现属性重要度计算与权重求解适合需要处理不完全信息系统的研究者、学生及工程实践者。资源共35个文件以32个.m脚本为主附带2个.asv自动备份和1个.mat示例数据压缩包仅38KB轻量且便于快速部署。已有297人学习下载。代码覆盖多种粗糙集变体包括基于核的模糊粗糙集特征评价与选择、邻域粗糙集特征约简、模糊偏好粗糙集、基于邻域互信息的特征选择等同时提供mRMR与互信息相关实现。包内还附带标准示例数据与演示脚本可快速体验完整流程或对照源码理解依赖度、覆盖度、信息增益等指标的计算逻辑为后续权重分配和决策分析提供可扩展的基础工具。1. 粗糙集重要度计算权重本质是在没有先验信息时从数据里要权重假设你要给一套评价指标体系定权重。专家打分主观层次分析法要做一致性检验熵权法又和决策目标脱钩粗糙集理论不假设数据分布不预置先验权重只看删除某个条件属性后决策不冲突的对象数量变化多少。变化越大属性越重要这就是粗糙集重要度计算权重的基本逻辑。标题里的matlab的粗糙集代码通常就是在 matlab 里实现等价类与正域计算再对每个条件属性算重要度最后归一化得到权重。这类代码在论文复现、毕设实验和工业评价系统里都很常见。matlab 的unique和矩阵索引让等价类划分非常直接但必须理解正域定义否则改几个参数就会得到不合理结果。这篇文章写给要自己写代码的人。你先理解计算顺序再拿到可直接运行的 matlab 实现最后处理离散化、零重要度和运行效率这些实际问题。2. 从正域到重要度粗糙集权重的模型怎么搭2.1 决策表、条件属性与等价类粗糙集建模的第一步是把数据组织成决策表。每一行是一个对象每一列是一个属性除了决策属性外其余都是条件属性。例如在供应商评价中条件属性可以是价格、交期、质量合格率决策属性可以是通过/不通过这样带有分类意义的字段。粗糙集所有运算都建立在对象的不可分辨关系上当两个对象在所有条件属性子集上的取值完全一样就认为它们在当前子集下不可分辨同属一个等价类。这种等价类划分和聚类不同它不计算距离只看取值是否严格相等。实际代码里等价类通常不是显式存成一个哨兵结构而是用一个标识向量表示。unique(data(:, attrs), rows)返回的第三个值就是每个对象所属等价类的编号。这是整个粗糙集 matlab 代码最重要的一个输出因为后续正域计算只依赖这个编号。需要注意这里的相等对连续数值属性是严格相等如果你直接丢入 3.14159 和 3.1416它们会被判为不同对象所以第 4 章会专门讲离散化。2.2 依赖度正域大小决定属性子集的决策确定性给定条件属性集合 B粗糙集把决策表中可以被 B 完全确定的对象集合称为正域 POS_B(D)。具体判断办法是看每个等价类如果某个等价类里所有对象的决策值彼此一致这类对象就属于正域如果同一个等价类里出现了两种或更多决策值说明这些对象仅仅依据 B 无法区分也就不进入正域。于是依赖度定义为正域对象数占总对象数的比例$$ \gamma_B(D) \frac{|POS_B(D)|}{|U|} $$依赖度越接近 1说明 B 对决策属性的解释能力越强如果某些等价类混杂了多个决策值说明信息还不够依赖度会小于 1。这个值就是后面所有权重计算的基础。matlab 实现中依赖度不关心正域的行号具体是什么只关心个数所以可以用numel(pos)一步得到。2.3 属性重要度的两种算法和权重公式有了依赖度就能定义单个条件属性的重要度。最常见的是删除影响法先算全部条件属性 C 对决策 D 的依赖度 $\gamma_C(D)$然后对每个 $a \in C$计算去掉 a 以后剩余属性 C - {a} 的依赖度两者之差就是 a 的重要度。$$ sig(a) \gamma_C(D) - \gamma_{C-{a}}(D) $$另一种是前向增量法从空集开始逐个加入属性看依赖度增量适合做属性约简而不是定权。标题里说的重要度计算权重更常用删除影响法因为每个属性的重要度都对应一个绝对变化量物理含义是去掉它损失多少确定能力。下表是两种方法的分工方法计算公式典型用途删除影响法$\gamma_C(D) - \gamma_{C-{a}}(D)$权重分配前向增量法$\gamma_{B\cup{a}}(D) - \gamma_B(D)$属性约简得到重要度后用归一化把负值和零值处理干净$$ w_i \frac{sig_i}{\sum_{j1}^{m} sig_j \epsilon} $$其中 $\epsilon$ 是一个非常小的正数避免所有权重为零时除零。这段归一化在 matlab 里可以这样保护importance zeros(1, m); % 重要度数组由后续正域计算填充 if sum(importance) 0 w importance / sum(importance); else w ones(1, m) / m; % 所有重要度都为零时的兜底策略 endeps是浮点相对精度实际运算里我更倾向用上面的sum 0判断而不是依赖eps强撑分母。如果所有权重都为零说明所有属性的删除都不影响依赖度此时均匀权重只是占位不能把它解释成每个属性等重要。3. matlab 粗糙集权重代码你直接能跑的最小实现3.1 数据准备离散数值决策表怎么摆代码里的数据约定非常死先写清楚。你的数据必须是一个 matlab 数值矩阵data行是样本前m列是条件属性最后一列是决策属性。所有值都必须是离散整数或者已经编码过的数值最忌讳直接把连续测量值放进来。如果条件属性本身是分类比如低1、中2、高3可以直接用 1、2、3 编码如果属性名字是字符串先用grp2idx转成整数。决策属性也必须是分类值不能是连续收益率。一个小陷阱是列顺序。代码里用data(:, end)指决策列如果你把决策列放在前面正域判断读到的就不是决策而是某个条件属性最后结果会完全错掉。更稳妥的做法是把决策列单独作为参数传入但为了保持最小可读版本这里约定决策列在最后一列。下面是一个 8 行 4 列的示例数据前 3 列条件属性最后 1 列决策。3.2 正域计算函数等价类拆分与决策一致性判断正域函数是全流程的核心它接收决策表和参与计算的条件属性列索引返回正域行号。实现思路是先用unique(X, rows)对属性子集的所有取值组合去重拿到等价类编号ic然后遍历每个编号对应的行用all(dec dec(1))判断该组决策是否完全一致一致的组全部放入正域。function pos positive_region(data, attr_idx) % data: 数值决策表最后一列为决策列 % attr_idx: 参与计算的条件属性列索引向量 % pos: 正域行号按升序排列 X data(:, attr_idx); % 取出条件属性子集 [~, ~, ic] unique(X, rows); % ic 是每个对象的等价类编号 pos []; for k 1:max(ic) rows find(ic k); % 该等价类包含的所有对象行 dec data(rows, end); % 取这些对象的决策值 if all(dec dec(1)) % 决策值完全一致 pos [pos; rows]; % 累积正域对象 end end pos sort(pos); % 排序便于调试和对比 end说明几个参数。attr_idx可以是任意列索引组合比如[1 3]表示第一列和第三列同时参与不可分辨关系当它是完整条件属性集1:m时得到全局正域。ic的取值从 1 开始每个值代表一种取值组合所以循环上界是max(ic)。dec是从等价类行里取出的决策列表all(dec dec(1))判断这一组是否只有一种决策如果等价类只有一个对象条件恒真该对象必然进入正域。这个版本用 for 循环累积行号数据量小几千行以内完全够用。第 4 章会给一个基于accumarray的加速版本但在你确认算法逻辑之前先用这个可读版本排查问题。3.3 主流程删除属性重算依赖度并输出权重主流程分成四步。第一步计算完整条件属性集的正域和依赖度第二步对每个属性单独删除计算剩余属性的正域和依赖度第三步用全局依赖度减去删除后的依赖度得到重要度第四步归一化输出。下面是完整可运行的主脚本% 粗糙集重要度计算权重最小可运行版本 % data 为离散数值决策表最后一列为决策属性 data [1 2 1 1; ... 1 2 1 1; ... 1 1 2 2; ... 2 2 2 1; ... 2 1 1 2; ... 2 1 2 1; ... 1 2 2 2; ... 2 1 1 1]; cond_idx 1:size(data, 2) - 1; % 前 3 列是条件属性 U size(data, 1); % 对象总数 pos_all positive_region(data, cond_idx); gamma_all numel(pos_all) / U; % 全集依赖度 m numel(cond_idx); importance zeros(1, m); for i 1:m sub_idx cond_idx(cond_idx ~ i); % 去掉第 i 个属性 pos_sub positive_region(data, sub_idx); gamma_sub numel(pos_sub) / U; importance(i) gamma_all - gamma_sub; end if sum(importance) 0 weights importance / sum(importance); else weights ones(1, m) / m; end fprintf(条件属性索引: %s\n, mat2str(cond_idx)); fprintf(重要度: %s\n, mat2str(importance)); fprintf(权重: %s\n, mat2str(weights));cond_idx ~ i会生成一个逻辑数组用来剔除当前属性cond_idx必须是一个区间向量不能有重复值否则条件和下标会不匹配。gamma_all在整段循环中保持不变因为它是相对完整属性集的基准每个属性的重要度都减去它自己删除后的依赖度不是两两配对的差值。weights最终和为 1但如果某个属性的重要度为 0对应权重就是 0这在粗糙集权重里是合法结果表示该属性在当前决策表中不提供额外决策信息。这个示例数据跑出来的结果是3 个属性的重要度分别是[0.375 0 0.5]权重是[0.4286 0 0.5714]。第 2 个属性因为删除前后正域没有缩减被判为冗余属性。你可以把这组数据手算一遍和fprintf输出对照验证代码逻辑。注意代码本身没有检查 data 是否离散步也没有检查 NaN。实际数据集进入这段代码前必须先做完缺失值处理和离散化否则结果没有意义。这一点在下一章展开。4. 离散化、零重要度和计算卡顿实际问题怎么处理4.1 连续属性先分箱别让浮点精度毁掉等价类粗糙集里的不可分辨关系是严格相等浮点连续值几乎不可能出现两个完全相同的对象于是每个对象都会单独成类正域会等于全集依赖度变成 1所有属性重要度都变成 0。这种情况不是代码 bug而是数据没有离散化。常见做法是等距离散、等频离散和基于决策的监督分箱。matlab 里最顺手的是discretize把连续值映射为区间编号edges [0 60 80 100]; % 人工指定边界 data(:, 1) discretize(raw_X1, edges);等距离散用linspace(min_x, max_x, K)生成边界等频用prctile找到分位数边界。下表列出三种方式的取舍离散化方式matlab 实现适用场景等距离散discretize(x, linspace(min(x),max(x),K))数据分布均匀等频离散discretize(x, prctile(x, 0:100/K:100))长尾数据监督分箱先按决策分组再合并相邻区间分类任务不是分得越细越好。区间数越多等价类越碎正域越接近全集重要度区分度下降区间数太少信息损失大正域变小。我一般会从 K5 试到 K10比较importance的排序是否稳定。若某个属性在 K6 和 K8 下排序变化很大说明它的边界效应强权重结论需要谨慎。4.2 重要度全为零或正域为空先检查数据和决策列如果运行后importance全是 0通常是因为gamma_all和所有gamma_sub都等于某个同一值。常见原因有三个一是上面说的连续数据离散化没做但此时gamma_all应该是 1不是 0二是决策列不是分类值比如放了连续得分等价类内决策几乎不相等positive_region返回空数组所有依赖度都变成 0三是数据里有NaN。NaN 的问题比较隐蔽。unique不会把 NaN 当成同一类含 NaN 的对象会被拆成多个含 NaN 的等价类而且 NaN 与任何值比较永远是 false会使all(dec dec(1))意外为 false。建议在进入算法前先执行assert(~any(isnan(data), all), 决策表包含NaN请先填充或删行); assert(all(isfinite(data(:))), 决策表包含Inf);如果你的决策列是连续数值可以先对它做区间化比如把收益率分成熟练和一般再放进最后一列。即使决策列已经是0/1形式也要确认它不是逻辑型数组导致的隐式转换错误。matlab 里true和1在比较时等价但建议用double(data)明确转换。4.3 重复计算优化缓存等价类编号或者用 accumarray上面的positive_region在循环里每次都对sub_idx调用unique(X,rows)。当属性只有 3、5 个时问题不大属性到 20 个、样本到上万行就会明显卡顿。一个简单优化是把等价类编号复用对每个条件属性先用grp2idx把所有取值压缩为从 1 开始的整数再用多列整数做unique时速度会略好一些但真正省时间的是用accumarray一次完成分组判断。function pos positive_region_fast(data, attr_idx) X data(:, attr_idx); [~, ~, ic] unique(X, rows); flags accumarray(ic, data(:, end), [], (d) all(d d(1))); pos find(flags(ic)); % flags(ic) 把每个等价类标志映射回每个对象 endaccumarray(ic, data(:, end), [], fun)会把相同ic的决策值收集成一组传给函数句柄(d) all(d d(1))返回 1 表示该等价类决策一致0 表示不一致。flags(ic)是向量化索引如果第 j 个对象所属类的 flag 为 1就说明它是正域内的对象find得到行号。这个版本不建中间 cell也不循环适合几千到几万行数据。如果有人把attr_idx换成多个属性组合来搜索约简还可以额外缓存每个组合计算后的flags因为同一组合可能在不同步骤被重复使用。但要注意缓存会占内存组合数按指数增长时反而得不偿失。我自己通常只在属性数不超过 12 时开缓存超过 12 就考虑贪心约简而不是穷举组合。5. 从权重到决策排序、一致性与一个快速验证技巧5.1 用权重排序并回到正域做一致性核对得到权重不是终点。先把weights从大到小排序观察排序结果是否和业务直觉一致。如果某个属性在业务上明确重要但权重为零很可能是数据本身没有把这个属性和决策的关联表达出来例如该属性在现有样本中几乎不变或者它和其他属性高度相关。粗糙集权重的解释边界就在这里它度量的是数据层面的重要度不是因果层面的重要性。想要增强解释可以逐个删除权重最高的属性观察依赖度下降量如果删除后依赖度下降最大说明该权重排序和正域逻辑一致。5.2 用重复抽样验证排序稳定性粗糙集对样本敏感一个冲突等价类的出现就可能让正域减少很多。常见做法是做不放回抽样每次随机取 80% 的行重新计算权重排序重复几十次然后统计每个指标排名进入前三的比例。先把第三章主流程封装成函数rough_weight(data)返回值是权重向量然后做循环验证rng(1); B 50; order zeros(B, m); for b 1:B idx randperm(U, round(U * 0.8)); % 不放回抽样 80% w rough_weight(data(idx, :)); [~, order(b, :)] sort(w, descend); end histogram(order(:, 1), 1:m); % 看最高权重属性落在第几名randperm(U, round(U * 0.8))不会出现重复行避免放回抽样把同一行复制多份、人为制造等价类合并。如果几十次抽样里最高权重属性经常互换说明数据样本量不够或者离散化边界不稳权重排序不能作为唯一决策依据。5.3 免费代码资源里最该警惕的雷网上有很多粗糙集免费代码但很多直接拿连续数据跑甚至把决策列写死在函数内部换成自己的数据就报错。你拿到任何一份免费 matlab 粗糙集代码第一件事是检查它的决策列约定和离散化入口。如果源码里没有离散化步骤你自己补上如果正域函数只返回数量而不返回行号建议改成返回行号否则无法调试。先用上面的最小可运行版本做对照组跑通自己的数据再决定要不要用别人的完整工具箱。本文还有配套的精品资源点击获取