文章目录
-
- 摘要
- [1 研究背景](#1 研究背景)
- [2 实验设置](#2 实验设置)
- [3 实验结果](#3 实验结果)
- [4 总结](#4 总结)
摘要
通信信号的自动调制识别(Automatic Modulation Classification, AMC)是认知无线电、频谱监测和军事通信中的关键技术。近年来,深度学习,特别是卷积神经网络(CNN),已被广泛应用于AMC任务,并展现出比传统特征工程方法更强的泛化能力。
本文将以MATLAB为工具,基于两个实验代码,详细介绍一个完整的AMC实验流程,包括数据生成、CNN训练、性能评估,并重点分析IQ幅度/相位不平衡这种硬件非理想因素对识别精度的影响。我们将对比基线模型与带IQ不平衡模型在不同信噪比(SNR)下的鲁棒性,为实际系统设计提供参考
1 研究背景
自动调制识别(Automatic Modulation Recognition, AMR)是指接收端在未知先验信息的条件下,自动识别到来信号的调制方式的技术。作为信号检测与解调之间的关键环节,AMR直接决定了后续信息处理的正确性和通信系统的整体可靠性。该技术在军用和民用领域均具有重要的战略价值:在军事上,它是电子对抗、信号侦察和战场态势感知的核心手段;在民用领域,它广泛应用于频谱监测、认知无线电、链路自适应以及干扰防护等场景。随着无线通信技术的快速发展,信号的调制方式日趋多样,电磁环境也变得愈加错综复杂,探索实时高效的调制识别技术具有重要的现实意义。
传统的AMR方法主要分为两类:基于似然比(Likelihood-Based, LB)的方法和基于特征(Feature-Based, FB)的方法。LB方法通过评估观测信号在不同调制假设下的概率分布来实现分类,虽然在理论上具有最优性能,但计算复杂度极高,难以满足实际工程需求。FB方法依赖于人工设计的统计特征(如高阶累积量、谱相关密度等),其性能在很大程度上取决于特征工程的质量,且在低信噪比和复杂信道条件下识别精度严重衰减。此外,传统方法在面对多种调制类型共存、信号参数动态变化等复杂场景时,往往缺乏足够的灵活性和自适应性。
近年来,深度学习的迅猛发展为AMR技术带来了革命性的突破。深度神经网络通过多层非线性变换,能够从原始IQ数据中自动学习信号的层次化特征表示,同时实现特征提取与分类识别的端到端优化。其中,卷积神经网络(Convolutional Neural Network, CNN)凭借其层次化的局部感知机制,能够有效捕获调制信号中的幅度和相位失真特征,在空间特征提取方面展现出独特优势。与传统的AMR方法相比,基于深度学习的AMR方法在识别精度和计算效率等方面均具有显著优势。
2 实验设置
实验涵盖了10种常见调制类型:
-
数字调制:BPSK, QPSK, 8PSK, 16QAM, 64QAM, PAM4
-
频移键控:GFSK, CPFSK
-
模拟调制:DSB-AM, SSB-AM
每个调制类型的符号映射采用标准方式,基带信号通过根升余弦滤波器(由helperModClassGetModulator生成)进行脉冲成形,过采样率为8(sps=8),每帧包含1024个采样点(spf=1024),采样率为200 kHz。
训练数据规模:每种调制类型生成5000帧,总计50000帧,按80%训练、10%验证、10%测试划分。
此外,为模拟真实无线信道,引入了:
瑞利/莱斯多径信道(comm.RicianChannel):三条路径,延迟0, 1.8e-6, 3.4e-6秒,增益0, -2, -10 dB,莱斯因子K=4,最大多普勒频移4 Hz。
载波频率偏移:随机偏移±5e-6 × fs,模拟本地振荡器偏差。
在零中频接收机中,I路和Q路的本振信号在幅度和相位上很难保持完美正交,会导致:
-
幅度不平衡:I/Q两路增益不一致(通常±5%以内)
-
相位不平衡:I/Q相位误差(通常±2°以内)
这种非理想性会引入镜像干扰,降低调制识别的准确性。
3 实验结果
| 层级编号 | 层类型 | 参数详情 | 输出尺寸(H×W×Channels) |
|---|---|---|---|
| 1 | 输入层 | 输入尺寸 [1×1024×2] |
1×1024×2 |
| 2 | 卷积层 1 | 8 个滤波器,滤波器大小 [1×3],步长 [1×1],填充 'same' |
1×1024×8 |
| 3 | 批归一化层 | - | 1×1024×8 |
| 4 | ReLU 激活层 | - | 1×1024×8 |
| 5 | 最大池化层 1 | 池化窗口 [1×2],步长 [1×2] |
1×512×8 |
| 6 | 卷积层 2 | 16 个滤波器,滤波器大小 [1×3],填充 'same' |
1×512×16 |
| 7 | 批归一化层 | - | 1×512×16 |
| 8 | ReLU 激活层 | - | 1×512×16 |
| 9 | 最大池化层 2 | 池化窗口 [1×2],步长 [1×2] |
1×256×16 |
| 10 | 卷积层 3 | 32 个滤波器,滤波器大小 [1×3],填充 'same' |
1×256×32 |
| 11 | 批归一化层 | - | 1×256×32 |
| 12 | ReLU 激活层 | - | 1×256×32 |
| 13 | 最大池化层 3 | 池化窗口 [1×2],步长 [1×2] |
1×128×32 |
| 14 | 卷积层 4 | 64 个滤波器,滤波器大小 [1×3],填充 'same' |
1×128×64 |
| 15 | 批归一化层 | - | 1×128×64 |
| 16 | ReLU 激活层 | - | 1×128×64 |
| 17 | 最大池化层 4 | 池化窗口 [1×2],步长 [1×2] |
1×64×64 |
| 18 | 全连接层 1 | 输出神经元数 256 | 1×1×256 |
| 19 | ReLU 激活层 | - | 1×1×256 |
| 20 | Dropout 层 | 丢弃概率 0.5 | 1×1×256 |
| 21 | 全连接层 2(输出层) | 输出神经元数 10(对应10种调制类型) | 1×1×10 |
| 22 | Softmax 层 | - | 1×1×10 |
| 23 | 分类输出层 | 交叉熵损失函数 | - |
| 超参数名称 | 取值 / 配置 | 说明 |
|---|---|---|
| 优化器(Optimizer) | adam |
自适应矩估计,兼顾收敛速度与稳定性 |
| 初始学习率 | 0.001 |
采用固定学习率,未使用学习率衰减策略 |
| 训练轮数(Epochs) | 10 |
训练集完整遍历次数,经验证足以使损失收敛 |
| 批量大小(Mini-Batch Size) | 256 |
每次迭代使用的样本数,平衡内存占用与梯度估计精度 |
| 数据打乱 | 'every-epoch' |
每个训练轮次开始时随机打乱样本顺序,增强泛化性 |
| 验证数据 | {valDataComplex, valLabel} |
占总数据集 10% 的验证集,用于监控过拟合 |
| 验证频率 | 50 |
每 50 次迭代在验证集上计算一次准确率和损失 |
| 损失函数 | 'crossentropy'(交叉熵) |
适用于多分类任务的常用损失函数 |




可以看到:
-
基线模型在 10 dB 测试集上的混淆矩阵,主对角线呈现高度集中的深色条带,表明绝大多数调制类型均能被正确识别。准确率约为90%
-
在测试数据注入随机的幅度不平衡(±5%)和相位不平衡(±2°)后,混淆矩阵呈现性能退化趋势:
-
全局准确率下降:相较于基线模型,总体准确率通常降低 5~10 个百分点,表明 IQ 不平衡确实对分类决策产生了负面影响。
-
模拟调制影响较小:DSB-AM 和 SSB-AM 的识别准确率下降幅度最小(通常 < 2%),因为模拟幅度调制的信息主要承载于包络,其对 I/Q 正交性的依赖程度低于数字调制。
c
%% 实验一:基线 CNN 训练与 SNR 鲁棒性测试
clear; clc;
addpath(genpath('help'));
% ---------- 1. 参数配置 ----------
sps = 8;
spf = 1024;
fs = 200e3;
SNR = 10;
numFramesPerModType = 5000;
modTypes = categorical(["BPSK","QPSK","8PSK",...
"16QAM","64QAM","PAM4",...
"GFSK","CPFSK",...
"DSB-AM","SSB-AM"]);
bitsPerSymbol = [1, 2, 3, 4, 6, 2, 2, 2, 2, 2];
% ---------- 2. 信道对象 ----------
awgnChannel = comm.AWGNChannel('NoiseMethod','Signal to noise ratio (SNR)','SNR',SNR);
ricianChannel = comm.RicianChannel('SampleRate',fs,...
'PathDelays',[0 1.8e-6 3.4e-6],'AveragePathGains',[0 -2 -10],...
'KFactor',4,'MaximumDopplerShift',4);
clockOffset = comm.PhaseFrequencyOffset('SampleRate',fs,'FrequencyOffset',0);
% ---------- 3. 生成数据集 ----------
numModTypes = length(modTypes);
totalFrames = numModTypes * numFramesPerModType;
frameData = zeros(2, spf, 1, totalFrames);
frameLabel = repmat(modTypes(1), totalFrames, 1);
frameIdx = 1;
for modIdx = 1:numModTypes
modType = modTypes(modIdx);
bps = bitsPerSymbol(modIdx);
modulator = helperModClassGetModulator(modType, sps, fs);
numSymbols = 10 * spf;
numBits = numSymbols * bps;
frameCount = 0;
while frameCount < numFramesPerModType
if ismember(modType, ["DSB-AM","SSB-AM"])
bits = 2 * randi([0 1], numBits, 1) - 1;
else
bits = randi([0 1], numBits, 1);
end
txSignal = modulator(bits);
rxSignal = ricianChannel(txSignal);
release(clockOffset);
clockOffset.FrequencyOffset = fs * 5e-6 * (2*rand()-1);
rxSignal = clockOffset(rxSignal);
release(awgnChannel);
rxSignal = awgnChannel(rxSignal);
frames = helperModClassFrameGenerator(rxSignal, spf, spf, 50, sps);
for k = 1:size(frames,2)
if frameCount >= numFramesPerModType; break; end
frame = frames(:,k);
if modType == "BPSK"
randomPhase = 2*pi*rand();
frame = frame * exp(1j*randomPhase);
frame = frame / sqrt(mean(abs(frame).^2));
end
frameData(1,:,1,frameIdx) = real(frame);
frameData(2,:,1,frameIdx) = imag(frame);
frameLabel(frameIdx) = modType;
frameIdx = frameIdx + 1;
frameCount = frameCount + 1;
end
end
fprintf('Generated %d frames for %s\n', numFramesPerModType, char(modType));
end
fprintf('Dataset complete: %d total frames\n', totalFrames);
% ---------- 4. 划分训练/验证/测试 (80/10/10) ----------
rng(42);
partition1 = cvpartition(frameLabel, 'HoldOut', 0.2);
trainData = frameData(:,:,:,training(partition1));
trainLabel = frameLabel(training(partition1));
tempData = frameData(:,:,:,test(partition1));
tempLabel = frameLabel(test(partition1));
partition2 = cvpartition(tempLabel, 'HoldOut', 0.5);
valData = tempData(:,:,:,training(partition2));
valLabel = tempLabel(training(partition2));
testData = tempData(:,:,:,test(partition2));
testLabel = tempLabel(test(partition2));
% ---------- 5. 构建网络 ----------
modCLassNet = helperModClassCNN(modTypes, sps, spf);
% ---------- 6. 准备训练数据(复数形式) ----------
trainDataComplex = squeeze(trainData(1,:,:,:)) + 1j*squeeze(trainData(2,:,:,:));
trainDataComplex = reshape(trainDataComplex, spf, 1, []);
valDataComplex = squeeze(valData(1,:,:,:)) + 1j*squeeze(valData(2,:,:,:));
valDataComplex = reshape(valDataComplex, spf, 1, []);
% ---------- 7. 训练选项 ----------
trainOptions = trainingOptions('adam', ...
'InitialLearnRate',0.001, 'MaxEpochs',10, 'MiniBatchSize',256, ...
'Shuffle','every-epoch', ...
'ValidationData',{valDataComplex, valLabel}, ...
'ValidationFrequency',50, 'Verbose',true, 'Plots','training-progress', ...
'ExecutionEnvironment','gpu');
% ---------- 8. 训练 ----------
trainedNet = trainnet(trainDataComplex, trainLabel, modCLassNet, ...
'crossentropy', trainOptions);
save('trainedNet_baseline.mat', 'trainedNet');
% ---------- 9. 测试集评估 ----------
testDataComplex = squeeze(testData(1,:,:,:)) + 1j*squeeze(testData(2,:,:,:));
testDataComplex = reshape(testDataComplex, spf, 1, []);
testScores = predict(trainedNet, testDataComplex);
networkClasses = categories(trainLabel);
[~, idx] = max(testScores, [], 2);
predictions = categorical(networkClasses(idx));
accuracy = mean(predictions == testLabel);
fprintf('Test Accuracy (SNR=%ddB): %.2f%%\n', SNR, accuracy*100);
figure;
confusionchart(testLabel, predictions, ...
'Title','Confusion Matrix --- Baseline CNN (SNR = 10dB)', ...
'RowSummary','row-normalized','ColumnSummary','column-normalized');
% ---------- 10. SNR 扫描 ----------
snrValues = -10:5:30;
accuracyVsSNR = zeros(1, length(snrValues));
numTestFrames = 200;
for snrIdx = 1:length(snrValues)
currentSNR = snrValues(snrIdx);
sweepData = zeros(2, spf, 1, numModTypes*numTestFrames);
sweepLabel = repmat(modTypes(1), numModTypes*numTestFrames, 1);
frameIdx = 1;
for modIdx = 1:numModTypes
modType = modTypes(modIdx);
bps = bitsPerSymbol(modIdx);
modulator = helperModClassGetModulator(modType, sps, fs);
reset(ricianChannel);
frameCount = 0;
numSymbols = 10 * spf;
numBits = numSymbols * bps;
while frameCount < numTestFrames
if ismember(modType, ["DSB-AM","SSB-AM"])
bits = 2 * randi([0 1], numBits, 1) - 1;
else
bits = randi([0 1], numBits, 1);
end
txSignal = modulator(bits);
rxSignal = ricianChannel(txSignal);
release(clockOffset);
clockOffset.FrequencyOffset = fs * 5e-6 * (2*rand()-1);
rxSignal = clockOffset(rxSignal);
release(awgnChannel);
awgnChannel.SNR = currentSNR;
rxSignal = awgnChannel(rxSignal);
frames = helperModClassFrameGenerator(rxSignal, spf, spf, 50, sps);
for k = 1:size(frames,2)
if frameCount >= numTestFrames; break; end
frame = frames(:,k);
if modType == "BPSK"
randomPhase = 2*pi*rand();
frame = frame * exp(1j*randomPhase);
frame = frame / sqrt(mean(abs(frame).^2));
end
sweepData(1,:,1,frameIdx) = real(frame);
sweepData(2,:,1,frameIdx) = imag(frame);
sweepLabel(frameIdx) = modType;
frameIdx = frameIdx + 1;
frameCount = frameCount + 1;
end
end
end
sweepDataComplex = squeeze(sweepData(1,:,:,:)) + 1j*squeeze(sweepData(2,:,:,:));
sweepDataComplex = reshape(sweepDataComplex, spf, 1, []);
sweepScores = predict(trainedNet, sweepDataComplex);
[~, idx] = max(sweepScores, [], 2);
sweepPredictions = categorical(networkClasses(idx));
accuracyVsSNR(snrIdx) = mean(sweepPredictions == sweepLabel);
fprintf('SNR = %3d dB -> Accuracy = %.2f%%\n', currentSNR, accuracyVsSNR(snrIdx)*100);
end
figure;
plot(snrValues, accuracyVsSNR*100, '-o', 'LineWidth',2, 'MarkerSize',8);
xlabel('SNR (dB)'); ylabel('Accuracy (%)');
title('Classification Accuracy vs SNR --- Baseline CNN');
grid on; ylim([0 100]); xticks(snrValues);
save('amc_workspace_baseline.mat');

4 总结
综上所述,本研究以CNN为工具,以I/Q不平衡为切入点,系统评估非理想硬件条件下调制识别模型的性能退化规律,兼具理论探索价值与工程指导意义。
- 源代码 出图所见即所得,代码获取方式见VX公众号