特征提取与表示学习基础

聊到对抗性域适应,核心绕不开一个东西——特征提取。

说直白点,我们做域适应的最终目标,就是让模型学到一个“不管数据来自哪个域,都能被统一对待”的特征空间。这就是表示学习的本质。我个人这些年做项目最大的感受就是:特征提取器的设计,基本决定了你域适应任务的上限。那些花里胡哨的对抗策略,本质上都是在想方设法帮特征提取器找到那条“跨域共性”的中间路。

先从“什么是好特征”说起

好特征的要素其实不多,就三个:判别性、不变性、紧凑性。

  • 判别性:特征得能区分不同的类别。比如猫和狗,特征空间里的距离要够远。
  • 不变性:同一个物体,换了光照、旋转、或者换了相机拍,特征不能差太多。
  • 紧凑性:同类特征在空间里要尽量聚在一起,不要散得到处都是。

我在一个工业缺陷检测项目里遇到过这样的场景:同一个划痕缺陷,因为光照角度不同,特征分布竟然完全错开了。嗯,那时候我才真正意识到,光靠分类损失,根本压不住这种域偏移。

常用特征提取网络结构

现阶段的特征提取器,基本都绕不开这几类。我挑几个最常用的跟你说说。

结构类型 代表网络 特点 适用场景
VGG 系列 VGG16/19 结构简单,堆叠小卷积核 实验基准、特征可视化
ResNet 系列 ResNet50/101 残差连接,解决梯度消失 大多数域适应任务的首选
DenseNet DenseNet121 密集连接,特征复用充分 细粒度分类、小样本场景
Transformer ViT-B/16 自注意力,全局感受野 需要大范围依赖关系的任务

我个人最常选 ResNet50。它既不轻到欠拟合,也不重到训不动。如果你刚开始接触域适应,我建议就从 ResNet50 起步,跑通之后再去换其他骨干。

表示学习的核心——损失函数

特征提取器本身是个黑盒子,我们靠损失函数去控制它学什么。
在域适应里,除了常见的交叉熵损失,还有几个非常关键的东西。

核心要点:域适应的损失通常是“分类损失 + 域判别损失 + 特征约束损失”的组合。你拆开来看,每部分都有明确的目标。

对比损失

这个在自监督学习里很常见。核心思想是拉近同类样本,推开异类样本。代码实现也很直接:

def contrastive_loss(anchor, positive, negative, margin=1.0):
    pos_dist = torch.norm(anchor - positive, dim=1)
    neg_dist = torch.norm(anchor - negative, dim=1)
    loss = torch.relu(pos_dist - neg_dist + margin)
    return loss.mean()

你看,这个损失会让模型学会“什么东西是相似的”。这正好就是特征不变性需要的底层能力。

三元组损失

对比损失的升级版。它不再是简单的一对一,而是用一个三元组(anchor, positive, negative)去优化。
曾经我在行人重识别任务里用三元组损失,效果比交叉熵好了不少。

def triplet_loss(anchor, positive, negative, margin=0.3):
    pos_dist = torch.norm(anchor - positive, dim=1)
    neg_dist = torch.norm(anchor - negative, dim=1)
    loss = torch.relu(pos_dist - neg_dist + margin)
    return loss.mean()

为什么有效?因为它在比较中学习,这正是跨域匹配时需要的特性。

中心损失

很多人忽略了它,但我认为中心损失在域适应里非常实用。它的目标是让每个类别的特征都有一个中心点,然后把同类特征向中心拉。

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, features, labels):
        batch_size = features.size(0)
        centers_batch = self.centers[labels]
        loss = (features - centers_batch).pow(2).sum() / batch_size
        return loss

在跨域场景中,中心损失帮你锚定每个类在特征空间中的位置,即使不同域的样本混在一起,也有个“坐标”可以参考。

特征提取的评价指标

特征好不好,不能光靠看loss曲线。我一般会看几个东西:

  • T-SNE 可视化:把高维特征压到二维,看一眼就知道域偏移的程度。
  • 类内距离 / 类间距离:量化指标,同类特征的平均距离和不同类特征的平均距离。
  • 迁移能力评估:用源域训练的特征提取器,直接在目标域上跑分类,看精度。

小技巧:每次改进特征提取器后,先把特征存成 .npy 文件,然后在可视化工具里反复对比。别偷懒这个步骤,我发现很多问题的根因就是特征提取器没学好。

注意:特征提取器的最后一层输出维度不要设得太小。我踩过坑,把维度压到16,结果信息损失太大,域适应再怎么学也补不回来。一般建议128~512之间。

核心知识体系

下面这张图是我自己梳理的本章知识逻辑,你看着应该就能串起来整个思路。

特征提取与表示学习知识体系 原始数据(源域/目标域) 特征提取网络(ResNet/ViT) 分类损失(CrossEntropy) 域判别损失(GAN/对抗) 特征约束(对比/中心) 联合优化(梯度反向传播) 域不变特征表示 三个分支 共同控制 特征学习方向

从这张图你可以看出来,整个特征提取的过程,其实就是在多个损失之间找一个平衡点。分类损失让你不忘记任务是啥,域判别损失让你不偏向某个特定域,特征约束损失让你的特征分布更规整。三管齐下,才能生成真正可用的域不变特征。

实战中的几个关键认知

光说理论没意思,我讲几个实战里摸出来的认知。

第一,不要过早冻结特征提取器。
我见过不少同学一上来就把预训练的特征提取器冻住,只训练后面的分类器。这在源域上可能还行,但在目标域上基本废了。特征提取器必须参与域适应的对抗训练,否则它学不到跨域的共性。

第二,特征空间的维度要留余量。
我曾经试过把维度降到64以内,想着能加速训练。结果特征表达能力不够,域适应的精度掉了8个点。后来我习惯设256维,既不浪费太多显存,也能保留足够的表达能力。

第三,别忘了加 Batch Normalization。
这个真的是一点小小的改动就能带来很大提升。BN能让特征的分布更稳定,尤其是在跨域场景里,不同域的统计数据可能差异很大,BN正好能做一层隐式的对齐。

一句话总结:特征提取是域适应的地基。地基不牢,上面再花哨的对抗策略都是白搭。把结构选好、损失配好、维度设好,这部分的功夫花得值。