数据预处理:缺失值处理、标准化、降维(PCA)
数据预处理这事,说白了就是给模型准备一顿「干净」的饭。你想想看,异常检测模型本身就是在找那些「不一样」的点,如果数据本身就有问题——缺胳膊少腿、尺度乱七八糟、噪音满天飞——那模型找出来的异常,到底是真异常还是数据质量问题?
我个人习惯把预处理看作整个异常检测 pipeline 里最花时间的一环。不是代码难写,是决策难做。每个缺失值怎么填,每个特征怎么缩,要不要降维,这些选择直接影响后面的检测效果。好,咱们一个一个来拆。
核心观点:预处理不是「做完就行」,而是要为你后续的异常检测算法量身定制。同样的数据,不同的预处理方式,模型结果可能天差地别。
缺失值处理
先问个问题:异常检测里,缺失值本身算不算一种「异常」?
我觉得得分场景。如果某个传感器在某个时间段突然没数据了,这本身就是一个信号。但大多数时候,缺失值就是数据采集过程中的「事故」,我们需要在建模前把它处理好。
常见处理方法
| 方法 | 适用场景 | 异常检测中的风险 |
|---|---|---|
| 直接删除 | 缺失比例 < 5%,且随机缺失 | 可能删除掉真正的异常点 |
| 均值/中位数填充 | 数值型特征,分布较对称 | 会拉低异常点的离群程度,掩盖异常 |
| 前向/后向填充 | 时间序列数据,短期波动不大 | 会平滑掉突变,而突变可能就是异常 |
| 插值法(线性/样条) | 连续型特征,变化有趋势 | 同样可能抹掉异常跳变 |
| 模型预测填充 | 特征间关联性强,计算资源充足 | 可能引入模型偏差,让异常点「被正常化」 |
我的习惯:先看缺失比例。超过 30% 的特征,我倾向于直接丢掉,或者单独做二值化标记「是否缺失」。对于 5%~30% 的情况,我会用 KNN 填充 或 迭代回归填充,尽量保留数据的原始分布结构。
我在项目中遇到过一件事:用均值填充一个用户行为指标,结果异常检测模型把所有缺值的用户都判为正常了。后来发现,那些缺值的用户恰恰是因为行为异常导致数据没采集到。嗯,从那以后,我对「缺失值本身可能就是异常信号」这点特别敏感。
避坑指南:我曾经在金融交易数据上用前向填充处理缺失值,把一笔可疑交易的金额「继承」到了下一笔正常交易上,导致模型漏报。后来我改成用标记法——缺失值先填充一个占位符(如 -999),再新增一列 is_missing 做标记。这样模型至少有机会学到「缺失」这个模式。
数据标准化
异常检测里,距离度量是家常便饭。欧氏距离、马氏距离、余弦相似度……你想想看,如果特征 A 的取值范围是 0~100000,特征 B 是 0~1,那计算距离时 A 几乎完全主导了结果。特征 B 不管怎么异常,都「吵」不过 A。
这就是标准化的意义——让每个特征在同一个尺度上「发言」。
三种主流标准化方法
| 方法 | 公式 | 对异常值的鲁棒性 | 输出范围 |
|---|---|---|---|
| Z-score 标准化 | (x - μ) / σ | 低(均值和标准差会被异常值拉偏) | 无固定范围,通常 ~ [-3, 3] |
| Min-Max 标准化 | (x - min) / (max - min) | 极低(min/max 本身就是异常值敏感) | [0, 1] |
| Robust 标准化 | (x - median) / IQR | 高(中位数和四分位距不受极端值影响) | 无固定范围,但更稳定 |
# 我常用的标准化代码示例
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# 如果数据里已经有明显异常,我首选 RobustScaler
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)
# 如果数据比较干净(比如已经做过异常剔除),Z-score 也行
# scaler = StandardScaler()
# X_scaled = scaler.fit_transform(X)
我习惯在异常检测任务里优先用 RobustScaler。为什么?因为异常检测本身就是要找离群点,如果用 Z-score,那些真正的异常会拉偏均值和标准差,导致正常点被「压缩」到一个更窄的范围里,异常反而没那么显眼了。RobustScaler 用中位数和 IQR,说白了就是「我不让你异常值影响我的基准尺度」。
小技巧:标准化之后,可以用箱线图快速检查一下各个特征的分布范围是否对齐。如果某个特征标准化后仍然有大量值超出 ±5 的范围,那这个特征可能需要单独处理——比如先做对数变换再标准化。
降维(PCA)
降维在异常检测里有两副面孔。一副是「减噪音」——把不重要的维度去掉,让异常在主成分上更突出。另一副是「抓结构」——在高维空间里,异常点往往藏在稀疏区域,PCA 可以把数据投影到低维子空间,让异常更容易暴露。
PCA 的核心逻辑
说白了,PCA 就是找一组新的坐标系,让数据在新坐标系里的方差尽可能大。第一个新轴(第一主成分)抓住最大的方差方向,第二个抓住剩余的最大方差方向,依次类推。最后我们只保留前 k 个主成分,丢掉后面的。
from sklearn.decomposition import PCA
from sklearn.preprocessing import RobustScaler
# 先标准化,再 PCA
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)
# 保留能解释 90% 方差的主成分数
pca = PCA(n_components=0.90)
X_pca = pca.fit_transform(X_scaled)
# 看看每个主成分的解释方差比
print("解释方差比:", pca.explained_variance_ratio_)
print("累计解释方差:", pca.explained_variance_ratio_.cumsum())
如何选择主成分数量?
我在项目中一般用两种方式交叉确认:
- 累计方差阈值法:保留能解释 80%~95% 方差的主成分数。具体选多少,看业务容忍度。容忍度低的(比如金融欺诈),我倾向多留一些维度,宁多勿少。
- 肘部法则:画解释方差比随主成分数的变化曲线,找「拐点」。过了那个点,再加主成分也解释不了多少新信息。
注意:PCA 对标准化非常敏感。如果不做标准化直接上 PCA,量级大的特征会主导主成分方向。我曾经见过一个案例,有人拿原始数据跑 PCA,结果第一主成分几乎完全等于那个量级最大的特征——那 PCA 就白做了。
我曾经在一个工业设备监控项目里,用 PCA 把 120 维的传感器数据降到 15 维。降维之后,用孤立森林检测异常,F1 分数从 0.67 提升到了 0.84。为什么?因为原始数据里很多传感器通道是强相关的(比如相邻位置的温度),这些冗余维度本质上是噪音,PCA 帮我们把信号集中到了少数几个主成分上。
本章知识体系
下面这张图把三个预处理步骤串起来了。从原始数据开始,先处理缺失值,再做标准化,最后用 PCA 降维。每一步的输出就是下一步的输入。注意看每个步骤里还有不同的「分支选择」——具体用哪个方法,取决于你的数据特性和业务场景。
三个步骤的串联思考
预处理不是流水线上的独立工位。缺失值处理会影响标准化——比如用均值填补缺失,会拉低标准差,进而影响 Z-score 的结果。标准化又会影响 PCA——不标准化直接降维,量级大的特征会喧宾夺主。
我一般按这个顺序走:
- 先处理缺失值——用标记法或填充法,确保数据完整
- 再做标准化——首选 RobustScaler,避免异常值干扰尺度
- 最后 PCA 降维——在标准化后的数据上做,用方差阈值或肘部法则定维度
这个顺序不是死规矩,但在大多数场景下是最稳的。你想想看,如果先标准化再填充缺失,那填充值用的是标准化后的尺度,逻辑上有点拧巴。
一个小建议:每次做预处理时,把每一步的参数和结果都记录下来。比如缺失值填充的数值、标准化所用的中位数和 IQR、PCA 的累计方差比。这些「元数据」在模型上线后排查问题时特别有用。我在生产环境里吃过这个亏——模型跑了一段时间,效果下降,查了半天才发现是上游数据分布变了,但预处理参数还是旧的。
好,数据预处理这部分就聊到这儿。核心就三件事:把缺失填上、把尺度对齐、把维度降下来。每件事都有好几个做法,选哪个取决于你的数据长什么样、你的模型怕什么。多试、多对比,慢慢就有感觉了。