机器学习网络入侵检测系统:从NSL-KDD到XGBoost的毕业设计实战 简介本资源为机器学习网络入侵检测系统Python源码与项目说明面向人工智能、通信工程、自动化、电子信息、物联网等计算机相关专业学生适用于毕业设计、课程设计、期末作业与实训等课题场景帮助读者快速获得一套可运行、可复现的入侵检测方案。压缩包共22个文件约12.99MB以7个py源码文件为核心配合9个xml配置、1个pt权重文件、1个md说明文档及若干工程配置文件覆盖数据预处理、SVM等机器学习算法、指标评估与网络流量嗅探等模块目录结构清晰便于按功能定位代码。项目代码及资料完整下载后可直接运行调试适合作为科研学习与二次开发的基础。目前已有75人学习下载可作为同类课题的参考实现帮助读者理解特征处理、模型训练与检测评估的完整链路并在此基础上完成自己的设计与实验。1. 从一份毕业设计说起机器学习网络入侵检测系统到底在做什么很多同学第一次接触「机器学习网络入侵检测系统」这个题目是在毕业设计选题表上。导师给的关键词往往就一行用机器学习做网络流量异常识别最好有 Python 源码和项目说明。听起来简单真动手才发现坑不少——数据集选哪个、特征怎么处理、模型怎么选、检测结果怎么评估每一步都能卡住人。这篇笔记就围绕这个方向把一套能跑通、能写进论文、能应付答辩的落地方案拆开讲清楚。网络入侵检测系统NIDS的核心任务是从网络流量数据里判断某条连接是正常访问还是攻击行为。传统做法靠规则匹配比如 Snort 那套特征库优点是解释性强缺点是遇到变种攻击就抓瞎。机器学习方案的价值在于不依赖人工写规则而是让模型从标注数据里自己学出「正常」和「异常」的边界。适合谁看正在做相关毕业设计的学生、想快速搭一个可演示原型的开发者、以及需要理解这套流程边界的技术负责人。下面从数据、特征、模型、评估四个环节依次展开每个环节都给可复现的操作。2. 数据集与特征工程把原始流量变成模型能吃的表格2.1 为什么 NSL-KDD 和 CIC-IDS 是绕不开的起点做入侵检测数据集选错后面全白搭。常见做法是先用 NSL-KDD 跑通流程再用 CIC-IDS2017 或 CIC-IDS2018 做扩展验证。NSL-KDD 是 KDD Cup 99 的改进版去掉了大量冗余记录训练集约 12 万条、测试集约 2 万条每条记录 41 个特征加 1 个标签。它的优点是体量小、类别清晰、论文引用多答辩时导师一看就认。缺点是年代久远攻击类型偏老容易过拟合。CIC-IDS 系列由加拿大网络安全研究所发布包含正常流量和多种攻击流量特征维度更高通常 70 多个更贴近现代网络环境。缺点是数据量大、类别不平衡严重直接训练容易偏向多数类。我一般建议论文主体用 NSL-KDD 讲清楚方法论再用 CIC-IDS 做一组对比实验证明方案不是只对老数据有效。提示数据集下载后先校验文件完整性NSL-KDD 的 KDDTrain.txt 和 KDDTest.txt 是标准命名别拿错版本。2.2 用 pandas 做特征清洗与数值化拿到数据后第一步是清洗。NSL-KDD 的 41 个特征里有 3 个是字符串类型protocol_type、service、flag需要做独热编码或标签编码。数值特征里有些量纲差异极大比如 src_bytes 可能到几百万而 duration 只有几十必须归一化。下面这段代码是标准的清洗流程import pandas as pd from sklearn.preprocessing import LabelEncoder, MinMaxScaler # 列名按 NSL-KDD 标准定义 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,difficulty] train pd.read_csv(KDDTrain.txt, namescol_names) test pd.read_csv(KDDTest.txt, namescol_names) # 把攻击类型归为五大类方便多分类 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 } train[label] train[label].map(attack_map) test[label] test[label].map(attack_map) # 类别特征编码 cat_cols [protocol_type,service,flag] for col in cat_cols: le LabelEncoder() full pd.concat([train[col], test[col]]) le.fit(full) train[col] le.transform(train[col]) test[col] le.transform(test[col]) # 数值特征归一化 num_cols [c for c in train.columns if c not in [label,difficulty]] scaler MinMaxScaler() train[num_cols] scaler.fit_transform(train[num_cols]) test[num_cols] scaler.transform(test[num_cols]) train.to_csv(train_clean.csv, indexFalse) test.to_csv(test_clean.csv, indexFalse)逻辑说明先按标准列名读入避免列错位attack_map 把 20 多种细粒度攻击归成 dos、probe、r2l、u2r 四大类加 normal这是论文里最常报告的分类粒度LabelEncoder 对三个类别特征做整数编码注意 fit 时用训练集加测试集的并集防止测试集出现训练集没见过的取值MinMaxScaler 把数值压到 0 到 1fit 只在训练集上做测试集用同一个 scaler 变换这是防止数据泄露的关键。参数说明MinMaxScaler 默认范围是 (0,1)如果数据里有极端离群值可以改用 StandardScaler 或 RobustScaler。LabelEncoder 对无序类别会引入虚假的大小关系严格来说应该用 OneHotEncoder但 NSL-KDD 的 service 有 70 个取值独热后维度爆炸实践中标签编码加树模型效果够用。2.3 特征选择41 维不是越多越好NSL-KDD 的 41 个特征里有些几乎全是 0比如 num_outbound_cmds对模型没有区分度。常见做法是用随机森林的 feature_importances_ 或卡方检验筛掉低贡献特征。我一般保留 20 到 25 维训练速度能快一倍准确率基本不掉。下面用随机森林做一轮特征重要性排序from sklearn.ensemble import RandomForestClassifier import numpy as np X train[num_cols].values y train[label].values rf RandomForestClassifier(n_estimators100, random_state42, n_jobs-1) rf.fit(X, y) importances rf.feature_importances_ idx np.argsort(importances)[::-1] for i in idx[:15]: print(f{num_cols[i]}: {importances[i]:.4f})跑完你会看到 src_bytes、dst_bytes、count、same_srv_rate 这几个排在最前面符合直觉——攻击流量在字节数和连接频率上确实和正常流量有差异。把重要性低于 0.005 的特征删掉重新训练对比准确率和训练时间论文里这就是一组消融实验。3. 模型选型与训练从逻辑回归到 XGBoost 的取舍3.1 为什么不能一上来就上深度学习很多同学觉得「机器学习」不够高级想直接上 LSTM 或 CNN。我的血泪经验是NSL-KDD 这种表格数据树模型随机森林、XGBoost、LightGBM的性价比远高于深度学习。原因有三一是数据量只有十几万条深度网络容易过拟合二是特征已经是统计量没有时序结构LSTM 的优势发挥不出来三是树模型训练快、调参少、可解释性强答辩时能说清楚每个特征怎么影响判断。常见做法是先用逻辑回归或决策树跑一个 baseline再用随机森林或 XGBoost 提升最后如果时间充裕可以加一个简单的全连接网络做对比。这样论文的实验部分层次分明导师也挑不出毛病。3.2 随机森林与 XGBoost 的训练代码下面这段是完整的训练与评估流程包含五折交叉验证和测试集评估from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.model_selection import cross_val_score from sklearn.metrics import classification_report, confusion_matrix, accuracy_score X_train train[num_cols].values y_train train[label].values X_test test[num_cols].values y_test test[label].values # 随机森林 rf RandomForestClassifier( n_estimators200, # 树的数量200 是精度和速度的平衡点 max_depth20, # 限制深度防过拟合 min_samples_split5, # 节点分裂最小样本数 random_state42, n_jobs-1 ) rf_scores cross_val_score(rf, X_train, y_train, cv5, scoringaccuracy) print(RF 五折准确率:, rf_scores.mean()) rf.fit(X_train, y_train) rf_pred rf.predict(X_test) print(RF 测试集报告:) print(classification_report(y_test, rf_pred)) # XGBoost xgb XGBClassifier( n_estimators300, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, use_label_encoderFalse, eval_metricmlogloss, random_state42 ) xgb_scores cross_val_score(xgb, X_train, y_train, cv5, scoringaccuracy) print(XGB 五折准确率:, xgb_scores.mean()) xgb.fit(X_train, y_train) xgb_pred xgb.predict(X_test) print(XGB 测试集报告:) print(classification_report(y_test, xgb_pred))逻辑说明cross_val_score 做五折交叉验证报告的是训练集内部的泛化能力比单次划分更可靠classification_report 输出每个类别的 precision、recall、f1-score入侵检测里 recall 比 accuracy 更重要因为漏报一个攻击的代价远大于误报一个正常流量confusion_matrix 可以直观看到哪类攻击被误判成了哪类。参数说明n_estimators 不是越大越好200 到 300 通常够用再大收益递减且训练变慢max_depth 控制模型复杂度随机森林设 20 左右XGBoost 设 6 左右因为 boosting 是串行叠加单棵树要浅learning_rate 是 XGBoost 的关键参数0.1 是常用起点调小到 0.05 需要相应增加 n_estimatorssubsample 和 colsample_bytree 是行采样和列采样比例0.8 能增加多样性、抑制过拟合。3.3 类别不平衡怎么处理NSL-KDD 里 u2r 和 r2l 的样本数远少于 dos 和 normal直接训练会导致模型几乎不预测这两类。常见做法有三种一是用 class_weightbalanced 让模型自动加权二是用 SMOTE 对少数类过采样三是调整评估指标不看 accuracy 看 macro-f1。我一般先用 class_weight简单且不容易引入噪声。SMOTE 要小心它是在特征空间插值对类别特征编码后的数据可能生成不合理的样本。rf_balanced RandomForestClassifier( n_estimators200, max_depth20, class_weightbalanced, # 按类别频率自动反比加权 random_state42, n_jobs-1 ) rf_balanced.fit(X_train, y_train) print(classification_report(y_test, rf_balanced.predict(X_test)))对比一下加权前后的 macro-f1如果 u2r 的 recall 从 0.1 提到 0.6 以上说明加权生效了。论文里可以把这组对比放进去说明你考虑到了类别不平衡问题。4. 避坑与排查五个让模型翻车的常见问题4.1 测试集准确率远低于训练集现象训练集准确率 99%测试集只有 75%。原因通常是数据泄露或分布不一致。NSL-KDD 的测试集里包含训练集没出现过的攻击类型这是设计好的不是 bug。解决不要用测试集调参把训练集再切一部分做验证集如果测试集准确率低但验证集正常说明模型对未知攻击泛化差可以尝试增加特征或换用对未知类更鲁棒的模型。4.2 归一化在划分数据之后做现象模型表现忽好忽坏换随机种子结果波动大。原因是先归一化再划分测试集的统计信息泄露到了训练过程。解决永远先划分训练集和测试集scaler 只在训练集上 fit再 transform 测试集。交叉验证时更要注意归一化必须放在 Pipeline 里让每一折内部独立 fit。4.3 把 difficulty 列当成特征现象模型准确率异常高但实际部署时完全不能用。原因是 NSL-KDD 的最后一列 difficulty 是数据集自带的难度标记不是流量特征误当成输入会导致标签泄露。解决读数据时明确列名把 label 和 difficulty 都排除在特征之外。4.4 忽略攻击类别的业务含义现象模型把 dos 攻击误判成 probeaccuracy 看起来还行但安全意义完全不同。原因是只看了整体准确率没看混淆矩阵。解决报告每个类别的 precision 和 recall重点关注 u2r 和 r2l 的 recall这两类漏报最危险。如果某类 recall 低于 0.5需要针对性处理。4.5 训练完不保存模型和 scaler现象论文写完了想演示发现每次都要重新训练或者预测时忘了用同一个 scaler。解决训练完立刻用 joblib 保存模型和 scaler预测时一起加载。import joblib joblib.dump(rf_balanced, nids_rf_model.pkl) joblib.dump(scaler, nids_scaler.pkl) joblib.dump(num_cols, nids_feature_cols.pkl) # 预测时 model joblib.load(nids_rf_model.pkl) scaler joblib.load(nids_scaler.pkl) cols joblib.load(nids_feature_cols.pkl)5. 从离线模型到可演示原型一个最小可用的检测脚本5.1 把模型包装成单条流量预测函数论文答辩时导师最常问的一句话是「你这个东西怎么用」。离线训练完的模型只是一个 pkl 文件需要包装成一个能接收单条流量记录、输出判断结果的函数。下面这个脚本模拟了从原始特征到预测的完整链路import joblib import numpy as np import pandas as pd model joblib.load(nids_rf_model.pkl) scaler joblib.load(nids_scaler.pkl) feature_cols joblib.load(nids_feature_cols.pkl) def predict_single(feature_dict): feature_dict: 包含 41 个原始特征的字典 返回: 预测类别和置信度 df pd.DataFrame([feature_dict]) # 类别特征编码这里用训练时的编码器实际项目要一起保存 # 简化处理假设输入已经编码好 X df[feature_cols].values X_scaled scaler.transform(X) pred model.predict(X_scaled)[0] proba model.predict_proba(X_scaled)[0] confidence np.max(proba) return pred, confidence # 模拟一条正常流量 sample {col: 0 for col in feature_cols} sample[duration] 0 sample[src_bytes] 200 sample[dst_bytes] 1000 sample[count] 5 print(predict_single(sample))逻辑说明predict_single 接收一个字典转成 DataFrame 后按训练时的特征顺序取列再用同一个 scaler 变换最后输出类别和置信度。置信度低于某个阈值时可以标记为「可疑」交给人工复核这是实际 NIDS 里常见的降级策略。参数说明置信度阈值一般设 0.7 到 0.8低于阈值不直接判定而是记录日志。这个阈值需要根据业务对误报和漏报的容忍度调整没有固定值。5.2 用 Flask 搭一个最小演示接口如果答辩需要现场演示可以用 Flask 起一个本地接口接收 JSON 格式的流量特征返回预测结果。代码不超过 30 行from flask import Flask, request, jsonify import joblib import numpy as np import pandas as pd app Flask(__name__) model joblib.load(nids_rf_model.pkl) scaler joblib.load(nids_scaler.pkl) feature_cols joblib.load(nids_feature_cols.pkl) app.route(/predict, methods[POST]) def predict(): data request.get_json() df pd.DataFrame([data]) X df[feature_cols].values X_scaled scaler.transform(X) pred model.predict(X_scaled)[0] proba model.predict_proba(X_scaled)[0] return jsonify({class: pred, confidence: float(np.max(proba))}) if __name__ __main__: app.run(host127.0.0.1, port5000)启动后用 curl 或 Postman 发一条 JSON 就能看到结果。这个原型虽然简单但足以说明「模型不是死的能对外提供服务」。论文里可以放一张接口调用的截图比只贴准确率数字有说服力。5.3 评估指标怎么选才经得起追问accuracy 在类别不平衡时具有欺骗性。假设 99% 是正常流量模型全猜正常也有 99% 准确率但一个攻击都抓不到。入侵检测里更该看的是指标含义为什么重要recall召回率实际攻击中被检出的比例漏报攻击代价高precision精确率报警中真实攻击的比例误报太多运维会麻木macro-f1各类 f1 的算术平均衡量少数类表现AUC排序能力不依赖阈值我一般会在论文里同时报告 accuracy、macro-f1 和各类 recall并解释为什么 macro-f1 比 accuracy 更能反映模型在入侵检测场景下的真实能力。导师如果追问「为什么不用 accuracy」这套说辞能接住。5.4 一个容易被忽略的技巧用混淆矩阵反推特征训练完看混淆矩阵如果发现 r2l 大量被误判成 normal可以回头查这些样本的哪些特征和 normal 接近。常见原因是 r2l 攻击在字节数、连接数上和正常流量差异小树模型难以区分。解决办法是补充领域特征比如登录失败次数、文件创建次数等。这个「从错误反推特征」的循环比盲目调参有效得多。我自己做的时候就是靠分析混淆矩阵把 r2l 的 recall 从 0.3 提到了 0.65。最后说个习惯每次实验都记录数据集版本、特征列表、模型参数和随机种子不然过两周自己都复现不出来。希望帮到你。本文还有配套的精品资源点击获取