
简介该项目是一套基于Python与CNN网络入侵检测算法源码面向网络安全、机器学习方向的课程设计、期末大作业及毕业设计场景。项目以NSL-KDD等数据集为基础涵盖数据预处理、CNN模型构建、训练、预测与评估的完整流程适合具备一定Python基础并希望深入理解深度学习安全应用的读者学习借鉴。压缩包共33个文件除py源码外还有csv数据文件、xml工程配置、pth模型权重、md项目说明、txt说明及训练过程可视化图片等整体约21.58MB。已有631人学习/下载。源码中的Train.py、Predict.py、CNNMould.py等模块分工明确可直接运行帮助读者快速复现实验并进一步调参和扩展尤其适合作为课程项目、毕设或论文研究的参考资料。1. 一套 CNN 入侵检测源码到底在解决什么问题你大概率是冲着“python CNN 网络入侵检测”这几个词来的手里拿到一个 zip里面有源码和项目说明但不确定这东西能不能跑、跑通了又能不能用在真实流量上。这套方案的核心是把网络流量里每条连接记录的特征协议类型、端口、数据包长度、标志位这类字段重组成矩阵或序列交给 CNN 卷积神经网络自动提取局部特征最后做二分类正常/攻击或多分类DoS、Probe、R2L、U2R 等。它替代了传统的“人工设计特征 机器学习分类器”的老路线主要价值是把特征工程这份苦差事部分交给卷积层去完成。适合三类人做安全方向毕设和课题复现的学生、想快速验证深度学习入侵检测可行性的工程师、以及刚入门想拿一份 python 源码当教程跟着改的研究者。它不是部署级产品但作为第一个能跑的模型基线足够撑起后续优化。2. 把网络流量喂给 CNN 之前特征工程与数据集准备2.1 为什么是 CNN 而不是随机森林或循环神经网络网络入侵检测的传统做法是梯度提升树或随机森林加手工特征效果不差但特征工程占掉一大半精力。CNN 卷积神经网络的优势在于它能自动学习局部特征组合比如某条连接里 duration 短、src_bytes 异常大、同时 service 是特殊端口这种“短连接 大流量 非常见服务”的组合模式卷积核在滑动过程中会自动提取出来不需要你显式写规则。用 CNN 处理表格型流量特征一般有两条路线一是套用图像里的二维卷积把每条连接记录的特征向量 padding 成固定宽高的矩阵当作灰度图二是直接用一维卷积 Conv1D把每条记录看作一个长度为特征数的序列。对于 NSL-KDD、CICIDS2017 这类结构化数据集我建议先走 Conv1D原因很简单特征维度本身是顺序无关的Conv2D 硬把它们排成矩阵会引入不存在的空间相邻关系收敛慢且解释性差。Conv1D 的卷积核只在一维方向上滑动等价于做特征组合筛选参数更少小样本下不容易过拟合。2.2 数据列名与预处理数值编码、归一化、样本平衡拿到源码包第一步不是跑模型是先确认数据长什么样。常见的 NSL-KDD 每条记录有 41 个特征加 1 个标签列前 4 个特征是离散或类别型protocol_type、service、flag中间 32 个是数值型后 5 个也是数值型。CICIDS2017 更麻烦列名是英文长描述还有大量 NaN 和无穷大。无论哪个数据集CNN 吃的只能是定长数值矩阵所以预处理是整套源码里最容易翻车、也最值得花时间的地方。# preprocessing.py # 0. 先读入原始数据KDD99 系列常见是 42 列最后一列是标签 import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder raw_df pd.read_csv(data/train.csv, headerNone) # 给列起名列名数量以你手里的数据为准别生搬硬套 raw_df.columns [ 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 ] # 1. 类别特征做 one-hot数值特征归一化 categorical_cols [protocol_type, service, flag] numeric_cols [c for c in raw_df.columns if c not in categorical_cols [label]] enc OneHotEncoder(handle_unknownignore, sparse_outputFalse) cat_features enc.fit_transform(raw_df[categorical_cols]) scaler StandardScaler() num_features scaler.fit_transform(raw_df[numeric_cols].replace([np.inf, -np.inf], np.nan).fillna(0)) # 2. 拼回特征矩阵 X np.hstack([cat_features, num_features]) # 3. 标签编码成整数 label_encoder LabelEncoder() y label_encoder.fit_transform(raw_df[label]) print(特征矩阵形状:, X.shape, 标签类别:, label_encoder.classes_) np.save(data/X.npy, X) np.save(data/y.npy, y)这里的核心点有三个。第一OneHotEncoder 必须加handle_unknownignore否则训练集里没出现过的 service 值在预测时会让程序直接抛异常第二replace([np.inf, -np.inf], np.nan).fillna(0)是处理 CICIDS 这类含脏数据集的常规手段CNN 对 NaN 极其敏感一个 NaN 就能让 loss 变成 NaN第三sparse_outputFalse是为了拿到普通 ndarray方便后面直接 hstack。如果类别特征特别多导致维度膨胀也可以只对 protocol_type 和 flag 做 one-hotservice 用 hash 编码但那是后期优化首次跑通不建议改。2.3 样本不平衡先看分布再谈训练入侵检测数据集几乎都是不平衡的KDD99 里 Normal 占比能到一半以上而 U2R、R2L 这类攻击样本往往只有几百条。直接训练出来的模型会无脑倾向多数类。我一般的做法是训练前打印np.bincount(y)如果某个类别占比小于 1%先用 class_weight 兜底不要急着上 SMOTE。from sklearn.utils.class_weight import compute_class_weight classes np.unique(y) weights compute_class_weight(class_weightbalanced, classesclasses, yy) class_weight_dict dict(zip(classes, weights)) print(各类别权重:, class_weight_dict)注意class_weight 只在训练时生效评估指标必须看每个类别单独的 precision 和 recall而不是全局 accuracy。这个细节后面第 5 章还会重点讲因为它是整个复现过程里最隐蔽的翻车点。3. 用 Keras 搭一个可复现的 CNN 入侵检测模型核心代码与参数调法3.1 Conv1D 模型结构过滤器数量、卷积核大小与池化预处理做完特征矩阵已经是定长的数值向量。现在搭 CNN 卷积神经网络我用 TensorFlow 的 Keras 接口结构尽量精简两层 Conv1D 两层 MaxPooling1D Flatten 全连接 Dropout。不要一上来就堆 ResNet 那种重型结构入侵检测的数据量撑不起深网络效果反而更差。# model.py from tensorflow.keras.models import Sequential from tensorflow.keras.layers import ( Conv1D, MaxPooling1D, Flatten, Dense, Dropout, BatchNormalization ) from tensorflow.keras.optimizers import Adam def build_cnn(input_dim, num_classes): model Sequential([ # 第一层卷积64 个过滤器卷积核大小为 3 Conv1D(filters64, kernel_size3, activationrelu, input_shape(input_dim, 1)), BatchNormalization(), MaxPooling1D(pool_size2), # 第二层卷积128 个过滤器继续提局部特征 Conv1D(filters128, kernel_size3, activationrelu), BatchNormalization(), MaxPooling1D(pool_size2), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(num_classes, activationsoftmax) ]) model.compile( optimizerAdam(learning_rate0.001, clipnorm1.0), losssparse_categorical_crossentropy, metrics[accuracy] ) return model参数说明input_shape(input_dim, 1)表示把每条记录看成长度为 input_dim、通道数为 1 的序列这是 Conv1D 的标准输入格式和图像里 HWC 布局类似通道数相当于灰度图的 1。kernel_size3的意思是卷积核每次滑动覆盖 3 个相邻特征这个值对表格型数据是比较稳妥的起点改成 5 能覆盖更宽的特征组合但容易把不相关特征也揉在一起。filters64/128是卷积核数量数据量大可以往上加但 NSL-KDD 这种万级样本再深收益很小。BatchNormalization放在卷积和激活之后能明显缓解梯度消失还能允许你用稍大的学习率。clipnorm1.0是做梯度裁剪防止某条异常样本把权重一步打飞导致 loss 变成 NaN——这个是提前给你打预防针。3.2 训练策略早停、模型保存与批次大小选择模型构建好了接下来是训练脚本。这个环节一定要加 EarlyStopping 和 ModelCheckpoint原因很实际深度学习 cnn 训练入侵检测模型时验证集 loss 通常在 10 个 epoch 左右开始回升你不加早停最后保存的往往是一个过拟合版本。# train.py import numpy as np from sklearn.model_selection import train_test_split from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint from model import build_cnn X np.load(data/X.npy) y np.load(data/y.npy) # 分层切分保证每个类别在训练集和验证集里都有 X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) # Conv1D 需要把最后一个维度显式补出来 X_train X_train[..., np.newaxis] X_val X_val[..., np.newaxis] model build_cnn(input_dimX_train.shape[1], num_classeslen(np.unique(y))) callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ModelCheckpoint(best_model.h5, monitorval_accuracy, save_best_onlyTrue) ] history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs30, batch_size64, callbackscallbacks, class_weightclass_weight_dict, verbose1 )三个关键参数要讲透。batch_size64是入侵检测数据集的常见选择样本量不大256 会导致收敛慢且容易震荡32 虽然更稳但训练时间长64 是平衡点。patience5表示验证集 loss 连续 5 个 epoch 不下降就停止这是给模型一定容错空间避免因为单次波动提前收工。restore_best_weightsTrue会让模型回滚到验证集最优的权重这个必须开否则 EarlyStopping 触发时保存的是停止那一刻的权重指标反而变差。class_weight 参数直接喂给 fitKeras 会在每个 batch 计算 loss 时自动放大少数类的权重比手工过采样省事得多。3.3 评估指标别只盯着 accuracy 看训练完第一件事不是看 accuracy是算混淆矩阵和分类的 precision、recall、F1。入侵检测场景里漏报攻击的代价远高于误报正常流量所以 recall 才是第一指标。# evaluate.py from sklearn.metrics import classification_report, confusion_matrix import numpy as np y_pred_proba model.predict(X_val) y_pred np.argmax(y_pred_proba, axis1) print(classification_report(y_val, y_pred, target_nameslabel_encoder.classes_)) print(confusion_matrix(y_val, y_pred)) # 单独看攻击类别的召回率是否偏低 report classification_report(y_val, y_pred, output_dictTrue) for cls in label_encoder.classes_: if cls ! normal: print(f{cls} recall {report[cls][recall]:.4f})这里的逻辑是分类报告会按类别输出你要重点盯攻击类别的 recall尤其是样本数极少的 R2L、U2R。如果 accuracy 有 95% 但 R2L 的 recall 是 0说明模型直接把这个类全判错了class_weight 或者数据增强还没吃透。4. 拿到源码包后怎么跑通与验证项目结构、训练顺序与指标判读4.1 解压之后先看这三个文件不要急着跑 train.py。任何一份成体系的 python 源码包拿到手先找三个文件README 或项目说明、requirements.txt、以及一个数据目录说明。我见过太多人解压后直接python train.py然后被 ModuleNotFoundError 和维度不匹配轮番折磨。合理顺序是# 1. 创建虚拟环境避免污染系统 python python -m venv .venv source .venv/bin/activate # Windows 下用 .venv\Scripts\activate # 2. 安装依赖 pip install -r requirements.txt # 3. 先看项目说明通常会有运行顺序和预期输出标准源码包的项目结构一般长这样data/放原始 CSV 和预处理后的 npy 文件preprocessing.py负责数据清洗和特征构建model.py放网络结构train.py是训练入口evaluate.py或predict.py做测试和推理requirements.txt锁依赖版本。如果你手里的包结构不一样也没关系按“数据处理 → 模型定义 → 训练 → 评估”这条主链去对应就行。这里要强调一点requirements.txt 里的版本号是作者在自己机器上验证过的不要盲目升级。比如 tensorflow 2.x 的 API 在 2.13 之后有些行为变化如果你直接装最新版源码里写的Adam(lr...)这种旧式用法就会报错。稳妥做法是完全按 requirements 装跑通后再考虑升级。4.2 训练预期loss 曲线怎么看才算正常训练开始后你要关注两个东西训练 loss 是否在稳步下降以及验证 loss 和训练 loss 的差距是否快速拉大。如果前 5 个 epoch 训练 loss 下降但验证 loss 纹丝不动说明模型容量不够或特征预处理有问题如果两者都在降但差距超过 30%说明过拟合正在发生靠早停能救一部分但更根本的办法是增大 Dropout 或减小网络宽度。监控曲线时还要看 batch 粒度还是 epoch 粒度。Keras 默认 verbose1 会输出每个 epoch 末的指标但我们更该关心的是验证集指标在最后几个 epoch 的表现。如果 best_model.h5 的保存条件是 val_accuracy而早停条件是 val_loss两者选中的模型可能不是同一个权重这是设计上的小坑。我一般把 ModelCheckpoint 的 monitor 也改成 val_loss让“最优”的定义保持一致避免最后拿到的模型和训练过程中表现最好的模型错位。4.3 验证集结果和公开基线对标用什么口径比模型训练完你拿到的准确率、召回率需要有个参照物。常见做法是看这套源码在数据集上的分类报告和论文里的基线对比但要注意口径差异一是部分作者用全部数据训练再在测试集评估那属于数据泄露指标不可信二是有的源码只做二分类normal/attack有的是五分类五分类的准确率天然低于二分类不能直接比。我一般先把二分类结果跑出来作为上界再去展开多分类。如果多分类里每个攻击类别的 recall 都过 80%这个模型的实用性就算过关了。5. 入侵检测模型复现避坑五个最容易翻车的地方与排查路径5.1 现象loss 变成 NaN训练直接中断原因很简单输入数据里残留了 NaN 或无穷大或者学习率偏大导致梯度爆炸。入侵检测数据集尤其是 CICIDS2017原始 CSV 里有大量空值和 Infinity预处理只要漏掉一个卷积层算出来的中间值就会溢出。解决在预处理脚本里统一做np.nan_to_num(X)并用replace([np.inf, -np.inf], np.nan).fillna(0)做清洗。同时在优化器上加clipnorm1.0做梯度裁剪双保险。如果训练中依然偶发 NaN还要检查是否出现了除零操作比如自定义 loss 或指标函数里分母是 precision recall 时没有加 epsilon。5.2 现象准确率 95%但每个攻击类别 recall 全是 0这是最典型的不平衡数据翻车现场。模型学到的最优策略是什么都不判全输出 Normal因为 Normal 占绝大多数光靠猜就有 90% 以上准确率。训练过程看起来一切正常loss 在降accuracy 在涨但混淆矩阵一出来就露馅。解决训练前用compute_class_weight给少数类加权如果效果不够再对少数类做 SMOTE 过采样。但 SMOTE 要做好只作用在训练集上别把验证集也生成一遍否则验证指标虚高得离谱。调权重时注意一个细节class_weight 只在 Keras 内部计算 loss 时生效最终评估还是要回到原始分布的数据上否则你算出来的 precision 没有参考意义。5.3 现象训练时维度正常预测时却报维度不匹配原因基本锁定在 one-hot 编码上。训练集的service列里有 66 个类别编码后多了 66 列预测时的数据里如果出现新 service 值handle_unknownignore能保证不报错但列数对不上就会崩。更隐蔽的是如果你用 pandas 的get_dummies而不是 sklearn 的 OneHotEncoder训练和预测的列顺序可能因为数据内容不同而错位。解决统一用 sklearn 的 OneHotEncoder训练完成后用joblib.dump(enc, artifacts/encoder.pkl)保存编码器和 scaler预测时重新加载。这个编码器要跟着模型一起走否则换一台机器推理就废了。# predict.py import joblib import numpy as np encoder joblib.load(artifacts/encoder.pkl) scaler joblib.load(artifacts/scaler.pkl) new_sample pd.DataFrame([[tcp, http, SF, 181, 5450]], columns[protocol_type, service, flag, src_bytes, dst_bytes]) cat encoder.transform(new_sample[categorical_cols]) num scaler.transform(new_sample[numeric_cols].fillna(0)) X_new np.hstack([cat, num]).reshape(1, -1, 1) prob model.predict(X_new) print(预测类别:, label_encoder.inverse_transform([np.argmax(prob)])[0])5.4 现象训练时 shuffle 后效果虚高部署后直线下降很多源码在train_test_split之后还会再做一次np.random.shuffle这在小数据集上是常规操作。但对于按时间采集的入侵检测数据shuffle 会把时间相关性打散模型学到的是“整个时间窗口的全局统计特征”而不是“当前流量切片”的特征。真实部署时流量是流式到达的这种模型往往第一天就失灵。解决如果你手里的源码是拿时间序列数据集做的改成按时间戳切分train 取前 80%test 取后 20%并且训练时不要 shuffle。如果不确定数据是否按时间排序打开原始 CSV 看看时间戳列有的话务必按时间切。5.5 现象训练到一半内存爆掉卡死不动入侵检测特征经过 one-hot 后维度可能到 120 以上如果把每条记录继续 pad 成 8x8 的矩阵内存开销还会翻几倍。Keras 默认把整个数据集加载进内存做 batch 切分NSL-KDD 几万条没问题但 CICIDS2017 几百万条直接吃满 32G 内存。解决改用tf.data.Dataset.from_tensor_slices配合batch()和prefetch()让数据按 batch 流式读取。另一个办法是训练前检查X.shape如果特征维度超过 200考虑删掉冗余的 one-hot 列或改用 Embedding 层做离散特征编码别硬扛。6. 从分类准确率到实战可用阈值校准与小流量切片模型训练完只完成了 70% 的工作剩下 30% 是把“模型能分类”变成“系统能决策”。对于一个二分类甚至多分类模型默认的 argmax 阈值 0.5 并不适合入侵检测——攻击样本少且难以识别直接套 0.5 往往让 recall 偏低。正确的做法是画 precision-recall 曲线按业务偏好选阈值。# threshold_tuning.py from sklearn.metrics import precision_recall_curve import numpy as np # 假设二分类取正类的预测概率 probs model.predict(X_val)[:, 1] precision, recall, thresholds precision_recall_curve(y_val, probs) # 怎在漏报和误报之间做取舍这里给一个 F1 最大化的示范 f1_scores 2 * precision * recall / (precision recall 1e-9) best_idx np.argmax(f1_scores) best_threshold thresholds[best_idx] print(最佳阈值:, best_threshold, 对应 F1:, f1_scores[best_idx])调完阈值再谈实战落地。源码里的模型是按“单条连接记录”做分类的但真实网络流量是连续数据流没有天然的“一条记录”。常见做法是用 Scapy 抓包按固定时间窗口或固定包数切出流量切片再把这些切片转成特征表喂给模型。窗口大小一般取 2 到 5 秒太短特征统计不稳定太长会被攻击流量平均掉检测时效性也差。这一步做好后模型才能从“离线复现”走向“实时检测原型”。最后说一个我的血泪经验第一次复现这类项目时我盯着 accuracy 调了一周直到把混淆矩阵打印出来才意识到模型在偷懒。从那以后我的习惯是先跑通最小二分类、再扩多分类每改一个参数都重新算一次每个类别的 recall。调阈值和剪枝这类优化手段是后半场的玄学但数据清洗和指标口径才是决定模型能不能用的基本功。这套流程走下来你手里的这份 python 源码至少能变成一个你真正理解的检测基线而不是一个跑完就丢的黑匣子。希望帮到你。本文还有配套的精品资源点击获取