资讯动态

基于UNSW-NB15的机器学习攻击检测实战:特征工程、模型训练与部署

发布时间:2026/10/13 1:05:21 来源:尧图企业网站定制
简介基于UNSW-NB15数据集的网络攻击检测机器学习算法项目面向计算机、人工智能等专业学生毕业设计与课程设计场景。项目包含决策树、逻辑回归与KNN三种分类算法的完整Python实现代码经作者预先测试通过解压后依照说明即可运行操作简单适合作为课程作业、毕设演示或初期项目立项参考。资源包共四个文件由三个Python脚本和一个Markdown文档组成压缩包整体仅十二KB非常轻量便于阅读和修改。脚本按算法模块划分逻辑清晰既能单独运行观察效果也能对比不同模型在入侵检测任务中的表现说明文档介绍了数据集的结构、特征含义以及基本的运行流程能够帮助初学者快速上手。目前已有143人学习该资源实用性得到部分用户验证对于希望快速搭建网络入侵检测原型、完成实验报告或进行答辩展示的同学这套源码和说明可以显著减少环境配置与代码调试时间值得下载使用。1. 为什么毕设选 UNSW-NB15比 KDD99 更能扛住答辩的数据主线如果你的毕设题目是“基于机器学习的网络攻击检测”数据选哪份基本就决定了后面三个月的日子。KDD99 和 NSL-KDD 虽然是教科书常客但里面的攻击类型还停留在二十年前的形态答辩老师只要追问一句“这个数据集里的攻击在今天还有没有代表性”场面很容易冷下来。UNSW-NB15 数据集是澳洲网络安全中心生成的现代仿真流量数据包含 9 类攻击、数十维网络流特征配合机器学习算法做网络攻击检测正好覆盖“数据新、特征多、可解释性强、能部署演示”这四个毕设刚需。这类交付物通常以“源码 教程”的 zip 形式出现标题里写了“简单部署即可运行”说明它面向的是课程设计和本科毕设场景而不是生产级 IDS。所以下面我不会只讲怎么把模型跑出个数字而是按拿到项目后最该走的顺序先读数据、再做特征工程、训练模型、部署接口、最后用一份能自圆其说的评估去应付答辩。坑我已经替你踩过一遍直接照着做就行。2. 读懂 UNSW-NB1549 维特征、9 类攻击和标签分布2.1 数据集的构成与常见文件形态UNSW-NB15 最常见的文件形态是几张 CSV训练集大约 17 万条记录测试集大约 8 万条记录另外还有按 chunk 分块的全量数据总记录数在 250 万左右。对毕设来说用官方给的训练集和测试集就够了不建议一上来就去啃全量分块文件内存和训练时间都会成为新的麻烦。CSV 里每一行是一条网络流记录常见是 49 列其中既有 dur、proto、service、state、spkts、dpkts 这类基础流特征也有 sload、dload、sttl、dttl 这类负载和时间特征最后两列一般分别是 attack_cat攻击类别和 label二分类标签0 为正常1 为攻击。不同发布版本列顺序略有差异有的会多一列 id所以拿到文件后第一件事不是建模而是打印表头。import pandas as pd df pd.read_csv(UNSW_NB15_training-set.csv) print(df.shape) print(df.columns.tolist())这段代码做的事情只有三件读取 CSV、看行列数、列出全部字段名。不要小看这一步很多翻车现场都是因为教程里说“第 3 列是特征”而你手里的文件因为多了 id 列所有列号整体错位。用 columns.tolist() 看到的才是真实列名特征工程阶段再根据列名而不是列号操作基本不会错。2.2 二分类标签还是多分类标签决定你后面怎么写代码UNSW-NB15 的标签体系很适合用来展示“层次化检测”思路。label 字段只区分正常和攻击是典型的二分类问题baseline 模型很容易跑到 90% 以上准确率。而 attack_cat 字段把攻击进一步分成 Fuzzers、Analysis、Backdoors、DoS、Exploits、Generic、Reconnaissance、Shellcode、Worms 这 9 类加上 Normal 一共 10 个取值属于多分类问题。答辩时比较讨巧的做法是两条线都做先用二分类证明整体检测能力再对 attack_cat 做多分类展示你能区分“它是哪种攻击”。很多现成教程只做了二分类因为简单准确率好看。但老师一旦问“Generic 攻击和 Exploits 攻击在特征上有什么区别”二分类模型是回答不了的这时候多分类的价值就体现出来了。还有一列值得单独检查label 列的类型。它是整数 0/1但用 pandas 读出来可能是 int64也可能是 object训练前必须显式转成 int否则不少机器学习框架会直接当作分类任务处理抛出类型错误。df[label] df[label].astype(int) print(df[label].value_counts()) print(df[attack_cat].value_counts())这段代码把 label 转成整数同时输出两个标签的分布。你会立刻看到类别不平衡Normal 与攻击样本的比例、各类攻击之间的比例都不均匀。这一步的意义是给后面做采样和评估提供依据而不是只看一眼就关掉。2.3 为什么 49 列不能全塞进模型49 列听起来不多但对树模型和线性模型来说并不是所有列都有平等的预测价值。最典型的是 id 列如果 CSV 里有这一列它只是记录编号和网络攻击没有任何因果逻辑必须删掉。attack_cat 和 label 是目标变量自然也要从特征集中排除。剩下的特征里还要警惕那些在真实部署时拿不到的字段。比如某些字段表示“这条流是否由恶意 IP 发起”这类特征在离线数据集里可以用但在实时流量场景中未必能在线计算。毕设不用做到生产级但选特征的时候想清楚“这个字段上线时能不能拿到”最终在答辩里说一句“我保留了可在流量入口处提取的字段”会比机械地堆 47 个特征分数更高。3. 特征工程决定检测上限预处理、压缩与类别不平衡处理3.1 字符串特征转数值proto、service、state 的统一处理UNSW-NB15 里有三列是典型的类别型字符串proto协议、service服务类型、state连接状态。机器学习算法不认识字符串必须转成数值。最常见的做法是 LabelEncoder把每个字符串映射成一个整数比如 tcp 变 0、udp 变 1。对树模型来说 LabelEncoder 就够用了因为树模型做划分时不关心数值本身的量级只关心能不能通过阈值把样本分开。不要误用 OneHot。proto 取值数量不多OneHot 效果也行但 service 和 state 的取值分支较多OneHot 会让特征维度膨胀训练速度下降而且对随机森林这类模型没有显著收益。毕设项目追求“简单部署即可运行”推荐用 LabelEncoder 统一处理代码最省解释也容易。from sklearn.preprocessing import LabelEncoder cat_cols [proto, service, state] for col in cat_cols: df[col] df[col].astype(str) lb LabelEncoder() df[col] lb.fit_transform(df[col])这段代码对三个字符串列逐一做标签编码。注意先 astype(str) 是为了把空值和 NaN 统一处理成字符串否则 fit_transform 会报错。LabelEncoder 在这个流程里是逐列独立 fit 的不需要像目标变量那样保存全局映射但如果你要把训练好的模型拿去部署这三列的映射表还是要记下来后面章节会展开讲。3.2 标准化只在训练集上 fit顺序错了分数全是虚高UNSW-NB15 的特征量级差异巨大sbytes 可能上万而某个比率字段可能是小数。对逻辑回归、SVM 这类基于距离的模型标准化是必须的对随机森林和 XGBoost理论上不做也行但做了之后特征量的波动降低训练更稳定。问题在于很多人直接对整个数据集做 StandardScaler然后再切分训练集和测试集这是最常见的数据泄漏也是最容易被答辩老师一眼看穿的问题。正确顺序是先切分再用训练集 fit 标准化器测试集只做 transform。这样测试集的分布信息不会提前混入标准化缩放系数里评估结果才真实反映模型对未知数据的表现。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler feature_cols [c for c in df.columns if c not in [id, attack_cat, label]] X df[feature_cols] y df[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)这段代码做了三件关键事。一是用列表推导式按列名排除掉 id 和标签列避免人为漏删二是划分数据时用了 stratifyy让训练集和测试集里的正负样本比例保持一致对不平衡数据很重要三是标准化器只在 X_train 上 fit然后才对测试集 transform。random_state 固定成 42保证二次运行结果可复现这也是毕设里应该养成的习惯。3.3 类别不平衡准确率虚高的真正来源UNSW-NB15 里 Normal 和攻击样本的比例并不是 1:1而 9 类攻击之间的样本量差距更大。如果你直接拿原始数据训练模型大概率会学成一个“多数类优先”的分类器把所有样本都预测成多数类准确率照样能到 80% 甚至 90%但对少数攻击类的召回率低得可怜。对付不平衡有两类常用手段。第一类是不改变数据分布在模型参数里加 class_weightbalanced让少数类在损失计算中获得更高权重随机森林和逻辑回归都支持这一参数。第二类是过采样用 SMOTE 合成少数类样本再训练。SMOTE 的问题在于它基于近邻插值对 UNSW-NB15 这种高维特征合成样本可能落在不合理的位置所以使用时要对比原始数据的评估结果。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train, y_train) print(pd.Series(y_train_res).value_counts())注意 SMOTE 必须在 train_test_split 之后、scaler 之后对训练集做。先缩放再插值合成样本的分布能保持同尺度测试集保持不变。很多教程把 SMOTE 放在切分之前本质上又造成训练信息和测试信息的交叉污染。fit_resample 这一行执行后训练集攻击样本会被补充到和 Normal 相同量级但你要意识到这些是合成样本不是真实采集的数据。3.4 特征选择先看树模型的 feature importance49 维特征全部进入模型不是不行但训练时间会拉长部署接口的输入也会更冗余。我一般会先用一个深度较浅的随机森林跑一次直接输出 feature_importances_把排名靠后的特征砍掉保留 top 20 到 top 30。这个做法的好处是特征重要性是模型自己给出的不需要手工猜也方便在论文里画一张特征重要性的柱状图。from sklearn.ensemble import RandomForestClassifier tmp_model RandomForestClassifier( n_estimators80, max_depth10, random_state42, n_jobs-1 ) tmp_model.fit(X_train_res, y_train_res) feat_importance pd.Series( tmp_model.feature_importances_, indexfeature_cols ).sort_values(ascendingFalse) print(feat_importance.head(20))这段代码用 80 棵树、最大深度 10 的快速模型做特征重要性评估。n_jobs-1 表示使用全部 CPU 核能明显缩短等待时间。跑完之后不要迷信结果还要看一眼保留的特征是否都有明确含义。如果某个靠前特征在真实流量入口根本取不到就应该手动删掉而不是为了指标好看硬留。4. 训练到交付随机森林、XGBoost 与一个能部署的 Flask 接口4.1 训练与验证集划分以及不要只盯 accuracy很多人在 train_test_split 之后直接训练最后输出一个 accuracy。这个习惯在 UNSW-NB15 上问题很大因为类别不平衡会让 accuracy 虚高。更好的做法是把训练集再切出一部分作为验证集用来做早停和调参最终测试集只评估一次。验证集的价值在 XGBoost 上体现最明显它可以实时监控验证集 loss发现模型开始过拟合就及时停止。测试集则要保持“最终一次性使用”的洁癖不要让它在调参过程中反复出现。毕设答辩时老师如果问“你的测试集测了几次”你至少能理直气壮地回答“只在确定好模型之后测过一次”。4.2 随机森林的 3 个必调参数随机森林是 UNSW-NB15 任务里性价比最高的模型。它不需要复杂预处理能处理高维特征还能给出特征重要性关键是调参空间不大。下面这 3 个参数是我每次必调的参数默认值常见范围参数含义与调参方向n_estimators100100~300树的数量越大越稳定但训练时间线性增加max_depthNone10~30树的最大深度限制深度可抑制过拟合min_samples_leaf11~5叶节点最少样本数越大模型越平滑泛化越好from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report model RandomForestClassifier( n_estimators200, max_depth20, min_samples_leaf2, class_weightbalanced, random_state42, n_jobs-1, ) model.fit(X_train_res, y_train_res) y_pred model.predict(X_test) print(classification_report(y_test, y_pred, digits4))逻辑很简单训练、预测、输出分类报告。class_weightbalanced 是应对不平衡的关键参数即使已经做过 SMOTE保留它也能增强模型对少数类的关注。分类报告里重点看二分类的 F1 和召回率别再只看 accuracy。如果 F1 比 accuracy 低很多说明模型是在“用多数类堆分数”。4.3 用 XGBoost 做二分类或三分类对比早停参数要用起来XGBoost 是机器学习算法里最能提分的选项也是毕业设计论文里很高频的对比模型。它的参数比随机森林多但真正影响结果的是学习率、树深度和早停。千万不要上来就 n_estimators1000那样会在训练集上死磕到底验证集早就开始上升了。from xgboost import XGBClassifier xgb_model XGBClassifier( n_estimators300, max_depth6, learning_rate0.1, eval_metriclogloss, early_stopping_rounds10, random_state42, n_jobs-1, ) xgb_model.fit( X_train_res, y_train_res, eval_set[(X_test, y_test)], verboseFalse, )early_stopping_rounds10 的意思是连续 10 轮验证集 loss 不再下降就停止训练最终保留的是验证集表现最好的那一轮模型而不是最后一轮。n_estimators300 只是一个上限早停通常会帮你提前截断。这里 eval_set 我直接用了测试集这是毕设场景下省事的做法严格说是该单独留一份验证集如果你追求规范把 X_train 再拆一次即可。4.4 Flask 部署最小接口把训练好的模型变成能调用的服务标题里写了“简单部署即可运行”对毕设来说最稳的演示方式就是用 Flask 起一个 HTTP 接口输入一条网络流的特征返回攻击类别和概率。先训练、再保存和训练脚本分开运行避免每次演示都要重新训练一遍。import joblib from flask import Flask, request, jsonify import numpy as np app Flask(__name__) model joblib.load(xgb_model.joblib) scaler joblib.load(scaler.joblib) feature_names joblib.load(feature_names.joblib) app.route(/predict, methods[POST]) def predict(): data request.get_json() features [] for col in feature_names: features.append(float(data[col])) x np.array([features]) x scaler.transform(x) proba model.predict_proba(x)[0] pred_idx int(np.argmax(proba)) return jsonify({ prediction: pred_idx, probability: proba.tolist() }) if __name__ __main__: app.run(host0.0.0.0, port5000)这个接口的逻辑是客户端传来一个 JSON字段名要和训练时的特征列一一对应。服务端按照保存的 feature_names 顺序取值先经过标准化再交给模型预测。这里最关键的是保存 scaler 和 feature_names否则上线时连特征顺序都说不清。host0.0.0.0 是为了在局域网内让同组同学和老师都能访问端口 5000 是 Flask 默认注意别被本机其他程序占用。5. 部署复现避坑指南模型跑起来之前必要排掉的 5 个坑5.1 数据泄漏Scaler 和 SMOTE 的先后顺序错了准确率虚高现象训练集准确率 99%测试集却跌到 85%且无论怎么调参都回不到训练集水平。原因在完整数据集上先做了 StandardScaler标准化器把测试集的均值和方差也学到了相当于测试信息提前进入了训练环节。SMOTE 也一样如果先对整个数据集做合成采样再切分合成的“假样本”同时出现在训练集和测试集里模型本质上是开卷考试。解决先 train_test_split再对训练集 fit_transform测试集只 transformSMOTE 严格只作用于训练集。这是一条铁律不用妥协。判断有没有做错的方式也很简单把 scaler 和 smote 的操作顺序列出来只要它们出现在 split 之前就重来。5.2 标签映射乱序模型输出 3接口却显示不出攻击类型现象模型预测结果是一串整数比如 0、1、3、7但接口把 3 当成 Normal把 0 当成 Exploits分类结果在语义上全是乱的。原因把 attack_cat 做 LabelEncoder 时生成了一个整数映射表但这个映射表没有原样保存到部署目录里。默认的 LabelEncoder 是按下标顺序映射下一次运行时字母顺序一样映射顺序倒是一致但不同环境间字符串排序规则可能有差异或者直接把两个不同数据集的编码混用了。解决训练时把 label_encoder.classes_ 单独保存一份部署时加载并构建 inverse 字典。代码上就是 joblib.dump(lb.classes_, attack_cat_classes.joblib)预测结束后用 dict(enumerate(classes)) 把整数映射回原始类别名称。这件事做完整个接口的输出才具备可解释性。5.3 sklearn 版本不一致本机能跑换台机器报错现象压缩包在自己电脑上训练好模型发给同学或换到实验室电脑后joblib.load 直接抛 ModuleNotFoundError或者模型加载成功但 predict 时报 ValueError。原因pickle/joblib 序列化时记录了模型类所在的模块路径sklearn 老版本例如 0.24 和新版本 1.2 在类结构上有差异跨版本加载大概率出问题。解决不要把陌生环境当成必然能跑的环境。压缩包里的 requirements.txt 必须锁版本至少锁 sklearn、xgboost、flask、pandas 这四类核心依赖。更省事的做法是部署电脑上直接用项目里训练脚本重新训练一次不加载旧模型。毕设场景对模型的“复用性”要求不高现场重训反而更能证明代码是真实可跑的。5.4 XGBoost 在 Windows 上报 DLL load failed现象import xgboost 这一行直接崩掉报 DLL load failed while importing xgboost或者训练时 CPU 被打满但进度缓慢。原因xgboost 版本与 Python 版本、系统架构不匹配最常见的是用老版本 pip 依赖在 Python 3.10 以上环境里安装到了不兼容的 wheel。解决升级到新版本pip install xgboost1.7Python 3.11 用户建议直接 1.9 以上。如果还失败用 conda 装会省心很多。训练速度方面XGBoost 默认使用全部 CPU 线程在共享实验室电脑上会对别人造成干扰n_jobs 可以设成 4 或 8别贪全核。5.5 数据全量读入内存不足250 万条记录不是毕设必须现象read_csv 全量文件时内存占满Notebook 直接卡死或者训练到一半电脑无响应。原因全量 UNSW-NB15 数据接近 250 万条加上 49 维特征和多个字符串列内存占用会超过普通笔记本的承受能力。解决优先使用官方训练集和测试集17 万条加 8 万条的规模已经完全够完成一篇本科毕设。如果一定要用全量可以用 pandas 的 chunksize 参数分批读取先抽样再进模型。这属于“示范了你会处理大数据”的加分项但不值得为此赌上电脑稳定。6. 验收模型与答辩演示混淆矩阵、阈值调节和一枚合格的 README模型训练完不是终点把评估结果讲明白才是毕业设计最后一步。不要只贴一张 accuracy 数字建议打印混淆矩阵特别是多分类场景。看哪些类别之间互相混淆比如 Generic 攻击是否常被识别成 Normal这能帮你定位模型真实短板也能成为答辩时的深度素材。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm) disp.plot(cmapBlues)这段代码输出一张标准混淆矩阵图。如果二分类矩阵里 FN 集中在某几条特定流上翻出这些样本的特征观察你会很快找到原因比如某些合法流量携带了大体积 TCP 包和 Generic 攻击的负载特征相似模型因此误判。二分类场景下还可以尝试调阈值。默认分类阈值是 0.5但对攻击检测而言宁可把少量正常流量误报为攻击也不能放跑真实的攻击流量。用 predict_proba 拿到攻击概率后把阈值降到 0.3 或 0.4观察召回率与误报率的反向变化挑一个能讲出理由的平衡点。最后补一枚 README别嫌它简单。项目压缩包再好没有运行说明就是给阅卷和抽查增加摩擦。我习惯的 README 结构是数据来源链接、环境依赖清单、训练脚本运行命令、部署脚本运行命令、模型评估表。评估表直接复用第 4 章分类报告里的 Precision、Recall、F1 三个数据老师扫一眼就知道项目做到了什么水平。我当年做入侵检测毕设时最大的教训是最后一个星期还在调 class_weight因为之前所有评估都只看 accuracy根本没发现少数类攻击的召回率已经低到不可用。后来花了半天时间把 SMOTE 挪到训练集切分之后并锁定全部随机种子测试集指标反而更好看答辩现场也顺利演示了三轮在线预测。项目该怎么验收第一标准永远是“指标是否真实、流程是否可复现”其次才是分数高低。沿着这个思路去做UNSW-NB15 这套题会稳稳把你带上及格线以上。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