数据预处理:清洗与归一化
日志数据拿到手,别急着往模型里塞。我见过太多人直接把原始日志丢给算法,结果模型学了一堆“垃圾”规律,根本没法用。数据预处理,说白了就是把脏数据洗干净,再给数据“统一度量衡”。这一步做不好,后面的一切都是白搭。
缺失值处理
日志里缺字段是家常便饭。有时候是采集出了问题,有时候是字段本身就不存在。对于缺失值,我的态度是——别盲目删除。
我个人习惯先做一次数据集概览,看看哪些字段缺失严重。
| 处理方法 | 适用场景 | 我踩过的坑 |
|---|---|---|
| 直接删除 | 缺失比例 < 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'])
最后,把数据预处理的全流程串起来,大概是这么个路子:
总的来说,数据预处理不是花架子,是模型性能的基石。你把数据洗得越干净,模型学得越准。少不了的投入,少不了的效果。