简介:这是一份面向高校学生与入门开发者的网络攻击检测实战项目资料,基于经典的KDD-CUP99数据集,用Python完成从数据预处理、特征分析到攻击类型识别的完整流程,适合用作课程设计、期末大作业或网络安全方向的练手项目。压缩包共75个文件,约6.93MB,包含14个Python源码文件、10个编译缓存文件、17个JavaScript与7个Vue前端文件,以及json、css、html等配置与页面资源,另附sqlite3数据库、npy与pickle模型数据、data_10_percent_corrected数据集文件,前后端结构清晰。项目采用Django后端搭配Vue前端,目录中可见NetAnalyze、model、static等模块,便于理解检测系统的整体架构与数据流转。目前已有351人学习下载,源码经本地编译可运行,难度适中,内容经助教审定,能帮助读者掌握数据集处理、模型训练与可视化展示的完整思路,并可直接参考其目录组织与排错方式完成自己的项目。
1. 从一份能跑通的 KDD-CUP99 网络攻击检测项目说起
很多做安全方向课程设计或期末大作业的同学,卡点从来不是"没有想法",而是"想法有了,数据也下了,代码跑不起来"。KDD-CUP99 这个数据集几乎是人手一份,但真正把它从原始 txt 清洗成模型能吃的特征矩阵,再套上一个能演示的前端界面,中间要填的坑比想象中多。这份资源给的是一个完整闭环:Python 侧做数据预处理和攻击分类建模,Django 提供后端接口,Vue 搭一个可视化分析页面,目录里KDD-CUP99-Analysis-main、net-analyze、Django、model几个文件夹各司其职。它适合三类人:赶期末大作业的学生、想快速搭一个入侵检测 demo 的初学者、以及需要一份可改可扩的基线代码的开发者。下面我按"数据怎么进、模型怎么出、界面怎么连、坑在哪"的顺序拆一遍。
2. 数据管道拆解:KDD-CUP99 从原始 txt 到特征矩阵
2.1 先搞清楚这份数据集到底长什么样
KDD-CUP99 的原始文件通常是kddcup.data_10_percent这类无表头的 CSV,每行 41 个特征加 1 个标签,标签形如normal.、smurf.、neptune.,注意末尾带点。41 维里混了三种类型:连续数值(duration、src_bytes)、离散符号(protocol_type、service、flag)、以及大量取值极不均衡的计数特征(count、srv_count、dst_host_count)。很多人一上来直接pd.read_csv然后丢进模型,结果报could not convert string to float,就是因为没处理符号列。
常见做法是先把列名补上,再做类型分离。下面这段是我一般会先跑的探查脚本:
import pandas as pd # KDD-CUP99 标准 41 维特征名 + 标签列 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' ] df = pd.read_csv('kddcup.data_10_percent', names=col_names) print(df['label'].value_counts().head(10)) # 先看类别分布,别急着建模 print(df.dtypes.value_counts()) # 看有多少 object 列需要编码逻辑说明:names参数把无表头文件补成有语义的列,方便后续按列名操作。value_counts用来确认标签分布——KDD-CUP99 里smurf和neptune两类能占到七成以上,这个不均衡会直接影响后面模型评估,不能忽略。dtypes则告诉你哪些列是字符串,需要单独编码。
参数上要注意:如果你用的是corrected版本,标签种类会更多,且部分攻击类型在训练集里没出现过,这是官方故意设计的泛化测试,别当成数据错误。
2.2 符号列编码与标签归并
protocol_type、service、flag三列是字符串,必须转成数值。这里有两种路线:LabelEncoder 或 One-Hot。service有 70 个左右取值,One-Hot 会让维度暴涨,我一般对protocol_type和flag用 One-Hot,对service用频次编码或 LabelEncoder。标签侧则要把几十种具体攻击归并成四大类:DoS、Probe、R2L、U2R,外加 normal。
from sklearn.preprocessing import LabelEncoder # 标签归并:把具体攻击名映射到 5 大类 attack_map = { 'normal': 'normal', 'back':'dos','land':'dos','neptune':'dos','pod':'dos','smurf':'dos','teardrop':'dos', 'ipsweep':'probe','nmap':'probe','portsweep':'probe','satan':'probe', 'ftp_write':'r2l','guess_passwd':'r2l','imap':'r2l','multihop':'r2l', 'phf':'r2l','spy':'r2l','warezclient':'r2l','warezmaster':'r2l', 'buffer_overflow':'u2r','loadmodule':'u2r','perl':'u2r','rootkit':'u2r' } # 去掉标签末尾的点再映射 df['label'] = df['label'].str.rstrip('.') df['attack_cat'] = df['label'].map(attack_map) df = df.dropna(subset=['attack_cat']) # 丢掉映射表没覆盖的罕见类 # 符号特征编码 for col in ['protocol_type', 'flag']: df[col] = LabelEncoder().fit_transform(df[col]) df['service'] = df['service'].map(df['service'].value_counts()) # 频次编码逻辑说明:rstrip('.')处理标签尾点,这是 KDD-CUP99 最经典的坑之一,不处理会导致map全部返回 NaN。attack_map把 20 多种细类压成 5 类,既降低分类难度,也符合多数课程设计对"攻击类型识别"的要求。service用频次编码而不是 LabelEncoder,是因为它的类别没有天然顺序,LabelEncoder 会引入虚假的大小关系。
参数提醒:如果你的任务要求识别具体攻击名而非大类,就别做归并,直接对label做 LabelEncoder,但要注意评估时用 macro-F1 而不是 accuracy。
2.3 数值归一化与训练集划分
连续特征量纲差异极大,src_bytes能到上亿,serror_rate只在 0 到 1 之间。不归一化的话,基于距离的模型(KNN、SVM)会被大数值特征主导。我一般用 MinMaxScaler 压到 [0,1],树模型则可以跳过这步。
from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split feature_cols = [c for c in df.columns if c not in ['label','attack_cat']] X = df[feature_cols].astype(float) y = df['attack_cat'] scaler = MinMaxScaler() X_scaled = scaler.fit_transform(X) X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.3, random_state=42, stratify=y ) print(X_train.shape, X_test.shape)逻辑说明:stratify=y保证训练集和测试集里各类比例一致,否则样本极少的 U2R 类可能整个落进测试集,导致训练时没见过。random_state=42固定随机种子,方便复现结果。astype(float)是为了防止某些列被 pandas 推断成 object 后 scaler 报错。
注意:scaler 只能在训练集上fit,然后transform测试集,否则会造成数据泄漏。上面为了简洁用了全量 fit,严格做法是先切分再分别处理,这一点在答辩时容易被追问。
3. 模型训练与 Django 后端接口打通
3.1 选哪个分类器:从基线到可用
课程设计里最常见的做法是先跑一个随机森林当基线,再对比 SVM 或 XGBoost。随机森林对特征量纲不敏感、训练快、可解释性好(能输出特征重要性),非常适合 KDD-CUP99 这种混合类型数据。我一般会同时训练两三个模型做对比表。
from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix rf = RandomForestClassifier( n_estimators=100, # 树的数量,100 是速度与精度的平衡点 max_depth=None, # 不限制深度,让树充分生长 class_weight='balanced', # 缓解类别不均衡 random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train) y_pred = rf.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))逻辑说明:class_weight='balanced'是关键参数,它按类别频率自动加权,让模型不会因为 normal 样本多就无脑预测 normal。n_jobs=-1用满所有 CPU 核。classification_report里重点看 U2R 和 R2L 的 recall,这两类样本少,最容易漏检。
参数怎么调:如果 recall 太低,先加n_estimators到 200,再考虑max_depth限制过拟合。如果训练集准确率 99% 但测试集掉到 90%,多半是过拟合,可以降深度或加min_samples_leaf。
3.2 模型持久化与 Django 侧加载
训练完的模型要存下来供后端调用,常见做法是 joblib 或 pickle。Django 里我一般把模型文件放在model/目录,在视图函数里加载一次并缓存,避免每次请求都重新读盘。
import joblib # 训练侧:保存模型和 scaler joblib.dump(rf, 'model/rf_model.pkl') joblib.dump(scaler, 'model/scaler.pkl') # Django 侧:views.py 中加载 import joblib import numpy as np from django.http import JsonResponse MODEL = joblib.load('model/rf_model.pkl') SCALER = joblib.load('model/scaler.pkl') def predict(request): if request.method == 'POST': features = request.POST.getlist('features') # 前端传来的 41 维 arr = np.array(features, dtype=float).reshape(1, -1) arr = SCALER.transform(arr) pred = MODEL.predict(arr)[0] return JsonResponse({'attack_cat': pred})逻辑说明:模型在模块加载时读一次,放在全局变量里,这是 Django 里最省资源的做法。reshape(1,-1)把单条样本变成二维数组,因为 sklearn 的predict要求二维输入。getlist用于接收前端表单里的多个字段。
参数提醒:model/目录路径建议用os.path.join(BASE_DIR, 'model', ...)拼绝对路径,否则换台机器跑就找不到文件。这是新手最常翻车的地方之一。
3.3 前后端联调:Vue 发请求到 Django
前端在net-analyze目录,是 Vue 项目,通过 axios 调 Django 接口。跨域是必踩的坑,Django 侧要装django-cors-headers并在 settings 里放行。
# settings.py INSTALLED_APPS = [ # ... 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # 必须放在 CommonMiddleware 之前 # ... ] CORS_ALLOW_ALL_ORIGINS = True # 开发阶段图省事,上线要收紧// Vue 侧调用 import axios from 'axios' export function predictAttack(features) { return axios.post('http://127.0.0.1:8000/api/predict/', features) }逻辑说明:CorsMiddleware的位置很关键,放在CommonMiddleware之前才能正确给响应加跨域头。CORS_ALLOW_ALL_ORIGINS只适合本地开发,正式部署要改成白名单。Vue 侧把请求封装成函数,方便在组件里复用。
注意:Django 默认的 CSRF 保护会拦截 POST,开发阶段可以在视图上加@csrf_exempt,但生产环境不要这么干。
4. 避坑与排查:这份源码最容易翻车的五个地方
4.1 标签末尾的点没去掉,映射全变 NaN
现象:跑attack_map之后df['attack_cat']全是 NaN,dropna一执行数据全没了。原因:KDD-CUP99 原始标签是normal.、smurf.这种带尾点的格式,映射表的 key 写的是不带点的。解决:在 map 之前先df['label'] = df['label'].str.rstrip('.'),或者映射表 key 直接带点。这个坑我见过太多人卡一整天。
4.2 训练集测试集划分没加 stratify,稀有类全丢
现象:模型对 U2R 类的 recall 是 0,混淆矩阵里那一行全是 0。原因:U2R 样本占比不到 0.1%,随机划分时可能整个落进测试集,训练集里一条都没有。解决:train_test_split加stratify=y,或者对稀有类做 SMOTE 过采样。加 stratify 是最省事的做法。
4.3 scaler 在全量数据上 fit,造成数据泄漏
现象:测试集准确率高得离谱,换一批新数据就崩。原因:归一化时用了包含测试集的全部数据来 fit,测试集的信息泄漏进了训练过程。解决:先切分,再在X_train上fit_transform,对X_test只transform。答辩时老师很爱问这个点。
4.4 Django 找不到模型文件,路径写成了相对路径
现象:本地跑得好好的,换台电脑或换个启动目录就报FileNotFoundError。原因:joblib.load('model/rf_model.pkl')是相对当前工作目录的,Django 启动目录一变就失效。解决:用os.path.join(settings.BASE_DIR, 'model', 'rf_model.pkl')拼绝对路径。这是部署环节最典型的血泪经验。
4.5 跨域请求被拦,前端一直报 CORS 错误
现象:浏览器控制台报has been blocked by CORS policy,接口明明能 curl 通。原因:Django 没配django-cors-headers,或者中间件顺序放错了。解决:装包、加corsheaders到 INSTALLED_APPS、把CorsMiddleware放到CommonMiddleware之前、设置允许的来源。三步缺一不可。
5. 进阶玩法:把检测结果做成可视化大屏并验证模型边界
5.1 用混淆矩阵和特征重要性做结果验证
光看 accuracy 没有说服力,KDD-CUP99 上随便一个模型都能到 99%,因为 normal 和 DoS 占了绝大多数。真正能体现水平的是分类报告里的 macro-F1 和各类 recall。我一般会在项目里加一个可视化页面,把混淆矩阵和特征重要性画出来。
import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm = confusion_matrix(y_test, y_pred, labels=rf.classes_) plt.figure(figsize=(8, 6)) sns.heatmap(cm, annot=True, fmt='d', xticklabels=rf.classes_, yticklabels=rf.classes_) plt.xlabel('Predicted') plt.ylabel('True') plt.title('Confusion Matrix') plt.savefig('static/confusion_matrix.png') # 特征重要性 Top 10 import pandas as pd imp = pd.Series(rf.feature_importances_, index=feature_cols).sort_values(ascending=False) print(imp.head(10))逻辑说明:labels=rf.classes_保证混淆矩阵的行列顺序和模型类别一致,不然标签会对错位。热力图存成图片后可以被 Django 模板直接引用,前端展示时不用实时计算。特征重要性排前几的通常是src_bytes、dst_bytes、count、same_srv_rate这几个,和领域经验吻合,可以拿来在报告里佐证模型学到了合理的东西。
参数提醒:fmt='d'表示显示整数,如果做了归一化混淆矩阵就改成.2f。annot=True把数值标在格子里,样本多的时候可能挤,可以调figsize。
5.2 用 corrected 数据集测泛化边界
KDD-CUP99 有个corrected版本,里面包含训练集没出现过的攻击类型。拿它当测试集,能看出模型是真的学到了攻击模式,还是只是记住了训练集里的类别。我一般会做这个对比实验:
| 测试集 | 准确率 | macro-F1 | 说明 |
|---|---|---|---|
| 10_percent 同分布测试 | 0.99+ | 0.95+ | 常规指标,参考价值有限 |
| corrected 全集 | 0.90 左右 | 0.70 左右 | 泛化能力真实体现 |
| corrected 中未知攻击 | 明显下降 | 明显下降 | 暴露模型边界 |
这张表放进报告里,比单纯写"准确率 99%"有说服力得多。如果 corrected 上掉得特别厉害,说明模型过拟合了训练集的攻击类型,可以考虑加正则化或换更强的特征工程。
5.3 一个我踩过的坑:别把测试集当验证集反复调参
刚做这个项目时,我拿测试集反复试不同的n_estimators和max_depth,挑了个测试集上最高的组合,结果答辩时被问"你怎么保证这个参数不是过拟合测试集",当场答不上来。正确做法是从训练集里再切一份验证集,或者用交叉验证选参数,测试集只在最后评估时用一次。从那以后我每次调参都强制走一遍cross_val_score,再也没在这个问题上翻过车。
from sklearn.model_selection import cross_val_score scores = cross_val_score(rf, X_train, y_train, cv=5, scoring='f1_macro') print('CV macro-F1:', scores.mean(), scores.std())逻辑说明:cv=5做五折交叉验证,scoring='f1_macro'对不均衡数据比 accuracy 更合适。scores.std()看稳定性,标准差大说明模型对数据划分敏感,需要再调。
这份资源的完整源码和资料都在压缩包里,Django 后端、Vue 前端、模型文件、数据集处理脚本一应俱全,拿到手按上面的顺序跑一遍就能复现。希望帮到你。
本文还有配套的精品资源,点击获取