资讯动态

数据清洗实战:多方法融合的异常检测与KNN-LSTM-RF缺失值填补

发布时间:2026/9/7 17:37:51 来源:尧图企业网站定制
数据分析和建模这几年我最大的一个体会是拿到手里的原始数据几乎从来没有“能用”的时候。数据清洗与填补模型这个项目就是在处理最烦人但也是最要命的一步——把脏数据洗干净。项目里我同时接入了多种异常检测方法用K均值聚类把离群点批量剔掉再用KNN-LSTM-RF三套模型配合着把缺失值填回来。整个流程都在Matlab里跑通有完整代码和数据下面把这些细节和坑一次性讲清楚。这个项目适合谁如果你手头有传感器数据、工业过程数据、业务表数据里面既有异常值又有缺失值而且你不想一个个点开Excel肉眼挑数据那这套流程能给你省下大量时间。我自己试过不少“单点”方案比如只用箱线图删离群点或者只用均值填缺失值但效果都很粗糙——尤其在数据分布不稳定、噪声比较大的场景里越简单的方法越容易把正常样本和异常样本搅在一起。所以这个项目选择了一条更稳的路先用多种异常检测交叉验证再用K均值聚类做结构化的离群点剔除最后用三种不同原理的模型做数据填补。1. 整体设计与思路拆解1.1 为什么异常检测要“多种”而不是只选一种很多人在做异常检测时习惯性只选一个算法比如Z-score或者孤立森林然后调几个参数就算完事。但实际数据很少会乖乖服从理想假设。Z-score假设数据大致正态但如果数据本身是多模态的Z-score会把一个本来正常但处于两个峰之间谷底的样本判成异常LOF这类密度方法在样本量不够大时特别敏感计算出的局部密度图经常飘得没法看KNN距离法对维度灾难又很无奈高维下所有距离都趋于相等检测效果大打折扣。这个项目里我没打算只靠某个单一算法下结论而是把统计法、距离法、密度法都跑一遍然后对每个样本的记录做统一投票或加权投票。这样设计的好处很直接不同算法的“偏见”能互相抵消。比如某个样本可能因为在正态假设下偏离均值较多被判异常但它周围邻居多、局部密度正常在KNN和LOF那里就得不到支持票最后它就不会被误杀。这其实和人判断异常的方式很接近——不是看单个指标的偏离而是综合几方面证据再下结论。从工程实现角度多种异常检测的代价主要是计算量。Matlab里做这些都不难Z-score和IQR一行函数搞定KNN距离法用pdist2LOF也有现成实现可以自己写一个短函数。只要样本量在几万以内跑起来完全没压力。1.2 K均值聚类在离群点剔除中的真实角色有些人看到“K均值聚类剔除离群点”会有点懵因为K均值本身是用来做聚类的不是专门做异常检测的。但把它用在离群点剔除阶段有它的特殊价值它能在删除样本之前先把数据的整体结构抓出来。在异常检测成功后我们手里已经有了一份“疑似离群点名单”。如果直接按名单删可能删掉一些处于类别边缘但仍然是正常业务的样本尤其当数据分布比较复杂、类别重叠明显的时候。这时候先用K均值把数据聚成几个簇再在簇内部判断某个点离自己簇中心到底有多远要比全局距离判断合理得多。打个比方全公司所有人的年龄做一个全局分布某个50岁的人可能在全局看是偏大的但如果他属于“资深管理层”这个簇那么他在这个簇里其实是正常的。K均值做的事情就是帮我们把“所属群体”先圈出来再在这个群体内部衡量个体是否异常。这个环节还有一层现实作用异常检测阶段的输出往往是一堆离散标记不适合直接作为后续填补模型的输入。而经过K均值聚类以后每个正常样本都能拿到一个“所属簇编号”和“到簇中心的距离”这两个特征可以作为重要的辅助信息交给KNN-LSTM-RF填补模型使用让填补过程更有结构感。后面我会在完整流程里详细说明这个特征怎么用。1.3 KNN-LSTM-RF三模型为什么能形成互补KNN、LSTM、RF这三种模型在很多项目里是“要么不用要么只用其中一个”的关系但它们在数据填补这件事上其实各管一块。KNN填补的核心假设是“相似样本有相似取值”它利用完整样本与缺失样本之间的特征距离用加权平均的方式估算缺失值。这种方法对表格型数据、特征间存在局部相似结构的数据非常友好实现简单解释性强。但它的问题在于如果缺失特征和其他特征之间完全是强非线性关系KNN用欧氏距离去衡量“相似”就不太可靠。LSTM填补比较特殊它不是拿别的样本去补而是拿样本自身的前后序列去推测缺失位置的值。如果数据带时间戳或隐含顺序——比如设备温升曲线、风速序列、股票指标——那么LSTM能捕捉到传统表格方法根本看不见的时序依赖。但缺点是它要求数据有序且量不能太少一旦训练样本少LSTM很难学出有意义的周期性。RF迭代填补则是另一条路。随机森林天然能处理非线性特征交互并且能输出特征重要性。在迭代填补中每一轮把缺失列当做目标变量已观测列和已有的部分填补值一起作为特征训练随机森林然后回填循环迭代直到收敛。这个方法在UCI等各种公开数据集上表现都很稳不会像单个决策树那样容易过拟合也不像线性模型那样对非线性无可奈何。更好的方式不是三选一而是把它们串在一条流水线里先用KNN做一个快速粗填让数据先“完整”起来再用LSTM对有明显时间结构的列做局部精修最后用RF迭代对整体数据做一遍全局优化。三个模型依次上场既解决了各自适用的数据特征又避免了一个模型面对所有问题的尴尬。这也是我在项目里实际采用的做法。2. 异常检测先用多把尺子量一遍数据2.1 统计型异常检测Z-score与IQR统计型方法的优点是快、透明、可解释。Matlab里做Z-score非常简单% 按列计算Z-score排除NaN mu mean(data, 1, omitnan); sigma std(data, 0, 1, omitnan); z_scores abs((data - mu) ./ sigma); threshold 3; % 根据正态分布经验超过3倍标准差视为异常 anomaly_z z_scores threshold;这段代码里需要注意一点计算均值和标准差时一定要跳过NaN否则整列结果都会被污染。我见过太多人忽略这个问题最后得到一堆NaN异常标记排查半天才发现是stats函数默认遇上NaN就返回NaN。IQR法更稳健它基于四分位数基本不受极端值影响q1 prctile(data, 25, 1); q3 prctile(data, 75, 1); iqr_vals q3 - q1; lower_bound q1 - 1.5 * iqr_vals; upper_bound q3 1.5 * iqr_vals; anomaly_iqr data lower_bound | data upper_bound;1.5倍IQR是箱线图默认的经验系数适合大多数对称分布数据。但如果你的数据偏态非常严重比如某些传感器读数会偶尔出现几个数量级的跳变那么1.5倍IQR往往会把大量正常样本误判为异常。这种情况下可以把系数放大到2甚至3或者先对数据做对数变换再计算IQR。这两类统计方法本质上都在描述“单个样本相对全局分布有多极端”优点是不需要任何训练过程但问题也在这里——它看不到局部结构。如果数据有明显的时间趋势或季节周期比如白天温度高、晚上温度低统计法会认为白天高温样本是“异常”可那明明就是正常规律。所以统计法在这个项目里只作为参考意见不作为最终判定标准。2.2 距离型与密度型异常检测KNN距离法与LOFKNN距离法的思路非常直觉化一个样本如果离它最近的k个邻居都很远那它多半是离群点。Matlab里可以这样写function d knn_anomaly_score(X, k) % X: m x n 数据矩阵, 每行一个样本 % 返回每个样本到其第k近邻居的距离 D pdist2(X, X, euclidean); D(D 0) inf; % 去掉自身距离 Ds sort(D, 2); d Ds(:, k); end这里取第k个近邻的距离作为异常分数。实际操作中k选5到20之间的值比较常用。k太小容易受局部噪声影响k太大会模糊掉局部分布。做完特征标准化很关键否则量纲大的特征会主导整个距离计算。我在项目里通常先做z-score标准化再算距离矩阵。LOFLocal Outlier Factor局部异常因子比KNN距离更进一步。它不光看每个点和邻居的距离还看这个点周围邻居的密度和它自身所处区域的密度之比。如果某个点的密度远低于邻居的平均密度那它的LOF值就明显大于1。LOF公式里最关键的是“局部可达密度”function lof lof_score(X, k) % 简化版LOF计算实际项目里可以用FEX上的优化版本 n size(X, 1); D pdist2(X, X, euclidean); D(D 0) inf; [~, idx] sort(D, 2); kNN_idx idx(:, 1:k); % 每个点的k近邻下标 k_dist D(sub2ind([n, n], (1:n), kNN_idx(:, k))); % 第k距离 reach_dist zeros(n, k); for i 1:n for j 1:k nb kNN_idx(i, j); reach_dist(i, j) max(k_dist(nb), D(i, nb)); end end lrd 1 ./ (mean(reach_dist, 2) eps); % 局部可达密度 lof zeros(n, 1); for i 1:n lof(i) mean(lrd(kNN_idx(i, :))) / (lrd(i) eps); end endLOF的突出优点是可以发现“小簇里的离群点”也就是局部离群而不是全局离群。但LOF的参数k对结果很敏感k没选好会出现大量样本LOF接近1、又偶尔冒出一两个超高分的情况。所以在项目里我一般把LOF当做一个“候选生成器”只把LOF超过某一个较高分位的样本纳入疑似异常名单而不是直接用它做一刀切。2.3 多方法融合判定规则把Z-score、IQR、KNN距离、LOF这四类方法的判定结果放在一起后就需要一个融合规则。我这里最常用的是“加权投票制”% scores每列是某一种方法得到的异常概率或异常得分 % 先二值化z_score 3 → 1; IQR越界 → 1; knn距离 P95 → 1; LOF 1.5 → 1 anomaly_matrix [anomaly_z, anomaly_iqr, anomaly_knn, anomaly_lof]; anomaly_count sum(anomaly_matrix, 2); vote_threshold 2; % 至少2个方法认为异常才标记为最终异常 final_anomaly anomaly_count vote_threshold;投票阈值怎么定要看业务上能接受多少误删率。如果数据质量很差宁多勿少阈值可以设为1只要有一个方法认为异常就先剔除如果数据本身比较干净阈值设为2或3能降低误删正常样本的风险。我自己在大多数工业传感器数据集上阈值设为2效果比较平衡既能删除明显的异常段又不会把边界样本统统删掉。融合环节还有一个值得注意的细节不要直接对原始异常得分求平均。因为Z-score得分和LOF得分的量纲完全不一样平均之后等于被量纲更大的方法主导了。我的经验是先各自变成0/1标记再做多数表决简单诚实不容易被长尾得分带偏。3. 用K均值聚类批量剔除离群点3.1 为什么剔除离群点前要先聚类如果只看异常检测结果很多边界样本的标签实际上是模糊的。比如Z-score把它判为异常但KNN距离法认为它周围还是有邻居的。这种情况下直接删风险较大因为一旦误删后面填补模型学到的分布就少了一部分真实信息。K均值聚类的意义在于把“全局是否异常”压缩成“局部是否异常”。先聚类数据就被分成若干簇每个样本都归属于某个局部中心。然后我们在每个簇内部计算样本到簇中心的距离再剔除那些“在簇里也离中心很远”的点。这种做法对多模态数据尤其有效。举个例子如果数据里有三种工况模式每种工况的均值差别很大全局看某些样本可能落在另一个簇附近容易被各种全局方法误判。但只要它们在自己所属簇内是紧密的就不应该删。操作上K均值聚类还能生成两个额外特征——簇编号和簇内距离这两个特征会作为后续填补模型的辅助输入。这样做的直接效果是填补模型能利用“这个样本更接近哪个群体”的结构信息而不是只看到一堆孤立特征。3.2 特征标准化与聚类数选择聚类前标准化是硬性要求。K均值是基于欧氏距离的算法特征量纲不同会直接扭曲聚类结构。比如一个特征是温度范围200~300另一个特征是湿度范围0~1温度会在距离计算中占据绝对主导。我的做法是统一用z-score标准化X_norm (X - mean(X, 1, omitnan)) ./ std(X, 0, 1, omitnan);聚类数K怎么选主要有两种思路。一种是画肘部图看簇内误差平方和随K变化的折线找到拐点另一种是用轮廓系数Silhouette来评估聚类效果选轮廓系数最高的K。Matlab里可以直接用evalclusterseva evalclusters(X_norm, kmeans, CalinskiHarabasz, KList, 2:10); K_opt eva.OptimalK;我一般会结合业务含义来定不盲目追求统计指标最大。比如某个工业过程有三段工况那K直接取3往往比统计指标选出的5更符合实际。聚类结果要对业务有解释力这是算法调参之外更重要的一步。3.3 离群点判定阈值与剔除K均值聚类完成后每个样本i都得到它所在簇的中心center_i。定义簇内距离% 假设idx是kmeans输出的簇编号C是簇中心矩阵 d_in_cluster zeros(size(X_norm, 1), 1); for i 1:size(X_norm, 1) d_in_cluster(i) norm(X_norm(i, :) - C(idx(i), :)); end % 每个簇内单独设阈值 for ci 1:K mask (idx ci); d_c d_in_cluster(mask); mu_d mean(d_c); sigma_d std(d_c); outlier_in_cluster(mask) d_c mu_d 3 * sigma_d; end这里用的是“簇内平均距离3倍标准差”的经验规则。3倍标准差的逻辑和Z-score类似但现在是“和局部中心的距离”来做判断比直接对单维特征做Z-score更合理。如果对误删特别敏感可以把系数放宽到4倍标准差如果对异常容忍度低就缩到2.5倍。这个系数是调参的核心我会在验证阶段看删掉后的数据分布曲线是不是还平滑再决定要不要调。剔除环节还有一个“要不要反馈回异常检测”的细节。我的选择是先做完所有异常检测投票再做K均值剔除然后重新标准化、重新跑一次K均值再检查一轮。这样的两轮清洗在实际项目中能把很多“前一轮没暴露出来”的次生异常也清掉。代价是计算量翻了倍但数据量不大时完全可接受。3.4 剔除后必须做的数据检查每次剔除完离群点不能直接进填补阶段。我至少会做三件事一是看数据量变化比例。如果一次清洗就删掉了超过10%的样本那大概率是阈值设定太激进需要重新检查是数据本身太脏还是误删了正常区域。二是看每个特征的分布形态。可以用histogram或boxchart画一下清洗前后的特征分布如果原本没删前是偏态的删除后偏态更严重说明删掉了长尾里的正常样本需要调回阈值。三是看样本在特征空间里的散布。可以用gscatter按簇编号画散点图观察簇是否仍然紧凑、簇之间是否有明显重叠。如果删除后某些簇只剩下很少样本也要警惕。我在一个真实项目的风电功率数据上试过不经过K均值剔除直接做填补填出来的缺段时间序列在变工况拐点处总是“圆润”得不自然而先做K均值剔除后再填补同样的LSTM模型填出来的曲线明显更贴近真实拐点。原因很简单K均值剔除把那些“搅局”的离群样本先清掉了后面模型学到的分布更干净。4. KNN-LSTM-RF数据填补的详细实现4.1 三种填补各自的适用场景先明确一点数据填补没有万能钥匙只有“适合当前数据特征的方法”。方法核心思想最适合的数据形态主要限制KNN填补找相似样本做加权平均表格数据、特征间有局部相似性对高维稀疏数据效果差计算量大LSTM填补用时间序列前后文预测缺失位置有序数据、存在时变规律样本量不足时容易过拟合训练成本高RF迭代填补以缺失列为目标随机森林建模后迭代回填特征间有非线性复杂关系耗时长对随机种子较敏感项目里的实际策略是对所有缺失列先用KNN粗填把“洞”先补上防止后续模型因为NaN报错然后对带时间顺序的列单独跑LSTM精修最后用RF对整个数据集做一轮迭代优化。这样做的好处是每一步都基于上一步更完整的输入模型逐步逼近真正的分布。4.2 KNN填补找到最像的邻居来取值KNN填补的实现要点有两个一是距离计算要基于没有缺失的列进行二是邻居的取值要用加权平均而不是简单平均。function filledData knn_fill(data, k) [m, n] size(data); filledData data; for i 1:m missCols find(isnan(data(i, :))); if isempty(missCols) continue; end obsCols setdiff(1:n, missCols); completeRows find(all(~isnan(data(:, obsCols)), 2)); if numel(completeRows) k k_local numel(completeRows); else k_local k; end dist_i sum((data(completeRows, obsCols) - data(i, obsCols)).^2, 2); [~, ord] sort(dist_i); nearestIdx completeRows(ord(1:k_local)); weights 1 ./ (dist_i(ord(1:k_local)) eps); weights weights / sum(weights); for col missCols filledData(i, col) sum(weights .* data(nearestIdx, col)); end end end这段代码最需要注意的是completeRows的选择。如果某个样本在所有观测列上不是完整的就不能用来做邻居。每次填补一个目标样本时都要重新筛选邻居因为不同目标样本的缺失组合不同可用的完整样本集合也不同。这样写虽然多了几层循环但逻辑清晰不容易出错。权重计算里加eps是为了防止距离为0时除以0。k值不宜太大我通常取5~10。k太大会把距离很远、实际上不相似的样本也拉进来平均导致填补结果偏平滑丢失局部波动。4.3 LSTM填补找回时间上的连续性LSTM填补的典型场景是这样的某传感器数据第300到400个采样点整段缺失但前后都正常。KNN想通过找其他时刻的相似样本去填但如果这段缺失对应的是一种独立工况其他时段根本没有相似样本KNN就会填出一个“四不像”的平均值。这时候LSTM就派上用场了。LSTM填补的基本操作流程是先只使用数据中没有缺失的时间序列段训练一个LSTM预测模型输入是前p个时刻的序列值输出是下一时刻的预测值。然后用这个模型从缺失段的前端往后逐步预测再从缺失段的后端往前逐步反向预测最后把正向预测和反向预测做加权平均。% 假设seq是去除离群点后的一维时间序列里面有一整段NaN % 取非缺失部分训练数据 trainX []; trainY []; for t 1:length(seq) - p - 1 if ~any(isnan(seq(t:tp))) trainX [trainX; seq(t:tp-1)]; trainY [trainY; seq(tp)]; end end % 用trainX/trainY训练一个layerLSTMfullyConnected网络 layers [ ... sequenceInputLayer(1) lstmLayer(16, OutputMode, last) fullyConnectedLayer(1) regressionLayer]; options trainingOptions(adam, MaxEpochs, 200, Plots, none); net trainNetwork(trainX, trainY, layers, options);LSTM训练时数据量少、epochs多、学习率不合适都容易导致严重过拟合。实际项目里我会在LSTM之后接一个很小的全连接层然后用validation split留出15%的验证集观察验证损失和训练损失的差距。如果验证损失一路飙升说明过拟合了要么加大数据量要么减小隐藏单元数量要么加dropout。正向预测和反向预测的融合公式% 从缺失段两端分别预测 for t startIdx:endIdx forwardPred(t) predictForward(net, seq(t-p:t-1)); backwardPred(t) predictBackward(net, seq(t1:tp)); end alpha (1:segLen) / (segLen 1); % 越靠近前段越信任正向预测 filledSeg alpha .* forwardPred (1 - alpha) .* backwardPred;这个加权设计的基本逻辑是越靠近缺失段头部正向预测的上下文信息越充分可信度越高越靠近尾部则反向预测越可靠。线性过渡虽然简单但在大多数场景下已经够用。如果你对数据形态有更强先验比如知道存在周期性可以把alpha改成和周期相位相关的函数。4.4 RF迭代填补全局优化的后处理RF迭代填补的思路来自R语言中的mice包但用随机森林替代了传统的线性回归作为基学习器。每一步迭代中对于包含缺失值的每一列把该列作为目标变量其余列作为特征训练一个随机森林然后用模型预测值替换该列的缺失值。迭代多轮后各列的填补值逐渐收敛。function dataFilled rf_iterative_fill(data) dataFilled data; % 先用均值快速初始化缺失值 for col 1:size(data, 2) nanIdx isnan(dataFilled(:, col)); dataFilled(nanIdx, col) mean(dataFilled(:, col), omitnan); end for iter 1:5 for col 1:size(data, 2) nanIdx isnan(data(:, col)); % 真实的缺失索引 if ~any(nanIdx) continue; end trainIdx find(~nanIdx); X_train dataFilled(trainIdx, setdiff(1:size(data, 2), col)); y_train dataFilled(trainIdx, col); X_pred dataFilled(nanIdx, setdiff(1:size(data, 2), col)); mdl TreeBagger(100, X_train, y_train, Method, regression); dataFilled(nanIdx, col) predict(mdl, X_pred); end end end这段代码里迭代次数我通常设为5就已经比较稳定了。关键是“真实缺失索引”要和“当前填补值”分开每一轮回的填充只针对原始缺失位置不要把上一轮填出来的新值当成新缺失。另外训练RF的特征中如果还包含尚未填补好的别的列那对当前列的影响会有噪声但迭代策略会让这些噪声逐步减小——这也是为什么不能只跑一轮的原因。RF迭代填补最大的优势是能捕捉特征之间的交互效应和非线性关系比简单均值填补或回归填补的偏差小很多。但它的缺点也很明显每一列都要训练一个随机森林循环5次就是5×列数次模型训练数据维度高时耗时会爆炸。所以在工程上我会先跑KNN粗填再跑RF迭代让RF训练输入更稳定同时可以通过TreeBagger的NumPredictorsToSample参数控制特征采样比例降低单颗树训练成本。4.5 三种填补结果如何融合项目里我并不是把三种方法的结果拿来做简单平均而是按数据特性做分区处理这样能最大程度保留每个模型的优势对时序特征列LSTM填补结果作为主填补KNN和RF的结果作为辅助。对普通表格特征列RF迭代填补结果作为主填补KNN结果作为辅助。对缺失比例极低的列直接优先用KNN结果因为此时KNN计算快且误差足够小。最终所有列都会有一个统一的“主要填补值”。如果需要让模型不确定度也能体现出来还可以记录每种方法之间的方差作为后续建模时特征置信度的一部分。filledFinal filledRF; timeCols [3, 5, 7]; % 示例时间相关列索引 for col timeCols % 时间相关列用LSTM结果替换 filledFinal(:, col) filledLSTM(:, col); end missingRatio sum(isnan(data), 1) / size(data, 1); lowMissCols find(missingRatio 0.02); filledFinal(:, lowMissCols) filledKNN(:, lowMissCols);这种“按列指派主模型”的方式比分数的平均更可解释也更方便后续维护。如果模型上线后某列效果不好只需要调整那一列的指派策略不需要重训整个流程。5. 完整工作流与工程化说明5.1 主脚本结构整个项目我建议按模块拆函数不要把所有步骤堆到一个脚本里。主脚本只负责串流程和调参具体逻辑放到函数文件里这样后续改参数和加模型都非常方便。我的主脚本逻辑顺序是读取原始数据处理表头和时间戳分离数值特征、标签列和辅助ID列调用multi_anomaly_detect.m得到异常标记调用kmeans_remove_outlier.m做聚类剔除调用knn_fill.m做粗填补对时序列调用lstm_fill.m做精修调用rf_iterative_fill.m做全局迭代输出清洗后的表格和若干评估指标5.2 数据组织与函数文件清单推荐的文件组织方式如下project_root/ ├── main_clean_fill.m # 主入口 ├── data/ │ ├── raw_data.xlsx # 原始数据 │ └── cleaned_data.xlsx # 清洗后数据 ├── functions/ │ ├── multi_anomaly_detect.m │ ├── kmeans_remove_outlier.m │ ├── knn_fill.m │ ├── lstm_fill.m │ └── rf_iterative_fill.m └── results/ ├── anomaly_report.mat └── fill_metrics.mat数据格式上我建议把表格整理成每行一个样本、每列一个特征的形式。时间列可以是数值序列比如Unix时间戳或相对采样序号。如果时间是datetime类型需要先datenum转成数值因为Matlab多数统计函数不支持datetime直接参与距离计算。5.3 验证清洗和填补效果的三类指标数据清洗和填补做完不能只看“NaN没了”就算完成。我一般用三类指标来量化效果第一类是缺失比例下降情况从原始的缺失率降到0这个指标最直观。第二类是填补误差。如果原始数据里有完整的验证子集可以人为在完整样本上制造缺失掩码再计算填补值和真实值的RMSE、MAE。这类指标能反映填补模型本身的精度但前提是要保证掩码生成方式与真实缺失模式接近。第三类是数据分布变化。比较填补后各特征的均值、标准差、分位数和原始完整样本的对应统计量偏移情况。如果填补后均值偏移超过0.5个原始标准差我会认为填补方法在系统性偏差。rmse sqrt(mean((filledTrue(:, missingMask) - trueValue).^2, omitnan)); mae mean(abs(filledTrue(:, missingMask) - trueValue), omitnan);在实际项目里这三类指标对应的报告建议做成一张表格方便和别人沟通。特别是当你需要跟业务方解释“为什么LSTM比均值填补好”的时候一个RMSE数字比十段描述有用得多。6. 常见问题与排查技巧实录6.1 异常检测删过头了怎么判断和处理判断是否删过头有个很实用的信号清洗后的样本量显著减少而且特征分布形状变化很大。比如处理温度数据时原始数据是两个峰清洗完变成一个峰那大概率是把第二个峰的正常工况样本当离群点给删了。处理上我通常先降低投票阈值——从要求3个方法投票通过降到2个再看样本保留率是否回升。同时把K均值剔除阈值从3倍标准差放大到3.5或4倍。两者结合一般能找回大部分误删样本。6.2 K均值聚类数K怎么定才合适统计指标可以用evalclusters但业务含义更重要。如果特征数据采集自几种明确工况K直接等于工况数。如果不清楚工况数我的经验是先做PCA降维到二维或三维可视化看自然聚成几团再回到原始维度设置K。这样比纯粹看肘部图直观得多。另外一个容易翻车的地方是K过大。K设成50甚至100时每个簇内样本太少簇内距离方差也小导致离群点判定越来越敏感正常样本被误删。一般K不超过特征数的平方根这个经验值或者不超过样本量的5%。6.3 LSTM训练时数据量太少不收敛LSTM的天然要求是数据量大。真实项目里时序数据往往只有几千个点去掉离群点后更少LSTM很容易在训练集上震荡。我的应对措施有三个一是用更短的输入序列长度p比如p3或者5减少模型负担二是把LSTM隐藏单元减到8个左右三是增加dropout并缩短epochs提前终止。如果数据量实在小我建议LSTM只做辅助验证不要作为主填补模型。对几十个几百个数据点的时间序列RF迭代和KNN反而更可靠。6.4 为什么填补后的数据看着很“平滑”其实不对劲如果填补结果和真实数据对比缺失段看起来特别平滑甚至没有上下波动大概率是因为KNN在加权平均时k值太大或者LSTM正向反向融合权重过于线性把细节抹平了。解决办法也很直接减小k值、增加LSTM隐藏单元数量、或者在融合公式里加入一个随机噪声项。对于带周期性的数据可以在LSTM输入里加入“周期相位”作为一个额外特征帮助模型生成更自然的波动形态。问题可能原因处理办法删掉太多正常样本异常检测投票阈值太低或K均值阈值太紧提高投票阈值、放大簇内距离倍数填补结果平均化严重KNN的k偏大或LSTM融合权重太平滑减小k值改用非线性融合权重LSTM不收敛数据量过少/隐藏单元过多/学习率不适减小模型尺寸缩短序列长度调低学习率RF迭代耗时长特征多、轮数多、树多先做特征筛选减少迭代轮数到3轮结果依然不稳离群点清洗不彻底执行第二轮K均值剔除后再填补7. 实操心得与一些避坑建议踩过几次坑之后我对这个流程最大的感触是不要对着一个算法调参调到头而要把整套流程拆开每步单独验证。异常检测阶段多画几个分布图K均值剔除阶段多观察几个簇的散点图填补阶段多对比几种方法的误差。每一步都稳了整体结果才可信。再分享一个我最近开始采用的小技巧清洗完成后把离群点样本单独保存在另一个文件里而不是直接物理删除。这样做有个额外好处——如果在后续建模中发现某些“被剔除样本”其实对预测结果有重要作用还能随时把它们找回来重新审视删除条件。数据清洗应该是可回溯、可迭代的不能做成一锤子买卖。项目里我在results/anomaly_report.mat里存了所有异常样本的原始索引、被判定为异常的原因和对应得分方便任何一个时刻复盘清洗决策。这个习惯一开始看不出来有多大价值但项目周期一长它会让你在模型异常时多一条排查路径。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价