ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python基于CNN与随机森林的网络入侵检测实战:从数据管道到模型融合

Python基于CNN与随机森林的网络入侵检测实战:从数据管道到模型融合 简介这份资源是面向计算机、通信、人工智能、自动化等专业学生与从业者的网络入侵检测实战项目包可作为毕业设计、课程设计或期末大作业直接使用。项目基于UNSW_NB15官方数据集围绕42列特征与二分类标签完整实现了特征处理、Pearson相关性过滤、随机森林特征选择以及Keras搭建CNN网络训练的流程并配套项目说明与答辩PPT便于理解整体方案与汇报展示。压缩包为zip格式大小约10.02MB内含源码、说明文档与演示材料代码按StrTonum、Min-max、Balanced、Pearson、Find_feature、Pro_Testdata、CNN等模块组织运行前需安装sklearn、TensorFlow与Keras并注意版本对应。目前已有339人学习关注适合希望借鉴机器学习与深度学习融合思路、快速上手入侵检测实验或进行二次开发的读者参考。1. 从一份毕设压缩包说起CNN 加随机森林到底在入侵检测里解决什么问题很多人第一次看到「Python基于CNN网络和随机森林的网络入侵检测源码项目说明答辩PPT.zip」这类标题第一反应是「又一个毕设缝合怪」。但如果你真在安全运营或流量分析岗待过就会知道这个组合并不玄学单一模型在真实流量上要么误报高得让人麻木要么对少数攻击类召回低得离谱而 CNN 提特征、随机森林做判别的两段式结构恰好卡在「能跑通」和「有效果」之间。它适合三类人想入门流量分类的 Python 开发者、需要复现一个可解释 IDS 原型的安全初学者、以及要交付毕设或课程设计的学生。核心链路是原始流量先转成数值矩阵CNN 负责从字节或统计特征里抽局部模式随机森林接在 CNN 输出后面做最终分类兼顾非线性拟合和抗过拟合。下面按「数据怎么来、模型怎么搭、坑在哪、怎么验证」四步拆开讲。2. 数据管道把 pcap 变成 CNN 能吃的数值矩阵2.1 为什么选 NSL-KDD 或 CICIDS2017 而不是直接喂原始包网络入侵检测的公开数据集里NSL-KDD 是 KDD99 的去重修正版特征已经做过数值化和归一化41 维特征加 1 列标签适合快速验证模型结构CICIDS2017 更接近现代流量包含 DDoS、PortScan、Web 攻击等但原始 pcap 需要自己提取流特征。我一般建议先用 NSL-KDD 跑通全流程再换 CICIDS2017 看泛化。直接拿原始 pcap 字节流喂 CNN 不是不行但包长不一、协议头对齐复杂新手很容易在 padding 和对齐上翻车。常见做法是用 CICFlowMeter 或 scapy 先做流级聚合每条流提取 70 多维统计量再转成二维矩阵。2.2 用 pandas 做特征对齐和标签编码的最小代码import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder, MinMaxScaler # 读取 NSL-KDD 训练集列名按官方特征表补全 col_names [...] # 41 个特征名 label difficulty df pd.read_csv(KDDTrain.txt, namescol_names) # 标签二分类normal 为 0其余攻击为 1 df[label] df[label].apply(lambda x: 0 if x normal else 1) # 类别型特征做 LabelEncoder数值型做 MinMax 归一化 cat_cols [protocol_type, service, flag] for c in cat_cols: df[c] LabelEncoder().fit_transform(df[c]) feature_cols [c for c in df.columns if c not in [label, difficulty]] scaler MinMaxScaler() df[feature_cols] scaler.fit_transform(df[feature_cols]) # 转成 CNN 需要的 3D 形状(样本数, 特征数, 1) X df[feature_cols].values.reshape(-1, len(feature_cols), 1) y df[label].values np.save(X.npy, X) np.save(y.npy, y)这段代码的关键点有三个一是标签二值化多分类攻击类型在初版里先合并成「异常」二是类别特征必须编码protocol_type 这种字符串直接进模型会报错三是 reshape 成(样本, 特征, 1)因为一维 CNN 的输入通道是 1。参数上MinMaxScaler 把特征压到 0 到 1避免量纲差异让 CNN 梯度爆炸。如果换 CICIDS2017特征列名不同但流程一样注意处理 inf 和 NaN否则 scaler 会直接抛异常。2.3 训练集和测试集的划分陷阱NSL-KDD 官方已经给了 KDDTrain 和 KDDTest但很多人直接把两者混在一起 shuffle 再切分这是典型的数据泄漏。正确做法是训练集只用 KDDTrain测试集用 KDDTest并且测试集里会出现训练集没见过的攻击子类这才是真实场景。如果你用随机切分准确率能到 99%但上线后面对新攻击直接崩。我一般会额外留 10% 训练集做验证用train_test_split时固定random_state42保证每次复现一致。3. 模型搭建CNN 提特征、随机森林做判别的两段式实现3.1 CNN 部分为什么用一维卷积而不是二维流量特征是一维序列41 个特征按顺序排列一维卷积核在特征维度上滑动能捕捉相邻特征间的局部关联比如 flag 和 service 的组合模式。二维卷积通常用于图像把特征强行排成矩阵反而破坏语义。常见结构是Conv1D(32, kernel_size3, activationrelu) → MaxPooling1D(2) → Conv1D(64, kernel_size3, activationrelu) → GlobalAveragePooling1D() → Dense(64, activationrelu)。最后一层不要直接接 softmax 分类而是输出 64 维特征向量交给随机森林。3.2 用 Keras 搭 CNN 特征提取器的代码from tensorflow.keras import layers, models def build_cnn_feature_extractor(input_shape): inputs layers.Input(shapeinput_shape) x layers.Conv1D(32, 3, activationrelu, paddingsame)(inputs) x layers.MaxPooling1D(2)(x) x layers.Conv1D(64, 3, activationrelu, paddingsame)(x) x layers.GlobalAveragePooling1D()(x) x layers.Dense(64, activationrelu, namefeature_vec)(x) model models.Model(inputs, x) return model cnn build_cnn_feature_extractor((41, 1)) cnn.summary()这里paddingsame保证卷积后长度不变MaxPooling 降维GlobalAveragePooling 把时间步压成一个向量避免 Flatten 后参数过多。namefeature_vec是为了后面单独取这一层输出。参数上卷积核数量 32 和 64 是经验值特征维度小的时候再大容易过拟合kernel_size3 覆盖三个相邻特征再大感受野过宽反而稀释局部模式。训练时这个 CNN 可以单独接一个 Dense(1, sigmoid) 做预训练也可以直接和随机森林串联。3.3 随机森林接在 CNN 输出后面的两种接法第一种是离线两段式先训练 CNN 到收敛然后用cnn.predict(X_train)得到 64 维特征再训练 RandomForestClassifier。第二种是端到端微调CNN 预训练后固定权重只训练随机森林但随机森林不可导所以实际还是两段。我一般用第一种因为可控。随机森林参数n_estimators100max_depth12min_samples_leaf2class_weightbalanced处理攻击样本少的问题。class_weight很关键NSL-KDD 里 normal 占多数不加这个少数攻击类召回会很难看。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report # 提取 CNN 特征 X_train_feat cnn.predict(X_train) X_test_feat cnn.predict(X_test) rf RandomForestClassifier( n_estimators100, max_depth12, min_samples_leaf2, class_weightbalanced, random_state42, n_jobs-1 ) rf.fit(X_train_feat, y_train) y_pred rf.predict(X_test_feat) print(classification_report(y_test, y_pred))n_jobs-1用满 CPU 核训练快很多。max_depth12是防止树太深记住噪声min_samples_leaf2保证叶子节点至少两个样本。输出用 classification_report 看 precision、recall、f1不要只看 accuracy。3.4 单独 CNN 和 CNNRF 的对比实验怎么做想说服自己或答辩老师必须做消融。用同一个 CNN 特征提取器分别接 Dense(1, sigmoid) 和随机森林在 KDDTest 上比。常见结果是单独 CNN 准确率 92% 左右CNNRF 能到 95% 以上且少数类召回提升明显。表格里列 accuracy、macro f1、攻击类 recall 三列一目了然。注意测试集要用 KDDTest不要用训练集切分否则数字虚高。4. 避坑与排查训练不收敛、误报高、答辩被问住的 5 个真实翻车点4.1 损失降到 0.1 但测试集准确率只有 60%现象训练集 accuracy 99%测试集 60% 出头。原因数据泄漏或过拟合。先检查是不是把 KDDTest 混进了训练再检查 CNN 是不是太深、Dense 层太大。解决减少卷积层数加 Dropout(0.3)用 EarlyStopping 监控 val_losspatience3。如果还不行把随机森林的 max_depth 降到 8。4.2 随机森林训练报「输入包含 NaN 或无穷大」现象ValueError: Input contains NaN, infinity or a value too large。原因CNN 输出的特征向量里可能有 nan通常是输入数据没归一化好或者某列方差为 0 导致除零。解决在cnn.predict后加np.nan_to_num(X_train_feat)并在数据管道里用df.replace([np.inf, -np.inf], np.nan).dropna()清洗。更稳妥的是在 MinMaxScaler 之前先处理 inf。4.3 攻击类召回率不到 50%但 accuracy 很高现象整体 accuracy 96%但 U2R 和 R2L 这类少数攻击 recall 只有 30%。原因类别不平衡模型偏向多数类。解决随机森林加class_weightbalancedCNN 训练时用 focal loss 或给少数类更高样本权重。还可以在数据层做 SMOTE 过采样但注意只在训练集做测试集保持原始分布。4.4 答辩被问「为什么用随机森林不用 SVM 或 XGBoost」这是高频问题。回答要点随机森林对高维稀疏特征鲁棒不需要额外核函数调参训练可并行且能输出特征重要性方便解释哪些流量特征贡献大。SVM 在样本量大时训练慢XGBoost 效果可能更好但调参成本高作为毕设或原型随机森林是性价比最高的选择。如果老师追问可以补一句「我也试过 XGBoostf1 提升不到 1 个百分点但训练时间翻倍」。4.5 换 CICIDS2017 后模型直接崩掉现象NSL-KDD 上 95%换 CICIDS2017 后掉到 70%。原因两个数据集特征分布差异大NSL-KDD 是旧流量CICIDS2017 有现代攻击和不同采集方式。解决不要直接套用重新做特征对齐和归一化最好在 CICIDS2017 上重新训练 CNN。如果要做跨数据集迁移先做领域自适应或者至少把两边的特征列映射到同一语义空间。这个坑在答辩时如果被问到能答上来是加分项。5. 进阶技巧用特征重要性反推攻击行为让答辩多拿 10 分模型跑通只是及格线真正让这份「源码PPT」有说服力的是你能解释模型为什么这么判。随机森林自带feature_importances_但注意它对应的是 CNN 输出的 64 维抽象特征不是原始 41 维。想看原始特征贡献有两个办法一是用 SHAP 对 CNN 部分做解释二是训练一个直接用原始特征的小随机森林做代理对比重要性排序。我一般用第二种快且直观。import matplotlib.pyplot as plt # 代理模型直接用原始特征训练随机森林 rf_proxy RandomForestClassifier(n_estimators100, max_depth10, random_state42) rf_proxy.fit(X_train.reshape(-1, 41), y_train) importances rf_proxy.feature_importances_ idx np.argsort(importances)[::-1][:10] plt.barh(range(10), importances[idx]) plt.yticks(range(10), [feature_cols[i] for i in idx]) plt.xlabel(Importance) plt.show()跑出来通常src_bytes、dst_bytes、count、serror_rate排前面这些正好对应扫描和 DoS 行为的流量统计特征。答辩时你可以说「模型学到的关键特征和已知攻击模式一致说明它不是黑匣子。」这句话比任何 accuracy 数字都有分量。另外把混淆矩阵画出来标出哪类攻击容易被误判成 normal比如 R2L 常被漏报因为它的流量模式和正常登录很像。针对这个可以单独对 R2L 类做阈值调整牺牲一点 precision 换 recall。验证方法上除了 KDDTest我习惯再切一个时间维度的验证集按时间排序用前 70% 训练后 30% 测试。如果时间切分后性能下降不超过 5 个百分点说明模型没有严重依赖时间泄漏。这个技巧在答辩时说出来老师基本不会再追问「你怎么保证泛化」。最后说个血泪经验别在答辩前一晚才跑最终实验。CNN 训练有随机性不同 random_state 结果能差 2 到 3 个百分点。提前跑五次取平均把标准差也记下来。PPT 里写「准确率 95.2% ± 0.8%」比写「95.2%」专业得多。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进