UNSW-NB15网络攻击检测毕设实战:从数据预处理到模型部署
发布时间:2026/9/29 14:08:14来源:尧图网络
简介这份资源面向计算机、人工智能、通信工程等专业的在校学生与教师提供一套基于 UNSW-NB15 数据集的网络攻击检测机器学习完整实现适合用作毕业设计、课程设计或大作业也便于初学者进阶学习。压缩包共 4 个文件包含 3 个 Python 脚本与 1 个 Markdown 说明文档整体约 12KB体积轻巧、部署简单下载后按说明即可运行。脚本分别实现了决策树二分类器、逻辑回归二分类与 KNN 分类器覆盖从数据加载、特征处理到模型训练与攻击识别的完整流程可帮助读者快速理解不同算法在入侵检测任务上的表现差异。目前已有 143 人学习下载代码均经测试运行成功拿来即用也可在此基础上修改扩展实现更多功能是入门网络安全与机器学习实战的实用参考。1. 从一份能跑起来的 UNSW-NB15 毕设包说起网络攻击检测到底在做什么如果你正在找一份能直接跑通、不用从零造数据的网络攻击检测项目UNSW-NB15 这个数据集大概率已经出现在你的候选清单里。它由澳大利亚网络安全中心ACCS在 2015 年发布用真实网络流量模拟了九类攻击行为包括 Fuzzers、Analysis、Backdoors、DoS、Exploits、Generic、Reconnaissance、Shellcode 和 Worms同时混入大量正常流量。相比 KDD99 和 NSL-KDD 那两个老数据集UNSW-NB15 的特征维度更贴近现代网络环境流量分布也更均衡所以近几年做机器学习检测、入侵识别、毕业设计的同学几乎绕不开它。这份资源包的核心价值在于它把数据预处理、特征工程、模型训练、评估对比这一整条链路都封装好了你拿到手不需要自己去啃原始 CSV 的字段含义也不用纠结怎么把类别标签转成数值。包里的源码覆盖了常见的机器学习算法——决策树、随机森林、XGBoost、SVM、朴素贝叶斯甚至可能包含简单的神经网络实现。对于课程设计或者毕业设计来说这种“开箱即跑”的完整度能省掉大量调试环境的时间。适合谁用如果你是计算机、网络安全、数据科学方向的学生需要一份有真实数据集支撑、有对比实验、有可视化结果的毕设或课设这份资源能直接作为基线。如果你是从业者想快速验证某个特征工程思路在 UNSW-NB15 上的效果也可以拿它当脚手架。但要注意它不是一个生产级的入侵检测系统而是一个教学和实验性质的机器学习项目理解这一点后面选型和调参才不会跑偏。2. 拆开资源包UNSW-NB15 的数据结构与预处理链路2.1 数据集字段到底怎么读UNSW-NB15 的原始数据分两部分一部分是流量特征 CSV另一部分是标签文件。训练集通常有 175341 条记录测试集 82332 条特征列一共 49 列其中 42 列是数值或类别特征剩下的是标签和攻击类别。很多同学第一次打开 CSV 会懵——proto、service、state这些是字符串sbytes、dbytes、rate是数值attack_cat是多分类标签label是二分类标签0 正常1 攻击。常见做法是先把attack_cat和label分开处理二分类任务只用label多分类任务才用attack_cat。但attack_cat里有空格和大小写不一致的问题比如Backdoors和Backdoor可能同时出现需要先做标准化。下面这段代码是我一般会先跑的字段清洗逻辑import pandas as pd import numpy as np # 读取训练集和测试集 train pd.read_csv(UNSW_NB15_training-set.csv) test pd.read_csv(UNSW_NB15_testing-set.csv) # 查看字段类型和缺失情况 print(train.dtypes) print(train.isnull().sum()) # 标准化 attack_cat去空格、统一大小写 train[attack_cat] train[attack_cat].str.strip().str.title() test[attack_cat] test[attack_cat].str.strip().str.title() # 把 Normal 单独标记出来方便后续做二分类 train[is_attack] (train[attack_cat] ! Normal).astype(int) test[is_attack] (test[attack_cat] ! Normal).astype(int) # 检查类别分布 print(train[attack_cat].value_counts()) print(train[is_attack].value_counts())这段代码的逻辑很直接先确认数据没有大面积缺失然后把攻击类别名称统一避免后面做多分类时同一个类被拆成两个。is_attack这个字段是我习惯加的因为很多毕设只要求二分类但你又不想丢掉多分类的扩展性。参数上注意str.title()会把backdoors变成Backdoors但如果原始数据里有Backdoor单数形式这里不会自动合并需要你手动映射。2.2 类别特征编码与数值标准化proto、service、state这三列是典型的类别特征取值数量分别是 100、13、11 左右。直接做 One-Hot 会让特征维度爆炸尤其是proto有上百种取值稀疏矩阵会拖慢训练速度。我一般会分两步走先看取值分布把出现次数少于 100 的稀有类别合并成Other然后再做编码。from sklearn.preprocessing import LabelEncoder, StandardScaler # 合并稀有类别 for col in [proto, service, state]: freq train[col].value_counts() rare freq[freq 100].index train[col] train[col].replace(rare, Other) test[col] test[col].replace(rare, Other) # 标签编码 le_dict {} for col in [proto, service, state]: le LabelEncoder() train[col] le.fit_transform(train[col]) # 测试集用训练集的编码器避免标签泄漏 test[col] test[col].map(lambda x: le.transform([x])[0] if x in le.classes_ else -1) le_dict[col] le # 数值特征标准化 num_cols [dur, sbytes, dbytes, rate, sttl, dttl, sload, dload] scaler StandardScaler() train[num_cols] scaler.fit_transform(train[num_cols]) test[num_cols] scaler.transform(test[num_cols])这里有个关键点测试集的编码必须用训练集的LabelEncoder不能重新fit。否则同一个proto值在训练集和测试集里可能对应不同的整数模型评估结果就是假的。稀有类别合并的阈值 100 不是固定的你可以根据value_counts()的结果调整原则是保证合并后每个类别至少有几十条样本不然模型学不到东西。标准化只对数值列做类别列做完标签编码后已经是整数不需要再标准化。2.3 特征选择哪些列其实可以扔掉UNSW-NB15 的 42 个特征里不是每一个都对检测有帮助。id列是行号直接删attack_cat和label是标签训练时要分离stime和ltime是时间戳如果不做时序分析也可以删。剩下的特征里sbytes、dbytes、rate、sttl、dttl、sload、dload、sinpkt、dinpkt这些流量统计特征通常重要性最高。我一般会跑一遍随机森林的特征重要性把重要性低于 0.01 的特征列出来再决定是否剔除。但注意特征选择要在训练集上做然后应用到测试集不能把测试集的信息混进来。下面是一个快速筛选的示例from sklearn.ensemble import RandomForestClassifier # 分离特征和标签 drop_cols [id, attack_cat, label, is_attack] feature_cols [c for c in train.columns if c not in drop_cols] X_train train[feature_cols] y_train train[is_attack] # 快速训练一个随机森林看重要性 rf RandomForestClassifier(n_estimators100, random_state42, n_jobs-1) rf.fit(X_train, y_train) # 输出重要性排序 importance pd.Series(rf.feature_importances_, indexfeature_cols) print(importance.sort_values(ascendingFalse).head(20))跑完这个你大概能看到前 15 个特征贡献了 90% 以上的重要性。如果毕设要求模型轻量化可以把后面的特征砍掉但砍之前要重新评估一次准确率和召回率确认没有明显下降。注意random_state固定住不然每次跑出来的重要性排序会有波动写论文的时候不好复现。3. 模型训练与评估从二分类到多分类的完整流程3.1 二分类检测随机森林和 XGBoost 怎么选二分类任务就是判断一条流量是正常还是攻击。这个场景下随机森林和 XGBoost 是最常用的两个基线。随机森林的优势是抗过拟合、对类别不平衡不敏感、训练速度快XGBoost 的优势是精度通常更高、支持自定义损失函数、能处理缺失值。如果你的毕设要求“模型对比”这两个都跑一遍再加上一个 SVM 或者朴素贝叶斯做参照表格就丰满了。from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix # 准备数据 X_test test[feature_cols] y_test test[is_attack] # 随机森林 rf RandomForestClassifier(n_estimators200, max_depth20, random_state42, n_jobs-1) rf.fit(X_train, y_train) rf_pred rf.predict(X_test) # XGBoost xgb XGBClassifier(n_estimators200, max_depth6, learning_rate0.1, use_label_encoderFalse, eval_metriclogloss, random_state42) xgb.fit(X_train, y_train) xgb_pred xgb.predict(X_test) # 评估函数 def evaluate(y_true, y_pred, name): print(f--- {name} ---) print(fAccuracy: {accuracy_score(y_true, y_pred):.4f}) print(fPrecision: {precision_score(y_true, y_pred):.4f}) print(fRecall: {recall_score(y_true, y_pred):.4f}) print(fF1: {f1_score(y_true, y_pred):.4f}) print(confusion_matrix(y_true, y_pred)) evaluate(y_test, rf_pred, Random Forest) evaluate(y_test, xgb_pred, XGBoost)参数上随机森林的n_estimators设 200 通常够用再往上收益递减max_depth设 20 是防止树太深导致过拟合。XGBoost 的max_depth一般设 6 左右learning_rate0.1 是常用起点如果欠拟合可以降到 0.05 但要把n_estimators加上去。use_label_encoderFalse和eval_metriclogloss是新版 XGBoost 的必填项不写会报警告。评估指标里准确率在类别不平衡时会有欺骗性。UNSW-NB15 的训练集里攻击样本占比大约 60%测试集也差不多所以准确率还能看。但如果你的场景里正常流量占 95% 以上就要重点看召回率和 F1。混淆矩阵能告诉你模型把多少攻击误判成了正常这个数字在安全场景里比准确率重要得多。3.2 多分类攻击识别九类攻击怎么分多分类任务是把攻击细分成九种类型。这个任务比二分类难因为有些攻击类型样本很少比如 Worms 只有 130 条左右Shellcode 也不多。直接用多分类模型跑小类别召回率会很低。常见做法是要么对少数类做过采样要么用类别权重让模型关注小类别。from sklearn.utils.class_weight import compute_class_weight from sklearn.ensemble import RandomForestClassifier # 多分类标签 y_train_multi train[attack_cat] y_test_multi test[attack_cat] # 计算类别权重 classes np.unique(y_train_multi) weights compute_class_weight(balanced, classesclasses, yy_train_multi) class_weight_dict dict(zip(classes, weights)) # 带权重的随机森林 rf_multi RandomForestClassifier(n_estimators300, max_depth25, class_weightclass_weight_dict, random_state42, n_jobs-1) rf_multi.fit(X_train, y_train_multi) multi_pred rf_multi.predict(X_test) # 多分类评估 from sklearn.metrics import classification_report print(classification_report(y_test_multi, multi_pred))compute_class_weight(balanced)会根据每个类别的样本数自动算权重样本越少权重越高。这样模型在训练时会更关注 Worms、Shellcode 这些小类别。但注意权重过高可能导致模型把正常流量误判成小类别攻击所以跑完要看classification_report里每个类别的 precision 和 recall不能只看 overall accuracy。如果过采样可以用 SMOTE但 SMOTE 对高维稀疏数据效果一般而且容易生成不真实的样本。我一般优先用类别权重实在不行再考虑过采样。另外多分类的标签编码要用LabelEncoder统一确保训练集和测试集的类别顺序一致。3.3 交叉验证与超参数搜索单次划分训练集和测试集的结果有随机性写毕设的时候导师可能会问“你这个结果稳定吗”。交叉验证能给出更可靠的评估。常见做法是 5 折或 10 折交叉验证配合网格搜索找最优参数。from sklearn.model_selection import GridSearchCV, StratifiedKFold # 定义参数网格 param_grid { n_estimators: [100, 200, 300], max_depth: [15, 20, 25], min_samples_split: [2, 5, 10] } # 分层交叉验证保证每折的类别比例一致 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) rf_cv RandomForestClassifier(random_state42, n_jobs-1) grid GridSearchCV(rf_cv, param_grid, cvcv, scoringf1, n_jobs-1, verbose1) grid.fit(X_train, y_train) print(fBest params: {grid.best_params_}) print(fBest F1: {grid.best_score_:.4f})StratifiedKFold比普通KFold更适合类别不平衡的数据因为它保证每一折里各类别的比例和整体一致。scoringf1是因为安全场景更看重 F1如果你更关心召回率可以改成recall。网格搜索比较耗时参数组合多的话可以先用RandomizedSearchCV粗筛再用GridSearchCV细调。4. 避坑与排查UNSW-NB15 实战中容易翻车的地方4.1 标签泄漏测试集信息混进了训练过程现象模型在测试集上准确率 99%但换一批数据就掉到 70% 以下。原因预处理时对全体数据做了标准化或编码测试集的统计信息泄漏到了训练阶段。解决所有fit操作只能在训练集上做测试集只能用transform。标准化、编码、特征选择都要遵守这个原则。4.2 类别不平衡导致小类别召回率为零现象多分类报告里 Worms 和 Shellcode 的 recall 是 0.00。原因这两个类别样本太少模型直接把它们归到了大类。解决用class_weightbalanced或手动设置权重也可以对小类别做过采样。如果还是不行考虑把极稀有类别合并成Other或者只做二分类不做多分类。4.3 特征编码顺序不一致现象训练时proto列编码后tcp是 3测试时变成了 5。原因测试集重新fit了LabelEncoder导致编码映射不同。解决训练集fit后保存编码器测试集用同一个编码器transform。如果测试集出现了训练集没见过的类别映射为 -1 或单独处理。4.4 随机种子不固定导致结果无法复现现象每次跑代码准确率都不一样论文里的数字对不上。原因random_state没设或者设了但没传给所有随机过程。解决在train_test_split、模型初始化、交叉验证里都固定random_state42。如果用了 XGBoost还要注意n_jobs并行时可能有微小随机性可以设n_jobs1保证完全可复现。4.5 内存不足数据量不大但特征维度爆炸现象做 One-Hot 编码后内存直接爆了。原因proto列有上百个取值One-Hot 后特征维度从 42 涨到 200 多稀疏矩阵存储不当会占大量内存。解决用标签编码代替 One-Hot或者用pd.get_dummies时加sparseTrue。如果一定要 One-Hot先合并稀有类别把proto的取值控制在 20 个以内。5. 进阶技巧用 SHAP 解释模型和保存可复用的推理管道5.1 用 SHAP 看模型到底学了什么毕设答辩的时候导师经常会问“你这个模型为什么判断这条流量是攻击”。SHAP 能给出每个特征对单条预测的贡献值比单纯看特征重要性更有说服力。下面是对随机森林做 SHAP 解释的代码import shap # 用训练集的一个子集做背景加速计算 explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(X_test.iloc[:500]) # summary plot全局特征重要性 shap.summary_plot(shap_values[1], X_test.iloc[:500], feature_namesfeature_cols) # force plot单条预测的解释 shap.force_plot(explainer.expected_value[1], shap_values[1][0], X_test.iloc[0], feature_namesfeature_cols)TreeExplainer对树模型的计算速度很快500 条样本几秒钟就能出结果。shap_values[1]取的是正类攻击的 SHAP 值summary_plot会画出每个特征对预测的影响方向和大小。如果某个特征在攻击样本里普遍推高预测值说明模型学到了合理的模式。注意 SHAP 计算量随特征数和样本数增长全量测试集可能跑很久取子集就够了。5.2 保存完整的推理管道训练完模型不能只保存权重预处理步骤也要一起保存不然部署的时候还得手动复现编码和标准化逻辑。用joblib把编码器、标准化器、模型打包成一个字典import joblib # 打包所有需要复用的组件 pipeline { label_encoders: le_dict, scaler: scaler, feature_cols: feature_cols, model: rf, model_type: RandomForest } joblib.dump(pipeline, unsw_nb15_pipeline.pkl) print(Pipeline saved.) # 加载并推理 loaded joblib.load(unsw_nb15_pipeline.pkl) model loaded[model] le_dict loaded[label_encoders] scaler loaded[scaler] # 对新数据做同样的预处理 def preprocess_new(df, le_dict, scaler, feature_cols): for col in [proto, service, state]: le le_dict[col] df[col] df[col].map(lambda x: le.transform([x])[0] if x in le.classes_ else -1) num_cols [dur, sbytes, dbytes, rate, sttl, dttl, sload, dload] df[num_cols] scaler.transform(df[num_cols]) return df[feature_cols] # 假设 new_data 是一条新流量 # X_new preprocess_new(new_data, le_dict, scaler, feature_cols) # prediction model.predict(X_new)这个管道的好处是你换一台机器、换一个环境只要加载这个 pkl 文件就能对新流量做完全一致的预处理和预测。le_dict里存的是每个类别列的LabelEncoder对象scaler是训练好的标准化器feature_cols是特征顺序。注意preprocess_new里对未见过的类别映射为 -1这个值在训练时没出现过模型可能会给出异常预测所以实际部署时要加一层判断遇到 -1 就标记为“未知类别”人工处理。5.3 一个我踩过的坑特征顺序不能乱有一次我把训练好的模型拿去推理准确率直接掉到 50%。排查了半天才发现新数据的列顺序和训练时的feature_cols不一致。sklearn的模型对特征顺序是敏感的列顺序变了模型看到的输入就完全变了。从那以后我每次保存管道都会把feature_cols一起存下来推理前强制df df[feature_cols]重排。这个习惯帮我省了很多次“玄学掉点”的排查时间。另外如果你用 XGBoost 保存模型joblib和save_model两种方式都可以但joblib能把预处理组件一起打包更省事。如果毕设要求部署成 API可以用 Flask 或 FastAPI 包一层把preprocess_new和model.predict串起来输入 JSON 输出预测结果。这样答辩的时候演示效果会好很多导师也能直观看到模型怎么用。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网