2026/10/12 18:51:21

基于机器学习的入侵检测系统实战:从数据预处理到模型部署

基于机器学习的入侵检测系统实战:从数据预处理到模型部署 简介这份资源是面向计算机相关专业学生与教师的高分Python毕业设计资料主题为基于机器学习的入侵检测系统适合用作毕设项目、课程设计、作业或项目初期立项演示也便于初学者进阶学习。压缩包共23个文件约19KB以py源码、xml配置、md说明文档、gitignore与license等工程文件为主其中Python脚本承担数据处理与SVM等机器学习算法实现xml与md用于配置和文档说明整体结构清晰、便于二次修改与功能扩展。资源内代码均经过测试运行成功功能正常可直接下载使用。目前已有550人学习下载读者可从中获得完整的入侵检测系统实现方案、数据集、详细文档以及可复用的机器学习建模与排错思路既能作为毕设参考也能在此基础上修改实现其他功能。1. 从一份毕业设计标题里拆出可落地的入侵检测方案毕业设计选「基于机器学习的入侵检测系统」很多人第一反应是找一份源码跑通交差。但真正做过的人知道这个题目的价值不在答辩那十分钟而在于它逼你把机器学习从fit()到predict()之间的整条链路走一遍数据怎么洗、特征怎么选、模型怎么调、误报怎么压。入侵检测系统IDS本质是一个二分类或多分类问题——把网络流量分成正常和异常或者进一步区分攻击类型。它适合两类人一是想拿一个完整项目练手的在校生二是刚转安全方向、需要快速建立「数据到模型到检测」闭环的工程师。这篇笔记按我实际搭这套系统的顺序展开从环境到数据到模型到排错每一步都给出可复现的命令和参数你照着走能跑通也能看到边界在哪。2. 环境搭建与数据集选择先让数据能读进来2.1 Python 环境与依赖库的版本约束做机器学习项目环境是第一道坎。我一般用 conda 建独立环境避免和系统 Python 打架。Python 版本选 3.9 或 3.10太新的版本有些库轮子还没跟上太老的又缺特性。核心依赖就几个numpy、pandas 做数据处理scikit-learn 做模型和评估matplotlib 和 seaborn 画图imbalanced-learn 处理类别不平衡。如果你打算用深度学习路线再装 PyTorch 或 TensorFlow但毕业设计层面sklearn 加 XGBoost 或 LightGBM 足够打。# 创建并激活环境 conda create -n ids python3.10 -y conda activate ids # 安装核心依赖指定版本避免兼容问题 pip install numpy1.24.3 pandas2.0.3 scikit-learn1.3.0 pip install matplotlib3.7.2 seaborn0.12.2 imbalanced-learn0.11.0 pip install xgboost1.7.6 lightgbm4.0.0 joblib1.3.1这里锁版本不是矫情。sklearn 1.3 之后有些 API 变了比如train_test_split的stratify行为更严格imbalanced-learn 0.11 和 sklearn 1.3 搭配才不报check_array的警告。装完用python -c import sklearn; print(sklearn.__version__)验证一下版本对不上后面报错会浪费你半天。2.2 入侵检测数据集怎么选NSL-KDD 与 CIC-IDS 的取舍数据集是这类项目的命门。常见的有 NSL-KDD、UNSW-NB15、CIC-IDS2017/2018。NSL-KDD 是 KDD99 的改进版去掉了冗余记录41 个特征训练集约 12 万条测试集约 2 万条类别标注清晰适合入门。缺点是太老攻击类型和现代网络脱节。CIC-IDS2017 更接近真实流量有 80 多个特征但原始 pcap 转 CSV 后有几百万行还带缺失值和无穷值清洗工作量大。我的建议毕业设计用 NSL-KDD 打底把流程跑通再用 CIC-IDS2017 的一个子集做扩展实验体现工作量。NSL-KDD 的文件结构是KDDTrain.txt和KDDTest.txt每行 43 列前 41 列是特征第 42 列是标签第 43 列是难度分数。标签有 normal、neptune、smurf、back 等 20 多种通常归成五大类Normal、DoS、Probe、R2L、U2R。import pandas as pd # 列名按 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) print(train.shape, test.shape) # (125973, 43) (22544, 43) print(train[label].value_counts().head(10))读进来先看形状和标签分布。NSL-KDD 训练集里 normal 占一半多DoS 类neptune、smurf占很大比例R2L 和 U2R 样本极少U2R 只有几十条。这个分布直接决定了后面要用类别权重或过采样不然模型会把所有少数类都判成多数类准确率看着高实际漏报严重。提示下载数据集时认准官方来源NSL-KDD 在加拿大网络安全研究所页面有原始压缩包。别用来路不明的「整合版」有些被人改过标签训练出来的模型指标虚高。3. 特征工程与预处理把原始流量变成模型能吃的数字3.1 类别特征编码与数值特征标准化NSL-KDD 的 41 个特征里protocol_type、service、flag是字符串类别特征。protocol_type只有 tcp、udp、icmp 三种用 one-hot 就行。service有 70 多种flag有 11 种one-hot 后维度会膨胀到 120 多维可以接受但要注意训练集和测试集的类别要对齐——测试集里可能出现训练集没见过的 service直接pd.get_dummies会导致列数不一致。from sklearn.preprocessing import StandardScaler, LabelEncoder # 合并后再做 one-hot保证列对齐 full pd.concat([train, test], axis0, ignore_indexTrue) full pd.get_dummies(full, columns[protocol_type, service, flag]) # 分离回训练和测试 train_enc full.iloc[:len(train)].copy() test_enc full.iloc[len(train):].copy() # 数值特征标准化fit 只在训练集上做 num_cols train_enc.select_dtypes(include[int64,float64]).columns num_cols [c for c in num_cols if c not in [label,difficulty]] scaler StandardScaler() train_enc[num_cols] scaler.fit_transform(train_enc[num_cols]) test_enc[num_cols] scaler.transform(test_enc[num_cols])关键点是fit_transform只在训练集调用测试集用transform。如果对全量数据 fit测试集的均值和方差信息会泄漏到训练过程评估结果会偏乐观。这个坑我在早期项目里踩过线下 AUC 0.98上线一跑就掉到 0.85查了半天才发现是标准化泄漏。3.2 标签映射与类别不平衡处理原始标签有 20 多种先映射到五大类再做二分类或多分类。二分类就是 normal vs attack多分类是 Normal、DoS、Probe、R2L、U2R。毕业设计建议两个都做二分类看整体检测能力多分类看攻击类型识别。# 标签映射 attack_map { normal: Normal, neptune: DoS, smurf: DoS, back: DoS, teardrop: DoS, pod: DoS, land: DoS, apache2: DoS, udpstorm: DoS, processtable: DoS, worm: DoS, satan: Probe, ipsweep: Probe, nmap: Probe, portsweep: Probe, mscan: Probe, saint: Probe, guess_passwd: R2L, ftp_write: R2L, imap: R2L, phf: R2L, multihop: R2L, warezmaster: R2L, warezclient: R2L, spy: R2L, xlock: R2L, xsnoop: R2L, snmpguess: R2L, snmpgetattack: R2L, httptunnel: R2L, sendmail: R2L, named: R2L, buffer_overflow: U2R, loadmodule: U2R, rootkit: U2R, perl: U2R, sqlattack: U2R, xterm: U2R, ps: U2R } train_enc[attack_type] train_enc[label].map(attack_map) test_enc[attack_type] test_enc[label].map(attack_map)映射完看分布U2R 在训练集里只有 52 条R2L 只有 995 条而 DoS 有 4 万多条。这种极端不平衡直接用 sklearn 的class_weightbalanced能缓解但更稳的做法是 SMOTE 过采样少数类。注意 SMOTE 只能在训练集上做测试集保持原始分布否则评估失真。from imblearn.over_sampling import SMOTE X_train train_enc.drop([label,difficulty,attack_type], axis1) y_train train_enc[attack_type] X_test test_enc.drop([label,difficulty,attack_type], axis1) y_test test_enc[attack_type] smote SMOTE(random_state42, k_neighbors3) X_res, y_res smote.fit_resample(X_train, y_train) print(pd.Series(y_res).value_counts())k_neighbors3是因为 U2R 样本太少默认的 5 会报错。过采样后各类别数量拉平到 6 万多条训练时间会变长但少数类的召回率能明显提升。4. 模型训练与调参从基线到集成4.1 三个基线模型随机森林、XGBoost、LightGBM不要一上来就上深度学习。表格数据上树模型往往比神经网络表现更好训练还快。我一般先跑三个基线随机森林、XGBoost、LightGBM对比一下再决定用哪个做最终模型。from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from lightgbm import LGBMClassifier from sklearn.metrics import classification_report, confusion_matrix models { RandomForest: RandomForestClassifier(n_estimators100, max_depth20, class_weightbalanced, n_jobs-1, random_state42), XGBoost: XGBClassifier(n_estimators200, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, use_label_encoderFalse, eval_metricmlogloss, random_state42), LightGBM: LGBMClassifier(n_estimators200, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, class_weightbalanced, random_state42) } for name, model in models.items(): model.fit(X_res, y_res) y_pred model.predict(X_test) print(f {name} ) print(classification_report(y_test, y_pred, digits4))随机森林的max_depth20是防止过拟合树太深会把训练集噪声也学进去。XGBoost 的learning_rate0.1配n_estimators200是常用组合再大容易过拟合再小收敛慢。LightGBM 在几万条数据上训练最快但小样本时容易过拟合num_leaves默认 31数据量小要调低。跑完看classification_report重点看 U2R 和 R2L 的 recall。如果这两个类的 recall 低于 0.5说明模型对少数类还是没学好可以进一步调scale_pos_weight或者用 focal loss 的思路改损失函数。4.2 超参数搜索与交叉验证的实操参数基线跑完选表现最好的那个做网格搜索。不要用GridSearchCV全量搜太慢。我一般用RandomizedSearchCV迭代 30 到 50 次配合 5 折交叉验证。from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint, uniform param_dist { n_estimators: randint(100, 500), max_depth: randint(5, 30), min_samples_split: randint(2, 20), min_samples_leaf: randint(1, 10), max_features: [sqrt, log2, None] } rf RandomForestClassifier(class_weightbalanced, n_jobs-1, random_state42) search RandomizedSearchCV(rf, param_dist, n_iter40, cv5, scoringf1_macro, n_jobs-1, random_state42, verbose1) search.fit(X_res, y_res) print(search.best_params_) print(search.best_score_)scoringf1_macro是因为类别不平衡accuracy 会偏向多数类f1_macro 对每个类平等看待。cv5是折中数据量大用 3 折省时间数据量小用 10 折更稳。n_iter40在 8 核机器上大概跑十几分钟能接受。搜完用最优参数在训练集上重新 fit再在测试集上评估。注意测试集从头到尾不能参与调参否则就是数据泄漏。我见过有人把测试集也放进交叉验证最后指标好看得离谱答辩时被老师一问就露馅。5. 避坑与排查那些让指标虚高或直接翻车的地方5.1 数据泄漏标准化和过采样的顺序错了现象线下评估 F1 0.97换一批数据或上线测试掉到 0.7 以下。原因标准化时对全量数据fit或者 SMOTE 在划分训练测试之前做了。解决严格按「先划分、再在训练集上 fit 预处理器、再 transform 测试集」的顺序。SMOTE 只在训练集上做测试集保持原始分布。检查方法很简单看你的fit_transform是不是只出现在训练集变量上。5.2 类别标签映射遗漏导致 NaN现象model.fit报错Input contains NaN或者标签里出现nan类别。原因NSL-KDD 测试集里有训练集没见过的攻击类型比如snmpgetattack在训练集里没有映射字典没覆盖就变成 NaN。解决映射前先set(train[label]) | set(test[label])看全集把测试集独有的标签也补进映射字典或者统一归到Other类。我一般会打印未映射的标签确认没有遗漏再继续。5.3 测试集类别特征列数不一致现象predict时报feature_names mismatch或维度不匹配。原因one-hot 时训练集和测试集分开做测试集多了一个 service 类别列数就多了。解决合并后一起get_dummies再按索引切回训练和测试。如果内存不够至少用reindex(columnstrain_columns, fill_value0)对齐列。这个坑在 CIC-IDS 上更常见因为它的特征里有大量类别值。5.4 过采样后模型过拟合少数类现象U2R 的 recall 上去了但 precision 很低大量正常流量被误判成 U2R。原因SMOTE 生成的少数类样本是插值出来的不一定符合真实分布模型学到了人工模式。解决降低过采样比例不要拉到完全平衡比如只过采样到多数类的 50%或者改用class_weight加scale_pos_weight的组合不做合成样本。另一个办法是用SMOTEENN先过采样再清理噪声样本。5.5 评估指标只看 accuracy现象模型 accuracy 0.99但实际漏报很多攻击。原因NSL-KDD 测试集里 normal 占比高全判 normal 也能有 0.7 以上的 accuracy。解决看 confusion matrix 和 per-class recall重点看攻击类的召回。二分类看 AUC 和 F1多分类看 macro-F1 和 weighted-F1。如果 U2R 的 recall 是 0accuracy 再高也没意义。6. 把模型变成能演示的系统推理封装与阈值调优训练完的模型不能只躺在 notebook 里。毕业设计答辩时老师通常想看到「输入一条流量系统输出是否异常」。这一步要做两件事把预处理和模型封装成一个predict函数以及根据业务需求调检测阈值。封装用joblib保存 scaler、encoder 列信息和模型推理时按同样顺序处理。下面是一个最小可用的推理封装import joblib import numpy as np # 保存 joblib.dump(scaler, scaler.pkl) joblib.dump(model, model.pkl) joblib.dump(list(X_train.columns), feature_cols.pkl) # 推理 def predict_single(raw_dict): raw_dict: 单条流量的原始特征字典 返回: (预测类别, 各类别概率) df pd.DataFrame([raw_dict]) df pd.get_dummies(df, columns[protocol_type,service,flag]) # 对齐训练时的列 feature_cols joblib.load(feature_cols.pkl) df df.reindex(columnsfeature_cols, fill_value0) # 标准化 scaler joblib.load(scaler.pkl) num_cols [c for c in feature_cols if c in scaler.feature_names_in_] df[num_cols] scaler.transform(df[num_cols]) model joblib.load(model.pkl) pred model.predict(df)[0] proba model.predict_proba(df)[0] return pred, dict(zip(model.classes_, proba))reindex那一步是关键保证推理时的列和训练时完全一致缺的补 0多的丢掉。scaler.feature_names_in_是 sklearn 1.0 之后才有的属性能拿到 fit 时的列名避免手动维护列表。阈值调优是另一个实战点。默认predict取概率最大的类但在入侵检测里漏报的代价通常比误报高。我一般会把攻击类的判定阈值从 0.5 降到 0.3牺牲一点 precision 换 recall。具体做法是拿predict_proba的输出对攻击类概率超过阈值的判为攻击。def predict_with_threshold(proba_dict, threshold0.3): 攻击类概率超过阈值就判为攻击 attack_prob sum(v for k, v in proba_dict.items() if k ! Normal) if attack_prob threshold: return max((k for k in proba_dict if k ! Normal), keylambda k: proba_dict[k]) return Normal阈值定多少要看场景。如果系统是旁路告警误报多一点没关系阈值可以低到 0.2如果是自动阻断误报会断正常业务阈值要调到 0.6 以上。这个没有标准答案拿验证集画一条 precision-recall 曲线找 F1 最大的点作为起点再根据业务反馈微调。最后说个我自己的习惯每次改完预处理或模型参数一定重新跑一遍完整的「读数据→预处理→训练→测试集评估」流程不跳步。因为中间任何一步的变量状态变了结果就不可比。我见过太多人调完参只重新 fit 模型忘了重新做标准化指标对不上还找不到原因。这套流程跑通大概需要半天到一天数据集和代码结构理顺之后换模型或加特征都是半小时的事。希望帮到你。本文还有配套的精品资源点击获取