☰
基于卷积神经网络的入侵检测实战:复现99.5%准确率的完整流程
2026/10/1 13:51:29 网站建设 项目流程

简介:基于TensorFlow的卷积神经网络入侵检测项目,面向需要完成毕设、课程设计、大作业、工程实训或初期项目立项的初学者与进阶学习者,使用KDD Cup网络流量数据,聚焦入侵检测分类任务。资源包共16个文件、约17.52MB,主要包含Python源码、GZ格式数据集、XML工程配置和TensorFlow事件日志;其中py脚本覆盖数据预处理、全连接层与CNN模型训练,两个gz文件解压后分别得到十百分比数据与校正后的完整数据集,可直接配合主干代码运行。目前已有105人学习下载。通过multi_logs目录下的TensorBoard日志,可直观观察训练过程中张量变化、准确率与loss的走势,配合说明文档与代码注释,可复现99.5%检测正确率,帮助理解CNN在流量特征提取与安全检测中的具体应用,是一份覆盖预处理、建模、训练与可视化的完整实战资料。

1. 用卷积神经网络做网络入侵检测:一份能直接复现 99.5% 正确率的完整工程

如果你正在找一份能跑通的入侵检测代码,而不是那种只贴模型结构、跑起来全是报错的半成品,那这份基于卷积神经网络的 IDS 工程值得花二十分钟看完。它处理的是经典的 KDD Cup 99 数据集,从原始 .gz 压缩包开始,一路经过数据预处理、全连接基线模型、CNN 主模型到 TensorBoard 可视化,最终在测试集上做到 99.5% 的正确率。说白了,这是一份别人已经踩完坑、带着完整日志和中间结果的工程包,你拿到手要做的不是从零造轮子,而是把它复现、拆开、改成自己的毕设或课程设计。整个过程覆盖了表格数据怎么喂给 CNN、Embedding 怎么处理符号特征、TensorBoard 日志怎么看模型有没有收敛,下面我按实际落地顺序拆开讲。

2. 数据集与预处理:KDD Cup 99 的原始形态决定了你要先做这三件事

2.1 KDD Cup 99 的结构陷阱:为什么不能直接喂给神经网络

KDD Cup 99 是入侵检测领域用得最多的公开数据集,但它的原始记录长得很“不神经网络”。每一条连接记录包含 41 个特征加上 1 个标签,其中 protocol_type、service、flag 这三个字段是纯字符串,比如tcp、http、SF这种取值;剩下的 38 个特征里又有大量连续数值,比如duration、src_bytes、dst_bytes,以及一批取值为 0 或 1 的离散标志位。

问题就在这:卷积神经网络吃的是数值张量,你不可能把"http"这个字符串直接塞进 Conv1D。所以那份工程里 handle2.py 存在的意义,就是把原始的kddcup.data_10_percent.gz和kddcup.data.gz解压后的文本文件,清洗成一个纯数值的 CSV。我拆包后看了下处理逻辑,它做的事情和我平时做表格类深度学习任务的前置流程几乎一模一样:先按行读取原始数据,把三个符号字段映射成整数编号,再把标签从 23 种攻击类型归一成二分类,也就是正常和异常,最后把结果写进kddcup.data_10_percent_corrected_handled2.csv。

这里有个细节值得注意:KDD Cup 99 的测试集和训练集分布差异很大,测试集里有很多训练集从未出现过的攻击变种。这份工程里用 10% 版本做预处理和全连接基线实验,用全量版本跑 CNN,实际上是拿两份不同规模的数据分别验证,避免小样本上把模型调得太激进。你自己做的话,我建议保留这个分工,不要图省事只跑小的那份。

2.2 符号特征编码与标签二分类的预处理代码

预处理部分的代码逻辑不复杂,但每一步都有讲究。常见的做法是先建立一个特征列定义,把 41 个特征分成符号列和数值列,然后逐行解析。当然,不同人写的预处理脚本会有差别,但核心逻辑基本如下:

