简介:面向网络安全和人工智能方向的机器学习实践资料,聚焦基于机器学习的DDoS入侵检测算法,整合了毕业设计项目的核心内容。压缩包共4个文件,含3个Python脚本和1个Word文档,包体仅241KB,便携易用。Python脚本分别实现了逻辑回归、正则化逻辑回归及多类别逻辑回归,可用于网络流量特征分类与异常识别;Word文档系统梳理了整体毕业设计思路。通过这份资料,读者能完整了解DDoS检测中数据清洗、归一化、特征选择、模型训练与评估等环节,掌握用逻辑回归区分正常流量与攻击流量的方法,也能为之后尝试决策树、支持向量机或神经网络等算法提供思路。已有1073人学习下载,适合网络安全初学者、机器学习爱好者及正在准备相关毕业设计的高校学生参考。
1. 为什么不把 DDoS 检测直接做成分类问题
凌晨收到两波 SYN Flood 告警,防火墙拉黑源 IP 之后,攻击者换段端口又把出口打满。固定阈值检测难在平衡:阈值低了把秒杀流量误判成攻击,阈值高了攻击已经拖垮业务你才收到告警。机器学习把这个判定从规则匹配换成二分类:把每条或每个窗口的流量抽成特征向量,用带标签的训练数据让分类器学到正常与攻击的边界。这类项目解压之后,通常不是拿来就能用的黑盒模型,而是一套从特征工程、模型训练到检测服务的代码流程;下面这套流程覆盖数据集选型、特征工程、训练评估、实时检测和重放验证。它适用的人群很明确:正在搭安全运营平台的一线运维,以及想把论文里检测算法落到工程里的算法工程师。
2. DDoS 数据集与特征工程:先定特征再谈机器学习算法
2.1 CICIDS、UNSW-NB15、NSL-KDD 怎么选
公开流量数据集有三个常见选择。CICIDS 2017 覆盖了 DDoS、PortScan、Botnet 等 14 种攻击,每条流有 80 多维特征,适合拿来做一个现代的二分类基线。UNSW-NB15 的特征维度少一些,混合了 DoS、Exploits、Fuzzers,适合验证模型在不同攻击风格下的泛化能力。NSL-KDD 里的 U2R、R2L 样本极少,却是很多论文复现“对稀有攻击检测”时的基准。选哪个不取决于模型,而取决于你最后部署到的场景。
| 数据集 | 常见攻击类型 | 特征维度 | 适合做什么 |
|---|---|---|---|
| CICIDS 2017 | DDoS、PortScan、Botnet | 80+ | 生产流量分类基线 |
| UNSW-NB15 | DoS、Exploits、Fuzzers | 49 | 攻击泛化能力验证 |
| NSL-KDD | R2L、U2R、DoS、Probe | 41 | 稀有攻击与论文复现 |
如果是用真实抓包自建数据集,重点就不是找现成 CSV,而是先定义流。一个流的常见定义是五元组加超时:源 IP、源端口、目的 IP、目的端口、协议,TCP 下通常 15 秒内没有新包认为流结束。特征从流持续时长、包长分布、标记位计数、窗口字节数里提取。这一步顺序不能颠倒,否则后面训练出的模型只会在特定数据集上有效。
2.2 用 Pandas 构建流量特征矩阵
CICIDS 2017 的官方 CSV 单个文件通常超过 1GB,列名里带空格和冒号,直接读会踩很多坑。我一般先统一列名再做最小清洗,再把“是否攻击”转成 0/1 标签。
import pandas as pd import numpy as np # 分块读取,避免 2GB 级 CSV 挤爆内存 df = pd.read_csv( "Friday-WorkingHours-Afternoon-DDos.pcap_ISCX.csv", low_memory=False, ) # CICIDS 列名含空格和冒号,统一成下划线风格 df.columns = [ c.strip().replace(" ", "_").replace(":", "") for c in df.columns ] # 挑选与 DDoS 判定强相关的特征子集 feature_cols = [ "Flow_Duration", "Total_Fwd_Packets", "Total_Backward_Packets", "Fwd_Packet_Length_Mean", "Bwd_Packet_Length_Mean", "SYN_Flag_Cnt", "ACK_Flag_Cnt", "Init_Win_bytes_forward", "Init_Win_bytes_backward", ] # CICIDS 的 Label 是字符串,DDoS 包含在类型名里 df["Label"] = df["Label"].astype(str) df["is_ddos"] = df["Label"].str.contains("DDoS", case=False, na=False).astype(int) df[feature_cols] = df[feature_cols].fillna(0) df = df[df["Flow_Duration"] > 0] print(df.shape, "DDoS占比:", df["is_ddos"].mean())代码里的特征子集是按检测语义挑的:SYN_Flag_Cnt 高通常是 SYN Flood 的直接信号,Flow_Duration 很短但包数很多也指向握手风暴。缺失值统一补 0,因为 CICIDS 某些特征在特殊流上根本没有统计出数值,补 0 比删行更安全,代价是这些样本在树模型上会走到同一个分支。DDoS 占比如果超过 70%,别急着高兴,这只说明数据挑选得过分理想,不代表模型够好。
2.3 特征筛选与 R2L/U2R 小类标签
如果目标是论文里常见的 R2L 和 U2R 检测,直接做 DDoS 二分类会漏掉关键信息。这两类攻击在 NSL-KDD 里的样本可能只有几十条,特征分布和正常流量重叠很大。常见做法是分成两级:第一级用一个泛分类器识别“是否异常”,第二级再对异常样本单独建多分类或规则模型。就算只做二分类,也要在训练前确认正样本数,少于 500 条的小类不建议直接进随机森林,否则召回会趋近于 0。
特征筛选不要一上来就做方差过滤。DDoS 检测里,方差极低但语义明确的字段,比如特定 Flag 计数,往往只在攻击时出现抖动。我一般先跑一版全特征模型,再按 feature_importances_ 取 Top 20 做对比实验,验证精度下降不超过 1% 才截断。否则宁可保留冗余,也不为减少训练时间牺牲对稀有流量的敏感性。
3. 用决策树与随机森林跑通 DDoS 分类器
3.1 为什么先从树模型开始
DDoS 流量特征大部分是离散计数和长度统计,树模型对这类表格特征往往更适合作为第一版模型。随机森林天然支持类别不平衡时的类权重,不用做繁重归一化,训练完还能直接输出特征重要性,这对后续解释“模型靠什么判断 DDoS”非常重要。深度学习在流量检测上的价值体现在原始包方向,比如把载荷做成时间序列或者图片,但这套流程依赖 GPU 和大量干净数据,不适合作为博客落地的首个基线。机器学习算法有很多种,做入侵检测时先跑通一个能用、能解释的模型,比追求新算法重要得多。
3.2 训练脚本与随机森林的 3 个必调参数
下面脚本基于上一章得到的 feature_cols 和 is_ddos 标签。切分时注意别用默认随机切分,最好按时间顺序切分,或至少保证同一来源 IP 只在训练集或测试集出现。用 stratify 保持标签比例一致性。
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report X = df[feature_cols].copy() y = df["is_ddos"].copy() X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y, ) clf = RandomForestClassifier( n_estimators=200, max_depth=12, min_samples_leaf=5, class_weight="balanced_subsample", n_jobs=-1, random_state=42, ) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(classification_report(y_test, y_pred, target_names=["normal", "ddos"]))随机森林有 3 个参数值得认真调。n_estimators 从 100 加到 300,AUC 通常只提升零点几个百分点,收益递减明显,默认 200 足够。max_depth 限制树的层数,防止包长特征被切得过细;在 14 维特征上 12 层已经很深,再大容易把训练集噪声记住。min_samples_leaf 是叶子最少样本数,设成 5 或 10 能避免叶子节点落到单条采样的局部模式上。class_weight 用不太常见的 balanced_subsample,它会在每棵树的自助采样里重新计算权重,比 balanced 对随机森林的方差控制更好。参数调整可以用 GridSearchCV,但要限定总搜索时间,否则 5 折交叉验证会让训练时长膨胀好几倍。
| 参数 | 常用取值范围 | 我常用的基线 | 调参方向 |
|---|---|---|---|
| n_estimators | 100-500 | 200 | 增量收益递减,不追求大 |
| max_depth | 8-20 | 12 | 特征多时加大,过拟合时降低 |
| min_samples_leaf | 3-10 | 5 | 小类太少时减小到 1-2 |
| class_weight | balanced/balanced_subsample | balanced_subsample | 正负样本比悬殊时开启 |
3.3 评估指标:别拿准确率骗自己
DDoS 检测里正样本往往占 60% 以上,模型全部预测成正样本也有 60% 准确率,这个数字没有意义。要盯着三个指标:精确率、召回率、ROC-AUC。精确率管误报,召回率管漏报,安全场景里通常先保底召回率,误报再靠运营手段消除。
from sklearn.metrics import roc_auc_score, confusion_matrix proba = clf.predict_proba(X_test)[:, 1] print("ROC-AUC:", roc_auc_score(y_test, proba)) cm = confusion_matrix(y_test, y_pred) print(cm) imp = pd.Series(clf.feature_importances_, index=feature_cols) print(imp.sort_values(ascending=False).head(5))ROC-AUC 描述模型在不同阈值下的整体区分能力,0.95 以上才算合格基线。混淆矩阵里重点看 FN,也就是把 DDoS 判成正常的部分,这类样本会直接变成漏网之鱼。特征重要性 Top 5 能告诉你 SYN_Flag_Cnt 或 Flow_Duration 在模型里起了多大作用,这也是树模型比深度学习好落地的地方。
4. 把模型接入实时流量:窗口投票与阈值设计
4.1 流式检测为什么不能等一条流结束
离线 CSV 里一条流是完整的,在线场景却要实时决策。如果等一条 TCP 流真正结束,SYN Flood 早就把连接表塞满。常见做法是把时间切成滑动窗口,比如每 5 秒聚合一次窗口内所有新包。窗口太大延迟高,窗口太小特征波动大,误报明显上升。我一般从 5 秒开始,按业务流量峰值调整;攻击量大时下调到 2 秒,流量平缓的办公网络可以到 10 秒。
注意:窗口内样本数过少时不要输出预测结果,这能避免空闲时段的小流量波动直接触发闪烁告警。
4.2 概率阈值加多数投票的工程实现
模型训练时用的是 0.5 默认阈值,在线检测最好改用 predict_proba 的输出,因为实时特征噪声会让概率在 0.5 附近来回跳动。下面这段代码用固定大小窗口聚合特征,再做硬投票判定。
from collections import deque import time WINDOW_SECONDS = 5 VOTE_SIZE = 5 P_THRESHOLD = 0.8 packet_buffer = deque(maxlen=10000) score_history = deque(maxlen=VOTE_SIZE) def on_packet(pkt_stat): packet_buffer.append((time.time(), pkt_stat)) current = time.time() window = [s for (ts, s) in packet_buffer if current - ts <= WINDOW_SECONDS] if len(window) < 20: return None # 聚合窗口内流特征,字段顺序和训练时的 feature_cols 保持一致 sample = aggregate_stats(window) proba = clf.predict_proba([sample])[0, 1] score_history.append(proba) if len(score_history) < VOTE_SIZE: return None # 连续 VOTE_SIZE 个时间片中,超过一半达到阈值才报攻击 vote = sum(1 for p in score_history if p >= P_THRESHOLD) return "DDoS" if vote >= VOTE_SIZE // 2 + 1 else "normal"这段逻辑里的两个关键点是:score_history 里的概率不做平均而是做硬投票,目的是容忍单次随机波动;P_THRESHOLD 取 0.8 是让告警更钝,宁可晚报五秒,也不要持续抖动式告警。aggregate_stats 要做的事和离线特征提取完全一致,平均值、最大值、计数都要保持相同字段顺序,否则 predict_proba 的输入维度对不上。窗口内样本数少于 20 就不预测,避免空闲时段的极端峰值进入判断。实时检测里窗口切片替代了五元组结束符,这是和离线模型最大的差别。
4.3 误报闭环与模型更新
实时检测上线后会面临一个现实问题:模型根据离线数据训练,线上的正常流量分布和训练集不会完全一样。误报闭环的意思是,把每次告警日志和对应窗口特征一起存档,运营人员核查后打标,每隔一个时间周期回填训练集。常见做法是保留最近两周的标注数据,每天重新训练一次随机森林,而不是用复杂的在线学习框架。模型更新前要做回归验证:把旧模型和新模型在同一份最近一周流量上比一遍,如果新模型召回率提升不足 0.5% 但误报率上升,就延迟上线。
5. 排错与边界:数据泄漏、类别不平衡、模型退化
5.1 特征泄漏的常见写法与判别方法
入侵检测项目里最容易翻车的是数据泄漏。很多人先对整个数据集做归一化、缺失值填补或特征选择,再切训练和测试集,这会让模型提前看到测试集信息。数据处理必须放在切分之后,并且只在训练集上 fit。标准化就是一个典型例子。
# 错误示范:测试集信息泄露到训练阶段 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2) # 正确做法:fit 只发生在训练集上,测试集仅 transform X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test)随机森林不依赖归一化,但特征选择、填补缺失值同样存在泄漏风险。判别方法是看某个特征是不是拿走了未来信息:比如用会话结束后才知道的总字节数去预测正在进行的流,就是典型的前视偏差。排查时把训练集和测试集合并做一次时间排序,看模型在中间时段的表现是否明显好于两端,如果是,几乎可以判断存在泄漏。
5.2 类别不平衡时怎么保护 R2L/U2R 小类
正负样本比到 10:1 以上时,分类器会把多数类学得很好,少数类几乎全漏。DDoS 检测里小类攻击的价值恰恰体现在漏报上,R2L 和 U2R 本身样本少,训练后召回率经常是 0。常用的有三招:第一招给模型加 class_weight,代价小见效快;第二招欠采样正常流量,把比例拉到 1:3 上下,适合数据量大时用;第三招用 SMOTE 生成少数类样本,但必须先做训练集内合成,否则同样泄漏。我一般先试 class_weight,效果不够再欠采样,最后才考虑 SMOTE,因为合成分布和真实攻击差距可能很大,也难和运维解释样本来源。
5.3 模型退化时看什么指标
模型上线后不是一劳永逸。DDoS 工具的变种会让流量特征平移,正常业务的大促也会让窗口均值突跳。要盯两个指标:预测概率分布漂移和攻击判定率。概率分布漂移可以用 PSI 计算,PSI 超过 0.2 说明特征整体偏移,通常需要重新采样训练。攻击判定率突然飙升时,先别急着调阈值,去看误报样本落在哪些特征区间,最常见的是把 P2P 大流量或跨机房同步误判成 DDoS。退化检测的另一个信号来自重放验证,也就是最后一章要做的事。
6. 用 pcap 重放验证检测率
6.1 搭建最小重放验证环境
模型训练完成后,真实线上流量没法直接做对照实验,比较常用的验证方式是流量重放。把抓包得到的正常流量和攻击流量分别保存成 pcap 文件,用 tcpreplay 设好速率发到镜像口,检测模块只读流量不动业务。最小环境只需要两台机器:一台抓过正常和攻击流量样本,一台跑检测程序。
# 先放正常流量,观察误报基线 tcpreplay --intf1=eth0 --pps=1000 normal_traffic.pcap # 再放攻击流量,观察检出率 tcpreplay --intf1=eth0 --pps=2000 attack_traffic.pcap我一般会先跑一遍纯正常流量,记录误报次数,再跑正常加攻击的混合流量。两种场景都建议跑三遍以上,因为正常流量里的随机因素会影响窗口聚合结果。检测模块日志要带上时间戳、窗口内样本数、概率值,方便和 pcap 的时间对齐。如果攻击流量在混合 pcap 里占比只有 5%,报警时间点和攻击开始时间做差,才能算出真正的检测时延。
6.2 从验证结果反推阈值与特征
重放后的日志会生成一张很直接的表格,按不同概率阈值统计检出率和误报率。
| 概率阈值 | 检出攻击流数 | 误报流数 | 检测时延 |
|---|---|---|---|
| 0.90 | 183 | 2 | 8s |
| 0.80 | 187 | 7 | 5s |
| 0.60 | 188 | 23 | 3s |
这张表能看出阈值从 0.8 降到 0.6,检出率只多一条,误报却翻了三倍,那生产环境就用 0.8。反过来,如果 0.9 到 0.8 检出率提升明显,说明漏掉的攻击概率集中在 0.8 附近,这时应该检查这批样本的特征,大概率是某个 Flag 计数的均值偏低导致分类器信心不足。回到特征工程里补一列 SYN/ACK 比或包长方差,通常比继续调参更有效。每次重放验证都留一份日志,积累三到五次之后再决定要不要更新模型,避免被单次流量波动带偏。
本文还有配套的精品资源,点击获取