系列 · 迁移学习 · 第 7 篇

迁移学习(七):零样本学习

从第一性原理推导零样本学习:DAP 属性原型、双线性与深度兼容性函数、DeViSE、生成式 ZSL 的 f-CLSWGAN、广义 ZSL 的偏置问题与校准方法,以及 CLIP 这种视觉-语言预训练带来的范式跃迁,附 PyTorch 核心实现。

你从未见过斑马。如果我告诉你它外形似马、身披黑白相间条纹,下次在动物园看到斑马时,你就能一眼认出来——无需标注数据,也无需微调,只需一座语义桥梁,将你已知的概念(如马、条纹)与未知类别(如斑马)关联起来。

零样本学习(Zero-Shot Learning, ZSL) 就是让机器学会这种能力。训练时,模型使用一组带标签的已见类(seen classes) 数据进行监督学习;测试时,却要对一组完全不重叠的未见类(unseen classes) 进行分类——这些类别从未在训练中出现过。模型对未见类的唯一线索,就是它们的语义描述:可以是一组属性、类别名称的词嵌入、一段文字描述,或一条 CLIP 风格的提示语。模型的表现完全取决于它在共享的视觉–语义空间中所学到的几何结构。

本文从一个核心公式出发——图像与类别描述之间的兼容性函数 $F(x, c)$ ——系统梳理过去十五年里出现的主要方法:属性原型、双线性和深度兼容性、生成式特征合成、广义零样本学习的校准策略,以及最终通过 CLIP 风格的大规模预训练,几乎将该领域“消解”为一个独立问题的现代范式。


你将学到什么#

  • ZSL 的问题定义及已见类与未见类的划分
  • 属性表示与直接属性预测(DAP)
  • 兼容性函数:双线性、深度学习及基于排序的方法(DeViSE、ALE)
  • 生成式 ZSL(f-CLSWGAN、f-VAEGAN-D2):如何将 ZSL 转化为有监督学习
  • 为什么广义 ZSL(GZSL) 在没有校准时会崩溃,以及三种有效修复方法
  • CLIP:现代、可扩展的终极答案

前置知识#


问题定义#

$$ \mathcal{C}^s \cap \mathcal{C}^u = \emptyset. $$

每个类别 $c$ (无论已见或未见)都配有一个语义描述向量 $a_c \in \mathbb{R}^M$ 。这是信息从已见类流向未见类的唯一通道。该描述可以是:

  • 二值或连续属性向量(例如 有条纹有翅膀水生);
  • 类别名称的词嵌入(如 Word2Vec、GloVe);
  • 百科式描述的句嵌入(如 BERT 的 [CLS] 向量);
  • 视觉–语言模型中的提示嵌入(如 CLIP)。

任务有两种设定:

设定测试标签空间现实性
经典 ZSL$y \in \mathcal{C}^u$更简单的基准;假设测试图像一定属于未见类。
广义 ZSL(GZSL)$y \in \mathcal{C}^s \cup \mathcal{C}^u$实际部署所需;难度更高,因为模型会强烈偏向已见类。
$$ F: \mathcal{X} \times \mathcal{C} \to \mathbb{R}, \qquad \hat{y} = \arg\max_{c \in \mathcal{C}_{\text{test}}} F(x, c; \theta). $$

我们在已见类上学习 $F$ ,并依赖语义空间的几何结构将其泛化到未见类。

有监督、小样本、零样本三种监督方式对比


属性表示与 DAP#

属性是最具可解释性的语义描述形式。它们是人类定义的简短谓词,如 有条纹四条腿有翅膀水生。广泛使用的 Animals with Attributes 2(AwA2) 数据集为 50 种动物提供了 85 维属性向量;细粒度鸟类识别数据集 CUB-200-2011 则使用了 312 个属性。

$$ a_{\text{斑马}} = (\underbrace{1}_{\text{条纹}}, \underbrace{1}_{\text{四条腿}}, \underbrace{0}_{\text{翅膀}}, \underbrace{1}_{\text{蹄子}}, \ldots). $$

直接属性预测(DAP)#

Lampert 等人(2009)——这篇定义了现代 ZSL 问题的论文——提出了一套清晰的两阶段流程:

