数据预处理:清洗与归一化

日志数据拿到手,别急着往模型里塞。我见过太多人直接把原始日志丢给算法,结果模型学了一堆“垃圾”规律,根本没法用。数据预处理,说白了就是把脏数据洗干净,再给数据“统一度量衡”。这一步做不好,后面的一切都是白搭。

缺失值处理

日志里缺字段是家常便饭。有时候是采集出了问题,有时候是字段本身就不存在。对于缺失值,我的态度是——别盲目删除。

我个人习惯先做一次数据集概览,看看哪些字段缺失严重。

处理方法 适用场景 我踩过的坑
直接删除 缺失比例 < 5%,且数据量充足 删太多会把有效信息也丢掉,样本量少的时候别用
均值/中位数填充 数值型字段,缺失不多 对异常值敏感,如果异常值多,中位数比均值靠谱
众数填充 类别型字段,如错误码、等级 如果有多个众数,随便选一个,但记得记录
前向/后向填充 时间序列日志,相邻记录关联性强 如果数据是乱序的,千万别用这个
模型预测填充 缺失比例高且字段重要 开销大,简单场景没必要

我建议一般情况下优先用中位数填充。为什么?因为日志里经常有突发高值,均值容易被带偏。我有个项目,CPU使用率字段有10%的缺失,我用了均值填充,结果模型把所有CPU都当成了平均值,后来发现中位数更贴近实际。


# 中位数填充示例
import pandas as pd
df = pd.read_csv('app_logs.csv')
cpu_median = df['cpu_usage'].median()
df['cpu_usage'].fillna(cpu_median, inplace=True)
    

⚠️ 注意:不要对缺失值过多的字段做填充(比如 > 50%),直接考虑丢弃该字段更安全。我曾经试过填充一个缺失70%的字段,结果模型性能反而下降了。

异常值过滤

日志里总有那么几个“捣乱分子”——某个节点网络延迟瞬间飙到10秒,或者磁盘IOPS突然暴涨。这些就是异常值。如果不处理,模型会把它们当成正常模式,结果就是真正的“故障异常”反而被淹没了。

我最常用的是3σ原则和箱线图(IQR)。

  • 3σ原则:假设数据服从正态分布,超出均值±3倍标准差的值视为异常。适用于数据量大、分布比较对称的场景。
  • IQR法:四分位距法,取Q1-1.5*IQR到Q3+1.5*IQR之外的值。适合数据分布不规整的情况。我偏爱这个,因为日志数据多半是偏态的。

举个例子,某个日志字段记录了请求响应时间。大部分在50ms~200ms之间,突然冒出一个5000ms的。用IQR可以快速定位。


# IQR法过滤异常值
Q1 = df['response_time'].quantile(0.25)
Q3 = df['response_time'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df = df[(df['response_time'] >= lower_bound) & (df['response_time'] <= upper_bound)]
    

💡 核心逻辑:异常值过滤不是要把所有“奇怪”的数据都删掉。对于那些一出现就代表系统故障的值(比如error日志条目),它们本身是我们要检测的目标,不能过滤。

我曾经接手过一套系统,日志里包含一个叫做“error_count”的字段。按理说它是正常值,但有一次误过滤后,模型再也没能正确识别出故障。所以,一定要弄清楚你的日志里哪些字段是“信号”,哪些是“噪声”。

数据标准化与归一化

标准化和归一化,这两个概念经常被搞混。我简单讲清楚。

方法 公式 结果范围 我什么时候用
Z-score 标准化 (x - μ) / σ 均值0,标准差1,范围不固定 数据存在异常值,或需要对比不同量纲的特征
Min-Max 归一化 (x - min) / (max - min) [0, 1] 数据范围固定,没有极端异常值
RobustScaler (x - median) / IQR 不固定 数据中异常值很多,不想被它们影响

为什么非要做这一步?因为很多机器学习算法(比如K-means、SVM、神经网络)对特征的尺度很敏感。你想想看,CPU使用率范围是0~100,而内存占用量按字节算,动辄几十亿,如果不统一尺度,算法就会认为“内存”比“CPU”重要一万倍。

我个人习惯,如果日志数据里有明确的物理限制(比如百分比、次数),我会优先用Min-Max归一化。但如果是那种会突然飙升的字段(比如网络丢包率),我更喜欢Z-score。RobustScaler我用的不多,但对付“脏数据”确实好使。


# Z-score 标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['cpu_usage', 'memory_usage', 'response_time']])

# Min-Max 归一化
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df[['cpu_usage', 'memory_usage', 'response_time']])
    

💡 注意:标准化和归一化的参数(均值、标准差、min、max)一定要只在训练集上计算,然后用同样的参数去转换验证集和测试集。要是用全量数据算,就“数据泄露”了。

嗯,这里还要提醒一点:类别特征也要处理。比如日志中的“错误级别”有INFO、WARNING、ERROR,不能直接当成1、2、3排序。INFO和ERROR的差距不是1和3的差距。我会选择独热编码(One-Hot Encoding)或者标签编码(Label Encoding)。独热编码更适合树模型,标签编码看具体场景。

我记得有一次,有人把日志里的“服务器ID”直接当数值特征喂给模型。结果模型学到的是“ID=3的服务器比ID=2的服务器更容易出故障”,简直滑稽。所以,对于这类无序类别,一定要做独热编码。


# 独热编码示例
df = pd.get_dummies(df, columns=['severity'])
    

最后,把数据预处理的全流程串起来,大概是这么个路子:

数据预处理全流程 原始日志数据 缺失值处理 异常值过滤 标准化/归一化 干净日志数据 说明: 三个分支可以并行,也可以按实际场景选择部分步骤。没有强制顺序。 但实战中建议: 先处理缺失值 → 再过滤异常值 → 最后做尺度变换。

总的来说,数据预处理不是花架子,是模型性能的基石。你把数据洗得越干净,模型学得越准。少不了的投入,少不了的效果。