拓冰建站拓冰建站
首页 / 资讯中心 / 正文

数据挖掘驱动网络入侵检测:KMeans与KNN改进实践解析

简介围绕网络安全与数据挖掘交叉领域该 Matlab 实现资源聚焦改进 KMeans 聚类在网络入侵检测中的应用针对传统规则与签名方法难以应对未知攻击的问题提供了基于聚类分析的解决思路适合高校学生、安全工程师及算法初学者借鉴。压缩包共12个文件含11个.m脚本和1个.mat数据文件整体仅约24KB体积小但代码结构完整。脚本中可以看到 KMeans、GAKMeans、KNN 以及多个 Main 主程序可直接加载自带数据运行便于复现聚类检测实验并对比不同方法识别异常流量的效果。已有250人浏览学习属于轻量、易扩展的入门级示例。通过细读源码与数据组织方式能够掌握初始聚类中心优化、异常点处理、网络流量特征提取等关键步骤也可在此基础上融入SVM、Isolation Forest等算法设计更完整的入侵检测流程。1. 网络入侵检测与数据挖掘为什么 KMeans 会翻车做网络入侵检测很多第一次接触的同学会直接拿聚类算法怼数据集结果发现 KMeans 在 KDD99 这类流量数据上表现很不稳定换个随机种子就换一批聚类结果少数攻击样本被直接吞进正常簇里误报率高到没法看。这不是算法本身不行而是初始中心敏感、数据尺度不统一、少数类不平衡这三个问题叠在一起了。这份《基于数据挖掘的网络入侵检测.zip》恰好把这条线走完整了——里面有 KMeans 原始版、GA遗传算法改进版、KNN 以及改进 KNN 的实现和主脚本还带一个封装好的数据文件适合做课程设计、本科毕设算法对比或者入门实验复现。你不需要从零造轮子重点是学会怎么改参数、怎么换数据集、怎么把四个算法放在同一套评估标准下跑出可信结论。我拿到压缩包后先把文件结构理了一遍然后又逐个脚本过了一遍逻辑下面直接按“数据准备 → KMeans 与改进 → KNN 与改进 → 踩坑 → 验证与进阶”的顺序拆给你。2. 数据集与预处理mydata.mat 里的字段决定算法上限先说一个容易被忽略的事实在入侵检测任务里算法只决定下限数据决定上限。KMeans 和 KNN 本质上都是算距离如果特征之间量纲差着三个数量级距离计算就被大数值特征垄断了聚类结果基本没有参考价值。所以拿到这份资源第一件事不是跑主脚本而是先搞清楚 mydata.mat 里装了什么样的数据。2.1 特征字段协议类型、端口、流量大小与持续时间从摘要描述看数据集中应该包含协议类型、源/目标 IP、端口号、流量大小、持续时间等字段。这类字段组合在 KDD99 和 NSL-KDD 里都很常见主要体现在四个特点特征类别典型字段数值特点预处理要点协议类型protocol_type类别型字符串需 one-hot 编码或数值映射连接状态建立/断开标志类别型one-hot 编码流量统计src_bytes, dst_bytes数值跨度极大对数变换或归一化时间特征duration, 连接频率长尾分布明显归一化必要时截断异常值你可以在 MATLAB 工作区里用load(mydata.mat)加载后直接执行whos查看变量名再用size()看规模。我一般会先跑一下summary()或者手动算每列的 min、max、mean看看数据范围是否均匀。如果发现某列最大值是另一列的几千倍那预处理就得重点处理这一列否则后面 KMeans 和 KNN 的计算都会被这列带着走。2.2 归一化的两种方式与选择依据数据挖掘类任务里归一化基本是必经步骤。常见做法是% 方法一min-max 归一化把数据映射到 [0, 1] function data_norm minmax_norm(data) min_val min(data, [], 1); max_val max(data, [], 1); data_norm (data - min_val) ./ (max_val - min_val eps); end % 方法二z-score 标准化均值为 0标准差为 1 function data_norm zscore_norm(data) mu mean(data, 1); sigma std(data, 0, 1); data_norm (data - mu) ./ (sigma eps); end两份代码都加了eps防止除零。min-max 的优点是结果范围固定缺点是如果某个特征存在极端离群点正常样本会被压缩到很小的区间里。z-score 对离群点相对稳健一些但处理后数据范围不固定。入侵检测场景我一般首选 z-score因为攻击流量经常产生极端的大数值min-max 会被这些极端值带偏。这批数据里如果持续时间这类字段存在大量 0 值比如 ICMP 攻击连接z-score 处理后会集中在 -0.1 到 0.1 之间反而有助于聚类算法识别“行为类似”的样本。2.3 特征选择不是字段越多越好摘要里明确提到了特征选择是关键步骤。常见误区是把手头所有字段都喂进模型结果特征维度一高距离计算在稀疏空间里失去区分度。我处理这类问题的习惯是先看数据里有没有明显冗余字段。% 计算特征相关性矩阵找出高度相关的特征对 corr_matrix corr(data_norm); [rows, cols] find(abs(corr_matrix) 0.95 ... corr_matrix 1.0); % 输出相关性超过 0.95 的特征对索引 disp([rows, cols]);如果 src_bytes 与某个统计字段相关系数超过 0.95保留其中一个即可。注意类别型字段如协议类型不能直接参与相关分析要先映射成数值。从实践看ID3 决策树里常见的“信息增益”思路也可以用来做特征筛选但在 KMeans 这类基于距离的算法里特征数量控制在 8~15 个通常表现比较稳过多反而拉低聚类质量。3. KMeans 与 GA-KMeans从初始化缺陷到遗传算法改进KMeans 是这份资源的主线之一包里有 kmeans.m、Main_kmeans20170604new.m 和 GAKMeans.m、Main_GA_kmeans20170604.m 两组脚本。理解这组脚本的关键是先明白 KMeans 在入侵检测场景里的定位它不是用来“识别攻击类型”的而是用来“发现离群行为”的。3.1 KMeans 为什么能用于入侵检测从距离到异常KMeans 的基本思路很直白随机选 K 个初始中心把每个点分给最近的中心然后重算中心不停迭代直到中心不再移动。放在入侵检测场景里它的使用逻辑是正常流量在特征空间中会聚集在少数几个区域攻击流量因为行为模式不同距离正常簇中心很远。所以 KMeans 的输出不是“这是不是攻击”的标签而是“这个样本距离最近的簇中心有多远”。距离超过阈值的就判定为可疑。这个阈值需要根据训练集上的距离分布手动设定常见做法是取 95% 分位数。3.2 kmeans.m 算法骨架初始中心选择与迭代逻辑压缩包里的 kmeans.m 大概率是手写实现核心逻辑可以概括为function [centers, labels, dist_sum] my_kmeans(data, k, max_iter) % data: n x m 的特征矩阵 % k: 聚类簇数 % max_iter: 最大迭代次数 [n, ~] size(data); % 随机从样本中选 k 个点作为初始中心 init_idx randperm(n, k); centers data(init_idx, :); labels zeros(n, 1); for iter 1:max_iter % 计算每个样本到所有中心的距离 dist pdist2(data, centers, euclidean); % 分配样本到最近的中心 [~, labels] min(dist, [], 2); % 重新计算每个簇的中心 new_centers zeros(size(centers)); for j 1:k cluster_data data(labels j, :); if ~isempty(cluster_data) new_centers(j, :) mean(cluster_data, 1); else % 簇为空保留原中心 new_centers(j, :) centers(j, :); end end % 中心不再变化则提前终止 if norm(new_centers - centers, fro) 1e-6 break; end centers new_centers; end % 计算所有样本到所属中心的距离总和 dist_sum sum(min(pdist2(data, centers, euclidean), [], 2)); endrandperm(n, k)是从全量样本里无放回抽取 k 个作为初始中心好处是避免落在空区域。pdist2是 MATLAB 内置的距离计算函数支持多种距离度量。max_iter一般设 100 到 300入侵检测数据集通常是几万条样本迭代太快可能没收敛太慢也没必要。norm(..., fro)判断前后两轮中心的变化量小于阈值就认为收敛。这个手写版没有设置随机种子所以你每次跑出来的结果都可能不同——这也是后面避坑章节要展开的问题。3.3 GA-KMeans 改进了什么初始化优化与迭代替代GAKMeans.m 的核心改进思路是不再随机选初始中心而是用遗传算法搜索一组更优的初始中心。遗传算法维护一个“种群”每个个体是一组 K 个中心的编码用 KMeans 的类内距离总和作为适应度通过选择、交叉、变异迭代出更好的一组中心。% GA-KMeans 简化流程示意 function [best_centers, best_fitness] ga_kmeans(data, k, pop_size, generations) % 初始化种群每个个体是 k 个随机样本索引 [n, ~] size(data); pop zeros(pop_size, k); for i 1:pop_size pop(i, :) randperm(n, k); end fitness zeros(pop_size, 1); for g 1:generations % 对每个个体运行 KMeans 迭代计算适应度 for i 1:pop_size temp_centers data(pop(i, :), :); [~, ~, fitness(i)] quick_kmeans(data, temp_centers); end % 选择保留适应度最好的个体 [best_fitness, best_idx] min(fitness); best_centers data(pop(best_idx, :), :); % 交叉与变异省略具体实现 end end注意这里 GA 的运算量比普通 KMeans 高出一个量级。种群数量设 20、迭代 20 代意味着要跑 400 次 KMeans 迭代。如果数据集有几万条样本MATLAB 里跑一次可能要好几分钟。资源包里的 Main_GA_kmeans20170604.m 直接运行没问题但如果你想在更大数据集上用建议把种群数量降到 10、代数降到 10先看效果再逐步加。3.4 主脚本怎么调Main_kmeans 与 Main_GA_kmeans 的使用要点主脚本命名有规律Main_kmeans20170604new.m 对应普通 KMeansMain_GA_kmeans20170604.m 对应 GA 改进版。打开脚本后重点看三段代码的位置。第一段是读数据通常长这样load(mydata.mat); % 假设数据存在 X 变量中标签存在 label 中 X mydata;第二段是参数设置你要改的核心参数就三个簇数 K、迭代次数、随机种子如果代码里用rng设置了的话。簇数 K 的选择没有标准答案常见做法是分别跑 K2、3、4、5对比类内距离总和选择拐点处的 K 值。第三段是结果输出通常会画散点图或者打印聚类中心。如果图形上不同簇的颜色混在一起先检查是不是没用归一化数据做聚类。4. KNN 与改进 KNN从有标签分类到高效检索压缩包里另外一组脚本是 KNN 体系包括 KNN.m、KNNnew.m、Main_KNN20170604.m、Main_KNN20170606.m、Main_IMP_KNN20170604.m 和 Untitled.m。KNN 与 KMeans 最大的区别是KMeans 是无监督聚类KNN 是有监督分类。在这个项目里KNN 是拿已知的攻击类型样本做训练然后给新样本分类。4.1 KNN 的分类原理投票机制与决策边界KNN 的思路是物以类聚一个待检测样本看它周围最近的 K 个已知样本属于什么类别少数服从多数把出现次数最多的类别作为预测结果。数学上说它是在特征空间中构建一个隐式决策边界边界形状不固定比线性分类器灵活。function pred_label knn_classify(X_train, y_train, X_test, K) % X_train: 训练样本特征矩阵 % y_train: 训练样本标签 % X_test: 测试样本特征矩阵 % K: 近邻数量 n_test size(X_test, 1); pred_label zeros(n_test, 1); for i 1:n_test % 计算测试样本到所有训练样本的距离 dist sqrt(sum((X_train - X_test(i, :)).^2, 2)); % 取距离最小的 K 个样本的索引 [~, sorted_idx] sort(dist); k_idx sorted_idx(1:K); % 统计 K 个邻居中出现最多的类别 k_labels y_train(k_idx); pred_label(i) mode(k_labels); end end这段代码逻辑清晰但性能很差每个测试样本都要和全部训练样本算距离。训练集 1 万条、测试集 5000 条、特征维度 15就要算 5000 万次距离运算在 MATLAB 里可能要跑几十秒到几分钟。KNNnew.m 存在的意义很可能就是优化这部分计算。常见优化手段包括用pdist2一次算出距离矩阵、用 KD-Tree 做近邻搜索、或者先对训练集聚类再用簇中心做粗筛选。4.2 K 值怎么选偏小过拟合偏大欠拟合K 值是 KNN 里最关键的参数。K 太小比如 K1分类结果对噪声极度敏感一个离群点就能改变判定结果。K 太大比如 K50相当于把大半个数据集都拉进来投票类别比例完全决定了预测结果个体特征基本失效。我常用的做法是交叉验证将训练集分成 5 份轮流拿其中 4 份做训练、1 份做验证测试 K1、3、5、7、9、11、15、21 时的平均准确率画出曲线后选最高点对应的 K 值。% K 值寻优示意 K_values [1, 3, 5, 7, 9, 11, 15, 21]; accuracies zeros(length(K_values), 1); for idx 1:length(K_values) accuracies(idx) cross_val_knn(X_train, y_train, K_values(idx), 5); end best_k K_values(find(accuracies max(accuracies), 1)); disp([最优 K , num2str(best_k)]);注意K 值选完后要用全部训练数据重新训练一次因为交叉验证只是选参不负责最终模型。4.3 改进 KNN 的方向加权投票与训练集约减Main_IMP_KNN20170604.m 对应的是改进版 KNN。最常见的改进是加权投票距离越近的邻居投票权重越大而不是所有 K 个邻居一样说话。% 距离加权 KNN近邻权重 1 / (距离 eps) function pred_label weighted_knn(X_train, y_train, X_test, K) n_test size(X_test, 1); pred_label zeros(n_test, 1); for i 1:n_test dist sqrt(sum((X_train - X_test(i, :)).^2, 2)); [dist_sorted, sorted_idx] sort(dist); k_idx sorted_idx(1:K); weights 1 ./ (dist_sorted(1:K) eps); k_labels y_train(k_idx); % 统计加权票数 unique_labels unique(k_labels); scores zeros(length(unique_labels), 1); for j 1:length(unique_labels) match (k_labels unique_labels(j)); scores(j) sum(weights(match)); end [~, best] max(scores); pred_label(i) unique_labels(best); end end加权 KNN 的好处是边缘样本对结果影响不会过大在入侵检测场景里有些攻击样本和正常样本在特征空间上距离很近不加权的话容易被少数几个误标样本带偏。MAIN_IMP_KNN 脚本很可能还做了训练集约减——把冗余样本删掉只保留支持边界形状的样本这样推理速度能快一倍以上。4.4 KNN 与 KMeans 在入侵检测里的分工从实际脚本用途看两条线是互补的KMeans 及其改进版适合在没有标签的情况下做异常发现通过距离簇中心的偏差判断异常行为KNN 体系适合在有标注数据时做精确分类判断具体的攻击类型比如 Probe、DoS、R2L、U2R。一个完整的检测系统是先用 KMeans 降维或剔除明显正常的数据再把可疑部分交给 KNN 分类这样能显著降低计算开销。5. 避坑实录五个让 MATLAB 脚本跑不完的细节这份资源包里的脚本我逐个过了一遍下面是实际操作中大概率会遇到的问题每条都是“现象 → 原因 → 解决”的结构能帮你少走弯路。5.1 每次运行结果都不一样初始化随机性现象连续跑两次 KMeans聚类结果和准确率都对不上甚至每次画出的图都不同。原因KMeans 用randperm随机选初始中心rand种子默认基于当前时间每次启动都是新序列。解决在脚本开头加rng(42)固定全局随机种子。42 只是一个习惯值你也可以用 0 或 2024但同类实验要对齐算法、对齐种子否则结果无法复现。KNN 一般不受影响因为它的分类不依赖初始化。5.2 duration 和 src_bytes 一起算距离聚类全是 src_bytes 的形状现象对归一化前的原始数据直接跑 KMeans聚类结果中 duration 字段几乎不起作用簇边界全由 src_bytes 决定。原因src_bytes 取值范围可能是 0 到几十万duration 只有 0 到几百欧氏距离的计算结果被大数值字段完全主导。解决先归一化再聚类。注意把归一化参数均值和标准差先算好用在训练集上测试集用同样的参数做变换不能单独对测试集拟合。5.3 加载 mydata.mat 时报错路径与工作区问题现象双击主脚本直接运行提示Unable to read file mydata.mat或Undefined variable mydata。原因脚本和执行环境不在同一个工作目录MATLAB 搜索路径里没有数据集所在目录或者文件名大小写没对上。解决用cd切换到压缩包解压目录后再运行或者在脚本开头加% 切换到脚本所在目录 file_dir fileparts(mfilename(fullpath)); cd(file_dir);注意文件夹路径里不要有中文和多余空格MATLAB 对这两者支持很差经常会莫名报错。5.4 聚类结果里某个簇是空的现象KMeans 迭代过程中某个簇分配不到任何样本聚类中心变成 NaN后续计算全部报错。原因K 设置过大或者初始中心选在了样本稀疏区域加上少数类样本极少迭代中该簇被判空。解决kmeans.m 里已经有空簇保留原中心的逻辑如果你自己的实现里没加参考前文代码补上。同时不要把 K 设得超过样本类别数的合理范围——入侵检测里 K2 到 5 足够覆盖“正常 几种攻击”的场景。5.5 归一化顺序错乱把测试集混进训练集拟合现象先用全部数据含测试集算均值和标准差再切分训练测试结果准确率虚高但换一批真实新数据就崩。原因测试集信息泄漏到了训练过程里“模型”在训练时已经见过了测试集的分布范围。解决先切分数据再归一化。训练集算出 min/max 或 mean/std测试集用同一组参数变换这是数据挖掘里最基础的纪律。6. 验证与进阶从跑通脚本到跑出可信结论跑通主脚本只是第一步课程设计或毕设答辩时真正拉开差距的是你会不会验证和改进。6.1 三件套评估指标准确率、检测率、误报率入侵检测不能只看准确率。比如数据里 95% 是正常样本5% 是攻击样本全部预测为正常的模型准确率也有 95%但毫无用处。至少要报告三个指标指标公式含义准确率 Accuracy(TPTN)/总样本数整体预测正确比例检测率 Detection RateTP/(TPFN)攻击样本被识别出的比例误报率 False AlarmFP/(FPTN)正常样本被误判为攻击的比例如果你的脚本里没有输出混淆矩阵自己加几行% 计算混淆矩阵和关键指标 C confusionmat(true_label, pred_label); TP C(2,2); FP C(1,2); TN C(1,1); FN C(2,1); accuracy (TPTN) / sum(C(:)); detection_rate TP / (TP FN); false_alarm FP / (FP TN);一个值得追求的目标是检测率超过 90% 的同时误报率低于 10%。很多改进算法的论文都在这两个指标间做平衡。6.2 把数据集换成 NSL-KDD 的实操路径mydata.mat 提供了一组现成数据但更好的实验往往需要换数据集。NSL-KDD 是 KDD99 的去重改进版可读性更好。换数据的要点是把外部数据整理成和 mydata.mat 相同的结构数值矩阵加一个标签列标签列用 1 表示正常、2 表示攻击或者反过来取决于主脚本的约定。% 从 CSV 读取新数据并转存为 mat 格式示例 % 假设 data.csv 最后一列是标签 new_data readmatrix(data.csv); X_new new_data(:, 1:end-1); y_new new_data(:, end); % 做一次 z-score 归一化 X_norm zscore(X_new); save(new_dataset.mat, X_norm, y_new);注意把脚本中原有的 load 和变量名改成与这里一致。如果脚本里写的是mydata你可以在加载后执行mydata X_norm;保持变量名不变。6.3 进阶方向从 KMeans 到二分 KMeans 和增量检测如果实验还有余力我建议尝试一个低成本改进方向用二分 KMeans 替代原始 KMeans。二分 KMeans 先把全部样本当作一个簇然后每次选一个簇二分直到达到指定簇数。它能明显缓解初始中心敏感问题实现也不复杂% 二分歧义每次对 SSE 最大的簇做 2-means function [centers, labels] bisecting_kmeans(data, k, max_iter) labels ones(size(data, 1), 1); current_k 1; while current_k k % 找到当前 SSE 最大的簇 sse zeros(current_k, 1); for i 1:current_k cluster_data data(labels i, :); if size(cluster_data, 1) 2 sse(i) -inf; % 样本太少不拆分 else sse(i) sum(sum((cluster_data - mean(cluster_data, 1)).^2, 2)); end end [~, split_idx] max(sse); % 对该簇做 2-means [centers_2, labels_2] my_kmeans(data(labels split_idx, :), 2, max_iter); % 更新全局标签 new_label current_k 1; member_idx find(labels split_idx); labels(member_idx(labels_2 1)) split_idx; labels(member_idx(labels_2 2)) new_label; current_k current_k 1; end % 最终重算中心 centers zeros(k, size(data, 2)); for i 1:k centers(i, :) mean(data(labels i, :), 1); end end这套二分歧义在多次实验里比原始 KMeans 稳定不少对于入侵检测这个场景尤其合适。从那以后我每次做聚类实验都强制先跑一遍二分 KMeans 做对照确认原始 KMeans 的波动范围再下结论。希望这套拆解能帮你在课程设计或毕设里少走弯路把资源包里的代码变成真正属于自己的实验成果。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门