简介:一份面向电子对抗与航空监视领域研究者的深度学习技术文献,聚焦ADS-B辐射源个体识别这一关键问题。文档系统梳理了辐射源指纹识别的基本框架,包括预处理、特征提取与分类识别三个环节,并重点介绍了采用卷积神经网络(CNN)结合center loss损失函数对相位数据进行训练与测试的方法。该研究还通过加入假冒飞机序列号来验证中心损失值的变化,从而判断消息真实性,实验表明识别精度可达99%左右。该方法能够有效提升辐射源个体识别效率,为区分真实与假冒消息提供了可行思路。资源为单份PDF文档,共1个文件,压缩包大小约1.06MB,内容精炼且技术路线清晰,适合深度学习、信号处理及电子对抗方向的科研人员快速了解前沿方法。目前已有395人学习下载,具有较强的参考价值。
1. 为什么ADS-B辐射源个体识别要绕开ICAO地址
ADS-B信号本身是明文广播的,飞行器每秒钟会向外发送位置、速度、识别码等信息。但真正让电子对抗领域感兴趣的,不是这些载荷内容,而是信号背后的“发射机指纹”——同一型号、同一批次的两部发射机,由于元器件容差、电路布局和温度漂移的差异,在脉冲包络和相位调制上会留下细微但稳定的偏差。这个偏差就像人的笔迹,可以用来做辐射源个体识别,也就是常说的SEI(Specific 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比较合适,分解层数设为4~5层。下面是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消息当作独立样本处理,因为消息之间没有相位连续性。SURE(Stein无偏风险估计)会自动估计噪声方差,不需要手动调阈值。如果你发现去噪后相位曲线仍然很毛糙,可以改用minimaxi阈值,它更保守,能保留更多边沿细节。注意一定先对实部虚部分别去噪再合成,而不是对幅度和相位分别去噪,否则相位跳变点会被平滑掉。
2.3 相位数据的归一化与数据集划分
求完相位后得到的是[-π, π]区间上的浮点数序列。直接送入网络会有两个问题:一是π和-π在数值上距离很远,但物理上它们是同一个角度,网络需要额外学习这种环状连续性;二是不同接收增益下相位噪声水平不同,样本间的尺度不一致。常见的做法是把相位映射到0~1区间:
# 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,卷积后特征图尺寸随之变化)。网络分两部分:特征提取部分是三个相同的卷积单元,每个单元由Conv1d+ReLU+MaxPool1d构成;分类部分是三个全连接层。下面是PyTorch的复现代码:
import torch import torch.nn as nn class ADSBSELossNet(nn.Module): def __init__(self, num_classes=260): super().__init__() self.features = nn.Sequential( # 输入 (batch, 1, 1010) nn.Conv1d(1, 16, kernel_size=8, stride=1, padding=4), nn.ReLU(), nn.MaxPool1d(2), # 长度 505 nn.Conv1d(16, 32, kernel_size=8, stride=1, padding=4), nn.ReLU(), nn.MaxPool1d(2), # 长度 253 nn.Conv1d(32, 64, kernel_size=8, stride=1, padding=4), 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的倍数。上面代码在padding=4时,第三层池化后长度是127,和原文不完全一致。实际使用时需要根据你的采样点数调整padding或去掉padding,保证全连接层输入维度匹配。一个更稳妥的做法是在全连接层前加一个AdaptiveAvgPool1d(1),把特征图压缩成固定长度,这样输入长度变化也不影响模型结构。不过原文为了保留时序位置信息,用的是固定长度。卷积核大小8对应8μs脉冲内的采样点数,设置padding=4是为了保持长度不变,池化核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.001~0.01。太小起不到约束作用,太大会把特征全部压成一个点,导致softmax无法区分类别。我是这么调λ的:先只用softmax训练20个epoch,看基线准确率;然后加上center loss,λ从0.0001开始逐步增大,观察验证集准确率和TSNE可视化。如果特征点云过度收缩,就调小λ。
3.3 训练超参数与优化器选择
原文训练设置:batch size为512,epoch为150,学习率0.001,优化器Adam。这个配置对中等规模数据集是合理的。相位数据经过预处理后已经比较平滑,不需要太大的batch size;512在单张消费级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} ], lr=0.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_size=512, shuffle=True, num_workers=4) model = ADSBSELossNet(num_classes=260) center_criterion = CenterLoss(num_classes=260, feat_dim=64 * 127) # 需要实现 ce_criterion = nn.CrossEntropyLoss() lambda_c = 0.005 optimizer = torch.optim.Adam([ {'params': model.parameters()}, {'params': center_criterion.parameters(), 'lr': 0.0001} ], lr=0.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(f"epoch {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的类别做过采样,或者对样本量过大的类别做降采样,保证每个飞机的训练样本量在200~1000之间。原文没有提这一点,但从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, threshold=0.02): model.eval() with torch.no_grad(): logits, feat = model(sample) # feat: (1, feat_dim) pred = logits.argmax(dim=1).item() center = center_loss.centers[pred] # 对应类中心 dist = torch.norm(feat[0] - center, p=2).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%的检出率在工程上已经很有价值。
本文还有配套的精品资源,点击获取