MATLAB数字调制样式识别:特征提取与分类实战

简介:这份数字调制样式识别程序源代码基于MATLAB编写,面向通信仿真、信号处理课程设计或科研入门者,解决在接收端自动辨别数字调制方式的问题。代码工程共十四个文件,其中四个核心脚本分别承担调制信号源仿真、信道模拟、瞬时参数提取与样式识别任务,此外附有十个相关辅助链接,整个压缩包仅约11KB,便于快速下载核对。实现上覆盖二进制幅移键控、四进制幅移键控、二进制相移键控、四进制相移键控、二进制频移键控、四进制频移键控以及十六进制正交幅度调制共七种样式的判决,完整展示了从产生测试信号到提取瞬时特征、最终归类识别的工作流程。需要注意,样式识别环节中的门限值并非固定,需根据实际统计分布自行确定,这也意味着学习者需要动手完成参数标定,从而加深对特征阈值与调制方式内在联系的理解。已有1036人学习使用,对想要看懂调制识别实现细节并自行扩展算法的人员具有较好的参考价值。

1. 数字调制解调样式识别:先分清你面对的任务是"识别"而不是"解调"

数字调制解调样式识别,简单说就是给一段接收到的 IQ 波形,判断发送端用的是哪种数字调制方式——是 BPSK、QPSK、8PSK,还是 16QAM、2FSK、4FSK。它和解调是两码事:解调要把比特流还原出来,识别只关心调制样式,哪怕不知道对方说了什么。这个任务在频谱监测、协议识别、认知无线电和信号侦察里都很常见,也是通信类课程设计和毕业设计的经典选题。

MATLAB 做这件事有一个很直接的优势:信号生成、加噪、特征提取、分类器训练都能在一个脚本里打通,不像 C++ 平台要先处理文件读取和内存管理。后面这套流程从零做出来,需要的代码量不大,但坑不少——比如相位模糊、符号定时、频偏,都会让特征变得不稳定。下面是给课设、论文验证和工程预研用的可复现路径,先跑通再逐步替换。

2. 调制样式识别的核心是特征:先会算高阶累积量和瞬时统计量

2.1 为什么先做特征工程,而不是一上来就上深度学习

很多人拿到这个题目第一反应是扔给神经网络。深度学习从 I/Q 样本直接出调制类型,确实能到很高的正确率,但有两个现实问题:第一,需要大量带标签样本,每个信噪比下每种调制至少几千帧,否则网络很容易记住噪声;第二,MATLAB 里训练 CNN/LSTM 虽然方便,但调参周期长,一个 epoch 可能在 CPU 上跑好几分钟,课设和工程预研都耗不起。

特征工程路径不一样:先用领域知识把 IQ 波形压缩成几个有物理意义的数字,再用决策树、SVM 甚至最朴素的最近邻分类。样本量几百到几千就够,单帧特征提取在零点几毫秒级,跑一遍数据集几分钟。更重要的是,特征可解释——当某两类调制分不开时,你能明确指出是幅度特征失效还是相位特征失效,这对调试非常关键。

所以我的建议是:无论最终要不要用深度学习,都先把手工特征基线做出来。这个基线一旦稳定在 80%~90% 准确率,后面换模型就有对照物;如果基线就乱成一团,直接上深度学习只会更乱。后面所有内容都基于这条路线展开。

2.2 一组能区分 PSK、FSK、QAM 的特征

数字调制样式识别常用的特征分成三类:瞬时幅度、瞬时相位/频率、高阶累积量。它们分别对应调制最基本的差异:幅度变化、相位跳变、频率切换,以及星座图的对称性。

瞬时幅度特征最直观。比如 2ASK 的包络忽大忽小,而 BPSK、QPSK 的包络基本恒定。用a = abs(x)得到幅度序列,再计算零中心归一化瞬时幅度谱峰值gamma_max,这个值对幅度调制比较敏感。

瞬时相位/频率特征能分开 PSK 和 FSK。FSK 的瞬时频率在几个离散频率值之间跳,标准差大;PSK 的瞬时频率集中在零附近,只是因为相位跳变产生短暂脉冲。计算时先unwrap(angle(x))去掉相位折叠,再差分得到近似瞬时频率。

高阶累积量是最有区分力的一组特征。它的思想是:不同星座图的统计对称性不同,BPSK 的 I/Q 分量分布和 16QAM 很不一样,这种差异会体现在四阶以上矩里。一个好特性是抗高斯噪声能力比较强,因为高斯过程的四阶累积量理论上为零。

下面给出一个完整的特征提取函数骨架,输入是复基带 IQ 序列,输出是 5 维特征向量:

function feat = extractFeature(sig) % 输入: 复基带信号,行向量或列向量均可 % 输出: [gamma_max, sigma_f, Mc40, Mc41, Mc42] sig = sig(:).'; N = length(sig); % 1) 零中心归一化瞬时幅度谱峰值 a = abs(sig); a_cn = a / mean(a) - 1; gamma_max = max(abs(fft(a_cn)).^2) / N; % 2) 零中心归一化瞬时频率绝对值的标准差 ph = unwrap(angle(sig)); inst_freq = [0, diff(ph)]; inst_freq = inst_freq / (max(abs(inst_freq)) + eps); sigma_f = std(abs(inst_freq)); % 3) 二阶、四阶矩 m20 = mean(sig.^2); m21 = mean(abs(sig).^2); m40 = mean(sig.^4); m41 = mean(sig.^3 .* conj(sig)); m42 = mean(sig.^4 .* conj(sig).^2); % 4) 四阶累积量,并用二阶矩做尺度归一 c40 = m40 - 3 * m20^2; c41 = m41 - 3 * m20 * m21; c42 = m42 - abs(m20)^2 - 2 * m21^2; feat = [gamma_max, sigma_f, abs(c40)/m21^2, ... abs(c41)/m21^2, abs(c42)/m21^2]; end

逻辑上,gamma_max捕捉幅度包络的变化强度,sigma_f捕捉频率切换的离散程度,三个四阶累积量捕捉星座图对称性。注意inst_freq除以最大值只是尺度化,不是精确的频率估计;如果后续要做更精细的 FSK 识别,建议改用diff(unwrap(angle(sig))) / (2*pi*Ts)得到真实频率,再统计频点个数。参数归一化用m21^2是因为四阶矩的物理量纲是功率的平方,归一化后特征与接收信号整体增益无关,否则同样的调制换个增益特征就漂了。

2.3 特征组合与区分能力

上面的 5 维特征不是每个都对所有调制都有效。我一般会先画一张特征分布表,搞清楚哪些特征负责分开哪几类。下面是常见的对应关系:

调制样式幅度特征 gamma_max频率特征 sigma_f|c40|/m21^2|c42|/m21^2
2ASK明显大于 1接近 0中等中等
BPSK接近 0接近 0大接近 0
QPSK/8PSK接近 0接近 0小接近 0
16QAM中等接近 0非零明显非零
2FSK/4FSK接近 0明显大于 0接近 0接近 0

这个表的数值不是绝对的,因为不同载波频偏、成型滤波、信噪比都会改变绝对大小,但相对趋势稳定。比如 BPSK 的|c40|明显大于 QPSK,因为 BPSK 只有两个相位,统计上峰值更大;QPSK 四象限对称,部分高阶累积量被抵消。这个规律在很多教材和论文里都能验证。

单独一个特征不能覆盖所有调制,但组合起来以后,BPSK 和 QPSK 靠|c40|,PSK 和 FSK 靠sigma_f,2ASK 靠gamma_max,QAM 靠|c42|。特征维度不高,分类器就不容易过拟合,这是这套特征能跑通的基础。

2.4 特征为什么必须先做归一化

