基于机器学习和深度学习的网络入侵检测系统实战

基于机器学习和深度学习的网络入侵检测系统实战 简介基于Python与机器学习/深度学习实现的入侵检测项目面向毕业设计、课程设计与项目开发场景提供完整源码、项目文档、参考论文及使用教程。项目基于UNSW_NB15公开数据集包含多种攻击类型采用CNN、LSTM等深度网络实现分类并针对特征不连续问题提出卡方过滤与随机森林结合的特征选择方法同时引入ADASYN算法缓解样本不平衡。资源打包为ZIP格式共31个文件整体约26.61MB包含14个Python脚本、3个CSV数据文件、2个HDF5模型文件、3个Markdown说明文档及Word文档等目录清晰便于按模块查阅。已有110人学习下载。读者可借此快速复现从数据预处理、特征工程到模型训练与评估的完整流程了解CNN与LSTM在入侵检测中的实际应用并参考其中卡方过滤、随机森林与ADASYN的改进思路拓展自身课题研究。1. 没有现成源码可抄也能把入侵检测项目做完整“基于python机器学习/深度学习实现的入侵检测项目”这个标题每年在毕设和课设里出现的频率很高。它看起来老是因为网络入侵检测NIDS本身不是一个新方向它每年还能作为新课设出现是因为数据好找、指标直观、可扩展的点多。一个完整的项目不是只有模型训练那一节而是包括数据预处理、模型选型与对比、工程化封装、实验记录以及能支撑答辩的文档和论文。适合做这个题目的人不只是正在找项目的学生也包括想在简历里增加一个“从数据处理到模型部署”完整闭环的开发者。这套方案的核心思路是不用去依赖某份现成的“源码包”而是自己把数据集、特征工程、两类模型基线、评估指标和推理接口串起来。这条路走完交付物自然齐全别人问起任何一环你都能说清楚这才是毕业设计或课程设计真正要的东西。2. 入侵检测的数据集选型与预处理拿什么训练才可信2.1 公开数据集横向对比KDD99落后在哪做入侵检测第一步不是选模型而是选数据集。公开数据集的质量决定了模型能学到什么也决定了答辩时别人会不会追着问“这个数据是否有意义”。数据集发布时间样本规模量级特征维度攻击类别适合场景主要坑点KDD Cup 1999 / NSL-KDD1999 / 2009约12万50万414大类DoS、Probe、R2L、U2R快速跑通全流程数据过时模拟环境造的数据类别极度不均UNSW-NB152015约25万499类攻击学术对比实验特征文档较少解析原始pcap成本高CICIDS20172017约288万8014类攻击良性接近真实流量的工业场景文件体积大重采样耗时正负样本不均匀我的经验是如果目标是做完整毕业设计用 CICIDS2017 的CSV子集最合适。它有真实流量抓包背景特征名规范攻击类型覆盖DDoS、PortScan、Brute Force、Web Attack等论文里写“基于真实网络流量”比“使用1999年模拟数据”好讲得多。如果只是想快速跑通模型流程先拿NSL-KDD几十万条数据作为开发集最后换到大数据集上复现结果。注意 NSL-KDD 有一个经典问题U2R 类样本极少R2L 类样本量也远小于 Normal 和 DoS。直接用原始分布训练模型会把 U2R 全判成 Normal准确率看起来仍有 90% 以上但没有任何检测价值。这个话题在第 3 章会展开。2.2 数据清洗与特征编码决定模型上限的“脏活”不管选哪个数据集拿到 CSV 后都要做同一套预处理。以 CICIDS2017 子集为例其原始列中包含Flow ID、Source IP、Timestamp这类对象列以及若干全空列、无穷大值。这些不处理sklearn 直接报错后面所有工作都会卡住。import pandas as pd import numpy as np from sklearn.preprocessing import LabelEncoder df pd.read_csv(cicids2017_subset.csv) # 剔除与检测无关的标识列 drop_cols [Flow ID, Source IP, Destination IP, Timestamp] df df.drop(columns[c for c in drop_cols if c in df.columns]) # 标签二分类映射正常流量为0其余攻击统一为1 df[Label] df[Label].apply(lambda x: 0 if x BENIGN else 1) # 取出特征列做统一清洗 X df.drop(columns[Label]) y df[Label] # 1) 类别特征转数值 for col in X.columns: if X[col].dtype object: X[col] LabelEncoder().fit_transform(X[col].astype(str)) # 2) 缺失值与无穷大统一置0 X X.fillna(0) X X.replace([np.inf, -np.inf], 0) # 3) 标准化让特征处于同一量纲 from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X) print(f特征维度: {X_scaled.shape[1]}, 样本数: {X_scaled.shape[0]}) print(f攻击样本占比: {y.mean():.4f})这段代码里有两个容易被忽略的点标识列必须删除。Flow ID、IP、端口如果保留模型会学到一个错误规律——训练集中某个IP出现过的攻击测试集里同样IP直接判为攻击。这属于特征泄漏答辩时被指出会很尴尬。标准化必须在划分训练集和测试集之前完成吗不需要。正确做法是先切分再在训练集上fitscaler再transform验证集。上面代码为了演示简洁写在了一起实际工程中要把fit放在训练集上否则验证集的信息会混入训练流程。2.3 训练集/测试集划分以及类别不平衡处理的顺序划分数据时要用stratify参数保证训练集和测试集中的攻击比例一致否则划分结果会受抽样波动影响。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.2, random_state42, stratifyy ) # 单独保存scaler后续做推理和部署时还需要它 import joblib joblib.dump(scaler, model/scaler.pkl)类不平衡的处理顺序很容易搞反。先划分、再上采样这是铁律。如果你在划分前对整个数据集做了 SMOTE那么合成的少数类样本会同时出现在训练集和测试集里测试结果虚高失去参考意义。后续章节所有实验都会在这一份干净的训练/测试划分基础之上进行。3. 机器学习模型做入侵检测从随机森林到 XGBoost 的完整基线3.1 随机森林先做基线参数怎么设机器学习模型里随机森林是入侵检测最适合的基线。它不需要特征归一化也能工作能输出特征重要性训练速度快不容易过拟合。先跑通 RF再把 XGBoost 加进来对比这是最稳妥的递进路线。from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score model RandomForestClassifier( n_estimators200, max_depth20, min_samples_leaf2, n_jobs-1, random_state42, class_weightbalanced ) model.fit(X_train, y_train) y_pred model.predict(X_test) y_proba model.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(fAUC: {roc_auc_score(y_test, y_proba):.4f}) print(confusion_matrix(y_test, y_pred))对参数做个说明n_estimators200一般 100500 之间树多了效果提升有限训练时间线性增长。200 是在效率和精度之间的折中值。max_depth20限制单棵树深度防止对少数类样本死记硬背。入侵检测的特征有 80 多个深度在 15~25 区间通常够用。class_weightbalanced根据类别频率自动调权重。攻击样本少时这个参数比手调阈值更省事。在 CICIDS2017 子集上这个配置的准确率通常能到 97% 以上但对少数攻击类别的召回率可能偏低这正是后续要优化的方向。3.2 XGBoost 对比实验梯度提升树的优势边界XGBoost 是第二个推荐加入对比的模型。它和随机森林的差别在于RF 对每棵树独立并行训练XGBoost 是串行地拟合残差。在入侵检测这种特征间存在非线性关系的数据上XGBoost 通常比 RF 高 12 个百分点的 AUC代价是训练时间更长、超参数更多。from xgboost import XGBClassifier xgb_model XGBClassifier( n_estimators300, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, scale_pos_weight(y_train 0).sum() / (y_train 1).sum(), eval_metricaucpr, random_state42, n_jobs-1 ) xgb_model.fit(X_train, y_train, verboseFalse) xgb_pred xgb_model.predict(X_test) print(classification_report(y_test, xgb_pred))几个参数的设置理由learning_rate0.1学习率调低到 0.050.1配合 300 棵树效果通常比直接调 0.3 配 100 棵要好。学习率越低每棵树贡献越小越不容易过早过拟合。scale_pos_weight正负样本比例。这是 XGBoost 里处理不平衡最直接的手段不需要提前做 SMOTE。数值为负样本数 / 正样本数在 CICIDS2017 这种攻击占比约 20% 的数据上效果立竿见影。eval_metricaucpr用 PR-AUC 作为评估指标比用 accuracy 更合理。入侵检测中正样本太少PR-AUC 能更敏感地反映少数类检测能力。subsample0.8, colsample_bytree0.8每棵树只用 80% 的样本和特征增加随机性降低过拟合风险。3.3 R2L 和 U2R 攻击为什么难检指标要分开看NSL-KDD / CICIDS2017 里的少数攻击类别集中在 R2L 和 U2R。R2LRemote to Local是远程未授权访问U2RUser to Root是本地提权攻击。它们的共同点是样本少、流量特征与正常流量差异小有些攻击单看报文和普通浏览行为几乎没有区别。这就引出评估指标的关键问题只看 accuracy 会得出“模型很好”的结论但少数类可能全部漏检。最稳妥的做法是打印每个类别的 classification report或者对二分类检测单独计算检测率Recall和误报率FPR检测率 Recall TP / (TP FN)回答“攻击样本有多少被抓住了”误报率 FPR FP / (FP TN)回答“正常流量有多少被误杀了”所在实际项目里如果发现 U2R 类 recall 低于 0.5不要慌这是公开数据集普遍存在的问题。处理方案是结合第 4 章的深度学习模型做特征自动提取或者对少数类做针对性重采样后再单独评估不建议为了论文里一个亮眼的 accuracy 数字去合并类别掩盖问题。4. 深度学习模型的接入从 LSTM 到 CNN 的实践路径4.1 表格数据和时序数据分别适合什么网络机器学习模型把每条流量当成独立的特征向量顺序无关。但网络流本质上有上下文关系——同一个 TCP 连接里的多个报文行为上存在前后依赖。这个依赖关系如果数据集中已经是“流级别特征”一行代表一个完整流那么 LSTM 的时序优势发挥不出来因为每行之间没有严格时序。我一般把两种情况分开处理数据是一行一个流的表格特征优先 MLP 或 1D-CNN。把特征向量直接映射到输出训练快效果不比 LSTM 差而且不容易踩 shape 的坑。数据是原始报文序列如每个流拆成若干包的特征序列用 LSTM 或 BiLSTM。输入 shape 变成(batch, time_steps, features)模型能学到报文之间的先后依赖关系例如一个端口扫描行为中“尝试连接→等待响应→再尝试”的模式。对于毕业设计来说最稳妥的路线是用 CICIDS2017 的 csv 特征做 1D-CNN然后在论文里保留一个 LSTM 对比实验说明时序建模在流级特征上的边界。这样既覆盖了深度学习又不至于在数据预处理上无限延长工期。4.2 基于 Keras 实现 LSTM 入侵检测模型以下代码以“每条样本有 80 个流特征”为前提把特征序列构造成sequence_length1, features80用 LSTM 建模。如果后续拿到真正的多包序列数据只需调整reshape部分。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout, BatchNormalization from tensorflow.keras.optimizers import Adam # 输入需要三维: (样本数, 时间步长, 特征数) # 流级特征这里设时间步长1 time_steps 1 X_train_lstm X_train.reshape((X_train.shape[0], time_steps, X_train.shape[1])) X_test_lstm X_test.reshape((X_test.shape[0], time_steps, X_test.shape[1])) model Sequential([ LSTM(128, return_sequencesTrue, input_shape(time_steps, X_train.shape[1])), Dropout(0.3), LSTM(64), Dropout(0.3), BatchNormalization(), Dense(64, activationrelu), Dense(1, activationsigmoid) ]) model.compile( optimizerAdam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy, AUC] ) model.summary()编译参数说明第一个 LSTM 层设return_sequencesTrue是为了把完整序列传递给第二个 LSTM 层。如果只有一层 LSTM可以设为 False。Dropout(0.3)设在 LSTM 层之间能显著减少过拟合。入侵检测的特征维度不算高Dropout 从 0.2~0.4 试起取验证集 loss 最低的值。BatchNormalization放在 Dense 之前让激活值分布更稳定在流量数据这种数值波动较大的场景下收敛更快。训练时设置类别权重或采样权重是常见的做法weight_0 (y_train 1).sum() weight_1 (y_train 0).sum() class_weight {0: weight_0, 1: weight_1} history model.fit( X_train_lstm, y_train, batch_size64, epochs20, validation_split0.2, class_weightclass_weight )class_weight的原理和机器学习里的scale_pos_weight一样训练时给少数类更大的损失权重。与 SMOTE 相比它不增加样本总量训练开销更小且不会改变原始分布。4.3 深度学习的训练坑shuffle 和阈值调整深度学习训练有两个容易忽略的细节Keras 的model.fit()默认会 shuffle 训练数据这是好事。如果你的训练集本身按攻击类别排序前 80% 全良性后 20% 全攻击不 shuffle 会导致每个 batch 里全是同一类别loss 震荡剧烈。阈值不能默认设为 0.5。深度学习模型输出的概率分布往往不平衡入侵检测里更关心“攻击不能漏”。一种做法是在验证集上搜索阈值from sklearn.metrics import precision_recall_curve y_val_proba model.predict(X_test_lstm).ravel() precision, recall, thresholds precision_recall_curve(y_test, y_val_proba) # 找满足recall 0.95时precision最高的阈值 valid_idx [i for i in range(len(thresholds)) if recall[i] 0.95] best_idx max(valid_idx, keylambda i: precision[i]) best_threshold thresholds[best_idx] print(f取阈值 {best_threshold:.3f})这个搜索逻辑意味着如果想保证攻击检出率不低于 95%误报会多一些。实际系统里阈值是产品参数应该允许运营人员调整。“只追求准确率”和“能接受一定误报保证攻击不漏”是两种不同的安全策略在论文实验里把这一步呈现出来评审会看到你理解了任务本身。5. 把模型变成可交付的项目工程化组织与文档写法5.1 源码目录怎么组织让答辩老师一眼看懂一个完整项目不是单个 ipynb 文件能承载的。我推荐用按功能拆分的目录结构ids_project/ ├── data/ # 原始数据与处理后数据 │ ├── raw/ │ └── processed/ ├── src/ # 核心代码 │ ├── preprocess.py # 数据清洗与特征工程 │ ├── train_ml.py # 机器学习模型实验 │ ├── train_dl.py # 深度学习模型实验 │ ├── evaluate.py # 评估指标与图表生成 │ └── inference.py # 推理接口 ├── model/ # 训练产物 │ ├── rf_model.pkl │ ├── xgb_model.pkl │ ├── scaler.pkl │ └── lstm_model.keras ├── docs/ # 项目文档与使用说明 │ ├── 需求说明.md │ ├── 技术方案.md │ └── 测试报告.md ├── paper/ # 参考论文与实验数据 │ └── 实验记录.xlsx └── requirements.txt这个结构的核心思想是“数据和代码分离、模型和评估分离”。答辩老师问“你的模型放在哪里”“数据处理在哪一步”你能直接指向具体文件。同时这也是一个真实项目该有的组织方式不只是为了应付检查。5.2 用 FastAPI 提供实时检测接口模型训练完只是项目的一半。为了展示工程能力应该把模型封装成 HTTP 接口让外部调用者传入一条流量的特征向量返回是否为攻击。from fastapi import FastAPI from typing import List import joblib import numpy as np app FastAPI(titleNIDS Inference API) rf_model joblib.load(model/rf_model.pkl) scaler joblib.load(model/scaler.pkl) app.post(/predict) async def predict(features: List[float]): # 转成模型输入格式并标准化 features_arr np.array(features).reshape(1, -1) features_scaled scaler.transform(features_arr) # 返回概率与阈值判断结果 proba rf_model.predict_proba(features_scaled)[0][1] is_attack bool(proba 0.5) return {is_attack: is_attack, probability: round(float(proba), 4)}启动服务命令是uvicorn src.inference:app --host 0.0.0.0 --port 8000。之后用 POST 请求访问/predict请求体里传一个特征数组即可。这里有个细节scaler从训练阶段保存推理时直接 load保证上线数据和训练数据经过同一套标准化逻辑。很多毕设项目卡在这一步就是因为训练时做了标准化推理时忘了处理。5.3 项目文档和参考论文怎么对应起来最后说说文档和论文。评审老师看过太多“代码能跑但说不清”的项目文档的核心价值是让人不用读完整份代码就能理解你的工作。需求说明.md写清楚检测对象、数据类型、性能指标目标。比如“对 CICIDS2017 测试集的攻击样本召回率≥90%误报率≤5%”这比写“达到较高准确率”有说服力。技术方案.md必须包含一张模型对比表格模型准确率攻击类召回率误报率训练耗时随机森林97.2%91.5%1.8%约15分钟XGBoost98.1%94.0%1.2%约30分钟LSTM97.8%93.3%1.5%约1小时参考论文的选择原则是“宁旧勿偏”。不建议找太偏门的方向而是找公开数据集的经典分析论文例如针对 CICIDS2017 的攻击类别分析、基于机器学习的入侵检测综述。从这类论文里可以引用的点是数据集介绍、攻击类别定义、评估指标选择的依据。引用时直接写上“本文方法借鉴了该文对 U2R 类样本不平衡问题的处理思路”能明显提高论文的可信度。最后的技巧是把实验记录做成 xlsx 文件保存每次跑完的实验结果包括日期、模型名、参数、每个指标的具体数值。这些原始记录不仅是论文实验部分的素材答辩时老师问“这个结果怎么来的”你把实验记录调出来比任何口述都有说服力。本文还有配套的精品资源点击获取