$$ \hat{a}_m(x) = P(\text{属性 } m \mid x), \qquad m = 1, \ldots, M. $$ $$ \hat{y} = \arg\min_{c \in \mathcal{C}^u} d\bigl(\hat{a}(x),\, a_c\bigr). $$

举个例子:查询图是一匹斑马。属性分类器在 有条纹四条腿有蹄子有鬃毛 上响应强烈,在 有翅膀水生 上响应微弱。通过余弦相似度与原型矩阵比对,斑马 排名第一,老虎 紧随其后。

基于属性的零样本流程(DAP)

为何有效:属性分类器能迁移,是因为属性在不同类别间是共享的。无论动物是斑马(未见)还是老虎(已见),有条纹 的含义一致。

为何不足

  1. 误差逐级累积。即使单个属性分类器准确率达 90%,预测出的 $\hat{a}(x)$ 仍含噪声,而最近邻搜索会放大这一噪声。
  2. 属性独立性假设。DAP 假设给定类别后属性相互独立,但 有翅膀会飞 显然高度相关。
  3. 标注成本高。定义和标注属性需要专家构建完整的分类体系。

变体 IAP(Indirect Attribute Prediction) 先预测已见类概率,再通过属性矩阵映射,但结构性局限依然存在。


兼容性函数:统一视角#

与其将属性预测作为中间步骤,不如直接端到端学习兼容性函数 $F(x, c)$

双线性(ALE / SJE)#

$$ F(x, c) = \phi(x)^\top W\, a_c, $$ $$ \mathcal{L}(x, y) = -\log \frac{\exp F(x, y)}{\sum_{c \in \mathcal{C}^s} \exp F(x, c)}. $$

Akata 等人的 ALE(2013)和 SJE(2015)改用结构化排序损失——正确类别的得分必须比所有错误类别高出一个间隔。实验表明,这种方法对未见类的泛化能力优于普通 softmax。

深度兼容性 / 双塔#

$$ F(x, c) = \frac{f_v(\phi(x))^\top f_s(a_c)}{\|f_v(\phi(x))\|\, \|f_s(a_c)\|} \cdot \tau, $$

其中 $\tau$ 是可学习的温度参数。这种双塔结构正是 DeViSE、CLIP 和现代稠密检索的核心架构,区别仅在于数据规模。

共享语义嵌入空间

几何直觉是关键:训练时,已见类的视觉特征会围绕其语义原型(如 )聚类;而未见类的原型(如 斑马狮子熊猫)完全由语义信息定位。查询图像被投影到同一空间后,会落在与其属性最匹配的未见类原型附近,从而完成预测。

DeViSE:以词嵌入作为语义侧#

$$ \mathcal{L} = \sum_{c' \neq y} \max\bigl(0,\; m - F(x, y) + F(x, c')\bigr), $$

推理时,计算图像与所有类别嵌入的得分,包括从未见过图像的类别。

DeViSE:视觉编码器 + Word2Vec 文本编码器,共享嵌入空间

DeViSE 首次证明 ZSL 可规模化:在 ImageNet 上训练后,能对 WordNet 中数万个零样本类别给出合理预测。但其弱点在于,词嵌入编码的是语言相似性(如 宠物 聚类),而非视觉相似性(如 斑马老虎美洲豹 因条纹而应聚类)。这一模态鸿沟推动了后续研究的发展。


生成式 ZSL:合成未见类的视觉特征#

判别式 ZSL 学习一个固定兼容性函数,并寄望其能泛化;生成式 ZSL 则反其道而行:根据语义描述合成未见类的视觉特征,再用真实已见类与合成未见类特征联合训练一个普通监督分类器——ZSL 由此回归监督学习。

f-CLSWGAN(Xian et al., 2018)#

该模型是一个带分类器引导的条件 Wasserstein GAN:

  • 生成器 $G(z, a_c) \to \tilde{x}$ :输入噪声 $z \sim \mathcal{N}(0, I)$ 和语义 $a_c$ ,输出合成 CNN 特征;
  • 判别器 $D(x)$ :区分真实与合成特征(使用 Wasserstein 损失,无 sigmoid);
  • 辅助分类器 $\mathrm{cls}$ :在生成特征上训练,但使用真实已见类数据。
