4、基于统计的异常检测:3-Sigma原则、Z-Score方法、IQR(四分位距)法、移动平均残差分析
聊到无人机遥测数据的异常检测,我第一个想到的就是统计方法。
为什么?因为简单、高效、可解释性强。你想想看,无人机在天上飞,数据哗哗地往回传,你不可能每秒钟都盯着屏幕看。这时候,统计方法就像个老练的哨兵,帮你盯着那些「不对劲」的数字。
我个人习惯,在搭建异常检测系统时,永远先上统计方法。不是因为它最牛,而是因为它最稳。深度学习模型再花哨,也得有个基线做对比,对吧?
4.1 3-Sigma原则:最经典的「三倍标准差」法则
3-Sigma原则,说白了就是:如果数据服从正态分布,那么99.7%的数据都会落在均值±3个标准差的范围内。落在外面?那大概率是异常。
我在项目中遇到过这样的情况:某款无人机的高度传感器,在悬停时偶尔会跳变。用3-Sigma一查,好家伙,那些跳变点全都在3σ之外。后来发现是传感器受到电磁干扰。
数学表达:
- 正常区间: [μ - 3σ, μ + 3σ]
- 异常判定: 数据点 x 满足 |x - μ| > 3σ
代码示例:
import numpy as np
def detect_3sigma(data):
mean = np.mean(data)
std = np.std(data)
threshold = 3 * std
anomalies = []
for i, val in enumerate(data):
if abs(val - mean) > threshold:
anomalies.append((i, val))
return anomalies
# 模拟遥测数据
telemetry = np.random.normal(100, 5, 1000) # 均值100,标准差5
telemetry[500] = 130 # 人为注入异常
telemetry[800] = 70 # 人为注入异常
result = detect_3sigma(telemetry)
print(f"发现 {len(result)} 个异常点")
4.2 Z-Score方法:标准化的异常评分
Z-Score其实就是3-Sigma的「标准化版本」。它把每个数据点转换成「距离均值多少个标准差」的分数。
公式: Z = (x - μ) / σ
Z-Score的好处是:它给出了一个连续的异常评分,而不是简单的「是/否」判定。你可以设定不同的阈值,比如|Z| > 2 算轻度异常,|Z| > 3 算严重异常。
代码示例:
def detect_zscore(data, threshold=3):
mean = np.mean(data)
std = np.std(data)
z_scores = [(x - mean) / std for x in data]
anomalies = [(i, data[i], z_scores[i])
for i in range(len(data))
if abs(z_scores[i]) > threshold]
return anomalies
# 使用示例
anomalies = detect_zscore(telemetry, threshold=2.5)
print(f"Z-Score检测到 {len(anomalies)} 个异常")
for idx, val, z in anomalies[:3]:
print(f" 索引{idx}: 值={val:.2f}, Z-Score={z:.2f}")
4.3 IQR(四分位距)法:不怕偏态分布的「稳健派」
IQR法是我个人最喜欢的统计方法。为什么?因为它不假设数据服从正态分布。你想想看,无人机在爬升、巡航、降落阶段,数据分布完全不一样。用IQR法,它只看数据的「相对位置」。
核心逻辑:
- Q1:下四分位数(25%分位点)
- Q3:上四分位数(75%分位点)
- IQR = Q3 - Q1
- 正常区间:[Q1 - 1.5×IQR, Q3 + 1.5×IQR]
代码示例:
def detect_iqr(data):
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
anomalies = [(i, data[i]) for i in range(len(data))
if data[i] < lower_bound or data[i] > upper_bound]
return anomalies, lower_bound, upper_bound
# 模拟非正态分布数据(比如飞行速度)
speed_data = np.concatenate([
np.random.normal(50, 10, 800), # 巡航阶段
np.random.normal(20, 5, 200), # 降落阶段
])
speed_data[900] = 100 # 异常:突然加速
anomalies, lb, ub = detect_iqr(speed_data)
print(f"IQR检测到 {len(anomalies)} 个异常")
print(f"正常区间: [{lb:.2f}, {ub:.2f}]")
4.4 移动平均残差分析:捕捉「趋势中的异常」
前面三种方法,都是针对「静态数据」的。但无人机遥测数据是时间序列啊!高度在变化、速度在变化、电池电压在下降。这时候,你需要的是「移动平均残差分析」。
思路:
- 计算移动平均(比如过去10个点的平均值),得到「预测值」
- 计算残差 = 实际值 - 预测值
- 对残差序列应用3-Sigma或IQR,检测异常
我记得有一次,无人机在爬升过程中,高度数据突然「卡住」了5秒钟。用静态方法根本检测不出来,因为高度本身就在上升。但用移动平均残差分析,残差突然变大,一下子就抓住了。
代码示例:
def moving_average_residual(data, window=10, threshold=3):
# 计算移动平均
ma = np.convolve(data, np.ones(window)/window, mode='valid')
# 计算残差(对齐索引)
residuals = data[window-1:] - ma
# 对残差应用3-Sigma
mean_res = np.mean(residuals)
std_res = np.std(residuals)
anomalies = []
for i, res in enumerate(residuals):
if abs(res - mean_res) > threshold * std_res:
# 注意:i对应原始数据的索引是 window-1+i
anomalies.append((window-1+i, data[window-1+i], res))
return anomalies, residuals
# 模拟爬升过程中的高度数据
time = np.arange(200)
altitude = 100 + 0.5 * time + np.random.normal(0, 2, 200) # 线性爬升
altitude[100:105] = altitude[100] # 模拟传感器卡住
anomalies, residuals = moving_average_residual(altitude, window=10)
print(f"移动平均残差法检测到 {len(anomalies)} 个异常")
for idx, val, res in anomalies[:3]:
print(f" 时间{idx}: 高度={val:.2f}, 残差={res:.2f}")
4.5 四种方法对比与选择
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 3-Sigma | 正态分布数据(如传感器噪声) | 简单直观,计算快 | 对偏态分布不友好 |
| Z-Score | 需要异常评分时 | 可设定多级阈值 | 同样依赖正态假设 |
| IQR | 非正态分布、多模态数据 | 稳健,不受极端值影响 | 对数据量有要求(至少几十个点) |
| 移动平均残差 | 时间序列、趋势数据 | 能检测趋势中的异常 | 需要调窗口参数 |
嗯,这里要注意:没有万能的方法。我在实际项目中,通常会把四种方法组合使用。比如先用IQR做粗筛,再用移动平均残差做细查。
4.6 知识体系图
下面这张图,帮你理清这四种方法的关系和适用场景:
可可精品资料库