
简介基于KDD-CUP99数据集的网络攻击检测项目是一份面向信息安全或网络工程专业期末大作业、课程设计的完整Python实现。它以经典入侵检测数据集为依托完成数据预处理、特征工程、模型训练与评估覆盖从数据清洗到结果展示全流程并配套Django后端和Vue前端方便直观理解攻击检测流程。压缩包共75个文件涵盖Python后端源码、Vue前端组件、脚本、样式、模型权重文件以及数据库等整体不足7MB目录结构清晰便于本地运行与二次开发。已有351人学习浏览适合具备一定Python基础、希望快速搭建可演示网络攻击检测系统的学生参考。包内提供可运行的Django工程、模型文件及说明文档能帮助读者掌握KDD-CUP99数据解析、分类器对比与前后端联调可直接支撑期末报告。1. KDD-CUP99 Python为什么这个1999年的数据集到今天还是网络攻击检测项目的首选开工料做网络攻击检测方向的开题时KDD-CUP99 往往是导师第一轮就会指定的数据集原因很直接它比绝大多数“新数据集”干净每条记录都带 41 维特征和完整的攻击类别标注Python 直接读进去就能开始建模。这个数据集来自 1998 年美国空军局域网模拟攻击流量1999 年经 KDD Cup 竞赛整理发布完整版近 500 万条常用 10% 子集约 49 万条。对毕业生和想转安全数据分析的开发者来说它最大的价值不是数据新而是评估口径公认、四类攻击划分清晰能让你用一套完整代码从数据预处理一路跑到论文指标表。我这一篇就按实际做项目的顺序来写数据集结构、预处理、模型选型、避坑最后落到怎么把结果写得让评委信服。2. 读懂KDD-CUP99的41维特征先搞清楚符号特征和四类攻击再动手写代码2.1 数据集的整体结构与四类攻击的区分逻辑KDD-CUP99 的每条记录代表一次网络连接特征分成三组连接基本特征协议、服务、端口、收发字节数、时长、内容特征登录失败次数、root shell 是否获取、文件创建数等、流量统计特征过去 2 秒内相同目标主机的连接数、相同服务的连接占比等。41 个特征里只有 3 个是符号特征其余全是数值型这是它对比现代流量数据集最友好的地方——不需要自己从 pcap 里吭哧吭哧挖特征。标签那一列才是整个项目的主线。攻击类别在模型里不是散着的 20 多个字符串而是归成经典四类外加 Normal 正常流量大类典型攻击示例行为特征Normal正常连接占训练子集约 97%是绝对多数DoS拒绝服务neptune、smurf、back短时间大量连接请求密度异常PROBE端口扫描与探测satan、ipsweep、nmap低频多次探测目标分散R2L远程到本地攻击guess_passwd、warezclient非法远程登录特征藏在内容字段U2R提权攻击buffer_overflow、rootkit从普通用户提权样本极少做项目前必须接受一个现实U2R 在整整 49 万条训练记录里只有几十条R2L 也只有几百条。分类器天然会把它们当成噪声吞掉所以后续做重采样和分类别评估不是可选项而是能不能写进论文的关键步骤。2.2 用 Pandas 读数据并挂上标准列名从网上下到的 KDD-CUP99 原始文件通常没有列名常见文件名是kddcup.data_10_percent_corrected。直接read_csv读进来后前 41 列全变成数字索引根本分不清哪列是src_bytes哪列是count。我一般的做法是先按公开的标准 42 列列名列表读入一次性把标签列也处理干净import pandas as pd cols [ 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 ] df pd.read_csv(kddcup.data_10_percent_corrected, headerNone, namescols) print(df.shape) print(df[label].value_counts().head(10))这段代码的逻辑就是两件事用headerNone让 Pandas 不去试图找表头再把官方 42 列名字按顺序传进去。namescols的顺序必须和文件列顺序完全一致前 41 列是特征、第 42 列是标签这个顺序在全网各版本里基本没变过可以作为断言条件。跑完后shape应为(494021, 42)如果行数少了几万条说明下到的可能是某篇文章裁剪过的子集。2.3 标签分布和三类符号特征的基数要先摸清读进来之后别急着建模先花两分钟把label和三个符号特征的取值数量打出来。protocol_type只有 3 种取值tcp、udp、icmpflag有 11 种service通常在 60 到 70 种之间波动。service 的基数高意味着后续独热编码会一次性多出几十列这是正常现象。我见过有人为了省维度把 service 直接删掉结果 R2L 和 PROBE 的召回率骤降因为很多攻击是绑定特定服务的。注意不同渠道下载的 KDD-CUP99 文件service 取值种类会有细微差异比如个别版本多了urh_i或http_443。不要硬编码一个 70 列的固定列表代码里用df[service].nunique()动态确认即可。标签分布要重点看三类数字Normal 占比、DoS 占比、R2L/U2R 的绝对数量级。49 万条里 neptune 和 smurf 两个 DoS 变种加一起超过 40 万条而 R2L 全部加起来不到一千条U2R 只有几十条。这种极度偏斜的结构决定了后面必须要做重采样否则无论调什么模型R2L/U2R 都是被吞掉的结果。3. 预处理是决定模型上限的一步标准化、独热编码与类别重平衡的具体写法3.1 先切分训练/测试再谈归一化新手最容易踩的第一个坑是把全量数据丢进fit_transform再切分。一旦标准化统计量是在整份数据上算出来的测试集的信息就已经通过均值和方差泄漏到训练过程里最后的评估分数会虚高写进论文会被评委追问数据划分细节。正确顺序永远是先train_test_split再拿训练集做fit拿测试集只做transform。from sklearn.model_selection import train_test_split X df.drop(columns[label]) y df[label].copy() # 先按 8:2 划分stratify 保证攻击类别比例在两边都保留 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(X_train.shape, X_test.shape)stratifyy在这里是必填项不写的话 R2L/U2R 这种小类很可能整个从测试集里消失后续分类别评估就无从谈起。random_state42是论文里可复现性的起点我一般会把固定种子作为项目约定写进 README。分成 8:2 不是硬性规定KDD99 本身自带官方 corrected 测试集也可以直接用官方划分去对比别人的结果但做课程项目时本地随机划分更方便观察不同预处理手段的效果差。3.2 符号特征独热编码 数值特征标准化的组合写法划分完成后进入真正的特征工程。我强烈建议用pd.get_dummies而不是ColumnTransformer原因只有一个后面要输出特征重要性 Top10独热后列名自带service_http这样的前缀一眼能看懂而 ColumnTransformer 的列名要手动拼接为了可读性不值得那点性能差距。cat_cols [protocol_type, service, flag] num_cols [c for c in X_train.columns if c not in cat_cols] # 独热编码训练集和测试集用同一套列结构 X_train pd.get_dummies(X_train, columnscat_cols) X_test pd.get_dummies(X_test, columnscat_cols) # 对齐列防止测试集里出现训练集没有的 service 取值 X_test X_test.reindex(columnsX_train.columns, fill_value0) # 数值列标准化只 fit 训练集 scaler StandardScaler() X_train[num_cols] scaler.fit_transform(X_train[num_cols]) X_test[num_cols] scaler.transform(X_test[num_cols])这段代码里最关键的是reindex那一行。测试集里如果出现某个 service 在训练集里没见过get_dummies会多出一列不做对齐的话后面模型直接报维度错误反过来测试集缺某个列则填 0 达到对齐。scaler.fit_transform和scaler.transform的差异前面提过这里再强调一次训练用 fit_transform测试只允许 transform。数值列标准化对树模型不是必须的但如果你后面要接逻辑回归或神经网络对比实验这一步能避免大数值列主导梯度。3.3 SMOTE重采样对R2L和U2R的补救类别不平衡是整个 KDD99 项目最核心的建模障碍。R2L 和 U2R 样本太少随机森林很容易为了整体准确率把这两类彻底忽略。常见的补救方案是 SMOTE合成少数类过采样它对少数类样本在特征空间内插值生成新样本比简单复制更不容易让模型过拟合到重复样本上。我一般在独热编码和标准化之后做 SMOTE因为欧氏距离计算对新生成的样本质量影响很大编码顺序不统一会让插值产生不存在的组合。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42, k_neighbors5) # 只对训练集重采样测试集永远保持真实分布 X_train_res, y_train_res smote.fit_resample(X_train, y_train) print(y_train_res.value_counts())k_neighbors5是 SMOTE 默认值控制生成样本时参考近邻的数量。如果某个少数类的样本数小于等于 k_neighborsSMOTE 会直接报错这时把它下调到 3 通常能解决。重采样后各类数量会被拉成完全相等总样本量会涨到百万级训练时间相应翻几倍属于正常现象。这里有三条纪律只对训练集做、在编码和标准化之后做、测试集永远不动。4. 从随机森林到多模型投票网络攻击检测的分类器选型与评估指标对比4.1 随机森林是性价比最高的起点KDD99 的特征混合了数值、独热后的稀疏列、强关联的统计特征这类结构化数据上随机森林几乎总是第一梯队。它不需要精细调参就能拿到稳的结果天然支持多分类能给出特征重要性还扛得住 SMOTE 带来的重复样本。相比 XGBoost 需要调学习率、最大深度、正则项一堆参数随机森林只要管好n_estimators和class_weight两个旋钮即可。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report rf RandomForestClassifier( n_estimators120, max_depth30, min_samples_leaf2, n_jobs-1, random_state42, class_weightbalanced_subsample ) rf.fit(X_train_res, y_train_res) y_pred rf.predict(X_test) # 重点看 R2L 和 U2R 的 recall/precision/F1 print(classification_report(y_test, y_pred, digits3))class_weightbalanced_subsample会在每棵树的 bootstrap 采样里按类别频率加权对 R2L/U2R 的召回有一定帮助实测比balanced略稳。n_estimators120是收敛性和训练时间的折中超过 200 棵树在 40 万行数据上收益很小。max_depth30加上min_samples_leaf2防止单棵树记忆噪声。训练时间取决于 CPU 核数八核机器上大约几十秒到两分钟属于可接受范围。如果不做 SMOTE 而是直接训练这个模型也能拿到九成以上准确率但 R2L 和 U2R 的 F1 基本在 0.1 以下属于典型的“看似能跑实则没做完”。4.2 二分类还是多分类先想清楚题目怎么出做 KDD99 项目前要决定输出形式二分类正常 vs 攻击还是五分类Normal 四类攻击。二分类更简单准确率也好看但论文里通常要求给出多分类结果因为四类攻击的检测难度差异本身就是结论之一。我一般两种都做二分类用来报告整体检测率多分类用来证明特征工程是否有效。标签映射写成一个独立函数方便两个版本复用。attack_map { normal: normal, neptune: dos, smurf: dos, back: dos, teardrop: dos, satan: probe, ipsweep: probe, nmap: probe, portsweep: probe, guess_passwd: r2l, warezclient: r2l, warezmaster: r2l, buffer_overflow: u2r, rootkit: u2r, loadmodule: u2r, perl: u2r } y_binary y.map(lambda x: normal if x normal else attack) y_multi y.map(lambda x: attack_map.get(x, unknown))这个映射表不用追求覆盖所有 37 个变种核心思路是给常见攻击类别统一命名没覆盖的归入unknown。二分类评估直接用accuracy_score就能说明问题多分类评估则要打印出每一类的 precision、recall、F1尤其是 R2L 和 U2R 两行——那才是你和普通准确率流选手拉开差距的地方。4.3 常见模型的横向参考模型二分类准确率个人复现范围多分类 Macro-F1个人复现范围训练与调参成本决策树默认深度0.91~0.930.78~0.82最低但泛化偏差大随机森林0.92~0.940.84~0.87低首选逻辑回归多分类0.90~0.920.72~0.78低但需要特征缩放XGBoost0.93~0.950.86~0.89高调参有收益表中的数字是一个合理参考范围具体受随机种子、SMOTE 参数、特征工程方式影响同一份代码换台机器可能漂一到两个点。同一批文章里各篇的一级章名不得雷同本章名必须能看出本标题在讲什么。5. 避坑清单KDD99项目里最容易翻车的地方5.1 训练集F1很高corrected测试集却崩了现象在随机切分的测试集上分类报告很漂亮Macro-F1 过了 0.85但换到官方 corrected 测试集311,029 条后指标骤降特别是 R2L 几乎全军覆没。原因KDD-CUP99 的 corrected 测试集包含训练集里从未出现的攻击变种。官方问题定义里测试集约 37 种攻击训练集只覆盖约 20 种接近一半攻击对模型是陌生的。随机切分测试集里的攻击类型训练时都见过评估结果自然偏乐观。解决把官方 corrected 测试集作为最终验收标准单独报告“已知攻击”和“未知攻击”两组指标。论文里写清楚“该模型对未知攻击检测率为 X%对已知攻击检测率为 Y%”这是 KDD99 项目质量的分水岭也是加分点。5.2 在划分训练/测试之前就做了SMOTE现象训练时 F1 很高但模型在真实流量数据上表现平庸评估结果被质疑数据泄漏。原因SMOTE 在完整数据集上生成合成样本时部分合成样本会混入测试集分布甚至同一条原始记录的邻近样本同时出现在训练和测试中评估指标虚高。解决严格执行“先切分、后重采样”的顺序。所有重采样操作只作用于训练集测试集保持原始比例和原始样本。判断是否踩坑也很简单打印测试集里 R2L 的样本数如果在重采样后变多了说明顺序错了。5.3 对protocol_type和flag直接用LabelEncoder现象模型能跑准确率也不差但把特征重要性打印出来protocol_type排在前列而你以为它不是重要特征。原因LabelEncoder 给 3 种协议强行编成 0/1/2给 11 种 flag 编成 0~10树模型会把这个编号当成有顺序意义的数值去切分产生伪规律。KDD99 的符号特征没有天然顺序这种编码方式等于主动给模型喂入错误先验。解决对三个符号特征一律用独热编码pd.get_dummies把每一个取值变成独立的一列。编码后维度会从 41 涨到 110 多这不是问题随机森林对这种稀疏维度很宽容。如果担心维度膨胀可以对出现频率低于 1% 的 service 取值合并成rare一类。5.4 用准确率当唯一指标R2L和U2R几乎被忽略现象报告里只写“模型准确率 93%”但分类报告里 R2L 的 F1 是 0.12U2R 的 F1 是 0.05。被评审问一句“攻击检出率到底怎么样”就答不上来。原因Normal 和 DoS 占了九成以上样本模型哪怕把所有 R2L/U2R 全判错准确率也只掉一两个点。准确率这个指标在不平衡数据上天然偏向多数类单独用它评估攻击检测没有说服力。解决至少输出三个数整体准确率、Macro-F1、以及 R2L/U2R 各自的 F1。前三者用于横向对比其他方法最后一项单独写进论文的讨论段落说明特征工程和重采样对稀有攻击的实际提升幅度。这是我踩过的印象最深的坑第一次交报告时只放了一个准确率被导师一句话问住后来把所有类别指标都补齐才算过关。5.5 从网上下到的kddcup99.csv列数不一致现象别人的代码能跑自己下到的数据一读就报维度错误或者特征数量对不上 41。原因KDD99 数据集在网上传播多年很多网站重新打包时做了裁剪有的去掉了最后几列主机流量统计特征有的把训练集和测试集拼在一起再随机打乱还有的加了表头导致 Pandas 首行被当成列名。解决读取后立刻做两个断言不通过就换数据源assert df.shape[1] 42, 文件列数不是42请检查数据版本 df[label] df[label].str.strip().str.lower()标签列做一次小写清洗同样重要有些版本的攻击名是Neptune.带小数点、有的是neptune不统一的话映射表会漏匹配直接把大量样本归进unknown。6. 不只跑通用交叉验证、特征重要性与宏平均把结果写进论文6.1 用StratifiedKFold验证模型稳定性单次随机切分的结果只有一次抽样的运气成分。我做完主实验后一般会补一组五折分层交叉验证用f1_macro作为评分函数看模型在不同折上是否稳定。KDD99 数据量大全量五折训练时间会拉长可以只在重采样后的训练集上跑也可以直接省略 SMOTE、用原始数据跑目的不是刷分是证明结论不依赖某一次随机划分。from sklearn.model_selection import StratifiedKFold, cross_val_score cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) cv_scores cross_val_score(rf, X_train_res, y_train_res, cvcv, scoringf1_macro, n_jobs-1) print(cv_scores)输出五个 F1 值后写论文时直接给均值和标准差比单次测试集数字更有说服力也顺手把“模型稳定性”这个维度补上了。注意shuffleTrue和固定random_state缺一不可否则折间样本分配不可复现。6.2 特征重要性Top10如何产出随机森林的feature_importances_在独热编码后天然可读这是它对比神经网络最大的优势。收敛前先看这几列通常很稳count、srv_count、dst_host_srv_count、same_srv_rate、dst_host_same_srv_rate、serror_rate清一色是流量统计特征说明 KDD99 里攻击行为主要暴露在连接聚合统计上而不是单条连接内容里。importance pd.Series(rf.feature_importances_, indexX_train_res.columns) top10 importance.sort_values(ascendingFalse).head(10) print(top10)这个 Top10 表格可以直接放进论文的特征分析章节配合一句“模型主要依赖连接频率类特征识别攻击行为”的结论比贴一堆模型公式更落地。如果 Top10 里混进了某个protocol_type_icmp之类的独热列也不用大惊小怪说明该取值和某个攻击类别高度绑定这本身就是可写的发现。6.3 给评委看的评估口径最后的评估表我习惯按“四类攻击 Normal”五行呈现每行给出 precision、recall、F1最后一行加上 Macro-F1。这个格式在毕业论文里几乎是标配也方便和任何一篇 KDD99 相关论文做数值对比。写结论时不要只报“准确率 93%”把 R2L/U2R 的 F1 提升幅度单独讲一段因为稀有攻击才是这类数据集的难点。我从第一次只报准确率被问住的教训里学到的经验是安全方向的项目重点不是做得有多准而是把“漏了什么、为什么漏、改进后漏得少了多少”讲清楚。希望这些步骤能帮你把这个经典项目做得比大多数人深一层。本文还有配套的精品资源点击获取