简介:本资源是一份面向Python数据科学初学者与中级开发者的异常检测实战代码包,聚焦无监督算法设计与工程实现,适用于金融风控、运维监控、IoT设备异常识别等实际场景。压缩包共3个文件(2个MATLAB格式数据集data1.mat、data2.mat用于算法验证,1个核心Python脚本AnomalyDetection.py含Isolation Forest、LOF及Z-score三种主流方法完整实现),总大小103KB,轻量易部署,便于快速复现与调试。已有1685人学习下载,说明其在实践教学与项目参考中具备较高认可度。读者可直接运行脚本完成端到端异常检测流程,获得含数据预处理、多算法对比、异常索引输出及基础可视化逻辑的可执行方案;代码结构清晰、注释详实,特别适合作为课程设计、毕设选题或Kaggle入门项目的算法基线参考。
1. 异常检测不是“报错就告警”,而是用 Python 构建可解释、可调参、能落地的工业级判别逻辑
很多工程师第一次写异常检测,直接套用sklearn.ensemble.IsolationForest或pyod里的模型,跑通 demo 就以为完成了——结果上线后误报率飙升、关键设备故障漏检、阈值调了三天还是飘忽不定。这不是算法不行,而是没把「异常」从业务语义里抠出来:产线振动信号的突变、IoT 设备温度序列的缓升漂移、数据库慢查询耗时的长尾分布偏移……每种场景下,“异常”的数学定义完全不同。本文聚焦真实工业与运维场景,不讲抽象统计理论,只拆解如何用 Python 从零设计一套可配置检测策略、支持多类型数据输入、输出带置信度的异常标签、且参数调整有明确物理意义的异常检测代码框架。适合已掌握 Pandas/Numpy 基础、正面临实际监控需求的开发与算法工程师——你不需要复现论文,但需要让算法在凌晨三点真正拦住那台即将过热停机的泵。
2. 为什么不用现成库?从零设计异常检测代码框架的三层结构
2.1 核心矛盾:通用库封装太深,而产线数据拒绝“黑盒”
PyOD提供 30+ 算法,但调用接口统一为fit(X)+predict(X),掩盖了关键细节:
- Isolation Forest 的
n_estimators对应多少棵决策树?每棵树切分时是否考虑时间序列依赖性? - LSTM-AE 的重建误差用
MAE还是MSE?误差阈值是固定百分位数还是动态滑动窗口? - 当输入是带时间戳的传感器数据(如
timestamp, temp, pressure, flow_rate),X是(N, 4)矩阵,但异常可能只发生在temp维度,其他维度正常——通用库默认对所有列联合建模,导致敏感度失衡。
提示:工业异常检测算法的首要目标不是 AUC 最高,而是降低运维人员的确认成本。这意味着:单点异常必须标注具体字段+时间点+偏离程度;连续异常需合并为事件段并给出持续时长;误报要能快速回溯是哪个参数导致。
2.2 框架设计原则:解耦“数据预处理—特征工程—判别逻辑—结果封装”四层
我们采用模块化设计,每个环节独立可插拔:
| 层级 | 职责 | 关键可配置项 | 典型实现方式 |
|---|---|---|---|
| DataLoader | 加载原始数据,处理缺失/重复/时序对齐 | time_col,resample_freq,fillna_method | pandas.read_csv()+resample('5T').mean() |
| FeatureEngineer | 构造时序特征(滑动统计、差分、频域变换) | window_size,agg_funcs=['mean','std','max'],use_fft=True | rolling(window=12).agg(['min','max','std']) |
| Detector | 执行核心判别(统计阈值/无监督/有监督) | method='iqr'/'zscore'/'isoforest',threshold_type='static'/'adaptive' | scipy.stats.iqr()+ 动态中位数更新 |
| ResultFormatter | 输出结构化结果(含置信度、影响字段、建议动作) | confidence_mode='prob'/'score',action_map={'temp_high':'check_cooling'} | 返回pd.DataFrame含is_anomaly,anomaly_score,affected_cols,confidence列 |
2.2.1 DataLoader 实现:强制对齐时间戳,避免“幽灵异常”
import pandas as pd from typing import Optional, Dict, Any class TimeSeriesLoader: def __init__(self, time_col: str = 'timestamp', resample_freq: str = '1T', fillna_method: str = 'ffill'): self.time_col = time_col self.resample_freq = resample_freq self.fillna_method = fillna_method def load(self, filepath: str, **kwargs) -> pd.DataFrame: df = pd.read_csv(filepath, **kwargs) # 强制转换时间列并设为索引 df[self.time_col] = pd.to_datetime(df[self.time_col]) df = df.set_index(self.time_col).sort_index() # 重采样对齐(避免传感器上报频率不一致导致的伪异常) df_resampled = df.resample(self.resample_freq).first() # 填充空值(工业数据常见断点) df_filled = df_resampled.fillna(method=self.fillna_method) return df_filled # 使用示例:加载某台电机的振动数据 loader = TimeSeriesLoader(time_col='ts', resample_freq='30S') raw_data = loader.load('motor_vibration.csv')参数说明:
resample_freq='30S'表示将原始数据统一到每30秒一个点,避免因采集间隔抖动触发虚假突变;fillna_method='ffill'用前向填充替代插值,防止在设备停机时段生成“平滑假数据”;set_index().sort_index()确保后续 rolling 操作按真实时间顺序执行。
2.2.2 FeatureEngineer 实现:构造对异常敏感的时序特征
import numpy as np class RollingFeatureEngineer: def __init__(self, window_size: int = 12, agg_funcs: list = ['mean', 'std', 'min', 'max'], use_fft: bool = False): self.window_size = window_size self.agg_funcs = agg_funcs self.use_fft = use_fft def transform(self, df: pd.DataFrame) -> pd.DataFrame: features = {} for col in df.columns: # 基础滑动统计 for func in self.agg_funcs: features[f'{col}_{func}_{self.window_size}'] = df[col].rolling( window=self.window_size, min_periods=1 ).agg(func) # 差分特征(捕捉变化速率) features[f'{col}_diff_1'] = df[col].diff(1) features[f'{col}_diff_2'] = df[col].diff(2) # 频域特征(对周期性异常敏感) if self.use_fft and len(df) > self.window_size * 2: # 取最近 window_size 点做 FFT,取主频能量 recent_window = df[col].tail(self.window_size).values fft_mag = np.abs(np.fft.fft(recent_window)) features[f'{col}_fft_energy'] = np.mean(fft_mag[1:6]) # 忽略直流分量 return pd.DataFrame(features).dropna() # 使用示例:为温度列构造 12 窗口滑动特征 fe = RollingFeatureEngineer(window_size=12, agg_funcs=['mean','std']) feature_df = fe.transform(raw_data[['temperature']])逻辑说明:
min_periods=1允许首window_size-1行产出部分结果,避免数据截断;diff(1)和diff(2)分别捕捉一阶/二阶变化率,对“缓慢升温→骤然超限”类异常更敏感;fft_energy计算低频段(1~5Hz)能量均值,适用于检测旋转设备轴承磨损引发的特定频段振动增强。
3. 三种主流异常检测方法的 Python 实现与参数调优指南
3.1 基于 IQR 的静态阈值法:适合基线稳定的单变量场景
IQR(四分位距)法无需训练,计算快,适用于温湿度传感器等长期基线波动小的设备。其核心是:
异常点 = 值 < Q1 - 1.5×IQR 或 值 > Q3 + 1.5×IQR
但工业现场需增强鲁棒性:
from scipy import stats class IQRDetector: def __init__(self, multiplier: float = 1.5, update_window: int = 1000, min_samples: int = 50): self.multiplier = multiplier # 可调宽松度:1.0 更严格,2.0 更宽松 self.update_window = update_window # 滑动窗口长度,避免被历史异常污染 self.min_samples = min_samples # 最小样本数,防止冷启动时阈值失效 def fit(self, series: pd.Series): # 用滑动窗口计算当前基线(避免单次大异常拉偏Q1/Q3) if len(series) > self.update_window: window_data = series.tail(self.update_window) else: window_data = series q1 = window_data.quantile(0.25) q3 = window_data.quantile(0.75) iqr = q3 - q1 self.lower_bound = q1 - self.multiplier * iqr self.upper_bound = q3 + self.multiplier * iqr self.median = window_data.median() def predict(self, series: pd.Series) -> pd.Series: # 输出布尔标签 + 异常强度(归一化偏离度) scores = np.abs(series - self.median) / (self.upper_bound - self.lower_bound + 1e-8) is_anomaly = (series < self.lower_bound) | (series > self.upper_bound) return pd.Series({ 'is_anomaly': is_anomaly, 'anomaly_score': scores, 'confidence': np.clip(1 - scores, 0.1, 0.95) # 置信度反比于偏离强度 }) # 使用示例 detector = IQRDetector(multiplier=1.8) # 放宽阈值减少误报 detector.fit(raw_data['temperature']) result = detector.predict(raw_data['temperature'])参数调优实战:
multiplier=1.8:当产线环境存在季节性温升(如夏季车间平均温度+5℃),提高 multiplier 避免将正常偏高判为异常;update_window=1000:对应约16小时数据(按1分钟采样),确保基线反映近期工况而非历史旧数据;confidence计算中np.clip(..., 0.1, 0.95)保证置信度不为0或1,便于后续规则引擎加权。
3.2 Isolation Forest 的工业适配:解决多维数据中的局部异常
Isolation Forest(IF)擅长发现“少数且易分离”的异常点,但原生实现对时间序列不友好。我们做三处关键改造:
- 输入特征降维:仅使用
RollingFeatureEngineer输出的差异特征(如temp_std_12,temp_diff_1),剔除原始值列,避免模型被平稳趋势主导; - 样本加权:对近期数据赋予更高权重,公式为
weight = np.exp(-0.001 * (len(data)-index)); - 异常分数校准:将 IF 的
decision_function输出映射到[0,1]区间,并关联物理量纲。
from sklearn.ensemble import IsolationForest from sklearn.preprocessing import StandardScaler class AdaptiveIsolationForest: def __init__(self, n_estimators: int = 100, max_samples: str = 'auto', contamination: float = 0.1, random_state: int = 42): self.n_estimators = n_estimators # 树数量:>50 提升稳定性,但增加延迟 self.max_samples = max_samples self.contamination = contamination # 预期异常比例,影响阈值自动设定 self.random_state = random_state self.scaler = StandardScaler() self.model = IsolationForest( n_estimators=n_estimators, max_samples=max_samples, contamination=contamination, random_state=random_state, n_jobs=-1 ) def fit(self, X: pd.DataFrame): # 特征标准化(IF 对量纲敏感) X_scaled = self.scaler.fit_transform(X) # 生成时间衰减权重 weights = np.exp(-0.001 * np.arange(len(X_scaled))[::-1]) self.model.fit(X_scaled, sample_weight=weights) def predict(self, X: pd.DataFrame) -> dict: X_scaled = self.scaler.transform(X) # raw_scores 越负越异常,转为 [0,1] 置信度 raw_scores = self.model.decision_function(X_scaled) # 归一化:将 raw_scores 映射到 0~1,0=正常,1=强异常 score_min, score_max = np.percentile(raw_scores, [10, 90]) normalized = np.clip((raw_scores - score_min) / (score_max - score_min + 1e-8), 0, 1) is_anomaly = (raw_scores < np.percentile(raw_scores, 10)) # 顶部10%为异常 return { 'is_anomaly': is_anomaly, 'anomaly_score': raw_scores, 'confidence': normalized, 'affected_features': self._identify_affected_columns(X, raw_scores) } def _identify_affected_columns(self, X: pd.DataFrame, scores: np.ndarray) -> list: # 找出对异常贡献最大的2个特征(基于局部敏感度近似) top_anomalies = np.argsort(scores)[:5] # 取最异常的5个点 feature_importance = np.abs(X.iloc[top_anomalies].corrwith( pd.Series(scores[top_anomalies]) )).sort_values(ascending=False) return feature_importance.head(2).index.tolist() # 使用示例:用滚动特征训练IF模型 fe = RollingFeatureEngineer(window_size=24, agg_funcs=['mean','std']) feature_df = fe.transform(raw_data[['temperature', 'pressure']]) detector_if = AdaptiveIsolationForest(n_estimators=200, contamination=0.05) detector_if.fit(feature_df) result_if = detector_if.predict(feature_df)关键参数说明:
n_estimators=200:工业场景数据噪声大,增加树数量提升判别鲁棒性;contamination=0.05:预设5%数据为异常,若实际异常率远低于此(如0.1%),模型会过度泛化,需下调;affected_features返回['temperature_std_24', 'pressure_diff_1'],直接告诉运维“温度波动过大且压力骤降”,而非笼统说“设备异常”。
3.3 LSTM Autoencoder 的时序重建法:捕获复杂模式偏移
当异常表现为“模式改变”而非“数值越界”(如泵效曲线畸变、电流谐波成分突变),LSTM-AE 更有效。我们采用轻量级结构,避免过拟合:
import torch import torch.nn as nn class LSTMAutoencoder(nn.Module): def __init__(self, input_dim: int, hidden_dim: int = 32, latent_dim: int = 8, num_layers: int = 1): super().__init__() self.hidden_dim = hidden_dim self.latent_dim = latent_dim self.num_layers = num_layers # Encoder self.lstm_encoder = nn.LSTM( input_size=input_dim, hidden_size=hidden_dim, num_layers=num_layers, batch_first=True, dropout=0.1 if num_layers > 1 else 0 ) self.fc_encoder = nn.Linear(hidden_dim, latent_dim) # Decoder self.fc_decoder = nn.Linear(latent_dim, hidden_dim) self.lstm_decoder = nn.LSTM( input_size=hidden_dim, hidden_size=hidden_dim, num_layers=num_layers, batch_first=True, dropout=0.1 if num_layers > 1 else 0 ) self.fc_output = nn.Linear(hidden_dim, input_dim) def forward(self, x): # x shape: (batch, seq_len, input_dim) _, (h_n, _) = self.lstm_encoder(x) # 取最后一层隐状态 z = torch.tanh(self.fc_encoder(h_n[-1])) # latent code h_dec = torch.tanh(self.fc_decoder(z)).unsqueeze(1) h_dec = h_dec.repeat(1, x.size(1), 1) # repeat for seq_len out, _ = self.lstm_decoder(h_dec) recon = self.fc_output(out) return recon # 训练循环(简化版) def train_ae(model, train_loader, epochs=50, lr=0.001): optimizer = torch.optim.Adam(model.parameters(), lr=lr) criterion = nn.MSELoss() for epoch in range(epochs): model.train() total_loss = 0 for batch in train_loader: x = batch.float() recon = model(x) loss = criterion(recon, x) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() if epoch % 10 == 0: print(f"Epoch {epoch}, Loss: {total_loss/len(train_loader):.4f}") return model # 使用提示:输入需为 (N, seq_len, features),seq_len 通常取 12~24(对应12~24分钟) # 异常分数 = mean(abs(x - recon)),阈值用 IQR 法动态计算部署要点:
hidden_dim=32/latent_dim=8:在边缘设备(如 Jetson Nano)上可实时推理;dropout=0.1:防止在小样本工业数据上过拟合;- 异常判定不直接用 reconstruction loss,而是:
# 对每个时间点计算重建误差 errors = np.mean(np.abs(x - recon), axis=2) # (N, seq_len) # 取最后1个点的误差(当前时刻重建偏差) current_errors = errors[:, -1] # 用 IQR 动态计算阈值 q1, q3 = np.percentile(current_errors, [25, 75]) iqr = q3 - q1 threshold = q3 + 1.5 * iqr
4. 工业落地必踩的5个坑及对应解决方案
4.1 坑:采样频率不一致导致“伪突变”,解决方案:强制重采样+插值策略分级
现象:同一产线的温度传感器(1s采样)和PLC状态码(10s上报)混合输入,rolling(12).std()在状态码列产生大量0值,被误判为“标准差突降”。
解决代码:
def robust_resample(df: pd.DataFrame, target_freq: str = '10S') -> pd.DataFrame: # 步骤1:对数值型列用线性插值(温度、压力) numeric_cols = df.select_dtypes(include=[np.number]).columns df_numeric = df[numeric_cols].resample(target_freq).interpolate(method='linear') # 步骤2:对类别型列用前向填充(设备状态、报警码) category_cols = df.select_dtypes(include=['object']).columns df_category = df[category_cols].resample(target_freq).ffill() # 步骤3:合并并填充剩余空值 result = pd.concat([df_numeric, df_category], axis=1) return result.fillna(method='ffill').fillna(0) # 调用 aligned_df = robust_resample(raw_data, target_freq='10S')4.2 坑:模型在训练集A上AUC=0.95,上线后F1<0.3,解决方案:引入概念漂移检测
原因:设备老化、环境温湿度变化、维护后工况偏移,导致数据分布缓慢变化(concept drift)。需定期检验模型有效性。
实现方案:用 K-S 检验对比新旧数据分布
from scipy.stats import ks_2samp def detect_concept_drift(new_data: np.ndarray, ref_data: np.ndarray, alpha: float = 0.05) -> bool: """Kolmogorov-Smirnov 检验:判断新数据分布是否显著偏移""" stat, p_value = ks_2samp(new_data, ref_data) return p_value < alpha # True 表示发生漂移,需重新训练 # 每24小时用最新1000点 vs 历史基准库检验 if detect_concept_drift( new_data=latest_features['temp_std_12'].values, ref_data=baseline_features['temp_std_12'].values ): print("检测到概念漂移,触发模型重训流程")4.3 坑:异常告警邮件刷屏,运维人员关闭通知,解决方案:事件聚合+置信度过滤
问题:单点异常每秒触发一次,1小时内发500封邮件。
聚合逻辑:
def aggregate_anomalies(anomaly_series: pd.Series, time_col: pd.Series, min_duration: str = '5T', min_confidence: float = 0.7) -> pd.DataFrame: """ 将连续异常点聚合成事件段 min_duration: 事件最小持续时间(如'5T'表示5分钟) min_confidence: 事件内最低置信度要求 """ # 筛选高置信度异常点 high_conf = anomaly_series[anomaly_series >= min_confidence] if len(high_conf) == 0: return pd.DataFrame() # 按时间排序并计算相邻点间隔 high_conf_sorted = high_conf.sort_index() intervals = high_conf_sorted.index.to_series().diff().dt.total_seconds() # 间隔 > 5分钟则切分新事件 event_groups = (intervals > 300).cumsum() events = [] for _, group in high_conf_sorted.groupby(event_groups): duration = (group.index.max() - group.index.min()).total_seconds() / 60 if duration >= 5: # 至少5分钟 events.append({ 'start_time': group.index.min(), 'end_time': group.index.max(), 'duration_min': duration, 'max_confidence': group.max(), 'avg_confidence': group.mean() }) return pd.DataFrame(events) # 调用示例 events = aggregate_anomalies( anomaly_series=result_if['confidence'], time_col=raw_data.index, min_duration='5T', min_confidence=0.75 )4.4 坑:GPU服务器训练快,但边缘设备无法部署,解决方案:ONNX 轻量化导出
LSTM-AE 模型转 ONNX 后体积缩小60%,推理速度提升3倍:
# 导出 ONNX dummy_input = torch.randn(1, 12, 4) # batch=1, seq=12, features=4 torch.onnx.export( model, dummy_input, "lstm_ae.onnx", input_names=["input"], output_names=["reconstruction"], dynamic_axes={"input": {0: "batch_size", 1: "sequence"}, "reconstruction": {0: "batch_size", 1: "sequence"}}, opset_version=11 ) # 边缘端推理(无需PyTorch) import onnxruntime as ort ort_session = ort.InferenceSession("lstm_ae.onnx") outputs = ort_session.run(None, {"input": x_numpy.astype(np.float32)})4.5 坑:算法准确率高,但运维看不懂报告,解决方案:生成可读性诊断摘要
def generate_diagnostic_report(anomaly_result: dict, raw_data: pd.DataFrame, feature_df: pd.DataFrame) -> str: """生成自然语言诊断报告""" if not anomaly_result['is_anomaly'].any(): return "当前数据未发现异常" # 找出最强异常点 idx = anomaly_result['anomaly_score'].argmax() time_point = raw_data.index[idx] affected = anomaly_result['affected_features'][0] # 如 'temperature_std_12' # 解析特征名含义 col_name = affected.split('_')[0] # 'temperature' feature_type = '_'.join(affected.split('_')[1:-1]) # 'std' window = affected.split('_')[-1] # '12' return f"""【异常诊断】 时间:{time_point.strftime('%Y-%m-%d %H:%M')} 位置:{col_name}列 现象:{feature_type}({window}点窗口)显著升高 解读:该列在最近{window}个采样点内波动性异常增强,可能原因: - 传感器接触不良(推荐检查接线) - 设备机械部件松动(建议安排振动分析) - 冷却系统效率下降(核查散热风扇状态)""" # 调用 report = generate_diagnostic_report(result_if, raw_data, feature_df) print(report)5. 验证异常检测效果:用真实故障注入测试框架量化指标
5.1 构建故障注入器:在历史数据中精准植入已知异常模式
不能只靠 AUC,要验证算法对真实故障类型的识别能力。我们设计 4 类工业典型故障并注入:
| 故障类型 | 注入方式 | 持续时间 | 检测难点 |
|---|---|---|---|
| 阶跃突变 | data.loc[start:end, 'temp'] += 15 | 3~5分钟 | 易检出,但需区分是故障还是校准操作 |
| 缓升漂移 | data.loc[start:end, 'temp'] += np.linspace(0, 8, end-start+1) | 30~120分钟 | 需长窗口特征,Z-Score 失效 |
| 周期性干扰 | data.loc[start:end, 'vib'] += 2*np.sin(2*np.pi*50*np.arange(end-start+1)/100) | 10秒~2分钟 | FFT 特征敏感,IQR 无效 |
| 数据中断 | data.loc[start:end, 'pressure'] = np.nan | 1~10分钟 | 需检测缺失模式,非数值异常 |
class FaultInjector: def __init__(self, data: pd.DataFrame): self.data = data.copy() self.original = data.copy() def inject_step_fault(self, col: str, start_idx: int, duration: int, delta: float): self.data.loc[start_idx:start_idx+duration, col] += delta return self.data def inject_drift_fault(self, col: str, start_idx: int, duration: int, max_delta: float): drift = np.linspace(0, max_delta, duration+1) self.data.loc[start_idx:start_idx+duration, col] += drift return self.data def inject_fft_fault(self, col: str, start_idx: int, duration: int, amplitude: float, freq_hz: float, sample_rate: int = 100): t = np.arange(duration+1) / sample_rate interference = amplitude * np.sin(2 * np.pi * freq_hz * t) self.data.loc[start_idx:start_idx+duration, col] += interference return self.data # 注入示例:在温度列第1000点开始注入5分钟缓升故障 injector = FaultInjector(raw_data) faulty_data = injector.inject_drift_fault('temperature', 1000, 300, 6.0) # 升高6℃5.2 定义工业级评估指标:不止 Precision/Recall,还要看“告警及时性”
| 指标 | 计算公式 | 工业意义 |
|---|---|---|
| Detection Delay (DD) | 告警首次触发时间 - 故障起始时间 | <3分钟为优秀,>10分钟可能错过干预窗口 |
| False Alarm Rate (FAR) | 误报次数 / 总监控时长(小时) | 目标 ≤0.1次/小时,避免告警疲劳 |
| Event Coverage (EC) | 正确捕获的故障事件数 / 总注入事件数 | 衡量对多类型故障的普适性 |
| Root Cause Accuracy (RCA) | 定位到正确字段的告警数 / 总告警数 | 反映affected_features的准确性 |
自动化评估脚本:
def evaluate_detector(detector, faulty_data: pd.DataFrame, fault_segments: list, # [(start, end, col), ...] time_col: str = 'timestamp') -> dict: # 运行检测 result = detector.predict(faulty_data) metrics = {'DD': [], 'FAR': 0, 'EC': 0, 'RCA': 0} total_events = len(fault_segments) for start, end, true_col in fault_segments: # 检查是否捕获该事件 detected_mask = result['is_anomaly'][start:end+1] if detected_mask.any(): metrics['EC'] += 1 # 计算延迟 first_alert = detected_mask.idxmax() delay = (faulty_data.index[first_alert] - faulty_data.index[start]).total_seconds() / 60 metrics['DD'].append(delay) # 检查根因准确性 if true_col in result['affected_features']: metrics['RCA'] += 1 # 计算 FAR:统计非故障时段的误报 normal_mask = ~pd.Series(False, index=faulty_data.index) for start, end, _ in fault_segments: normal_mask.loc[start:end] = True false_alarms = result['is_anomaly'][~normal_mask].sum() total_hours = len(faulty_data) / 60 # 假设1分钟采样 metrics['FAR'] = false_alarms / total_hours # 汇总 metrics['DD_mean'] = np.mean(metrics['DD']) if metrics['DD'] else np.inf metrics['EC_ratio'] = metrics['EC'] / total_events metrics['RCA_ratio'] = metrics['RCA'] / metrics['EC'] if metrics['EC'] > 0 else 0 return metrics # 评估示例 metrics = evaluate_detector(detector_if, faulty_data, fault_segments=[(1000, 1300, 'temperature')]) print(f"检测延迟均值: {metrics['DD_mean']:.1f}分钟") print(f"事件覆盖率: {metrics['EC_ratio']*100:.1f}%")注意:工业场景中,Detection Delay 比 Precision 更关键。一个延迟5分钟的告警,价值远高于一个即时但误报的告警——因为前者仍可能阻止停机,后者只会消耗人力。
本文还有配套的精品资源,点击获取