ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于机器学习的入侵检测系统实战:从SMOTE平衡到SHAP可解释性

基于机器学习的入侵检测系统实战:从SMOTE平衡到SHAP可解释性 简介面向网络安全与机器学习方向研究者提供一套基于机器学习的入侵检测系统完整源码以解决传统检测规则依赖性强、对未知攻击识别弱、误报率高及模型解释性差等问题。项目覆盖SMOTE数据平衡、传统机器学习、集成学习、深度学习与自动机器学习多种建模方式并集成SHAP与DALEX解释工具提升模型透明度。压缩包共30个文件约11.21MB包含md说明文档、sql数据脚本、png界面截图以及多层zip源码包目录清晰区分vue前端界面、flask后端服务、数据库脚本与相关工具模块。通过仿真环境验证系统兼顾检测性能与可解释性在真实生产条件下表现出良好实用性。已有175人学习下载适合需要快速部署可解释型入侵检测系统、进行模型对比实验或论文复现的开发者。1. 入侵检测为什么要机器学习从规则匹配到未知攻击识别规则库翻车、误报轰炸传统 IDS 在未知攻击面前基本靠猜——这是我在实际部署中反复碰到的现实。这套《基于机器学习的入侵检测系统》源码把数据预处理、SMOTE 平衡、模型训练、Streamlit 可视化整条链路完整串起来了前端用 Vue 做管理界面后端用 Flask 提供推理接口适合正在做安全方向课设、毕设或者已经受够了 Snort 规则维护想换一条路的开发者。你不需要从零搭框架拿到就能跑通整个检测闭环重点观察它怎么处理类别不平衡、怎么把 SHAP 和 DALEX 的解释能力接进界面这两点比模型本身更值钱。2. 项目架构与数据流Streamlit、Vue、Flask 的分工逻辑这套资源在架构上最值得先说清楚的一点它没有把所有功能塞进一个框架而是拆成了三块。Streamlit 负责数据分析与模型演示Vue 负责后台管理界面Flask 负责后端服务和模型推理。拆开的好处是职责清晰坏处是两个 Web 服务同时跑的时候端口和依赖都容易打架这个我在第 5 章会专门讲。检测链路可以从一条请求来理解管理员在 Bootstrap 或 Vue 页面登录上传或选择要检测的数据Flask 收到请求后调用训练好的模型做预测结果写回 MySQLStreamlit 页面读同一份数据来做特征分析和 SHAP 解释。也就是说数据库是三个模块之间的共享状态sql/model-d.sql就是初始化这个库的建表脚本。如果你只想验证最核心的检测能力最小跑通路径是先导入 model-d.sql 建库再启动 Flask 服务最后分别拉起 Vue 和 Streamlit。整个过程大概十分钟能完成难的不是顺序而是环境依赖。2.1 文件结构与模块边界解压 ids-master.zip 之后第一步是把嵌套的 zip 也解压。项目正文里能看到 vue_manage.zip、0my_pro_flask.zip、bootstrap_login.zip 和 pages.zip 都是压缩包形式说明作者在分发时把不同模块独立打包了。我用一张表把目录和职责对应起来路径模块职责vue/vue_manage.zipVue 管理前端用户管理、检测结果展示flask/0my_pro_flask.zipFlask 后端模型推理 API、登录鉴权、数据库交互bootstrap/bootstrap_login.zipBootstrap 登录页登录/注册入口streamlit/pages.zipStreamlit 多页面数据分析、特征可视化、SHAP 展示sql/model-d.sql数据库脚本MySQL 建表与初始数据images/*.png截图README 配图、UI 预览README.md / README.en.md文档中英文项目说明解压操作在 Linux 下用 unzip 一条命令就能完成cd ids-master unzip vue/vue_manage.zip -d vue/vue_manage unzip flask/0my_pro_flask.zip -d flask/0my_pro_flask unzip bootstrap/bootstrap_login.zip -d bootstrap/bootstrap_login unzip streamlit/pages.zip -d streamlit/pages ls -l vue vue/vue_manage flask streamlit这里每一行把 zip 解压到同名目录避免压缩包内文件直接散落到上级目录。我一般会加-d参数强制指定解压目录这样就算压缩包内部带了嵌套文件夹也不会乱。最后一个 ls 是为了确认解压后目录结构符合预期别直接跳到启动那一步。解压之后先翻 README.md 里写的启动命令因为不同作者习惯不同有的用python app.py有的用flask run有的在 streamlit 目录下直接streamlit run main.py以 README 为准不要凭经验猜。2.2 Streamlit 多页面与 pages 机制streamlit/pages.zip 解压后的目录是 Streamlit 的多页面模式。Streamlit 从 1.0 开始支持pages/目录放置在 pages 下的.py文件会自动出现在侧边栏每个文件对应一个独立页面。这种组织方式比在一个.py里堆十几个 if 分支清爽得多。pages 目录下通常会有数据预览页、特征分布页、模型对比页和 SHAP 解释页除了四类页面之间要共享同一个模型加载逻辑避免重复训练——你可以把这个设计借鉴到自己的项目里把模型加载函数抽成公共模块页面文件只做展示和交互。在 Streamlit 多页面结构里页面之间传数据用st.session_state是最省事的做法。例如在第一个页面选定模型后把模型对象存进 session_state后续页面直接读取不需要重新加载。注意 session_state 存模型对象时要确保模型文件路径是绝对路径因为 Streamlit 的工作目录可能不是streamlit/而是项目的根目录。import streamlit as st import joblib if model not in st.session_state: st.session_state[model] joblib.load(../models/ids_model.joblib) model st.session_state[model]这段代码里joblib.load加载一次模型后放进 session_state后续页面复用。路径../models/ids_model.joblib是相对于 streamlit 运行目录的常见位置如果加载失败把模型文件放在与页面相同目录下改成相对路径即可。2.3 Flask 如何承接模型推理Flask 部分的核心是加载训练好的模型文件并提供 predict 接口。正常流程是模型文件在启动时加载到内存然后每个请求通过 POST JSON 把特征数组传过来后端返回类别和概率。这里有一个细节模型文件和 Flask 代码分离存放模型文件放在models/或 flask 同级目录下这样前端页面更新不会影响模型文件。from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) model joblib.load(../models/ids_model.joblib) app.route(/predict, methods[POST]) def predict(): data request.get_json() features np.array(data[features]).reshape(1, -1) proba model.predict_proba(features)[0] return jsonify({ prediction: int(model.predict(features)[0]), probability: float(max(proba)) }) if __name__ __main__: app.run(host0.0.0.0, port5000)reshape(1, -1)是为了把单条样本的列表转成二维数组sklearn 模型不接受一维输入。predict_proba返回的数组里每个元素代表一个类别的概率取最大值作为置信度返回前端。启动参数host0.0.0.0是让服务监听所有网卡这样 Vue 或 Streamlit 在同一局域网内也能访问。如果你想限制只本机能用改成127.0.0.1即可。3. 数据预处理与 SMOTE类别不平衡是第一个翻车点入侵检测数据集最典型的问题是类别不平衡正常流量占绝大多数攻击流量可能只有百分之几甚至千分之几。直接拿原始数据训练模型只要全部预测为正常就能拿到 99% 的准确率但这没有任何意义。这套资源里专门提了 SMOTE 技术说明作者对这个问题是认真处理过的。SMOTE 的全称是 Synthetic Minority Over-sampling Technique核心思路是对少数类样本做插值生成新的合成样本而不是简单地复制现有样本。它先在少数类样本的 k 近邻里随机选一个邻居然后在两者连线上随机取一个点作为新样本。和随机过采样相比SMOTE 生成的样本不完全重复模型不容易过拟合。不过 SMOTE 也有它自己的坑最典型的是必须先划分训练集和测试集再对训练集做 SMOTE千万不能对整个数据集做了 SMOTE 再切分。原因在第 5 章会展开这里先记住结论。3.1 数据清洗先看标签分布和缺失值拿到数据第一步我一般会做三件事看标签列分布确认少数类占比检查缺失值和无穷值把类别特征编码、数值特征标准化。import pandas as pd df pd.read_csv(data/ids_data.csv) print(df[label].value_counts(normalizeTrue)) print(df.isnull().sum()[df.isnull().sum() 0]) print(df.describe()) # 把标签列转为 0/1正常为 0攻击为 1 df[label] (df[label] ! normal).astype(int)value_counts(normalizeTrue)输出的是各类别占比能直接从数字上看出不平衡程度。isnull().sum()对每一列统计缺失值数量当某列缺失占比超过三成时我一般直接丢弃因为补出来的噪声往往比信息更多。describe()是为了快速看数值范围很多网络流量特征的分布差异极大比如数据包长度和连接时长不标准化会让梯度类模型收敛很慢。3.2 SMOTE 参数选择与实际采样用 imbalanced-learn 库的 SMOTE 做采样关键参数是sampling_strategy和k_neighbors。from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split X df.drop(columns[label]) y df[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) smote SMOTE(sampling_strategyauto, k_neighbors5, random_state42) X_train_res, y_train_res smote.fit_resample(X_train, y_train) print(SMOTE 前:, y_train.value_counts().to_dict()) print(SMOTE 后:, y_train_res.value_counts().to_dict())sampling_strategyauto意味着把所有少数类补到与多数类一样多如果想让少数类只补到多数类的 50%就传0.5。k_neighbors是生成样本时参考的近邻数量默认 5当少数类样本特别稀疏时不要调太小否则生成的样本容易贴着边界甚至变成噪声。切分时stratifyy是必须的它保证训练集和测试集里正负样本比例与全量数据基本一致不然切分本身就会引入新的不平衡。3.3 特征列处理与标准化顺序SMOTE 只能处理数值特征如果原始数据里有 IP 地址、协议类型这种类别特征需要先做 One-Hot 编码或标签编码。顺序上要记住先编码再切分再 SMOTE再做标准化。顺序错了最容易出现的是标准化用的均值和方差把测试集信息混进来这属于另一个容易被忽略的信息泄漏点。from sklearn.preprocessing import StandardScaler scaler StandardScaler().fit(X_train_res) X_train_scaled scaler.transform(X_train_res) X_test_scaled scaler.transform(X_test)这里fit只放在 SMOTE 后的训练集上测试集用同一个 scaler 做transform严禁重新 fit。你也可以把 scaler 保存成文件和模型一起部署接口里复用同一份归一化参数。4. 模型构建与训练从逻辑回归到自动机器学习的选型路径这一章说模型怎么选。资源里提到的四个层次我很认同传统机器学习、集成学习、深度学习、自动机器学习。在实际做入侵检测时我的习惯是先跑一个逻辑回归或者决策树做基线再上随机森林和梯度提升树如果数据集规模足够大再考虑深度学习最后如果时间预算充足用 AutoML 工具自动搜一下参数。为什么按这个顺序因为入侵检测对误报率很敏感模型的可解释性也非常重要。逻辑回归和决策树的解释性最强集成学习在性能和解释性之间有一个平衡深度学习效果可能最好但解释性最差。自动机器学习则是把超参数搜索交给工具省人力但可控性下降而且搜索过程消耗大量计算资源。这是选型时的核心考量不是模型越复杂越好而是要在可解释性和误报率之间找到你能接受的点。4.1 传统机器学习与集成学习做基线随机森林和 XGBoost 是入侵检测场景里最常用的两个模型。随机森林是 Bagging 思路对每棵树的预测结果投票抗过拟合能力强XGBoost 是 Boosting 思路通过加法模型拟合残差在大多数表格数据上表现优于随机森林但对参数更敏感调参不好反而过拟合。from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier from sklearn.metrics import classification_report rf RandomForestClassifier( n_estimators200, max_depth12, min_samples_leaf4, class_weightbalanced, random_state42, n_jobs-1, ) rf.fit(X_train_scaled, y_train_res) print(classification_report(y_test, rf.predict(X_test_scaled))) xgb XGBClassifier( n_estimators200, max_depth6, learning_rate0.1, scale_pos_weight1, eval_metriclogloss, random_state42, ) xgb.fit(X_train_scaled, y_train_res) print(classification_report(y_test, xgb.predict(X_test_scaled)))class_weightbalanced是随机森林自带的对抗类别不平衡的手段它会给少数类更高的惩罚权重。scale_pos_weight是 XGBoost 里等价的做法通常设为多数类样本数除以少数类样本数例如 95:5 就填 19。min_samples_leaf控制叶子节点最小样本数调大这个值能明显抑制过拟合入侵检测数据噪声多建议从 3 到 5 起步。如果训练后测试集 F1 明显低于训练集优先检查是不是 SMOTE 顺序出了问题而不是急着加正则化。4.2 深度学习模型什么时候该上深度学习在入侵检测里主要是多层感知机和基于自编码器的异常检测。MLP 的优势是能自动学习特征交互缺点是训练时间长、可解释性差而且在小数据集上容易被调参好的 XGBoost 反超。一般只有当训练数据足够大比如超过 50 万条流量记录才值得花精力调神经网络。在资源提供的流程里深度学习更多是作为对比实验存在用来证明集成学习在中小数据集上性价比更高。4.3 自动机器学习AutoML 的定位资源里提到自动机器学习我理解其定位不是替代主模型而是帮你找一个更好的参数组合或模型组合。常见做法是用 TPOT 做流程搜索它会自动尝试预处理方式、模型选择和超参数组合。from tpot import TPOTClassifier tpot TPOTClassifier( generations5, population_size20, cv5, scoringf1, random_state42, verbosity2, ) tpot.fit(X_train_scaled, y_train_res) print(tpot.fitted_pipeline_)generations和population_size越大效果越好但时间成倍增加第一次跑建议用generations3, population_size10验证流程能走通。scoring用f1而不是accuracy因为类别不平衡场景下 f1 更能代表真实性能。TPOT 搜索出来的模型可以直接通过tpot.fitted_pipeline_导出保存成 joblib 文件用于部署。5. 避坑排查部署这套 IDS 最容易翻车的五个地方这一章专门记录实际运行这套系统时容易踩进去的坑。如果你是自己下载下来研究第 2 章说的目录解压其实是小事真正让人头疼的是环境、泄漏和打包。每条我都按现象、原因、解决三个步骤写清楚方便你出问题时快速定位。5.1 端口冲突Streamlit 与 Flask 抢同一个端口现象是 Flask 启动后 Streamlit 启动直接报 Address already in use或者反过来。原因是 Streamlit 默认端口是 8501Flask 开发服务器默认是 5000本来不冲突但某些机器上 8501 已经被占用Flask 里也可能有人显式写了端口 8501。解决方法是启动前先查端口占用然后强制指定不同端口lsof -i:8501 lsof -i:5000 streamlit run main.py --server.port 8502 python flask/0my_pro_flask/app.py如果是因为一个 Python 进程同时拉起多个服务也会出现这种问题。尽量分开进程跑不要在一个脚本里用 subprocess 串行启动。5.2 SMOTE 信息泄漏训练集和测试集互相污染现象是训练时 F1 很高但用新采集的流量做验证时效果断崖式下跌。原因基本可以锁定为先 SMOTE 后切分或者先标准化后切分。SMOTE 根据少数类样本的邻居生成新样本如果测试集数据参与了合成模型在训练时已经见过测试集的邻居信息测试分数自然虚高。解决方法是严格固定处理顺序切分 → SMOTE → 标准化。验证时用新的测试集重新评估如果分数明显下降说明之前泄漏问题已经把模型污染了需要重新走一遍流程。5.3 Vue 打包后接口 404现象是开发环境npm run dev一切正常打包部署后接口全部 404。原因是开发环境下 Vite 或 webpack 的代理配置会转发/api请求到 Flask打包后这个代理失效请求落到静态服务器或 Flask 错误路径。解决方法是把 Vue 构建产物dist目录交给 Flask 托管并确认 Flask 路由统一以/api前缀声明。如果你不想改代码最省事的方案是开发阶段用 dev 模式跑 Vue生产环境把 dist 放到 Flask static 目录下面。5.4 model-d.sql 导入报字符集错误现象是mysql model-d.sql时报 Invalid utf8mb4 character string 或 collation 相关错误。原因是 SQL 文件内部用 utf8mb4 字符集写入中文或特殊字符本地 MySQL 默认字符集不一致。解决方法是先设置连接字符集再导入同时确认数据库版本支持 utf8mb4mysql -u root -p --default-character-setutf8mb4 sql/model-d.sql导入后用SHOW TABLES;验证表是否建立成功别直接启动 Flask否则连接数据库会报 Table doesnt exist。5.5 模型文件换机器后无法加载现象是训练机加载模型没问题换到部署机就报 ModuleNotFoundError 或 ValueError。原因是直接用 pickle 保存模型时模型内部引用了训练环境的自定义类换机器后类缺失。解决方法是统一用joblib.dump和joblib.load并把依赖库打包到 requirements.txt。部署前在新环境里先写一个最简加载脚本测试成功后再启动 Flask不要等请求来了才暴露问题。6. 可解释性验证SHAP 与 DALEX 的正确打开方式最后一章落在可解释性上。很多入侵检测项目能做到高精度但说不出每个决策为什么生产环境根本不敢用。这套资源里提到用 SHAP 和 DALEX 提升透明度这一步才是它能落地的关键。6.1 SHAP 全局与局部解释SHAP 输出的全局特征重要性比随机森林自带的feature_importances_可靠得多它给每个样本、每个特征一个贡献值还可以画依赖图看某个特征在不同取值下如何影响预测。实际部署时我会保存一张 SHAP 摘要图在告警页面展示给安全人员看。import shap explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(X_test_scaled[:1000]) shap.summary_plot(shap_values, X_test_scaled[:1000], feature_namesX_test.columns)对树模型用TreeExplainer是速度最快且精确的做法。shap_values可能是二维数组二分类或三维数组多分类多分类时需要按类别索引。summary_plot生成的图里横坐标是 SHAP 值正负代表对预测结果的推动方向颜色代表特征取值高低。6.2 单条流量解释的工程实现安全人员更关心的是某一条流量为什么被判定为攻击。把单样本解释封装成接口是让模型从黑匣子变成可追责工具的关键一步。sample X_test_scaled.iloc[[0]] shap_val explainer.shap_values(sample)[0] if isinstance(shap_values, list) else shap_values[0][0] local_fi pd.DataFrame({feature: X_test.columns, shap: shap_val}) print(local_fi.reindex(local_fi[shap].abs().sort_values(ascendingFalse).index).head(10))取单条样本输出 SHAP 绝对值最大的前 10 个特征这就是这条流量被判为攻击的主要依据。6.3 DALEX 交叉验证解释结论DALEX 是模型无关的可解释性工具包和 SHAP 最大的区别是它不依赖模型内部结构。做验证时我会把 SHAP 和 DALEX 的解释结果对照如果两条路径给出的特征排名差异很大说明模型内部不稳定需要回头检查数据泄漏或特征冗余。去年我维护一个 IDS 服务时就靠这种方式定位到一个特征泄漏问题源 IP 的编码出现在训练和测试里SHAP 把它排到第一位DALEX 却排在十名开外。查下去才发现是切分时没有按时间分组同一 IP 的流量同时出现在训练测试两端。从那以后我每次上线前都强制走一遍 SHAP 与 DALEX 特征排名一致性检查希望帮到你。本文还有配套的精品资源点击获取
返回列表