简介面向数据挖掘与网络安全相关的课程设计与期末大作业场景这份基于KDD-CUP99数据集的网络攻击检测项目提供了完整的Python实现和配套资料。项目源码经过本地编译验证难度适中内容由助教老师审定可直接用于学习与验收展示。压缩包体积约6.93MB共75个文件核心包括14个Python脚本与10个pyc编译文件同时含有7个Vue前端组件、4个CSS样式、4个JSON配置、HTML入口页面、Markdown说明文档以及SQLite数据库等覆盖数据预处理、特征提取、模型训练与可视化展示的完整链路。前后端分离的目录结构清晰便于按模块理解入侵检测流程。已有349人浏览学习适合需要快速搭建网络攻击检测系统原型、完成期末汇报或入门机器学习安全应用的开发者参考。1. KDD-CUP99 网络攻击检测一门课设如何把 IDS 从理论拉到工程大四做网络攻击检测的项目老师递过来一份基于 KDD-CUP99 数据集的源码包。当时我第一反应是这东西太老——1999 年 DARPA 采集的 9 周网络流量和今天动辄百 G 的骨干网不在同一个量级。可真把它拆完我发现这门课设考的不是算法多新而是三件事懂特征、会评估、能把模型包成系统。每个连接 41 维特征、四类攻击DoS、Probe、R2L、U2R加一条正常标签这个分类框架二十多年后仍然是 IDS 行业的标准姿势。适合期末大作业、毕设起步、以及想弄清入侵检测数据链路怎么走的人。更难得的是这份资料把数据预处理、模型训练、Django 后端和 Vue 前端串成了一条完整链路不是那种只丢给你一个 notebook 的半成品。2. 数据先行的第一关读懂 41 维特征再谈分类怎么落地2.1 数据集结构KDD-CUP99 的四类攻击到底在检测什么KDD-CUP99 每条记录对应一次 TCP 连接是一个 41 维特征加一条标签的 tabular 数据。41 维内部可以分成四组第一组是基础特征duration、protocol_type、service、flag、src_bytes、dst_bytes 等 9 个描述一条连接的时长、协议、上下行字节数第二组是内容特征hot、failed_logins、num_compromised、root_shell 等 13 个主要设计用来捕捉 U2R 和 R2L 攻击在会话里留下的痕迹第三组是基于时间的流量特征以 2 秒窗口统计相同目标主机的连接次数、错误比例第四组是基于主机的流量特征窗口扩大成 100 条连接。四组特征测的是不同粒度的行为画像。你如果只用前 9 个特征拿到 40% 准确率都属于正常——攻击检验大量分布在流量统计特征上。标签侧项目把攻击归成四类加正常共五类。DoS 是最常见的拒绝服务样本量最大Probe 是端口扫描与漏洞探测属于侦查行为R2L 是远程未授权访问U2R 是本地提权这两类样本占比极小但恰恰是一般分类器最容易漏掉的部分。做这项网络攻击检测我建议先把标签分布打印出来再往下走这一步决定你后面要不要做重采样也决定答辩时你能不能讲清楚为什么某些攻击类型检测不出来。2.2 预处理代码字符串编码、数值标准化与标签映射把 CSV 读成矩阵并不难麻烦的是类别特征。protocol_typetcp/udp/icmp、servicehttp/ftp/…几十种取值、flagSF/REJ/S0/…都是字符串数值列里 src_bytes 能从 0 到上亿不标准化会让树模型之外的算法直接崩。下面是资源的预处理核心逻辑我用 sklearn Pipeline 重构过一版import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder, StandardScaler, LabelEncoder from sklearn.compose import ColumnTransformer # 假设 train_df 是读入的原始 DataFrame列包含 41 个特征 label num_cols [duration, src_bytes, dst_bytes, wrong_fragment, urgent, hot, num_failed_logins, num_compromised, root_shell, su_attempted, num_root, num_file_creations, num_shells, num_access_files, num_outbound_cmds, 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] cat_cols [protocol_type, service, flag] X train_df.drop(label, axis1) y LabelEncoder().fit_transform(train_df[label]) preprocessor ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) X_processed preprocessor.fit_transform(X) # 严格流程先 fit_transform 训练集保存 preprocessor # 之后对测试集只调用 preprocessor.transform(X_test)这段代码里最容易踩的坑是 pipeline 的 fit 边界。ColumnTransformer 里 StandardScaler 和 OneHotEncoder 必须只在训练集上 fit之后测试集复用同一个 preprocessor。如果你把全量数据丢进去然后一次性划分均值、方差和类别字典都包含了未来信息交叉验证分数会虚高换到真实流量上立刻现形。handle_unknownignore 是为了测试集里出现训练集没见过的 service 值时能安全跳过编码矩阵里这一列会全部为 0。标签映射上原始文件里是字符串normal、neptune、satan…LabelEncoder 会把它们压成 0-4 的整数配合后面可视化时你需要在代码里维护一张真实名字的对照表不要靠数字猜攻击类型。我做网络攻击检测时习惯把 normal 定为 0后面分别对应 DoS、Probe、R2L、U2R。源码里自带的清洗脚本已经处理过行尾点号这些脏数据我复现时基本只改了下文件路径。2.3 看一眼类别分布再动手不平衡比模型更重要训练集包含了几十万条记录但分布极其倾斜DoS 占了大头U2R 只有几十到几百条。这意味着即使什么都不学把所有样本预测成 DoS整体准确率也能到 60% 以上。所以预处理阶段我会额外做一件事——分层抽样stratify把各类比例在训练集/验证集里保持一致X_train, X_val, y_train, y_val train_test_split( X_processed, y, test_size0.2, stratifyy, random_state42 )stratify 参数在样本不均衡时属于必选否则运气差一点验证集里可能一条 U2R 都没有模型报告直接失去参考意义。分完层之后我还会用 value_counts() 核对每类样本数U2R 如果少于 10 条说明这份数据切得太狠考虑换成 k 折分层交叉验证或者把 10% 训练集和 corrected 测试集合并起来重新划分。重采样方案先放后面先用类权重class_weightbalanced跑通基线U2R/R2L 实在拉不起来再上 SMOTE别一上来就生成合成样本不然边界容易更乱。3. 模型主线随机森林跑五分类准确率不是唯一指标3.1 为什么选树模型而不是一上来就上神经网络KDD-CUP99 这类表格型数据里树模型有天然优势对特征尺度和单调变换不敏感特征交互靠规则分裂就能表达训练速度快到可以在笔记本上反复试参数。相比之下深度学习最出效果的是高维稀疏或序列数据在这里往往需要大量调参才能追平随机森林的基线。资源源码里用的方案正是 sklearn 的 RandomForestClassifier配合少量特征筛选效果足够支撑一场答辩。我在复现时保留了数据集原始特征组合没有做 PCA。原因是 PCA 会把单个特征的可解释性抹掉后面答辩讲「为什么 count 和 srv_count 组合能识别端口扫描」时根本说不出人话。模型的 joblib 文件从源码里直接导出来后是十几 MB 级别Django 后端加载完全可行这也是我当时没用 XGBoost 的一个考量。如果你后面想换 LightGBM数据链路不用动只换训练脚本和模型文件即可这也是这套工程结构比较舒服的地方。3.2 训练主代码参数边界与 OOB 验证随机森林有两个参数最容易纠结n_estimators 和 max_depth。n_estimators 太少会抖太多训练慢且收益递减max_depth 限制过深会过拟合但树模型在 41 维特征上即使不限制也通常不会太离谱。我在源码基础上跑过的配置如下from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix import joblib model RandomForestClassifier( n_estimators200, # 我习惯先固定 200看 OOB 是否收敛 max_depth20, # 限制深度防止单棵树记住噪声 min_samples_split5, # 节点再划分所需最少样本数控制分裂风险 class_weightbalanced, # 缓解 U2R/R2L 的倾斜 n_jobs-1, # 全核并行 random_state42, oob_scoreTrue # 用袋外样本评估泛化不用额外切验证集 ) model.fit(X_train, y_train) print(OOB score:, model.oob_score_) joblib.dump(model, ids_model.joblib) joblib.dump(preprocessor, preprocessor.joblib)这里的 oob_scoreTrue 是白捡的泛化参考每棵树只用约三分之二样本训练剩下三分之一袋外样本做预测对随机森林来说等价于内置了一次交叉验证。我一般拿它和验证集分数对照两者差超过两个百分点就说明有泄漏或过拟合。class_weightbalanced 会按类别频率反比放大少数类权重代价是 DoS 的精确率可能轻微下降但从「攻击检测」的目标看值得。model 和 preprocessor 分开保存有个好处接口层换模型不影响预处理改模型不改代码。3.3 评估指标该怎么看混淆矩阵里藏着问题一个典型翻车现场是整体准确率 99%但 U2R 的 recall 是 0。原因很简单U2R 样本太少分类器把所有样本都判成 Normal 或 DoS全局 accuracy 几乎不受影响。应对方案是打印 per-class 报告再谈优化y_pred model.predict(X_val) report classification_report( y_val, y_pred, target_names[Normal, DoS, Probe, R2L, U2R], digits3 ) print(report) print(confusion_matrix(y_val, y_pred))对于本项目的四类攻击场景重点看两个数R2L 和 U2R 的 recall。只要这两个不过半系统在真实场景就约等于没装。Probe 和 DoS 通常不难难的是少数类。如果 val 集上 U2R recall 实在拉不起来先确认是不是分层抽样抽到了极少数样本再考虑在 pipeline 里给少数类扩样。模型训练完我会顺手把当时的参数和 per-class 指标写进一个 JSON 文件跟模型放同目录以后换数据集再跑至少知道这个基线是怎么来的这个习惯帮我避免了好几次「这个分数怎么复现不出来」的尴尬。4. Django Vue 双端联动把模型包成能演示的检测系统4.1 Django 端模型加载与预测接口设计资源工程里 Django 目录承担后端服务manage.py 在根目录配置里注册了数据库sqlite3static 和 templates 负责传统服务端渲染页面。既然前端用 Vue 写了 net-analyze 这个工程实际联调时我会让 Django 只暴露 API页面交给 Vue 去拉。预测接口的核心是避免重复加载模型——sklearn 的 joblib.load 在机械硬盘上动不动几百毫秒放进 view 函数里每次请求都执行接口延迟会高到难以接受。项目源码里我看到的是把模型加载放在模块层这个习惯建议保留# views.py import json import joblib import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt _model None _pre None def _load_model(): global _model, _pre if _model is None: _model joblib.load(ids_model.joblib) _pre joblib.load(preprocessor.joblib) csrf_exempt def predict_api(request): if request.method ! POST: return JsonResponse({error: need POST}, status405) data json.loads(request.body) features data.get(features) if len(features) ! 41: return JsonResponse({error: feature length must be 41}, status422) _load_model() x _pre.transform([features]) y _model.predict(x)[0] proba _model.predict_proba(x)[0].tolist() return JsonResponse({ prediction: int(y), probabilities: proba })注意这里我把 _load_model() 的调用放在拿到请求并校验完字段之后才执行是懒加载写法。第一次请求慢一点后续请求全部复用内存中的模型对象。URL 配置很简单path(api/predict/, views.predict_api)。校验 features 长度是一个低成本高收益的约定前端传错维度时接口直接返回 422而不是让模型去抛一个让人看不懂的维度错误。另外sqlite3 数据库我用来记录每次预测的日志包括时间、原始特征、预测结果后面画趋势图不用重新跑模型。4.2 Vue 端仪表盘、攻击分布图与跨域联调net-analyze 的 Vue 工程包含 babel.config.js、vue.config.js、jsconfig.json 和 package.json是标准 Vue CLI 结构。前端页面要做的事有三件上传或粘贴连接特征、调用 Django 接口拿预测结果、把结果渲染成攻击分布图。核心调用代码并不复杂// src/api/detect.js import axios from axios export function detectAttack(features) { return axios.post(/api/predict/, { features }) .then(res res.data) .catch(err { console.error(predict failed:, err.message) throw err }) }这里的关键是开发环境不能直接写死http://127.0.0.1:8000否则会撞上跨域。项目在 vue.config.js 里配了 devServer 代理把 /api 前缀转发给 Django// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }配好 proxy 之后前端 axios 请求 /api/predict/ 时浏览器看到的请求是同源的由开发服务器转发给后端跨域被化解在 dev server 这一层。jsconfig.json 里通常还有 开头的路径别名import 时可以写/api/detect而不是一串相对路径。babel.config.js 保持默认即可如果你在项目里用了比较新的 JavaScript 语法preset-env 会自动做降级处理。4.3 前后端数据的字段约定与图表渲染图表部分我建议前端自己做聚合后端不要塞一堆 label 映射给前端。后端只返回整数预测结果和概率数组前端维护一张攻击类型对照表。用 ECharts 的话饼图和柱状图可以直接消费 probabilities 数组。做网络攻击检测演示时把 DoS 和 Probe 的概率画成悬浮条是答辩时的加分项——评审一眼就能看出系统不是黑匣子。这套字段约定里最容易出问题的是「前端和后端各维护一份攻击类型映射表」时间一长两边必然对不上。我习惯以 Django 端的一个常量类为唯一事实源前端从接口拿类型名称而不是自己写死。整套源码包从预处理脚本到 Vue 页面是一次完整闭环换掉模型文件就能复用到别的数据集上这也是我愿意把它完整跑一遍的原因。5. 避坑实录从数据泄漏到跨域预检五个翻车点逐一拆解5.1 one-hot 编码导致特征数量对不上现象训练时准确率正常模型保存后新数据进来transform 报出特征数量不符或者预测结果全乱。原因预处理用 pd.get_dummies 直接对全量数据编码如果新数据里某个 service 值没有出现在训练集那么新数据编码后的列数和训练时不一致模型无法对齐特征矩阵。解决统一走 sklearn 的 OneHotEncoder在 preprocessor 里 fit 训练数据之后所有新数据都用同一个 transform。参数 handle_unknownignore 会跳过新类别而不是报错代价是这一列的编码全部为 0但至少接口不会崩。5.2 StandardScaler 在全量数据上 fit造成信息泄漏现象交叉验证分数 99%但把模型放到 corrected 测试集或新流量上检测率掉到 80% 以下。原因StandardScaler 的均值和方差在全量数据上计算测试数据的信息早已进入训练流程。这在课设里最隐蔽因为分数很好看很难察觉。解决把预处理器和模型绑在同一个 Pipeline 里对训练集 fit对验证集/测试集只 transform。我复现时把 preprocessor 和 model 分开保存只是为了接口方便但 fit 边界必须严格这是一个原则问题。5.3 整体准确率 99%查 R2L/U2R 的召回率却是 0现象classification_report 里前三行都漂亮后两行全是 0看起来像模型坏了。原因类别严重不平衡max_depth 又设得比较浅树的分裂完全被 Normal 和 DoS 主导少数类样本一片叶子都分不到。单独优化整体 accuracy 会掩盖这个问题。解决先用 class_weightbalanced 跑一遍看 U2R/R2L 的 recall 有没有变化不行再对训练集做 SMOTE 过采样。注意 SMOTE 必须在 train_test_split 之后做只对训练集生成合成样本否则又是一轮泄漏。如果项目方只要整体准确率好看那另说但做入侵检测的人应该知道这个坑。5.4 Django 接口每次请求都重新加载模型响应要 3 秒现象第一次请求慢还能忍之后每次都慢看日志发现 joblib.load 被反复执行。原因模型加载写在 view 函数体内Django 每个请求都会重新走一遍机械硬盘上加载一个十几 MB 的模型当然慢。解决把模型加载提升到模块顶层或者用 lru_cache 包裹加载函数进程启动时加载一次之后只读内存。另外如果开着 debug 模式代码变更会自动重启进程也会让人觉得模型加载慢部署时记得关掉 autoreload。5.5 Vue 联调报跨域OPTIONS 预检请求失败现象前端 axios 请求 Django 接口控制台报 CORS errorNetwork 面板里有一个失败的 OPTIONS 请求。原因Django 默认不允许跨域前端直连 127.0.0.1:8000 时浏览器先发预检请求被后端拒绝。有人会在 settings.py 里闷头加 django-cors-headers其实开发期更稳妥的是用 vue.config.js 的 proxy。解决开发环境配 devServer.proxy把 /api 转发到后端如果一定要跨域直连再装 django-cors-headers在 CORS_ALLOWED_ORIGINS 里明确写 http://localhost:8080。写 * 在课设演示里能跑通但答辩被问安全策略时很难圆。6. 进阶把单次检测变成一张能交代的攻击态势图6.1 做一个轻量级的检测回放脚本单次 POST 只能验证一条记录但答辩和实际使用需要看到整体效果。我会额外写一个回放脚本读取测试集的一条条记录把预测结果按攻击类型和时间窗口聚合生成 JSON 给前端画趋势图# replay.py import json, joblib import pandas as pd from collections import Counter model joblib.load(ids_model.joblib) pre joblib.load(preprocessor.joblib) df pd.read_csv(corrected.csv) result [] for _, row in df.head(5000).iterrows(): x pre.transform([row[:41].tolist()])[0] result.append(int(model.predict([x])[0])) # 按窗口聚合出攻击类型占比 print(Counter(result))这个脚本的价值不在效果而在于把「检测系统」从单接口演示延伸成批量回放工具之后想画准确率曲线、误报随时间变化都从这个结果文件出发。6.2 阈值自整定让概率输出可调同样一条记录把判断阈值从 0.7 调到 0.3结果会完全不同。接口里我还保留了一个 threshold 参数前端滑块改阈值后端只把概率数组返回由前端决定是否判定为攻击。这是模型 demo 里最容易被忽略的一个交互点加了它演示时就能现场展示「阈值调低 → 检出率上升、误报也上升」的关系比空口讲概念有用得多。从那以后我每次做这类课设都会强制自己走一遍「先看标签分布 → 再定评估口径 → 最后写接口」的顺序。数据没看清楚之前模型调得再花哨都是白搭。希望帮到你。本文还有配套的精品资源点击获取