从extractFeature出来的 5 个特征,量纲差很多:gamma_max可能是几十,|c42|/m21^2可能只有 0.1。如果不做归一化,决策树虽然对尺度不敏感,但 SVM、KNN 这类需要距离度量的分类器就会完全被大尺度特征主导;即使是决策树,SVM 和集成模型混用的时候也会有麻烦。

常见的做法是在训练集上计算每个特征的均值和标准差,然后zscore = (feat - mu) / std。注意均值和标准差必须只在训练集上算,再应用到测试集,千万不要在包含测试样本的整个数据集上统一归一化,那样会泄漏测试信息,导致评估虚高。保存这两个参数,后面在线识别同一套到底。

% 训练集 [featNorm, mu, stdv] = zscore(Xtrain); % 测试集必须用训练集的 mu/stdv XtestNorm = (Xtest - mu) ./ stdv;

这个细节在不少课设项目里被忽略,最后混淆矩阵漂亮得不行,换一段新信号却崩盘。记住:归一化是模型的一部分,不是数据预处理的外部步骤。如果换了新的接收机增益,特征的整体尺度会变,但用固定的mu/stdv调整后,分类器依然能正确响应;这就是把归一化常数当成模型参数保存的好处。

3. 用 MATLAB 搭通一套最小可跑的识别流程:信号生成、特征提取、分类

3.1 生成调制样本:从 pskmod/qammod 到基带 IQ

做识别,第一步是准备样本。不要急着拿真实采集的数据,先自己仿真生成,因为真实信号没有标签,你不知道它到底是什么调制,没法评估正确率。仿真信号的好处是标签天然存在,等整个流程验证过了再换真实数据。

常见的做法是用 Communications Toolbox 里的pskmod、qammod生成 PSK/QAM,用fskmod或自定义振荡器生成 FSK。下面这段生成 QPSK 基带信号:

M = 4; % QPSK numSym = 2000; % 符号数 sps = 8; % 每符号采样点数 data = randi([0 M-1], numSym, 1); modSym = pskmod(data, M, 0, 'gray'); % 可选:升余弦成型滤波 rrcTx = comm.RaisedCosineTransmitFilter( ... 'RolloffFactor', 0.35, ... 'FilterSpanInSymbols', 10, ... 'OutputSamplesPerSymbol', sps); tx = rrcTx(modSym);

pskmod的第三个参数是初始相位,设成 0 表示星座图以标准方向摆好。注意这里modSym是符号速率的复数序列,直接用的话相邻符号之间会有相位跳变,频谱会带外泄漏;加成型滤波器会让信号更接近真实发射机。对于特征提取,成型滤波不是必须的,但加上以后特征分布更平滑,分类器更容易收敛。RolloffFactor取 0.35 是工程上最常见的取值,滤波器跨符号 10 个能基本收敛,sps=8是为了后续特征计算时每个符号有足够的采样点。

FSK 不能用pskmod直接改,常见做法是创建频率序列再积分成相位:

function sig = genFSK(numSym, sps, fSpace, M) % 生成 MFSK 基带信号,fSpace 为相邻频点间隔 h = fSpace / sps; % 调制指数折算 data = randi([0 M-1], numSym, 1); phase = zeros(numSym*sps, 1); for n = 1:numSym idx = (n-1)*sps + (1:sps); phase(idx) = 2*pi*h*(data(n)- (M-1)/2) * (0:sps-1) / sps; end sig = exp(1j * cumsum(phase)); end

这段是简化做法,没有做相位连续处理,但用来做识别样本足够了。实际工程中,FSK 调制指数会影响瞬时频率特征,fSpace太小时 2FSK 和 4FSK 的频率直方图会重叠,识别会变难。后面避坑部分会展开。

3.2 把接收信号加噪

识别必须在不同信噪比下测试。MATLAB 给复基带信号加高斯白噪声的标准做法:

SNRdB = 10; noise = (randn(size(tx)) + 1j*randn(size(tx))) / sqrt(2); rx = tx + 10^(-SNRdB/20) * noise;

