简介一份面向电子对抗与航空监视领域研究者的深度学习技术文献聚焦ADS-B辐射源个体识别这一关键问题。文档系统梳理了辐射源指纹识别的基本框架包括预处理、特征提取与分类识别三个环节并重点介绍了采用卷积神经网络CNN结合center loss损失函数对相位数据进行训练与测试的方法。该研究还通过加入假冒飞机序列号来验证中心损失值的变化从而判断消息真实性实验表明识别精度可达99%左右。该方法能够有效提升辐射源个体识别效率为区分真实与假冒消息提供了可行思路。资源为单份PDF文档共1个文件压缩包大小约1.06MB内容精炼且技术路线清晰适合深度学习、信号处理及电子对抗方向的科研人员快速了解前沿方法。目前已有395人学习下载具有较强的参考价值。1. 为什么ADS-B辐射源个体识别要绕开ICAO地址ADS-B信号本身是明文广播的飞行器每秒钟会向外发送位置、速度、识别码等信息。但真正让电子对抗领域感兴趣的不是这些载荷内容而是信号背后的“发射机指纹”——同一型号、同一批次的两部发射机由于元器件容差、电路布局和温度漂移的差异在脉冲包络和相位调制上会留下细微但稳定的偏差。这个偏差就像人的笔迹可以用来做辐射源个体识别也就是常说的SEISpecific Emitter Identification。传统的SEI依赖专家手工提取暂态特征或稳态特征工程落地时非常依赖经验而且一旦对面换了新设备特征库基本要重做。2017年之后深度学习把这条链路简化成“预处理 自动特征提取 分类”但这里埋着一个特别隐蔽的坑ADS-B的112位消息里含有24位ICAO地址也就是飞机的全球唯一编号。如果用原始IQ数据直接训练网络最省事的做法是直接去读这个编号而不是学习真正的射频指纹。文献里对这种“作弊”有过明确警告网络轻松就能拿到99%的准确率但那只是查表不是识别辐射源。本文要拆的这个方案核心思路就是用相位数据替代IQ数据配合center loss做特征约束把“设备ID依赖”打掉让模型真正学到发射机的非线性物理特性。适合正在做信号识别、电子对抗算法仿真或者想了解center loss在非人脸任务上怎么落地的工程师。2. 从IQ到相位小波去噪与特征提纯2.1 为什么不能直接用IQ数据训练ADS-B 1090ES消息的物理层是脉冲位置调制PPM数据速率1Mbps每条消息由8μs的前导同步码和112位数据块组成总时长120μs。用USRP以8MHz采样每条消息能采到约960个复数点加上同步头一般取1010点。直接把这个IQ序列喂给一维卷积网络在数学上完全可行已经有文献用深度残差网络在实测IQ数据上做到98.3%的识别率。但问题不在精度而在泛化。ICAO地址在每个消息里是固定不变的。如果训练集和测试集来自同一批飞机网络只要学会把ICAO字段的24比特模式记住就能在测试集上表现完美。可一旦出现同一个ICAO地址被不同发射机冒用这在ADS-B里很常见或者换一台接收机重新采集原来的模型立刻失效。因为ICAO是逻辑标识不是物理特征。解决办法有两种一种是用complex-valued neural network让网络自己学会避开欺骗性信息另一种更轻量就是本文采用的——把复数信号转换成相位序列。相位信号对幅度抖动不敏感又保留了调制边沿的时间信息而且去掉了与设备ID强相关的幅度包络迫使网络去学习脉冲边沿和相位轨迹上的细微差异。2.2 小波去噪的参数选择与实现采集到的原始信号里除了ADS-B脉冲还有带外干扰、接收机热噪声和多径残留。直接求相位会把这些噪声映射到相位上形成大量毛刺。常规的FIR低通滤波会平滑掉脉冲边沿而边沿恰恰是辐射源指纹的载体。小波去噪的优势在于信号的小波系数幅度大、集中在少数尺度上噪声的小波系数幅度小且分布均匀。选定一个阈值把低于阈值的系数置零再重构信号就能在保留边沿细节的同时压制噪声。常用的阈值选择有硬阈值和软阈值两种。硬阈值保留系数原值软阈值做收缩。对ADS-B这种瞬态脉冲我一般选软阈值加无偏风险估计rigrsure因为硬阈值容易在边沿附近产生伪吉布斯振荡。小波基选择上db4或sym5比较合适分解层数设为45层。下面是MATLAB侧的预处理示意% 读取USRP采集的mat文件数据为复数IQ load(adsb_iq.mat); % 变量名 iq_data, 维度 Nx1010 wname sym5; level 5; thrType soft; % 逐条消息去噪 denoised zeros(size(iq_data)); for k 1:size(iq_data,1) % 将复数拆分实部虚部分别去噪 re wdenoise(real(iq_data(k,:)), level, ... Wavelet, wname, DenoisingMethod, SURE, ... ThresholdRule, thrType); im wdenoise(imag(iq_data(k,:)), level, ... Wavelet, wname, DenoisingMethod, SURE, ... ThresholdRule, thrType); denoised(k,:) complex(re, im); end % 求相位 phase_data angle(denoised); % 输出范围 [-pi, pi]上面这段代码把每一条ADS-B消息当作独立样本处理因为消息之间没有相位连续性。SUREStein无偏风险估计会自动估计噪声方差不需要手动调阈值。如果你发现去噪后相位曲线仍然很毛糙可以改用minimaxi阈值它更保守能保留更多边沿细节。注意一定先对实部虚部分别去噪再合成而不是对幅度和相位分别去噪否则相位跳变点会被平滑掉。2.3 相位数据的归一化与数据集划分求完相位后得到的是[-π, π]区间上的浮点数序列。直接送入网络会有两个问题一是π和-π在数值上距离很远但物理上它们是同一个角度网络需要额外学习这种环状连续性二是不同接收增益下相位噪声水平不同样本间的尺度不一致。常见的做法是把相位映射到01区间# phase_numpy: shape (N, 1010), 值域 [-pi, pi] phase_normalized (phase_numpy np.pi) / (2 * np.pi)这样每个特征都在[0,1]内配合网络的输入归一化层更稳定。但要注意这种线性映射并没有解决π/-π的跳变问题。如果你发现识别率上不去可以考虑把相位展开phase unwrapping或者把相位变换成[sin(phase), cos(phase)]两个通道输入相当于把角度信息拆成两个连续变量让网络绕过跳变。原文里没有提到这一步但它属于工程上很常见的优化手段。数据集按9:1划分训练测试集时必须保证同一架飞机的所有消息只出现在一个集合里不能把同一条飞机消息的消息拆散否则会造成数据泄漏精度虚高。正确做法是先按飞机ICAO地址分组再按组划分。3. 卷积神经网络与center loss的配合3.1 一维卷积网络结构解析论文使用的网络结构非常精简输入是一条长度为1010的相位序列原文说采样长度1010卷积后特征图尺寸随之变化。网络分两部分特征提取部分是三个相同的卷积单元每个单元由Conv1dReLUMaxPool1d构成分类部分是三个全连接层。下面是PyTorch的复现代码import torch import torch.nn as nn class ADSBSELossNet(nn.Module): def __init__(self, num_classes260): super().__init__() self.features nn.Sequential( # 输入 (batch, 1, 1010) nn.Conv1d(1, 16, kernel_size8, stride1, padding4), nn.ReLU(), nn.MaxPool1d(2), # 长度 505 nn.Conv1d(16, 32, kernel_size8, stride1, padding4), nn.ReLU(), nn.MaxPool1d(2), # 长度 253 nn.Conv1d(32, 64, kernel_size8, stride1, padding4), nn.ReLU(), nn.MaxPool1d(2), # 长度 127 ) self.fc nn.Sequential( nn.Linear(64 * 127, 120), nn.ReLU(), nn.Linear(120, 80), nn.ReLU(), nn.Linear(80, num_classes) ) def forward(self, x): feat self.features(x) feat feat.view(feat.size(0), -1) logits self.fc(feat) return logits, feat # feat 用于计算 center loss原文给出的线性层是Linear(64*120, 120)说明输入长度可能是120的倍数。上面代码在padding4时第三层池化后长度是127和原文不完全一致。实际使用时需要根据你的采样点数调整padding或去掉padding保证全连接层输入维度匹配。一个更稳妥的做法是在全连接层前加一个AdaptiveAvgPool1d(1)把特征图压缩成固定长度这样输入长度变化也不影响模型结构。不过原文为了保留时序位置信息用的是固定长度。卷积核大小8对应8μs脉冲内的采样点数设置padding4是为了保持长度不变池化核2则是常规的下采样策略。3.2 center loss为什么能提点center loss首次出现在ECCV 2016的人脸识别论文目的是让同一类别的特征向量在欧氏空间里向一个可学习的“类中心”聚拢。分类用的交叉熵损失只要求特征线性可分不要求类内紧凑。对于260类飞机来说类内不紧凑意味着不同飞机之间的特征分布可能重叠边界模糊。center loss的公式是L_center 0.5 * mean(|| x_i - c_{y_i} ||^2)其中x_i是全连接层之前即最后一层ReLU输出的特征的第i个样本特征c_{y_i}是第y_i类的特征中心。这个中心不是预先统计的而是在训练过程中通过梯度下降不断更新的。更新规则里有个指示函数δ(condition)只有当样本的类别标签j等于当前中心所属类别时该中心才更新否则保持不变。这样每个batch里每个类别的中心只会被本类样本拉动类内方差被逐步压缩。配合center loss后总损失变成L L_softmax λ * L_centerλ是权衡系数原文没有给出具体值但这类任务里通常取0.0010.01。太小起不到约束作用太大会把特征全部压成一个点导致softmax无法区分类别。我是这么调λ的先只用softmax训练20个epoch看基线准确率然后加上center lossλ从0.0001开始逐步增大观察验证集准确率和TSNE可视化。如果特征点云过度收缩就调小λ。3.3 训练超参数与优化器选择原文训练设置batch size为512epoch为150学习率0.001优化器Adam。这个配置对中等规模数据集是合理的。相位数据经过预处理后已经比较平滑不需要太大的batch size512在单张消费级GPU如RTX 2080Ti上没有问题。学习率0.001是Adam的默认推荐值但如果loss在早期震荡可以降到0.0005。Adam对中心更新和分类层共享学习率时可能会出现center loss收敛过快或过慢的问题常见的做法是给center loss相关的参数单独设置学习率比主网络小一个数量级。PyTorch里可以用多个param_group实现optimizer torch.optim.Adam([ {params: model.parameters()}, {params: center_loss.parameters(), lr: 0.0001} ], lr0.001)注意center loss中的类中心c也是可学习参数需要用nn.Parameter注册。更新公式里那个分母1δ是为了防止除零实现时直接按batch内每个类别的样本数量做平均即可。4. 训练实验复现从数据准备到精度对比4.1 数据采集与mat文件解析原文的实验环境是Windows 10上用MATLAB R2018a接收USRP数据Linux服务器上用PyTorch训练。工程上推荐用Python统一处理但如果你手里已经有mat文件用scipy.io.loadmat解析即可。USRP的采样率设为8MHz采样长度1010外场采集了260架飞机的ADS-B信号。这里有个容易被忽略的问题ADS-B信号在空中是随机到达的你用USRP连续采流得到的不是一条条对齐的消息而是一段包含多个脉冲的原始流。必须先用脉冲检测和同步算法把每条消息截出来再做下采样到1010点。原文没有展开这一步但它直接决定了后续识别的上限。我一般用能量检测加相关同步先计算滑动窗口能量找到超过噪声底限3dB以上的脉冲起点再用本地8μs前导码模板做互相关确定精确的符号边界。4.2 完整训练脚本骨架数据准备好后训练流程如下import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset # phase_data: (N, 1010) 已归一化label: (N,) dataset TensorDataset(phase_data.unsqueeze(1), label) loader DataLoader(dataset, batch_size512, shuffleTrue, num_workers4) model ADSBSELossNet(num_classes260) center_criterion CenterLoss(num_classes260, feat_dim64 * 127) # 需要实现 ce_criterion nn.CrossEntropyLoss() lambda_c 0.005 optimizer torch.optim.Adam([ {params: model.parameters()}, {params: center_criterion.parameters(), lr: 0.0001} ], lr0.001) for epoch in range(150): model.train() for x, y in loader: logits, feat model(x) loss_ce ce_criterion(logits, y) loss_center center_criterion(feat, y) loss loss_ce lambda_c * loss_center optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 10 0: print(fepoch {epoch} | ce {loss_ce.item():.4f} | center {loss_center.item():.4f})CenterLoss类需要自己实现核心逻辑如下class CenterLoss(nn.Module): def __init__(self, num_classes, feat_dim): super().__init__() self.centers nn.Parameter(torch.randn(num_classes, feat_dim)) def forward(self, x, labels): # x: (batch, feat_dim), labels: (batch,) batch_size x.size(0) centers_batch self.centers[labels] # 取出每个样本的类中心 diff x - centers_batch loss 0.5 * (diff.pow(2).sum(1)).mean() return loss上面代码里直接用self.centers[labels]进行索引批量取中心。PyTorch的nn.Parameter会自动参与梯度更新不需要手动实现公式(4)和(5)里的偏导数。但要注意原论文里center是每个batch按类平均更新的而上面的实现是标准center loss的简化版梯度经由diff回传到中心时只有被索引到的中心会得到梯度效果等价。4.3 有无center loss的精度对比与可视化原文的对比实验非常关键不加center loss时150个epoch后识别率约97%加了之后约99%提升约2个百分点。这个提升在SEI任务里不算小因为ADS-B信号的信噪比通常较高但260类分类的边界仍然拥挤。图5的TSNE可视化能看出明显区别加了center loss的260类样本点云更稀疏类间间隔更大没加的时候很多类黏在一起。在复现时建议把TSNE可视化也作为训练流程的一部分。取训练完成后模型的全连接层之前的输出特征随机抽每类20个样本用sklearn.manifold.TSNE降维到2维并绘制散点图。这一步能快速验证模型到底是在“认指纹”还是在“认ID”——如果可视化里同一飞机的消息聚成几个不连续的小簇说明模型可能还在依赖消息里的某种固定模式需要检查预处理。4.4 一个容易踩的坑类不平衡260架飞机在空中的消息数量差异很大。繁忙的商用飞机每秒可能发6.2条消息而通用航空飞机可能几分钟才发一条。如果直接按消息数量划分训练集少数类样本可能只有几十条根本训练不出稳定的类中心。我处理这类数据会先做样本数统计对样本量小于100的类别做过采样或者对样本量过大的类别做降采样保证每个飞机的训练样本量在2001000之间。原文没有提这一点但从1364220条消息除以260类来估算平均每类约5247条实际分布可能更均衡或者他们有意筛选了数据。复现时务必检查。5. 用center loss门限检测假冒ADS-B消息5.1 门限值设定的依据训练完成后的模型不光能分类还能用来做异常检测。思路是这样正常情况下真实飞机的消息经过特征提取后会落在该类别的特征中心附近center loss值很小如果某人伪造了一个消息把ICAO地址写成“7813BA”但射频前端不是那架飞机的发射机那么相位特征就会偏离真实飞机的特征分布center loss值会明显偏大。原文用整个数据集统计了center loss的分布在1364220条真实消息中center loss 0.02的比例约为2.74%在测试集的423467条消息中这个比例是2.81%。两个比例几乎一致说明0.02可以作为一个稳定的门限。用这个门限去检测1000条伪造消息检出异常的比例约93%也就是说能正确拒绝93%的假冒消息。这个方法的本质是把“辐射源个体识别”从单一的分类问题扩展成“分类 置信度”问题。工程实现上不需要为每个已知类别单独训练一个二分类器只需要保存训练阶段每个类别的特征中心即center loss中的centers参数在推理时计算输入特征与预测类别的中心距离。5.2 推理时的检测代码在PyTorch里实现这个检测逻辑很简单def detect_spoof(model, center_loss, sample, threshold0.02): model.eval() with torch.no_grad(): logits, feat model(sample) # feat: (1, feat_dim) pred logits.argmax(dim1).item() center center_loss.centers[pred] # 对应类中心 dist torch.norm(feat[0] - center, p2).item() return pred, dist, dist threshold # True 表示异常 # 加载一条待检测消息的相位数据shape (1,1,1010) # is_spoof detect_spoof(model, center_loss, test_sample)[2]代码里用了二范数距离而center loss公式里是平方和再开根所以门限0.02要对应到二范数距离。如果你实现的是原始center loss取平方和均值推理时计算距离时要保持一致否则门限会失效。实际操作中我还会加一个平滑策略连续检测到10条消息中有超过3条异常才判定为假冒。因为单条消息可能受突发干扰影响产生误判。5.3 调门限与误报率的权衡门限值不是固定不变的。接收机不同、采集天气不同相位噪声底都会变化。换环境后重新做一次统计取一小段新环境下的真实数据跑一遍模型画出center loss值的直方图把门限设在高斯分布的95%或99%分位数上。如果误报太频繁就把门限调高代价是漏报率上升。如果追求高检出率可以适当降低门限配合时间维度上的多数表决来抑制误报。另外伪造者如果知道这个检测机制可以尝试用真实相位模板来调制假冒消息这种情况下基于center loss的检测会失效需要引入时频域特征或两次接收的一致性校验。但从目前公开的威胁模型看绝大多数ADS-B造假只是改ICAO和位置字段射频指纹仍然属于原有发射机所以93%的检出率在工程上已经很有价值。本文还有配套的精品资源点击获取