$$ \mathcal{L} = \underbrace{\mathbb{E}[D(\tilde{x})] - \mathbb{E}[D(x)] + \lambda\,\mathrm{GP}}_{\text{WGAN-GP}} \;+\; \beta \cdot \underbrace{\mathbb{E}\bigl[-\log P(y \mid \tilde{x})\bigr]}_{\text{分类损失}}, $$

其中 $\mathrm{GP}$ 是梯度惩罚项 $\mathbb{E}[(\|\nabla_{\hat{x}} D(\hat{x})\|_2 - 1)^2]$ 。分类损失是关键——它迫使合成特征不仅逼真,还要具备类别可分性

测试时,对每个未见类采样 $\tilde{x}^{(u)} \sim G(z, a_u)$ ,然后在 $\{(x, y) : y \in \mathcal{C}^s\} \cup \{(\tilde{x}^{(u)}, u)\}$ 上训练 softmax 分类器。仅此一步,AwA2 上的 GZSL 调和平均值就从约 22 提升至约 58。

f-VAEGAN-D2#

Xian 等人(2019)引入 VAE 编码器以稳定训练,并增加第二个判别器利用未标注测试图像(transductive)。如今,“VAE 稳定 + GAN 锐化 + 分类器可分”已成为生成式 ZSL 的标准配方。


广义零样本学习:偏置问题#

经典 ZSL 基准将测试标签限制在 $\mathcal{C}^u$ ,这掩盖了真实挑战:实际部署中,我们无法预知输入图像属于已见类还是未见类。

一旦标签空间扩展至 $\mathcal{C}^s \cup \mathcal{C}^u$ ,所有仅在已见类上训练的模型都会产生强烈已见类偏置——其视觉特征落在模型熟悉的区域,而未见类特征稍显异常,几乎总在 argmax 中落败。

$$ H = \frac{2 \cdot S \cdot U}{S + U}. $$

该指标惩罚“顾此失彼”的方法。例如,$S = 88, U = 12$ 时,$H = 21$ ——表现极差。

GZSL 偏置问题与三种解决方法

三种主流解决方案:

$$ F_{\text{cal}}(x, c) = F(x, c) - \gamma \cdot \mathbb{1}[c \in \mathcal{C}^s]. $$

在验证集上调优 $\gamma$ 即可。简单有效,是强基线。

2. 生成式特征合成第 4 节 ):一旦能合成未见类特征,GZSL 就退化为均衡数据下的监督分类。

3. OOD 门控:训练二分类器判断“图像是否来自已见类”,再路由至相应子分类器。性能上限取决于 OOD 检测器本身。


CLIP 与视觉–语言预训练时代#

$$ \mathcal{L} = -\sum_i \log \frac{\exp\bigl(I_i \cdot T_i / \tau\bigr)}{\sum_j \exp\bigl(I_i \cdot T_j / \tau\bigr)} \;+\; (\text{文本侧对称项}). $$

零样本推理时,动态用文本构建分类器:对 $K$ 类问题,编写提示模板如 "a photo of a {class}",用文本塔编码所有 $K$ 个提示,再将图像嵌入与之比对,选择最近邻。

CLIP 零样本分类流程

两大深远影响:

  1. 无需语义工程:类别用自然语言描述即可。新增类别?写一句话就行。
  2. GZSL 不再是独立难题:CLIP 在训练中“见过”和“未见过”的类别上表现相近,因为它从未以类别判别方式训练。

基准演进印证了这一轨迹:

AwA2 与 CUB 零样本基准的演进

DAP(2009)→ ALE/DeViSE(2013):双线性与排序兼容性;SAE(2017):语义自编码器;f-CLSWGAN(2018)→ CADA-VAE(2019):生成式特征合成弥合 GZSL 鸿沟;CLIP(2021):大规模对比预训练重定义性能上限。


实现#

以下是一个精简的 PyTorch 构建块,涵盖深度兼容性模型与特征生成 GAN。完整训练脚本(含 AwA2 数据加载器、ZSL/GZSL 评估)见文末参考实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import torch
import torch.nn as nn
import torch.nn.functional as F