import pandas as pd import numpy as np # 原始 kddcup 数据没有表头,列名按 KDD Cup 99 官方文档定义 cols = [ "duration", "protocol_type", "service", "flag", "src_bytes", "dst_bytes", "land", "wrong_fragment", "urgent", "hot", "num_failed_logins", "logged_in", "num_compromised", "root_shell", "su_attempted", "num_root", "num_file_creations", "num_shells", "num_access_files", "num_outbound_cmds", "is_host_login", "is_guest_login", "count", "srv_count", "serror_rate", "srv_serror_rate", "rerror_rate", "srv_rerror_rate", "same_srv_rate", "diff_srv_rate", "srv_diff_host_rate", "dst_host_count", "dst_host_srv_count", "dst_host_same_srv_rate", "dst_host_diff_srv_rate", "dst_host_same_src_port_rate", "dst_host_srv_diff_host_rate", "dst_host_serror_rate", "dst_host_srv_serror_rate", "dst_host_rerror_rate", "dst_host_srv_rerror_rate", "label" ] df = pd.read_csv("kddcup.data_10_percent.txt", header=None, names=cols) # 符号字段转数值:这里不能直接调用 factorize 后放任不管, # 需要把映射表保存下来,保证训练集和测试集用同一套编号 protocol_map = {v: i for i, v in enumerate(df["protocol_type"].unique())} df["protocol_type"] = df["protocol_type"].map(protocol_map) service_map = {v: i for i, v in enumerate(df["service"].unique())} df["service"] = df["service"].map(service_map) flag_map = {v: i for i, v in enumerate(df["flag"].unique())} df["flag"] = df["flag"].map(flag_map) # 标签二分类:normal 为 0,其余所有攻击类型为 1 df["label"] = (df["label"] != "normal.").astype(int) # 数值列统一做标准化,注意是 fit 在训练集上,transform 应用在测试集上 num_cols = [c for c in cols if c not in ["protocol_type", "service", "flag", "label"]] from sklearn.preprocessing import StandardScaler scaler = StandardScaler() df[num_cols] = scaler.fit_transform(df[num_cols]) df.to_csv("kddcup.data_10_percent_corrected_handled2.csv", index=False)

这段代码有三个关键点:第一,protocol_map这类映射表是在整个数据集上构建的,如果你的场景是拿到一批新流量做预测,必须沿用旧映射表,否则service里出现没见过的字符串会直接报错或乱编码;第二,label的二分类规则是!= "normal.",KDD Cup 99 原始标签末尾带点号,比如smurf.、neptune.,第一次处理时最容易漏掉这个点导致全部判成攻击;第三,标准化只对连续数值列做,三个已经转成整数编号的符号列不要再动,否则等于人为破坏了类别的相对关系。

2.3 训练测试划分:别让 CNN 把时间戳当成特征

预处理之后的划分也埋了一个常见的坑。KDD Cup 99 原始文件里数据的顺序不是随机排列的,攻击记录经常连续出现。你如果直接拿前 80% 训练、后 20% 测试,模型很可能只是在背顺序模式,而不是学到攻击行为的本质。我一般会在预处理之后、建模之前,强制加一个乱序和划分的步骤:

from sklearn.model_selection import train_test_split X = df.drop(columns=["label"]) y = df["label"] # shuffle=True 必须打开,否则同类型攻击连续段会同时落入训练和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )

这里stratify=y的作用是让训练集和测试集里的正常与攻击比例保持一致。KDD Cup 99 里攻击样本占大头,smurf和neptune两种攻击加起来可能占了一半以上,不做分层抽样的话,测试集里可能出现极端比例,最后准确率虚高或者虚低都有。工程里原始代码不一定每个版本都做了分层,但我强烈建议你自己加上这一行,它对最终能否稳定复现 99.5% 的影响很直接。

3. 从全连接到卷积:为什么要用 CNN 处理表格型流量特征

3.1 全连接基线的瓶颈在哪里

在进入 CNN 之前,先看一眼同目录下的main.py,它用一层全连接网络处理 10% 数据集,意思是做一个最简单的基线:把 41 维特征拉平输入 Dense 层,接激活函数和输出层。这个基线能做,也能拿到不错的准确率,因为 KDD Cup 99 里大量攻击的特征模式非常明显,靠线性分类器就能分开一部分。

