ARTICLE DETAIL

资讯详情

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

Matlab干扰源聚类实战:K-means、DBSCAN、层次聚类与GMM调参

Matlab干扰源聚类实战:K-means、DBSCAN、层次聚类与GMM调参 常年在无线电台站做监测数据处理的人应该都有过这种感觉手里攥着成千上万条信号测量记录却没有一条能告诉你它们分别来自哪部发射设备。我们唯一能看到的是每条记录的到达角、带宽、信号强度、脉宽等参数然后要在毫无标签的情况下判断“这几条属于同一个干扰源那几条属于另一个干扰源”。这就是干扰源聚类分析典型的使用场景。这篇文章想聊的就是这件事用Matlab把几种主流聚类方法在干扰源分选任务里跑通包括K-means、DBSCAN、层次聚类和高斯混合模型并给出能直接改来用的完整代码。适合正在做频谱监测、信号分选、电子侦查数据处理的朋友参考也适合那些刚接触聚类、想知道方法之间到底差在哪的Matlab使用者。我不会只贴代码会把每种方法为什么适合这类数据、踩过的坑和调参经验一起写清楚。1. 干扰源聚类在做什么一个真实的信号分选场景1.1 问题本质从“疑似”到“确认”先说清楚任务本身。假设你在城市里架设了一台监测接收站某频段内出现了不明干扰。接收站按时间顺序捕获了几千条信号测量记录每条记录本质上是一个特征向量通常包含到达时间、频率、带宽、信号强度、到达角、脉宽等。这些记录里既有真正干扰源发出的信号也有环境噪声和偶发信号而干扰源本身可能不止一部——可能是几部设备在不同位置同时工作。聚类的目标就是把这些没有标签的样本按照“来自同一部干扰源”的相似度分成若干组。完成这一步之后后续的干扰源定位、优先级排序、处置决策才有依据。所以这种聚类分析不是学术游戏而是频谱管理流程里非常前置的一个环节。这里有个很容易误解的地方很多人把聚类当成分类来做总想先训练一个模型再预测。但干扰源场景里根本拿不到可靠标签你没法提前知道“这个信号属于哪部设备”更不可能把所有可能出现的新干扰源都预先建进训练集。聚类的好处是它不做这种假设直接从数据自身的分布结构出发把相似的样本归到一起。1.2 为什么通用检测方法不够用在实际项目里最先被尝试的往往是两种方法门限检测和人工分选。门限检测的原理很简单比如设定“到达角在30度附近、强度大于-70dBm”就算命中某个源但当干扰源本身有频率漂移、功率起伏时门限很容易误判窄了漏检宽了误报。人工分选在数据量少的时候可以几百条数据还能在散点图里圈一圈样本上万时就完全不可行了。还有一种思路是上监督学习比如用SVM或神经网络做分类。问题是这类算法需要的标注数据恰好是我们最缺的东西。就算你今天花大力气标了一批数据明天出现一部新型干扰源它的特征分布和原来的都不一样模型基本失效。所以聚类分析才会成为这类任务中的主力工具。它不做先验假设能从数据本身发现结构而且对“类别数不确定”的情况天然友好。但“聚类”这个词下面其实藏着一大堆方法不同方法对数据形态的假设差别很大选错了结果会很难看。这正是我接下来要展开的重点。2. 四种经典聚类方法在干扰源数据上的取舍2.1 K-means快速摸底但脆弱K-means应该是大多数人接触到的第一个聚类算法。它的核心思路是确定类别数K随机初始化K个质心然后反复迭代让每个样本归到距离最近的质心再重新计算质心位置直到收敛。数学上是在最小化样本到所属质心的欧氏距离平方和。在干扰源场景里K-means最大的价值是快。数据量上万甚至几十万条时它依然能在很短时间跑完特别适合做数据摸底——先粗略看看大致有几堆。但也有三个明显的弱点。第一它必须预先指定K。在真实干扰监测中你往往不知道现场有几部干扰源只能通过尝试多个K值来猜。第二它对噪声和离群点非常敏感。一次偶然捕获的异常信号比如远处雷电引起的突发脉冲会被单独拉成一个质心或者把某个簇的中心拽偏。第三它假设簇形状是类球形的。真实干扰源的特征分布往往比这复杂簇可能细长、弯曲甚至相互搭边K-means这时候就力不从心了。2.2 DBSCAN密度聚类更贴近信号散布DBSCAN是另一条完全不同的路子。它不看质心而是看密度如果一个点周围足够密集就把它所在的区域向外扩展连成一片那些处在低密度区域的孤点则被标记为噪声。它有两个关键参数epsilon定义“邻域”的半径以及minPts定义“邻域内至少要有几个点才算密集”。这种算法对干扰源数据的适配性比K-means好很多。真实信号在特征空间里的分布往往就是“一团一团”的干扰源的观测样本越多那一团就越密实簇与簇之间通常存在稀疏地带——这正好是DBSCAN最喜欢的数据形态。而且它不需要预先指定聚类数还能自动把环境噪声单独标出来相当于聚类和滤噪一步完成。但DBSCAN也有自己的麻烦。它对epsilon极其敏感设小了会把一个簇拆成好几块设大了会把两个相邻干扰源合在一起。还有一个更麻烦的问题它使用全局统一的密度阈值如果现场既有关得很近的固定台站又有距离监测站很远、信号散布很大的移动干扰源单一epsilon就很难同时照顾这些情况。这个问题我后面会专门讲应对方案。2.3 层次聚类不预设类别数的最大优势层次聚类和前面两种都不一样它不直接给你一个分组结果而是先递归地把样本合并成树状结构。Matlab里最常用的是自底向上的聚合层次聚类每一步都把距离最近的两组样本合并直到所有样本成为一个整体。这棵“谱系树”会把数据从单个样本到完整一类的全部合并过程记录下来。对干扰源分析来说层次聚类最有吸引力的地方在于它的探索性。你可以先用算法生成树然后在任意截断高度取分组——不需要提前纠结K值而是看完树状图再决定从哪里切。我常把这个过程类比成看地图K-means是别人替你标好了几个城市而层次聚类是先把所有道路画出来你再看哪里是天然的分界。不过代价是计算开销大。我在工程里一般只对不超过两万条样本的数据跑层次聚类超过这个量级ward方法也就是最小化合并后簇内方差的合并策略的计算时间会迅速膨胀。所以它在项目中更适合小规模、需要仔细判断的场景比如对K-means和DBSCAN结果不一致的样本做复核。2.4 高斯混合模型处理特征重叠的软聚类工具GMM和K-means有相似之处都要预先指定类别数K但它比K-means灵活得多。GMM假设每类数据都来自一个多维高斯分布整个数据集是多个高斯分布的混合。算法通过期望最大化迭代估计每个高斯成分的均值、协方差和混合权重然后输出每个样本属于每个成分的概率。这种“软划分”能力在干扰源特征出现重叠时特别好用。比如两部干扰源的到达角接近强度范围也差不多它们的特征分布在平面上有交叠。K-means只能硬生生把交叠区域切开产生明显错分而GMM能给出“这个样本有60%属于源A、40%属于源B”的概率结果让后续的决策留有回旋余地。GMM的短板在于训练不稳定。协方差矩阵的估计容易受到奇异点影响有时候需要加正则化参数才能收敛。它同样需要预设K而且对初始化比较敏感。我的做法是把GMM当作验证工具当K-means和DBSCAN的结果有分歧时用GMM输出一个更精细的概率判断而不是把它当主力方法。为了直观比较我总结了一张表方法需要预设类别数能否识别噪声对密度不均数据计算开销典型用途K-means是否较差低快速摸底、大样本粗分DBSCAN否是中等中类别数未知、存在异常样本层次聚类最终切树时需指定否较好高小样本探索、复核分歧GMM是软划分较好中高特征重叠、边界模糊3. Matlab代码实现从模拟数据到四种聚类结果3.1 先按干扰源的物理特征造一份模拟数据很多朋友拿到真实测量数据之后喜欢直接跑算法我建议先别急。第一真实数据没有标签你即使跑出结果也没法验证算法选得对不对第二真实数据的噪声形态很复杂刚开始就处理它容易把问题和方法混在一起。正确流程是先用模拟数据把整个链路跑通确认每种方法的行为符合预期再切换到真实数据上去。我构造一个典型的场景四个干扰源每个源用三个特征描述——到达角单位度、带宽单位MHz、信号强度单位dBm。四个源的参数设置不同然后在每个源周围加一定幅度的随机扰动模拟测量误差和信号波动。rng(42); % 四个干扰源的样本数量 n [60; 50; 55; 45]; % 干扰源中心[到达角, 带宽, 信号强度] mu [35, 2.0, -60; 120, 1.5, -55; 230, 3.2, -70; 310, 1.8, -48]; % 每个源的扰动幅度标准差 sigma [1.5, 0.2, 1.5; 2.0, 0.3, 2.0; 2.5, 0.4, 1.8; 1.8, 0.25, 2.5]; data []; truth []; for i 1:4 block randn(n(i), 3) .* sigma(i,:) mu(i,:); data [data; block]; truth [truth; i * ones(n(i), 1)]; end % 随机打乱顺序模拟实际捕获时的无规律状态 perm randperm(size(data, 1)); data data(perm, :); truth truth(perm, :);这里有个细节容易出错很多人在打乱顺序时会写两次randperm一次给数据、一次给标签结果标签和数据没有同步置换后面做效果评估就全错了。正确写法是像上面这样只生成一次索引然后同时作用到两个变量上。3.2 标准化预处理这一步不做后面全白搭如果直接把上面这份数据丢给聚类算法结果会非常糟糕。原因很简单到达角以度为单位数值在几十到三百多之间带宽以MHz为单位数值只有一到几信号强度以dBm为单位数值在负几十左右。三个特征的尺度天差地别在计算欧氏距离时量纲大的特征会完全吞掉量纲小的特征等于只有信号强度这一个维度在起作用。解决办法是标准化。Matlab里一行命令就能完成data_z zscore(data);zscore做的事很简单每个特征减去均值再除以标准差使每个特征变换成均值为0、标准差为1的分布。这一步做完三个特征的贡献权重就平衡了。后面所有聚类算法都应该在data_z上运行而不是原始数据。强调一下特别是用过DBSCAN的朋友标准化对DBSCAN的影响比K-means大得多因为DBSCAN的距离阈值epsilon是直接把半径限定在一个数值上如果某个特征尺度特别大epsilon选多大都会被它主导结果就是要么整片区域都连成一体要么全被标成噪声。先把特征尺度统一后续调参才有意义。3.3 四种算法一次跑通现在把四种聚类方法都跑一遍。需要注意kmeans、dbscan、linkage、fitgmdist这些函数属于Statistics and Machine Learning Toolbox没有这个工具箱的话需要先装上。K 4; % 1. K-means [idx_k, C_k] kmeans(data_z, K, Replicates, 10, MaxIter, 500); % 2. DBSCAN epsilon 0.5; minPts 5; idx_db dbscan(data_z, epsilon, minPts); % 3. 层次聚类ward方法欧氏距离 Z linkage(data_z, ward, euclidean); idx_h cluster(Z, MaxClust, K); % 4. 高斯混合模型 gm fitgmdist(data_z, K, RegularizationValue, 0.01); idx_g cluster(gm, data_z);关于K-means里的Replicates, 10这是一个很值得养成的习惯。K-means的初始质心是随机的不同初始点可能收敛到不同的局部最优解多跑几次可以降低这种随机性保留最优的那次结果。DBSCAN那边epsilon我初步取0.5、minPts取5这两个参数不是拍脑袋后续我会在参数调优部分详细说怎么通过k-距离图来确定。层次聚类的cluster(Z, MaxClust, K)表示把树在某个高度切断得到K个簇。GMM里的RegularizationValue是防止协方差矩阵奇异用的样本特征相关性太强或者某个簇样本太少时会遇到收敛失败加一点正则值能避免这个情况。3.4 结果可视化与自动标记聚类结果是用来看的更是用来判断的。Matlab里最方便的工具是gscatter它能把不同类别的样本用不同颜色和符号画出来。我通常把四个子图画在同一个figure里方便对比四种方法的行为差异。figure; tiledlayout(2,2); nexttile; gscatter(data(:,1), data(:,3), idx_k); title(K-means (K4)); xlabel(到达角 (度)); ylabel(信号强度 (dBm)); nexttile; gscatter(data(:,1), data(:,3), idx_db); title(DBSCAN); xlabel(到达角 (度)); ylabel(信号强度 (dBm)); nexttile; gscatter(data(:,1), data(:,3), idx_h); title(层次聚类); xlabel(到达角 (度)); ylabel(信号强度 (dBm)); nexttile; gscatter(data(:,1), data(:,3), idx_g); title(GMM (K4)); xlabel(到达角 (度)); ylabel(信号强度 (dBm));这里我用的是原始数据中的到达角和信号强度两维而不是标准化后的数据主要是为了让坐标轴有明确的物理含义。有一点需要特别提醒DBSCAN的idx_db里可能含有-1标签代表噪声点。gscatter对噪声点的处理是把它单独归为一类画出来没问题但计算轮廓系数时一定要先把这些点滤掉否则会报错。这类小细节在实际使用中容易卡住人。4. 聚类效果评估与调参实操把参数定到能落地的程度4.1 轮廓系数与evalclusters的使用跑完聚类只算完成了第一步关键问题是这些结果到底行不行对带标签的模拟数据我们可以拿聚类结果和真实分组做对比计算调整兰德指数但对没有标签的真实测量数据我们只能用内部指标评估其中轮廓系数是最常用的一种。轮廓系数的含义简单说就是对一个样本看它跟同簇其他样本的平均距离记为a再看它跟最近邻簇样本的平均距离记为b轮廓值就等于(b-a)/max(a,b)。这个值越接近1说明该样本离自己簇的紧密程度明显优于离其他簇的接近程度聚类效果越好接近-1则说明很可能分错了簇。整体聚类质量用所有样本轮廓系数的均值来衡量。sil_k mean(silhouette(data_z, idx_k)); % DBSCAN需先去掉噪声点标签为-1的样本 valid idx_db 0; sil_db mean(silhouette(data_z(valid, :), idx_db(valid))); sil_h mean(silhouette(data_z, idx_h)); sil_g mean(silhouette(data_z, idx_g)); fprintf(平均轮廓系数K-means%.3f, DBSCAN%.3f, 层次%.3f, GMM%.3f\n, ... sil_k, sil_db, sil_h, sil_g);需要说明的是轮廓系数本身不能完全代表聚类正确性它更像一个内部自洽性的度量。如果某个方法轮廓系数低先别急着否认方法很可能是参数没调好——这正是下一步要解决的问题。Matlab里还有一个集成函数evalclusters可以直接帮你扫一组候选K值并计算指标避免手动写循环eva evalclusters(data_z, kmeans, Silhouette, KList, 2:7); figure; plot(eva);evalclusters的KList可以设成你怀疑的范围。在干扰源场景里我一般把K从2扫描到7或8、配合轮廓系数和Calinski-Harabasz指标一起看如果两个指标给出的最佳K不一致就说明数据本身的结构不够清晰需要结合人工经验判断。4.2 DBSCAN的epsilon怎么定k-距离图DBSCAN是唯一不需要预设簇数的算法但它的epsilon参数比K值更难猜。很多人在这里用暴力枚举法从0.1试到2.0看轮廓系数变化。这方法不是不行只是效率低而且容易因为轮廓系数波动错过合适的区间。更稳妥的做法是先画k-距离图。基本思路是对每个样本找出离它第minPts近的那个邻居的距离把所有这些距离升序排列画成曲线。曲线通常会出现一个明显拐点拐点对应的纵坐标就是比较合理的epsilon。minPts 5; distances pdist2(data_z, data_z); kdist zeros(size(data_z, 1), 1); for i 1:size(data_z, 1) d sort(distances(i, :), ascend); kdist(i) d(minPts 1); % 因为d(1)是样本自身距离为0 end kdist sort(kdist, ascend); figure; plot(kdist, o); xlabel(样本序号按距离排序); ylabel(sprintf(第%d近邻距离, minPts)); grid on;理论上如果数据里有明确的密度分界拐点会非常清晰但真实实测数据的k-距离图往往是一条缓坡曲线没有明显拐点。这时候我的经验是以k-距离曲线的中后段为参考选一个能让轮廓系数稳定在较高水平的epsilon同时在0.3到0.8这个范围内多扫几个值对比。minPts的设定则和数据规模有关样本越多minPts可以越大一般取5到10之间就够了取太大容易把小簇全部吞掉。4.3 GMM成分数与BICGMM的成分数K没法用轮廓系数直接选因为GMM输出的是软聚类概率轮廓系数在这种输入下并不完全合适。工程上更常用的是BIC贝叶斯信息准则。BIC把所有可能的高斯成分数都拟合一遍然后看哪个K对应的BIC值最低模型在拟合能力和复杂度之间取得平衡。bic_values zeros(1, 6); for g 1:6 gm_tmp fitgmdist(data_z, g, RegularizationValue, 0.01); bic_values(g) gm_tmp.BIC; end figure; plot(1:6, bic_values, -o); xlabel(高斯成分数 K); ylabel(BIC); grid on;注意BIC的绝对值没有意义只有相对大小有意义所以只要比较“哪个K的值最低”或“下降趋势在哪里变缓”就行。而且GMM对初值敏感同一个K可能因为初始参数不同跑到不同的局部最优所以每个K值最佳尝试跑两三次取BIC最小的那次。4.4 综合调参顺序建议在实际项目里我不会在一个指标上纠结太久更常用的是“先粗后细”的流程第一步用kmeans配合evalclusters快速扫描K大致确定干扰源数量的合理范围。第二步用DBSCAN的k-距离图选epsilon扫几个候选值看轮廓系数和噪声比例。噪声比例过高通常意味着epsilon太小或minPts太大。第三步如果前两步结果分歧明显用GMMBIC做交叉验证看GMM给出的成分数和最可能的簇中心落在哪里。从我个人的经验看这个流程基本能把参数定在“能落地”的程度。相比某一次的具体取值更重要的是整个过程的可复现性每次调整参数都记录轮廓系数、噪声比例、簇中心位置。尤其在频谱监测这种需要定期出报告的场合可复现的记录比一次漂亮的聚类结果更有价值。5. 实测中最容易翻车的四个细节量纲、密度、规模与时变性5.1 特征量纲问题DBSCAN对距离度量极其敏感我最初跑真实监测数据时犯过一个特别低级的错误直接把原始特征矩阵丢进dbscan结果整片数据几乎被聚成一团。当时我一度怀疑是数据本身的问题后来检查发现根本没有做标准化。到达角、频率和信号强度三个特征量纲差异大到让距离计算完全失效。解决办法不只是zscore一种。如果某些特征本身就是区间型的比如到达角是0到360度直接按数值计算距离会忽略“0度和360度是同一个方向”这个事实。对这类角度特征更合理的做法是先变换成单位圆上的坐标也就是把角度转成正弦值和余弦值两个维度再做标准化。不过这个处理会让特征维度增加需要结合你的具体特征定义来判断。至少任何特征不做归一化就上聚类算法在干扰源这种混合量纲数据上是必翻车的。5.2 全局epsilon与密度不均远处干扰源的簇更松散DBSCAN的epsilon是全局的这在实际电磁环境中会带来麻烦。靠近监测站的干扰源信号强、测量稳定特征散布小簇很密实而距离较远的干扰源信号经过长距离传播和多径效应特征波动明显簇明显松弛。如果你按远处源的标准选大epsilon近处的几个源就可能被连成一片按近处源的标准选小epsilon远处的源又会碎成噪声。两条应对思路供你参考。一是把特征空间划分成几个子区域比如先按到达角分区间再在每个区间里单独做DBSCAN让密度估计更贴合局部数据。二是干脆放弃DBSCAN改用层次聚类或GMM因为这两种方法不用全局密度阈值对密度不均的容忍度更高。我的建议是同时保留DBSCAN和GMM的结果如果两者在某个样本上的归属不一致就把这个样本标记为“需要复核”而不是简单选一个结果。5.3 数据量大时的计算瓶颈层次聚类从好用到不可用Matlab的linkage在样本量小的时候非常好用谱系图也能看得很清楚。但一旦样本量超过两三万ward方法的计算量会急剧上升而且linkage需要保存距离矩阵的中间信息内存占用很容易失控。我在一次处理连续监测数据时就遇到过这种情况程序跑了十几分钟还没结束最后只能强制中断。如果你的数据规模也很大可以考虑两个策略。第一个策略是先做一次快速的K-means预处理把样本粗分成几十个“子簇”再用层次聚类去分析子簇之间的关系。这种两级策略在文献里叫两阶段聚类精度损失不大但速度提升非常明显。第二个策略是换工具DBSCAN在大样本上的表现比层次聚类稳定得多因为它不需要构造全局谱系树计算量可以接受。如果连DBSCAN也慢那就先抽样一部分做参数标定再对全量数据跑参数已经确定的模型。5.4 特征漂移与时变性同一个干扰源今天和昨天“长得不一样”干扰源不是一成不变的。固定台站的信号参数相对稳定但移动干扰车、无人机搭载的干扰设备会随着运动持续改变到达角和信号强度有些干扰源还有跳频或扫频工作模式带宽特征也会随时间变化。如果在很长的时间跨度内直接做一次聚类同一个源的特征分布被拉伸变形很容易被算法拆成多个“伪源”。我的处理办法是给时间加上约束。具体操作是把长时间数据按小时或半小时切成时间窗在每个窗口内单独做聚类得到每个干扰源在该时段的簇中心然后对簇中心做跟踪判断哪些簇中心随时间连续移动、属于同一个移动源哪些静止不动、属于固定源。这样做的好处是既利用了短时数据的稳定性又不会把时变特征误当成多个干扰源。这个方法比单纯把时间作为一个特征维度丢进聚类算法要可靠得多因为前者尊重了特征的物理含义。最后再说点实际操作层面的体会。我到今天做干扰源聚类分析依然不会只跑一种方法常规流程是先上K-means快速摸底再用DBSCAN精分并自动剔除噪声最后对落点模糊的样本用GMM验一遍概率。三种方法互相咬合比任何单一算法的输出都值得信赖。如果你现在只是刚开始接触建议先把我给的模拟数据代码改造成你自己的特征矩阵把整个流程跑通一次再逐步加入调参、评估这些环节。这套思路不只在干扰源分选上适用任何“特征已知、标签未知”的分组问题都可以照搬。
返回列表