多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

NSL-KDD与KDDCup双数据集评估:网络入侵检测模型泛化能力实战

NSL-KDD与KDDCup双数据集评估:网络入侵检测模型泛化能力实战 简介这份资源面向计算机、网络安全相关专业的本科生与研究生以及需要完成课程设计或期末大作业的学习者核心是用NSL-KDD数据集训练一个网络入侵检测模型并借助KDDCup与NSL-KDD两个数据集完成模型评估属于可直接复现的满分项目方案。压缩包共27个文件约29.98MB包含10个csv数据文件、7个txt说明、4个ipynb实验笔记、3个m脚本、1个py脚本及md、docx文档覆盖数据处理、建模、评估全流程。已有403人学习下载。资源内含带注释的代码与多份Notebook分别演示无PCA与含PCA的建模路径并配有KDDCup数据读取与评估脚本便于理解特征工程、降维与指标对比。目录结构清晰部署简单新手也能看懂适合作为课程设计、期末大作业或入门网络入侵检测的实践参考。1. 从 NSL-KDD 到 KDDCup一个入侵检测大作业为什么值得认真做很多人做网络安全方向的大作业第一反应是找个现成模型跑一遍 NSL-KDD看到准确率 99% 就交差。但真正做过的人都知道这个数据集上的高准确率是个陷阱——它更多反映的是数据分布本身容易分而不是模型真的学到了入侵行为的本质。NSL-KDD 作为 KDDCup99 的修正版去掉了大量冗余记录平衡了训练集和测试集的难度但它依然保留了 41 维特征和 5 大类标签Normal、DoS、Probe、R2L、U2R。用 NSL-KDD 训练一个网络入侵检测模型再用 KDDCup 和 NSL-KDD 分别做评估核心价值在于让你亲眼看到同一个模型换一个数据分布性能会掉成什么样。这件事适合已经学过机器学习基础、想真正理解「模型评估与选择」到底在评什么的人。下面我从数据准备、模型训练、双数据集评估、踩坑排查到进阶技巧把这条链路完整走一遍。2. 数据准备NSL-KDD 的 41 维特征怎么读、怎么转、怎么切2.1 NSL-KDD 与 KDDCup 的文件结构和标签体系NSL-KDD 的标准分发一般包含这几个文件KDDTrain.txt、KDDTest.txt以及对应的 20% 子集。KDDCup99 的原始文件通常是 kddcup.data_10_percent 和 kddcup.data.corrected。两者格式一致每行 41 个特征加 1 个标签逗号分隔没有表头。标签分两类写法一种是具体的攻击名如 neptune、smurf、portsweep另一种是映射后的 5 大类。做多分类时用具体攻击名做二分类或五分类时先做映射。常见做法是先定义一个攻击名到五大类的映射字典。这一步不能省因为 NSL-KDD 的 KDDTest 里有很多训练集没出现过的攻击子类如果直接按具体攻击名做标签编码测试集会出现大量未见类别评估结果会失真。# 攻击名到五大类的映射覆盖 NSL-KDD 和 KDDCup 常见攻击 attack_map { normal: Normal, # DoS back: DoS, land: DoS, neptune: DoS, pod: DoS, smurf: DoS, teardrop: DoS, apache2: DoS, udpstorm: DoS, processtable: DoS, worm: DoS, mailbomb: DoS, # Probe ipsweep: Probe, nmap: Probe, portsweep: Probe, satan: Probe, mscan: Probe, saint: Probe, # R2L ftp_write: R2L, guess_passwd: R2L, imap: R2L, multihop: R2L, phf: R2L, spy: R2L, warezclient: R2L, warezmaster: R2L, snmpgetattack: R2L, named: R2L, xlock: R2L, xsnoop: R2L, snmpguess: R2L, httptunnel: R2L, sendmail: R2L, # U2R buffer_overflow: U2R, loadmodule: U2R, perl: U2R, rootkit: U2R, ps: U2R, sqlattack: U2R, xterm: U2R }这段映射的逻辑是把细粒度攻击名归并到 NSL-KDD 论文定义的五大类。参数上注意KDDCup 里有些攻击名和 NSL-KDD 不完全一致比如 KDDCup 的 corrected 文件里有 snmpgetattack 但训练集里没有映射时要统一处理遇到未在字典中的标签直接归为 Unknown 或丢弃不要硬编码。2.2 类别特征编码与数值特征归一化41 维里有 3 个是符号特征protocol_typetcp/udp/icmp、servicehttp/ftp/smtp 等 70 种、flagSF/S0/REJ 等 11 种。这三个必须做编码。常见做法有两种一是独热编码维度会从 41 膨胀到 122 左右二是标签编码加嵌入但树模型对标签编码不敏感。我一般用独热编码配合树模型因为可解释性好排查问题时能直接看特征重要性。数值特征里src_bytes、dst_bytes、duration 这些跨度极大从 0 到上亿。归一化用 MinMaxScaler 或 StandardScaler 都行但要注意scaler 只能在训练集上 fit然后 transform 测试集。如果先把训练集和测试集合并再 fit就是典型的数据泄漏评估结果会虚高。import pandas as pd from sklearn.preprocessing import MinMaxScaler, OneHotEncoder from sklearn.compose import ColumnTransformer # 读取无表头41 特征 1 标签 cols [ff{i} for i in range(41)] [label] train pd.read_csv(KDDTrain.txt, namescols) test pd.read_csv(KDDTest.txt, namescols) # 标签映射到五大类 train[category] train[label].map(attack_map).fillna(Unknown) test[category] test[label].map(attack_map).fillna(Unknown) # 符号特征列 cat_cols [f1, f2, f3] # protocol_type, service, flag num_cols [c for c in cols if c not in cat_cols [label]] # 只在训练集 fit preprocessor ColumnTransformer([ (num, MinMaxScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) X_train preprocessor.fit_transform(train) X_test preprocessor.transform(test)这里的关键参数是 OneHotEncoder 的 handle_unknownignore。NSL-KDD 测试集里会出现训练集没有的 service 值如果不设这个参数transform 时会直接报错。MinMaxScaler 对异常值敏感如果发现某个数值特征有极端离群点可以先做对数变换再归一化。2.3 训练集与测试集的切分策略NSL-KDD 官方已经给了 KDDTrain 和 KDDTest直接拿来用就行。但如果你想更严谨可以从 KDDTrain 里再切 20% 做验证集调参最后用 KDDTest 做最终评估。KDDCup 的评估则用 kddcup.data.corrected 作为测试集或者用 10_percent 训练、corrected 测试。注意 KDDCup 的 corrected 里包含大量 NSL-KDD 训练集没有的攻击类型这正是检验模型泛化能力的点。提示不要用 KDDTest 调参后再用 KDDTest 报告结果那样等于把测试集当验证集用。正确顺序是训练集训练、验证集选模型、测试集只评一次。3. 模型训练从随机森林到 XGBoost 的选型与参数设置3.1 为什么树模型在 NSL-KDD 上通常优于深度模型NSL-KDD 只有 12 万条训练样本、41 维特征且特征里有大量离散值和类别不平衡。这种规模下随机森林和 XGBoost 这类集成树模型往往比多层感知机更稳。原因有三一是树模型对特征尺度不敏感归一化做不做影响不大二是树模型能自动处理特征交互比如 flagS0 和 count 高的组合往往指向 DoS三是训练快调参成本低。深度模型在这个数据量上容易过拟合尤其是 U2R 和 R2L 样本极少神经网络很容易全部预测成 Normal。我一般先用随机森林跑一个 baseline确认数据管道没问题再上 XGBoost 或 LightGBM 做提分。如果非要试深度模型建议用一维卷积或简单的全连接加 Dropout并且一定要做类别权重平衡。3.2 随机森林 baseline 的完整训练代码from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix # 训练随机森林n_estimators 先设 100max_depth 不限制 rf RandomForestClassifier( n_estimators100, max_depthNone, min_samples_split2, class_weightbalanced, # 缓解类别不平衡 random_state42, n_jobs-1 ) rf.fit(X_train, train[category]) # 在 NSL-KDD 测试集上评估 y_pred_nsl rf.predict(X_test) print(classification_report(test[category], y_pred_nsl)) print(confusion_matrix(test[category], y_pred_nsl))class_weightbalanced 的作用是按类别频率反比给权重让 U2R 和 R2L 这些少数类在分裂时不被忽略。n_estimators 设 100 是起点如果发现验证集准确率还在涨可以加到 200 或 300但超过 300 后收益递减明显。min_samples_split 默认 2如果过拟合严重可以调到 5 或 10。3.3 XGBoost 多分类的参数调优与早停XGBoost 在多分类上通常比随机森林高 1 到 2 个百分点尤其是 R2L 的召回率。关键参数有n_estimators、max_depth、learning_rate、subsample、colsample_bytree。我一般用早停法确定 n_estimators避免手动试。import xgboost as xgb from sklearn.preprocessing import LabelEncoder # 标签编码 le LabelEncoder() y_train_enc le.fit_transform(train[category]) y_test_enc le.transform(test[category]) # 从训练集切 20% 做验证集用于早停 from sklearn.model_selection import train_test_split X_tr, X_val, y_tr, y_val train_test_split( X_train, y_train_enc, test_size0.2, random_state42, stratifyy_train_enc ) model xgb.XGBClassifier( objectivemulti:softmax, num_class5, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, eval_metricmlogloss, early_stopping_rounds20, random_state42 ) model.fit( X_tr, y_tr, eval_set[(X_val, y_val)], verboseFalse ) # 用最佳迭代次数在 NSL-KDD 测试集上预测 y_pred_xgb model.predict(X_test)max_depth6 是 NSL-KDD 上的经验值再深容易过拟合少数类。learning_rate0.1 配合 early_stopping_rounds20 是比较稳的组合。subsample 和 colsample_bytree 设 0.8 是为了增加随机性、降低方差。注意 early_stopping_rounds 需要验证集不能直接用测试集。3.4 类别不平衡的处理过采样、欠采样与代价敏感NSL-KDD 训练集里 Normal 约 6.7 万DoS 约 4.6 万Probe 约 1.1 万R2L 约 1000U2R 只有 52。这种极端不平衡下模型很容易把 R2L 和 U2R 全预测错。常见做法有三种一是 SMOTE 过采样少数类但要注意 SMOTE 只能在训练集上做且对高维稀疏的独热特征效果一般二是随机欠采样多数类会丢信息三是用 class_weight 或 scale_pos_weight 做代价敏感。我一般先试 class_weight不行再上 SMOTE并且用 imblearn 的 Pipeline 确保采样只发生在训练折内。4. 双数据集评估KDDCup 和 NSL-KDD 上的指标差异与解读4.1 评估指标不能只看准确率在入侵检测里准确率是最容易骗人的指标。因为 Normal 样本占多数一个全预测 Normal 的模型在 NSL-KDD 测试集上也能有 40% 多的准确率。真正要看的是每个类别的召回率和 F1尤其是 U2R 和 R2L。另外二分类视角下要看检测率Recall和误报率False Positive Rate。我一般会输出一张按类别分组的 classification_report再单独算宏平均 F1 和加权平均 F1。from sklearn.metrics import f1_score, recall_score # 宏平均 F1各类别等权能反映少数类表现 macro_f1 f1_score(test[category], y_pred_xgb, averagemacro) # 加权平均 F1按样本数加权反映整体 weighted_f1 f1_score(test[category], y_pred_xgb, averageweighted) # 各类别召回率 recall_per_class recall_score(test[category], y_pred_xgb, averageNone, labelsle.classes_) print(dict(zip(le.classes_, recall_per_class)))宏平均 F1 和加权平均 F1 的差距越大说明类别不平衡越严重、少数类拖后腿越明显。如果宏平均 F1 只有 0.5 而加权 F1 有 0.95那模型基本没学到少数类。4.2 在 KDDCup 上评估时的标签对齐问题KDDCup 的 corrected 文件里有很多 NSL-KDD 训练集没有的攻击名比如 snmpgetattack、httptunnel、named。如果直接用 NSL-KDD 训练好的 LabelEncoder 去 transform会报未见标签错误。正确做法是先用同一套 attack_map 把 KDDCup 的标签映射到五大类遇到映射不到的归为 Unknown评估时可以选择忽略 Unknown 或单独报告。另外KDDCup 的 corrected 里 Normal 比例和 NSL-KDD 不同直接比较准确率意义不大重点看各类别召回率的变化。# 读取 KDDCup corrected kddcup pd.read_csv(kddcup.data.corrected, namescols) kddcup[category] kddcup[label].map(attack_map).fillna(Unknown) # 用同一个 preprocessor transform注意 handle_unknownignore 已设 X_kddcup preprocessor.transform(kddcup) # 用 NSL-KDD 训练的模型预测 y_pred_kddcup model.predict(X_kddcup) # 只评估五大类忽略 Unknown mask kddcup[category] ! Unknown print(classification_report( kddcup.loc[mask, category], le.inverse_transform(y_pred_kddcup)[mask] ))这里有个细节le.inverse_transform 把预测的整数标签转回字符串再和真实标签对比。如果 KDDCup 里某类样本数为 0classification_report 会报警告可以加 zero_division0 参数。4.3 两个数据集上的性能对比表与差异归因跑完两个数据集后建议做一张对比表按类别列出召回率和 F1。典型结果是NSL-KDD 上 DoS 和 Probe 的 F1 能到 0.95 以上R2L 和 U2R 只有 0.3 到 0.6换到 KDDCup 后DoS 和 Probe 可能还稳但 R2L 会进一步掉因为 KDDCup 里 R2L 的攻击模式更多样。这个差异不是模型不行而是数据分布偏移。NSL-KDD 的测试集虽然去掉了冗余但攻击类型和训练集重叠度高KDDCup corrected 则故意引入了新攻击考的是泛化。类别NSL-KDD 召回率KDDCup 召回率差异原因Normal0.950.90分布接近DoS0.950.85KDDCup 有新的 DoS 子类Probe0.900.80扫描模式变化R2L0.30-0.600.10-0.40新攻击多样本少U2R0.20-0.500.05-0.30样本极少泛化难这张表的意义在于如果你的大作业只报 NSL-KDD 准确率等于只展示了模型在「见过类似攻击」时的表现。加上 KDDCup 评估才能说明模型面对未知攻击时的真实能力边界。5. 避坑与排查入侵检测大作业里最容易翻车的 5 个点5.1 现象测试集准确率 99%但 U2R 召回率为 0原因U2R 在训练集只有 52 条模型直接学会了全部预测成 Normal 或 DoS因为这样整体损失最小。解决加 class_weightbalanced或者对 U2R 单独做 SMOTE 过采样再不行就二分类先分 Normal/Attack再在 Attack 里做多分类。5.2 现象KDDCup 评估时报 ValueError: y contains previously unseen labels原因KDDCup 的标签里有 NSL-KDD 训练集没出现的攻击名LabelEncoder 没见过。解决不要用 LabelEncoder 直接 transform KDDCup 标签而是用同一套 attack_map 先映射到五大类映射不到的归 Unknown 并排除评估。5.3 现象归一化后模型性能反而下降原因把训练集和测试集合并做 fit或者对已经独热编码的 0/1 特征又做了标准化。解决scaler 只在训练集 fit独热后的 0/1 列不要再归一化ColumnTransformer 里只对数值列做缩放。5.4 现象随机森林训练极慢内存爆掉原因n_estimators 设太大或者 max_depth 不限制导致树无限生长加上 n_jobs-1 开满核。解决先设 n_estimators100、max_depth20 跑通再看验证集曲线决定是否加树。内存不够就把 n_jobs 降到 4。5.5 现象两个数据集上评估结果几乎一样没有差异原因很可能 KDDCup 的标签映射错了或者 preprocessor 对 KDDCup 做了 fit 而不是 transform导致数据泄漏。解决检查 preprocessor 是否只在训练集 fit检查 KDDCup 的 category 分布确认 R2L 和 U2R 的样本数是否合理。6. 进阶技巧用混淆矩阵和特征重要性定位模型的真实短板跑通基础流程后真正拉开差距的是对模型错误的分析。我一般做两件事一是画归一化混淆矩阵看错分集中在哪二是看特征重要性确认模型有没有学到不该学的东西。混淆矩阵不要只看绝对值要看按行归一化的版本。比如 R2L 有 1000 条其中 600 条被预测成 Normal那召回率就是 0.4比看总数直观得多。用 sklearn 的 confusion_matrix 加 normalizetrue 就能得到。import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm confusion_matrix(test[category], y_pred_xgb, labelsle.classes_, normalizetrue) plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmt.2f, xticklabelsle.classes_, yticklabelsle.classes_, cmapBlues) plt.xlabel(Predicted) plt.ylabel(True) plt.title(NSL-KDD Normalized Confusion Matrix) plt.show()特征重要性方面随机森林的 feature_importances_ 和 XGBoost 的 get_score 都能看。如果发现 src_bytes 或 dst_bytes 重要性异常高要警惕这两个特征在 DoS 和 Normal 上差异大但在 R2L 上区分度低模型可能过度依赖它们。这时候可以试着去掉这两个特征重训看少数类召回率是否提升。另一个进阶点是做跨数据集的特征对齐验证。NSL-KDD 和 KDDCup 的 41 维特征定义一致但取值范围可能不同。可以分别统计两个数据集上每个数值特征的均值和方差如果某个特征差异超过一个数量级说明分布偏移严重模型在这个特征上学到的阈值在另一个数据集上会失效。这时候可以考虑做分位数归一化或者用对抗验证判断哪些样本来自哪个数据集。我自己的习惯是每次跑完模型先看混淆矩阵再看特征重要性最后做一次跨数据集的特征分布对比。这三步做完基本能定位 80% 的性能问题。大作业里如果能把这三步写进报告比单纯堆模型分数更有说服力。希望帮到你。本文还有配套的精品资源点击获取
返回列表