第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 在深度域适应中的典型位置——特征提取器输出特征后,立即在特征空间计算 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 损失结合起来,做到互补。不过那是后面章节的内容了,这里不展开。