ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

粗糙集约简算法详解:Python实现、可视化与数据挖掘应用

粗糙集约简算法详解:Python实现、可视化与数据挖掘应用 简介这是一份面向数据挖掘与粗糙集理论学习者的Python实现资源聚焦粗糙集属性约简算法并配套图形化交互界面可直观展示约简过程与前后属性变化适合课程实验、毕业设计或算法入门场景也能帮助研究者快速验证算法思路。压缩包共16个文件涵盖4个Python脚本分别负责程序入口、数据输入与主界面展示、3个编译缓存文件、4个工程配置XML、2个文本说明及1个CSV样例数据整体仅14KB轻量且结构清晰无需复杂依赖即可运行。该资源已被1214人学习下载适合需要动手实践粗糙集算法的数据挖掘学习者参考使用。使用时只需运行主程序文件即可启动内置样例数据与平台使用说明便于快速验证算法效果同时保留项目配置与缓存目录可辅助理解工程实现细节。需注意必须在data_dimension_reduction目录下运行避免路径读取异常。1. 粗糙集简约算法不是黑匣子一份数据挖掘入门的完整资源最常见的诉求是手里一张几万行的表格几十个条件属性想知道到底哪几列真正决定了最后的分类结果。粗糙集简约算法干的就是这件事——不依赖概率分布假设直接从决策表算出哪些属性冗余留下一个维度更低、决策能力不掉的属性子集。这份“粗糙集简约算法图形可视化”资源把约简算法和可视化脚本绑在一起前半段能算出核属性与约简结果后半段把依赖度曲线、属性贡献条形图和降维散点图画出来。适合数据挖掘课设、论文预研、做系统原型的从业者直接上手也适合想把粗糙集从公式落成代码的人。2. 先搞清楚粗糙集在算什么从等价类到核属性约简的判定标准只有一条2.1 粗糙集的对象是决策表等价类决定了你能看到什么粗糙集理论是 Pawlak 在 1982 年提出的它不要求数据满足正态分布也不需要先验概率核心对象就是一张决策表。决策表的行是样本列分两类条件属性描述对象特征决策属性给出类别。比如订单数据里金额、频次、渠道是条件属性最终标记“正常/异常”是决策属性。资源里的算法和可视化全部围绕这种表格展开跑任何实验前先把表结构确认好。等价类是理解粗糙集的入口。给定一组属性所有在属性上取值完全相同的样本被分进同一个等价类。以下面这张六条样本的决策表为例按属性 a 划分得到两个等价类 {U1, U2} 和 {U3, U4}。每个等价类内部决策属性 d 的取值完全一致说明只看 a 就能确定分类。表 1 一个极端简化的决策表示例样本abdU1000U2010U3101U4111再看属性 b按 b 划分得到 {U1, U3} 和 {U2, U4}第一个类里 d 是 0 和 1第二个类里 d 也是 0 和 1每个等价类内部都不一致。也就是说单看 b 无法确定样本类别b 是冗余属性的候选对象。粗糙集把“某个等价类内决策一致”的样本归入正域正域大小直接决定一个属性子集有多强的判别力。2.2 约简的判定标准依赖度不降且没有更小的属性子集正域样本数除以总样本数就是依赖度 γ。全集属性 {a, b} 的依赖度是 1因为每个样本单独成类类内决策必然一致。只看 {a} 时正域包含全部四条样本依赖度同样是 1只看 {b} 时正域为空依赖度是 0。这个对比说明 a 保留了完整决策能力b 拿掉后依赖度不掉。约简的定义由此而来属性子集 B 是约简当且仅当 γ_B(D) γ_C(D)并且 B 的任何真子集的依赖度都严格小于 γ_B(D)。用大白话说一是决策能力不能掉二是不能再少任何一个属性。核属性是另一个关键概念所有约简的交集。只要去掉某个属性后依赖度下降这个属性就在核里。上表去掉 b 后 γ 仍是 1所以 b 不在核里去掉 a 后 γ 变成 0所以 a 是核属性。实际数据集里核可能为空也可能包含多个属性。求核的作用是给后续贪心搜索一个可靠的起点避免从空集盲猜。资源里 compute_core 用的就是逐个删除再比较依赖度的方法小数据量上直观且不容易出错。2.3 资源包里的三个部分算法脚本、样例数据、可视化模块下载这份资源后通常会看到三块内容配合使用。算法脚本负责离散化、依赖度计算、核属性与贪心约简这是整个流程的主干样例数据是一张已经离散化或半离散化的决策表用来验证算法是否跑通可视化模块读取同一份数据和约简结果生成依赖度曲线、属性贡献条形图、PCA 散点图和对比表格。表 2 资源包的典型构成与接口模块作用主要接口约简算法离散化、正域、依赖度、核、贪心约简dependency_degree()、compute_core()、greedy_reduct()样例数据验证流程是否跑通data.csv条件属性列 决策属性列可视化模块把约简过程变成可解释图形plot_gamma_curve()、plot_attr_contribution()、plot_compare_scatter()参数配置固定分箱数、随机种子、测试集比例config.py 或函数入参我的建议是先把样例数据跑一遍确认输出的是核属性列表和一个约简结果再替换成自己的数据。替换时最容易出问题的是列名和数据类型资源里所有函数都要求传入条件属性列名组成的列表和决策属性列名保持一致就不会翻车。3. 把约简算法跑通离散化预处理与贪心属性约简代码实现3.1 数据预处理连续属性离散化的三种做法与选择理由粗糙集算法天然要求属性是离散的。连续数值属性直接参与 groupby几乎每个样本都会成为独立等价类依赖度虚高到 1约简结果失去意义。所以第一步是把连续列切成离散区间常见做法有三种等宽分箱、等频分箱、按业务阈值手动切分。等宽分箱实现最简单但数据分布不均匀时某个箱可能空掉等频分箱保证每箱样本量接近推荐优先使用业务阈值的可解释性最好适合有明确规则的场景。下面这段代码加了判断如果属性唯一值数量超过阈值才离散化否则直接转成整数避免把已经离散的类别列再切一刀。import pandas as pd def auto_discretize(df, cols, bins5, unique_threshold10): df df.copy() for col in cols: if df[col].nunique() unique_threshold: df[col] pd.cut( df[col], binsbins, labelsFalse, duplicatesdrop ) else: df[col] df[col].astype(int) return df逻辑说明pd.cut 把连续值切成 bins 个区间返回每个样本所在区间的编号labelsFalse 表示不要区间标签直接给整数编号方便后面参与 groupby。duplicatesdrop 处理边界值重复导致空箱的情况。unique_threshold 的作用是保护已经离散的列比如性别、星期几这类取值很少的字段不应当做连续属性处理。参数说明bins 是分箱数量默认 5数值越大等价类分得越细正域可能变大但约简更不稳定unique_threshold 是离散/连续的判断阈值超过它就认定是连续列一般设在 1020 之间。对不平衡分布我一般会改用 pd.qcut 做等频分箱再把分箱边界打印出来记录在实验配置里方便别人复现。3.2 正域与依赖度粗糙集算法里最核心的一段代码正域计算是粗糙集算法的心脏。给定一个属性子集先按这些属性分组得到等价类然后检查每个等价类里的决策属性是否完全一致一致的类加入正域。依赖度就是正域样本数除以总样本数。def equivalence_classes(df, attrs): groups df.groupby(list(attrs)).groups return [list(v) for v in groups.values()] def positive_region(df, cond_attrs, dec_attr): pos_indices [] for idx_list in equivalence_classes(df, cond_attrs): dec_values df.iloc[idx_list][dec_attr].unique() if len(dec_values) 1: pos_indices.extend(idx_list) return set(pos_indices) def dependency_degree(df, cond_attrs, dec_attr): pos positive_region(df, cond_attrs, dec_attr) return len(pos) / len(df)逻辑说明equivalence_classes 用 pandas 的 groupby 按指定属性列分组得到每组样本索引positive_region 遍历每个等价类unique() 只保留决策值去重结果长度为 1 说明该类内决策一致整个类并入正域。最终 dependency_degree 返回 01 之间的依赖度。这三个函数被核属性计算和贪心约简反复调用。参数说明attrs 是条件属性列表dec_attr 是决策属性列名。这里有个性能关键点equivalence_classes 每次调用都会重新 groupby属性子集一变就要重算。数据量上万后这个开销会被放得很明显第 5 章会专门讲怎么优化。小数据集上这段代码足够直接也最容易验证每一步的输出。3.3 核属性与贪心约简从核出发让结果更稳定计算核属性的思路是逐个尝试删掉一个属性看依赖度是否下降。删掉后依赖度掉了说明该属性承载了其他属性无法替代的决策信息属于核属性。贪心约简从核开始不断挑选一个剩余属性加入当前集合要求它带来的依赖度增量最大直到依赖度追平全属性依赖度。def compute_core(df, cond_attrs, dec_attr): full_gamma dependency_degree(df, cond_attrs, dec_attr) core [] for attr in cond_attrs: remaining [x for x in cond_attrs if x ! attr] current_gamma dependency_degree(df, remaining, dec_attr) if abs(current_gamma - full_gamma) 1e-9: core.append(attr) return core def greedy_reduct_with_trace(df, cond_attrs, dec_attr): target dependency_degree(df, cond_attrs, dec_attr) current compute_core(df, cond_attrs, dec_attr) trace list(current) remaining [a for a in cond_attrs if a not in current] while dependency_degree(df, current, dec_attr) target - 1e-9: best_attr, best_gain None, -1.0 for attr in remaining: gamma dependency_degree(df, current [attr], dec_attr) gain gamma - dependency_degree(df, current, dec_attr) if gain best_gain: best_gain, best_attr gain, attr if best_attr is None: break current.append(best_attr) trace.append(best_attr) remaining.remove(best_attr) return current, trace逻辑说明compute_core 对每个属性执行一次删除再比较1e-9 是浮点误差容差确保依赖度差值为 0 时不做误判greedy_reduct_with_trace 记录 trace保存属性被加入的顺序。trace 在后续可视化里直接可用画依赖度曲线时不需要重新推演一遍贪心过程。参数说明cond_attrs 顺序不影响贪心结果吗影响。当两个属性带来相同增益时先遍历到谁就选谁这会让约简结果在属性增益相同的情况下依赖列顺序。想要可复现就把 cond_attrs 排序后传入。从核出发而不是从空集出发能减少这种随机性得到的约简也更容易被解释因为核属性是必须保留的部分。3.4 参数说明分箱数、缺失值与数据规模怎么定跑通这套代码后真正要花心思的是参数。分箱数 bins 直接改变等价类边界进而改变正域大小最好多试 3、5、8 三档观察核属性是否稳定。缺失值没有默认机制groupby 会把 NaN 当成一个独立类别导致正域计算失真我一般先删除缺失行再跑离散化。数据规模决定能不能用朴素版本。几百行时 compute_core 的逐个删除没有压力几万行时每次依赖度计算都要全表 groupby时间和内存都会暴涨。常见做法是先用去重后的决策表计算重复行合并后决策表规模变小正域和依赖度理论上不受影响。资源里的样例数据规模不大新手先用它验证流程是对的别一上来就丢几百万行进去。提示如果离散化后发现某个属性几乎所有样本都落在同一个箱子里这个属性基本没有区分力后续约简大概率会被剔除可以先人工确认它是不是真的需要参与计算。4. 让约简过程可见图形可视化的四种视图与 Python 实现4.1 可视化要看什么四张图对应四个问题约简算法跑完只给一个属性列表很难说服别人“为什么保留这几个”。图形可视化的作用是把过程变成可以检查的证据链。依赖度曲线回答“属性加入顺序是否合理”曲线上升快说明前面加入的属性重要属性贡献条形图回答“每个属性单打独斗的能力”适合做汇报素材PCA 散点图回答“约简前后类别分得开吗”用于直观感受信息损失对比表回答“属性数量降了多少、准确率掉没掉”是最终结论数据。表 3 可视化视图与对应问题视图回答的问题产出物依赖度曲线属性加入顺序的价值增长gamma_curve.png属性贡献条形图单个属性对正域的贡献attr_contribution.pngPCA 散点图约简前后类别分布差异reduct_compare.png指标对比表属性数、依赖度、准确率变化reduct_compare.csv4.2 依赖度曲线与属性贡献条形图的实现依赖度曲线需要传入贪心过程的 trace。横轴是已经加入的属性个数纵轴是当前属性子集的依赖度每加入一个属性就记录一个点。属性贡献条形图则计算每个属性单独放入空集时的依赖度并按贡献大小排序。import matplotlib.pyplot as plt def plot_gamma_curve(df, trace, dec_attr): gammas [] selected [] for attr in trace: selected.append(attr) gammas.append(dependency_degree(df, selected, dec_attr)) plt.figure(figsize(6, 4)) plt.plot(range(1, len(trace) 1), gammas, markero) plt.xlabel(已加入属性数) plt.ylabel(依赖度 γ) plt.title(属性约简过程中的依赖度变化) plt.ylim(0, 1.05) plt.grid(True) plt.tight_layout() plt.savefig(gamma_curve.png, dpi150) def plot_attr_contribution(df, cond_attrs, dec_attr): items [] for attr in cond_attrs: gamma dependency_degree(df, [attr], dec_attr) items.append((attr, gamma)) items.sort(keylambda x: x[1], reverseTrue) names [x[0] for x in items] values [x[1] for x in items] plt.figure(figsize(6, 4)) plt.barh(names, values, colorsteelblue) plt.xlabel(单属性依赖度) plt.title(每个条件属性的独立贡献) plt.tight_layout() plt.savefig(attr_contribution.png, dpi150)逻辑说明plot_gamma_curve 把 trace 里的属性逐个累计调用 dependency_degree 计算累计依赖度markero 让每个点可见。plot_attr_contribution 用单属性依赖度排座次这里要特别注意单属性依赖度高不等于它一定在约简里因为约简考虑的是属性组合后的冗余关系。参数说明dpi150 满足论文插图的基本清晰度要求ylim 上限留到 1.05 避免依赖度等于 1 时曲线顶到图框。如果 trace 为空曲线图只有一个空坐标这种情况出现在决策表本身没有属性时实际使用中不会发生。属性排序用 reverseTrue 让贡献大的属性排在图上最上面符合阅读习惯。4.3 PCA 散点图约简前后类别分布怎么对比PCA 散点图把决策表映射到二维平面。离散化后的属性可以直接进入 PCA但类别型属性需要先转 one-hot否则 PCA 会把类别大小的数值关系当作真实的线性关系。对比时把全属性约简后放在两张子图里类别用决策属性着色观察两个图中类别点的重叠程度。from sklearn.decomposition import PCA def plot_compare_scatter(df, cond_attrs, reduct, dec_attr): fig, axes plt.subplots(1, 2, figsize(10, 4)) for ax, attrs, title in zip(axes, [cond_attrs, reduct], [全属性, 约简后]): data pd.get_dummies(df[attrs].astype(str)) coords PCA(n_components2, random_state42).fit_transform(data) sc ax.scatter(coords[:, 0], coords[:, 1], cdf[dec_attr], cmapviridis, s18, alpha0.7) ax.set_title(title) ax.set_xlabel(PC1) ax.set_ylabel(PC2) fig.colorbar(sc, axaxes) plt.tight_layout() plt.savefig(reduct_compare.png, dpi150)逻辑说明zip 同时遍历两张子图和两个属性配置astype(str) 先把所有取值统一成字符串避免 pandas 类型差异引发 get_dummies 的遗漏。PCA 用固定 random_state42 保证每次结果一致这在对比实验里是必须的否则重跑一次图就变一个样。参数说明n_components2 选择两个主成分解释方差比例低是正常的离散属性和 one-hot 展开后维度大量增加前两个主成分只能保留一部分信息。散点图的重点不是精确坐标而是约简前后类别点是否仍然相对分开。s18 和 alpha0.7 是为了缓解样本重叠造成的视觉遮挡样本上万时可以再调小点。5. 避坑指南粗糙集约简最容易翻车的四个位置5.1 离散化分箱一变约简结果全变现象同一份数据等宽分箱 bins5 时约简结果是 {a, b, c}换到 bins8 后变成 {a, b, c, e}核属性也跟着变了。初次跑的人往往以为算法不稳定其实问题出在预处理。原因粗糙集的等价类完全由离散取值决定。分箱边界一旦变化跨越旧边界的样本就换了邻居原本决策一致的等价类可能变成不一致正域随之缩小。等宽分箱对离群点尤其敏感一个极端值就能把一大段样本挤进同一个箱子边界不合理时无关属性也可能被误判为重要属性。解决先把每个连续属性的分布画出来再用等频分箱或业务阈值替换等宽分箱。固定 bins 后把分箱边界打印出来存成配置文件不要每次都手动改。我在 auto_discretize 里用 unique_threshold 防止已离散字段被二次切分这个习惯能避开大部分这类问题。5.2 数据上万正域计算慢到怀疑人生现象小数据集跑完约简用不了一秒换成两万行的表compute_core 跑了几分钟还没出结果内存占用也一路上涨。原因依赖度计算每次都要对当前属性集合重新 groupbycompute_core 一次性调用几十次 dependency_degree贪心循环再叠加多次。groupby 本身的成本随数据规模线性增长Python 循环再把这份成本放大。解决先去重决策表里条件属性和决策属性完全相同的行合并成一条正域不会因此改变。然后用 frozenset 做缓存键把算过的属性子集依赖度存下来避免重复计算。最后在能容忍的前提下限制候选属性数量或者对连续属性离散化后只保留样本量足够大的等价类。两万行数据跑不动时第一步去重通常就能缓解一大半。5.3 核属性与约简结果对不上现象算法算出来的约简里没有核属性或者核属性列表和某个约简交集对不齐。不少人以为代码写错了翻半天最后发现是算法策略不同。原因核是“所有约简的交集”但贪心约简从空集开始搜时如果初始位置偏离核很可能走出一个不包含核的可行解。只有当多个属性共同支撑决策信息时逐个删除单个属性可能看不出依赖度变化核属性被漏判的情况也会出现。解决代码里让 compute_core 先跑一遍再让 greedy_reduct_with_trace 从核出发初始化 current这样贪心结果天然包含核。如果资源实现是从空集开始的版本跑完后手动比一次核和结果发现不含核就换成从核启动。核属性和约简结果本来就不是“必须相等”的关系别拿它们直接对比。5.4 可视化图形看不出效果好坏现象PCA 散点图画出来全属性和约简后的图看起来都挤成一团颜色混在一起完全不知道约简到底有没有保留信息。原因只画了最终状态缺少对照和量化指标。PCA 只保留前两个主成分原本就被压缩的大部分方差根本显示不出来样本多时散点重叠严重散点图只能用于辅助判断不能单独当证据。解决把依赖度曲线、属性贡献条形图、PCA 对比图和指标表四件套一起输出。指标表列清楚属性数、正域大小、依赖度、分类准确率汇报时先给指标表再给散点图别人就不会说约简效果“看着没区别”了。散点图里把 alpha 调低只保留轮廓也有帮助。6. 把约简结果验证一遍留出法对比与可视化报告输出6.1 留出法对比全属性与约简属性在同一条数据上的公平比拼约简只是降低了属性维度真正的价值要看分类模型能不能保住准确率。用同一份训练集和测试集分别跑全属性和约简属性才是一组公平对比。关键点有两个train_test_split 必须指定同一个 random_state确保两边拿到的训练测试样本完全一致决策树模型也用同一个 random_state排除模型随机性对结果的干扰。from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score def compare_full_reduct(df, cond_attrs, reduct, dec_attr, test_size0.3): y df[dec_attr] X_full pd.get_dummies(df[cond_attrs].astype(str)) X_reduct pd.get_dummies(df[reduct].astype(str)) X_tr, X_te, y_tr, y_te train_test_split( X_full, y, test_sizetest_size, random_state42 ) X_red_tr X_te # 占位实际写法见下行 X1_tr, X1_te, y1_tr, y1_te train_test_split( X_full, y, test_sizetest_size, random_state42 ) X2_tr, X2_te, y2_tr, y2_te train_test_split( X_reduct, y, test_sizetest_size, random_state42 ) model_full DecisionTreeClassifier(max_depth5, random_state42).fit(X1_tr, y1_tr) model_reduct DecisionTreeClassifier(max_depth5, random_state42).fit(X2_tr, y2_tr) acc_full accuracy_score(y1_te, model_full.predict(X1_te)) acc_reduct accuracy_score(y2_te, model_reduct.predict(X2_te)) return acc_full, acc_reduct逻辑说明全属性特征矩阵和约简属性特征矩阵分别做 one-hot再分别划分数据集。由于 random_state 都是 42两次划分在样本层面是对齐的只是特征维度不同。max_depth5 限制树深避免过拟合把准确率虚高这一项在对比时必须固定。参数说明test_size0.3 表示三成样本做测试数据量小可以提到 0.2max_depth 可以试试 37 三档选结果差异最小的档位。要注意两个模型准确率差几个点以内才算可接受如果约简后准确率下降超过 5%优先检查离散化参数是否合理。6.2 把结果归档成一张表汇报和复现都少踩坑实验做完了最怕换台机器重跑一遍结果对不上。我会把关键中间产物全部保存下来分箱边界、核属性列表、约简结果、依赖度曲线、PCA 图、分类对比 CSV。CSV 里固定放四列属性数、正域大小、依赖度、准确率全属性和约简后各一行这样别人一眼就能看到降维换来了什么代价。result_rows [ {属性数: len(cond_attrs), 正域大小: len(positive_region(df, cond_attrs, dec_attr)), 依赖度: dependency_degree(df, cond_attrs, dec_attr), 准确率: acc_full}, {属性数: len(reduct), 正域大小: len(positive_region(df, reduct, dec_attr)), 依赖度: dependency_degree(df, reduct, dec_attr), 准确率: acc_reduct}, ] import pandas as pd pd.DataFrame(result_rows).to_csv(reduct_compare.csv, indexFalse)这套归档方式看着简单实际帮我避免了无数次重跑。从那以后我每次跑粗糙集约简都强制走一遍固定流程先固定离散化参数再算核和约简然后跑对比分类最后把四张图加一张 CSV 存进同一个实验目录。中间任何一步觉得玄学就回去看中间产物。希望帮到你。本文还有配套的精品资源点击获取
返回列表