注意10^(-SNRdB/20)这个系数对应的是电压信噪比,因为信号和噪声都是复数幅度。如果你已经做了成型滤波,这里可以直接对滤波后信号加噪;没滤波也可以,但要保证符号序列幅度归一化。经验是每个信噪比多生成几个独立样本,比如 200 个,取不同随机数据和不同噪声实现,避免模型只记住某个噪声样板。

这里有个容易搞混的地方:噪声功率应该由信号的Es/N0而不是采样点数来判断。如果直接对信号序列加噪,SNRdB是定义在采样点上的信噪比;如果你后续要对符号级特征做分析,需要把sps的因素考虑进去。简单起见,我们统一在采样序列级别加噪,所有特征也都在采样序列上计算,保持口径一致。

3.3 批量构造训练数据集

数据集结构很简单:一个特征矩阵X,一个标签向量Y。下面这段把所有调制类型、所有信噪比、多次重复组合起来:

modSet = {'BPSK','QPSK','8PSK','16QAM','2FSK','4FSK'}; snrSet = -5:5:15; numRep = 200; X = []; Y = []; for mi = 1:numel(modSet) for si = 1:numel(snrSet) for rep = 1:numRep sig = genSignal(modSet{mi}, snrSet(si)); feat = extractFeature(sig); X = [X; feat]; Y = [Y; modSet{mi}]; end end end Y = categorical(Y);

这里genSignal需要你自己封装一个函数:根据调制名返回一个加噪后的 IQ 序列,具体实现就是 3.1 和 3.2 两段代码的组合。X是 6000×5 的矩阵,Y是 6000×1 的 categorical。用repmat或cellfun可以写得更优雅,但 for 循环最容易读、不容易错,前期就这样。

有一个细节:每个样本的符号数numSym取多少。特征提取里涉及 FFT、相位差分,符号太少参数估计方差大,建议不少于 1000;符号太多则单样本计算慢,也没必要。折中是 1024 或 2048,这是很多论文里常用的帧长度。你可以做个实验:numSym=256时准确率掉多少,4096时又提升多少,最后选一个性价比最高的。

3.4 训练决策树分类器并做交叉验证

特征和标签准备好以后,第一版分类器用决策树就够了。MATLAB 的fitctree自带交叉验证选项:

rng(1); mdl = fitctree(X, Y, 'CrossVal', 'on', 'KFold', 5); acc = 1 - kfoldLoss(mdl); fprintf('5折交叉验证正确率: %.2f%%\n', acc*100);

KFold=5表示把样本分成 5 份,轮流用 4 份训练、1 份验证,最终准确率是 5 次平均。第一次跑这个流程,如果准确率能上 85%,说明特征和标签没问题;如果只有 50%~60%,不要急着换分类器,八成是前面的生成或特征代码写错了。决策树的好处是简单、训练快、不需要调太多超参,适合当冒烟测试。

注意这里我们刻意没有把X归一化,因为fitctree本身不依赖尺度。但如果你后面要跑 KNN 或 SVM,一定要回到 2.4 节做归一化。交叉验证的结果只是告诉你基线的水平,不能代替独立测试集;后面评估章节会再说。

3.5 一个可维护的脚本结构

到这一步,代码已经有点散了。我会把它组织成四个文件,这样改一个环节不动其他地方:

  • genSignal.m:根据调制方式名和 SNR 生成加噪 IQ 序列
  • extractFeature.m:上一章的特征提取函数
  • buildDataset.m:批量生成样本并返回 X、Y
  • main.m:主流程,包括数据划分、训练、评估

genSignal内部用switch分派到pskmod、qammod或自定义 FSK 生成器。这样的结构对后面扩展新调制类型很友好:加一个 case,跑一遍数据集,看混淆矩阵哪里变差就行。不要让主脚本变得像一个超长的面条,否则后续加特征、加调制样式都会牵连到别处,血泪经验。

4. 样式识别系统的评估:别只看总准确率,要分信噪比看混淆矩阵

