尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

持续学习实战:原理、方法对比与EWC代码实现

持续学习实战:原理、方法对比与EWC代码实现 持续学习Continual Learning正在经历一场不太容易被察觉、但影响深远的转型。过去几年一提起持续学习大多数人的第一反应还是“解决灾难性遗忘的学术课题”在 MNIST 上拆几个子任务跑一跑 EWC看遗忘率下降多少。但从近两年的研究热点、工业落地案例和综述论文的框架来看这个领域已经不再只是“让模型记得住旧任务”那么简单而是在回答一个更现实的问题当模型部署到真实环境后数据分布不断变化、任务边界模糊、存储和算力有限模型应该怎样持续进化而不是推倒重来。这篇文章想借着 Continual Learning in Transition 这个主题把持续学习的现状、方法脉络、工程落地方式和一套可运行的示例代码串起来。无论你是刚开始接触增量学习的研究生还是正在做推荐系统、自动驾驶、机器人或大模型微调的工程师这篇文章都能提供一份相对完整的地图它解决什么问题现在走到哪一步以及真正动手时最容易踩到哪些坑。文章会先讲清楚持续学习的基本概念和评测口径再对比三类主流方法然后用一个不依赖 GPU 的 PyTorch 示例跑通“Split-MNIST EWC”的完整流程最后给出工程落地建议和常见问题排查表。建议收藏备用写论文实验、做算法选型或准备面试时都可以回来看。1. 这篇文章真正要解决的问题先看一个真实场景。你训练了一个图像分类模型上线后效果不错。三个月后业务方告诉你新增了两类商品旧数据因为合规要求不能长期保存新数据还在持续产生。如果重新从零训练每次都要消耗大量算力和数据准备时间如果不重新训练模型不认识新类。更麻烦的是把新旧数据混在一起微调模型往往会在旧类别上明显退化——这就是经典的灾难性遗忘。持续学习Continual Learning也叫增量学习、终身学习就是为了解决这类问题而出现的。它要求模型像一个不断学习的人一样学新知识时不把旧知识彻底忘掉同时还能利用旧知识帮助学新知识。这里的核心矛盾是稳定性与可塑性之争模型太稳定新任务学不进去模型太可塑旧任务立刻被覆盖。这篇文章要回答的核心问题有三个。第一持续学习到底在解决什么问题和普通的“多任务学习”“模型微调”有什么区别。第二当前主流方法有哪些派系各自的边界和适用场景是什么。第三如何用最小成本在你的项目里复现一个持续学习基线并把评测指标、训练流程、常见坑一次讲清楚。最值得读这篇文章的读者是那些已经知道“灾难性遗忘”这个名词但还没有真正动手跑过一个持续学习实验的人以及正在做算法选型、想知道“这个方法到底行不行”的工程师。2. 持续学习的基本概念与问题定义2.1 从“一次性训练”到“序列学习”传统监督学习的假设是训练数据和测试数据来自同一个静态分布模型训练一次就可以部署。但真实世界很少满足这个假设。持续学习把问题重新表述为模型需要依次学习一系列任务 T1、T2、……、Tn在学习当前任务时不能访问或只能有限访问之前任务的训练数据但要求在完成所有任务后模型在全部任务上都能保持可用。这里最容易混淆的是持续学习和多任务学习。多任务学习是同时拿到所有任务的训练数据一起优化一个模型持续学习则是顺序到达数据旧数据可能无法访问。很多新人把持续学习理解成“多了一个增量训练接口”这是不准确的——增量训练如果不加任何约束本质上就是在旧模型上继续 SGD结果基本都会掉向灾难性遗忘。2.2 灾难性遗忘与稳定性-可塑性困境灾难性遗忘Catastrophic Forgetting指的是神经网络在拟合新任务数据时会覆盖掉对旧任务至关重要的权重信息导致旧任务性能断崖式下降。这不是某一个模型的问题而是基于梯度下降的神经网络的结构性倾向。原因在于旧任务的知识分布在整个权重空间中新任务的梯度更新会同时扰动这些权重。由此引出持续学习最核心的理论矛盾稳定性-可塑性困境Stability-Plasticity Dilemma。可塑性指模型吸收新知识的能力稳定性指模型保留旧知识的能力。二者本质上是对立的。持续学习算法的设计目标就是在二者之间找到合理的平衡点而不是简单地把某一个指标拉到最高。2.3 三种评测协议Task-IL、Domain-IL、Class-IL持续学习的方法选择和评测结果高度依赖你使用哪种任务协议。目前学术界最常用的三种协议如下。协议全称任务特点测试时是否给任务 ID难度Task-ILTask-Incremental Learning每个任务有独立的标签空间测试时知道当前样本属于哪个任务是最简单Domain-ILDomain-Incremental Learning所有任务共享同一套标签但输入分布不同否中等Class-ILClass-Incremental Learning新任务不断引入新类别测试时不知道任务 ID否最难很多人实践时栽跟头就是因为协议没定清楚。比如在 Split-MNIST 上如果按 Task-IL 评测模型在推理时知道当前样本来自哪两个数字可以只在这两个类别上做 softmax难度会低很多如果按 Class-IL 评测模型需要在全部已见类别中做选择难度显著上升。写论文和做方案对比时务必在实验配置里写明协议否则结论没有可比性。2.4 核心评价指标持续学习领域常用的评价指标有三个分别刻画模型在“整体表现”“对旧任务的保持”和“对新任务的帮助”三个维度上的情况。平均准确率Average AccuracyACC模型学习完所有任务后在所有已见任务测试集上的平均准确率。反向迁移Backward TransferBWT学习新任务后模型在旧任务上的准确率相对于刚学完旧任务时的变化量。BWT 为负说明存在遗忘越接近 0说明保持得越好。正向迁移Forward TransferFWT模型在学习了前面任务后对新任务的学习效率相对于从零训练的提升程度。实操中最常被关注的是 ACC 和 BWT因为它们直接反映“能不能用”和“忘得厉不厉害”。在工程汇报里建议把每个任务的测试准确率做成矩阵横轴是训练过的任务纵轴是评估的任务这样遗忘情况可以一目了然。3. 持续学习方法的三大流派与当前转型方向3.1 基于回放的方法Replay-based回放方法的核心思路是既然模型会忘记旧任务那就让它在学新任务时“复习”旧内容。最简单的实现是经验回放Experience Replay用一个固定大小的内存缓冲区保存旧任务样本在训练新任务时混合这些旧样本一起更新。复杂一些的方法包括 GEM、A-GEM它们通过约束新任务梯度方向保证更新不会提高旧任务上的损失。回放方法的优点非常直接效果好且稳定几乎所有实际项目中先跑一个 Experience Replay 基线都能获得不错的结果。缺点也明显需要存储原始样本带来存储成本和隐私合规问题。因此出现了另一个分支——用生成模型生成旧任务的伪样本但生成模型的训练本身又是一个不稳定因素。3.2 基于正则化的方法Regularization-based正则化方法不存储旧数据而是在损失函数中加入约束项限制新任务训练时对旧任务重要参数的改动。代表方法包括 EWCElastic Weight Consolidation、SISynaptic Intelligence和 LwFLearning without Forgetting。EWC 的核心思想是在训练完旧任务后用 Fisher 信息矩阵估计每个参数对旧任务的重要程度然后在新任务的损失函数中加上一项带权重的 L2 惩罚让重要参数的更新幅度变小。这个方法看起来简单却非常优雅也是很多人入门的第一个持续学习算法。它的局限在于当任务数量很多、任务之间差异很大时仅靠约束权重不足以完全阻止遗忘而且 Fisher 矩阵的近似计算也有开销。3.3 基于架构的方法Architecture-based架构方法通过改变模型结构来隔离不同任务的知识。典型代表有 Progressive Neural Networks每学一个新任务就新增一个子网络并建立到旧子网络的横向连接还有 PackNet、HAT 这类参数分配方法用剪枝或掩码把不同任务的参数隔离出来。架构方法几乎不会遗忘因为每个任务的知识存储在独立参数中。但代价是模型体积随任务数量线性增长推理时需要根据任务 ID 选择对应参数这对端侧部署不太友好。它更适合任务边界清晰、任务数量可控的场景。方法流派代表方法核心思路优点主要局限回放Experience Replay、GEM、A-GEM存储或生成旧样本训练时混合复习效果稳定、实现简单存储成本、隐私合规正则化EWC、SI、LwF约束旧任务重要参数的更新幅度无需存储旧数据长序列任务上遗忘仍明显架构Progressive NN、PackNet、HAT按任务隔离或动态扩展参数几乎无遗忘模型体积增长、需任务 ID3.4 转型方向预训练模型、提示学习与理论化持续学习当前最大的变化是从“从零训练小模型”转向“基座模型 轻量适配”。在大模型时代与其在全部权重上做持续学习不如冻结主干网络只学习少量适配参数例如 Additive、Prompt 和 Adapter 类的持续学习方法。这类方法把持续学习和参数高效微调结合了起来在视觉模型和语言模型上都有不少工作。另一个转型方向是理论化。近年来出现了以 “A Comprehensive Survey of Continual Learning: Theory, Method and Application” 为代表的系统性综述把持续学习划分成理论、方法和应用三个层面来讨论理论层面研究任务相似性、泛化边界和可学习性方法层面继续扩展上述三类算法应用层面则覆盖开放环境分类、推荐系统、自动驾驶和机器人等真实场景。这意味着持续学习正在从“刷 Benchmark”走向“回答基础问题 解决工程问题”并重。对工程师来说理解这三层有助于判断哪些新方法只是 Benchmark 上的数字游戏哪些才能真正迁移到自己的业务场景。4. 环境准备与数据集划分4.1 运行环境本文示例代码基于 PyTorchMNIST 数据集训练一个两层 MLPCPU 也可以短时间跑完不强制要求 GPU。建议环境如下版本以实际安装为准这里给出的是通用兼容版本区间。Python 3.9 或更高版本PyTorch 2.0 或更高版本torchvision 0.15 或更高版本numpy 1.24 或更高版本先创建项目目录和虚拟环境mkdir continual_learning_demo cd continual_learning_demo python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate创建requirements.txt文件torch2.0.0 torchvision0.15.0 numpy1.24.0安装依赖pip install -r requirements.txt4.2 为什么选择 Split-MNISTSplit-MNIST 是持续学习领域最常见的入门基准把 MNIST 的 10 个数字按顺序分成 5 个任务每个任务包含 2 个数字。比如任务 1 是数字 0 和 1任务 2 是数字 2 和 3以此类推。模型依次学习这 5 个任务训练过程中只能访问当前任务的数据但评测时要在所有已见任务上测试。选择这个基准有两个原因。第一MNIST 规模小CPU 训练快便于在几分钟内复现完整流程。第二任务边界清晰方便新手理解协议定义。更贴近真实场景的替代基准包括 Split-CIFAR-100、Split-ImageNet 以及带有分布漂移的 Domain-IL 基准代码逻辑完全一致只是数据加载方式不同。5. 完整示例代码实现基于 EWC 的持续学习下面实现一个完整的 EWC 训练流程。核心逻辑分四步构造单任务数据、计算 Fisher 信息、计算 EWC 正则损失、按顺序训练所有任务并记录每个任务上的准确率。# 文件路径continual_learning_demo/ewc_demo.py import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader, Subset from torchvision import datasets, transforms # ---------- 1. 模型定义 ---------- class SimpleMLP(nn.Module): def __init__(self, input_size28 * 28, hidden_size256, num_classes10): super().__init__() self.fc1 nn.Linear(input_size, hidden_size) self.fc2 nn.Linear(hidden_size, hidden_size) self.fc3 nn.Linear(hidden_size, num_classes) def forward(self, x): x x.view(x.size(0), -1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x) # ---------- 2. 按类别构造任务数据集 ---------- def make_task_loader(dataset, classes, batch_size64, shuffleTrue): indices [i for i, (_, label) in enumerate(dataset) if label in classes] subset Subset(dataset, indices) return DataLoader(subset, batch_sizebatch_size, shuffleshuffle) # ---------- 3. 估计 Fisher 信息对角近似 ---------- def compute_fisher(model, dataloader, devicecpu): model.eval() fisher {name: torch.zeros_like(param) for name, param in model.named_parameters()} total_samples 0 for x, y in dataloader: x x.to(device).view(x.size(0), -1) y y.to(device) model.zero_grad() logits model(x) loss F.cross_entropy(logits, y) loss.backward() for name, param in model.named_parameters(): if param.grad is not None: fisher[name] param.grad.data.pow(2) * x.size(0) total_samples x.size(0) for name in fisher: fisher[name] / total_samples return fisher # ---------- 4. EWC 正则损失 ---------- def ewc_penalty(model, fisher_accum, param_anchor, lambda_ewc500.0): penalty 0.0 for name, param in model.named_parameters(): if name in fisher_accum and name in param_anchor: penalty (fisher_accum[name] * (param - param_anchor[name]) ** 2).sum() return lambda_ewc * penalty # ---------- 5. 训练与评估 ---------- def evaluate(model, dataloader, devicecpu): model.eval() correct, total 0, 0 with torch.no_grad(): for x, y in dataloader: x x.to(device).view(x.size(0), -1) logits model(x) pred logits.argmax(dim1) correct (pred.cpu() y).sum().item() total y.size(0) return correct / total * 100 def train_task(model, train_loader, fisher_accum, param_anchor, epochs5, lr1e-3, lambda_ewc500.0, devicecpu): optimizer torch.optim.Adam(model.parameters(), lrlr) for epoch in range(epochs): model.train() total_loss 0.0 for x, y in train_loader: x x.to(device).view(x.size(0), -1) y y.to(device) optimizer.zero_grad() logits model(x) ce_loss F.cross_entropy(logits, y) if fisher_accum is not None and param_anchor is not None: penalty ewc_penalty(model, fisher_accum, param_anchor, lambda_ewc) else: penalty torch.tensor(0.0) loss ce_loss penalty loss.backward() optimizer.step() total_loss loss.item() print(f epoch {epoch 1}: loss{total_loss / len(train_loader):.4f}) # ---------- 6. 主流程 ---------- def main(): device cuda if torch.cuda.is_available() else cpu print(fdevice: {device}) transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) test_dataset datasets.MNIST(root./data, trainFalse, downloadTrue, transformtransform) # Split-MNIST5 个任务每个任务 2 个数字 task_classes [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)] model SimpleMLP().to(device) fisher_accum None # 所有已学任务累积的 Fisher 信息 param_anchor None # 旧任务训练完成时的参数快照 all_test_loaders [] for t, classes in enumerate(task_classes): print(f--- Task {t 1}: {classes} ---) train_loader make_task_loader(train_dataset, classes) test_loader make_task_loader(test_dataset, classes, shuffleFalse) all_test_loaders.append((classes, test_loader)) # 在训练新任务之前保存当前参数作为旧任务锚点 if t 0: param_anchor {name: param.detach().clone() for name, param in model.named_parameters()} train_task(model, train_loader, fisher_accum, param_anchor, devicedevice) # 训练完当前任务后估计当前任务上的 Fisher 信息并累积 fisher_current compute_fisher(model, train_loader, device) if fisher_accum is None: fisher_accum {name: val.clone() for name, val in fisher_current.items()} else: for name in fisher_accum: fisher_accum[name] fisher_current[name] # 在所有已见任务上评估一次观察遗忘情况 accs [] for classes, t_loader in all_test_loaders: acc evaluate(model, t_loader, device) accs.append(acc) print(f after task {t 1}: acc on seen tasks {[round(a, 2) for a in accs]}) print() if __name__ __main__: main()运行方式python ewc_demo.py这段代码的关键逻辑需要强调几点。第一Fisher 信息的计算使用的是简化后的对角近似在当前任务数据上算交叉熵损失的梯度平方再对样本求平均。严格来说Fisher 信息是对数似然梯度外积的期望但在分类任务中用交叉熵梯度的平方近似已经足够作为演示也有不少公开实现采用这种做法。如果追求更精确的结果可以改为逐类别计算概率加权梯度但训练速度会明显下降。第二参数锚点param_anchor和累积 Fisherfisher_accum的配合是 EWC 的精髓。在学第 t 个任务时param_anchor 是学完前 t-1 个任务后的模型参数fisher_accum 是前 t-1 个任务的 Fisher 累积值。EWC 正则项的意思是如果某个参数对旧任务很重要就不要让它偏离锚点太远。这里的“重要程度”由累积 Fisher 决定。第三代码在每学完一个任务后会打印所有已见任务上的测试准确率。你会发现随着任务推进早期任务的准确率会波动但 EWC 约束下的下降幅度通常远小于不做任何约束的普通微调。为了让对比更直观可以把 lambda_ewc 设置为 0 再跑一遍相当于纯微调基线。或者直接注释掉 ewc_penalty 的调用。两轮结果一对比灾难性遗忘的影响和 EWC 的抑制作用就非常清楚了。6. 运行结果与效果验证6.1 如何判断实验成功运行上面的代码正常情况下你会看到两类输出。一类是每个 epoch 的 loss数值整体呈下降趋势另一类是每个任务结束后的已见任务准确率列表。判断实验成功的关键指标是学习完第 5 个任务后模型在第 1 个任务数字 0/1上的准确率不能出现断崖式下降。以经验来看在 MNIST 上用两层 MLP单任务本身很容易达到 95% 以上的准确率。如果把 lambda_ewc 设成 0 跑纯微调学完第 5 个任务后再看第 1 个任务准确率往往会明显掉下去而带上 EWC 正则后这个下降幅度会显著减小。具体数字受随机种子、学习率、Fisher 近似方式和正则系数影响不同机器上会不一样所以不建议把某个固定数值当作目标而是看“有 EWC 与无 EWC 之间的相对差距”。6.2 记录准确率矩阵建议把代码输出的准确率整理成如下格式的矩阵方便写报告或做对比评估任务学完任务1学完任务2学完任务3学完任务4学完任务5任务10/1高变化变化变化关注是否保持任务22/3-高变化变化关注是否保持任务34/5--高变化关注是否保持任务46/7---高关注是否保持任务58/9----高对角线往下看是“当前任务的学习效果”对角线往右看是“旧任务被遗忘的情况”。一个合格持续学习算法的表现是对角线保持在高位同时右上角的衰减尽量平缓。6.3 如果结果不对先检查哪里如果运行后出现了“所有任务准确率都很低”或“新任务完全学不动”的情况优先检查三处lambda_ewc 是否过大导致正则项压制了交叉熵损失每个任务的数据加载是否真的只包含当前类别的样本模型是否每次都在同一个实例上继续训练而不是误用了“每个任务新建模型”的写法。7. 常见问题与排查思路下面整理了持续学习实验中最常见的几类问题按“现象—原因—排查—解决”的结构列出。问题现象可能原因排查方式解决方案加入 EWC 后新任务学不动lambda_ewc 过大正则项压过交叉熵损失打印两种损失项的量级调小 lambda_ewc或对不同层设置不同权重Fisher 计算非常慢逐样本/逐类计算梯度且重复 backward检查 compute_fisher 的循环次数使用 batch 级交叉熵梯度近似限制用于估计的样本数旧任务准确率仍然明显下降对角 Fisher 近似不足以刻画参数重要性打印每个任务训练前后的准确率矩阵换成 Experience Replay 做基线或使用更精确的 Fisher 估计新任务学习效果好但旧任务全部崩溃评测协议写错测试时用了所有类别检查 evaluate 函数是否限制了类别范围明确 Task-IL / Class-IL 协议按协议设计评测逻辑不同实验之间结果波动大没有固定随机种子查看是否设置 seed在代码开头设置 torch.manual_seed 和 numpy seed内存或显存占用过高Fisher 信息或记忆缓冲区过大用资源监控工具查看占用减小 batch size降低 buffer 容量必要时换用正则化方法任务 ID 在测试时不存在误用了需要任务 ID 的算法检查推理流程是否依赖任务 ID部署场景没有任务 ID 时选择 Class-IL 友好的方法这些坑里最隐蔽的是数据泄露。持续学习对数据顺序非常敏感如果 DataLoader 在任务之间做了全局随机 shuffle或者把多个任务的数据混在一个 loader 里取 batch就会导致模型“偷看”未来任务的数据实验结论完全失效。这也是为什么代码里要为每个任务单独构造 DataLoader而不是把所有数据放在一起按标签切分。8. 最佳实践与工程建议8.1 先跑一个简单的回放基线在实际项目中不要一上来就尝试复杂的正则化或架构方法。正确顺序是先实现一个最简单的 Experience Replay用一个小型记忆缓冲区保存旧任务样本训练新任务时把旧样本混入当前 batch。它的代码改动量最小效果通常也不错是判断其他方法是否有效的基准线。# 文件路径replay_baseline.py核心片段非完整脚本 memory_buffer [] # 每个元素是 (x.cpu(), y.cpu()) BUFFER_SIZE 200 SEARCH_BATCH 64 def update_buffer(x_batch, y_batch, buffer, max_sizeBUFFER_SIZE): for x, y in zip(x_batch, y_batch): buffer.append((x.detach().cpu(), y.detach().cpu())) if len(buffer) max_size: # 简单策略随机丢弃旧样本 # 工程上可以替换为按样本难度、多样性或代表性采样的策略 import random random.shuffle(buffer) del buffer[max_size:] def sample_replay_batch(buffer, batch_sizeSEARCH_BATCH): if not buffer: return None, None indices torch.randint(0, len(buffer), (min(batch_size, len(buffer)),)) xs, ys [], [] for idx in indices: x_old, y_old buffer[idx.item()] xs.append(x_old) ys.append(y_old) return torch.stack(xs), torch.tensor(ys)这段片段演示了回放方法最核心的“存”和“取”两部分。训练时把当前 batch 和回放 batch 拼接后一起喂给模型即可。真实工程项目中缓冲区容量和采样策略往往比算法本身更影响最终效果。随机采样虽然简单但会导致模型偏向记忆频繁出现的旧样本基于遗忘程度或样本难度的采样通常能在相同容量下得到更好的保持效果。8.2 方法选型判断标准三类方法没有绝对优劣选择取决于你的约束条件。如果旧数据可以合法存储且存储成本可控优先使用回放类方法它最稳、最不容易翻车。如果旧数据因为隐私或合规要求不能保留正则化方法是合理的起点但要做好长任务序列下遗忘比预期严重的准备。如果任务边界清晰、推理时能拿到任务 ID并且模型体积可以扩展架构类方法能提供几乎零遗忘的保证。如果有大规模预训练基座模型优先考虑冻结主干、只学轻量适配参数的方向这样既能持续吸收新任务又不会因全量微调破坏基座的通用能力。8.3 工程落地注意事项在真实系统里持续学习不只是算法问题还涉及数据链路和运维体系。第一评测协议要写进项目文档。团队协作时如果每个人用不同的协议评测最后的数字无法互相对比。建议在 README 中固定任务划分、指标公式和评估脚本。第二每个任务训练完成后都保存 checkpoint并记录该任务上的 Fisher 信息或回放缓冲区。这样即使后续任务效果不佳也能回滚到任意历史版本而不是只有一个“最新模型”。第三生产环境不要直接在线更新主模型。更稳妥的做法是训练影子模型在小流量灰度验证通过后再切换。持续学习本身就会引入数据分布漂移如果再叠加在线更新带来的不确定性出问题时会很难定位。第四关注数据合规。回放方法需要缓存旧样本这在一些业务场景下可能违反数据保留政策。使用前先和合规团队确认样本留存周期和脱敏要求必要时改用正则化或生成式回放方案。第五给模型加漂移监控。持续学习的前提是“数据会变”但变化速度可能快于模型迭代速度。在服务端记录输入分布的特征统计量当漂移超过阈值时触发重新训练或告警比被动等线上指标下跌更可靠。9. 总结与后续学习方向本文用一篇可运行的示例把持续学习的核心问题、三类经典方法和工程落地关键点串了一遍。你现在应该能回答清楚四个问题持续学习到底解决什么问题灾难性遗忘为什么会发生EWC 为什么能缓解遗忘以及在真实项目里选择方法时应该看哪些约束条件。如果想继续深入建议按下面的路径推进。第一步把本文的 EWC 代码改成 Experience Replay对比两者的准确率矩阵理解回放和正则化在机制上的差异。第二步去读那篇以 Theory、Method and Application 为框架的持续学习综述重点关注其中对任务相似性和泛化边界的讨论这能帮助你在新任务出现时预判模型会不会崩。第三步尝试把方法迁移到你自己的任务序列上比如推荐场景的新品类、质检场景的新缺陷类型、或 NLP 场景的新意图从 Split-MNIST 切换到真实数据时数据不均衡和任务边界模糊会很快暴露出来。最后提醒一句持续学习的实验结论非常依赖评测协议先定协议、再调算法永远比反过来高效。建议把本文的代码、指标矩阵模板和排查表保存下来作为后续实验的统一基线。
返回列表