数据预处理:数据清洗、缺失值处理、时间戳对齐、数据标准化与归一化
数据预处理,说白了就是给原始数据「洗澡」。我做了这么多年安全分析,见过太多因为数据没洗干净就上模型,结果跑出来一堆垃圾结论的案例。你想想看,网络流量数据本身就很脏——采集器丢包、时间不同步、字段缺失,这些都是家常便饭。这一章,咱们就把这些坑一个一个填上。
核心观点:数据预处理不是可有可无的步骤,它直接决定了基线模型的上限。数据质量差,再牛的算法也救不回来。
3.1 数据清洗:把垃圾数据挡在门外
数据清洗,我习惯把它分成三步走:去重、去噪、去异常。每一步都有讲究。
3.1.1 去重处理
网络数据里重复记录很常见。比如同一个数据包被两个探针同时捕获,或者日志重传导致重复写入。重复数据会让统计指标失真,比如把流量计数翻倍。
我一般这样处理:
- 完全重复行:直接用
drop_duplicates()干掉 - 关键字段重复:比如相同的时间戳+源IP+目的IP,保留第一条,删除后续
- 业务语义重复:比如同一个会话的多次握手包,只保留一次
import pandas as pd
# 加载原始流量数据
df = pd.read_csv('network_flow.csv')
# 完全重复行删除
df_clean = df.drop_duplicates()
# 基于关键字段去重:时间戳+五元组
df_clean = df_clean.drop_duplicates(subset=['timestamp', 'src_ip', 'dst_ip', 'src_port', 'dst_port'])
我的经验:去重之前一定要先排序。按时间戳排好序再删,能保证保留的是最早出现的那条记录,这对时序分析很重要。
3.1.2 噪声过滤
网络数据里的噪声,常见的有:广播包、组播包、ICMP 探测包、DNS 查询失败记录。这些数据对建立正常行为基线没什么帮助,反而会拉偏统计分布。
我记得有一次做内网基线,发现流量峰值总是在凌晨3点出现。查了半天,原来是某台服务器定时发广播包。把这些广播包过滤掉之后,基线才恢复正常。
# 过滤广播和组播流量
df_clean = df_clean[~df_clean['dst_ip'].str.startswith('255.')]
df_clean = df_clean[~df_clean['dst_ip'].str.startswith('224.')]
# 过滤已知的探测流量
df_clean = df_clean[df_clean['protocol'] != 'ICMP']
3.1.3 异常值识别
这里的异常值不是指攻击流量,而是指明显不合逻辑的数据。比如:
- 数据包长度为负数
- 时间戳是未来时间
- 端口号为 0 或大于 65535
- IP 地址格式错误
这些数据直接删除,不要犹豫。
3.2 缺失值处理:别让空值毁了你的模型
缺失值在网络数据里太常见了。采集器负载高的时候会丢字段,网络抖动会导致部分日志没传上来。处理缺失值,我一般分三种情况:
| 缺失情况 | 处理方法 | 适用场景 |
|---|---|---|
| 少量缺失(<5%) | 均值/中位数填充 | 数值型字段,如包长度、持续时间 |
| 中等缺失(5%-20%) | 前向填充(ffill) | 时序数据,如连续采样的流量指标 |
| 大量缺失(>20%) | 直接删除该字段 | 该字段对建模贡献不大时 |
# 均值填充
df_clean['packet_len'].fillna(df_clean['packet_len'].mean(), inplace=True)
# 前向填充(时序数据)
df_clean['bytes_per_sec'].fillna(method='ffill', inplace=True)
# 删除缺失过多的列
df_clean.drop(columns=['unreliable_field'], inplace=True)
注意:千万不要用 0 来填充缺失值。0 本身是有意义的数值,用 0 填充会引入偏差。我曾经见过有人把缺失的流量字节数填成 0,结果基线模型把正常流量也判成了异常。
3.3 时间戳对齐:让不同数据源说同一种「时间语言」
网络环境里,不同设备的时间往往不同步。防火墙、IDS、服务器、交换机,各自的时间可能差几秒甚至几分钟。如果不做对齐,后续的关联分析全是错的。
我一般按以下步骤做时间戳对齐:
- 统一时间格式:全部转为 Unix 时间戳(秒级或毫秒级)
- 确定基准时间源:以 NTP 服务器的时间为准
- 计算时间偏移:每个数据源与基准时间的差值
- 重采样对齐:按固定时间窗口(如 1 秒、5 秒)重采样
# 统一转为 Unix 时间戳
df_clean['timestamp'] = pd.to_datetime(df_clean['timestamp']).astype(int) / 10**9
# 按 5 秒窗口重采样
df_resampled = df_clean.set_index('timestamp').resample('5S').agg({
'packet_count': 'sum',
'byte_count': 'sum',
'flow_count': 'count'
}).fillna(0)
避坑指南:我曾经遇到过一个问题——两个数据源的时间戳精度不同,一个精确到秒,一个精确到毫秒。直接对齐会导致大量数据错位。我的做法是先把高精度的时间戳向下取整到秒,再做对齐。
3.4 数据标准化与归一化:把不同量纲的数据拉到同一把尺子上
网络数据里,字段的量纲差异很大。比如包长度是几十到几千字节,而连接持续时间可能是几毫秒到几小时。如果不做标准化,模型会天然偏向数值大的字段。
我常用的两种方法:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Min-Max 归一化 | (x - min) / (max - min) | [0, 1] | 数据分布较均匀时 |
| Z-score 标准化 | (x - mean) / std | 均值为0,标准差为1 | 数据存在离群点时 |
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# Min-Max 归一化
scaler_minmax = MinMaxScaler()
df_normalized = pd.DataFrame(
scaler_minmax.fit_transform(df_resampled[['packet_count', 'byte_count']]),
columns=['packet_count_norm', 'byte_count_norm']
)
# Z-score 标准化
scaler_zscore = StandardScaler()
df_standardized = pd.DataFrame(
scaler_zscore.fit_transform(df_resampled[['packet_count', 'byte_count']]),
columns=['packet_count_std', 'byte_count_std']
)
我的建议:对于网络行为基线建模,我倾向于先用 Z-score 标准化。因为网络数据里经常有突发流量(比如凌晨的批量任务),这些离群点用 Min-Max 归一化会被压缩到很小的区间,丢失信息。Z-score 能更好地保留这些突发的统计特征。
3.5 数据预处理整体流程
下面这张图,是我做数据预处理的完整流程。你可以把它当成一个检查清单,每次处理数据时对照着走一遍。
这四步走完,你的数据才算真正「能用」。我做了这么多年,发现很多人急着调模型,却不愿意花时间在数据预处理上。结果模型跑出来效果不好,又回头去调参数,折腾半天才发现是数据本身有问题。
最后说一句:数据预处理没有银弹。每套网络环境都有自己的「脾气」,你得花时间去了解你的数据长什么样。多画几张分布图,多看看统计摘要,比什么都管用。