class DeepCompatibility(nn.Module):
    """DeViSE / ALE / CLIP 使用的视觉-语义双塔兼容性模型。"""

    def __init__(self, visual_dim: int, semantic_dim: int,
                 embed_dim: int = 512, temperature: float = 10.0):
        super().__init__()
        self.vis = nn.Sequential(
            nn.Linear(visual_dim, 1024), nn.ReLU(),
            nn.Dropout(0.5), nn.Linear(1024, embed_dim),
        )
        self.sem = nn.Sequential(
            nn.Linear(semantic_dim, 512), nn.ReLU(),
            nn.Dropout(0.5), nn.Linear(512, embed_dim),
        )
        self.tau = temperature

    def forward(self, x: torch.Tensor, a: torch.Tensor) -> torch.Tensor:
        """为每张图像对每个类别打分,返回 [B, C] logits。"""
        v = F.normalize(self.vis(x), dim=-1)        # [B, d]
        s = F.normalize(self.sem(a), dim=-1)        # [C, d]
        return self.tau * v @ s.t()

class FeatureGenerator(nn.Module):
    """f-CLSWGAN 风格的条件生成器。"""

    def __init__(self, noise_dim: int, semantic_dim: int, feature_dim: int):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(noise_dim + semantic_dim, 4096), nn.LeakyReLU(0.2),
            nn.Linear(4096, feature_dim), nn.ReLU(),
        )

    def forward(self, z: torch.Tensor, a: torch.Tensor) -> torch.Tensor:
        return self.net(torch.cat([z, a], dim=-1))

def gzsl_evaluate(model: DeepCompatibility, features: torch.Tensor,
                  labels: torch.Tensor, attrs: torch.Tensor,
                  seen_ids: torch.Tensor, unseen_ids: torch.Tensor,
                  gamma: float = 0.0) -> tuple[float, float, float]:
    """在 GZSL 测试集上计算 (S, U, H),支持校准堆叠。"""
    model.eval()
    with torch.no_grad():
        logits = model(features, attrs)                 # [N, C]
        logits[:, seen_ids] -= gamma                    # 已见类降权
        pred = logits.argmax(dim=-1)
        seen_mask = torch.isin(labels, seen_ids)
        S = (pred[seen_mask] == labels[seen_mask]).float().mean().item()
        U = (pred[~seen_mask] == labels[~seen_mask]).float().mean().item()
    H = 2 * S * U / (S + U + 1e-8)
    return S, U, H

gzsl_evaluate 中的 gamma 参数实现了校准堆叠——这是解决 GZSL 问题时最简单且值得优先尝试的方法。

从零开始的双线性兼容性#

双线性兼容性函数 $F(x, c) = \theta(x)^\top W\, \phi(c)$ 是捕捉零样本学习(ZSL)几何结构的最简模型——仅用一个矩阵 $W$ 连接视觉侧与语义侧,而学习过程只需发现这个矩阵。ALE(Akata 等,2013)和 SJE(Akata 等,2015)都属于这一框架;区别仅在于损失函数。交叉熵虽可用,但对所有错误类别一视同仁。而采用带类别相关边距的结构化排序损失,可在 AwA2 数据集上额外提升两到三个百分点。

$$ \mathcal{L}(x_n, y_n) = \sum_{y \in \mathcal{C}^s} \max\bigl(0,\; \Delta(y_n, y) + F(x_n, y) - F(x_n, y_n)\bigr), $$

其中 $\Delta(y_n, y)$ 为边距——通常取 $\mathbb{1}[y \neq y_n]$ ,但也可替换为语义距离 $\|a_{y_n} - a_y\|$ ,以更大力度推开语义上相距较远的干扰类。关于 $W$ 的梯度可简化为若干秩一更新项 $\theta(x_n)\bigl(\phi(y) - \phi(y_n)\bigr)^\top$ 的和(仅对违反约束的类别求和),这解释了为何单个矩阵已具备足够容量,且收敛迅速。

