第4章 最大均值差异(MMD)与深度域适应

做域适应这么多年,最大均值差异(MMD)是我最早接触的度量工具之一。说实话,它的数学形式并不复杂,但在实际项目中特别好用。你想想看,我们要衡量两个分布是否相似,最简单的办法就是比较它们的均值——但单纯比均值太粗糙了。MMD 的高明之处在于,它把数据映射到再生核希尔伯特空间(RKHS)里,再比较均值差异。这样做的好处是,能捕捉到更高阶的统计特征。

4.1 MMD 的核心思想

MMD 的定义其实很直观。给定源域分布 P 和目标域分布 Q,我们采样得到两组数据,然后在 RKHS 里计算两个平均特征向量的距离。数学公式长这样:

MMD(P, Q) = || μ_P - μ_Q ||_H^2

这里的 μ_P 和 μ_Q 分别是 P 和 Q 在 RKHS 中的均值嵌入。我在项目中遇到过不少误解,有人以为 MMD 就是直接算欧氏距离,其实不是——关键在于核函数的选择。不同的核函数对应不同的特征映射,直接影响域适应的效果。

核心要点: MMD 是无参数的双样本检验统计量,可以直接用来判断两个分布是否相同。其值越小,说明源域和目标域越相似。

4.2 核函数与经验估计

实际计算时,我们一般用高斯核(RBF 核)。为什么呢?因为它能把数据映射到无限维空间,表达能力更强。经验 MMD 的计算公式是:

MMD^2(X, Y) = 1/(m^2) Σ_i Σ_j k(x_i, x_j)
             + 1/(n^2) Σ_i Σ_j k(y_i, y_j)
             - 2/(mn) Σ_i Σ_j k(x_i, y_j)

其中 m 是源域样本数,n 是目标域样本数。我记得第一次自己实现这个公式时犯了一个低级错误——忘记了核矩阵的对角线项,导致 MMD 值不稳定。嗯,这里要注意:经验估计是无偏的,但方差控制很关键,核带宽 σ 的选择直接影响结果。

实践技巧: 带宽 σ 一般设为所有样本对距离的中位数。这样做的好处是鲁棒性比较强,不需要频繁调参。

4.3 深度域适应中的 MMD

接下来聊聊深度域适应。早期的方法(比如 DDC——Deep Domain Confusion)直接在深度网络的某一层后面加 MMD 损失,强制特征分布对齐。架构大概是这样的:

# 伪代码示意
class DDCNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.feature_extractor = nn.Sequential(...)
        self.classifier = nn.Sequential(...)
    
    def forward(self, x_s, x_t):
        f_s = self.feature_extractor(x_s)
        f_t = self.feature_extractor(x_t)
        # 计算 MMD 损失
        mmd_loss = mmd_loss_function(f_s, f_t, kernel='rbf')
        # 分类损失
        cls_loss = nn.CrossEntropyLoss()(self.classifier(f_s), y_s)
        total_loss = cls_loss + lambda_mmd * mmd_loss
        return total_loss

你看,思路非常简单——让特征提取器对源域和目标域都提取通用特征。但这里有个坑:如果你把 MMD 加在太浅的层,特征语义不够强;如果加在太深的层,又可能破坏分类器的判别能力。我个人习惯在网络中间层(比如 ResNet 的 bottleneck 之后)加上 MMD 损失,效果最稳。

4.4 多核 MMD (MK-MMD)

单核 MMD 有一个明显缺点:核带宽选择困难。后来有人提出多核 MMD,说白了就是把多个不同带宽的高斯核加权组合。这样做的好处是自适应能力更强,不需要手动调带宽。我在项目中测试过,MK-MMD 在 Office-31 数据集上比单核 MMD 平均提升 2-3 个百分点。

MK-MMD 的计算稍微复杂一点:

MMD^2_multi(X, Y) = Σ_k β_k * MMD_k^2(X, Y)

其中 β_k 是每个核的权重,通常通过优化学习得到。注意权重不能为负,一般加一个 softmax 约束。

4.5 深度 CORAL 与 MMD 的关系

你可能也听说过深度 CORAL(Deep CORAL)——它也是对齐分布的方法,但 CORAL 对齐的是协方差矩阵,而非均值。这和 MMD 有什么区别呢?我画了一张对比表格:

方法对齐目标适用范围计算复杂度
MMD(高斯核)无限阶矩(所有统计量)通用分布对齐O(m^2 + n^2 + mn)
MK-MMD多尺度信息适应不同数据分布O(k·(m^2 + n^2 + mn))
CORAL二阶矩(协方差)特征缩放差异明显时O(d^2·(m + n))
MMD+线性核一阶矩(均值)仅均值偏移场景O(m + n)

从表中可以看出,MMD 能对齐全部阶矩(理论上),CORAL 只关心二阶。实际项目中,如果源域和目标域只是旋转、缩放等线性变化,CORAL 更快更准;但如果是复杂的非线性偏移,MMD 效果更好。我曾经在一个医学图像分割项目里同时试了两种,最后发现 MK-MMD 的 DANN 变体效果最佳。

深度域适应中 MMD 的应用框架 源域图片 X_s 目标域图片 X_t 共享特征提取器 φ(·) (CNN / ResNet) 源域特征 f_s 目标域特征 f_t MMD 损失 (MK-MMD / 单核) 分类器 C(·) 无标签(对齐目标)

上图展示了 MMD 在深度域适应中的典型位置——特征提取器输出特征后,立即在特征空间计算 MMD 损失,与分类损失一起反向传播。注意目标域分支不需要标签,这就是无监督域适应的精髓。

4.6 实际部署中的注意事项

  • 批次大小要一致: 源域和目标域每个 batch 的样本数建议相同,否则 MMD 计算会有偏差。我曾经因为 batch size 不匹配导致训练震荡,排查了半天才发现。
  • 核矩阵的数值稳定性: 矩阵维度较大时,建议用双精度浮点或者加上小的 epsilon 防止除零。
  • 损失权重 λ: 一般从 0.1 开始调,先固定分类器预训练几轮,再逐步加入 MMD 损失。我习惯用指数衰减的 λ 调度策略,前期注重分类,后期注重对齐。

避坑指南: 我曾经尝试直接把 MMD 损失加在分类器输出层,结果发现目标域准确率反而下降。原因很简单——MMD 迫使分类器输出分布一致,但分类器本身需要保留判别边界。更好的做法是加在特征层(如全连接层之前),而不是分类器输出。

4.7 小结与扩展

MMD 作为域适应里最经典的度量,简单有效,适合入门。但你必须清楚它的局限性:

  • 计算复杂度是二次的,数据量大时很慢(可以用 linear-time MMD 近似)。
  • 对核函数的依赖较强,推荐用 MK-MMD 或者 learnable kernel。
  • MMD 只衡量特征分布差异,不关心类别信息——要是源域和目标域类别分布不一致,MMD 反而有害。

下一个方向是结合对抗训练和 MMD,也就是把判别器的对抗损失和 MMD 损失结合起来,做到互补。不过那是后面章节的内容了,这里不展开。