数据预处理:数据清洗、缺失值处理、时间戳对齐、数据标准化与归一化

数据预处理,说白了就是给原始数据「洗澡」。我做了这么多年安全分析,见过太多因为数据没洗干净就上模型,结果跑出来一堆垃圾结论的案例。你想想看,网络流量数据本身就很脏——采集器丢包、时间不同步、字段缺失,这些都是家常便饭。这一章,咱们就把这些坑一个一个填上。

核心观点:数据预处理不是可有可无的步骤,它直接决定了基线模型的上限。数据质量差,再牛的算法也救不回来。

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、服务器、交换机,各自的时间可能差几秒甚至几分钟。如果不做对齐,后续的关联分析全是错的。

我一般按以下步骤做时间戳对齐:

  1. 统一时间格式:全部转为 Unix 时间戳(秒级或毫秒级)
  2. 确定基准时间源:以 NTP 服务器的时间为准
  3. 计算时间偏移:每个数据源与基准时间的差值
  4. 重采样对齐:按固定时间窗口(如 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 数据预处理整体流程

下面这张图,是我做数据预处理的完整流程。你可以把它当成一个检查清单,每次处理数据时对照着走一遍。

数据预处理整体流程图 原始网络流量数据 步骤1:数据清洗 去重 → 去噪 → 异常值识别 步骤2:缺失值处理 均值填充 / 前向填充 / 删除字段 步骤3:时间戳对齐 统一格式 → 计算偏移 → 重采样 步骤4:标准化与归一化 Min-Max / Z-score 标准化 干净可用的基线数据

这四步走完,你的数据才算真正「能用」。我做了这么多年,发现很多人急着调模型,却不愿意花时间在数据预处理上。结果模型跑出来效果不好,又回头去调参数,折腾半天才发现是数据本身有问题。

最后说一句:数据预处理没有银弹。每套网络环境都有自己的「脾气」,你得花时间去了解你的数据长什么样。多画几张分布图,多看看统计摘要,比什么都管用。