1. 背景与问题Few-shot Learning 和 Meta-Learning 到底在解决什么做机器学习的时间久了你会发现一个很“反直觉”的现实我们拼命喂数据训练出来的模型拿到一个新任务上往往脆弱得像纸糊的。哪怕只是换了个数据分布模型可能就需要重新训练更别提只给几个样本的极端场景了。Few-shot Learning 要解决的就是让模型在看到极少样本比如每个类别5张图的情况下仍然能快速学会新任务。但这里有个核心矛盾常规训练方式学到的参数是基于源任务分布的换到新任务时初始参数并不是“空的”而是带着源任务的偏见。这种偏见在少量样本下会被放大导致模型很难快速适应。Meta-Learning 的答案是“learn to learn”通过多个任务来学习一个更好的初始条件让模型见过足够多的任务类型之后在新的 few-shot 任务上能更快收敛。MAMLModel-Agnostic Meta-Learning就是最有名的一类方法它直接对初始参数做梯度更新目标是让初始参数在一步或几步梯度之后就能适应新任务。MAML 的框架很简单在多个任务上从当前参数出发每个任务做几步梯度更新然后计算这些更新后的参数在对应任务上的 loss再对初始参数求梯度。公式写出来就是θ ← θ - β ∇_θ Σ_T L_T(θ - α ∇_θ L_T(θ))其中内层 θ θ - α ∇_θ L_T(θ) 是任务 T 上的快速适应外层是对初始参数 θ 的元梯度更新。这个方法在实际中很有效但它有一个隐含假设所有任务的梯度方向大致一致或者至少最终能收敛到一个对多数任务都友好的区域。问题恰恰出在这里。MAML 优化的只是“在所有任务上平均表现好”它没有约束每个任务适应之后的性能差异。假设有三个任务其中一个任务用初始参数适应后 loss 降得非常快另外两个任务挣扎半天还停在原地平均值可能仍然好看但初始参数实际上已经偏向那个“轻松任务”了。这个就是 task bias——模型对任务分布不敏感被某些任务牵着鼻子走。TAML 这篇工作就是针对这个问题提出来的。它的思路非常直接我们不光要优化平均表现还要让初始参数对所有任务保持“中立”也就是 task-agnostic。具体做法是在 MAML 的元目标里加一项正则惩罚不同任务在适应后的性能差异同时用熵最大化来鼓励初始参数对任务不产生过强偏好。这一篇博文我会从 TAML 的核心思想、两种实现形式、算法细节、实验结论以及我实际复现时踩过的坑这几个角度展开。适合已经大概了解 MAML想进一步理解 task-agnostic 概念或者准备在项目里尝试 TAML 的读者。2. TAML 是怎么做到 task-agnostic 的2.1 从“平均表现”到“性能分布均衡”要理解 TAML先要抓住一个转变。MAML 的目标是最小化所有任务适应后 loss 的和或均值数学上看就是min_θ Σ_{T~p(T)} L_T(θ - α ∇_θ L_T(θ))TAML 在同样框架下加了第二个约束不同任务适应后的 loss 要尽可能接近。也就是说我们希望初始参数 θ 不仅能让每个任务适应后 loss 都很低而且这些 loss 的离散程度也要很小。这个离散程度可以用方差、标准差、或者更通用的散度来衡量。为什么强调这一点因为 few-shot 场景下少数样本本身噪声大如果初始参数已经偏向某些任务那么遇到一个新任务时它的适应过程极可能沿着偏见的方向走导致泛化能力下降。task-agnostic 的初始参数就像一个不带“立场”的中介不管新任务来自哪个分布它都能公平地提供起点。TAML 的做法是用一个正则项 R 来惩罚不同任务之间的性能差异。硬版本hard TAML里R 取任务适应后 loss 的最大熵软版本soft TAML里R 取任务适应后 loss 的方差或标准差。这样元目标变成min_θ Σ_T L_T(θ) λ · R(θ)其中 θ θ - α ∇_θ L_T(θ)。λ 控制公平性约束的强度。2.2 熵最大化与任务偏好的关系要说清楚 hard TAML先得聊一个概念熵。在信息论里熵刻画的是分布的不确定性。分布越均匀熵越高分布越尖峰熵越低。对一个分类模型来说假设输出是类别概率分布如果这个分布非常尖锐比如某个类概率接近1说明模型非常确定自己的判断。如果在元学习场景下我们拿初始参数去跑一组任务把每个任务适应后的 loss 归一化成一个分布比如所有任务 loss 归一化后总和为1那么熵就能描述这个 loss 分布的均匀程度。loss 分布越均匀熵越高意味着所有任务表现越接近反之如果某个任务 loss 特别小、其他任务 loss 特别大归一化后的分布会非常不均匀熵就低。hard TAML 正是把“让 loss 分布熵最大化”作为正则项。当熵达到最大时所有任务适应后的 loss 趋于相等初始参数不偏心。这样训练出来的初始参数不会因为某个任务梯度更强、样本更多或者 loss 曲面更平就“倒向”那一侧。2.3 soft TAML 与方差正则的直觉soft TAML 用了另一个度量任务 loss 的方差。如果说熵是全局分布角度的度量方差就是数值离散度的度量。方差越小说明不同任务表现越接近。soft TAML 的正则项可以写成R_var Σ_{i,j} (L_i - L_j)^2 的某种归一化形式或者直接表示为 loss 向量的经验方差。相比 hard TAMLsoft TAML 计算更简单梯度也更平稳。因为熵对分布的敏感度受归一化方式影响如果任务数量少或 loss 数值量级差异大熵的计算容易不稳定。方差作为二阶矩在梯度反传时相对直接。两种形式的本质是相同的约束初始参数不要过度适配任何单一任务。区别在于“不公平”的度量标准不同。hard 更强调概率均匀性soft 更强调数值接近性。实际中两者的表现接近但计算复杂度和稳定性、以及需要调节的超参数比如 λ有所不同。3. TAML 算法核心细节与推导3.1 任务采样与支持集/查询集的划分在描公式之前先锁定训练流程。每次迭代从任务分布 p(T) 中采样一批任务比如 batch size 为 4 或 8。每个任务 T_i 都有自己的支持集support set和查询集query set。支持集用来做内层快速适应查询集用来计算适应后参数的表现也就是元目标中的任务 loss。举个例子假设做 5-way 1-shot 图像分类每个任务随机选5个类别每个类别1张图作为支持集查询集再从这些类别里取一批图片比如每类15张。内层适应时模型用支持集计算梯度 ∇_θ L_T_i(θ)然后做一步或几步梯度下降得到任务特定参数 θ_i。这个 θ_i 是跟任务相关的临时参数它不会直接更新初始模型只有外层元梯度更新才会最终改变 θ。外层目标里除了每个任务在查询集上的 lossTAML 还要算这些 loss 之间的差异。于是得到损失L_TAML(θ) Σ_{i1}^B L_T_i(θ_i, query) λ · R({L_T_1, ..., L_T_B})这个式子看起来很简单但实际实现时有两个容易忽略的点。第一内层梯度计算涉及二阶导数因为 θ_i 是 θ 的函数所以外层对 θ 求导时需要通过 θ_i 对 θ 的依赖关系反传梯度。第二正则项 R 是多个任务 loss 的函数它拉平任务间差异的趋势但如果你对 batch 内所有任务求 R那么 batch 中任务的选择会影响正则梯度的稳定性。因此通常会适当增大 batch size让正则估计更可靠。3.2 hard TAML 的熵正则推导hard TAML 把任务 loss 转换成一个概率分布。常见做法是先算每个任务在查询集上的 loss L_i然后做归一化p_i L_i / Σ_{j1}^B L_j注意这里要求 L_i 都是非负的。常见的分类 loss 比如交叉熵本身就是非负的所以没问题。如果 loss 可能为负就需要先做平移或使用其他转换比如 softmax。然后定义熵H(p) -Σ_{i1}^B p_i log p_i正则项就是 -H(p)因为我们要最大化熵所以最小化负熵。于是整体损失L Σ_i L_i λ · (-H(p))梯度怎么算这里有意思的地方在于p_i 依赖所有任务的 loss而每个 loss 又依赖对应的 θ_i。所以当你反传熵正则项对 θ 的梯度时需要先把 -H(p) 对每个 L_j 求偏导然后再接上 L_j 对 θ 的链式梯度。数学形式不复杂但实现时需要将 batch 内所有任务的计算图保持到外层不能提前 detach。有一个细节熵最大化的力度随着任务数量的变化而变化。当任务数量 B1 时归一化后 p1熵为0正则不起作用。B 越大熵的最大值 ln B 越大正则的潜力越大。所以如果你在实现中 batch size 很小比如2hard TAML 的效果会非常有限这是要注意的。另外如果 loss 数值非常大或非常小p_i 可能接近0或1导致 log 出现数值不稳定。实践中需要给 p_i 加一个很小的 epsilon 截断防止 NaN。3.3 soft TAML 的方差正则推导soft TAML 使用方差或标准差作为正则。在 batch 内定义任务 loss 的均值μ_L (1/B) Σ_i L_i方差σ^2 (1/B) Σ_i (L_i - μ_L)^2正则项直接取 σ^2。整体损失L Σ_i L_i λ · σ^2为什么有时候用标准差 σ 而不是 σ^2因为 σ^2 是平方量级对 loss 的绝对尺度非常敏感。如果每个任务 loss 本身很大方差也会很大需要很大的 λ 才能起效果用标准差可以减少尺度影响但梯度在 σ0 附近有奇点需要做微小偏移。从梯度的角度看soft TAML 的正则梯度是让 batch 内各个任务 loss 向均值靠拢。如果某个任务 loss 高于均值正则会增大它的梯度权重反过来压低它如果低于均值正则会减小它的梯度权重让它在元更新中不那么突出。这个机制比 hard TAML 更“直观”一点因为它直接作用于数值差异而熵是通过概率分布间接作用的。实现时soft TAML 不需要归一化也不用担心 log(0)。它的主要超参数是 λ。λ 太大模型会过分强调公平性可能导致所有任务都“平均地差”即整体表现被拉低λ 太小正则项形同虚设。实际中 λ 往往需要通过验证集搜索常见范围是 0.01 到 1具体取决于 loss 的量级。3.4 与 MAML 的统一视角把 TAML 放到 MAML 的统一框架里看它只是在 MAML 的 loss 后面加了一个额外的约束项。这个约束项在计算时不依赖单个任务内部的数据而是依赖 batch 内多个任务之间的相对表现。因此TAML 可以说是一种“即插即用”的元学习正则化方案。如果你的代码里已经实现了 MAML那么改造为 TAML 的步骤非常简单计算 batch 内每个任务的内层适应得到 θ_i计算每个任务在查询集上的 loss L_i根据 L_i 列表计算熵正则项或方差正则项将正则项乘以 λ 加到总 loss 上对总 loss 求梯度回传到初始参数。这也是 TAML 吸引人的地方它没有对模型结构做任何假设任何可以用 MAML 的模型都可以直接用 TAML。CNN、RNN、全连接网络理论上都兼容。4. 实验设计如何在基准数据上验证 task-agnostic4.1 基准数据集与任务构建TAML 论文中用的实验设置是 few-shot 分类和回归的经典基准。分类任务常用 Omniglot 和 miniImageNet。Omniglot 有 1623 个字符类别每个类别只有少量样本非常适合 few-shot。miniImageNet 从 ImageNet 中选了 100 个类别每类 600 张图是评估元学习算法的标准数据集之一。任务构建方式N-way K-shot 分类。N 表示每次任务中抽几个类别K 表示每个类别用几张支持集图片。常见配置是 5-way 5-shot、5-way 1-shot、20-way 5-shot。回归任务常用的是正弦波回归每个任务的振幅和相位随机变化支持集给几个点模型需要拟合出曲线。这个任务虽然简单但能很好反映模型对新函数分布的适应能力。我个人觉得评估 TAML 时有个容易被忽略的点任务批大小episodes per batch。TAML 的正则依赖 batch 内的任务多样性。如果你在复现时 batch size 太小比如1完全无法计算任务间差异如果 batch size 是2正则只能在两个任务之间“较劲”估计的方差噪声极大。论文使用较大的 batch比如 4 或 8才能让正则信号稳定。4.2 关键对比指标除了常规的准确率或 lossTAML 特别值得关注一个指标不同任务之间性能的方差。比如在每个训练 epoch 结束后在验证集上跑一批任务记录每个任务适应后的准确率然后计算这些准确率的标准差。这个标准差越小说明模型越 task-agnostic。传统 MAML 即使平均准确率不低任务之间的准确率 std 往往很大——有的任务准确率90%有的只有60%。TAML 的贡献就是把这个 std 压下来。你在自己实验里不要只盯着平均 acc建议把每个任务的 acc 分布画出来。我见过不少复现报告说 TAML 平均表现和 MAML 差不多但分布明显更集中这说明它确实达成了设计目标只是平均指标没有充分体现。还有一个隐形收益task-agnostic 的模型在遇到分布外的任务时更稳。因为初始参数不偏向训练任务分布遇到稍微偏离分布的新任务时内层适应不容易被“带跑”。如果想验证这个性质可以在训练完后构造一些和训练任务分布不同的测试任务比如训练时 N5测试时 N10或者训练时用灰度图测试时加噪声。这种情况下 TAML 的优势会体现得比 MAML 更明显。4.3 消融实验的作用TAML 有两个可调节的部分正则类型hard/soft和正则强度 λ。做消融实验时可以固定其他条件单独改变 λ 观察变化。λ0 时TAML 退化为 MAML。随着 λ 增大训练集上的平均 loss 可能会略升但任务间方差下降。当 λ 过大时平均 loss 显著上升因为模型为了公平性牺牲了拟合能力。通过这个曲线你可以找到最优 λ。另一个消融点是内层适应步数。MAML 常用一步梯度更新但 TAML 的正则统计是在适应后计算的因此内层步数会影响正则信号。步数多每个任务适应得更充分任务间差异更接近“收敛后的差异”步数少差异更接近“初始参数直接的表现”。论文中一般使用一步或两步。我实测过在噪声较大的数据上两步适应的正则效果反而更平滑因为单步适应容易受支持集噪声干扰。5. 复现 TAML 的实操过程与踩坑记录5.1 代码架构建议如果你想在一个已有 MAML 实现上加入 TAML改动最少的办法是在“计算每个任务的 queryset loss”之后先不要立刻对 loss 求和而是先收集一个列表。具体流程# 伪代码: MAML TAML 风格 meta_loss_list [] for task in task_batch: # 内层适应 adapted_params inner_update(model, task.support, task.query) # 得到 theta_i # 查询集 loss loss_i compute_loss(model, adapted_params, task.query) meta_loss_list.append(loss_i) # 计算任务间正则 if taml_type hard: reg entropy_regularizer(meta_loss_list) elif taml_type soft: reg variance_regularizer(meta_loss_list) meta_loss sum(meta_loss_list) lambda * reg meta_grads torch.autograd.grad(meta_loss, model.parameters())注意inner_update中的梯度更新必须保持计算图完整不能 detach 支持集梯度的依赖。有些人为了省内存会做 detach那会导致外层元梯度完全错误。如果你内存紧张可以考虑用检查点checkpoint等技术但不要直接用 detach 代替。5.2 熵正则和方差正则的具体实现熵正则实现时一定要处理数值边界。下面是一份 PyTorch 风格的参考def entropy_regularizer(loss_list): # loss_list: list of torch scalars, length batch_size losses torch.stack(loss_list) # 每个loss需要梯度 # 归一化到概率分布 # clamp防止log(0) eps 1e-8 probs losses / (losses.sum() eps) # 计算熵的负值 neg_entropy torch.sum(probs * torch.log(probs eps)) return neg_entropy # 最小化负熵 最大化熵在这个实现里我们返回的是负熵直接加进总 loss 最小化。因此loss_total sum_loss lam * neg_entropy。方差正则实现def variance_regularizer(loss_list): losses torch.stack(loss_list) mean losses.mean() var torch.mean((losses - mean) ** 2) return var如果你担心量纲问题可以改为返回标准差std torch.sqrt(var 1e-6) return std实际测试中用标准差比用方差更稳定。因为 loss 的水平会随着训练推进下降方差也会同步下降正则项可能在后期变得很小而标准差同样会下降但下降速度更平缓。最好根据你的 loss 量级对比一下两种形式的曲线。5.3 超参数调整的经验λ 是 TAML 最敏感的超参数。我在几个数据集上做过简单搜索给出参考范围Omniglot 5-way 5-shotλ 在 0.05 ~ 0.2 之间比较好。超过 0.5 会导致平均acc明显下降。miniImageNet 5-way 5-shotloss 量级更大λ 可能需要 0.1 ~ 0.5。正弦回归任务loss 是 MSE一般小于1λ 取 0.01 ~ 0.1 即可。原因其实很简单正则项必须和任务 loss 在一个量级才能起到有意义的平衡作用。如果任务 loss 平均是 0.5而你 λ 取 10那么正则会完全压过任务 loss模型只顾着拉平任务间差异不管拟合质量了。建议先跑一个不加热 sig 的 MAML记录训练初期和末期的任务 loss 范围再让 λ × 正则项 大约占任务 loss 的 10% ~ 50%这个区间通常比较合理。还有一点任务 batch size 不够大的时候方差正则的梯度噪声很大。我在复现时试过 batch2loss 曲线剧烈抖动。增大到 batch8 后正则的梯度才稳定。如果你显存有限可以减小内层步数或者使用较浅的网络但不要牺牲 batch size。5.4 一个容易忽略的二阶导数问题TAML 和 MAML 一样涉及对初始参数 θ 的二阶梯度。原理上内层适应是 θ → θ_i 的过程而外层 loss 是 θ_i 的函数所以总体是复合函数。PyTorch 的自动微分能处理但需要确保create_graphTrue。在 PyTorch 中典型写法adapted model.parameters() - alpha * grads # 这里 grads 是支持集loss对参数的梯度 # 注意 grads 的求导需要 create_graphTrue后面再用 adapted 参与查询集 loss 计算外层 autograd 就会自动构造二阶图。如果你用torch.autograd.grad求外层梯度记得设置create_graphFalse默认就好因为外层不需要再求二阶了。我遇到的常见错误是内层求梯度时忘了加create_graphTrue导致 adapted 是“叶子节点”和原始参数断了联系外层梯度变成0。这个问题不报错但训练完全不更新特别坑。排查时可以先检查 adapted 的grad_fn是否为 None。如果是 None说明计算图断了。6. 常见问题与排查技巧实录6.1 训练不收敛loss 变成 NaN最常见的原因是熵正则的 log(0)。当任务 loss 归一化后某个任务 loss 占绝对主导地位其他任务 loss 接近0probs 中出现0log(0) 直接 NaN。解决办法在 log 里面加 epsilon比如 1e-8对 loss 做 softmax 而不是归一化实际上 softmax 也需要温度参数不如直接加 epsilon检查任务 loss 是否为负数比如 MSE 不会负但某些自定义 loss 可能负需要对 loss 做 max(0, loss) 或平移。另一个 NaN 来源是内层更新的学习率 α 过大导致支持集梯度更新后参数发散。尤其在使用二阶梯度时发散会被放大。建议在调试时先用小 α比如 0.01确认没问题再调大。6.2 正则项不生效模型表现和 MAML 一样如果你发现加了正则和没加结果几乎相同先检查一下 λ 是不是被设成了0或者正则项的值是否一直很小。打印正则项数值如果它在训练初期就一直是0或接近0说明实现有 bug。hard TAML 中如果 batch 内所有任务 loss 都一样熵正好等于 ln(B)。如果你的模型在训练初期对所有任务都给出相似的 loss——这在小规模数据上有可能——那么正则梯度接近0更新缓慢。随着训练进行任务差异才会体现正则开始起作用。不要因为开头几步没变化就认为实现错了。但如果你训练了几百步后正则项数值和 ln(B) 差了十万八千里那大概率就是梯度没传回来。soft TAML 中如果正则项一直很小可能因为你的任务 loss 很接近。这时可以把 λ 调大看看如果调大后任务 loss 出现反效果说明正则路径是通的如果怎么调 loss 曲线都不变那就是梯度没有接上。6.3 计算资源不足怎么缩减显存TAML 比 MAML 多一个 batch 维度的统计量如果实现时把每个任务的查询集都保留完整计算图显存会比较大。我的建议使用梯度检查点gradient checkpointing重放前向过程减少每个任务的查询集样本数量比如每类只取5张而不是15张内层适应只做一步如果任务数很多可以把一个 batch 拆成两个子 batch分别计算正则再对两个正则取平均。不过要注意子 batch 计算的正则和完整 batch 的正则并不完全等价。如果子 batch 内任务类型刚好都相似正则会错误地认为没有分歧所以拆分子 batch 时要确保每个子 batch 包含多样的任务。一个更彻底的方案是使用 Reptile 式的近似但那就不是 TAML 了不推荐在正统实现里走这条路。6.4 评估时应该用多少任务才靠谱TAML 本身是让任务间性能更均衡但如果你评估时只跑 10 个任务随机性太大标准差算出来不可信。我建议在 miniImageNet 上至少跑 1000 个任务Omniglot 上 600 个任务取平均和标准差。每个任务单独进行内层适应不要复用训练时的支持集否则会高估性能。还有一个细节评估时的支持集数量要和训练一致。比如训练用 5-shot测试也用 5-shot。如果你测试时只给 1-shot模型的 task-agnostic 特性可能反而让它不如专门偏向高频任务的模型表现好因为任务分布变了。TAML 追求泛化但泛化不等于在任何变化下都优于 MAML这一点要理性看待。7. 个人对 TAML 的体会与后续扩展我在实际踩过 MAML 的坑之后再看 TAML最大的感悟是很多元学习算法一味追求平均表现忘了“平均”能被少数极端任务拉高而实际部署时我们更关心模型在陌生任务上是否稳定。TAML 的正则思路不算复杂但它把 task bias 这个问题显式地摆到了损失函数里这是它在方法论上的价值。如果你想把 TAML 进一步扩展可以考虑两个方向。第一把熵正则从分类任务迁移到强化学习或在线学习场景那里的任务分布更动态task-agnostic 可能更有意义。第二结合可学习的 λ让模型自动平衡平均表现和公平性而不是人工固定。现在已经有类似 Meta-Weight-Net 的做法用一个小网络根据任务loss分布调整每个任务的权重和 TAML 有异曲同工之处。最后给准备复现的朋友一个建议不要一上来就在 miniImageNet 上跑先用一个简单的正弦回归任务验证逻辑。它的任务构造简单、模型小、训练快能够快速暴露梯度连接和数据处理的 bug。逻辑走通后再换到图像数据集上你会发现排障速度快很多。
阅读完成 · 觉得有帮助?