ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

NSL-KDD入侵检测大作业:双数据集评估与模型融合实战

2026/10/2 21:17:46 拓冰建站 浏览量
NSL-KDD入侵检测大作业:双数据集评估与模型融合实战 简介这份资源面向计算机、网络安全相关专业的课程设计与期末大作业场景提供一套基于NSL-KDD数据集训练网络入侵检测模型的完整方案并额外使用KDDCup与NSL-KDD两个数据集进行模型评估适合新手对照学习与直接部署。压缩包共27个文件约29.98MB包含10个csv数据文件、7个txt说明、4个ipynb实验笔记、3个m脚本、1个py脚本及md、docx文档覆盖数据处理、建模、评估全流程。资源中既有带PCA与不带PCA的建模对比也有KDDCup数据读取与修正脚本以及针对两个数据集的评估笔记代码注释较为完整便于理解特征工程与模型调参思路。目前已有403人学习下载可作为课程设计或大作业的参考模板帮助读者快速搭建入侵检测实验环境、复现评估结果并整理报告素材。1. 大作业选 NSL-KDD 做入侵检测为什么这套数据组合至今仍是性价比最高的方案如果你正在为课程大作业找一个既能体现完整机器学习流程、又不会在数据清洗上无限翻车的题目NSL-KDD 训练网络入侵检测模型并用 KDDCup 与 NSL-KDD 双数据集评估几乎是目前最稳妥的选择。原因很直接NSL-KDD 去掉了 KDDCup99 中大量冗余记录训练集和测试集分布更合理不会出现模型在训练集上准确率 99% 但换一份数据就崩到 60% 的尴尬。同时它保留了完整的 41 维特征和 5 大类攻击标签足够你展示特征工程、类别不平衡处理、多分类建模和跨数据集泛化评估的全链路能力。适合谁适合已经学过 sklearn 基础、想拿一个能写进简历的完整项目、但不想在数据采集和标注上耗掉两周的人。这一章先把数据集的本质和评估逻辑讲清楚后面几章直接给可复现的代码和参数。NSL-KDD 的核心价值在于它修正了 KDDCup99 的两个致命问题一是原始 KDDCup99 训练集包含大量重复记录导致模型偏向高频攻击类型二是测试集和训练集分布差异过大评估结果不可信。NSL-KDD 通过去重和重新划分让训练集 125973 条、测试集 22544 条且测试集中包含了训练集未出现的攻击子类型这恰好模拟了真实场景中「已知攻击训练、未知攻击检测」的需求。所以你的大作业如果只在一个数据集上跑出高准确率说服力有限用 KDDCup 和 NSL-KDD 交叉评估才能体现模型的泛化边界。常见做法是用 NSL-KDD 训练集训练分别在 NSL-KDD 测试集和 KDDCup99 测试集上评估观察准确率、召回率和 F1 的下降幅度下降越小说明模型学到的特征越本质。2. 把 NSL-KDD 的 41 维特征喂进模型前字段映射与预处理流水线2.1 先搞清楚 41 维里哪些是数值、哪些是类别、哪些是坑NSL-KDD 的 41 个特征分为四组基本 TCP 连接特征如 duration、protocol_type、service、flag、内容特征如 logged_in、num_failed_logins、流量统计特征如 count、srv_count和主机统计特征如 dst_host_count、dst_host_srv_count。其中 protocol_type、service、flag 是字符串类别特征label 是攻击类型difficulty 是难度分级训练时通常丢弃。很多新手直接把字符串丢进 sklearn 的模型里结果报错或效果极差。正确做法是先做标签编码或独热编码但 service 有 70 种取值独热后维度爆炸我一般用 pandas 的 category 编码配合 LightGBM 或 CatBoost省去独热步骤。import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder # 列名定义NSL-KDD 官方提供 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 pd.read_csv(KDDTrain.txt, namescol_names) test pd.read_csv(KDDTest.txt, namescol_names) # 攻击类型映射到 5 大类 attack_map { normal:normal, back:DoS,land:DoS,neptune:DoS,pod:DoS,smurf:DoS,teardrop:DoS, apache2:DoS,udpstorm:DoS,processtable:DoS,worm:DoS, ipsweep:Probe,nmap:Probe,portsweep:Probe,satan:Probe,mscan:Probe,saint:Probe, ftp_write:R2L,guess_passwd:R2L,imap:R2L,multihop:R2L,phf:R2L,spy:R2L, warezclient:R2L,warezmaster:R2L,sendmail:R2L,named:R2L,snmpgetattack:R2L, snmpguess:R2L,xlock:R2L,xsnoop:R2L,httptunnel:R2L, buffer_overflow:U2R,loadmodule:U2R,perl:U2R,rootkit:U2R,ps:U2R, sqlattack:U2R,xterm:U2R } train[attack_class] train[label].map(attack_map) test[attack_class] test[label].map(attack_map) # 类别特征编码 cat_cols [protocol_type,service,flag] for col in cat_cols: le LabelEncoder() full pd.concat([train[col], test[col]]) le.fit(full) train[col] le.transform(train[col]) test[col] le.transform(test[col]) # 丢弃 difficulty 和原始 label train train.drop([difficulty,label], axis1) test test.drop([difficulty,label], axis1)这段代码的关键点有三个第一列名必须按官方顺序写否则特征错位第二attack_map 把 39 种攻击细类归到 5 大类这是 NSL-KDD 论文的标准做法大作业里必须做否则类别太多模型学不动第三LabelEncoder 要在训练集和测试集合并后 fit保证编码一致否则测试集出现训练集没见过的 service 值会报错。参数上如果你用树模型LabelEncoder 足够如果用神经网络建议对 service 做 embedding 或独热后降维。2.2 处理类别不平衡U2R 只有 52 条别让模型直接忽略NSL-KDD 训练集中 normal 有 67343 条DoS 有 45927 条Probe 有 11656 条R2L 有 995 条U2R 只有 52 条。如果直接训练模型会把所有 U2R 预测成 normal准确率依然很高但召回率为 0。常见做法是分层采样加类别权重或者用 SMOTE 过采样。我一般用 LightGBM 的 class_weight 参数简单且不易过拟合。from sklearn.utils.class_weight import compute_class_weight import lightgbm as lgb # 计算类别权重 classes np.unique(train[attack_class]) weights compute_class_weight(balanced, classesclasses, ytrain[attack_class]) class_weight_dict dict(zip(classes, weights)) # 训练 LightGBM 多分类模型 X_train train.drop(attack_class, axis1) y_train train[attack_class] X_test test.drop(attack_class, axis1) y_test test[attack_class] model lgb.LGBMClassifier( objectivemulticlass, num_class5, class_weightclass_weight_dict, n_estimators300, learning_rate0.05, num_leaves31, max_depth-1, min_child_samples20, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train)参数说明n_estimators300 是树的数量大作业里 200 到 500 都合理learning_rate0.05 配合 300 棵树收敛稳定num_leaves31 控制模型复杂度太大容易过拟合class_weight 直接传入字典LightGBM 会自动加权损失函数。训练完后用 model.predict 和 model.predict_proba 分别拿标签和概率概率用于后续画 ROC 曲线。3. 双数据集评估KDDCup 和 NSL-KDD 上到底该看哪些指标3.1 准确率会骗人混淆矩阵和宏平均 F1 才是大作业的得分点很多同学只报一个 accuracy结果老师一问 U2R 召回率就露馅。正确的评估流程是在 NSL-KDD 测试集上算一遍再把 KDDCup99 测试集按同样的预处理流程跑一遍对比两个数据集上的 macro-F1 和各类召回率。KDDCup99 测试集可以从原始 KDDCup99 数据中按 NSL-KDD 的划分逻辑重建或者直接用 NSL-KDD 提供的 KDDTest 作为 NSL-KDD 测试集另外下载 KDDCup99 的 corrected 测试集作为跨数据集评估。注意KDDCup99 的 corrected 测试集包含大量 NSL-KDD 训练集未出现的攻击正好用来测泛化。from sklearn.metrics import classification_report, confusion_matrix, f1_score # NSL-KDD 测试集评估 y_pred_nsl model.predict(X_test) print(NSL-KDD 测试集评估) print(classification_report(y_test, y_pred_nsl, digits4)) print(Macro-F1:, f1_score(y_test, y_pred_nsl, averagemacro)) # 假设 kddcup_test 是已经按相同流水线预处理好的 KDDCup99 测试集 # y_pred_kdd model.predict(kddcup_test.drop(attack_class, axis1)) # print(KDDCup99 测试集评估) # print(classification_report(kddcup_test[attack_class], y_pred_kdd, digits4))classification_report 会输出每个类别的 precision、recall、f1-score 和 support。重点看 U2R 和 R2L 的召回率如果低于 0.5说明类别不平衡处理不够。macro-F1 比 weighted-F1 更适合大作业因为它对少数类一视同仁。混淆矩阵可以帮你定位模型把 U2R 误判成了哪一类常见的是误判成 normal 或 R2L。3.2 跨数据集评估的预处理对齐一个列错位就能让准确率掉 30%跨数据集评估最大的坑是预处理不一致。KDDCup99 原始数据的列名和 NSL-KDD 完全一样但类别特征的取值集合可能不同。比如 NSL-KDD 的 service 有 70 种KDDCup99 可能多出几种。如果你在 NSL-KDD 上 fit 了 LabelEncoder直接 transform KDDCup99 会报错。正确做法是把两个数据集的类别特征合并后重新 fit或者用 pandas 的 cat 类型并指定 categories。我一般写一个 preprocess 函数传入训练集和测试集内部合并 fit返回对齐后的数据。def preprocess(train_df, test_df, cat_cols, attack_map): train_df train_df.copy() test_df test_df.copy() train_df[attack_class] train_df[label].map(attack_map) test_df[attack_class] test_df[label].map(attack_map) for col in cat_cols: le LabelEncoder() full pd.concat([train_df[col], test_df[col]]) le.fit(full) train_df[col] le.transform(train_df[col]) test_df[col] le.transform(test_df[col]) train_df train_df.drop([difficulty,label], axis1) test_df test_df.drop([difficulty,label], axis1) return train_df, test_df这个函数保证训练集和测试集的编码空间一致。如果你用 KDDCup99 的 corrected 测试集它的 label 列可能叫别的名字需要先重命名。另外KDDCup99 的 corrected 测试集里有些攻击类型在 NSL-KDD 的 attack_map 里没有比如 snmpgetattack 在 R2L 里但 httptunnel 也在 R2L检查你的映射表是否覆盖全。4. 避坑与排查训练集准确率 99% 但测试集崩了怎么办4.1 现象NSL-KDD 测试集准确率只有 75%但训练集 99%原因NSL-KDD 测试集包含训练集未出现的攻击子类比如训练集没有 mscan 和 saint测试集有。模型没见过这些模式只能靠泛化。解决不要盲目调参先看混淆矩阵确认是哪些类被误判。如果是 Probe 类召回率低可以增加 Probe 的样本权重或者用异常检测思路把未知攻击当作异常。我一般会保留一个 baseline 模型用随机森林跑一遍如果随机森林也是 75% 左右说明数据本身难度就这样不用怀疑代码。4.2 现象U2R 召回率始终为 0原因U2R 只有 52 条且特征和 normal 高度重叠。解决用 SMOTE 对 U2R 过采样到 500 条或者用 focal loss 替代交叉熵。LightGBM 里可以设置 scale_pos_weight但多分类下不如 class_weight 直接。另一个技巧是二阶段分类先分 normal 和 attack再在 attack 里细分四类这样 U2R 的召回率会提升。4.3 现象KDDCup99 测试集上准确率骤降到 60%原因KDDCup99 的 corrected 测试集分布和 NSL-KDD 训练集差异大且包含大量 R2L 和 U2R 攻击。解决这是正常现象跨数据集评估本来就会下降。你可以在报告里分析下降原因比如特征分布偏移而不是强行调参刷高。如果非要提升可以用领域自适应但大作业里不要求。4.4 现象LabelEncoder 报错 y contains previously unseen labels原因测试集出现了训练集没有的类别取值。解决用 preprocess 函数合并 fit或者用 pandas 的 cat 类型并设置 categories 为训练集的取值测试集未知的归为 unknown。我一般用合并 fit简单且不会丢信息。4.5 现象模型训练时间过长LightGBM 跑 300 棵树要 10 分钟原因num_leaves 太大或数据量太大。解决把 num_leaves 降到 31或者用 histogram 算法LightGBM 默认就是 histogram。另外可以设置 max_bin255减少内存。如果还是慢用 XGBoost 的 hist 模式或者直接上 CatBoost但 CatBoost 对类别特征更友好训练也更快。5. 把大作业从及格推到满分模型融合与可解释性分析5.1 用 Stacking 融合 LightGBM、随机森林和 XGBoostmacro-F1 再涨 3 个点单模型调参到瓶颈后融合是性价比最高的提升手段。我一般用 Stacking基模型选 LightGBM、随机森林和 XGBoost元模型用逻辑回归。注意基模型要输出概率元模型用概率作为特征。代码上用 sklearn 的 StackingClassifiercv5避免过拟合。from sklearn.ensemble import StackingClassifier, RandomForestClassifier from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier base_models [ (lgb, lgb.LGBMClassifier(objectivemulticlass, num_class5, n_estimators300, learning_rate0.05, num_leaves31, random_state42)), (rf, RandomForestClassifier(n_estimators300, max_depth20, class_weightbalanced, random_state42, n_jobs-1)), (xgb, XGBClassifier(objectivemulti:softmax, num_class5, n_estimators300, learning_rate0.05, max_depth6, random_state42, use_label_encoderFalse, eval_metricmlogloss)) ] stack StackingClassifier(estimatorsbase_models, final_estimatorLogisticRegression(max_iter1000), cv5, n_jobs-1) stack.fit(X_train, y_train) y_pred_stack stack.predict(X_test) print(Stacking Macro-F1:, f1_score(y_test, y_pred_stack, averagemacro))参数说明cv5 表示 5 折交叉验证生成元特征n_jobs-1 用满 CPU。注意 XGBoost 的 use_label_encoder 在新版本已弃用如果报错就删掉。融合后如果 macro-F1 提升不明显检查基模型是否多样性不足比如随机森林和 LightGBM 都是树模型可以换成 KNN 或朴素贝叶斯增加多样性。5.2 用 SHAP 解释模型为什么把某条记录判成 U2R大作业里加一节可解释性分析老师会觉得你不仅会调包还懂模型。SHAP 可以输出每个特征对预测的贡献值。对 U2R 类重点看 num_root、root_shell、su_attempted 这几个特征。如果 SHAP 显示这些特征贡献高说明模型学到了有意义的模式如果贡献分散说明模型在瞎猜。import shap # 用 LightGBM 模型做 SHAP 解释 explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test.iloc[:500]) # 对 U2R 类假设索引为 4画 summary plot shap.summary_plot(shap_values[4], X_test.iloc[:500], feature_namesX_test.columns)SHAP 计算量较大建议只取测试集前 500 条。summary_plot 会显示每个特征对 U2R 预测的影响方向和大小。如果 num_root 值越高越容易判成 U2R符合直觉。把这张图放进报告比堆一堆准确率数字更有说服力。5.3 一个我踩过的坑别在测试集上调参血泪经验我曾经为了刷高 NSL-KDD 测试集准确率反复调整 num_leaves 和 learning_rate结果在 KDDCup99 测试集上崩得更厉害。后来才明白测试集只能看一次调参要用验证集。正确做法是从训练集里切 20% 做验证集用验证集调参最后在测试集上跑一次。这样得到的跨数据集评估才可信。另外随机种子要固定否则每次跑的结果不一样报告里没法复现。我一般设 random_state42并在报告里写明。希望帮到你。本文还有配套的精品资源点击获取