无线电信号调制识别是通信领域的关键技术,而DeepSig发布的RadioML 2018.01A数据集已成为该领域的基准测试集。这个19GB的HDF5文件包含了24种常见调制类型,从基础的OOK、BPSK到复杂的256QAM、128APSK,覆盖了无线通信中的典型场景。
数据集最显著的特点是它的信噪比(SNR)覆盖范围:从-20dB到30dB,以2dB为步长,共26个信噪比等级。每个调制类型在每个信噪比下都包含4096个样本,每个样本是1024个IQ数据点的复数序列。这种精心设计的结构使得研究者可以系统地分析不同调制方式在不同信道条件下的表现。
我第一次接触这个数据集时,最让我惊喜的是它的组织方式。X、Y、Z三个关键数组分别存储信号数据、调制类型标签和信噪比信息。Y标签采用one-hot编码,这种设计特别适合直接输入神经网络进行分类任务。记得当时为了验证数据完整性,我特意写了个小脚本检查数据分布:
python复制import h5py
with h5py.File('GOLD_XYZ_OSC.0001_1024.hdf5', 'r') as f:
print(f"信号数据维度:{f['X'].shape}")
print(f"调制标签维度:{f['Y'].shape}")
print(f"信噪比维度:{f['Z'].shape}")
输出结果应该是(2555904, 1024, 2)、(2555904, 24)和(2555904, 1),对应总样本数和特征维度。这种统一的数据结构极大简化了后续处理流程。
实际项目中,我们经常需要分析特定信噪比下的信号特征。比如10dB这个典型的中等信噪比条件,既不像高信噪比那样理想化,也不像低信噪比那样充满挑战,非常适合算法验证。下面分享两种我常用的提取方法。
这是最直观的提取方式。由于数据集按固定顺序排列(先按调制类型,再按信噪比),我们可以直接计算10dB数据的位置偏移量:
python复制import h5py
import numpy as np
source_file = 'GOLD_XYZ_OSC.0001_1024.hdf5'
target_file = 'snr10_dataset.hdf5'
with h5py.File(source_file, 'r') as src, h5py.File(target_file, 'w') as dst:
# 10dB是第15个信噪比(从0开始计数)
snr_index = 15
# 初始化第一个调制类型的数据
X = src['X'][snr_index*4096 : (snr_index+1)*4096]
Y = src['Y'][snr_index*4096 : (snr_index+1)*4096]
Z = src['Z'][snr_index*4096 : (snr_index+1)*4096]
# 拼接其余23种调制类型
for mod in range(1, 24):
start = mod*26*4096 + snr_index*4096
end = start + 4096
X = np.vstack((X, src['X'][start:end]))
Y = np.vstack((Y, src['Y'][start:end]))
Z = np.vstack((Z, src['Z'][start:end]))
# 保存结果
dst['X'] = X
dst['Y'] = Y
dst['Z'] = Z
print(f"最终数据维度:{X.shape}")
这种方法运行速度快,但需要理解数据排列规律。有个小技巧:当不确定信噪比索引时,可以先打印Z数组的unique值确认顺序。
对于更灵活的需求,比如提取多个信噪比或特定调制类型,条件筛选更合适。这里用布尔索引实现:
python复制def extract_by_condition(input_path, output_path, target_snr=10):
with h5py.File(input_path, 'r') as src:
Z = src['Z'][:] # 读取所有信噪比
mask = (Z == target_snr).flatten()
X = src['X'][mask]
Y = src['Y'][mask]
new_Z = Z[mask]
with h5py.File(output_path, 'w') as dst:
dst['X'] = X
dst['Y'] = Y
dst['Z'] = new_Z
print(f"提取到{len(X)}个样本")
虽然这种方法需要加载全部Z数组到内存,但代码更易读。在处理大型数据集时,可以分块处理避免内存问题。
原始数据不能直接输入模型,需要经过一系列预处理。这里分享几个关键步骤和容易踩的坑。
无线电信号的幅度变化很大,直接使用原始IQ数据可能导致训练不稳定。我通常采用逐样本归一化:
python复制def normalize_iq(X):
# X形状为(N, 1024, 2)
magnitude = np.sqrt(X[:,:,0]**2 + X[:,:,1]**2)
scale = np.max(magnitude, axis=1, keepdims=True)
X_norm = X / scale[:,:,np.newaxis]
return X_norm
注意不要在整个数据集上做全局归一化,这会导致信息泄露。曾经有个项目因为这个问题,验证集准确率虚高20%,调试了很久才发现。
有限信噪比下的样本量可能不足,这些增强方法很有效:
python复制def augment_signal(X, p=0.5):
if np.random.rand() > p:
return X
# 相位旋转
angle = np.random.uniform(0, 2*np.pi)
rot_matrix = np.array([[np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]])
X = np.dot(X, rot_matrix)
# 时间偏移
shift = np.random.randint(0, X.shape[1])
X = np.roll(X, shift, axis=1)
return X
与原始文章的TensorFlow实现不同,这里给出完整的PyTorch方案,包含这些特性:
python复制from torch.utils.data import Dataset
import h5py
import numpy as np
import torch
class RadioMLDataset(Dataset):
def __init__(self, file_path, snr=None, augment=False):
self.file_path = file_path
self.snr = snr
self.augment = augment
self.indices = []
# 预计算有效索引
with h5py.File(file_path, 'r') as f:
Z = f['Z'][:]
if snr is not None:
mask = (Z == snr).flatten()
self.indices = np.where(mask)[0]
else:
self.indices = np.arange(len(Z))
def __len__(self):
return len(self.indices)
def __getitem__(self, idx):
with h5py.File(self.file_path, 'r') as f:
true_idx = self.indices[idx]
X = f['X'][true_idx]
Y = f['Y'][true_idx]
if self.augment:
X = self._augment(X)
# 转换为PyTorch张量
X = torch.from_numpy(X).float()
Y = torch.from_numpy(Y).float()
return X.permute(1, 0), Y # 调整为(2, 1024)格式
def _augment(self, X):
# 实现前述增强逻辑
return X
使用示例:
python复制dataset = RadioMLDataset('snr10_dataset.hdf5', snr=10, augment=True)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)
这个实现支持动态信噪比过滤,比如想比较不同信噪比下的性能时,只需修改snr参数即可。内存效率方面,它只在__getitem__时加载当前样本,适合大型数据集。