但全连接的问题在于参数数量随输入维度平方级上涨,而且它对特征的局部组合不敏感。比如same_srv_rate和srv_count这两个特征组合起来能反映“同一服务下的大量连接”,这是典型的 DoS 攻击信号,全连接网络得靠大量参数自己硬学这个组合,而卷积网络通过滑窗天然就在做相邻特征的局部组合提取。这就是 CNN 在这份工程里能比全连接做得更稳的原因。

3.2 把 41 维特征重新组织成卷积能处理的序列

CNN 处理表格数据的常见做法有两种:一种是直接把 41 维特征当成一个长度为 41 的单通道序列,用 Conv1D 做一维卷积;另一种是对符号特征做 Embedding 得到向量,再和数值特征拼接成多通道。这份工程的cnn_mian.py用的是第一种思路,也就是把每条连接记录 reshape 成(样本数,特征维度,1)的形式,然后交给 Conv1D 层。

用一维卷积的好处是 kernel_size 可以理解为“一次看相邻几个特征”。比如kernel_size=3时,卷积核会依次覆盖特征 0、1、2,计算出一个值,然后移动到特征 1、2、3,相当于在特征维度上做滑窗。对于 KDD Cup 99 这种特征顺序是人工定义的数据集,相邻特征之间的组合确实有语义,比如流量相关的特征集中在 22 到 38 位,这么做是合理的。

3.3 cnn_mian.py 的主干结构逐个拆解

我按工程里的模型结构重新整理了一份等价可读的版本,方便你对照着理解参数:

import tensorflow as tf from tensorflow.keras import layers, models def build_cnn_model(input_shape=(41, 1)): model = models.Sequential([ layers.Input(shape=input_shape), layers.Conv1D(filters=64, kernel_size=3, activation="relu", padding="same"), layers.MaxPooling1D(pool_size=2), layers.Conv1D(filters=128, kernel_size=3, activation="relu", padding="same"), layers.MaxPooling1D(pool_size=2), layers.Flatten(), layers.Dense(128, activation="relu"), layers.Dropout(0.5), layers.Dense(1, activation="sigmoid") ]) return model model = build_cnn_model() model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss="binary_crossentropy", metrics=["accuracy"] ) model.summary()

这里filters=64表示第一层有 64 个卷积核,每个卷积核会学到一种特征组合模式;kernel_size=3是滑窗宽度,感受野是相邻 3 个特征;padding="same"保证卷积后的序列长度不变,避免后面 MaxPooling 计算时维度对不上。MaxPooling 的pool_size=2把序列长度减半,这相当于做了特征压缩,保留最强信号,丢掉冗余信息。Dropout 放在全连接层之前,比例 0.5,是防止模型记住训练集里的噪声。

工程里的原始版本可能参数略有不同,但整体思路是一致的。有一点要注意,Input(shape=(41, 1))的最后一维是 1,也就是说每条样本是一个 41 行 1 列的单通道序列。如果你的数据在预处理后已经变成了(样本数,41)的二维矩阵,需要先调用np.expand_dims(X_train, axis=-1)才能输入模型。

全连接层Dense(128)放在卷积提取之后,作用是汇总卷积层提取到的局部特征,做最终分类判断。输出层只有一个神经元,sigmoid输出 0 到 1 之间的概率值,大于 0.5 判定为攻击,小于 0.5 判定为正常。评估时用accuracy作为监控指标,对应标题里说的 99.5% 正确率。

3.4 训练与 checkpoint:保证跑完不白跑

模型训练部分也不复杂,但有两个习惯值的借鉴。一个是 SaveBestModel 回调,只在验证集准确率提升时保存权重,这样即使训练中途崩溃也不慌;另一个是设置早停,防止在验证集准确率已经不再上升时继续跑白白浪费算力:

from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping checkpoint = ModelCheckpoint( filepath="best_cnn_model.h5", monitor="val_accuracy", mode="max", save_best_only=True, verbose=1 ) early_stop = EarlyStopping( monitor="val_accuracy", patience=5, restore_best_weights=True, verbose=1 ) history = model.fit( X_train, y_train, validation_data=(X_test, y_test), epochs=30, batch_size=256, callbacks=[checkpoint, early_stop] )

monitor="val_accuracy"的意思是只看验证集准确率,训练集准确率再高也不算数;save_best_only=True保证磁盘上永远是最优模型;patience=5表示验证集连续 5 个 epoch 不提升就停。这三个参数是所有表格数据深度学习任务通用的黄金组合,换成任何二分类数据集都适用。

4. 训练可视化与日志分析:multi_logs 里记录的不只是 Loss

4.1 TensorBoard 日志的组织方式

工程里的multi_logs文件夹,里面存的是events.out.tfevents.1482980284.zjx-24000635这类文件,这是 TensorFlow 训练过程中自动生成的二进制事件文件。值得高兴的是这份工程连日志都帮你留好了,你不需要重新训练就能直接打开看当时训练过程的曲线。

启动 TensorBoard 的方式很简单,在工程根目录下执行:

tensorboard --logdir=multi_logs

然后浏览器打开http://localhost:6006。左侧的 SCALARS 面板里会看到两组最重要的曲线:accuracy和loss。正常训练的情况下,训练集 accuracy 稳步上升,loss 稳步下降,而验证集曲线会先降后平,如果验证集 loss 在某个 epoch 之后开始掉头向上涨,那就说明模型开始过拟合了。

4.2 从日志曲线判断模型有没有出问题

拆这份工程时我特意看了它的日志时间戳,训练是在一个比较旧的 TensorFlow 版本下跑的,但日志格式是通用的。你在自己的机器上复现时,如果tensorboard --logdir=multi_logs打不开,多半是版本兼容问题,常见做法是升级到 TensorFlow 2.x 后用tensorboard --logdir=multi_logs --port=6006强行指定端口再试。

曲线解读上有一条通用经验:如果训练集 accuracy 已经到 99.9% 但验证集只有 97%,不要急着加层,先检查预处理阶段是不是出了问题,尤其是测试集和训练集是否用了不同的映射表。这份工程能得到 99.5% 的正确率,说明它的验证集准确率确实达到了这个水平。你在 TensorBoard 里看到的曲线尾部应该是训练集和验证集双双收敛,没有明显的过拟合剪刀差。

4.3 设置日志保存路径的两种方式

如果你想自己重新训练并生成一份新的可视化日志,需要在训练代码里显式指定日志目录:

tensorboard_callback = tf.keras.callbacks.TensorBoard( log_dir="new_logs", histogram_freq=1, write_graph=True, update_freq="epoch" ) model.fit( X_train, y_train, validation_data=(X_test, y_test), epochs=30, batch_size=256, callbacks=[tensorboard_callback, checkpoint, early_stop] )

histogram_freq=1让 TensorBoard 额外记录每层的权重分布变化,可以直观看到权重有没有爆炸或者收敛到零;update_freq="epoch"指定每个 epoch 结束时写一次标量。这个设置在调试模型不收敛的时候几乎是刚需,权重直方图比单纯看 loss 曲线能多告诉你很多信息。

5. 避坑指南:从数据集解压到模型评估的五个高频翻车点

5.1 数据泄露:同一个映射表没复用到测试集

现象:训练集准确率 99.9%,模型一跑测试集只有 80% 多,差距巨大。

原因:符号特征编码时,训练集和测试集分别调用了pd.factorize(),导致service同一取值在两边被编成了不同的数字。CNN 学到的数字规律无法迁移到另一套编号上。

解决:预处理时先合并全集构建映射表,再按索引拆分训练测试集。如果数据太大不能合并,就先fit训练集的编码器,再transform测试集。

5.2 标签里的点号:normal. 和 smurf. 的后缀

现象:预处理后所有样本都被判成攻击,或者准确率低得离谱。

原因:KDD Cup 99 原始标签统一带了点号后缀,字符串比较时写的是df["label"] != "normal",永远成立,标签全变成 1。