为何选择双线性而非深度 MLP?原因有三。
第一,$W$$dM$ 个参数——当 $d=2048, M=85$ 时约为 $174\text{k}$ ,比双塔 MLP 少两个数量级;而 AwA2 的已见类训练集仅约 $23\text{k}$ 张图像。
第二,双线性形式是对联合得分的张量分解:它无法像非线性模型那样过拟合已见类的特异性,这一点至关重要,因为测试损失作用于互不相交的类别集合。
第三,$W$ 具备可解释性——其左奇异向量对应携带最多属性信号的视觉方向。

$$ F(x, c) = \sum_{k=1}^{r} \sigma_k \langle u_k, \theta(x)\rangle \langle v_k, \phi(c)\rangle. $$

每个秩一项将单一视觉方向通过标量增益映射至单一语义方向。对未见类 $c$ 的泛化仅需在训练中使 $v_k$ 与正确语义轴对齐——只要任一已见类激活该属性,损失函数就会完成对齐。深度双塔模型则无此保证:其非线性可能构造出仅适配已见类流形的表示,而未见类语义根本无法投影其上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import torch
import torch.nn as nn
import torch.nn.functional as F

class BilinearCompatibility(nn.Module):
    """F(x, c) = theta(x)^T W phi(c)。仅训练一个矩阵。"""
    def __init__(self, visual_dim: int, semantic_dim: int):
        super().__init__()
        self.W = nn.Parameter(torch.randn(visual_dim, semantic_dim) * 0.01)

    def forward(self, x: torch.Tensor, A: torch.Tensor) -> torch.Tensor:
        # x: [B, d], A: [C, M]  ->  scores [B, C]
        return x @ self.W @ A.t()

def structured_ranking_loss(scores: torch.Tensor, y: torch.Tensor,
                            margin: float = 1.0) -> torch.Tensor:
    """L = sum_y max(0, Delta + F(x,y) - F(x,y_n)),对错误类别求和。"""
    B, C = scores.shape
    correct = scores.gather(1, y.unsqueeze(1))           # [B, 1]
    delta = torch.ones_like(scores) * margin
    delta.scatter_(1, y.unsqueeze(1), 0.0)               # 真实类边距为零
    violations = (delta + scores - correct).clamp(min=0)
    violations.scatter_(1, y.unsqueeze(1), 0.0)          # 排除真实类
    return violations.sum(dim=1).mean()

# 小型合成实验:5 类,100 维特征,85 维属性。
torch.manual_seed(0)
C, d, M = 5, 100, 85
A = torch.randn(C, M)                                    # 类别属性矩阵
true_W = torch.randn(d, M) * 0.1
X = torch.randn(256, d)
y = (X @ true_W @ A.t()).argmax(dim=1)                   # 合成标签

model = BilinearCompatibility(d, M)
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
for step in range(300):
    scores = model(X, A)
    loss = structured_ranking_loss(scores, y, margin=1.0)
    opt.zero_grad(); loss.backward(); opt.step()
acc = (model(X, A).argmax(1) == y).float().mean().item()
print(f"训练准确率={acc:.3f}, 损失={loss.item():.4f}")

该合成实验仅用一个 $100 \times 85$ 矩阵,在数百步内即可收敛至 $>0.95$ 准确率。在真实 AwA2 特征上,相同模型使用交叉熵可达 $\sim 58\%$ ZSL 准确率;改用上述排序损失可提升 $\sim 2$$3$ 个百分点。

方法AwA2 ZSL 准确率学习参数量备注
双线性 + softmax58.2$dM$基线
双线性 + 排序 (ALE)60.7$dM$边距很重要
DeViSE (深度双塔)59.7$\sim 2M$Word2Vec 语义
SJE (结构化 + 多线索)61.9每线索 $dM$最佳非生成方法

数据源自 Xian 等人 2019 年 TPAMI 评测;具体数值随骨干网络略有浮动。以下两点实现细节决定成败:

  • 小幅度初始化 $W$ 。双线性得分无界;若 $W \sim \mathcal{N}(0, 1)$ ,早期 softmax 会饱和,导致排序梯度消失。代码中 100 倍缩放绝非装饰。
  • 使用类别均衡的小批量。AwA2 已见类频率偏斜达 2–3 倍;普通 SGD 在非均衡采样下会欠训练稀有类——而这些类的属性签名恰恰是泛化至未见类的关键。