4.1 为什么按信噪比分段统计

总准确率是一个笼统指标。一个模型如果只在 15dB 下测试,准确率可能 99%;但真实信号大多在 0~5dB 甚至更差,这时 QPSK 和 8PSK、16QAM 和 64QAM 几乎分不开。设计识别系统时,需求通常有一个信噪比范围,比如“在 SNR≥0dB 时正确率不低于 90%”。所以评估必须拆开信噪比来看。

正确的做法是把测试集按照生成时的 SNR 分组,或者直接让测试集覆盖同一个 SNR 网格,然后分别统计每组的正确率,画一条“正确率-SNR”曲线。如果你的特征在 0dB 下只有 60%,大概率需要补特征或换分类器;如果 15dB 下还是 80%,那问题出在特征本身或者标签方案上。

画分 SNR 准确率曲线时,我通常会这样写:

snrGroup = repelem(snrSet, numRep * numel(modSet)); % 与 X 的行一一对应 % 注意 X 的行顺序是由三层循环生成的:外层调制、中层SNR、内层重复 accBySnr = zeros(size(snrSet)); for si = 1:numel(snrSet) idx = snrGroup == snrSet(si); accBySnr(si) = mean(Ypred(idx) == Ytest(idx)); end plot(snrSet, accBySnr * 100, 'o-'); xlabel('SNR (dB)'); ylabel('正确率 (%)');

这里的核心是保证snrGroup和特征矩阵的行顺序一致。如果你在buildDataset里用的是循环而不是repmat,一定按照循环顺序排列;顺序乱了整条曲线都没意义。分 SNR 统计后,你能看到模型在低信噪比下的疲态,这比单个数字有价值得多。

4.2 用混淆矩阵定位“到底谁和谁混了”

比总准确率更值得看的是混淆矩阵。MATLAB 里confusionchart一行代码就能画:

Ypred = predict(mdl, Xtest); figure; confusionchart(Ytest, Ypred);

这里的mdl可以是训练好的分类器,Xtest/Ytest是独立测试集。如果矩阵对角线上数字很大,说明模型整体可靠;如果某两行之间有大块非零值,说明这两类特征高度重叠。常见的重叠模式是:QPSK 和 8PSK 混、16QAM 和 64QAM 混、2FSK 和 4FSK 混。这些调制在星座图或频率图上相似,单靠五维特征可能压不下去,后面增补特征才有效。

拿到混淆矩阵后,不要只看一眼就翻过去。我会把它按行归一化成百分比,比如“真实 8PSK 的信号有多少被判成 QPSK”,这能暴露分类器的偏置。如果训练集各类样本数量均衡,百分比和原始计数差不了太多,但归一化后更容易对比。还可以把不同信噪比下的混淆矩阵打印出来,观察误判是从哪个 SNR 开始爆发的。

4.3 决策树不够就换集成或 SVM

基线跑通后,可以试试更复杂的分类器。MATLAB 常用三个选项:

分类器调用函数适合场景注意
决策树fitctree特征少、要解释性容易过拟合
随机森林/装袋fitcensemble(...,'Method','Bag')特征稍多、样本量大训练慢,但稳定
SVMfitcecoc特征量级已归一化必须做 zscore

示例:

bagMdl = fitcensemble(Xtrain, Ytrain, 'Method', 'Bag', ... 'NumLearningCycles', 30); svmMdl = fitcecoc(Xtrain, Ytrain);

NumLearningCycles可以理解成树的数量,30 棵左右在 6000 样本下已经能稳定。SVM 的fitcecoc会自动做一对一多分类,不用手工拆。对比时固定同一个测试集,记录训练时间和准确率。结论往往是这样:SVM 在低 SNR 下比决策树好 3~5 个百分点,但训练时间也长了 10 倍;随机森林则居中。

