ARTICLE DETAIL

资讯详情

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

数字调制信号识别实战:从IQ数据到CNN模型与PyQt系统

数字调制信号识别实战:从IQ数据到CNN模型与PyQt系统 简介这份资源面向通信、电子信息及计算机相关专业的本科生提供一套完整的数字调制信号调制方式识别检测系统源码可用于毕业设计、期末大作业或课程设计。项目围绕MPSK、8QAM等常见调制信号的自动识别展开结合高阶累积量等特征提取方法完成分类检测难度适中适合作为信号处理与机器学习入门实践。压缩包共125个文件约10.87MB其中45个py文件承载核心算法与界面逻辑45个m文件对应MATLAB仿真脚本另有fig结果图、csv数据集、xml配置及md说明文档便于对照实验数据与识别效果。目前已有98人学习下载。源码经本地编译可运行评审分达98分并获助教老师审定读者可据此掌握从信号生成、特征计算到调制识别与结果可视化的完整流程快速搭建可演示的检测系统。1. 数字调制信号识别到底在做什么从一段 IQ 数据到调制方式标签拿到一段来路不明的无线电采样数据你手里只有一串复数序列既没有协议头也没有帧结构说明第一件想搞清楚的事往往是这串信号到底用的是哪种调制方式。数字调制信号调制方式识别做的就是把这个盲猜变成可复现的工程流程——输入是 IQ 采样输出是 BPSK、QPSK、8PSK、16QAM、64QAM、2FSK、4FSK 这类标签之一。它常见于频谱监测、信号侦测、教学实验台也是本科毕业设计里少有的信号处理 机器学习 软件工程三合一题目。这个方向适合两类人一类是通信、电子、物联网工程方向想找一个能跑出准确率曲线、又能写进论文的题目另一类是想练 Python 科学计算栈但不想做烂大街的爬虫或管理系统。它不需要昂贵设备一台普通笔记本加开源数据集就能跑通全流程难点在于特征怎么提、模型怎么选、噪声下怎么不翻车。下面按数据怎么来 → 特征怎么提 → 模型怎么训 → 系统怎么搭 → 坑在哪的顺序讲透。2. 数据集与信号仿真没有实测数据时怎么造出可信的 IQ 样本2.1 公开数据集和自仿真两条路怎么选做调制识别第一步永远是数据。常见做法有两条一是用公开数据集二是自己用仿真生成。公开数据集里被引用最多的是 DeepSig 的 RadioML 系列其中 RadioML 2016.10a 包含 11 种调制方式、20 个信噪比档位-20dB 到 18dB步进 2dB每类每 SNR 约 1000 条样本每条是 2×128 的 IQ 矩阵。这个规模对本科毕设刚好既不会大到跑不动又能画出漂亮的 SNR-准确率曲线。自仿真则用 GNU Radio 或纯 Python 生成。纯 Python 的好处是可控、可复现、不依赖额外软件。核心思路是随机比特 → 星座映射 → 脉冲成型 → 上变频 → 加高斯白噪声。下面是一段最小可用的 QPSK 生成代码其他调制方式换映射表即可。import numpy as np def gen_qpsk(n_sym128, snr_db10, sps4): # 1. 随机比特每2比特映射一个QPSK符号 bits np.random.randint(0, 2, 2 * n_sym) syms (2 * bits[0::2] - 1) 1j * (2 * bits[1::2] - 1) syms syms / np.sqrt(2) # 归一化功率 # 2. 上采样 根升余弦脉冲成型 up np.zeros(n_sym * sps, dtypecomplex) up[::sps] syms t np.arange(-6 * sps, 6 * sps 1) beta 0.35 h np.sinc(t / sps) * np.cos(np.pi * beta * t / sps) / (1 - (2 * beta * t / sps) ** 2 1e-9) h / np.sqrt(np.sum(h ** 2)) wave np.convolve(up, h, modesame) # 3. 按目标SNR加高斯白噪声 p_sig np.mean(np.abs(wave) ** 2) p_noise p_sig / (10 ** (snr_db / 10)) noise np.sqrt(p_noise / 2) * (np.random.randn(len(wave)) 1j * np.random.randn(len(wave))) return wave noise这段代码里三个参数最关键n_sym决定样本长度snr_db决定难度档位sps是每符号采样点数。sps4是常见折中太小脉冲成型失真太大计算量翻倍。beta0.35是滚降系数通信里常用 0.2~0.5太小旁瓣抑制好但码间干扰敏感。噪声功率按信号功率和目标 SNR 反推这是标准做法别用固定噪声方差否则不同调制方式的实际 SNR 会不一致。2.2 样本格式统一让训练和推理走同一条管道不管数据来自公开集还是自仿真进模型前必须统一成同一形状。我一般固定为(2, 128)第一行 I 路第二行 Q 路长度 128。RadioML 2016.10a 本身就是这个格式自仿真数据要截断或补零对齐。截断时从信号能量最强的位置取窗避免取到全是噪声的头尾。def to_fixed_shape(iq, length128): # iq: 复数序列 if len(iq) length: # 用滑动能量找最强窗口起点 energy np.convolve(np.abs(iq), np.ones(length), valid) start np.argmax(energy) iq iq[start:start length] else: iq np.pad(iq, (0, length - len(iq))) return np.stack([iq.real, iq.imag], axis0).astype(np.float32)能量最强窗口这个细节很多人忽略直接取前 128 点结果低 SNR 下窗口里大半是噪声模型学不到东西准确率曲线整体下移。统一格式后训练集、验证集、推理入口都调这一个函数避免训练时一个形状、部署时另一个形状这种低级翻车。3. 特征工程与模型选型从高阶累积量到 CNN 的两条技术路线3.1 高阶累积量特征小样本下的经典打法在深度学习普及之前调制识别主要靠高阶累积量HOC。原理是不同调制方式的星座图对称性不同导致二阶、四阶、六阶累积量取值有区分度。比如 BPSK 的 C40 和 QPSK 的 C40 符号相反QAM 的 C42 又和 PSK 不同。这套方法的优点是样本需求小、可解释性强缺点是低 SNR 下累积量估计方差大-5dB 以下基本失效。def cumulant_features(iq): x iq - np.mean(iq) # 去均值 m20 np.mean(x ** 2) m21 np.mean(np.abs(x) ** 2) m40 np.mean(x ** 4) m41 np.mean(x ** 3 * np.conj(x)) m42 np.mean(np.abs(x) ** 4) m60 np.mean(x ** 6) m63 np.mean(np.abs(x) ** 6) # 累积量组合实际使用时常取模和归一化 c40 m40 - 3 * m20 ** 2 c41 m41 - 3 * m20 * m21 c42 m42 - np.abs(m20) ** 2 - 2 * m21 ** 2 c63 m63 - 9 * m42 * m21 12 * m21 ** 3 return np.array([abs(c40), abs(c41), abs(c42), abs(c63)])这段里m20到m63是矩c40到c63是累积量。实际做毕设时通常把 4 个累积量加上信噪比估计、频谱对称性等凑成 8~12 维特征再喂给 SVM 或随机森林。参数上要注意累积量对载波频偏和相位偏移敏感仿真时要保证没有残留频偏否则 C40 会虚部乱跳。如果做实测数据先做频偏估计和补偿再提特征。3.2 CNN 直接吃 IQ端到端路线怎么搭深度学习路线把 IQ 当图像处理用一维或二维卷积自动提特征。RadioML 上表现较好的结构是两层卷积 全连接输入(2, 128)第一层卷积核沿时间轴滑动。下面是一个能跑通的 PyTorch 最小模型。import torch import torch.nn as nn class ModCNN(nn.Module): def __init__(self, n_class11): super().__init__() self.net nn.Sequential( nn.Conv1d(2, 64, kernel_size3, padding1), nn.ReLU(), nn.Conv1d(64, 64, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size3, padding1), nn.ReLU(), nn.MaxPool1d(2), nn.AdaptiveAvgPool1d(8), nn.Flatten(), nn.Linear(128 * 8, 128), nn.ReLU(), nn.Dropout(0.5), nn.Linear(128, n_class) ) def forward(self, x): return self.net(x)Conv1d(2, 64, ...)的 2 是 I/Q 两路输入通道不是图像三通道。AdaptiveAvgPool1d(8)把时间维压到 8避免全连接层参数爆炸。Dropout(0.5)在低 SNR 数据上很关键不加容易过拟合到训练 SNR 档位。训练时按 SNR 分层采样别让高 SNR 样本主导梯度否则模型在 -10dB 下表现会很难看。两条路线怎么选如果毕设重点是信号处理理论走累积量 SVM论文里公式推导有内容如果重点是软件系统和模型走 CNN准确率曲线更好看。也有折中方案CNN 提取特征后再接累积量做融合但工作量翻倍本科阶段量力而行。4. 训练、评估与系统封装把脚本变成能演示的检测系统4.1 训练循环里必须盯住的三个量训练调制识别模型光看 loss 不够要同时盯训练准确率、验证准确率和分 SNR 准确率。分 SNR 准确率是这类任务的核心指标因为整体准确率会被高 SNR 样本拉高掩盖低 SNR 下的短板。def evaluate_by_snr(model, loader, device): model.eval() snr_correct {} snr_total {} with torch.no_grad(): for x, y, snr in loader: x, y x.to(device), y.to(device) pred model(x).argmax(dim1) for s, p, t in zip(snr.tolist(), pred.tolist(), y.tolist()): snr_total[s] snr_total.get(s, 0) 1 snr_correct[s] snr_correct.get(s, 0) int(p t) return {s: snr_correct[s] / snr_total[s] for s in sorted(snr_total)}snr这个字段要在 Dataset 里一并返回别只返回 x 和 y。评估结果画成折线图横轴 SNR、纵轴准确率这是毕设论文里最有说服力的一张图。正常情况下-10dB 以下准确率接近随机猜1/11≈9%0dB 左右开始爬升10dB 以上应达到 80% 以上。如果 18dB 还上不去 70%先查数据管道有没有把 I/Q 搞反或归一化做错。4.2 系统封装从 Jupyter 到可演示的桌面程序毕设答辩时老师不会看你 notebook要的是一个能点按钮、能出结果的系统。常见做法是 PyQt5 或 Tkinter 做界面后端调训练好的模型。最小结构分三层数据层读文件或仿真、推理层加载模型、预处理、预测、界面层选文件、显示波形和结果。import torch from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel class DetectWindow(QMainWindow): def __init__(self, model_path): super().__init__() self.model torch.load(model_path, map_locationcpu) self.model.eval() self.btn QPushButton(选择IQ文件并识别, self) self.btn.clicked.connect(self.run_detect) self.label QLabel(等待输入, self) self.label.move(0, 50) def run_detect(self): iq np.fromfile(sample.iq, dtypenp.complex64) x to_fixed_shape(iq) x torch.tensor(x).unsqueeze(0) with torch.no_grad(): pred self.model(x).argmax(dim1).item() self.label.setText(f识别结果: {CLASS_NAMES[pred]})torch.load时加map_locationcpu避免在没有 GPU 的答辩机器上加载失败。界面层不要写复杂逻辑预处理和推理都调前面已经验证过的函数保证训练时怎么处理演示时就怎么处理。文件读取用np.fromfile读二进制 IQ这是软件无线电设备的常见输出格式比 CSV 快得多。5. 避坑与排查调制识别项目里最容易翻车的五件事现象一训练准确率 99%验证准确率 30%。原因通常是数据泄漏——同一段信号切出的多个样本被分到了训练集和验证集。解决按原始信号文件划分而不是按切窗后的样本划分确保同一来源的样本只出现在一个集合里。现象二所有 SNR 下准确率都接近随机猜。先查标签和数据的对应关系有没有错位再查 I/Q 是否被当成两路独立实数送进模型而丢了复数结构。常见错误是np.stack([iq.real, iq.imag])写成了np.stack([iq.real, iq.real])这种 bug 不报错但模型学不到东西。现象三低 SNR 下某些调制方式完全分不开。比如 16QAM 和 64QAM 在 -5dB 以下几乎不可分这是信息论决定的不是模型问题。解决论文里如实报告并说明这是该方法的理论边界别硬调参去凑。现象四换一台机器推理结果全变。多半是归一化方式不一致训练时用了全局均值方差推理时用了单样本归一化。解决把归一化参数存成文件训练和推理共用同一套。现象五PyQt 界面卡死。推理放在主线程模型稍大就阻塞 UI。解决用 QThread 把推理放到子线程或者至少加进度提示。答辩演示时卡死一次印象分直接掉一半。6. 进阶技巧用 SNR 感知训练把低信噪比准确率再抬一截基础模型跑通后如果想让曲线更好看可以试 SNR 感知训练。核心思路是让模型知道当前样本的信噪比把 SNR 作为一个额外输入通道或条件向量。最简单做法是把 SNR 归一化后拼到全连接层输入。class SNRModCNN(nn.Module): def __init__(self, n_class11): super().__init__() self.cnn nn.Sequential( nn.Conv1d(2, 64, 3, padding1), nn.ReLU(), nn.Conv1d(64, 64, 3, padding1), nn.ReLU(), nn.AdaptiveAvgPool1d(8), nn.Flatten() ) self.fc nn.Sequential( nn.Linear(64 * 8 1, 128), nn.ReLU(), nn.Dropout(0.5), nn.Linear(128, n_class) ) def forward(self, x, snr): feat self.cnn(x) snr snr.view(-1, 1) / 20.0 # 归一化到大致[-1,1] return self.fc(torch.cat([feat, snr], dim1))snr / 20.0是因为 SNR 范围大致 -20 到 18除以 20 后落在 [-1, 1] 附近和常见归一化尺度一致。这个改动让模型在低 SNR 下可以知道自己不可靠从而输出更保守的概率分布实测在 -6dB 到 0dB 区间通常能涨 3~8 个百分点。另一个技巧是测试时增强对同一条 IQ 做多次随机相位旋转取预测概率平均。相位旋转不改变调制方式但能降低模型对绝对相位的依赖。def predict_with_tta(model, x, n_aug8): probs [] for _ in range(n_aug): phase np.random.uniform(0, 2 * np.pi) rot np.array([[np.cos(phase), -np.sin(phase)], [np.sin(phase), np.cos(phase)]]) x_rot np.einsum(ij,jk-ik, rot, x) with torch.no_grad(): p torch.softmax(model(torch.tensor(x_rot).unsqueeze(0)), dim1) probs.append(p.numpy()) return np.mean(probs, axis0)n_aug8是精度和耗时的折中再多收益递减。这套 TTA 在答辩演示时也能当亮点讲同一段信号多角度确认结果更稳。我自己做这类项目的习惯是先把数据管道和评估脚本写死、跑通再动模型结构。模型可以换十版但数据管道错了换什么模型都是白搭。每次改完预处理先跑一遍 18dB 的 sanity check准确率上不了 90% 就别往下走。希望帮到你。本文还有配套的精品资源点击获取
返回列表