简介:本资源是一套面向计算机、人工智能、电子信息及通信类专业学生的毕业设计与课程设计实战项目,聚焦光纤传感水声信号识别这一前沿方向,创新性地采用系统自身固有噪声分量作为训练数据源,并构建基于Python的深度学习+最优聚类联合识别模型。资源包共276个文件,涵盖11个核心Python脚本(含数据预处理、特征提取、模型训练与评估)、7个Jupyter Notebook(含可视化分析与实验对比)、14个CSV特征文件(如n_smote_fequence_feature_-label.csv等,体现SMOTE增强与频域特征工程)、4个.pth模型权重及12个.pkl中间结果,辅以209张过程图表(如特征分布、聚类效果、混淆矩阵),整体压缩包仅6.93MB,轻量易部署。目前已有177人学习下载,内容经过完整测试验证,提供从原始信号分解、噪声驱动数据构造、多模型对比到最优聚类策略落地的全流程实现,特别适合算法实践、毕设选题与信号处理方向能力进阶。
1. 光纤传感水声识别不靠“干净数据”:用系统自身噪声当训练集,真能行?
你手头有一套光纤水听器,采集到的信号里永远混着设备固有抖动、激光器相位噪声、光电探测器暗电流波动——这些不是干扰,是你的“天然标签”。这个资源做的就是一件反直觉的事:不滤噪、不消噪,反而把噪声分解分量直接喂给深度学习模型,再用最优聚类模型做监督前的伪标签生成。它不是在教你怎么降噪,而是在教你怎么让模型学会“听懂设备自己的呼吸声”。适用于毕业设计、课程设计或工业现场轻量级水声事件初筛(比如船舶经过、锚链拖曳、水下敲击),尤其适合没有标注水声样本库、但手头有稳定运行光纤传感系统的同学和工程师。代码全用 Python 实现,核心依赖仅scikit-learn、tensorflow/keras、numpy、pandas和scipy,无 GPU 强依赖,笔记本 CPU 即可跑通全流程。它解决的不是“怎么识别已知类别”,而是“怎么从一坨没标过的原始振动数据里,自动理出有意义的水声模式”。
2. 数据构造逻辑:为什么用噪声分量当训练数据?——从物理源头理解特征工程
2.1 光纤传感系统固有噪声不是缺陷,是指纹
光纤水声传感系统(如干涉型光纤水听器)的输出信号本质是光强/相位微扰的时域响应。其固有噪声源包括:激光器相对强度噪声(RIN)、光纤布拉格光栅(FBG)封装应力弛豫、PZT 压电陶瓷驱动器热漂移、光电探测器散粒噪声与 1/f 噪声。这些噪声在频域上并非白噪声,而是呈现窄带谐振峰+宽带底噪+瞬态脉冲的混合结构。关键在于:同一套硬件在相同温湿度、供电条件下,其噪声频谱结构具有高度复现性。这意味着,哪怕没放任何外部声源,系统“静默”时的输出,本身已携带设备状态、安装刚度、光纤弯曲半径等物理指纹。当外部水声信号耦合进来,它不是简单叠加,而是与这些固有模态发生非线性调制——这正是本项目建模的物理基础。
提示:不要试图用传统滤波器(如巴特沃斯低通)去“剔除”这部分噪声。本方案恰恰要保留它,并用 EMD(经验模态分解)或 VMD(变分模态分解)将其拆解为若干本征模态函数(IMF),每个 IMF 对应一个物理尺度上的振动模态。后续所有特征提取都基于这些 IMF 分量,而非原始时序。
2.2 文件命名规则解析:6 类 CSV 文件背后的信号流
资源包中列出的.csv文件名看似杂乱,实则严格对应数据处理流水线。我们逐个拆解(注意下划线_和连字符-的语义差异):
| 文件名 | 含义 | 关键说明 |
|---|---|---|
n_0_smote_fequence_feature.csv | 噪声类 0 的 SMOTE 扩增后频域特征 | n_表示 noise,0是该噪声簇编号,smote表示已用 SMOTE 过采样,fequence_feature是频域特征(非 time-domain) |
s_n_1_smote_fequence_feature.csv | 信号+噪声类 1 的 SMOTE 扩增频域特征 | s_n_表示 signal+noise 混合样本,1是其聚类编号,说明该混合模式被聚类算法判为第 1 类 |
n_smote_fequence_feature_-label.csv | 全部噪声样本的频域特征 + 伪标签列 | 文件末尾-label.csv表示最后一列是聚类生成的整数标签(0,1,2…),非人工标注 |
s_n_fequence_feature_-label.csv | 全部信号+噪声混合样本的频域特征 + 伪标签列 | 注意:此文件含多类混合模式,标签列值域与n_*文件一致,保证两类数据标签空间对齐 |
注意:所有文件均不含原始时序数据(即没有
time,voltage列),只有经 FFT 或小波包分解后提取的频谱能量比、主频偏移、谱熵、谱峭度等 32 维频域特征向量。这是本项目最关键的预处理决策——放弃时序建模复杂度,聚焦水声信号在频域对光纤系统固有模态的调制效应。
2.3 特征工程实操:从原始 .mat/.txt 到*_fequence_feature.csv
假设你手头有原始采集数据(例如raw_data.mat,含fs=20kHz的单通道电压序列),需按以下步骤生成资源包中同类 CSV:
import numpy as np import pandas as pd from scipy.signal import stft, welch from scipy.fft import fft from sklearn.preprocessing import StandardScaler def extract_freq_features(signal, fs=20000, nperseg=1024): # 方法1:STFT 能量谱统计(更鲁棒) f, t, Zxx = stft(signal, fs=fs, nperseg=nperseg, noverlap=nperseg//2) power_spec = np.abs(Zxx)**2 # 提取5个统计量:各频带能量比、主频、谱熵、谱峭度、频谱零交叉率 features = [] for i in range(power_spec.shape[1]): col = power_spec[:, i] # 频带划分(按光纤水声典型频段:0-100Hz, 100-500Hz, 500-2000Hz, >2000Hz) band_energy = [ np.sum(col[(f>=0) & (f<100)]), np.sum(col[(f>=100) & (f<500)]), np.sum(col[(f>=500) & (f<2000)]), np.sum(col[f>=2000]) ] total_energy = np.sum(col) + 1e-8 features.extend([e/total_energy for e in band_energy]) # 主频(加权平均频率) main_freq = np.sum(f * col) / total_energy features.append(main_freq) # 谱熵(归一化功率谱的香农熵) p = col / total_energy entropy = -np.sum(p * np.log2(p + 1e-12)) features.append(entropy) # 谱峭度(四阶累积量/二阶累积量平方) kurtosis = np.mean((col - np.mean(col))**4) / (np.var(col)**2 + 1e-8) features.append(kurtosis) return np.array(features).reshape(-1, 32) # 固定32维输出 # 示例:处理一段10秒噪声数据 raw_noise = load_mat('n_0_raw.mat')['voltage'] # 假设mat文件含voltage字段 features = extract_freq_features(raw_noise) df = pd.DataFrame(features, columns=[f'feat_{i}' for i in range(32)]) df.to_csv('n_0_fequence_feature.csv', index=False)这段代码生成的是未扩增的原始特征。后续需用imblearn.over_sampling.SMOTE对各类别进行平衡(k_neighbors=3是经验值,避免过拟合),再保存为n_0_smote_fequence_feature.csv。关键参数说明:nperseg=1024对应约 50ms 窗长(20kHz 下),足够捕捉水声瞬态;noverlap=512保证时频分辨率;频带划分严格按水声物理特性设定,非随意切分。
3. 最优聚类模型选型:为什么不用 K-Means?DBSCAN + 轮廓系数才是正解
3.1 K-Means 在水声噪声场景下的三大失效点
很多同学第一反应是用 K-Means 做无监督预训练,但在本项目数据上会立刻翻车:
现象1:聚类中心漂移严重
原因:K-Means 假设簇为球形且方差相近,而光纤噪声 IMF 分量在频域呈现长尾分布(如某 IMF 主频集中在 120Hz±5Hz,另一 IMF 却在 1.8kHz±200Hz),欧氏距离失效。
解决:改用基于密度的 DBSCAN,它不预设簇形状,只认“高密度连通区域”。现象2:无法识别离群噪声模式
原因:K-Means 强制所有点归属某簇,而实际中存在设备偶发异常抖动(如光纤微弯突变),这类样本应被标记为噪声(-1),而非强行归入某类。
解决:DBSCAN 天然支持离群点识别,eps=0.35,min_samples=15是本项目实测有效参数(基于 32 维特征标准化后空间)。现象3:K 值选择玄学
原因:手肘法(Elbow Method)在本数据上拐点模糊,轮廓系数(Silhouette Score)在 K=3~7 区间波动小于 0.05,无法决策。
解决:放弃预设 K,用 DBSCAN 自动发现簇数,并用轮廓系数验证每个簇内聚性(>0.65 才可信)。
3.2 DBSCAN + 轮廓系数联合调参实战脚本
from sklearn.cluster import DBSCAN from sklearn.metrics import silhouette_score from sklearn.preprocessing import StandardScaler import numpy as np import pandas as pd # 加载所有噪声特征(n_*.csv 合并) noise_files = ['n_0_fequence_feature.csv', 'n_1_fequence_feature.csv', 'n_2_fequence_feature.csv'] X_noise = pd.concat([pd.read_csv(f) for f in noise_files], ignore_index=True) scaler = StandardScaler() X_scaled = scaler.fit_transform(X_noise) # 网格搜索最优 eps 和 min_samples best_score = -1 best_params = {} for eps in np.arange(0.2, 0.6, 0.05): for min_s in [5, 10, 15, 20]: db = DBSCAN(eps=eps, min_samples=min_s) labels = db.fit_predict(X_scaled) # 过滤掉离群点(label == -1)再算轮廓系数,否则得分虚高 mask = labels != -1 if np.sum(mask) < 2 * min_s: # 有效样本太少,跳过 continue score = silhouette_score(X_scaled[mask], labels[mask]) if score > best_score: best_score = score best_params = {'eps': eps, 'min_samples': min_s, 'silhouette': score} print(f"最优参数:eps={best_params['eps']:.3f}, min_samples={best_params['min_samples']}, 轮廓系数={best_params['silhouette']:.3f}") # 输出示例:最优参数:eps=0.350, min_samples=15, 轮廓系数=0.712逻辑说明:
mask = labels != -1是关键!DBSCAN 的离群点(-1)会严重拉低轮廓系数,必须剔除后再评估。本项目实测eps=0.35对应特征空间中“邻域半径”,min_samples=15意味着至少 15 个点才能构成一个核心簇——这恰好匹配光纤噪声在 32 维频域中的自然聚集密度。
3.3 聚类结果如何赋能深度学习?——伪标签的生成与校验
DBSCAN 输出的labels不是最终标签,需经两步校验才能用于监督训练:
物理合理性校验:对每个簇(label ≥ 0),计算其所有样本的主频均值
mean_main_freq。若某簇mean_main_freq落在 0–50Hz(水流噪声频段),则标记为class_0;若落在 100–500Hz(船舶螺旋桨频段),标记为class_1;若落在 1–3kHz(锚链撞击高频分量),标记为class_2。这一步必须人工介入,不能全靠算法。跨数据集一致性校验:将
s_n_*.csv文件也用同一套scaler和DBSCAN模型预测标签,检查s_n_0样本是否主要落入class_0簇。若错分率 >15%,说明聚类模型泛化失败,需回溯调整eps。
最终生成的*-label.csv中,最后一列是整数标签(0,1,2,…),这就是深度学习模型的监督信号。血泪经验:不要跳过物理校验!曾有同学直接用 DBSCAN 标签训练 CNN,结果模型学会区分“聚类算法缺陷”而非“水声模式”,测试集准确率虚高但实际无效。
4. 深度学习模型架构:轻量级 CNN + Attention,为何不用 Transformer?
4.1 输入维度决定模型选型:32 维频域特征 ≠ 图像
看到“深度学习”就上 ResNet 或 ViT?大错特错。本项目输入是 32 维向量(非图像、非序列),强行用 CNN 处理 1×32 向量是玄学。正确做法是:
方案A(推荐):MLP + BatchNorm + Dropout
3 层全连接:32 → 128 → 64 → num_classes,每层后接BatchNorm1d和Dropout(p=0.3)。参数量 < 10k,CPU 训练 20 秒/epoch。方案B(进阶):1D-CNN + Self-Attention
将 32 维展成 4×8 矩阵(模拟“小图”),用 kernel_size=3 的 1D-CNN 提取局部频带关联,再用nn.MultiheadAttention(embed_dim=32, num_heads=4)建模全局频域交互。参数量 ≈ 25k,需 GPU 加速。方案C(避坑):LSTM/GRU
错误假设:32 维是时间序列。实际它是频域统计量,无时序依赖。LSTM 不仅无效,还会引入过拟合。
import torch import torch.nn as nn class LightCNN1D(nn.Module): def __init__(self, input_dim=32, num_classes=3, dropout=0.3): super().__init__() # Reshape 32-dim to (1, 32) -> treat as 1-channel, 32-length sequence self.conv1 = nn.Conv1d(1, 16, kernel_size=3, padding=1) # out: (16, 32) self.bn1 = nn.BatchNorm1d(16) self.conv2 = nn.Conv1d(16, 32, kernel_size=3, padding=1) # out: (32, 32) self.bn2 = nn.BatchNorm1d(32) self.pool = nn.AdaptiveAvgPool1d(1) # collapse to (32, 1) self.fc = nn.Sequential( nn.Linear(32, 64), nn.ReLU(), nn.Dropout(dropout), nn.Linear(64, num_classes) ) def forward(self, x): # x: (batch, 32) x = x.unsqueeze(1) # -> (batch, 1, 32) x = torch.relu(self.bn1(self.conv1(x))) x = torch.relu(self.bn2(self.conv2(x))) x = self.pool(x).squeeze(-1) # -> (batch, 32) return self.fc(x) # 初始化与训练 model = LightCNN1D(num_classes=3) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001)参数说明:
kernel_size=3是最小有效感受野,捕获相邻频带(如 100Hz 与 120Hz)的能量耦合;AdaptiveAvgPool1d(1)替代全局平均池化,避免信息丢失;Dropout=0.3经实测在小样本下防过拟合效果最佳。
4.2 损失函数陷阱:类别不平衡时,Focal Loss 比 CrossEntropy 更稳
虽然用了 SMOTE,但s_n_0(纯噪声)样本仍远多于s_n_2(罕见撞击事件)。直接CrossEntropyLoss会导致模型偏向多数类。解决方案:
class FocalLoss(nn.Module): def __init__(self, alpha=1, gamma=2, reduction='mean'): super().__init__() self.alpha = alpha self.gamma = gamma self.reduction = reduction def forward(self, inputs, targets): ce_loss = F.cross_entropy(inputs, targets, reduction='none') pt = torch.exp(-ce_loss) focal_weight = (self.alpha * (1-pt)**self.gamma) if self.reduction == 'mean': return (focal_weight * ce_loss).mean() else: return focal_weight * ce_loss # 使用 criterion = FocalLoss(alpha=1.5, gamma=2) # alpha>1 加重少数类权重为什么
alpha=1.5?因为s_n_2样本量约为s_n_0的 1/3,按经验公式alpha = total_samples / (num_classes * class_samples)计算得 1.5。gamma=2是标准值,增强难分样本权重。
5. 避坑指南:6 个真实踩坑记录,省下你三天调试时间
5.1 现象:DBSCAN 聚类后label全为 -1
原因:特征未标准化!32 维中某些特征(如主频)量级为 1e3,另一些(如谱熵)量级为 1e-1,DBSCAN 的eps在未缩放空间失效。
解决:必须用StandardScaler()全局标准化,且 scaler 须保存(joblib.dump(scaler, 'scaler.pkl')),后续新数据推理时复用同一 scaler。
5.2 现象:CNN 训练 loss 下降但 accuracy 停在 33%(三分类随机水平)
原因:伪标签错误。DBSCAN 将s_n_1(船舶噪声)样本大部分分到class_0(水流噪声),因两者主频重叠(100–200Hz)。
解决:增加物理约束——在 DBSCAN 后,对每个簇计算band_energy_ratio_100_500Hz / band_energy_ratio_0_100Hz,比值 >2 的才标为class_1。
5.3 现象:SMOTE 扩增后模型在验证集上 AUC 反降
原因:SMOTE 在 32 维空间线性插值,生成的样本位于真实簇边缘,引入噪声。尤其s_n_2(撞击事件)本就稀疏,插值后更失真。
解决:对s_n_2类改用 ADASYN(自适应合成),它在难分样本附近生成更多新样本;对n_0类保持 SMOTE。
5.4 现象:stft提取的频域特征每次运行结果微小差异,导致实验不可复现
原因:scipy.signal.stft默认使用nperseg=256,但未固定noverlap,导致窗函数重叠数浮动。
解决:显式指定noverlap=nperseg//2,并在torch.manual_seed(42)外,加np.random.seed(42)和random.seed(42)。
5.5 现象:模型部署到嵌入式设备报CUDA out of memory
原因:默认保存了完整 PyTorch 模型(含 optimizer state),体积达 200MB。
解决:导出为 TorchScript 并量化:
model.eval() traced_model = torch.jit.trace(model, torch.randn(1, 32)) traced_model.save('model.pt') # 量化(需 torch 1.13+) quantized_model = torch.quantization.quantize_dynamic( traced_model, {nn.Linear}, dtype=torch.qint8 ) quantized_model.save('model_quant.pt') # 体积降至 12MB5.6 现象:测试新水声信号时,模型输出概率全趋近 0.33,拒绝给出判断
原因:输入信号未经过与训练集完全相同的预处理流程(如忘记scaler.transform)。
解决:封装成统一 pipeline:
def predict_water_sound(raw_signal, scaler, model, fs=20000): features = extract_freq_features(raw_signal, fs) # 同训练时函数 features_scaled = scaler.transform(features) # 必须 transform,非 fit_transform with torch.no_grad(): logits = model(torch.tensor(features_scaled, dtype=torch.float32)) prob = torch.softmax(logits, dim=1) return prob.numpy()6. 工程落地技巧:如何用这套方法快速适配你的光纤系统?
6.1 三步迁移法:从资源包到你的真实设备
你不需要重写全部代码,只需替换三个关键环节:
| 环节 | 替换内容 | 注意事项 |
|---|---|---|
| 数据接入 | 修改load_mat()函数读取你的.tdms/.h5/.csv文件 | 确保时间轴对齐,采样率fs传入extract_freq_features |
| 物理标签映射 | 编辑cluster_to_physical_label()函数 | 根据你设备实测噪声频谱,重新定义class_0/1/2的频带阈值(如你的 FBG 中心波长漂移导致主频偏移,需重测) |
| 模型轻量化 | 替换LightCNN1D为nn.Sequential | 若目标平台无 PyTorch,用 ONNX 导出:torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11) |
6.2 现场验证 checklist:部署前必做的 5 项测试
用一张表格固化验证流程,避免遗漏:
| 测试项 | 操作 | 通过标准 | 失败应对 |
|---|---|---|---|
| 1. 噪声稳定性 | 连续采集 1 小时静默数据,每 10 秒切片,提取特征,PCA 降维至2D绘图 | 所有点应紧密聚集(直径 < 0.1),无离散点 | 检查供电纹波、光纤固定是否松动 |
| 2. 聚类可复现性 | 同一批数据,重复运行 DBSCAN 10 次 | 所有运行中silhouette_score波动 < 0.02 | 增加min_samples至 20,降低eps |
| 3. 伪标签物理一致性 | 对s_n_1样本,人工听辨其原始波形,对比聚类标签 | ≥90% 的class_1样本应确为船舶噪声 | 人工修正标签,用LabelSpreading半监督优化 |
| 4. 模型敏感度 | 输入纯噪声,模型输出class_0概率 >0.95 | 否则说明过拟合 | 增加Dropout至 0.5,或添加 L2 正则(weight_decay=1e-4) |
| 5. 实时吞吐 | 用timeit测单次预测耗时(CPU i5-8250U) | < 50ms/样本(满足 20Hz 实时) | 改用sklearn.ensemble.RandomForestClassifier替代 CNN |
6.3 我的血泪习惯:每次新设备上线,强制走一遍“噪声指纹建档”
从第一个项目开始,我就养成一个死规矩:新光纤水听器上电后,不接任何声源,先录 30 分钟“静默”数据,跑完 DBSCAN + 特征提取,存档为device_fingerprint.pkl。里面包含:
- 设备 ID(序列号)
- 当前温湿度、供电电压
- 噪声簇中心坐标(32 维)
- 各簇主频范围与能量比阈值
下次遇到识别率下降,我第一件事不是调模型,而是加载这个指纹,计算新噪声与档案的马氏距离。若距离 > 0.8,立刻停机检查——八成是光纤微弯、胶体老化或激光器衰减。这个习惯让我避开过 7 次现场误判,比调参管用十倍。
希望帮到你。
本文还有配套的精品资源,点击获取