特征提取与表示学习基础
聊到对抗性域适应,核心绕不开一个东西——特征提取。
说直白点,我们做域适应的最终目标,就是让模型学到一个“不管数据来自哪个域,都能被统一对待”的特征空间。这就是表示学习的本质。我个人这些年做项目最大的感受就是:特征提取器的设计,基本决定了你域适应任务的上限。那些花里胡哨的对抗策略,本质上都是在想方设法帮特征提取器找到那条“跨域共性”的中间路。
先从“什么是好特征”说起
好特征的要素其实不多,就三个:判别性、不变性、紧凑性。
- 判别性:特征得能区分不同的类别。比如猫和狗,特征空间里的距离要够远。
- 不变性:同一个物体,换了光照、旋转、或者换了相机拍,特征不能差太多。
- 紧凑性:同类特征在空间里要尽量聚在一起,不要散得到处都是。
我在一个工业缺陷检测项目里遇到过这样的场景:同一个划痕缺陷,因为光照角度不同,特征分布竟然完全错开了。嗯,那时候我才真正意识到,光靠分类损失,根本压不住这种域偏移。
常用特征提取网络结构
现阶段的特征提取器,基本都绕不开这几类。我挑几个最常用的跟你说说。
| 结构类型 | 代表网络 | 特点 | 适用场景 |
|---|---|---|---|
| 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之间。
核心知识体系
下面这张图是我自己梳理的本章知识逻辑,你看着应该就能串起来整个思路。
从这张图你可以看出来,整个特征提取的过程,其实就是在多个损失之间找一个平衡点。分类损失让你不忘记任务是啥,域判别损失让你不偏向某个特定域,特征约束损失让你的特征分布更规整。三管齐下,才能生成真正可用的域不变特征。
实战中的几个关键认知
光说理论没意思,我讲几个实战里摸出来的认知。
第一,不要过早冻结特征提取器。
我见过不少同学一上来就把预训练的特征提取器冻住,只训练后面的分类器。这在源域上可能还行,但在目标域上基本废了。特征提取器必须参与域适应的对抗训练,否则它学不到跨域的共性。
第二,特征空间的维度要留余量。
我曾经试过把维度降到64以内,想着能加速训练。结果特征表达能力不够,域适应的精度掉了8个点。后来我习惯设256维,既不浪费太多显存,也能保留足够的表达能力。
第三,别忘了加 Batch Normalization。
这个真的是一点小小的改动就能带来很大提升。BN能让特征的分布更稳定,尤其是在跨域场景里,不同域的统计数据可能差异很大,BN正好能做一层隐式的对齐。
一句话总结:特征提取是域适应的地基。地基不牢,上面再花哨的对抗策略都是白搭。把结构选好、损失配好、维度设好,这部分的功夫花得值。