第【21】期--基于基于CNN的通信信号调制识别--matlab完整代码

文章目录

    • 摘要
    • [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公众号