ARTICLE DETAIL

资讯详情

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

MATLAB生成调制信号时频图与RML2016a数据集对比实践

MATLAB生成调制信号时频图与RML2016a数据集对比实践 做通信调制信号识别RML2016a这个数据集绕不开用MATLAB画调制信号的时频图也绕不开。我最近正好做了一件事用MATLAB自己生成11类通信调制信号画出时频图再跟RML2016a数据集里对应的调制信号时频图逐帧摆在一起比较确认两者在视觉特征和统计指标上是否足够接近。整个流程走完踩了不少坑也理清了一个很重要的结论——只要生成参数、时频分析参数和归一化方式全部对齐自生成的时频图完全可以作为数据集样本的补充用来做数据增强或者预训练。这篇文章不是教科书式的原理堆砌而是我把整套流程跑通后的记录。内容包括RML2016a数据集的读取方式、MATLAB生成各类调制信号的完整代码、spectrogram参数怎么定、11种调制信号时频图的特征对照以及我在短帧时频图上踩过的几个典型坑。适合正在做深度学习调制识别、需要扩充数据集或者想搞明白“时频图作为模型输入到底怎么生成才靠谱”的朋友。1. 为什么大家都换成了时频图输入表示这件事比模型更重要1.1 RML2016a到底是个什么数据集先说数据集。RML2016a严格说通常指RadioML 2016.10a是OShea团队公开的一个调制信号识别基准数据集当年就是为了验证深度学习在物理层信号处理上的能力而做的。它里面包含11种调制类型8种数字调制BPSK、QPSK、8PSK、CPFSK、GFSK、PAM4、QAM16、QAM64和3种模拟调制AM-DSB、AM-SSB、WBFM。每个样本都是一帧128点的复基带I/Q数据采样率1MHz也就是说一帧只有128微秒。信噪比从-20dB到30dB步进2dB一共26个SNR档位每个调制类型SNR组合下有接近1000帧样本总量大概22万帧。这些I/Q样本在Python里以pickle字典形式存储键是(mod, snr)值是一个(N, 2, 128)的数组第一维是样本序号第二维分别是I路和Q路第三维是128个采样时刻。这个数据集在调制识别论文里属于“出厂标配”几乎所有基于深度学习的调制识别研究都会拿它做基准。但有个现实问题它的样本是固定的你想在它基础上做数据增强、想补一些自己信号采集条件下的样本或者想验证自己搭的发射链路模型靠不靠谱都必须先学会用同样的“口径”生成调制信号再画成可比较的时频图。这就是我这次做的事情的起点。1.2 从I/Q时序到时频图中间经历了什么模型训练里的输入表示常见有三条路线第一种是直接用原始I/Q时序。128个复采样点拆成I、Q两路实数序列变成2×128的矩阵喂给网络。这种做法的好处是信息无损但问题也很明显——网络得自己从原始波形里学出频率、相位、符号速率这些概念训练效率低而且对样本长度很敏感。第二种是星座图。把I/Q看成实部和虚部画成散点图。星座图对相位调制PSK/QAM很友好一眼能看出调制阶数但它丢掉了时间演化信息星点重合之后比如多径和频偏影响下散点会糊成一团很难区分。第三种就是时频图。用短时傅里叶变换STFT把信号按时间窗口切成一段段每一段做FFT最后得到一张以时间为横轴、频率为纵轴、颜色表示能量强度的二维图像。时频图同时保留了时间、频率、能量三个维度的信息对CNN这类二维卷积模型来说是最自然的输入形态。这也是很多论文里“I/Q图像输入”的主流做法。但时频图有个隐藏问题它是在“用图像的方式描述信号”而图像的样子高度依赖STFT参数。同一个QPSK信号窗函数用32点和用128点画出来完全是两张图。如果把两张参数不一致的图混在一起训练模型学到的很可能不是调制方式的区别而是“窗长不同导致的图像纹理差异”。这也是我这次坚持“所有比较必须在同一组参数下进行”的根本原因。1.3 搞清“像不像”能解决什么问题把自生成信号的时频图和数据集的时频图做对比表面上是在做可视化实际上是在回答三个问题我的信号生成链路符号映射、成型滤波、帧对齐是否真实能不能产生和公开数据集同分布的数据如果我把自生成数据混进RML2016a一起训练模型会不会因为两种图像风格不一致而掉点反过来如果我只用自生成数据做预训练再用数据集微调这个迁移路径是否成立。这三个问题在做工程落地时都会遇到。数据集永远是有限的真实采集又贵又慢自生成数据是唯一可规模化的补充来源但前提是“风格对齐”。所以这篇文章后面所有的代码和参数选择都在围绕“如何让两张时频图看起来、统计起来都像出自同一套系统”展开。2. 吃透RML2016a文件格式、调制类型和读入MATLAB的正确姿势2.1 数据集结构和调制类型RML2016a常见分发文件是RML2016.10a_dict.pkl一个Python pickle序列化文件体积几百MB。它内部是一个字典键是元组(mod_name, snr)比如(QAM16, 8)代表“QAM16调制、8dB信噪比”这一类样本对应的值是一个numpy数组shape是(N, 2, 128)。这里有个容易搞混的细节数组的第二维顺序是I路、Q路不是复数实部虚部的顺序问题而是两个独立的实数通道。在MATLAB里要还原成复基带信号需要做x I 1j*Q不是complex(I(:), Q(:))就完事了要注意I/Q是行向量还是列向量别在组装时把转置搞错。另一个细节是数据集的SNR网格。以-20dB起步进2dB一直到30dB共26档。低SNR段-20到-6dB的样本在时频图上基本被噪声淹没肉眼看不出调制特征这也是后面做可视化和统计对比时要把SNR下限卡在-2dB以上的原因。2.2 用Python转成.mat的转换脚本MATLAB不能直接读pickle所以我先用Python把数据集转成MATLAB友好的.mat文件。注意Python3读取这个pickle时要用encodinglatin1因为文件的生成环境是Python2直接用默认编码会报错。import pickle import numpy as np import scipy.io as sio with open(RML2016.10a_dict.pkl, rb) as f: Xd pickle.load(f, encodinglatin1) mods [8PSK, AM-DSB, AM-SSB, BPSK, CPFSK, GFSK, PAM4, QAM16, QAM64, QPSK, WBFM] snrs list(range(-20, 32, 2)) # -20,-18,...,30 N_PER 1000 # 每类默认帧数 X np.zeros((len(mods), len(snrs), N_PER, 2, 128), dtypenp.float32) for i, mod in enumerate(mods): for j, snr in enumerate(snrs): arr Xd[(mod, snr)] X[i, j] arr[:N_PER, :, :] sio.savemat(RML2016a_raw.mat, { IQ_data: X, mod_types: np.array(mods, dtypeobject), snr_list: snrs }, do_compressionTrue)这段代码会把所有样本整理成一个五维数组调制类型、SNR档位、样本序号、I/Q通道、128个采样点。转出来的.mat会比较大大概600MB左右所以do_compressionTrue要带上。如果你的机器内存紧张可以在循环里只挑你关心的调制类型和SNR段转没必要一次全转。2.3 MATLAB端读取和I/Q重组转换完之后MATLAB这边读取很简单load(RML2016a_raw.mat, IQ_data, mod_types, snr_list); mods cellstr(string(mod_types)); % 统一转成cell字符串 snrs double(snr_list); % 取 QAM16、8dB、第5个样本 iMod find(strcmp(mods, QAM16)); iSnr find(snrs 8); iq squeeze(IQ_data(iMod, iSnr, 5, :, :)); % 2x128 x_ref iq(1, :) 1j * iq(2, :); % 复数基带信号 x_ref x_ref / max(abs(x_ref)); % 归一化便于比较squeeze之后得到2×128矩阵第一行是I路第二行是Q路组装成复数向量后一定要做幅度归一化。数据集里不同调制方式的发射功率天然不同如果不归一化美化和统计比较都无从谈起。这个归一化操作在后面自生成信号那边也要做同样处理保持口径一致。3. MATLAB生成11类调制信号时频图的完整流程3.1 数字调制部分符号映射、成型滤波和帧对齐数字调制生成的核心是随机比特/符号映射、过采样、脉冲成型滤波、帧对齐。我用的参数如下采样率fs1MHz帧长128点过采样倍数sps8所以一帧正好等于16个符号128/816成型滤波器用根升余弦RRC滚降系数0.35滤波器跨度6个符号。为什么是sps8因为数据集帧长就是128又是1M采样率你总希望一帧里至少包含十几个符号才有统计意义。sps8意味着符号速率125ksymbol/s信号带宽大约(10.35)×125kHz≈170kHz在1MHz的采样率下能清晰看到完整的频谱结构不至于带宽太窄变成一根细线也不至于太宽顶到采样率边界。RRC滤波器用rcosdesign生成fs 1e6; N 128; sps 8; rolloff 0.35; rrc rcosdesign(rolloff, 6, sps, sqrt);然后写一个通用的“符号序列转帧”函数。这里最容易踩坑的是滤波延迟。直接filter(rrc, 1, pulse)得到的是因果滤波输出开头有一段暂态如果不做群延迟补偿截出来的帧会对不齐符号边界。我推荐用conv加对称裁剪function tx symbol_frame_to_tx(sym, sps, rrc) % sym: N_symbols x 1 复数符号序列 pulse upsample(sym, sps); % 每符号插sps-1个零 tx conv(pulse, rrc); % 滤波后长度 numel(pulse)numel(rrc)-1 trim (numel(rrc) - 1) / 2; % RRC滤波器延迟 半滤长 tx tx(trim1 : end-trim); % 截出稳态段 tx tx(:).; % 统一成行向量 endconv输出长度比输入多出numel(rrc)-1个点而RRC滤波器的群延迟正好等于滤长的一半即(numel(rrc)-1)/2。从两边各裁掉这么多点就能把滤波暂态完全去掉得到和符号流对齐的128点帧。有了这个函数生成各种数字调制就是一两条命令的事% BPSK sym pskmod(randi([0 1], 16, 1), 2, 0); x_bpsk symbol_frame_to_tx(sym, sps, rrc); % QPSK sym pskmod(randi([0 3], 16, 1), 4, pi/4); x_qpsk symbol_frame_to_tx(sym, sps, rrc); % 8PSK sym pskmod(randi([0 7], 16, 1), 8, 0); x_8psk symbol_frame_to_tx(sym, sps, rrc); % PAM4 sym pammod(randi([0 3], 16, 1), 4, 0, gray); x_pam4 symbol_frame_to_tx(sym, sps, rrc); % QAM16 / QAM64 sym qammod(randi([0 15], 16, 1), 16, UnitAveragePower, true); x_qam16 symbol_frame_to_tx(sym, sps, rrc); sym qammod(randi([0 63], 16, 1), 64, UnitAveragePower, true); x_qam64 symbol_frame_to_tx(sym, sps, rrc);注意QAM用了UnitAveragePower, true这样星座点的平均功率归一为1避免不同调制阶数之间能量差异过大这对后续时频图的色标统一很重要。PAM4的相位参数设成0实测里PAM4的时频特征比较明显——信号能量在时间方向上有明显起伏但频率位置基本不动。CPFSK和GFSK不经过RRC成型滤波直接用工具箱的调制函数% CPFSK连续相位频移键控载波间隔5kHz x_cpfsk cpfsckmod(randi([0 3], 16, 1), 4, 5000, sps, fs); % GFSK高斯滤波频移键控BT0.5 x_gfsk gfskmod(randi([0 1], 16, 1), sps, 0.5);CPFSK我用的cpfskmod注意函数名是cpfskmod不是cpfsckmod输出长度16×8128正好一帧。GFSK的高斯滤波会平滑频率跳变体现在时频图上就是频率过渡更圆润和CPFSK的锐利跳变形成对比。3.2 模拟调制部分通带生成再变复基带模拟调制比数字调制麻烦一点因为它本质上产生的是实通带信号而RML2016a里存的是复基带I/Q。我的做法是先在通带生成再下变频到复基带。下变频这一步有讲究不是简单乘一个复指数然后丢掉虚部而是取希尔伯特变换后的解析信号再搬移这样能保留正确的边带结构和数据集里“信道化后的复基带”含义一致。t_frame (0:N-1). / fs; fc 80e3; % 通带载波频率选在采样率可容纳的范围内 % 消息信号两个不同频率的正弦叠加模拟一段有内容的话音/数据 msg 0.5 * sin(2*pi*5e3*t_frame) 0.3 * sin(2*pi*12e3*t_frame); % AM-DSB带载波的双边带调幅 x_am_real ammod(msg, fc, fs); x_am_dsb hilbert(x_am_real) .* exp(-1j*2*pi*fc*t_frame); % AM-SSB单边带上边带 x_ssb_real amssbmod(msg, fc, fs, upper); x_am_ssb hilbert(x_ssb_real) .* exp(-1j*2*pi*fc*t_frame); % WBFM宽带调频频偏40kHz x_fm_real fmmod(msg, fc, fs, 40e3); x_wbfm hilbert(x_fm_real) .* exp(-1j*2*pi*fc*t_frame);下变频之后载波落在0Hz附近上下边带对称地分布在两侧这和数据集里复基带样本的频谱形态是一致的。如果你直接把ammod的输出拿去画时频图得到的是一个以80kHz为对称中心的实信号谱跟数据集的复基带谱对不上对比自然无从谈起。3.3 时频变换spectrogram参数为什么这么设信号生成好了接下来是时频变换。MATLAB自带的spectrogram就是STFT的实现我用的参数是winLen 32; noverlap 28; nfft 128; [P, F, T] spectrogram(x, hann(winLen), noverlap, nfft, fs, centered);这三个参数是我调了几轮后固定的理由如下窗长32点。128点的帧长用64点以上的窗时间方向只剩1到2个窗格时频图基本变成竖条纹失去“时间演化”信息。窗长32点配合87.5%的重叠时间方向能出大约31个窗格既保留了符号切换的细节又不会让频率分辨率差到没法看。重叠28点。重叠率28/3287.5%属于高重叠。短帧信号本来就样本少高重叠相当于在时间方向做了插值平滑让时频图更连续有利于CNN提取纹理特征。nfft128。频率分辨率fs/nfft7.8125kHz。对170kHz带宽的信号来说大约22个频率格承载主要能量足够区分PSK和FSK这类频率特征差异大的调制。nfft再大比如256多出来的频率格全是过采样对区分度帮助不大计算量反而上去。centered选项很关键。我们处理的是复基带信号频谱应该以0Hz为中心对称显示默认的twosided会把负半轴频点映射到正半轴画出来是乱的而且能量分布看起来像双峰完全不对。可视化部分我用imagesc加dB转换figure; imagesc(T*1e3, F/1e3, 10*log10(abs(P) eps)); axis xy; xlabel(时间 (ms)); ylabel(频率 (kHz)); caxis([-40 10]); colorbar;这里两个细节值得说。第一10*log10(abs(P)eps)里的eps是为了防止log10(0)出现-Inf颜色轴遇到-Inf会显示成空洞。第二caxis([-40 10])是固定色标范围让所有调制类型共用同一个色彩映射尺度后面做对比才有意义。新版本MATLAB里caxis改叫clim但caxis仍然兼容可用。3.4 统一可视化动态范围和色标既然要把11类信号放在一起比较就必须保证“同一把尺子”。我把绘图逻辑封装成一个函数所有信号都走同一个流程function h plot_tf(x, fs, titleStr) x x / max(abs(x)); % 每帧幅度归一化 [P, F, T] spectrogram(x, hann(32), 28, 128, fs, centered); h figure; imagesc(T*1e3, F/1e3, 10*log10(abs(P) eps)); axis xy; xlabel(时间 (ms)); ylabel(频率 (kHz)); caxis([-40 10]); colorbar; title(titleStr); enddB下限-40dB、上限10dB这个范围是我对比了低SNR和高SNR样本之后定的。上限10dB保证信号主瓣不会饱和成一片白下限-40dB保证噪声底的显示有层次同时不至于让-20dB噪声样本满屏噪点。实际做对比时如果有人把每张图单独自动缩放色标那所有图看起来都会“差不多亮”细节全部丢失这是做图对比最容易犯的错误后面踩坑部分我会再展开。4. 11类信号的时频图长什么样特征对照与判别要点4.1 相位类BPSK、QPSK、8PSK相位调制的共同特征是频率位置基本恒定能量集中在0Hz附近一条水平亮带上符号切换的瞬间频谱出现短时展宽表现为亮带上的“毛刺”或“暗缝”。BPSK只有0和π两个相位状态符号切换时相位跳变180度频谱展宽最剧烈但由于每跳变一次频谱才扩展一次亮带的整体连续性反而比QPSK好毛刺稀疏。QPSK有四个相位状态跳变是90度跳变频率更高毛刺更密。8PSK八个相位状态每次跳变只有45度单个毛刺的能量更弱但跳变最频繁亮带看起来更均匀、更细。低SNR下这三者的差异在于亮带的“纹理细度”。如果把时频图输入CNN网络实际上就是靠这种纹理密度区分BPSK、QPSK和8PSK的。自生成信号和数据集样本在这一点上只要sps和滚降系数一致视觉上几乎无法区分。4.2 频率类CPFSK、GFSKCPFSK和GFSK的共同特征是没有固定的频率位置能量在时间方向上“游走”时频图呈现一条或多条连续弯曲的亮曲线。CPFSK的相位是连续的所以频率切换处曲线是平滑过渡的没有PSK那种“断点”GFSK在CPFSK基础上加了高斯滤波器频率过渡更圆润曲线更柔。这两者的时频图和PSK类在形态上差异极大甚至比PSK和QAM的差异还大。原因在于PSK/QAM是相位-幅度调制频率几乎不变FSK/GFSK是频率调制频率本身就是信息载体。CNN模型做分类时这类时频图几乎不可能被误判成别的调制。4.3 幅度类与联合调制PAM4、QAM16、QAM64PAM4是纯幅度调制频率位置固定但能量在时间方向上有明显的四个电平变化时频图上表现为亮带宽度或亮度随时间分档变化。由于我加了RRC成型滤波幅度切换也有平滑过渡亮带的“亮度跳变”比理论上的方形跳变要柔和。QAM16和QAM64是幅度加相位的联合调制。符号切换时幅度和相位同时跳变频谱展宽比PSK更明显时频图亮带的“毛边”比任何PSK都粗。QAM16的16个星座点切换还算稀疏QAM64的64个点切换非常密集时频图看起来更“碎”毛刺几乎连成一片。这三个调制内部的自区分主要靠亮带边缘的粗糙程度。PAM4边缘相对干净QAM16中等QAM64最粗糙。这个规律在数据集样本里同样成立是自生成数据与数据集对比时最容易验证的特征之一。4.4 模拟类AM-DSB、AM-SSB、WBFM模拟类的时频图结构和数字类完全不是一个路数。AM-DSB下变频到复基带后载波落在0Hz上下边带对称地分布在两侧整体呈现一个“上下包络随消息信号起伏”的带状结构。因为消息是低频正弦时频图里能看到边带强度随时间周期性变化。AM-SSB因为去掉了下边带只剩上边带加载波频谱明显不对称肉眼一眼就能从对称性上和AM-DSB区分开。这也是三种模拟调制里最容易识别的特征。WBFM的时频图是“频率大范围摆动”的连续亮线频偏40kHz意味着亮线在-40kHz到40kHz之间随着消息信号来回扫。它和FSK的区别在于FSK的频率是离散的几个状态WBFM的频率是连续扫过的。这个区别极其明显。4.5 特征汇总表调制类型时频图核心特征最容易和谁混淆BPSK0Hz附近一条亮线跳变毛刺稀疏QPSK依靠毛刺密度区分QPSK亮线连续跳变毛刺较密BPSK、8PSK8PSK亮线细且均匀毛刺密而弱QPSKCPFSK频率连续游走的亮曲线切换平滑GFSKGFSK同CPFSK但曲线过渡更圆润CPFSKPAM4频率固定亮度/带宽随时间分档变化QAM16边缘粗糙度不同QAM16频率固定亮带毛边粗QAM64、PAM4QAM64频率固定毛边极碎QAM16AM-DSB0Hz载波对称上下边带包络起伏AM-SSB看对称性AM-SSB频谱结构不对称只保留单边带AM-DSBWBFM频率连续大范围摆动无固定亮带CPFSK/GFSK看是否离散这张表也是我后来做自动识别时的“先验知识清单”。做数据增强或者设计分类器之前先拿这张表对照一下生成信号的特征比直接扔进网络盲目训练要高效得多。5. 与RML2016a样本逐帧对比不只靠“看”5.1 同款参数下的频谱轮廓对比可视化对比的第一个层次是“并排看”。取同一个调制类型、同一个SNR档位的数据集样本和自生成样本用完全相同的spectrogram参数画两张图放在一起。比如QAM16、8dB% 数据集样本 iq squeeze(IQ_data(7, 15, 5, :, :)); % mods{7}QAM16, snr8 - 第15档 x_ref iq(1,:) 1j*iq(2,:); x_ref x_ref / max(abs(x_ref)); % 自生成样本 sym qammod(randi([0 15], 16, 1), 16, UnitAveragePower, true); x_self symbol_frame_to_tx(sym, sps, rrc); x_self x_self / max(abs(x_self)); subplot(121); plot_tf(x_ref, fs, RML2016a QAM16 8dB); subplot(122); plot_tf(x_self, fs, MATLAB generated QAM16);注意SNR档位的索引不要算错。-20到30步进28dB应该是第15档也就是索引15。这个索引错误我犯过好几次所以后来都写了个辅助函数snr2idx (s) (s20)/2 1。并排看的时候重点检查三件事主能量带的频率位置是否都在0Hz附近亮带的宽度是否一致边缘粗糙程度是否接近。只要sps、滚降系数、归一化方式一致这两个图像在视觉上很难分出谁是谁。如果发现数据集样本的亮带明显更宽或更窄那多半是符号速率和你的sps对不上需要调整。5.2 三个可以量化的指标光靠眼睛不行还得有量化指标。我用了三个第一个是99%能量带宽。把时频图按频率方向做时间平均然后从频谱累积能量里找包含中间99%能量的频率范围。这个指标反映信号占用的频谱宽度对符号速率和成型滤波参数最敏感。Pmean mean(abs(P), 2); Pmean Pmean / sum(Pmean); cumP cumsum(Pmean); fLow F(find(cumP 0.005, 1, first)); fHigh F(find(cumP 0.995, 1, first)); bandwidth99 fHigh - fLow;第二个是频谱质心即频率的加权平均。理论上复基带信号质心应该在0Hz附近。如果自生成信号质心明显偏离0说明有残余频偏模型训练时会引入额外的不确定性。centroid sum(F .* Pmean) / sum(Pmean);第三个是时频图能量分布的变异系数用来描述“纹理粗细”。把整个时频图的像素值dB域取标准差除以均值粗糙的时频图数值波动大变异系数高平滑的波动小变异系数低。TF abs(P); cv std(TF(:)) / (mean(TF(:)) eps);以我复现的经验QAM16在sps8、滚降0.35下99%能量带宽大约在160到180kHz之间数据集样本也在这个区间附近波动频谱质心两者都在几百赫兹以内几乎为0变异系数差别也很小。不同随机种子下具体数字会有浮动但量级和趋势是稳定的。只要这三个指标在同类样本上的分布范围互相重叠就可以认为自生成信号和数据集样本在时频层面是同分布的。5.3 混训时要注意的坑确认“像”了之后如果打算把自生成数据混进RML2016a一起训练还有几个实际问题一是SNR对齐。数据集按2dB一档离散分布自生成数据如果做连续SNR训练时标签和评估时的SNR网格对不上。我建议无论是加噪还是后期处理都先把目标SNR量化到数据集的26个档位上保持评估口径一致。二是归一化一致性。我前面反复强调每帧除以最大幅值这个操作在训练pipeline里也要做而且最好先做归一化再切帧。如果数据集样本归一化自生成样本也归一化图像亮度才在一个水平。三是数据增强的尺度。自生成数据可以配合随机时移、随机相位旋转、I/Q互换这些操作来扩充。但注意不要把时移做得太大128点一帧移掉四分之一就丢信息了。6. 短帧时频图踩坑实录窗长、滤波延迟和色标6.1 坑一窗长太长时间轴直接“失踪”我第一次画时频图时习惯性地用了64点汉宁窗加32点重叠结果所有信号画出来都是一条竖直的亮带完全没有时间变化。原因很好算128点帧长窗长64、重叠32时时间窗数量是fix((128-32)/(64-32)) 3只有3个时间格图像里时间维几乎不存在自然看不到任何时间演化特征。后来我把窗长降到32点、重叠28点时间格数变成fix((128-28)/(32-28)) 25图像宽高比才正常。所以对短帧信号窗长第一原则是“先保证时间方向有足够的窗格再谈频率分辨率”。频率分辨率不够可以靠nfft补时间方向的信息丢了就真没了。6.2 坑二成型滤波的群延迟把帧对齐破坏了早期生成数字调制信号时我用filter(rrc, 1, pulse)然后直接取前128点画出来的时频图总感觉符号边缘是糊的和数据集样本对不上。排查了很久才意识到是群延迟问题filter是因果滤波输出前几十个点是滤波器的暂态响应符号能量还没完全建立直接截取当然会得到一段“半残”波形。正确做法就是3.1节里那个conv加对称裁剪的方案。裁剪量是滤波器长度减1的一半。这里提醒一句如果你换用rcosdesign的span参数比如span从6改成8trim要同步更新它是(numel(rrc)-1)/2别写死成24。这个坑在数据集样本里看不出问题因为你拿到的就是别人生成好的完整帧但自生成信号如果裁剪不对混训时模型会发现“自生成的图像边缘模糊、数据集图像边缘清晰”从而学出虚假的判别特征。6.3 坑三自动色标让所有图看起来都一样还有一个隐蔽的坑在可视化层面。MATLAB的imagesc默认根据当前图像数据自动调整色标这意味着每一张图都有自己的“最亮点”和“最暗点”。单独看每张图都挺清晰但放在一起比较时所有图都被拉伸到同样的明暗范围QAM16和WBFM的亮度差异被抹掉了。更糟糕的是低SNR样本的噪声底会被自动放大到和信号主瓣同样的亮度看起来比高SNR样本还“干净”完全误导判断。解决方式就是固定色标范围。我在所有对比图里统一用caxis([-40 10])并且把归一化放到spectrogram之前。这样不同调制、不同SNR之间的图像亮度差异才是真实的能量差异。6.4 坑四工具箱函数缺失和版本兼容最后补一个环境层面的坑。rcosdesign、cpfskmod、gfskmod、ammod这些函数都属于Communications Toolbox如果你的MATLAB没有安装这个工具箱运行到rcosdesign就会直接报错。这种情况有两个替代方案一是用Signal Processing Toolbox里的firrcos自己搭根升余弦滤波器二是在线下载RRC系数文件。我自己遇到过license问题换到2022b版本重新激活后才正常。另外新版MATLAB把caxis改名为clim旧脚本直接跑会提示“推荐使用clim”不影响运行但强迫症建议顺手改掉。还有一个小坑在数据集转换环节不同渠道下载的RML2016a文件有的类样本数不是1000有的SNR范围少了某几档。转换脚本里最好先打印一下所有键和值的shape确认数据结构再批量转换不要想当然。最后聊两句实在的整套流程走下来我最大的体会是时频图生成这件事参数一致性比算法本身重要得多。K80和V100不会让时频图变好看但窗长差一倍、色标差两档就能让同一个信号看起来像两种调制。做自生成数据和公开数据集对比时先把“口径”统一——采样率、帧长、成型滤波、归一化、STFT参数、色标范围——再谈特征差异否则所有对比都是对着空气挥拳。最后分享一个小技巧如果想快速验证自己的生成链路是否正常不要一上来就画全部11种调制选QPSK和WBFM这两个差异最大的类型用同一组参数各画一张再和数据集同SNR样本放在一起。这两类要是都对得上其他类型的差异基本也在可控范围内。这套流程现在已经固定成我自己的一个标准化脚本每次要生成新数据或者验证新调制类型时跑一遍对比就能心里有数。后续如果想进一步自动化可以把“自生成时频图与数据集时频图的指标距离”做成一个自动校验模块每次生成完数据自动输出三项指标分布的重叠度超过阈值就报警这比肉眼对比高效得多也是我下一步准备做的事。
返回列表