雷达抗干扰|−6 dB 下 90.45% 准确率:MIMO 雷达抗欺骗干扰,小波散射 + 原型网络能走多远【附代码链接】

基于双树复小波散射与原型网络的 MIMO 雷达欺骗干扰识别与抑制实现

原文链接EW Frontier

复制代码
环境:Python 3.9 + PyTorch 2.0 + pytorch-wavelets 1.0 + NumPy 1.24 + scikit-learn 1.3
关键词:DRFM 欺骗干扰;DTCWT 散射;CBAM 注意力;监督对比学习;原型网络;时域滑窗抑制
摘要:给出一套完整的 MIMO 雷达欺骗干扰识别实现:复数回波数据加载与双通道化、DTCWT 散射特征提取、CBAM 注意力重加权、预训练骨干微调、监督对比学习训练、原型网络小样本分类,以及基于时域滑窗的概率累积干扰抑制。每个关键公式后紧跟实现,并给出完整的对比、消融与分布实验结果表。

一、任务与数据

11 类信号:点目标 + RDFJ、VDFJ、RVDJ、ISFJ、ISRJ、CSJ、SSJ(密集假目标类)+ RGJ、VGJ、RVGJ(拖引类)。

参数 训练集 测试集
载频 10 GHz 8 ~ 12 GHz
带宽 40 MHz 20 ~ 60 MHz
采样率 48 MHz 68 MHz
脉宽 1 μs 1 ~ 10 μs
PRI 5 μs 5 ~ 50 μs
SNR / INR −6 ~ 10 dB −10 ~ 15 dB
CNR --- −10 ~ 10 dB
点目标数 1 1 ~ 3

输入为脉冲压缩之前 的快-慢时间复矩阵 Z ∈ C Q × L \mathbf{Z} \in \mathbb{C}^{Q \times L} Z∈CQ×L,本实验为 C 128 × 241 \mathbb{C}^{128 \times 241} C128×241,每类 1000 个训练样本。

为什么不用匹配滤波后的数据 :时域正交 MIMO 雷达为了获得虚拟阵列增益必须用匹配滤波分离发射通道,但这会破坏回波时域波形结构,使假目标呈现出与真目标相同的时域尖峰,直接导致虚警。


二、数据加载:复数矩阵的双通道化

雷达回波是复数,网络需要实数输入。标准做法是把实部与虚部拼成两个通道:

python 复制代码
import numpy as np, scipy.io as sio, h5py, torch

def load_complex_mat(path):
    """兼容 v7 / v7.3 两种 .mat 格式,返回复数组"""
    try:
        mat = sio.loadmat(path)
        keys = [k for k in mat.keys() if not k.startswith("__")]
        return mat[keys[0]]
    except NotImplementedError:                      # v7.3 需走 HDF5
        with h5py.File(path, "r") as f:
            return np.array(f[list(f.keys())[0]]).T

def complex_to_2ch(data_np):
    """复数回波 -> 双通道实张量 (B, 2, Q, L)"""
    if np.iscomplexobj(data_np):
        real, imag = data_np.real, data_np.imag
    else:                                            # MATLAB struct 形式
        real, imag = data_np["real"], data_np["imag"]
    if real.ndim == 3:                               # (B, Q, L) -> (B, 1, Q, L)
        real, imag = real[:, None], imag[:, None]
    x = np.concatenate([real, imag], axis=1)         # (B, 2, Q, L)
    return torch.tensor(x, dtype=torch.float32)

踩坑 :scipy.io.loadmat 读 v7.3 会抛 NotImplementedError,必须用 h5py;且 HDF5 读出的数组维度顺序与 MATLAB 相反,需要转置。判断条件是 data.dtype == np.dtype([('real','<f8'),('imag','<f8')])。


三、编码器:DTCWT 散射 + 注意力 + 预训练骨干

散射算子由小波变换与取模级联:

S λ Z = ∣ Z ∗ ψ λ ∣ ∗ ϕ J , S p Z = ∣ ∣ ∣ Z ∗ ψ λ 1 ∣ ∗ ψ λ 2 ∣ ⋯ ψ λ m ∣ ∗ ϕ J S\\lambda\mathbf{Z} = \big|\mathbf{Z} * \psi_{\lambda}\big| * \phi_J, \quad Sp\mathbf{Z} = \Big|\big|\big|\mathbf{Z} * \psi_{\lambda_1}\big| * \psi_{\lambda_2}\big| \cdots \psi_{\lambda_m}\Big| * \phi_J SλZ= Z∗ψλ ∗ϕJ,SpZ= Z∗ψλ1 ∗ψλ2 ⋯ψλm ∗ϕJ

(1)

python 复制代码
import torch.nn as nn
from pytorch_wavelets import ScatLayerj2
from fightingcv_attention.attention.CBAM import CBAMBlock
from torchvision import models

class Encoder(nn.Module):
    """DTCWT 散射 -> CBAM 注意力 -> 通道压缩 -> 预训练骨干"""
    def __init__(self, size=(224, 224), backbone="efficientnet_v2", device="cuda"):
        super().__init__()
        self.device, self.target_size = device, size
        self.scat = ScatLayerj2().to(device)         # 无可学习参数
        self._init_backbone(backbone)
        self.to(device)

    def _init_backbone(self, name):
        if name == "efficientnet_v2":
            m = models.efficientnet_v2_s(weights=models.EfficientNet_V2_S_Weights.IMAGENET1K_V1)
            self.dim = m.classifier[-1].in_features
            m.classifier[-1] = nn.Identity()
        elif name == "resnet50":
            m = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
            self.dim = m.fc.in_features
            m.fc = nn.Identity()
        else:
            raise ValueError(name)
        self.backbone = m.to(self.device)

    def _build_heads(self, z, att):
        """散射/注意力输出通道数依赖输入尺寸,需在 forward 中惰性构建"""
        if not hasattr(self, "att"):
            c = z.shape[1]
            self.att = CBAMBlock(channel=c, reduction=max(1, c // 16 if c > 64 else 8),
                                 kernel_size=7).to(self.device)
        if not hasattr(self, "reducer"):
            c = att.shape[1]
            self.reducer = nn.Sequential(
                nn.Conv2d(c, 32, 3, padding="same"), nn.BatchNorm2d(32), nn.ReLU(True),
                nn.Conv2d(32, 3, 3, padding="same"), nn.BatchNorm2d(3), nn.ReLU(True),
            ).to(self.device)                        # 降到 3 通道以匹配 ImageNet 预训练输入

    def forward(self, x):
        z = self.scat(x)
        self._build_heads(z, z)
        f = self.reducer(self.att(z))
        if f.shape[2:] != self.target_size:
            f = nn.functional.adaptive_avg_pool2d(f, self.target_size)
        out = self.backbone(f)
        return out.flatten(1) if out.ndim == 4 else out

两个必须注意的实现细节 :其一,散射层输出通道数由输入尺寸决定,注意力的 reduction 必须据此计算,写死会报错;其二,必须把散射特征降到 3 通道 后才能喂给 ImageNet 预训练骨干,这是迁移学习能生效的前提。

为什么用 DTCWT 而不是普通 DWT。 两条理由:其一是近似平移不变性 ,传统 DWT 对输入平移极其敏感,平移一个采样点就可能使系数完全改变,而 DTCWT 用两棵树分别构造复小波的实部与虚部 ψ ~ = ψ ( r ) + j ψ ( i ) \tilde{\psi} = \psi_{(r)} + j\psi_{(i)} ψ~=ψ(r)+jψ(i),二者构成正交 Hilbert 对,显著缓解该问题;其二是散射层无可学习参数,滤波器预先选定,因此不会在小样本下过拟合,对噪声与形变天然稳定。

需要明确的是散射层的定位:它提供的是稳定的物理表征,不是判别器。t-SNE 可视化显示,仅经过 DTCWT 后各类特征仍是大量重叠的散点云,单独用它分类不可行;注意力模块的改善也很有限(去掉它 −6 dB 从 52.44% 到 42.18%),真正把特征空间分开的是预训练骨干。

骨干可替换为 ResNet50、ConvNeXtV2、ShuffleNet、ResNeXt、MobileNetV3。


四、监督对比学习

L = ∑ i ∈ I − 1 ∣ P ( i ) ∣ ∑ p ∈ P ( i ) log ⁡ exp ⁡ ( α i T α p / τ ) ∑ b ∈ A ( i ) exp ⁡ ( α i T α b / τ ) \mathcal{L} = \sum_{i \in I} \frac{-1}{|P(i)|} \sum_{p \in P(i)} \log \frac{\exp\big(\boldsymbol{\alpha}_i^{\mathrm{T}}\boldsymbol{\alpha}p / \tau\big)}{\sum{b \in A(i)} \exp\big(\boldsymbol{\alpha}_i^{\mathrm{T}}\boldsymbol{\alpha}_b / \tau\big)} L=i∈I∑∣P(i)∣−1p∈P(i)∑log∑b∈A(i)exp(αiTαb/τ)exp(αiTαp/τ)

(2)

数据增强只用两种,且与物理场景对应:加高斯噪声 (噪声不确定性)与随机水平平移(时延不确定性)。

python 复制代码
import torch, torch.nn.functional as F

def augment(x, rng, noise_std=0.05, max_shift=0.1):
    """两种增强:加性高斯噪声 + 随机水平平移"""
    x = x + noise_std * torch.randn_like(x)
    k = int(max_shift * x.shape[-1])
    if k > 0:
        s = rng.integers(-k, k + 1)
        x = torch.roll(x, shifts=int(s), dims=-1)
    return x

def supcon_loss(feat, labels, tau=0.07):
    """式(2):feat 已归一化,shape (3B, D);labels shape (3B,)"""
    feat = F.normalize(feat, dim=1)
    sim = feat @ feat.T / tau                        # (3B, 3B)
    self_mask = torch.eye(sim.size(0), device=sim.device).bool()
    sim.masked_fill_(self_mask, -1e9)
    pos = (labels.unsqueeze(0) == labels.unsqueeze(1)) & (~self_mask)
    log_p = sim - torch.logsumexp(sim, dim=1, keepdim=True)
    return -(log_p * pos).sum(1) / pos.sum(1).clamp(min=1)

默认超参:batch size = 64,epochs = 50,学习率 0.0005,Adam 优化器,单卡 RTX 4080Ti / 32 GB。


五、原型网络分类

α k = 1 ∣ S k ∣ ∑ Z ∈ S k g ρ ( Z ) , c t ∗ = arg ⁡ min ⁡ k ∥ β t − α k ∥ 2 \boldsymbol{\alpha}k = \frac{1}{|S_k|}\sum{\mathbf{Z} \in S_k} g_{\rho}(\mathbf{Z}), \qquad c_t^* = \arg\min_k \|\boldsymbol{\beta}_t - \boldsymbol{\alpha}_k\|^2 αk=∣Sk∣1Z∈Sk∑gρ(Z),ct∗=argkmin∥βt−αk∥2

(3)

python 复制代码
@torch.no_grad()
def build_prototypes(encoder, loader, n_cls, dim, device):
    """式(3):支持集按类求均值。新增类别无需重训,只需重算原型"""
    encoder.eval()
    proto = torch.zeros(n_cls, dim, device=device)
    cnt = torch.zeros(n_cls, device=device)
    for x, y in loader:
        f = encoder(x.to(device))
        for c in torch.unique(y):
            m = (y.to(device) == c)
            proto[c] += f[m].sum(0); cnt[c] += m.sum()
    return proto / cnt.clamp(min=1).unsqueeze(1)

@torch.no_grad()
def predict(encoder, x, proto):
    f = encoder(x)
    logits = -(torch.cdist(f, proto) ** 2)
    return logits.argmax(1), logits.softmax(1)

六、实验结果

6.1 方法对比(均以 SNR = 10 dB 数据训练,支持集相同)

方法 −6 dB −3 dB 0 dB 10 dB 参数量 推理时间
Prior-K-NN 13.76% 20.16% 30.17% 99.11% 20 M 0.18 s
MS-WSN 18.06% 19.15% 22.97% 99.21% 5.4 M 0.05 s
PSPNet 38.55% 40.55% 57.31% 99.21% 0.202 M 0.0006 s
AWSPNet(EfficientNetV2) 52.44% 75.25% 93.56% 99.91% 24 M 0.19 s
AWSPNet(ResNet50) 45.32% 72.54% 91.91% 99.91% 25.5 M 0.20 s

10 dB 上所有方法都接近满分,区分度全在低 SNR 区 。PSPNet 以 0.202 M 参数做到 38.55%,是轻量方案里性价比最高的。

6.2 消融

配置 −6 dB −3 dB 0 dB 10 dB 推理时间
完整模型 52.44% 75.25% 93.56% 99.91% 0.19 s
去掉 DTCWT 42.12% 72.31% 92.01% 99.01% 0.16 s
去掉注意力 42.18% 72.14% 93.06% 99.91% 0.19 s
去掉预训练权重 15.95% 24.13% 37% 93.45% 0.20 s

去掉预训练权重是灾难性的:−6 dB 掉到 15.95%,0 dB 仅 37%,接近随机猜测。迁移学习是这个方案的承重墙,不是装饰。

6.3 训练集 SNR 分布的影响(最关键的实验)

训练 / 支持集配置 −6 dB −3 dB 0 dB 10 dB
多 SNR 混合训练 90.45% 94.33% 95.78% 95.01%
仅 −6 dB 数据训练 82.42% 61.91% 55.95% 50.11%
10 dB 训练 + 多 SNR 支持集 42.44% 55.15% 91.49% 99.11%

三条实践结论:多 SNR 混合训练是唯一在全体 SNR 都稳的配置;只用低 SNR 训练会严重偏科(−6 dB 82.42% 但 10 dB 仅 50.11%,噪声形态被当成判别特征);换支持集救不了训练集分布错配(42.44%)。

为什么支持集补偿不了训练集。 原型网络确实有小样本适应能力,但它适应的前提是编码器已经把同类样本映射到邻近区域。若编码器在训练阶段从未见过 −6 dB 的噪声形态,它输出的 −6 dB 样本特征本身就是散乱的,此时无论用哪个分布的支持集去求均值,得到的原型都不具备判别力。一句话:原型网络能适应"新类别",不能适应"新分布",这两件事在实践中经常被混为一谈,部署时应该把"支持集维护"与"编码器重训"当作两个独立流程管理。

另外值得注意的读法:多 SNR 混合训练在 10 dB 上从 99.91% 降到 95.01%,看起来是退步,但换来 −6 dB 从 52.44% 到 90.45%。在真实系统里高 SNR 样本本来就容易分,这笔交易是划算的。


七、时域滑窗抑制

分类只是手段。实用流程:按快时间滑窗,输出目标类概率序列,再用长度等于脉宽的矩形函数卷积累积,峰值即目标距离单元。

python 复制代码
@torch.no_grad()
def slide_suppress(encoder, Z, proto, L0, pulse_len, target_cls=0):
    """Z: (Q, L) 复回波;返回沿快时间的目标类概率与累积能量"""
    probs = []
    for t in range(Z.shape[1] - L0 + 1):
        win = Z[:, t:t + L0]                         # 滑窗
        x = torch.view_as_real(torch.as_tensor(win)).permute(2, 0, 1).unsqueeze(0)
        _, p = predict(encoder, x.to(next(encoder.parameters()).device), proto)
        probs.append(p[0, target_cls].item())
    probs = np.asarray(probs)
    kernel = np.ones(pulse_len) / pulse_len          # 长度 = 脉宽的矩形函数
    energy = np.convolve(probs, kernel, mode="same")
    return probs, energy

关键在于连续性判据:干扰位置偶尔出现概率为 1.0 的孤立尖峰,但不连续,累积后形不成足够能量;真目标的概率序列是连续的一整段(长度约等于脉宽),累积后呈平顶梯形峰。实测在两个干扰源并存、CNR = 10 dB 下均无虚警目标。

另外,模型训练时完全未见过杂波,但在 CNR = 10 dB 下仍能正确定位目标 ------ 目标与杂波的时频特性差异足够大,编码器把它们映射到了特征空间中相距较远的位置。

窗长与卷积核怎么选。 滑窗长度 L 0 L_0 L0 应覆盖一个完整的脉宽,太短则目标概率序列不连续、太长则距离分辨率下降;卷积核长度取脉宽对应的采样点数即可,它的作用是"概率累积"而非平滑。若脉宽在测试集上是变化的(本实验 1 ~ 10 μs),建议按实际脉宽动态调整核长,或取训练集脉宽对应的核长并接受一定的失配。

t-SNE 选型启发式。 对最终特征做 t-SNE 可视化时,注意观察簇的形态:EfficientNetV2、ConvNeXtV2、MobileNetV3 呈团状聚集,ResNet50、ResNeXt 呈条状线性,而前者在低 SNR 上明显更稳(52.44% vs 45.32%)。在没有低 SNR 测试集的情况下,看簇形态即可粗略判断哪个骨干更抗噪。


八、常见问题 FAQ

Q1:CBAM 报通道数不匹配?

散射层输出通道数由输入尺寸决定。注意力的 reduction 必须按实际通道数算(c//16 if c>64 else 8),建议在 forward 里惰性构建而不是写死在 __init__。

Q2:预训练骨干完全不收敛?

检查是否把散射特征降到了 3 通道。ImageNet 预训练权重期望 3 通道输入,直接把几十通道的散射系数喂进去,迁移学习等于废掉。

Q3:低 SNR 性能上不去,是不是模型太小?

先查训练集 SNR 分布。实测仅改训练数据分布,−6 dB 可从 52.44% 到 90.45%,收益超过除迁移学习外的任何结构改动。

Q4:换成多 SNR 支持集有用吗?

对训练集错配无效(42.44%)。原型网络能适应"新类别",不能适应"新分布",这两件事要分开管理。

Q5:加载 .mat 报 NotImplementedError?

v7.3 格式,用 h5py 读并转置。


九、可扩展方向

  1. SNR 解耦表征:仅用低 SNR 训练会导致噪声形态被当成判别特征,可引入 SNR 条件归一化或分层域适应。
  2. 未知干扰检测:原型网络对训练集外类别仍会强行归类,需要开集识别(open-set)机制给出"未知"判定。
  3. 实时化:24 M 参数、0.19 s 单帧推理,需量化(INT8)与算子优化才能满足实时约束。
  4. 实测数据验证:当前为仿真数据,真实 DRFM 的量化失真与相位噪声未完整建模。

十、总结

这套方案的价值不在于堆叠模块,而在于分工明确:DTCWT 提供稳定的物理表征,预训练骨干提供可分性(去掉它 −6 dB 掉到 15.95%),原型网络提供类别扩展性。而最有工程价值的一条经验不是结构性的 ------ 训练数据的 SNR 分布决定低 SNR 性能的天花板,换支持集无法补偿训练集分布错配。

本文对应的完整实现(含数据加载、DTCWT 散射编码器、多种可切换骨干、监督对比学习训练、原型网络分类、t-SNE 可视化、消融与时域滑窗抑制脚本)已整理打包,需要的同学可以评论区留言或私信,我会统一回复;也欢迎在评论区交流你在实现中遇到的问题。


参考文献

  1. Jia Y, Xiao S, Jia W, et al. AWSPNet: Attention-based dual-tree wavelet scattering prototypical network for MIMO radar target recognition and jamming suppressionJ. arXiv:2510.18422, 2025.
  2. Selesnick I W, Baraniuk R G, Kingsbury N C. The dual-tree complex wavelet transformJ. IEEE Signal Processing Magazine, 2005, 22(6): 123-151.
  3. Bruna J, Mallat S. Invariant scattering convolution networksJ. IEEE Transactions on PAMI, 2013, 35(8): 1872-1886.
  4. Khosla P, et al. Supervised contrastive learningC. NeurIPS, 2020, 33: 18661-18673.
  5. Woo S, et al. ConvNeXt V2: Co-designing and scaling ConvNets with masked autoencodersC. CVPR, 2023: 16133-16142.
相关推荐
EW Frontier10 个月前
【抗干扰】雷达抗ISRJ干扰新路径:发射波形与非匹配滤波联合设计详解【附MATLAB代码】
雷达抗干扰·isrj·间歇采样重复转式干扰