卷积神经网络识别信号 ResNet RadioML2016.10A数据集11种信号识别分类 出图包含每隔2dB的分类准确率曲线、混淆矩阵、损失函数迭代曲线等 Python实现

无线电信号调制识别这事儿听起来挺玄乎,实际就是把不同形状的波形分门别类。咱们今天用厨房里常见的食材——ResNet和RadioML2016.10A数据集,来炖一锅信号分类的硬菜。

数据加载这步得仔细点,IQ信号就像两条纠缠的蛇,得把它们盘成适合卷积网络吃的形状:

def load_data(file_path):
    with h5py.File(file_path, 'r') as hdf:
        iq_data = np.transpose(hdf['X'][()], (0, 2, 1))  # [N,2,128]变成[N,128,2]
        labels = hdf['Y'][()]
        snrs = hdf['Z'][()].flatten()
    return iq_data, labels, snrs

这里把通道维度放在最后,方便后续做1D卷积。原始数据长得像心电图,不同调制类型的信号在时域上扭出不同的舞姿,比如BPSK像折线图,QAM16像毛线团。

改造ResNet得动点小手术,传统二维卷积得换成一维的:

class ResNet1D(nn.Module):
    def __init__(self, num_classes=11):
        super().__init__()
        self.conv1 = nn.Conv1d(2, 64, kernel_size=7, stride=2, padding=3)
        self.bn1 = nn.BatchNorm1d(64)
        self.layer1 = self._make_layer(64, 64, 2)
        self.avgpool = nn.AdaptiveAvgPool1d(1)
        
    def _make_layer(self, in_channels, out_channels, blocks):
        layers = []
        layers.append(ResBlock1D(in_channels, out_channels))
        for _ in range(1, blocks):
            layers.append(ResBlock1D(out_channels, out_channels))
        return nn.Sequential(*layers)

残差块里的shortcut连接就像给网络装了个记忆增强器,防止深层网络把重要特征给忘了。特别要注意输入输出通道数变化时的1x1卷积适配,这相当于给数据换马甲。

训练时发现数据不平衡问题,某些调制类型在低信噪比时多得离谱。祭出Focal Loss大法:

class FocalLoss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2):
        super().__init__()
        self.alpha = alpha
        self.gamma = gamma

    def forward(self, inputs, targets):
        BCE_loss = F.cross_entropy(inputs, targets, reduction='none')
        pt = torch.exp(-BCE_loss)
        loss = self.alpha * (1-pt)**self.gamma * BCE_loss
        return loss.mean()

这个损失函数专治各种不服,让模型更关注难分类样本。训练曲线显示前10轮准确率坐火箭似的从20%窜到70%,后面就进入慢跑模式。

测试环节最刺激的是看不同信噪比下的表现:

snr_acc = {}
for snr in np.unique(snrs):
    mask = (snrs == snr)
    acc = accuracy_score(all_labels[mask], all_preds[mask])
    snr_acc[snr] = acc

画出来的准确率曲线像坐滑梯,从20dB的92%咻地滑到-20dB的23%。特别是QPSK和BPSK这对冤家,在低信噪比时互相认错,混淆矩阵里这两类的交叉项红得发亮。

可视化方面有个骚操作——把中间特征层输出用t-SNE降维:

features = model.conv5_output.cpu().numpy()
tsne = TSNE(n_components=2)
components = tsne.fit_transform(features)

结果图上11个类别各自抱团,但QAM16和QAM64的领地总是犬牙交错,说明高阶调制确实更难区分。有个别样本像孤岛一样飘在别家地盘,估计是数据标注时留下的坑。

最后模型在-10dB以上能稳定达到80%准确率,这成绩放在实际场景中够用但不算顶尖。改进方向可以试试时频分析双管齐下,或者用自注意力机制捕捉长程依赖——不过那就得换Transformer上场了。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