ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

KDD99数据集上实现网络入侵检测的CNN实战与避坑指南

KDD99数据集上实现网络入侵检测的CNN实战与避坑指南 简介一份基于Python与卷积神经网络CNN的网络入侵检测项目源码旨在利用深度学习从网络流量数据中识别异常行为适合网络安全学习者、算法工程师及高校相关专业学生参考。压缩包共16个文件包括多个Python脚本、KDD99数据集压缩包、配置文件与说明文档等整体大小约17.45MB目录划分清晰。已有116人浏览学习代码结构完整覆盖了数据预处理、CNN模型构建、训练调优与效果评估等关键环节并配有训练日志文件。通过动手实践读者可掌握网络流量数据的清洗与特征处理方法理解卷积层、池化层在序列数据上的应用并学会用TensorFlow/Keras搭建入侵检测模型同时还能根据自身需求调整模型参数拓展到其他网络攻击类型的分类实验。1. 网络入侵检测的 CNN 实战这份 KDD99 源码能帮你少走哪些弯路打开“pythonCNN的网络入侵检测算法源码.zip”之前我先扫了一遍文件清单kddcup.data_10_percent.gz 是 KDD99 的 10% 数据集main.py、cnn_main.py、mian_cnn.py 三个入口脚本并存.idea 和一堆 iml/workspace 是 PyCharm 工程残留还有写着 1482980284 时间戳的 tfevents 日志——这是真正跑过、留过训练痕迹的项目不是网上那种只有 README 的空壳。它解决的是“如何用一维 CNN 对网络连接记录做五分类正常 四类攻击”核心价值不在模型多深而在数据预处理、训练参数设置和踩坑记录。适合想把深度学习落到网络安全流量检测上的开发者也适合拿 CNN 做序列分类入门的人。2. 数据预处理全流程KDD99 的 41 维特征如何变成 CNN 输入张量2.1 读懂 KDD99 的 41 个特征和 4 类攻击标签KDD Cup 1999 虽然发布二十多年了但直到现在依然是入侵检测论文里最常出现的基准数据集。原因很简单它每条样本是明确的特征向量不涉及原始报文解析拿来验证“深度学习模型能不能区分正常流量和攻击流量”非常顺手。这个压缩包里同时给了 kddcup.data.gz完整数据集和 kddcup.data_10_percent.gz10% 抽样版完整版接近 490 万条记录10% 版约 49 万条日常复现跑 10% 的就够用。每条连接记录的结构是 41 个特征 1 个类别标签。41 个特征按语义可以分成四组下面这张表是分组情况方便后面写预处理脚本时对照列名特征分组代表特征类型在模型里的处理方式TCP 连接基本特征duration、protocol_type、service、src_bytes、dst_bytes符号型数值型service 要编码数值要归一化内容特征hot、failed_logins、logged_in、num_compromised数值型归一化后直接进网络流量特征count、srv_count、same_srv_rate、dst_host_count数值型描述两秒窗口内的连接行为主机流量特征dst_host_same_src_port_rate、dst_host_srv_diff_host_rate数值型攻击行为高频出现在这里标签部分原始文件里是 22 种攻击名加 normal常见做法是粗分为四大类DoS拒绝服务、Probing探测、R2L远程到本地、U2R用户到根再加上正常类就是五分类。源码里的 cnn_main.py 大概率就是按这个思路做的标签映射。这里要说清楚为什么 CNN 能处理这类数据把一条记录的 41 个特征看成沿一个方向排列的序列卷积核在这个序列上滑动提取局部模式本质上和一维卷积处理时间序列是同一套机制。三个符号特征protocol_type、service、flag如果全部 one-hot特征维度会从 41 涨到 122这正好是很多 KDD99 论文里取到的输入维度。2.2 数值化、归一化与标签编码把原始记录变成张量handle2.py 就是做数据预处理这一步的脚本。常见的处理流程是读 CSV → 列名映射 → 符号特征编码 → 数值特征归一化 → 标签映射。我给一份按这个思路写的最小可跑版本逻辑和源码里 handle2.py 做的事一致import pandas as pd from sklearn.preprocessing import LabelEncoder, StandardScaler COLUMNS [duration, protocol_type, service, flag, src_bytes, dst_bytes, land, wrong_fragment, urgent, hot, # ... 中间省略共 41 个特征列 dst_host_srv_serror_rate] LABEL_COL label def load_kdd99(path): # gzip 压缩的数据文件直接读不需要先解压到磁盘 df pd.read_csv(path, headerNone, namesCOLUMNS [LABEL_COL]) return df def encode_symbolic(df): # 三个符号字段protocol_type、service、flag df[protocol_type] LabelEncoder().fit_transform(df[protocol_type]) df[service] LabelEncoder().fit_transform(df[service]) df[flag] LabelEncoder().fit_transform(df[flag]) return df def normalize(df, cols): scaler StandardScaler() df[cols] scaler.fit_transform(df[cols]) return df, scaler代码逻辑分三段load_kdd99 用 pandas 直接读 gzip 压缩文件避免手动解压再读占用磁盘空间encode_symbolic 把三个符号字段用 LabelEncoder 映射成整数因为卷积网络只能吃数值normalize 对连续特征做零均值单位方差标准化。需要注意 StandardScaler 的 fit 只能在训练集上做一次测试集只调 transform原因在下一节详细讲。如果想让模型更稳protocol_type 和 flag 建议用 one-hot 而不是 LabelEncoder因为这两个字段类别数量少3 和 11one-hot 后维度增加有限service 有 70 种做 one-hot 会让稀疏维度太多我一般的处理是 LabelEncoder 后直接当数值用或者干脆降维成常见服务/其他服务二分类。KDD99 里 src_bytes 和 dst_bytes 的分布极度偏斜有些连接是 0 字节对几十万字节标准化后仍然会有一部分样本数值很大训练时表现为 loss 在个别 batch 上突然跳动这个现象后面避坑章节还会提到。2.3 切分与归一化的先后顺序别把测试集污染了10% 数据集虽然比全量小一个量级但类别不平衡程度没有变DoS 类占了绝大部分R2L 和 U2R 的样本只有几百条甚至几十条U2R 在 10% 版本里大约就是几十条。如果直接随机切分测试集里 U2R 可能只分到几条甚至一条评估结果完全不可信。标准做法是分层采样切分同时把归一化的 fit 严格限定在训练集上from sklearn.model_selection import train_test_split # X 是预处理后的特征矩阵y 是 0~4 的整数标签 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy) # 只在训练集上 fit scaler测试集只 transform scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)stratifyy 是关键参数它保证切分后每类样本在训练集和测试集中的占比一致。random_state 固定为 42 是为了复现实验结果不要随意改。这里有一个常见的血泪教训有人先对整个数据集 fit StandardScaler 再切分验证集得分虚高到 99%一到真实场景就原形毕露——因为测试集的统计量已经通过标准化偷看到了。切分完之后再把 X_train 用 reshape 加一个通道维度比如 X_train.reshape(-1, 122, 1)就能直接送到 CNN 里。3. 模型构建与训练参数从一维卷积到五分类输出的完整配置3.1 CNN 处理流量特征的两种输入形式Conv1D 还是 Conv2DCNN 这个词大家第一反应都是图像识别里的二维卷积但网络流量特征本质是长度固定的特征序列用 Conv1D 更自然。每个样本是 122 维向量reshape 成 (122, 1) 喂给一维卷积层卷积核沿着特征维度滑动感受野覆盖相邻特征的组合。另一种做法是把 122 维 reshape 成 11×11 之类的二维矩阵用 Conv2D 去学特征之间的二维相关结构。这个做法在部分论文里出现过效果未必比 Conv1D 好但可以复用图像领域的预训练思路。两者本质区别在于卷积核移动方向Conv1D 只沿特征轴移动Conv2D 同时沿两个轴移动。KDD99 特征之间没有天然的空间相邻关系我个人更推荐 Conv1D参数少、训练快源码里 cnn_main.py 的写法一般也是 Conv1D 为主。如果你在 main.py 里看到 Conv2D 版本说明作者在做对照实验两种都可以跑通但 Conv1D 更适合这类表格型特征。3.2 用 Keras 搭一个能跑的入侵检测 CNN结构与参数逐行拆这份源码是 TensorFlow 1.x 时代的东西从 tfevents 日志的 1482980284 时间戳能看出来大约是 2016 年底的产物。现在复现直接用 TensorFlow 2.x 的 Keras API 重写更省事结构保持一致即可from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout model Sequential([ Conv1D(filters64, kernel_size3, paddingsame, activationrelu, input_shape(122, 1)), MaxPooling1D(pool_size2), Conv1D(filters32, kernel_size3, paddingsame, activationrelu), MaxPooling1D(pool_size2), Flatten(), Dense(64, activationrelu), Dropout(0.5), Dense(5, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) model.summary()几个参数说清楚input_shape 是 (122, 1)122 是三个符号字段 one-hot 后的特征维度最后的 1 是通道数如果你的预处理直接用 LabelEncoder 保留了 41 维就把这里改成 (41, 1)。filters 从 64 降到 32是让浅层学细节、深层学抽象这是 CNN 的通用设计习惯kernel_size3 是一维卷积最常用的窗口大小太小感受野不足、太大容易过拟合paddingsame 保证卷积前后特征长度不变配合 MaxPooling 每层减半。Dropout 放在全连接层之前对抗小数据集上的过拟合。loss 用 sparse_categorical_crossentropy 是因为标签直接是整数 0~4如果标签做了 one-hot 就要换成 categorical_crossentropy两者对不上会直接报 shape 错误。3.3 训练参数与类别不平衡batch_size、epochs、class_weight 怎么配KDD99 的类别不平衡是训练环节最大的坑不管模型结构多好不理清楚这个就没法收敛到有用状态。训练配置我一般这样写from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint from sklearn.utils.class_weight import compute_class_weight import numpy as np class_weight compute_class_weight( balanced, classesnp.unique(y_train), yy_train) class_weight dict(enumerate(class_weight)) callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ModelCheckpoint(ids_cnn.h5, monitorval_loss, save_best_onlyTrue) ] history model.fit( X_train, y_train, validation_split0.1, batch_size128, epochs30, class_weightclass_weight, callbackscallbacks, verbose1 )compute_class_weight 会自动算每个类别的权重让模型在每轮迭代中给 U2R、R2L 这些少数类更高的惩罚否则模型会把所有样本都预测成 normal 或 DoS整体准确率照样很高但没有任何检测价值。batch_size 选 128 是因为 49 万条训练样本不算小选太大 loss 曲线噪声大选太小训练慢epochs 设 30 配合 EarlyStopping patience5val_loss 连续 5 轮不降就停实际一般在 10 轮左右就会触发。ModelCheckpoint 只保存在验证集上表现最好的权重避免最后几轮过拟合把最优模型覆盖掉。训练日志里出现的 events.out.tfevents.1482980284.zjx-24000635 说明作者当时用了 TensorBoard 记录训练过程multi_logs 目录是用来区分多组实验的。现在重跑的时候在 callbacks 里加一个 TensorBoard(log_dirlogs) 就能复现同样的效果想对比多次实验就把 log_dir 换成带时间戳的目录。4. 避坑排查训练翻车的五处重灾区与解决办法4.1 数据泄露归一化顺序错了99% 的准确率是假的现象验证集准确率冲到 99%但模型拿到新数据预测时一塌糊涂。原因把整份数据先做 StandardScaler().fit_transform(X)再 train_test_split。fit 过程中拿到了全量数据的均值和方差测试集的分布信息提前混进了训练统计量这叫数据泄露。这是表格型特征项目里最容易翻车的地方。解决严格先切分再归一化scaler 只在训练集上 fit测试集只用 transform。同样的问题也适用于任何需要看全局统计量的预处理比如缺失值填充、PCA 降维。判断有没有泄露的标准很简单所有在训练阶段碰过测试集的算子都要单独保存并在预测流程里重放。4.2 类别不平衡准确率高U2R 一条都抓不到现象classification_report 里 normal 和 DoS 的 F1 很高U2R 和 R2L 的 precision、recall 是 0模型等于对这两类攻击完全失明。原因10% 数据集中 U2R 只有几十条R2L 也只有几百条模型把多数类学好了但少数类几乎没有梯度信号。准确率在这种分布下是个会骗人的指标——全预测成 normal 也已经赢过一半样本了。解决用上一节写的 class_weightbalanced如果还不够就做过采样比如对少数类样本做 SMOTE 或者复制若干份。评估时别只看 accuracy重点看 macro F1 和 U2R/R2L 的单独召回率。如果业务上只需要区分“异常”和“正常”也可以先把四类攻击合并成 anomaly 做二分类少数类样本的梯度压力会小很多。4.3 三个入口脚本并存mian_cnn.py 是个拼写错误现象压缩包里 main.py、cnn_main.py、mian_cnn.py 三个都能作为入口不知道该跑哪个跑了其中一个还可能报错。原因作者在开发过程中留了多个版本mian_cnn 是 main 的拼写笔误README.md.bak 说明 README 也改过一版。.idea 目录和 ids-kdd99.iml、workspace.xml、deployment.xml 都是 PyCharm 配的工程文件和算法本身没关系。解决先对比 README.md 和 README.md.bak 的差异确认哪个是最终版。我按文件名的语义推测 main.py 是完整流程脚本、cnn_main.py 是模型脚本mian_cnn.py 直接忽略。工程残留文件不用删但不要被它们干扰判断。复现老项目时第一件事永远是理清入口而不是急着跑代码。4.4 老代码在 TensorFlow 2.x 下直接报错现象run 起来报 AttributeError: module tensorflow has no attribute Session或者 placeholder 找不到、tf.nn 里的函数签名对不上。原因源码写于 TF 1.x 时代tf.Session()、tf.placeholder、tf.nn 这些 API 在 TF 2.x 里要么移除要么要开 compat.v1 模式。老项目踩的坑几乎都集中在 API 迁移上不是模型本身的问题。解决我复现时直接用 Keras 高层 API 重写模型部分数据和调用逻辑不变。如果一定要跑原版可以在文件开头加 tf.compat.v1.disable_eager_execution() 并手动 import tensorflow.compat.v1 as tf但老代码跑通之后的维护成本很高不如重写。重写时对照原模型的层顺序和参数效果基本能对齐。4.5 loss 曲线震荡、不收敛先查这三处现象训练几轮后 loss 不降反升或者每轮之间跳得厉害看起来像训练过程在玄学撞大运。原因常见原因有三个——src_bytes 和 dst_bytes 这类长尾数值特征没压住个别样本的 loss 特别大学习率太高导致在最优解附近震荡类别权重没传对导致梯度方向不稳定。解决第一数值特征归一化后画个直方图确认没有极端离群值必要时对 src_bytes、dst_bytes 做 np.log1p 变换把长尾收进来。第二把 Adam 的默认学习率 0.001 先用住不要一上来就调大。第三检查 model.fit 里 class_weight 是否真的传进去了打印 model.optimizer.get_config() 确认学习率没有被意外覆盖。这三处查完loss 曲线基本能恢复正常的下降形态。5. 评估与落地从混淆矩阵到单条流量的实时预测闭环5.1 用混淆矩阵和 F1 判断模型是否真的能抓攻击训练结束不等于模型可用。在入侵检测场景里准确率是个会骗人的指标因为 normal 类占比太高全预测成 normal 也有超过一半的准确率。正确做法是输出每个类别的精确率、召回率和 F1from sklearn.metrics import classification_report, confusion_matrix import numpy as np # y_test 是整数标签y_pred 需要从 one-hot 概率取 argmax y_pred model.predict(X_test) y_pred np.argmax(y_pred, axis1) print(classification_report(y_test, y_pred, target_names[normal, DoS, Probing, R2L, U2R])) print(confusion_matrix(y_test, y_pred))看报告时先盯 U2R 和 R2L 两行的 recall这两类是绝大多数模型的短板再看 Probing 和 DoS这两个类如果 F1 低于 0.95说明特征提取还不够。模型能抓攻击的定义不是整体准确率多高而是少数类攻击不要被全漏过去。5.2 导出模型并在单条新流量上做预测训练完成后把模型和预处理器一起持久化形成一个输入原始连接记录就输出攻击类型的闭环from tensorflow.keras.models import load_model import joblib # 保存阶段 model.save(ids_cnn.h5) joblib.dump(scaler, scaler.pkl) joblib.dump(encoders, encoders.pkl) # 上线预测阶段 model load_model(ids_cnn.h5) scaler joblib.load(scaler.pkl) encoders joblib.load(encoders.pkl) # 新样本走与训练时完全相同的预处理再 reshape 成 (1, 122, 1) prob model.predict(new_sample.reshape(1, 122, 1)) attack_type label_names[np.argmax(prob)]这里最容易漏的是把 scaler 和 encoders 一起保存现场部署时如果只带模型不带标准化参数输入分布对不上预测结果全是乱的。从那以后我每次拿到老项目源码都强制自己先跑通数据流程再动模型结构这次拆包也一样——先把 handle2.py 的预处理复现出来再在 main.py 上验证模型最后才改 cnn_main.py 里的结构做对比实验。希望帮到你。本文还有配套的精品资源点击获取
返回列表