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和随机森林为什么要串在一起用以及我拿到代码之后怎么在自己的机器上跑起来。网络入侵检测Network Intrusion Detection本质是一个多分类问题把一条条网络连接记录分成Normal、DoS、Probe、R2L、U2R等类别。传统做法是拿NSL-KDD或CIC-IDS2017数据集手工做特征工程然后喂给随机森林、SVM、XGBoost。但手工特征有个天花板——流量里的时序模式和局部相关性很难用统计量表达。CNN擅长从原始特征矩阵里提取局部模式随机森林擅长在CNN输出之上做非线性决策两者串起来正好互补。这套组合在毕设和入门项目里出现频率极高不是没有道理的。2. CNN与随机森林的分工为什么不是二选一2.1 CNN在流量特征上到底卷出了什么网络流量数据经过预处理后通常被整理成二维矩阵的形式行是样本每条连接记录列是特征如duration、protocol_type、src_bytes、dst_bytes、flag等。直接把这个矩阵喂给CNN看起来有点奇怪因为图像有空间局部性而特征列之间未必有像素那样的邻域关系。但实际做法是先对特征做归一化和编码然后把一维特征向量reshape成类似“1×N”或“M×N”的伪图像结构让卷积核在特征维度上滑动提取局部组合模式。比如NSL-KDD有41个特征经过独热编码后维度会膨胀到120。把这120维向量reshape成比如12×10的矩阵卷积核就能捕捉“某几个特征同时出现时意味着什么”。这不是玄学本质是在做特征交叉只不过用可学习的卷积核代替了手工的叉乘。import numpy as np from tensorflow.keras import layers, models def build_cnn(input_dim, num_classes): model models.Sequential([ # 将一维特征向量reshape成二维模拟“特征图” layers.Reshape((input_dim, 1), input_shape(input_dim,)), layers.Conv1D(64, kernel_size3, activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling1D(pool_size2), layers.Conv1D(128, kernel_size3, activationrelu, paddingsame), layers.BatchNormalization(), layers.GlobalAveragePooling1D(), layers.Dense(128, activationrelu), layers.Dropout(0.4), layers.Dense(num_classes, activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) return model这段代码里几个关键点Reshape层把一维特征变成(特征数, 1)的“单通道信号”Conv1D在特征维度上做卷积。BatchNormalization放在卷积之后、激活之前还是之后有讲究这里放在激活之后是Keras的常见写法但严格来说BN应该放在卷积和激活之间。GlobalAveragePooling1D替代Flatten可以减少参数量、降低过拟合。Dropout设0.4是经验值流量数据噪声大太低容易过拟合太高欠拟合。2.2 随机森林为什么接在CNN后面而不是替代它CNN最后输出的是softmax概率或者倒数第二层的128维嵌入向量。如果直接用CNN的softmax结果做分类小样本类别比如U2R只有几十条很容易被淹没。随机森林接在CNN后面有两种接法第一种把CNN当作特征提取器取倒数第二层的128维向量作为新特征训练随机森林做最终分类。第二种把CNN的softmax输出概率比如5类就是5维概率作为随机森林的输入特征再叠加原始特征一起训练。第一种更常见因为128维嵌入已经包含了CNN学到的抽象表示随机森林在这个表示空间里做决策树分裂对类别不平衡的鲁棒性比softmax强。随机森林的bagging机制和特征随机选择天然适合处理“某些类别样本极少”的情况。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report # 假设cnn_model是训练好的CNNX_train是原始特征 feature_extractor models.Model( inputscnn_model.input, outputscnn_model.layers[-3].output # 取Dropout之前的Dense层输出 ) X_train_deep feature_extractor.predict(X_train) X_test_deep feature_extractor.predict(X_test) rf RandomForestClassifier( n_estimators200, max_depth20, min_samples_split5, class_weightbalanced, n_jobs-1, random_state42 ) rf.fit(X_train_deep, y_train) y_pred rf.predict(X_test_deep) print(classification_report(y_test, y_pred))n_estimators200是起点树再多边际收益递减。max_depth20防止单棵树太深导致过拟合。class_weightbalanced是关键——NSL-KDD里U2R和R2L样本极少不加这个参数随机森林会倾向于预测多数类。n_jobs-1用满CPU核心。2.3 串行结构的信息流与梯度问题CNN和随机森林串行时CNN部分用反向传播训练随机森林部分用决策树分裂准则训练两者不共享梯度。这意味着你不能端到端地fine-tune整个pipeline。常见做法是分两阶段先单独训练CNN到收敛固定CNN参数提取嵌入特征再训练随机森林。这种两阶段训练有个隐患CNN学到的嵌入空间未必对随机森林最优。如果CNN过拟合了训练集嵌入特征在测试集上分布偏移随机森林的表现会跟着崩。缓解办法是在CNN训练时加早停和验证集监控不要等到训练准确率冲到99%才停。3. 从NSL-KDD到模型输入数据管道的四个关键步骤3.1 数据集加载与类别映射NSL-KDD的KDDTrain.txt和KDDTest.txt是标准文件每行41个特征加一个类别标签。类别有Normal、DoS、Probe、R2L、U2R五大类但原始标签有20多种细分类别需要先做映射。import pandas as pd # 列名定义NSL-KDD标准41维 col_names [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,difficulty] train_df pd.read_csv(KDDTrain.txt, namescol_names) test_df pd.read_csv(KDDTest.txt, namescol_names) # 攻击类别映射到5大类 attack_map { normal: Normal, back: DoS, land: DoS, neptune: DoS, pod: DoS, smurf: DoS, teardrop: DoS, mailbomb: DoS, apache2: DoS, processtable: DoS, udpstorm: DoS, ipsweep: Probe, nmap: Probe, portsweep: Probe, satan: Probe, mscan: Probe, saint: Probe, ftp_write: R2L, guess_passwd: R2L, imap: R2L, phf: R2L, multihop: R2L, warezmaster: R2L, warezclient: R2L, spy: R2L, xlock: R2L, xsnoop: R2L, snmpguess: R2L, snmpgetattack: R2L, httptunnel: R2L, sendmail: R2L, named: R2L, buffer_overflow: U2R, loadmodule: U2R, rootkit: U2R, perl: U2R, sqlattack: U2R, xterm: U2R, ps: U2R } train_df[label] train_df[label].map(attack_map) test_df[label] test_df[label].map(attack_map)映射表必须完整漏掉任何一个细分类别都会导致该行变成NaN。NSL-KDD的测试集里有些攻击类型在训练集里没出现过这是故意设计的用来测试模型对未知攻击的泛化能力。3.2 类别特征编码与数值归一化protocol_type、service、flag是三列类别特征需要做独热编码。service有70种取值独热编码后维度会大幅增加。数值特征用MinMaxScaler归一化到[0,1]。from sklearn.preprocessing import MinMaxScaler, OneHotEncoder from sklearn.compose import ColumnTransformer cat_cols [protocol_type, service, flag] num_cols [c for c in col_names[0:41] if c not in cat_cols] preprocessor ColumnTransformer( transformers[ (num, MinMaxScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) X_train preprocessor.fit_transform(train_df.drop([label,difficulty], axis1)) X_test preprocessor.transform(test_df.drop([label,difficulty], axis1))handle_unknownignore很重要——测试集里可能出现训练集没见过的service值忽略而不是报错。fit_transform只在训练集上做测试集用transform避免数据泄露。3.3 类别不平衡处理与样本权重NSL-KDD训练集里Normal占53%DoS占36%Probe占9%R2L和U2R加起来不到2%。直接训练模型会严重偏向多数类。除了随机森林的class_weightbalancedCNN训练时也要用class_weight。from sklearn.utils.class_weight import compute_class_weight import numpy as np classes np.unique(y_train) weights compute_class_weight(balanced, classesclasses, yy_train) class_weight_dict dict(zip(classes, weights)) # 训练CNN时传入 # cnn_model.fit(X_train, y_train, class_weightclass_weight_dict, ...)compute_class_weight的balanced模式会让少数类的权重按反比放大。U2R可能只有几十条样本权重会到几十训练时梯度会被这些样本主导。如果发现模型在Normal上准确率暴跌说明权重给得太猛了可以手动调低少数类权重。3.4 训练集/验证集划分与CNN输入形状CNN需要三维输入(样本数, 特征数, 通道数)。这里通道数设为1。from sklearn.model_selection import train_test_split X_tr, X_val, y_tr, y_val train_test_split( X_train, y_train, test_size0.2, stratifyy_train, random_state42) # 如果X_tr是稀疏矩阵先转稠密 if hasattr(X_tr, toarray): X_tr X_tr.toarray() X_val X_val.toarray() X_tr_cnn X_tr.reshape(X_tr.shape[0], X_tr.shape[1], 1) X_val_cnn X_val.reshape(X_val.shape[0], X_val.shape[1], 1)stratifyy_train保证划分后各类别比例一致。稀疏矩阵转稠密会吃内存NSL-KDD只有12万条左右120维特征转稠密大概100MB可以接受。如果是CIC-IDS2017那种几百万条的数据得用生成器分批。4. 训练、评估与调参把准确率从90%推到98%4.1 CNN训练的超参数与早停策略CNN训练最容易翻车的地方是过拟合。流量数据里Normal样本太多模型很快学会“全猜Normal”就能到50%以上准确率。早停和验证集监控是必须的。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-6) ] history cnn_model.fit( X_tr_cnn, y_tr, validation_data(X_val_cnn, y_val), epochs50, batch_size256, class_weightclass_weight_dict, callbackscallbacks, verbose1 )patience5表示验证损失连续5轮不下降就停。restore_best_weightsTrue把权重回滚到最佳轮次。ReduceLROnPlateau在验证损失停滞时降低学习率factor0.5每次减半。batch_size256在12万条数据上大概每轮470个batch训练速度适中。4.2 随机森林的调参n_estimators、max_depth与max_features随机森林的调参比CNN直观。n_estimators从100开始试到500看OOB误差或者验证集准确率。max_depth控制树深度太深过拟合太浅欠拟合。max_features是每次分裂时考虑的特征数默认是sqrt(特征数)在嵌入特征上可以调大。from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [10, 20, 30, None], max_features: [sqrt, log2, 0.5], min_samples_leaf: [1, 2, 4] } rf RandomForestClassifier(class_weightbalanced, n_jobs-1, random_state42) grid GridSearchCV(rf, param_grid, cv3, scoringf1_macro, n_jobs-1, verbose1) grid.fit(X_train_deep, y_train) print(grid.best_params_)scoring用f1_macro而不是accuracy因为类别不平衡时accuracy会骗人。cv3在12万条数据上每轮训练4万条200棵树大概几分钟。如果嫌慢先用小网格粗调再在最优附近细调。4.3 评估指标为什么accuracy会骗人NSL-KDD测试集里Normal占43%如果模型全猜Normalaccuracy也有43%。但U2R的召回率会是0。所以必须看混淆矩阵和每类的precision/recall/f1。from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt cm confusion_matrix(y_test, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsclasses, yticklabelsclasses) plt.xlabel(Predicted) plt.ylabel(True) plt.title(Confusion Matrix) plt.show() print(classification_report(y_test, y_pred, target_namesclasses))重点看U2R和R2L的召回率。如果U2R召回率低于0.5说明模型基本没学到U2R的模式。这时候要么增加U2R样本SMOTE过采样要么调整class_weight要么在CNN嵌入层加对比学习损失。4.4 模型保存与推理管道封装训练完之后要把preprocessor、CNN特征提取器、随机森林三部分一起保存推理时按顺序调用。import joblib import pickle # 保存preprocessor joblib.dump(preprocessor, preprocessor.pkl) # 保存CNN特征提取器 feature_extractor.save(cnn_feature_extractor.h5) # 保存随机森林 joblib.dump(rf, random_forest.pkl) # 推理时 def predict(raw_df): X preprocessor.transform(raw_df) if hasattr(X, toarray): X X.toarray() X_cnn X.reshape(X.shape[0], X.shape[1], 1) X_deep feature_extractor.predict(X_cnn) return rf.predict(X_deep)保存feature_extractor而不是整个CNN是因为推理时只需要嵌入向量不需要softmax层。这样加载更快也避免softmax层的数值不稳定问题。5. 避坑与排查那些让准确率一夜回到解放前的问题5.1 现象测试集准确率远低于训练集差距超过20%原因CNN过拟合。流量数据里Normal样本的模式太单一CNN在训练集上记住了这些模式但测试集里Normal的分布略有不同就崩了。解决先检查早停是否生效。如果val_loss在训练后期还在下降但test准确率不涨说明验证集和测试集分布不一致。NSL-KDD的测试集是故意设计成分布偏移的这时候要在CNN里加Dropout和L2正则化或者减少CNN层数。另一个办法是冻结CNN前几层只训练后几层。5.2 现象U2R和R2L的召回率始终为0原因类别极度不平衡U2R在训练集里只有52条随机森林的每棵树在bootstrap采样时可能一条U2R都没抽到。解决除了class_weightbalanced还要在随机森林的bootstrap采样时用stratified sampling。sklearn的RandomForestClassifier不支持直接分层但可以用imbalanced-learn的BalancedRandomForestClassifier。或者对U2R做SMOTE过采样到500条以上再训练。5.3 现象独热编码后特征维度爆炸训练极慢原因service列有70种取值独热编码后增加70维。如果再加上protocol_type和flag总维度从41涨到120。CNN的参数量随输入维度线性增长。解决对service列做目标编码target encoding或者频率编码把70维压缩到1维。或者用Embedding层代替独热编码把每个类别映射到低维稠密向量。Embedding层在Keras里就是layers.Embedding(input_dim70, output_dim8)8维比70维省太多。5.4 现象随机森林的feature_importances_全是零原因CNN嵌入特征已经是稠密向量每个维度都是原始特征的线性组合随机森林在嵌入空间里做分裂时单个维度的区分度不高。解决这是正常现象不代表模型有问题。如果非要看特征重要性应该在原始特征上训练一个单独的随机森林做对比而不是在CNN嵌入上。或者用SHAP值分析CNN嵌入的每个维度对最终预测的贡献。5.5 现象推理时单条样本预测结果和批量预测不一致原因CNN的BatchNormalization层在推理时用的是移动平均的均值和方差如果批量预测时batch_size和训练时差异大BN的统计量会偏移。解决推理时固定batch_size或者把BN层换成LayerNormalization。另一个常见原因是preprocessor的transform在单条样本上行为不同比如MinMaxScaler在单条样本上会除以零。确保推理时至少传一个batch哪怕batch_size1也要保持二维形状。6. 把CNN嵌入喂给随机森林之外三个值得试的进阶方向第一个方向是用一维CNN直接处理原始流量字节序列跳过特征工程。CIC-IDS2017提供了pcap文件可以把每个流的前N个字节作为输入CNN直接学字节级模式。这比手工特征更接近端到端但需要处理变长序列和字节对齐问题。我试过用TextCNN的结构处理字节序列在CIC-IDS2017上F1能到0.95左右但训练时间比特征工程方案长三倍。第二个方向是把随机森林换成LightGBM或XGBoost。随机森林的bagging对少数类不够友好boosting会逐步聚焦难分类样本。在CNN嵌入上跑LightGBMU2R的召回率通常能比随机森林高5到10个百分点。代价是调参更麻烦学习率和叶子数要仔细调。第三个方向是给CNN加注意力机制。流量特征里不同维度的重要性差异很大比如src_bytes和dst_bytes通常比urgent重要。在CNN后面加一个SE block或者Self-Attention层让模型自己学每个维度的权重。我一般会在GlobalAveragePooling之前加一个通道注意力模块参数量增加不到5%但Probe类的召回率能涨3个点左右。验证方法上我习惯用分层K折交叉验证而不是单次划分。NSL-KDD的测试集分布偏移太严重单次划分的方差很大。5折分层交叉验证能给出更稳定的评估虽然训练时间乘以5但调参时值得。如果时间紧至少跑3折。最后说个血泪教训不要等到答辩前一天才跑完整流程。CNN训练加随机森林调参在普通笔记本上至少需要两三个小时。如果中途发现数据加载有bug或者类别映射漏了重跑一遍时间根本不够。我一般会在第一天就把数据管道跑通保存中间结果后面调参只动模型部分。希望帮到你。本文还有配套的精品资源点击获取