ARTICLE DETAIL

资讯详情

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

基于CNN的入侵检测系统实战:数据预处理与模型评估全流程

基于CNN的入侵检测系统实战:数据预处理与模型评估全流程 简介基于CNN卷积神经网络的网络入侵检测毕业设计项目面向计算机相关专业毕业生、课程设计学生及想用深度学习解决安全问题的开发者。项目围绕KDD Cup入侵检测数据集覆盖数据预处理、CNN模型构建、训练、评估与多日志记录等完整流程。压缩包共16个文件主要包含Python源码py、数据集压缩包gz、项目配置文件xml与说明文档md另有模型日志与备份文件整体大小约17.51MB目录结构较清晰便于按模块查阅。目前已有120人学习下载。源码均经过本地编译调试可在常规Python环境中稳定运行文档说明和日志输出有助于理解CNN在入侵检测中的特征提取与分类原理。该资源适合毕业设计参考、课设复现或算法实战能帮助节省数据预处理、环境配置与调参时间是一份完整可用的高分项目方案。1. 把网络流量喂给 CNN这篇入侵检测源码到底能解决什么一份标着「基于 CNN 卷积神经网络的网络入侵检测 python 源码 全部数据 文档高分毕业设计」的项目听起来像是一个包装好的交付包但真正值钱的并不是那几份.py 文件而是背后的完整落地链路原始流量特征怎么清洗、怎么把一维特征变成 CNN 能消费的输入、模型结构怎么定、评估指标为什么不能只看准确率。很多同学拿到类似源码后第一件事是跑训练跑完看 acc 很高就以为完事了一开会发现测试集换一批数据立刻崩盘或者导师问一句「为什么用 CNN 不用 XGBoost」就答不上来。这篇文章就按一份可复现的毕设项目标准把数据准备、模型构建、训练评估到排错整条线拆开讲清楚适合正在做网络入侵检测毕设的学生也适合刚接触深度学习、想拿一个落地点练手的从业者。2. 围入侵检测的数据准备NSL-KDD 与特征归一化的三条硬规矩2.1 数据集选型NSL-KDD 比 KDD99 好在哪UNSW-NB15 什么时候该换网络入侵检测方向做深度学习首先绕不开的是数据集。公开数据集里最常用的是 NSL-KDD它是 KDD99 的增强版本剔除了原版海量的重复记录训练集 KDDTrain 约 12.6 万条测试集 KDDTest 约 2.25 万条每条连接记录有 41 个特征加一个攻击标签。对毕设来说NSL-KDD 最大的优点是轻量一张普通显卡甚至纯 CPU 都能在几分钟内完成一轮训练这让调参和答辩演示的压力小很多。KDD99 的原始版本不推荐用原因很直接训练集里有大量重复记录模型会在重复样本上反复拟合导致准确率虚高这在论文里很难解释清楚。NSL-KDD 解决了这个问题但它的缺点也明显数据是 1999 年前后的模拟网络流量和现在的真实攻击形态差别很大。所以做毕设时一般这么取舍只要题目没有明确要求真实流量就用 NSL-KDD因为相关工作多、评估标准成熟、文档好写如果导师要求更有说服力,可以再看 UNSW-NB15它有 49 个特征、9 类攻击更新但样本量更大、类别更不平衡训练时间随之上升。2.2 用 pandas 清洗 KDDTrain特征列、标签映射与可复现代码拿到原始 KDDTrain.txt 时它没有表头每行是一条连接记录的 41 个特征列最后两列是攻击类型和难度等级。第一步是把列名补上、把字符串特征转成数值、把攻击标签映射成模型可训练的多分类标签。这个步骤属于「错一步后面全崩」的关键节点。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler feature_41 [ 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 ] train_df pd.read_csv(KDDTrain.txt, headerNone) test_df pd.read_csv(KDDTest.txt, headerNone) train_df.columns feature_41 [attack_type, difficulty] test_df.columns feature_41 [attack_type, difficulty] # 字符串特征编码 for col in [protocol_type, service, flag]: train_df[col] train_df[col].astype(category).cat.codes test_df[col] test_df[col].astype(category).cat.codesprotocol_type 是 TCP/UDP/ICMPservice 是 http/ftp 等 64 种网络服务flag 是连接状态标记它们都是离散值。用astype(category).cat.codes是常见做法效果等于把类别映射成 0 到 N-1 的整数。注意这里有一个容易被忽略的坑训练集和测试集的类别表可能不一样比如测试集里出现了训练集没见过的 service 值直接映射会拿到 -1。稳妥做法是在两套数据合并的类别表上统一编码或者先取并集再映射。上面这段代码在两套数据上分别调用cat.codes是为了保持行文简洁实际做项目时建议先pd.concat后再编码。2.3 归一化与切分的正确顺序一个坑直接漏掉测试集特征里的数值类型跨度很大src_bytes 可能到几百万而 logged_in 只有 0 和 1。不归一化直接进 CNN卷积核会被大数值特征支配。正确的归一化方式是把 StandardScaler 只 fit 在训练集上再分别 transform 训练集和测试集。from sklearn.preprocessing import StandardScaler def attack_to_class(attack): attack attack.lower() if attack normal: return 0 if attack in [neptune,smurf,back,teardrop,pod,land,apache2,mailbomb,processtable,udpstorm]: return 1 # DoS if attack in [satan,ipsweep,nmap,portsweep,mscan,saint]: return 2 # Probe if attack in [guess_passwd,warezmaster,warezclient,imap,ftp_write,multihop,phf,spy,snmpguess,snmpgetattack,xlock,xsnoop,named,sendmail,httptunnel]: return 3 # R2L if attack in [buffer_overflow,rootkit,loadmodule,perl,ps,sqlattack,xterm]: return 4 # U2R return 4 X_train train_df[feature_41].values.astype(float) X_test test_df[feature_41].values.astype(float) y_train train_df[attack_type].map(attack_to_class).values y_test test_df[attack_type].map(attack_to_class).values scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) # 这里绝对不能重新 fit标签映射这一层把它压成 5 分类0 是正常1 是 DoS2 是 Probe3 是 R2L4 是 U2R。NSL-KDD 自带细节是 KDDTrain 里 R2L 和 U2R 样本极少尤其是 U2R 只有一百多条这个不平衡会在第 4 节严重影响评估结果提前给出这个映射是为后面分析做铺垫。最关键的是scaler.fit_transform(X_train)和scaler.transform(X_test)的区别如果对整个合并数据集先 fit 再切分测试集的均值方差等于提前进入了模型视野测试分数会虚高。这个错误在网上的源码包里出现频率相当高拿到任何项目第一步就该检查归一化是放在切分前还是切分后。3. CNN 模型构建一维卷积和二维卷积怎么选参数怎么定3.1 为什么表格型流量数据用 CNN 而不是 RNN 或 XGBoost网络入侵检测的数据是表格形态每行是独立的连接记录没有时间先后顺序这是选型的关键前提。RNN 和 LSTM 天然适合序列建模但这里的 41 个特征并不是时间序列强行用 LSTM 等于把「相邻特征」当成「相邻时间步」反而丢失了特征间的局部组合关系。XGBoost 这类树模型在表格数据上很强但它只能做特征重要性的线性组合学不到卷积核这种「相邻局部模式」的表达。CNN 的行为恰好介于两者中间一维卷积核沿特征维度滑动每次覆盖 3 到 5 个连续特征可以组合出类似「同一时间窗口内的连接状态和服务类型共同决定攻击概率」这类局部规则。另一个角度是对抗样本和特征分布的鲁棒性。卷积网络天然对输入的小幅扰动不敏感因为池化层会把局部响应取最大或平均这对网络流量里常见的噪声特征有一定容忍度。而且把流量特征转成二维图像再做 CNN 的路径也成熟我在工程里见过直接用原始流量字节重排成灰度图跑 CNN 做恶意软件家族识别的方案思路和这里完全一致。所以对于这份毕设代码一维 CNN 是性价比最高的起点跑通之后如果还想提升再让部分特征重排成二维输入走 Conv2D。3.2 用 Keras 搭一个最少能跑的一维 CNN 模型下面这个模型结构是我在类似流量检测项目里常用的基础配置结构清晰、容易解释、不容易过拟合首次跑毕设数据时可以直接照抄。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import ( InputLayer, Conv1D, BatchNormalization, MaxPooling1D, GlobalAveragePooling1D, Dropout, Dense ) SEQ_LEN 41 # 与特征数量保持一致 NUM_CLASSES 5 def build_cnn_1d(): model Sequential([ InputLayer(input_shape(SEQ_LEN, 1)), Conv1D(filters64, kernel_size3, activationrelu, paddingsame), BatchNormalization(), MaxPooling1D(pool_size2), Conv1D(filters32, kernel_size3, activationrelu, paddingsame), BatchNormalization(), GlobalAveragePooling1D(), Dropout(0.3), Dense(64, activationrelu), Dense(NUM_CLASSES, activationsoftmax) ]) return model model build_cnn_1d() model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losscategorical_crossentropy, metrics[accuracy] ) model.summary()Conv1D 在输入维度上滑动kernel_size3表示每 3 个连续特征做一次加权组合paddingsame 保证输出长度不变。BatchNormalization 放在卷积之后能稳定训练尤其适合小数据集。MaxPooling1D 把长度从 41 压到 20减少参数量的同时保留强响应。GlobalAveragePooling1D 是关键设计它把最后一个卷积层的每个通道压缩成 1 个数值相比 Flatten 能显著减少全连接层的参数量也更抗过拟合。Dropout 放在全连接前实测里 0.3 对这个小数据集是安全起点。输入形状是(41, 1)也就是说一条样本是 41 行 1 列。如果要喂给二维卷积需要 reshape 成(7, 7, 1)那就是 3.3 节的内容。3.3 变体把特征 reshape 成 7×7 二维图像用 Conv2D 做特征图二维 CNN 在毕设里是天然的加分项因为它能把「图像识别」的故事讲得通顺。常见做法是把 41 个特征重排成一个近似的二维矩阵比如 41 拆成 7×7 需要补 8 个零填充或者直接取前 36 个特征排成 6×6。这里有个工程细节直接按原始列顺序硬排相邻的列之间没有实际的空间关系CNN 学到的所谓「局部模式」其实是列顺序的产物。更好的做法是按特征语义分组排列把协议类、流量统计类、连接状态类各自放成相邻块。import numpy as np from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten def reshape_to_image(x, h7, w7): n x.shape[0] if x.shape[1] h * w: x np.pad(x, ((0, 0), (0, h * w - x.shape[1])), modeconstant) return x[:, :h * w].reshape(n, h, w, 1) def build_cnn_2d(): model Sequential([ InputLayer(input_shape(7, 7, 1)), Conv2D(filters32, kernel_size(3, 3), activationrelu, paddingsame), BatchNormalization(), MaxPooling2D(pool_size(2, 2)), Conv2D(filters16, kernel_size(3, 3), activationrelu, paddingsame), Flatten(), Dropout(0.3), Dense(64, activationrelu), Dense(NUM_CLASSES, activationsoftmax) ]) return model X_train_img reshape_to_image(X_train, 7, 7) X_test_img reshape_to_image(X_test, 7, 7)7×7 经过一次 2×2 池化后变成 3×3第二层卷积还能继续滑动结构上是合理的。对比一维 CNN二维卷积在相同参数量下能捕捉到「行间」和「列间」的组合特征但前提是特征排布有意义。我的习惯是先跑通一维作为 baseline再做二维变体用同一个评估脚本比较两者这是毕设论文里很扎实的一个实验小节。另外补充一点两套模型的训练代码可以共用只需要把输入 reshape 的步骤按模型切换。4. 训练与评估从 model.fit 到能答辩的 per-class F14.1 训练主流程早停与模型保存别让 val_loss 飘走训练这一步最大的误区是闷头 fit 几十轮不管。需要同时配置早停和模型保存保证训练结束时留下来的是验证集上表现最好的那一版权重而不是最后一轮。下面这段是可直接嵌进项目的训练流程。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint from tensorflow.keras.utils import to_categorical X_train_1d X_train.reshape(-1, SEQ_LEN, 1) X_test_1d X_test.reshape(-1, SEQ_LEN, 1) y_train_oh to_categorical(y_train, num_classesNUM_CLASSES) y_test_oh to_categorical(y_test, num_classesNUM_CLASSES) early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) checkpoint ModelCheckpoint( best_model.h5, monitorval_loss, modemin, save_best_onlyTrue ) history model.fit( X_train_1d, y_train_oh, validation_data(X_test_1d, y_test_oh), epochs60, batch_size128, callbacks[early_stop, checkpoint] )EarlyStopping 的 patience 在这里设成 10表示连续 10 轮验证损失没有创新低就停止。restore_best_weightsTrue 是关键默认的 False 会把训练中断时的权重留给你那个权重往往已经过拟合。ModelCheckpoint 同时落盘一份 h5后续跑测试、画图、写论文都可以反复加载。batch_size128 对小数据集是常规选择显存不吃紧、梯度稳定如果训练 loss 震荡剧烈优先把 batch_size 加到 256而不是急着降学习率。epochs60 在早停存在的情况下只是一个上限实际训练多数在 20 到 40 轮之间收敛。4.2 用 sklearn 算 macro F1 和混淆矩阵准确率不算做完训练完成后模型自带的 accuracy 只能告诉你整体判断正确的比例但在入侵检测场景里这远远不够。NSL-KDD 的测试集里 Normal 和 DoS 占大头Probe 次之R2L 和 U2R 样本极少一个「无脑判 Normal」的模型也能有很高的准确率所以必须用按类别加权的评估指标。from sklearn.metrics import classification_report, confusion_matrix, f1_score import numpy as np y_pred model.predict(X_test_1d) y_pred_label np.argmax(y_pred, axis1) report classification_report( y_test, y_pred_label, target_names[Normal, DoS, Probe, R2L, U2R], digits4 ) print(report) macro_f1 f1_score(y_test, y_pred_label, averagemacro) print(macro F1:, round(macro_f1, 4))重点看averagemacro它是把每个类别的 F1 先算出来再取平均类别少不影响权重。相比 accuracymacro F1 对 R2L 和 U2R 这两个稀有类别极敏感。我在实际项目里见过不少模型 accuracy 到 99%macro F1 只有 0.6 出头说明稀有攻击类别基本没学到。打印 classification_report 后还要配合混淆矩阵看漏判方向比如 Normal 被误判成 DoS、Probe 被误判成 Normal这两类错误在安全场景里的代价完全不同。把 prerun 的准确率、macro F1、每个类别的 recall 一起写进论文结果表比只放一个 acc 有说服力得多。4.3 参数收敛技巧learning rate、batch size、dropout 的搭配表调参不是瞎试核心原则是先用小模型大学习率确认能收敛再逐步增复杂度。下面这张表是经过多次实验沉淀的搭配参考方向和区间比具体数值更重要。参数推荐起步值调整方向观察指标learning_rate1e-3降为 1e-4 防止震荡loss 曲线是否在 10 轮内明显下降batch_size128大 batch 配小学习率梯度方向是否稳定kernel_size3调到 5 看感受野变大是否过拟合validation loss 是否回升dropout0.3过拟合提高到 0.4欠拟合降到 0.2train loss 与 val loss 的间距patience10数据越少 patience 越大是否训练 15 轮左右就触发早停l2 正则1e-4不轻易超过 1e-2权重过大时 train loss 下降缓慢learning rate 是优先级最高的参数。如果训练 5 轮后 val_loss 还是 1.6 左右不动先降到 5e-4 重跑不要动网络结构。卷积核 size 从 3 调到 5 意味着每步组合 5 个连续特征在 41 维输入上感受野变宽对小数据集容易过拟合所以我的顺序是固定 dropout0.3先调卷积核再调学习率。Batch size 在毕设场景不需要卡太细显卡能放得下就用 128 或 256。把训练曲线图保存下来答辩时展示「模型在多少轮收敛、val_loss 何时不再下降、早停是否触发」比说「我调到最好结果」更有工程说服力。5. 避坑指南五条让毕业设计复现翻车的常见问题5.1 全局归一化导致测试集 F1 虚高现象把 KDDTrain 和 KDDTest 拼在一起后直接Scaler.fit_transform(合并数据)再重新切分训练测试集训练时 acc 和 macro F1 都高得离谱但换一批新数据立刻大跌。原因归一化时测试集的均值和方差已经参与了训练数据的缩放等于测试集的分布信息提前泄漏给了模型。这属于典型的数据泄漏论文里被问到「测试集为什么这么高」时会很难解释。解决严格按第 2 节的流程scaler.fit(X_train)之后只用scaler.transform(X_test)。这是流程问题不是模型问题检查顺序比调模型结构重要得多。5.2 类别不平衡识别率被 DoS 绑架R2L/U2R 几乎不可见现象训练完成后的 classification_report 里Normal 和 DoS 的 F1 都超过 0.97但 R2L 只有 0.3 左右U2R 甚至只有 0.05。整体 accuracy 却有 99%。原因NSL-KDD 训练集中 DoS 样本有几万条R2L 只有几千条U2R 不足 200 条。模型把大量参数用来拟合多数类少数类的特征被淹没在卷积池化里。解决要么在训练时给少数类更高的权重要么在评估时只看 macro F1 和每类 recall。常见做法是用sklearn.utils.class_weight.compute_class_weight(balanced)算权重然后把 class_weight 传给model.fit。但要注意加了 class_weight 后总 loss 会升高这个是正常的重点看每类 recall 是否起来。5.3 把 KDDTrain 与 KDDTest 合并再随机切泛化能力凭空消失现象训练集 acc 和测试集 acc 都非常高但把模型拿到 KDDTest 上单独评估时掉了好几个点甚至 R2L 直接归零。原因KDDTrain 和 KDDTest 来自不同的原始捕获环境攻击类型分布差异很大测试集里有训练集没见过的攻击样例。把两个文件合并后随机切分等于把两种分布混在一起再随机抽样模型在「混合分布」上做的评估失去了跨分布泛化的意义。解决按官方设定KDDTrain 只做训练KDDTest 只做测试不要合并。如果一定要合并作为全部数据请在论文里明确说明你评估的是「同分布下的分类能力」而不是跨分布的泛化能力。5.4 reshape 成图片时特征乱序卷积核学不出逻辑现象把 41 维特征 reshape 成 7×7 图像后二维 CNN 的表现反而比一维 CNN 差F1 掉了 5 个点以上。原因原始列顺序没有语义上的空间邻近性。比如第 3 列 service 和第 4 列 flag 在二维图里靠在一起卷积核会在它们之间学习组合特征但这种组合没有物理意义白白浪费参数。解决先按特征语义分组把协议类型、连接状态、流量统计、主机统计各归一块再按新列顺序 reshape。或者在 3×3 卷积核之前先用一个 1×1 卷积做通道混合让网络自己学习特征重排。最简单的方案是老老实实用一维 CNN 作为主结果二维 CNN 作为补充实验。5.5 早停触发太早val_loss 刚抬头就被钉死现象训练跑到 12 轮左右就停了val_loss 停在 0.35 左右继续手动训练到 30 轮时还能降到 0.28但早停已经把最佳权重恢复到 12 轮的版本。原因patience 设置太小比如 3 或 5而小数据集上 val_loss 本身就有波动一次轻微反弹就触发了停止条件。解决把 patience 调到 10 到 15 之间同时配合min_delta1e-4意思是 val_loss 至少要下降 0.0001 才算一次有效改善避免微小波动被当成「没有进步」。如果显存和时间允许还可以把 epochs 上限提到 100让早停充分判断。6. 进阶验证混淆矩阵、对比实验与可视化让源码从能跑到能答辩模型训练完成之后多数毕设会止步于一张 classification_report但要让评阅老师认为你真的理解这个项目还需要做三件事混淆矩阵可视化、基线模型对比、特征分布可视化。这三件事都不需要额外训练大模型成本很低但能把「深度学习识别效果为什么好」讲清楚。第一件是混淆矩阵热力图。用 seaborn 画一个 5×5 的热力图横轴是预测类纵轴是真实类。你会发现几乎所有误判集中在 Normal 与 DoS 之间、R2L 被误判成 Normal 这种模式。把这些观察写进论文比单纯贴一个 98% 的 acc 有力得多。第二件是基线对比实验。用同一个预处理数据跑一个逻辑回归或随机森林作为 baseline再把一维 CNN、二维 CNN 的 macro F1 放进同一个表里。这个对比表的结论通常很明确CNN 在整体 acc 上不一定会碾压树模型但在 Probe 和 R2L 这类局部模式特征明显的类别上 recall 更高。这个表就是「为什么用 CNN」的直接论据。第三件可视化有一点进阶取测试集输入在 model 的某个中间层输出特征用 PCA 或 UMAP 降到二维散点图按攻击大类着色。如果 R2L 和 U2R 的样本在图上聚成独立的小簇说明 CNN 确实学到了区分类别的内部表示如果和 Normal 完全混在一起说明模型在这些类别上并没有真正区分能力这也能提前预警你的结论别写过头。我的习惯是每个新项目跑通后第一件事就是画出这三张图确认模型「真的学到了东西」再写文档而不是盯着 training loss 曲线自我安慰。希望这一套流程能帮你把这个毕设源码做成一个经得起追问的完整项目而不是一个能跑的黑盒。本文还有配套的精品资源点击获取
返回列表