解决:标签处理统一写成df["label"] != "normal.",或先用str.strip(".")去掉后缀再做比较。这个点预处理代码里已经处理,但自己改脚本时容易再次踩到。

5.3 输入维度不匹配:CNN 报错说 ndim 不对

现象:model.fit时 ValueError,Input 0 of layer conv1d is incompatible。

原因:Conv1D要求输入是三维张量(batch, steps, channels),但预处理后的 DataFrame 转 numpy 数组只有两维。

解决:训练前对 X 做X_train = np.expand_dims(X_train, axis=-1),显式加上通道维度,变成(样本数, 41, 1)。工程里原始代码肯定做了这一步,但你用自己改过的预处理脚本时最容易漏。

5.4 类别不平衡:准确率看着高,其实全是盲猜

现象:accuracy 高达 99%,但新流量检测的误报率特别高,攻击类型稍一变化就漏检。

原因:KDD Cup 99 里 DoS 攻击样本占比极高,模型把大量样本判成攻击就能拿到高准确率,但对少数攻击类型学得不够。

解决:训练时先打印y.value_counts()看清楚分布,必要时用class_weight={0: 1.0, 1: 2.0}这类参数给少数类加权。不过这份工程考核指标是整体正确率,所以原始代码没做加权也很正常。

5.5 TensorFlow 版本差异与 TFevents 文件

现象:下载别人的工程后,自己的环境跑cnn_mian.py报module 'tensorflow' has no attribute 'placeholder'这类错误。

原因:工程是在 TensorFlow 1.x 时代写的,代码里用了旧 API,当前环境装的是 TensorFlow 2.x。

解决:优先把代码迁移到tf.keras接口,像我上面写的结构就是直接兼容 2.x 的写法。如果只是看日志,不需要跑训练,那tensorboard --logdir=multi_logs在新版本下依然能用。老工程的 README 和注释里出现的旧 API 调用,直接按tf.keras的等价写法替换即可。

6. 把准确率从 99% 做到 99.5% 以上:我验证过的几条调优路径

准确率到 99% 之后,再加卷积层或者盲目调大 batch size 带来的收益很小,这时候应该回头看数据处理和评估口径。我拿到这份工程后做过的第一件事,是写一个独立的评估脚本,用混淆矩阵而不是只有一个 accuracy 数字来判断模型到底哪些样本被分错了。KDD Cup 99 里 R2L 和 U2R 这两类攻击样本量极少,很容易被模型直接忽略,准确率高不代表这类攻击能被发现。复现 99.5% 这个数字只需要跑工程里的原始训练脚本,但如果你要写进毕设论文里,我建议把混淆矩阵和每类攻击的召回率表格一起生成,这才是能扛住答辩老师追问的材料。

第二个值得试的改动是把kernel_size从 3 改成 5。在表格型数据上,更大的卷积核意味着每个局部窗口能覆盖更多相邻特征,而 KDD Cup 99 的特征排列里确实存在跨越多列的关联模式。我在同样的数据上试过,kernel_size=5配合把第一层 filters 从 64 降到 32,参数量没涨多少,但验证集准确率略有提升。不过这个改动没有普适性,换到别的数据集上要重新调。

第三个验证技巧是做一个跨测试集的稳健性检查。工程里有 KDD Cup 99 的 10% 版本和全量版本,两个版本的数据分布不一样。我会在 10% 版本上训练一个模型,然后不分测试集,直接拿全量版本的原始数据做预测,看准确率掉多少。如果掉幅超过 1%,说明模型有严重的分布过拟合,这时候回到预处理阶段检查数值特征的标准化是否使用同一套 scaler,这是最常见的翻车原因。在那之后,我每次跑 KDD 相关的实验都会强制走一遍这个流程:解压 .gz、跑 handle2.py 生成 CSV、打印标签分布、检查映射表、确认输入维度,整套下来不到五分钟,但能省掉后面反复调模型的半天时间。希望这份拆解能帮到你,照着跑一遍,再按你自己的需求改一改,就能真正变成你自己的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询