后续两节介绍的深度兼容性族与特征生成 GAN 最终将 GZSL 调和均值推高至 $\sim 60$ 以上——但上述双线性基线仍是每篇论文必须超越的标杆。


常见问题#

Q1:何时选用 ZSL 而非小样本或主动学习?
当满足以下任一条件:(a) 长尾分布宽且不稳定,新类别涌现速度超过标注能力;(b) 已有丰富语义资源(如属性体系、产品目录、文本描述);(c) 可直接使用预训练视觉–语言模型(CLIP、SigLIP、OpenCLIP),跳过专用 ZSL 流程。

Q2:属性、词嵌入、句嵌入与 CLIP 提示,如何选择?
属性单位区分度最高,但需专家设计;词嵌入易扩展,但反映语言而非视觉相似性;句嵌入是折中方案;CLIP 提示在多数自然图像基准上占优,因其编码器经联合训练。

Q3:为何 GZSL 无校准则崩溃?
训练目标仅奖励已见类正确预测,导致已见/未见类 logit 分数未对齐。测试时,已见类分数系统性偏高,总抢走 argmax。校准堆叠、生成合成与 OOD 门控均针对此不平衡。

Q4:CLIP 是否使传统 ZSL 研究过时?
对自然图像而言,基本如此。但在网络数据稀缺领域(如医学影像、工业缺陷检测、卫星图像),老式语义属性方法仍是数据效率最高的冷启动手段。况且 CLIP 本质仍是深度双塔兼容性函数,本文概念框架依然适用。

Q5:如何缓解 Hubness 问题?
Hubness 是高维病态现象:少数“枢纽”原型成为过多查询的最近邻。对策包括:打分前对双模态 L2 归一化(代码已含)、使用排名打分法(CSLS、ZestNorm),或对每类得分做标准化。

参考文献#

  • Lampert, C. H., Nickisch, H., & Harmeling, S. (2009). Learning to detect unseen object classes by between-class attribute transfer. CVPR. — DAP, AwA dataset.
  • Frome, A., et al. (2013). DeViSE: A deep visual-semantic embedding model. NeurIPS.
  • Akata, Z., Perronnin, F., Harchaoui, Z., & Schmid, C. (2013). Label-embedding for attribute-based classification. CVPR. — ALE.
  • Chao, W.-L., et al. (2016). An empirical study and analysis of generalized zero-shot learning for object recognition in the wild. ECCV. — GZSL and calibrated stacking.
  • Xian, Y., Lampert, C. H., Schiele, B., & Akata, Z. (2019). Zero-shot learning — A comprehensive evaluation of the good, the bad and the ugly. TPAMI.
  • Xian, Y., Lorenz, T., Schiele, B., & Akata, Z. (2018). Feature generating networks for zero-shot learning. CVPR. — f-CLSWGAN.
  • Xian, Y., Sharma, S., Schiele, B., & Akata, Z. (2019). f-VAEGAN-D2: A feature generating framework for any-shot learning. CVPR.
  • Schönfeld, E., et al. (2019). Generalized zero- and few-shot learning via aligned variational autoencoders. CVPR. — CADA-VAE.
  • Radford, A., et al. (2021). Learning transferable visual models from natural language supervision. ICML. — CLIP.
本系列

迁移学习 12 篇

  1. 01 迁移学习(一):基础与核心概念
  2. 02 迁移学习(二):预训练与微调
  3. 03 迁移学习(三):域适应
  4. 04 迁移学习(四):小样本学习
  5. 05 迁移学习(五):知识蒸馏
  6. 06 迁移学习(六):多任务学习
  7. 07 迁移学习(七):零样本学习 当前
  8. 08 迁移学习(八):多模态迁移
  9. 09 迁移学习(九):参数高效微调
  10. 10 迁移学习(十):持续学习
  11. 11 迁移学习(十一):跨语言迁移
  12. 12 迁移学习(十二):工业应用与最佳实践

读有所得?

GitHub 关注我 → 新文周更

GitHub