简介:这套基于Python深度学习的光纤传感水声信号识别方法完整实现,面向毕业设计、人工智能与信号处理方向的开发者,解决光纤传感系统中水声信号准确分类与水下环境监测问题。方法采用最优聚类模型,并以光纤传感系统自身固有噪声信号分解分量作为训练数据,降低了对额外数据采集的依赖。资源共276个文件,压缩包约6.93MB,包含11个Python脚本、7个Jupyter Notebook、209张图片、14个CSV特征文件、12个PKL模型文件、4个PTH权重文件及说明文档,覆盖数据处理、特征提取、模型训练与可视化全过程。目前已有46人学习。读者可获得完整可运行代码、特征数据集、训练好的模型权重以及结构清晰的目录,便于直接复现实验、理解聚类与深度学习的结合方式,并可在此基础上进行参数调整与扩展研究,适用于水下目标探测、环境监测等场景。
1. 为什么“固有噪声分解分量”可以作为水声信号识别的原始训练数据
识别水声信号最难拿到的往往不是模型,而是干净的正样本。光纤传感系统(光纤水听器、分布式声学传感)在现场会同时收到水流、平台振动、生物扰动和环境机械噪声,真实水声片段要么标注成本高,要么类别极不均衡。一个可行的突破口,是先采集光纤传感系统自身固有噪声,比如激光相对强度噪声、光电探测器散粒噪声、探头附近低频机械振动,再用 VMD 或 EMD 这类方法把噪声分解成多个本征模态分量,以这些分量作为原始训练数据去训练最优聚类模型。背后的判断是:水声信号本质上是“不属于任何已知噪声分布”的异常模式。因此不需要事先准备大量真实水声样本,只要把噪声基底建模得足够细,就能在高维特征空间用距离把水声帧挑出来。这套做法尤其适合岸站长期监测和分布式光纤传感的入侵识别。
2. 信号分解:把固有噪声拆成可训练的分量与特征矩阵
直接把原始时域波形送进深度网络不是不行,但光纤传感信号的信噪比往往低于 -20 dB,模型很容易把噪声的统计起伏当成“特征”。常见做法是在输入端先做模态分解,把一帧噪声拆成一组有限带宽的分量,再用这些分量构造训练矩阵。分解这一步决定了后面聚类能不能形成清晰边界,值得先把参数和采集条件说清。
2.1 为什么在光纤传感上优先用 VMD 而不是 EMD 或小波包
EMD 的好处是不需要预设基函数,但在光纤传感噪声上模态混叠和端点效应很明显,同一频带在不同帧里可能被拆到不同分量,导致后续聚类不稳定。小波包分解需要选小波基和分解层数,选得不合适会把窄带噪声切碎。相比之下,VMD(变分模态分解)把信号分解为 K 个具有中心频率的带限本征模态函数,每个模态在频域上有明确位置,更适合用来构造稳定的特征空间。
| 分解方法 | 是否需要预设参数 | 在光纤噪声数据上的主要问题 | 适用性 |
|---|---|---|---|
| EMD | 不需要 | 模态混叠、端点效应,分量不固定 | 不适合直接喂聚类模型 |
| 小波包 | 需要小波基、层数 | 基函数依赖经验,分解结果解释性弱 | 适合做滤波,不适合做基底建模 |
| VMD | 需要 K 和 alpha | K 选错会出现中心频率重叠 | 中心频率稳定,适合后续聚类 |
VMD 也不是没有代价。它要求提前指定模态数量 K,这个 K 和后面聚类的簇数不是同一个概念。VMD 的 K 是“把一帧信号切成几个频带”,聚类簇数是“整个噪声库里有几种稳定状态”。两者如果混为一谈,后面调参会很难收场。
2.2 采集“干净固有噪声”的三个前提
要让 VMD 分解出来的是系统自身噪声,现场采集时要满足三个条件。第一,选择没有人工声源和水流扰动的时段,比如凌晨或设备检修窗口,连续采集 5 到 10 分钟。第二,如果环境噪声仍然躲不掉,可以在探头表面加阻尼材料,模拟“无水声激励”状态。第三,原始解调相位数据先做预处理:去掉直流分量、做 50 Hz 工频陷波、再按帧切分。
帧长一般取 4096 或 8192 个采样点,重叠 50%。如果采样率是 50 kHz,4096 点对应约 82 ms,能分辨到几十赫兹以上的水声信号。对更低频的水声,需要把帧长加到 8192 甚至 16384,否则 VMD 的低频模态实际中心频率会不稳定。
2.3 VMD 分解的最小可用代码
以下代码使用了常见的vmdpy封装,负责把一维噪声帧分解成 K 个模态,并返回每个模态的中心频率。
from vmdpy import VMD import numpy as np def decompose_frame(frame, fs, K=5, alpha=2000): """ frame: 一维时间序列,长度建议 4096 或 8192 fs : 采样率,单位 Hz 返回: modes : (K, N) 的模态分量 center_freqs : (K,) 每个模态的物理中心频率 """ u, u_hat, omega = VMD( frame, alpha, tau=0, K=K, DC=0, init=1, tol=1e-7 ) # omega 最后一行为归一化中心频率,范围 0 到 pi center_freqs = omega[-1, :] * fs / (2 * np.pi) return u, center_freqs这段代码里,VMD会内部完成维纳滤波和希尔伯特变换来估计瞬时频率。omega最后一行的值需要乘以fs / (2π)转成物理频率。tau=0表示不启用噪声补偿项,DC=0表示不要把直流单独拆成一个模态,init=1表示初始中心频率均匀初始化。
参数调试时可以这样看:alpha 越大,每个模态的带宽越窄,抗混叠能力增强,但过大的 alpha 会让模态丢失细节;K 设得太大,两个相邻模态的中心频率会挤在一起,甚至出现镜像对称的假模态。一般先用 Welch 功率谱数一下明显峰值个数,把峰值数作为 K 的起点。
2.4 从分解分量构造固定维度的特征向量
每一帧会得到 K 个模态,直接把这些长度不一的模态序列平均起来不现实。我对每个模态提取 5 个标量特征,这样一帧的特征维度可以固定为K * 5 + 1,最后的 1 是原始帧的短时标准差。
| 特征名 | 计算对象 | 说明 |
|---|---|---|
| RMS | 每个模态 | 表示该频带幅度的强弱 |
| 中心频率 | 每个模态 | VMD 本身会输出,直接保留 |
| 峰值因子 | 每个模态 | 最大值除以 RMS,反映脉冲成分 |
| 排列熵 | 每个模态 | 衡量时序复杂度,水声与机械噪声差异较大 |
| 能量占比 | 每个模态 | 该模态 RMS 平方占全帧比例 |
| 短时标准差 | 原始帧 | 补充未分解前的总能量信息 |
如果 K=5,每帧特征向量维度就是 26。把这套特征按时间顺序排列,就得到训练聚类模型的输入矩阵。需要强调,在帧滑动时不要引入未来信息,重叠部分必须只来自过去,否则在线部署时特征分布会偏。
3. 最优聚类模型:从噪声分解分量中确定聚类数与噪声基底
特征矩阵拿到之后,下一步是训练一个“最优聚类模型”。这里的关键不是随便跑一个 K-Means 就结束,而是要同时解决两个问题:用深度网络对特征做非线性压缩,以及在多个候选聚类数中选择真正符合噪声物理状态的 K。
3.1 深度嵌入:先用自编码器压缩特征
VMD 得到的 5 个特征之间并不是独立的。能量占比和 RMS 高度相关,排列熵和峰值因子也会互相影响。如果直接在 26 维空间上聚类,轮廓系数会被冗余维度抬高,很多看似分开的簇在物理上并不稳定。常见做法是先用一个自编码器把特征压到 3 到 6 维的隐空间,再做聚类。
import torch import torch.nn as nn class NoiseAE(nn.Module): def __init__(self, in_dim=26, latent=4): super().__init__() self.encoder = nn.Sequential( nn.Linear(in_dim, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, latent), ) self.decoder = nn.Sequential( nn.Linear(latent, 32), nn.ReLU(), nn.Linear(32, 64), nn.ReLU(), nn.Linear(64, in_dim), ) def encode(self, x): return self.encoder(x) def forward(self, x): z = self.encoder(x) return self.decoder(z), z训练时用 Adam 优化器,学习率 1e-3,batch size 256,MSE 损失,跑 200 epoch。隐空间维度取 4 一般够用。隐空间太大会把 VMD 对噪声的敏感细节也保留下来,聚类结果反而不稳定;太小又会丢掉低频段差异。我一般会在训练集上观察重建误差,同时把连续两天采集的噪声放在验证集里,如果验证重建误差超过训练误差 2 倍,说明噪声本身已经出现漂移,需要重新采集。
3.2 用 GMM 作为聚类模型并按 BIC 选择最优分量数
相比硬聚类 K-Means,GMM(高斯混合模型)更适合光纤传感噪声基底。原因很简单:同一噪声源在不同温度、不同偏置电流下,幅度和中心频率会有缓慢漂移,GMM 允许一个样本同时属于多个簇并输出概率。在深度聚类里常见的做法是先用 K-Means 初始化 GMM,再用 EM 算法迭代拟合。
import numpy as np from sklearn.mixture import GaussianMixture from sklearn.metrics import silhouette_score def select_gmm(Z, k_range=range(2, 13)): best_gm = None best_k = 0 best_bic = np.inf for k in k_range: gm = GaussianMixture( n_components=k, covariance_type="diag", init_params="kmeans", random_state=0, ).fit(Z) bic = gm.bic(Z) if bic < best_bic: best_bic = bic best_k = k best_gm = gm labels = best_gm.predict(Z) sil = silhouette_score(Z, labels) return best_gm, best_k, sil这段代码把 BIC 作为主要选择标准。BIC 会惩罚参数过多的模型,所以它通常比只看似然更不容易过拟合。但要注意,BIC 在真实的连续噪声分布上可能随着 K 增加一直下降,这时我们不能只选分数最大的 K。结合轮廓系数一起看比较稳:轮廓系数大于 0.3 且 BIC 曲线出现明显拐点,才说明聚类结构真的存在。
3.3 聚类数的物理校验
指标只是第一步。每个 GMM 聚类应该能被解释为一种典型噪声状态,否则聚类再紧凑也没有工程价值。我在实际项目里会用下面这套校验表:
| 检查项 | 通过标准 |
|---|---|
| 每个聚类的样本占比 | 不少于总样本数的 3% |
| 任意两个聚类中心的最小距离 | 标准化后大于 0.5 |
| 簇内样本的时域重构 | 频谱形状一致,不包含脉冲型突变 |
| 各聚类对应的时间段 | 与原始日志中的环境状态能对应上 |
如果某个聚类只包含极少数样本,而且这些样本在时间轴上随机散布,那它大概率是 VMD 分解中的瞬时颤振,不是稳定噪声模式。把它保留在模型里,水声信号反而会被它吸收,导致漏报。
3.4 注意区分 VMD 的 K 和 GMM 的聚类数
这是新手最容易绕晕的地方。VMD 的 K 是单帧频带切分数,解决“一帧噪声怎么拆开”;GMM 的聚类数是整个数据集的噪声模式数量,解决“现场有哪几类典型噪声状态”。如果 VMD 的 K 设得太大,会把一条宽带噪声拆成多个窄带分量,这些分量在特征空间里很分散,GMM 就会看到很多假簇;如果 VMD 的 K 设得太小,宽带水声信号会被压在某个模态里,导致后续距离打分失效。先固定 VMD 的 K,再调 GMM 的聚类数,不要两个参数同时搜索。
4. 水声信号识别:用噪声聚类结果构造异常打分与分类器
在最优聚类模型训练完成后,整个系统已经知道了“光纤传感系统自身固有噪声”在隐空间中的分布。剩下的问题是如何用这个分布去判决一个全新帧是噪声还是水声。这里有两种路径可以同时用:一种是直接计算新帧到已知噪声簇的距离;另一种是把聚类结果作为伪标签,去训练一个轻量级深度分类器。
4.1 从 GMM 分量到距离打分
对于 GMM 的每个高斯分量,可以计算新样本隐向量 z 到该分量中心的马氏距离,最后取所有分量中的最小值作为该帧的噪声偏离度。用马氏距离而不是欧氏距离,是因为不同噪声簇在隐空间各方向上的方差差别很大,欧氏距离会把高方差方向上的正常波动误判为异常。
import numpy as np def noise_score(z, gmm): min_score = np.inf for mu, cov in zip(gmm.means_, gmm.covariances_): inv_cov = 1.0 / (cov + 1e-6) delta = z - mu mahal = np.sqrt(np.sum(delta * delta * inv_cov, axis=1)) min_score = np.minimum(min_score, mahal) return min_score这段代码假设 GMM 使用diag协方差,所以协方差矩阵可以直接按元素取倒数。如果换成full协方差,就需要用np.linalg.solve解线性方程,但光纤传感特征维度通常不高,diag已经够用。
4.2 阈值选择与连续帧去抖
对训练噪声库里的每一帧都计算一个 score,就可以得到噪声分数分布。把分布的 95%、99%、99.9% 分位数分别作为候选阈值,具体效果参考下表:
| 阈值位置 | 误报率 | 响应速度 | 适用阶段 |
|---|---|---|---|
| 95% 分位数 | 高,几分钟内可能误报 | 单帧即可触发 | 只适合实验室验证 |
| 99% 分位数 | 中等,适合初步上线 | 需要结合连续帧判定 | 推荐首版采用 |
| 99.9% 分位数 | 低,漏掉小信号的风险高 | 稳定但迟钝 | 长期无人值守时配合日志复核 |
单帧 score 超标不一定就是水声。光纤传感系统经常出现随机尖峰噪声,持续时间只有几毫秒。我一般会加一条连续帧规则:连续 3 帧中至少有 2 帧超标,且超标状态持续 50 ms 以上,才输出一次水声事件。这个规则能把大部分随机尖峰滤掉,同时不会压低真正的脉冲型水声信号。
4.3 用少量真实水声样本校正阈值
尽管训练数据全部来自固有噪声,系统部署时通常还会拿到一小批真实水声片段。这批数据量可能只有几十到几百条,直接用来重新训练 VMD 和 GMM 会过拟合。正确做法是只拿它来搜索最佳阈值。
from sklearn.metrics import f1_score train_scores = ... # 训练噪声帧的 score test_scores = ... # 测试帧的 score test_labels = ... # 0 表示噪声,1 表示水声 best_t, best_f1 = 0, 0 for p in [90, 95, 99, 99.5, 99.9]: t = np.percentile(train_scores, p) pred = test_scores > t score = f1_score(test_labels, pred) if score > best_f1: best_t, best_f1 = t, score注意这里计算 F1 时会遇到类别严重不平衡。如果测试集中水声帧只占 1%,即使全部预测为噪声,F1 也会因为低召回率而不合格。更实用的做法是把测试集按事件切分,只要一个事件里有超过一半帧被检测到,就算该事件命中,而不是对每一帧求 F1。
4.4 再用 VMD 分量训练一个一维 CNN 作为快速分类器
距离打分在隐空间里工作,对现场部署稍微有点麻烦,因为每个新帧必须经过 VMD、特征提取、自编码器、GMM 打分四步。如果算力紧张,还可以用 VMD 分解分量堆叠作为输入,训练一个一维 CNN,直接把“该不该报警”学进去。训练时噪声帧标为 0,距离打分超标的候选帧标为 1。
import torch.nn as nn class VMDNet(nn.Module): def __init__(self, K=5, N=4096): super().__init__() self.conv = nn.Sequential( nn.Conv1d(K, 16, 5, padding=2), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(4), nn.Conv1d(16, 32, 5, padding=2), nn.BatchNorm1d(32), nn.ReLU(), nn.AdaptiveAvgPool1d(16), nn.Flatten(), ) self.fc = nn.Sequential( nn.Linear(32 * 16, 64), nn.ReLU(), nn.Linear(64, 2), )输入张量的形状是(batch, K, N),也就是一帧 VMD 分解后的 K 个模态直接作为通道。CNN 的作用是学习模态之间的频率拓扑关系。需要提醒的是,伪标签中“标为 1”的帧本身可能包含部分强噪声,所以 CNN 不能单独承担最终判决,通常是final_score = 0.6 * cnn_prob + 0.4 * normalized_distance做软融合。
5. 在线识别时的三个关键细节:阈值、新噪声聚类与模型迭代
噪声聚类模型上线之后,最大的坑不是算法本身,而是现场噪声分布的缓慢漂移。温度变化会让激光器中心波长偏移,平台振动状态会改变低频噪声能量,光纤链路衰减也会让解调相位噪声变大。面对这些变化,最重要的是保持模型可以迭代,而不是期望一个静态模型永远有效。
5.1 漂移监控而不是频繁调阈值
我会每天对当天所有噪声帧重新计算一次 score 序列,并记录 99% 分位数。如果连续三天这个值比上线第一周上升超过 20%,第一反应不应该是调高阈值,而是先检查系统和环境。阈值一旦被调高,真正的水声小信号也会跟着消失。更合理的做法是每天生成一份 score 分布曲线,人工观察分布形状从中峰变成了多峰,往往意味着出现了新的噪声源。
5.2 新增噪声聚类的合并流程
当监控发现大量未命中噪声簇但人工复核确认是环境噪声的样本时,可以给它们执行一次独立聚类。步骤是:把这些帧的隐向量 z 缓存下来,用 DBSCAN 观察它们是否形成连续簇;如果某个簇样本数超过 200 且中心与已有 GMM 分量距离较远,就把它作为一个新的 GMM 分量加入模型。注意不要每个月都加,否则水声信号会被逐渐吸收成“已知噪声”。
5.3 每次告警都记录隐向量
我一般会在日志系统里把每次告警的帧号、score、隐向量 z 一起保存。一周之后,把 z 全部取出来再跑一次轮廓系数。如果发现新增簇的轮廓系数很高,就直接把该簇加入噪声模型;如果轮廓系数很低,说明是区间噪声,不需要处理。这样做的收益是可以随时回放上一次聚类是在什么数据上得到的,模型漂移时也有一条可追溯的路径。
在光纤传感系统里,水声信号的声压级通常很微弱,但它的时频纹波足以让 VMD 分解后的分量分布发生偏移。先用固有噪声把聚类基底做稳,再谈识别精度,远比重金标注真实水声样本、直接训练大模型更可持续。
本文还有配套的精品资源,点击获取