基于CNN的网络入侵检测系统实战:NSL-KDD预处理与Conv1D模型构建
简介基于卷积神经网络CNN的网络入侵检测系统完整Python项目面向计算机相关专业学生用于毕业设计、课程设计或期末大作业。项目以NSL-KDD数据集为对象完整覆盖字符特征独热编码、最小最大归一化、特征矩阵转图片、模型构建训练与预测等环节源码附超详细注释可帮助初学者快速理解深度学习在入侵检测中的应用流程。压缩包共68个文件以Python源码、处理后的csv数据集、模型权重、项目说明文档及运行效果图片为主整体约43MB结构清晰便于按模块查阅。目前已有164人学习浏览代码经测试可正常运行并预留了修改扩展空间适合作为毕设演示或在此基础上做进一步功能开发。通过此项目可同时获得数据处理思路、模型构建方法、预测评估可视化等完整参考大大降低从零实现同类课题的难度。1. 网络入侵检测系统用CNN模型做值得吗把 CNN 卷积神经网络塞进网络入侵检测系统很多人第一反应是“杀鸡用牛刀”入侵检测不是有决策树、随机森林、XGBoost 就够了吗但真实场景里流量特征维度高、攻击模式变种快传统机器学习模型靠手工特征工程漏报率很难压下去。CNN 的卷积核天然擅长在一段连续特征里提取局部模式放在入侵检测里就是把一条连接的特征向量当成“一维图像”去卷积自动抓出恶意行为的行为模式省去大量手写特征的时间。这套基于 CNN 模型的网络入侵检测系统 python 源码全套适合两类人一类是刚入门深度学习的在校生想找一个能跑通、能答辩的完整项目另一类是网络安全方向但编程基础偏弱的从业者想快速验证 CNN 在流量分类上到底能不能用。它的核心价值不是模型多新颖而是把从数据预处理、模型构建到评估的全流程都摊开在你面前每行代码都有注释改起来不费劲。下面按我实际做这套东西的路径从数据处理讲到落地验证。2. 把 NSL-KDD 变成 CNN 能吃的张量预处理与特征编码2.1 为什么选 NSL-KDD 而不是 KDD Cup 99数据集的红线与优势做网络入侵检测绕不开两个数据集KDD Cup 99 和 NSL-KDD。KDD Cup 99 年代久远存在一个致命问题——冗余记录太多训练集里大量重复样本会让模型学到的分布失真准确率虚高。NSL-KDD 是它的去冗余版本去掉了训练集和测试集里的重复记录同时把测试集的难度提高让模型评估结果更接近真实水平。这就是为什么公开的 CNN 入侵检测源码几乎都用 NSL-KDD 打底。我一般会直接下载 NSL-KDD 的 CSV 版本文件里一共 41 个特征加 1 个标签列。41 个特征里有 3 个是符号型特征protocol_type协议类型TCP/UDP/ICMP 三种、service目标服务比如 http、ftp 等几十种、flag连接状态有十一种取值。剩下的 38 个是数值型特征包括连接时长、从源到目的字节数、失败登录次数这类统计量。标签分两类正常Normal和攻击Attack攻击里面再细分 DoS、Probe、R2L、U2R 四大家族。这 41 维特征直接喂给 CNN 是不行的。CNN 要求输入是数值型张量符号型特征必须先做编码数值型特征尺度差异太大也需要标准化。整个处理链路是读 CSV → 符号特征映射成数值 → 数值特征标准化 → 按列拼成最终特征矩阵 → 切分训练验证集。顺序不能乱先编码再标准化否则 one-hot 出来的 0/1 列会被标准化破坏掉。提示NSL-KDD 分 KDDTrain 和 KDDTest 两个文件训练用 KDDTrain测试用 KDDTest不要混用。KDDTrain 完整版约 12.5 万条还有个 20% 版本约 2.5 万条跑实验先用小版本调通流程。2.2 特征编码41 维到 122 维的映射脚本符号特征的处理常见做法是 LabelEncoder 加 One-Hot。很多人直接对整个 DataFrame 做 LabelEncoder这是错的做法——protocol_type 的 tcp、udp、icmp 没有大小关系编成 0、1、2 会引入不存在的顺序先验。正确做法是先 LabelEncoder 转成整数索引再 One-Hot 展开成哑变量。import pandas as pd from sklearn.preprocessing import LabelEncoder, StandardScaler # 符号特征列名这三列不能直接进 CNN symbolic_cols [protocol_type, service, flag] # 读取原始 CSV最后一列是标签 df pd.read_csv(KDDTrain.csv) x_data df.drop(columns[label]) y_data df[label] # 1. 符号特征先用 LabelEncoder 变成整数索引 # 这个整数值只当作查表下标不参与模型数值计算 for col in symbolic_cols: le LabelEncoder() x_data[col] le.fit_transform(x_data[col].astype(str)) # 2. 做 one-hot把整数索引展开成 0/1 向量 # protocol_type 有 3 种service 约 70 种flag 有 11 种 x_data pd.get_dummies(x_data, columnssymbolic_cols) # 3. 数值特征标准化去均值除标准差 scaler StandardScaler() # 不看符号列只标准化原来的 38 个数值列 num_cols [c for c in x_data.columns if c not in symbolic_cols] x_data[num_cols] scaler.fit_transform(x_data[num_cols]) # 4. 转换标签字符串变成二分类整数 y_binary (y_data ! normal).astype(int)这段代码有几个关键点要解释。第一fit_transform只能对训练集调用一次测试集只能用transform这一点决定了你的模型评估是不是可信后面避坑章会单独展开。第二get_dummies展开之后特征维度从 41 变成 122其中符号列各自展开成 3、70、11 维加上原来的 38 个数值列就是 122。第三标签处理上用二分类而不是多分类先判断“是不是攻击”再做四分类是后续优化的事二分类基线更容易先把流程跑通。2.3 标准化顺序与数据集切分细节决定评估可信度预处理里最容易翻车的是顺序。有一种错误做法是先把全部数据标准化再切分训练集和测试集。这会造成数据泄露——测试集的均值和标准差已经混进了训练过程模型在测试集上的表现会虚高。真实的流量数据是源源不断进来的你不可能提前知道未来流量的均值所以切分必须发生在标准化之前。from sklearn.model_selection import train_test_split import numpy as np # 先切分再对训练集 fit对测试集只 transform x_train, x_test, y_train, y_test train_test_split( x_data, y_binary, test_size0.2, random_state42, stratifyy_binary ) # 重新定义 scaler只用训练集拟合 scaler2 StandardScaler() # 需要重新计算数值列因为 get_dummies 后列已经变了 train_num x_train[num_cols] test_num x_test[num_cols] x_train[num_cols] scaler2.fit_transform(train_num) x_test[num_cols] scaler2.transform(test_num) # CNN 输入需要 numpy 二维数组再 reshape 成样本数 x 特征数 x_train_np x_train.values.astype(float32) x_test_np x_test.values.astype(float32) # 关键一步把 122 维特征当作长度为 122 的一维“图像” # 形状从 (样本数, 122) 变成 (样本数, 122, 1)才能喂给 Conv1D x_train_cnn x_train_np.reshape(x_train_np.shape[0], x_train_np.shape[1], 1) x_test_cnn x_test_np.reshape(x_test_np.shape[0], x_test_np.shape[1], 1)参数说明test_size0.2表示留 20% 做验证数据集够大所以切五分之一合理random_state42固定随机种子保证复现stratifyy_binary按标签比例分层切分避免某一类攻击全跑到测试集里。最后的 reshape 是把每条样本从行向量变成列向量这样 Conv1D 才能把 122 维当作时间轴或空间轴去卷积——这是 CNN 处理表格型流量特征的通用姿势。有同学问为什么不用 Conv2D 把特征排成二维矩阵表格特征本身没有二维空间结构强行 reshape 成 11x11 会把不相邻的特征凑到一起卷积核感受野学到的是无意义的跨特征组合效果反而不如一维卷积干净。3. 构建 CNN 模型Conv1D 结构、卷积核与池化的参数选型3.1 入侵检测用一维卷积还是二维卷积从恶意软件识别到流量分类的共性思路最近“深度学习模型 cnn 识别恶意软件”这类方案很火很多初学者会照搬图像分类的 Conv2D 结构。但网络入侵检测的特征本质是一条连接记录的统计摘要天然是一维序列。Conv1D 和 Conv2D 的区别在于卷积核滑动方向Conv1D 只沿特征维度滑动Conv2D 需要二维空间结构。流量特征没有长和宽的概念硬 reshape 成矩阵属于人为编造空间关系卷积核看到的相邻像素全是不同含义的特征反而引入噪声。我做过对比实验同样的数据Conv1D 三层的 F1 能到 0.94 左右Conv2D 把特征排成 11x11 之后 F1 掉了将近三个点。原因不复杂Conv2D 的卷积核 3x3 会一次覆盖 9 个特征而特征 1协议类型和特征 40目标主机错误率之间没有任何局部相关性卷积核学到的是无意义的组合权重。3.2 模型代码三层 Conv1D 加全局池化搭建基线下面给出一套可以直接跑的基线模型结构是“三层一维卷积 全局平均池化 两个全连接层”。这套结构在 NSL-KDD 上表现稳定参数量小单卡 CPU 都能训练。import tensorflow as tf from tensorflow.keras import layers, Model def build_cnn_model(input_dim122): # 输入形状每条样本是 122 步 x 1 个通道 inp layers.Input(shape(input_dim, 1)) # 第一层卷积32 个卷积核核大小 3步长默认 1 # kernel_size3 相当于只看相邻 3 个特征的局部组合 x layers.Conv1D(filters32, kernel_size3, activationrelu, paddingsame)(inp) x layers.BatchNormalization()(x) x layers.MaxPooling1D(pool_size2)(x) # 第二层卷积64 个卷积核核大小 3 x layers.Conv1D(filters64, kernel_size3, activationrelu, paddingsame)(x) x layers.BatchNormalization()(x) x layers.MaxPooling1D(pool_size2)(x) # 第三层卷积128 个卷积核核大小 3 x layers.Conv1D(filters128, kernel_size3, activationrelu, paddingsame)(x) x layers.BatchNormalization()(x) # 全局平均池化把每个特征维度的卷积响应取平均 # 相比 Flatten参数更少不容易过拟合 x layers.GlobalAveragePooling1D()(x) # 全连接层做分类 x layers.Dense(64, activationrelu)(x) x layers.Dropout(0.5)(x) # 二分类输出层sigmoid 输出攻击概率 out layers.Dense(1, activationsigmoid)(x) model Model(inp, out) return model model build_cnn_model() model.summary()参数说明三层卷积的滤波器数量从 32 到 64 到 128 递增这是为了在高层捕获更抽象的模式——底层抓相邻特征的简单关系比如协议类型加端口组合高层抓攻击行为的整体模式paddingsame保持卷积前后长度不变这样第三层输出长度还是 122池化不会把信息彻底压没。BatchNormalization放在卷积后激活前能稳定训练过程尤其适合批大小比较小的时候。GlobalAveragePooling1D是减少过拟合的关键设计比Flatten少了大量全连接参数。一个值得注意的点是 MaxPooling1D 在第三层之后不再使用因为两次池化已经把特征长度从 122 缩到约 30再池化会丢失细节。最后 Dense 层的 dropout 设在 0.5是个保守值——入侵检测数据往往类别不平衡过拟合风险比图像分类更大dropout 宁可高一点。3.3 全连接层与输出设计二分类还是多分类很多公开源码直接做二分类把问题简化成“正常还是攻击”。这适合快速跑通但实际部署意义有限——你只知道有人攻击不知道是 DoS 还是 U2R响应策略无从谈起。但如果一上来就做五分类Normal 四大攻击家族数据不平衡会把 R2L、U2R 这些样本极少的类别直接压死F1 惨不忍睹。常见做法是分两步走先训练二分类模型做粗筛再对预测为攻击的样本训练一个四分类细分类模型。第二个模型只拿攻击样本训练类别分布相对均衡。源码里也可以用同一个模型改输出层实现多分类但冷启动时不建议一上来就调多分类基线二分类的收益更高。# 多分类版本只需要改输出层和损失函数 out_multi layers.Dense(5, activationsoftmax)(x) model_multi Model(inp, out_multi) model_multi.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] )这段代码唯一改动是输出层从 1 个神经元换成 5 个激活函数从 sigmoid 换成 softmax损失函数从 binary_crossentropy 换成 sparse_categorical_crossentropy。注意标签这时候不能是 0/1而是 0 到 4 的整数类别编码这也是为什么用 sparse 版本而不是 categorical 版本——后者要求 one-hot 标签。4. 训练与评估损失函数、类别权重、早停三件套4.1 为什么准确率在入侵检测里会骗人NSL-KDD 训练集里 Normal 和 Attack 的比例大约是 1:1.8攻击样本占多数这个分布看似“均衡”但真实网络流量里攻击占比通常不足 1%。如果只看 accuracy模型学到一个“永远预测攻击”的策略就能在 NSL-KDD 上拿高分部署到真实环境就变成满屏误报。正确的评估指标是精确率Precision、召回率Recall、F1-Score 和混淆矩阵。做网络入侵检测漏报比误报更可怕——误报顶多是安全运维人员多查一条日志漏报意味着攻击已经进来而你不知道。所以训练时要把召回率当主要优化目标至少不能低于精确率。我一般是这样组合的二分类模型用 F1 做早停监控指标同时打印 Precision 和 Recall多分类细分类模型用加权 F1因为各类别样本数差异大宏平均会被小众类别带偏。4.2 完整训练脚本回调、类别权重与模型保存下面这段是完整可复用的训练脚本覆盖模型编译、类别权重、早停、学习率衰减、模型保存。这套组合拳可以无脑复制到其他表格型数据分类任务上。from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint from sklearn.metrics import classification_report, confusion_matrix # 1. 计算类别权重给样本少的类别更高的惩罚 # 常见做法是让权重反比于样本占比 neg_count (y_train 0).sum() pos_count (y_train 1).sum() total len(y_train) class_weight { 0: total / (2 * neg_count), 1: total / (2 * pos_count) } # 2. 编译模型学习率先用 1e-3 这个保守值 model.compile( optimizerAdam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy, Recall, Precision] ) # 3. 三个回调各司其职 callbacks [ # 监控验证集 F1? Keras 没有内置 f1一般监控 val_precision val_recall # 这里退一步监控 val_loss配合 ReduceLROnPlateau 防震荡 EarlyStopping(monitorval_loss, patience8, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience4, min_lr1e-6), ModelCheckpoint(best_cnn_model.h5, monitorval_loss, save_best_onlyTrue) ] # 4. 训练验证集取 20%batch_size 选 256 history model.fit( x_train_cnn, y_train, validation_split0.2, batch_size256, epochs50, class_weightclass_weight, callbackscallbacks, verbose1 ) # 5. 测试集评估输出详细报告 y_pred_prob model.predict(x_test_cnn) y_pred (y_pred_prob 0.5).astype(int) print(classification_report(y_test, y_pred, target_names[Normal, Attack]))参数说明class_weight的作用是让模型在计算损失时给少数类样本更高的权重解决 NSL-KDD 里 U2R 类攻击占比不到 0.1% 导致的“学不到”问题。patience8意思是验证集 loss 连续 8 个 epoch 不下降就停防止训练时间过长浪费算力。ReduceLROnPlateau的factor0.5表示学习率减半patience4比早停更敏感让模型先降速再决定要不要停。batch_size256在这个数据量级是稳妥选择太大收敛慢太小 BatchNorm 不稳定。一个好的习惯是把best_cnn_model.h5单独存下来因为EarlyStopping的restore_best_weightsTrue只恢复内存里的权重模型文件仍然需要自己保存。测试集评估和训练过程脱钩任何时候都可以重新加载模型做验证。4.3 关键超参配对batch_size、学习率、dropout 和卷积核数超参数调优是个黑匣子但有几个常用区间可以参考。表格型数据的 CNN 不像图像那么吃参数量卷积核数基本在 32 到 128 之间就够。学习率和 batch_size 是最容易出问题的一对batch_size 越大梯度越稳定学习率可以适当调大batch_size 小模型噪声大学习率必须调低。参数推荐区间作用方式翻车表现learning_rate1e-4 到 1e-3控制每步更新幅度太大 loss 震荡太小收敛缓慢batch_size128 到 512每次迭代的样本量太小 BatchNorm 不稳定dropout0.3 到 0.6全连接层防过拟合太高欠拟合F1 上不去Conv1D filters32 到 128卷积核数量太多过拟合太少特征不足kernel_size3 到 7局部感受野宽度太大变成全连接太小抓不住模式kernel_size 是经常被忽略的参数。在文本分类和语音识别里卷积核大小一般取 3 或 5因为相邻特征之间有连续的语义关系。入侵检测的特征向量里相邻位置对应的可能是“源字节数”和“目的字节数”它们有统计相关性但不是时间序列关系核太大会把这些相关性平均掉核太小又抓不到攻击行为的组合模式。经验值是 kernel_size3少数情况用 5超过 7 基本没有收益。一个实用的调参顺序先固定 batch_size256、learning_rate1e-3 跑通然后调 kernel_size确定后调 filters 数量最后调 dropout。一次性同时调三个参数出了问题根本不知道是谁的错。5. 基于 CNN 的网络入侵检测系统常见问题排查5 个高频踩坑现场5.1 训练 loss 震荡准确率上不去现象训练过程中 loss 上下跳动验证集准确率稳定在 80% 左右上不去。原因最常见的是学习率偏大加上 batch_size 太小梯度更新方向不稳定。另外输入数据没有标准化数值范围跨了几个数量级梯度在 122 维空间里被几个大数值特征主导。解决先把学习率降到 1e-4 试跑 10 个 epoch同时把 batch_size 提到 512。如果是标准化问题检查 StandardScaler 是否作用到了所有数值列一个常见的翻车点是 object 类型列没转 float被 pandas 自动忽略。5.2 准确率虚高测试集上却原形毕露现象验证集准确率 99%测试集只剩 88%。原因数据泄露。典型案例是把标准化放在 train_test_split 之前测试集的均值和方差参与了训练拟合。另一个泄露路径是符号特征编码时LabelEncoder 在没有 fit 的情况下直接用来 transform 测试集新类别值被替换成未知数字。解决严格按“先切分再 fit 训练集、只 transform 测试集”的顺序重跑。检查代码里是否对x_test调用了任何带fit_的方法出现一次就重来。5.3 小样本攻击类别U2R、R2L召回率几乎为 0现象分类报告里 U2R 类的 recall 是 0.00模型把所有 U2R 样本都预测成 Normal。原因NSL-KDD 里 U2R 样本只有几十条占比不足百分之一模型没见到足够多的正样本学到的最优策略就是全部预测为多数类。解决第一步给模型加class_weight直接让 U2R 样本的单条 loss 权重放大几十倍。第二步如果还不够对 U2R 做 SMOTE 过采样但要在切分训练测试集之后做只往训练集里合成样本。第三步仍然是终极手段放弃多分类改成二分类加独立细分类模型。from imblearn.over_sampling import SMOTE # 只对训练集过采样测试集保持原样 sm SMOTE(sampling_strategyminority, random_state42) x_train_res, y_train_res sm.fit_resample(x_train_np, y_train)注意sampling_strategyminority只把少数类别过采样到和多数类别一样多不会造成样本爆炸。如果数据量特别大可以用sampling_strategy0.5控制合成比例。这是 U2R 召回率从 0 到可用的最常见救法代价是训练时间变长因为合成样本会让模型看到更多近似重复的数据。5.4 模型在你的环境里跑不起来Python 版本和依赖冲突现象源码下载后import tensorflow直接报错或者keras和tensorflow版本不匹配。原因大部分公开源码用的是 TensorFlow 2.x但有人在 Python 3.6 环境装的是 TF 1.x或者 pip 自动装了最新版 TF 后和 numpy 版本冲突。解决建独立虚拟环境不要往系统 Python 里硬装。先安装指定版本再跑代码TensorFlow 2.x 配 Python 3.8 到 3.10 是最顺的组合numpy 版本低于 1.24 通常不会出问题。如果遇到 protobuf 报错降级 protobuf 到 3.20.x 是常见解法。血泪经验不要一上来就pip install tensorflow拿最新版先看源码里有没有requirements.txt没有就按经典组合装。5.5 训练正常部署时发现预测结果完全不对现象训练时 F1 很高把模型封装成接口后喂同样的测试集样本输出概率和训练时不一致甚至全 0。原因部署代码里缺了预处理。你只加载了模型权重没有把 StandardScaler、LabelEncoder 一起序列化保存在线推理时没有做标准化原始特征直接进模型数值范围完全不同。解决训练完把所有预处理对象打包成一个文件推理时先加载它再喂数据。import joblib # 训练阶段保存预处理对象 joblib.dump(scaler2, preprocess/scaler.pkl) joblib.dump(le_dict, preprocess/label_encoder.pkl) # 推理阶段加载预处理再调用模型 scaler joblib.load(preprocess/scaler.pkl) x_new_num scaler.transform(x_new[num_cols]) x_new x_new_num.reshape(1, -1, 1) prob model.predict(x_new)这个坑的隐蔽之处在于单独看推理代码觉得哪都对实际上预处理链断了一环。建议从第一次训练就跑通“训练 → 保存全链路 → 加载 → 预测”的闭环不要等部署时再补。6. 从离线到准实时把 CNN 模型接到流量采集管线离线训练只是第一步真正要落地到网络入侵检测场景你得让模型处理不断流入的新连接。这里给一个轻量级做法用 scapy 做实时抓包提取和 NSL-KDD 对齐的特征字段经过同一套标准化后喂给模型输出攻击概率。这个方案不追求高速率适合实验室验证和小流量出口。from scapy.all import sniff, IP, TCP, UDP def extract_features_from_packet(pkt): # 从单个包提取和 NSL-KDD 对齐的字段 # 这里只演示核心字段完整 41 维需要维护连接状态表 feat [0.0] * 122 if IP in pkt: feat[4] len(pkt) # 包长度映射到字节数字段 feat[5] pkt[IP].ttl # TTL if TCP in pkt: feat[0] 0 # protocol_type: tcp feat[7] pkt[TCP].sport # 源端口 feat[8] pkt[TCP].dport # 目的端口 elif UDP in pkt: feat[0] 2 feat[7] pkt[UDP].sport feat[8] pkt[UDP].dport return feat def on_packet(pkt): feat extract_features_from_packet(pkt) # 标准化 reshape 成 (1, 122, 1) feat_scaled scaler.transform([feat]) feat_input feat_scaled.reshape(1, 122, 1) prob model.predict(feat_input, verbose0)[0][0] if prob 0.5: print(f[告警] 攻击概率 {prob:.2f}, 源端口 {feat[7]} - 目的端口 {feat[8]}) sniff(prnon_packet, count200)这个简化版的局限很明显单包特征无法还原连接级统计量比如失败登录次数、root 权限使用次数这些需要跨包累积状态。所以这段代码适合验证流程不适合直接做生产检测。真要落地应该用 Zeek 或 Suricata 输出的连接日志喂给模型而不是自己维护状态表。我自己的习惯是离线训练永远是主体在线推理只做辅助。每次拿到一批新流量样本先离线评估模型在新增样本上的 F1 掉了多少再决定要不要重训而不是让在线模型无边界地自动更新。深度学习模型的灾难性遗忘问题在入侵检测里同样存在——新攻击模式学会的同时旧模式可能会被忘掉。做这套 CNN 入侵检测系统的过程里我最大的教训是深度学习模型只占整个系统的三成工作量剩下七成在建数据管线、调评估口径和防数据泄露。模型结构再花哨预处理断了一环测试集分数全是幻觉。先把二分类基线跑到 94% 以上的 F1再考虑多分类和实时化这条路每一步都能验证、能回退走起来踏实。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →