简介:配电网故障定位是电力系统可靠运行的关键环节,资源面向电气工程专业学生、电力系统研究人员及故障诊断算法开发者,提供一套基于Matlab的粗糙集理论故障定位实现方案,可在传感器误差、通信延迟等不完整信息条件下辅助识别故障发生区域。压缩包共4个文件,以m格式主程序源码为核心,配合两个txt数据文件用于规则学习与测试,另含asv自动备份文件,整体体积仅5KB,结构精简便于快速上手。截至目前已有519人浏览学习。通过运行源码可直观了解数据预处理、属性选择、等价类划分及决策规则生成等完整流程,并可根据实际配电网场景调整输入数据,观察不同故障条件下的定位响应,适合用作算法仿真、课程设计或工程预研的起点。
1. 粗糙集配电网故障定位:当FTU数据不全还能不能快速找到故障区段
10kV馈线跳闸后,调度端最头疼的往往不是“有没有故障”,而是“故障到底在哪一段”。尤其FTU漏报、误报甚至批量离线时,死板的开关逻辑判据会立刻卡壳——少一个输入,判断链就断了。粗糙集在这个场景里的路子完全不同:它把每一条故障样本当作决策表的一行,通过属性约简保留真正决定故障区段的开关组合,输出的是“只看这几个开关怎么报,就能定区段”的规则,天生容忍冗余和不完整信息。这篇笔记按“建决策表→实现约简→在线推理→避坑”的顺序,在MATLAB里从零写出一套可以跑的定位程序。适合做配网自动化课题、毕设题目带“粗糙集matlab”、或者想用数据驱动方法替代人工判据的从业者。
2. 先建模再约简:配电网故障定位决策表怎么搭
粗糙集处理的对象是决策表。配电网故障要走进粗糙集算法,第一步不是写程序,而是把“故障发生时FTU上报了什么、故障最终出在哪”整理成一张规整的表。这张表的质量直接决定后面约简结果能不能用,数据组织这一环偷懒,后面所有坑都会提前埋下。
2.1 条件属性与决策属性:从FTU上报信息到粗糙集输入
决策表由条件属性和决策属性组成。配电网故障定位里,条件属性就是各开关处FTU的上报状态,常见做法是把每个开关的故障电流信息编码成一个属性:0表示无故障电流,1表示有故障电流。如果现场信号能区分方向,可以扩展成三值编码:-1表示反向故障电流,0表示无电流,1表示正向故障电流。决策属性就是故障区段编号。例如一条从母线引出、经过10个区段的辐射状馈线,区段5发生故障时,母线出口开关到区段5首端的开关都会上报有故障电流,区段5之后的开关上报无故障电流,那么这一行样本就是[1 1 1 1 1 0 0 0 0 0,决策=5]。
粗糙集并不要求条件属性完全精确。它基于“不可分辨关系”工作:两个样本在所选属性上取值相同,就被视为不可分辨。正因如此,FTU漏报一两个开关状态不会让整个推理失效,只要漏掉的属性不在约简结果里,规则照样能命中。这也是粗糙集区别于朴素贝叶斯、决策树这类黑匣子模型的地方,它给出的规则是“开关S2过流且S5不过流,则故障在区段4”,现场人员能直接对照开关编号复核。
属性选择的常见原则是:把所有参与故障判别、又可能被采集到的开关状态都放进来,宁多勿缺。约简环节会自动剔除冗余属性,放进决策表的属性多一些不会破坏最终规则的可解释性。但也要避免把与故障无关的遥测量(比如环网柜温度、电池电量)塞进来,那只会增大决策表规模和冲突检测难度。
2.2 离散化与冲突检测:进区分矩阵前的数据预处理
如果条件属性里混有连续量,比如零序电流幅值、故障电流峰值,必须先做离散化。粗糙集原本是处理离散符号数据的,连续值不能直接进区分矩阵。常见做法有三种:等宽离散化、等频离散化、按决策类别边界的朴素离散化(Naive Scaler)。配电网故障定位里,FTU上报的过流状态本身已经是布尔量,但如果你手头是录波文件里读出的电流幅值,我一般用等频离散化,让每个区间样本数大致相同,避免某个属性值域过宽导致规则覆盖失衡。
离散化后的决策表要做三件事:去重、查冲突、补缺失。去重是去掉完全相同的行;冲突检测是找出“条件属性完全相同但决策属性不同”的样本对,这种行说明数据采集时有误报或故障本身存在多解,需要人工核对或标记为不确定样本;缺失则要决定填充策略,详见第5章。MATLAB里可以直接用unique和逻辑索引处理,下面给出一个组织决策表的参考代码。
冲突和缺失如果不处理,区分矩阵构建时会得到错误的不可分辨关系,约简出来的属性组可能根本无法在真实场景中区分故障区段。这个环节宁可多花半小时人工核对,也不要直接跑算法。
2.3 MATLAB里组织决策表:矩阵、去重与冲突样本的排查
我习惯用纯矩阵保存决策表:每一行是一个故障样本,前N列是条件属性,最后一列是决策属性(区段编号)。这样后续区分矩阵、约简、规则匹配都能直接用矩阵运算,不用引入复杂的数据结构。离散化用的映射表单独存一份,方便把规则还原成人话。
% 决策表组织示例: 10区段, 每段首端一个开关 % 条件属性10个, 最后1列是决策属性(区段号) nSec = 10; samples = []; for k = 1:nSec % 故障在区段k -> 开关1~k过流置1, k+1~10置0 vec = [ones(1, k), zeros(1, nSec - k)]; for rep = 1:3 % 每个区段生成3个基线样本 samples = [samples; vec, k]; end end % 去重: 合并完全相同的故障样本 [DT, ia] = unique(samples, 'rows'); % 冲突检测: 条件相同但决策不同的样本组 condCols = 1:nSec; decCol = nSec + 1; [condOnly, ~, ic] = unique(DT(:, condCols), 'rows'); conflictIdx = []; for r = 1:size(condOnly, 1) rows = find(ic == r); if numel(unique(DT(rows, decCol))) > 1 conflictIdx = [conflictIdx; rows]; end end fprintf('去重后样本数: %d\n', size(DT, 1)); fprintf('冲突样本组数: %d\n', numel(conflictIdx));代码逻辑说明:内层循环按区段生成基线样本,每个区段重复3次是为了让后续规则置信度统计有足够的支持计数。unique先去重,再按条件列分组,检查每组内决策值是否唯一,conflictIdx收集的就是条件相同但决策不同的样本行。参数上nSec是区段数,决定特征维度;实际工程里样本应该来自历史故障记录或仿真枚举,而不是这样简单复制。如果FTU数据是三值编码,把ones替换成-1/0/1组合即可,决策表结构不变。跑完这段,DT就是能直接交给粗糙集算法的输入。
3. 用MATLAB从零实现粗糙集约简:区分矩阵、核与规则提取
理论已经立住,这一章进入算法实现。粗糙集在MATLAB里落地的难点不在概念,而在区分矩阵的表示方式和约简的搜索策略。下面三个函数是整套定位程序的核心,可以直接拼进自己的脚本里。
3.1 区分矩阵构建:只比较决策不同的样本对
区分矩阵的定义是:对任意两个对象xi和xj,如果它们的决策属性不同,就把“取值不同的条件属性集合”记入矩阵元素M(i,j);如果决策相同,M(i,j)记为空白。为什么要只比较决策不同的样本对?因为我们要找的是能区分不同故障区段的属性组合,相同区段的样本不需要被区分,比较它们只会引入大量无意义的噪声属性。
理论上区分矩阵是n×n的上三角,但工程中决策表往往有几百行,全量存成矩阵很浪费内存。我用元胞数组只存需要比较的项,同时顺手完成核属性计算:如果某个M(i,j)里只有一个属性,这个属性就是核属性,所有约简都必须包含它。
function [discMat, coreAttr] = buildDiscMatrix(DT, condCols, decCol) % DT: 决策表, 行=样本, 列=属性 % condCols: 条件属性列索引向量, decCol: 决策属性列索引 % discMat: 元胞数组, 元素为能区分两个样本的属性集合 % coreAttr: 核属性列索引 n = size(DT, 1); discMat = cell(n, n); coreAttr = []; for i = 1:n for j = (i+1):n % 只处理决策不同的样本对 if DT(i, decCol) ~= DT(j, decCol) diffAttr = condCols(DT(i, condCols) ~= DT(j, condCols)); discMat{i, j} = diffAttr; % 单个属性即可区分的决策对 -> 该属性属于核 if numel(diffAttr) == 1 coreAttr = union(coreAttr, diffAttr); end end end end fprintf('核属性: %s\n', mat2str(coreAttr)); end参数说明:condCols和decCol必须与决策表列结构对应,比如条件属性是1到10,决策是11,就传condCols=1:10;decCol=11。这个函数的复杂度是O(n²·m),n是样本数,m是条件属性数,几百个样本内可以接受。样本超过几千时,建议先按决策值分组、只在组间比较,计算量至少减半。需要注意,核属性的“单属性区分项取并集”只在经典Pawlak粗糙集下成立,如果用变精度粗糙集或容差关系粗糙集,核的定义会变化,本文只讨论经典版本。
3.2 约简求解:贪心算法与核优先级
得到区分矩阵后,理论上可以构造区分函数求最小析取范式,得到全部约简。构造方法是:对每个M(i,j)里的属性做析取,再把所有样本对的析取项做合取,化简后的析取范式每一项就是一个约简。问题是这是NP难问题,属性超过20个后穷举基本不可行。配电网故障定位决策表的条件属性一般是10个左右,小规模可以用穷举验证,规模化时我推荐贪心约简。
贪心策略是:先拿核属性打底,然后在剩余属性里反复选择“当前能区分最多尚未被区分决策对”的属性加入约简集,直到所有决策不同的样本对都已被区分。这样得到的约简不保证最小,但和精确解非常接近,计算时间可以接受。
function reduct = greedyReduct(DT, condCols, decCol, coreAttr) % 基于区分覆盖的贪心约简 n = size(DT, 1); reduct = coreAttr; % 从核属性出发 covered = false(n, n); % 记录哪些决策对已被约简集区分 % 计算核属性已经覆盖的决策对 for i = 1:n for j = (i+1):n if DT(i, decCol) ~= DT(j, decCol) cands = condCols(DT(i, condCols) ~= DT(j, condCols)); if ~isempty(intersect(cands, reduct)) covered(i, j) = true; end end end end remaining = setdiff(condCols, reduct); while ~all(covered(:)) && ~isempty(remaining) bestGain = 0; bestAttr = []; for a = remaining gain = 0; for i = 1:n for j = (i+1):n if ~covered(i, j) && DT(i, decCol) ~= DT(j, decCol) cands = condCols(DT(i, condCols) ~= DT(j, condCols)); if ~isempty(intersect(cands, a)) gain = gain + 1; end end end end if gain > bestGain bestGain = gain; bestAttr = a; end end if bestGain == 0 break; % 剩余属性无法再区分任何新的决策对 end reduct = [reduct, bestAttr]; remaining = setdiff(remaining, bestAttr); % 更新覆盖标志 for i = 1:n for j = (i+1):n if ~covered(i, j) && DT(i, decCol) ~= DT(j, decCol) cands = condCols(DT(i, condCols) ~= DT(j, condCols)); if ~isempty(intersect(cands, bestAttr)) covered(i, j) = true; end end end end end end逻辑说明:covered(i,j)=true表示“已选入约简的属性集合足以区分该样本对”。每轮贪心挑选能新增覆盖最多样本对的属性,时间复杂度O(n²·m²),在10区段、几十个样本下毫秒级完成。这段代码为了函数独立,每轮都重新计算cands;工程上更好的做法是把buildDiscMatrix的结果缓存进来,直接用discMat{i,j}做集合运算,能省掉大量重复比较。
参数调整建议:如果发现某个属性始终不被选中,但删掉它之后定位又出错,多半是贪心陷入局部最优。此时可以在核属性之外,把现场人员认为重要的开关属性排在遍历顺序前面,让贪心优先选中它们,结果会更贴近实际经验。小规模场景可以先用nchoosek枚举子集做精确约简,验证贪心结果是否正确,再在真实规模上用贪心。
3.3 规则生成和置信度:让定位结果可解释
约简属性集合定下来后,就可以生成规则。规则的形态是:如果约简属性取值满足某种组合,则决策属性为某个区段。生成方法是对决策表按约简属性列做分组,统计每组内决策值的分布;如果某组决策值唯一,直接生成一条确定规则;如果不唯一,生成带置信度的模糊规则,置信度等于该决策值样本数除以组内总样本数。
实际规则库里往往既有确定规则又有带置信度的规则,在线推理阶段按置信度加权投票。下面这个函数输出规则矩阵:每一行是“约简属性取值 + 决策值 + 置信度”。
function rules = extractRules(DT, reductCols, decCol) % 按约简属性组合统计决策分布, 生成规则表 % 输出rules: 每行 = [约简属性取值..., 决策值, 置信度] nRed = numel(reductCols); [comb, ~, ic] = unique(DT(:, reductCols), 'rows'); rules = zeros(0, nRed + 2); for r = 1:size(comb, 1) rows = find(ic == r); decVals = unique(DT(rows, decCol)); for d = 1:numel(decVals) cnt = sum(DT(rows, decCol) == decVals(d)); conf = cnt / numel(rows); rules = [rules; comb(r, :), decVals(d), conf]; %#ok<AGROW> end end end逻辑说明:unique按约简列分组,比如约简结果是原始第2列和第5列,规则就是“属性2=1且属性5=0 → 区段k,置信度0.9”。reductCols的顺序决定rules中条件列的顺序,后面推理时必须用同一顺序取原始属性。需要留意的是,如果某个区段只有1到2条样本,置信度会在0和1之间剧烈摆动,规则不稳定,实际工程建议每个决策类至少保留5条以上样本。规则生成后可以打印出来对比:约简前属性多时规则组合爆炸,约简后往往只剩2到4个开关属性,规则数量骤减,这就是粗糙集对可解释性的直接收益。
4. 在线故障定位推理:规则匹配、投票机制与10区段完整算例
规则库建好了,真正要用的时候是另一套逻辑:新来一个故障事件,FTU上报一组开关状态,拿它跟规则表匹配,输出定位区段。这章的难点在匹配策略——现场数据不会跟训练样本一模一样,漏报、误报、新故障模式都会出现,匹配逻辑必须留出余量。
4.1 匹配策略:全匹配、缺属性匹配与多规则加权投票
规则表结构里,每一行rules(r,:)的最后一个是置信度,倒数第二个是决策值,前半部分是约简属性取值。在线推理时,我一般设计三个递进的匹配层次。
第一层是全匹配:输入状态向量与规则条件部分完全一致,直接把该规则的决策值作为候选输出;命中多条规则且决策值相同时,取置信度最高的作为主判据。第二层是部分匹配:输入向量有缺失属性(某台FTU没上报)时,跳过缺失列,只要非缺失列与规则一致就算命中。这里有一个关键点:缺失信息必须单独标记,不能简单填0,否则会把“没上报”误当成“无故障电流”。第三层是多规则加权投票:所有命中规则按置信度累加到各自决策值的得分上,得分最高的区段为最终定位结果,得分低于阈值就输出“无法定位”,而不是硬凑一个区段。
加权公式我一般用:score(k) = Σ 命中规则的置信度。更稳妥的做法是乘上规则支持度(该规则覆盖的训练样本数占总样本比例),样本不平衡时不会被高频区段带偏。下面是推理函数的核心代码,特别注意规则条件列的顺序问题。
function [zone, scoreVec, hitRules] = locateFault(rules, reductCols, condVec, missingCols) % rules: extractRules生成的规则表, 每行=[约简属性值..., 决策值, 置信度] % reductCols: 约简使用的原始属性列号 % condVec: 当前FTU上报的完整状态向量 % missingCols: 缺失属性列索引, 没有则传[] nRed = numel(reductCols); nZones = max(rules(:, nRed + 1)); scoreVec = zeros(1, nZones); hitRules = []; for r = 1:size(rules, 1) hit = true; for c = 1:nRed attr = reductCols(c); % 当前规则列对应的原始开关号 if ismember(attr, missingCols) continue; % 缺失属性跳过, 不参与比对 end if rules(r, c) ~= condVec(attr) hit = false; break; end end if hit conf = rules(r, end); zoneVal = rules(r, nRed + 1); scoreVec(zoneVal) = scoreVec(zoneVal) + conf; hitRules = [hitRules; r]; %#ok<AGROW> end end [~, zone] = max(scoreVec); if max(scoreVec) < 0.5 % 阈值可按规则置信度分布标定 zone = -1; % -1表示置信度不足, 无法定位 end end逻辑说明:这条函数对每条规则逐属性比对,attr = reductCols(c)是这一列对应的原始开关号,所以condVec(attr)取的是该开关的上报值。这套间接索引避免了“规则按约简列排序、输入按原始列排序”导致的错位问题,这也是推理阶段最容易翻车的地方。missingCols传的是原始属性列号向量,例如[3 7]表示第3和第7台开关没上报。阈值0.5是经验起点:如果规则置信度普遍在0.7以上,可以降到0.3;如果误判偏多就调高,阈值本身也应该通过后文K折验证来标定。
部分匹配带来的一个实际好处是:即使两个开关漏报,系统仍然可能命中多条规则,定位结果的分差会拉开。就算最终得分不足、输出“无法定位”,命中的规则列表也能给现场工程师做人工复核。粗糙集的输出不应该是黑匣子,这套推理设计保留了完整的可审计性。
4.2 10区段算例:从决策表到最终定位结果全流程
用一个10区段馈线完整跑一遍。场景:母线 → S1 → 区段1 → S2 → 区段2 → … → S10 → 区段10,单电源辐射网,S1到S10共10个开关。决策表沿用第2章的生成脚本,每个区段3条基线样本,另加每条区段2条带噪声的样本,共50条。然后依次调用buildDiscMatrix、greedyReduct、extractRules,最后用一条模拟漏报的测试向量做定位。
% 全流程: 生成数据 -> 约简 -> 规则 -> 推理验证 nSec = 10; rng(42); % 固定随机种子, 保证结果可复现 samples = []; for k = 1:nSec base = [ones(1, k), zeros(1, nSec - k)]; for rep = 1:3 samples = [samples; base, k]; %#ok<AGROW> end for rep = 1:2 noisy = base; flip = randi(nSec, 1); % 随机翻转一个开关状态 noisy(flip) = 1 - noisy(flip); samples = [samples; noisy, k]; %#ok<AGROW> end end [DT, ~] = unique(samples, 'rows'); condCols = 1:nSec; decCol = nSec + 1; [discMat, coreAttr] = buildDiscMatrix(DT, condCols, decCol); reduct = greedyReduct(DT, condCols, decCol, coreAttr); rules = extractRules(DT, reduct, decCol); fprintf('约简属性(开关号): %s\n', mat2str(reduct)); fprintf('规则条数: %d\n', size(rules, 1)); % 在线验证: 模拟区段7故障, 且S7漏报 testVec = [ones(1, 7), zeros(1, 3)]; testVec(7) = 0; % 这里把S7状态置0模拟缺失 predZone = locateFault(rules, reduct, testVec, 7); fprintf('定位结果: 区段%d\n', predZone);运行后通常会发现,约简属性只集中在少数几个开关上,比如S3、S5、S8,规则条数不超过10条,远小于全属性时的几十条。测试向量中的S7漏报往往不影响定位结果,因为S7大概率不在约简集合里。代码里rng(42)必须显式设置,否则每次运行的随机噪声样本不同,约简结果也会变,做实验记录时没法复现。locateFault传入的missingCols=7表明S7状态缺失,和第5章强调的一致——缺失属性被跳过,而不是被填成0。
强调一下,这个算例是小规模辐射状电网。实际配网如果有联络开关、分布式电源、双端供电,故障电流方向不再是单调的“前k个过流、后面不过流”。处理方法是把条件属性从“有无过流”改成“电流方向编码”,决策表原理完全一样,只是样本需要依赖配电网仿真软件生成,不能靠单端辐射网的单调性手工推导。
5. 粗糙集配电网故障定位避坑指南:5个常见翻车点
粗糙集理论看着简单,实际跑起来全是细节坑。这里整理5个我踩过或者帮别人排查过的高频问题,每条按“现象→原因→解决”展开,照着排查能省下不少调试时间。
5.1 连续量不离散或离散化过细,规则表爆炸
现象:把零序电流幅值直接作为条件属性进决策表,约简后规则条数飙到上百条,很多规则置信度只有0.05,完全没法解释。原因:连续值在不可分辨关系下几乎没有两个样本取值完全相同,区分矩阵里每个样本对都能被多个属性区分,约简过程失去意义,规则表被每个样本的独特性撑爆。解决:进决策表前必须离散化。过流状态本身一般是离散的,但如果用的是故障电流幅值,我建议先用等频离散化成3到5档,宁可粗一点。离散化后重新生成决策表,规则条数通常会下降一个数量级。如果离散化过细,比如弄出20档以上,又会退回“每个样本一条规则”的境地。档位数要根据样本量调整,经验值是每档至少分配到10%的样本。
5.2 故障样本不平衡,规则偏向高频区段
现象:训练决策表里区段5的故障样本占了40%,区段9只有5%,推理时区段9的真实故障经常被误判成区段5。原因:贪心约简和置信度投票都对样本量敏感,覆盖度高的区段产生的规则支持度高,投票时天然占优。解决:训练样本先做类型均衡,每个区段生成等量样本;实在凑不齐,就在推理打分时乘上该区段样本占比的倒数做定权补偿。粗糙集本身不提供代价敏感机制,这个平衡必须在规则生成之前做掉,否则约简属性选择也会被高频区段带偏,后续再补偿都救不回来。
5.3 约简结果不唯一,不同运行得到不同规则
现象:同一份决策表,上次跑出的约简是{S2,S5,S8},这次跑出{S3,S6,S9},定位精度看起来都还行,但现场工程师不知道怎么挂这些规则。原因:粗糙集约简在数学上就不唯一,区分函数往往有多个极小析取项,贪心算法又受属性遍历顺序影响。解决:先看核属性。核是所有约简的交集,唯一且稳定,规则库必须保证核属性对应的开关是硬条件。然后对候选约简做交叉验证,选定位精度最高的那一套;如果精度差不多,选开关数量少的那套,现场实施成本和接线难度更低。我习惯把“核+最优约简”作为基线规则库,其它约简作为备选,既不丢信息,也给了现场选择空间。
5.4 信息缺失被低估,FTU漏报直接误判
现象:某台FTU故障漏报,上报向量里少了一位,系统把它当成“无故障电流”去匹配规则,结果定位到相邻区段。原因:这是最常见也最隐蔽的坑——把“缺失”和“无故障电流”画了等号。两者物理含义完全不同,缺失是不知道,无故障电流是确定没有。解决:条件属性用三值编码而不是二值编码:1过流,0无过流,-1缺失。定位匹配时对-1列做跳过而不是参与比对,前面的locateFault里missingCols就是为此设计的。千万不要把缺失填充成0或平均值,那等于向数据里注入假信息。如果现场大面积FTU离线,缺失列太多,命中规则自然会变少,此时应该让系统输出“无法定位”而不是强给一个区段,宁可人工巡线也不要误导调度。
5.5 仿真样本当现场样本用,规则泛化不起来
现象:决策表全部来自离线仿真,换到另一个拓扑差异大的网架后,定位正确率从95%掉到60%。原因:仿真数据把故障场景理想化了——开关状态、故障电阻、采样时刻都很规整,而现场的真实样本分布完全不同。解决:决策表来源必须混合。常见做法是先搭配电网模型把所有单重故障枚举一遍生成基础决策表,再把历史故障记录(哪怕不完整)追加进去,重新执行去重和冲突检测。如果现场数据量不够,就把仿真数据的故障条件做随机扰动:故障电阻从0到100Ω均匀采样、FTU漏报率按实际统计值注入,这样生成的样本更接近真实。现场投运后,规则库要支持定期用真实事件校准,不能一劳永逸。
这5条覆盖了从数据预处理到现场部署的主要翻车点。前面三条属于算法本身和样本处理的边界,后两条直接关系到能不能用起来。规则库上线前,至少拿一条真实历史故障走一遍完整链路,哪怕定位结果不准,也比仿真跑通了就交付强得多。
6. 把规则库用在现场前:K折验证与增量更新的两个后续动作
规则库不能摆在那直接用,上线前必须验证泛化性,上线后新故障样本也得有个融进去的机制,这两件事决定项目能走多远。
先做K折交叉验证。把决策表随机分成K份,常见K=5或10,轮流取1份做测试、其余做训练,重复K次,统计平均定位正确率。粗糙集规则匹配本身有阈值,所以除正确率外还要看“拒动率”(无法定位的比例)和“误判率”(定位错区段的比例)。折分前固定随机种子,否则同一次实验跑出的验证结果都不一样,汇报时没法解释。
% K折交叉验证主框架 K = 5; n = size(DT, 1); idx = crossvalind('Kfold', n, K); % 需要Statistics Toolbox errSum = 0; for k = 1:K trnRows = DT(idx ~= k, :); tstRows = DT(idx == k, :); [~, coreA] = buildDiscMatrix(trnRows, condCols, decCol); redK = greedyReduct(trnRows, condCols, decCol, coreA); rulesK = extractRules(trnRows, redK, decCol); nCorr = 0; for t = 1:size(tstRows, 1) vec = tstRows(t, condCols); trueZone = tstRows(t, decCol); predZone = locateFault(rulesK, redK, vec, []); nCorr = nCorr + (predZone == trueZone); end errSum = errSum + nCorr / size(tstRows, 1); end meanAcc = errSum / K; fprintf('K折平均定位正确率: %.1f%%\n', meanAcc * 100);逻辑说明:每一折都在训练子集上重新执行“建区分矩阵→约简→提取规则”,保证测试样本在规则生成过程中从未被见过,这才是真实的泛化评估。如果为了省时间只在全量规则上验证,正确率会虚高,现场表现会立刻打折。crossvalind需要Statistics Toolbox,没有的话可以用randperm手动划分,效果一样。如果K折平均正确率低于90%,要么是样本量不足,要么是条件属性选取不当,应该回到第2章重新梳理决策表。
第二个动作是增量更新。新故障样本每次都全量重跑约简太昂贵,但也不能一直堆在一边不管。粗糙集的区分矩阵天然支持增量:新样本进来,只需把它和原有样本逐对比较,更新对应矩阵项;只有出现规则无法解释的真实冲突样本时,才触发全量重建。我在现场一般设置两条规则:新增样本与现有规则在约简属性下完全匹配且决策一致,只更新该规则置信度和支持度;一旦出现现有规则库解释不了的新样本,立即触发全量重建,并把这条样本标记为待人工审核。全量重建成本高,但真实故障占比很低,一年触发三四次完全可以接受。
这套K折验证加增量更新的流程跑顺后,规则库才真正算得上“可部署”。我做这类项目最大的体会是:粗糙集不会给你一个万能黑匣子,它给的是一套能被审校、能持续进化的规则资产。算法跑通只是开始,把验证和迭代机制搭好才是项目不翻车的关键。希望这些踩坑和复现步骤能帮到你。
本文还有配套的精品资源,点击获取