需要注意的是,fitcecoc默认用线性核还是 RBF 核,取决于版本和参数;如果特征没有归一化,SVM 的结果可能比决策树还差,所以前面强调的zscore不能省略。保存模型用save('trainedModel.mat', 'mdl', 'mu', 'stdv'),下次直接加载,不需要重新训练。

4.4 调参顺序:先特征、后样本量、再分类器

很多人在分类器超参数上反复调,但效果不明显。我的经验是严格按三步来:

  1. 先看混淆矩阵,确定哪两类混。是gamma_max区分不了 2ASK 和 BPSK,还是|c42|区分不了 16QAM 和 64QAM?针对性地增加一个特征,例如从四阶累积量扩展到六阶,或者加入高阶统计特征。
  2. 再看样本量。如果混的那类样本只有几十个,特征再准也学不好。增加到每类每 SNR 至少 200 个样本,一般都会有明显提升。
  3. 最后才碰分类器超参数。比如决策树的MinLeafSize,或集成里的NumLearningCycles。超参调整对准确率的影响往往在 2~3 个百分点以内,远小于前两步。

这个顺序能避免把大量时间花在最不重要的环节。调参时每次只改一个变量,记录结果,否则没法判断是哪个改动起了作用。我见过有人把NumLearningCycles从 10 调到 2000,准确率只涨了 0.5%,却认为是模型变强了——其实早就收敛了。

5. 避坑指南:调制样式识别最容易翻车的五个细节

5.1 相位模糊让 QPSK 特征忽高忽低

现象:同一段 QPSK 信号,稍微旋转星座图,|c40|和|c42|差好几倍,分类器在训练时学到的规律到测试时失效。

原因:高阶累积量对信号的整体相位很敏感。接收机不知道载波绝对相位,解调后的星座图可能整体旋转任意角度,QPSK 在旋转 45° 时统计特性会变。

解决:特征提取前做相位归一化。常见做法是把 IQ 序列乘一个旋转因子,让星座图的主轴对准实轴,比如计算phi = angle(mean(sig.^2)) / 2,然后sig = sig * exp(-1j*phi)。这个操作对 BPSK/QPSK 有效,但对 QAM 不总能确定唯一主轴,这时可以直接用累积量的绝对值,并在训练数据里加入随机相位偏移,让分类器学到旋转不变性。

5.2 符号定时不准让瞬时幅度特征失效

现象:2ASK 的gamma_max理论上应该很大,但实际计算时整个包络都差不多,识别不出来。

原因:采样点落在符号跳变边缘上,幅度被平均了。基带符号经过成型滤波后,如果定时时刻偏了半个符号,眼图张开度最小,幅度特征会被磨平。

解决:在生成信号时固定sps并在接收端做匹配滤波,然后按最佳采样点抽取。简单测试中可以用升余弦滤波器的群延迟补偿;更实际的做法是先用comm.SymbolSynchronizer恢复符号定时,再对输出信号做特征提取。识别不一定要精确解调,但定时偏差会严重破坏幅度和相位特征,这点和做离散时间系统仿真不一样,不能只看波形形状。

5.3 载波频偏把 FSK 特征推到系统性的误差上

现象:2FSK 在无频偏时sigma_f很大,加一点频偏反而变小,最后 2FSK 被识别成 PSK。

原因:瞬时频率是瞬时相位的差分,频偏会给所有采样点加一个固定偏移。归一化除以最大值时,如果频偏大于调制频偏,那调制频偏就被压缩到很小的比例,特征区分度消失。

解决:先做粗频偏估计。简单做法是取瞬时频率直方图的众数作为频偏估计,从相位差分中减掉;或者对信号做平方/四次方后估计残留载波频偏(适用于 PSK),再用comm.CoarseFrequencyCompensator。对于 FSK,可以直接用频率直方图的峰间距作为特征,而不是统计标准差。

5.4 训练和测试信噪比范围不匹配

现象:训练时只用 0dB 样本,测试时 12dB 样本准确率反而下降;或者训练时覆盖全范围,但测试时集中在某一段,结果波动大。

原因:分类器在训练数据密度高的区域发挥好,在密度低的区域会偏向先验概率。信噪比不同的特征分布并不重合,只是部分重叠。

解决:训练集按 2dB 步长覆盖目标范围的每个点,每个点采样数量均衡。如果目标范围是 -5~15dB,就按 -5:2:15 分成 11 个点,每点 100 个样本,而不是只在 -5、0、5、10、15 五个点抽样。测试时也要用同样的 SNR 网格,画出曲线后看是否平滑;如果曲线在某个点突然塌陷,大概率那个点的样本太少或噪声实现有倾向。

5.5 不同 MATLAB 版本的工具箱函数差异让人反复翻车

现象:同学电脑上fskmod能用,你这里报错Undefined function;或者pskmod的参数在旧版本不支持gray选项。

原因:Communications Toolbox 的函数名和签名在不同版本有调整。fskmod在 R2018b 之后有过行为修改,升余弦滤波器的对象写法也有变化。

解决:代码开头加存在性检查,或者统一使用最稳定的函数组合。我的做法是:PSK/QAM 用pskmod/qammod加旧式rcosfir滤波器,FSK 自己写积分器调频生成,完全不依赖fskmod。这样同一份代码在 R2016a 到 R2023b 上都能跑。另一点:不要在代码里写死路径或使用addpath指向带中文的文件夹,MATLAB 在部分版本上会报编码错误。

6. 进阶:用滑窗把离线识别变成对连续 IQ 流的在线识别

离线数据集训练好以后,真正工程中拿到的是连续 IQ 流,比如软件无线电采集的几百万个采样点。要识别每一段的调制样式,需要用滑动窗口把长序列切成块,逐块提取特征、逐块分类。这比离线仿真多一个窗口参数问题。

先看最简单的滑窗实现:

winLen = 4096; % 窗长,对应至少几百个符号 hopLen = 2048; % 步进,50% 重叠 blocks = buffer(rxStream, winLen, winLen - hopLen)'; % 每行一个窗 labels = categorical(zeros(size(blocks,1),1)); % 预分配 for k = 1:size(blocks,1) feat = extractFeature(blocks(k,:)); feat_norm = (feat - mu) ./ stdv; % mu/stdv 来自训练集 labels(k) = predict(mdl, feat_norm); end

buffer是信号处理工具箱的函数,第一个参数是长序列,第二个是块长,第三个是重叠长度。这里winLen-hopLen=2048表示每次后移 2048 点,两块之间重叠 2048 点。窗长和时延要权衡:窗太长,识别一个标签要等很久,如果信号中途切换调制,切换处会模糊;窗太短,特征估计方差大,误判多。我的经验是窗内至少包含 500 个符号,这样才能让高阶累积量稳定到可用程度;重叠率 50% 可以在时延和更新率之间取折中。

滑动窗口输出的标签序列可以做后处理,比如用多数投票去掉孤立跳变。常见做法是对连续 5 个窗的标签取众数,只有连续 3 个以上窗口都变成新标签才认为调制切换了,否则认为是一次误判。这个后处理很像通信里的判决器,能让在线结果稳定很多。

验证在线识别是否可靠,要重放一段已知调制顺序的信号,比如前 3 秒 QPSK、中间 2 秒 16QAM、后面 3 秒 2FSK,逐窗输出预测标签,再和真实切换时刻对比,统计切换延迟和误判点。如果不做这个测试,很难发现滑窗参数的坑。我第一次跑的时候,窗长取了 1024,结果高阶累积量在 16QAM 和 QPSK 之间乱跳,误报率特别高;后来把窗长提到 4096,再叠加多数投票,才稳定下来。这个教训让我记住了:在线识别不是把离线函数拿来套一下就完事,窗长和重叠度要按特征方差重新调,最好先把特征提取函数做成独立对象,固定所有参数,避免每次调用因为状态不同而结果漂移。

希望帮到你。

本文还有配套的精品资源,点击获取