
最近这半年我在好几个技术社群和线下分享里都被问到同一个问题持续学习Continental Learning到底怎么入门不少人把多任务学习和持续学习混为一谈觉得无非是让模型多学几个任务而已。可一旦真把模型部署到真实环境新数据一来、一微调旧能力就崩这时候才意识到问题没那么简单。如果你也卡在这个阶段我建议直接把这篇综述当成地图来读A Comprehensive Survey of Continual Learning: Theory, Method and Application。这篇发表在IEEE TPAMI上的综述由国内多个团队合作完成是目前我见过的对持续学习领域覆盖最全面、结构最清晰的系统性整理。它不只是一份方法清单而是把理论根基、方法谱系、应用落地和开放问题串在一条逻辑线上。无论你是刚接触持续学习的学生还是已经在业务里摸爬滚打的算法工程师都能从里面找到自己的位置。1. 持续学习到底在解决什么问题灾难性遗忘与部署困境1.1 模型一更新就“失忆”这不是玄学先讲一个真实的部署场景。我有一个做工业视觉检测的朋友他们产线上有一套良品/缺陷分类模型上线时效果很好准确率98%以上。运行了两个月产线新增了一种缺陷类型他们按常规做法收集新数据、在原有模型基础上继续微调。结果新缺陷确实能识别了但旧的几类缺陷准确率从98%掉到了80%出头甚至出现了把旧缺陷漏检的情况。产线负责人直接崩溃最后只能把旧模型回滚重新走一次完整训练流程。这个现象就是持续学习领域研究了三十多年的核心问题——灾难性遗忘Catastrophic Forgetting。1989年McCloskey和Cohen在分析神经网络时就发现用后向传播算法顺序学习多个任务网络在新任务上收敛之后旧任务的性能会显著下降。它不是一个偶然的数值波动而是权重更新机制带来的结构性后果。1.2 灾难性遗忘的机理梯度更新在“踩”旧任务要理解遗忘为什么发生可以看一个极其简化的直觉模型。假设我们的网络参数是θ旧任务在参数空间里有一个比较好的解区域新任务也有自己的梯度方向。当我们用新任务的样本做梯度下降时θ会沿着新任务的梯度方向移动。这个方向如果和旧任务的“敏感方向”重叠就会把原本适合旧任务的参数值推出有效区域。在连续学习多个任务后参数会被一步步推离早期任务的最优区域。尤其是深层网络不同任务之间共享底层特征上层分类器却要不断重写。你很难指望同一个线性层在区分“猫和狗”之后还能无伤区分“轿车和卡车”。有一类理论工作把遗忘分析落在最简单的线性模型上当模型是线性映射时新任务的最优梯度方向对旧任务参数的影响可以被精确推导。结论是如果不对更新方向做任何约束SGD每一步都会在旧任务的重要子空间里留下破坏性的更新分量。这不是玄学是数学上可以证明的。1.3 稳定性-可塑性困境持续学习的一切矛盾都从这里开始理解了遗忘机制就能引出持续学习最核心的矛盾——稳定性-可塑性困境Stability-Plasticity Dilemma。可塑性Plasticity指模型吸收新知识的能力模型需要足够灵活才能学会新任务。稳定性Stability指模型保留已有知识的能力模型需要足够稳定才不遗忘旧任务。这两个需求本质上是互斥的。过拟合新任务旧知识就会被覆盖过度保护旧参数新任务又学不进去。持续学习的所有方法本质上都是在稳定性和可塑性之间寻找一个可接受的平衡点。这也是为什么很多初学者一开始会对持续学习的方法感到困惑为什么有的方法要冻结网络的一部分参数为什么有的方法要存一批旧样本为什么有的方法在损失函数里加各种正则项这些操作全都是为了调节稳定性和可塑性的配比。读这篇综述时建议把“稳定性-可塑性”这个框架记在心里后面所有方法你都可以问一句它在哪个环节上调节了这对矛盾2. 读这篇综述前需要建立的坐标系场景、流程与指标2.1 三种持续学习范式任务增量、域增量与类别增量持续学习领域最让人头疼的第一件事就是不同论文里的“任务设定”可能完全不一样。同一个方法在一种设定下效果很好换一种设定就崩。van de Ven和Tolias提出的三分类现在基本成了领域内的通用语言这篇综述也沿用这套框架来组织方法讨论。范式任务ID是否已知输出空间难度典型场景任务增量Task-IL已知每个任务独立的输出头较简单每次给一个明确任务ID的多任务训练域增量Domain-IL未知共享输出空间不需要区分任务身份中等同一组类别但在不同域下的数据不断到来类别增量Class-IL未知共享输出空间需要区分所有见过的类别最难新类别逐步出现推理时不知道样本来自哪个任务之所以说类别增量最难是因为你不仅要“不遗忘旧类别”还要在推理时对所有已见类别做出统一判断。你无法借助任务ID来选择合适的分类头模型必须自己学会区分新类和旧类。很多在Task-IL设定下表现良好的方法一到Class-IL就失效原因就在这里。2.2 持续学习的一般流程任务序列、训练循环与记忆管理不管是哪种范式持续学习的训练流程都可以抽象成这样一个模式模型接收一个任务序列任务按顺序到达。在第t个任务上模型只能访问当前任务的数据以及可选的记忆缓冲区。训练完后模型要能同时处理所有已见任务。综述里把这一过程拆成了几个可组合的组件数据流、任务调度、模型更新策略、记忆管理策略、评测协议。大多数持续学习方法其实就是在这些组件里做了不同的设计。以记忆管理为例经验回放Experience Replay, ER会在每个任务上抽一部分样本存入缓冲区后续训练时混入旧样本一起更新。但缓冲区大小有限选哪些样本存、存满后淘汰哪些样本、每次混入多少旧样本都是一套需要设计的策略。光这一点就能写出一堆论文。2.3 平均准确率、平均遗忘量与迁移指标的正确打开方式看持续学习论文最常用的两个指标是平均准确率Average Accuracy, ACC)和平均遗忘量Average Forgetting, BWT。它们的定义并不复杂假设一共有T个任务R_{T,i}表示训练完第T个任务后模型在第i个任务测试集上的准确率。平均准确率就是所有任务测试准确率的均值ACC (1/T) * Σ R_{T,i}。平均遗忘量衡量的是“训练新任务让旧任务掉了多少”BWT (1/(T-1)) * Σ (R_{T,i} - R_{i,i})即每个旧任务在训练完最终任务后的准确率减去它刚训练完时的准确率。注意一个容易踩的坑只看“最后一个任务的准确率”或者“训练完成那一刻的ACC”是不够的。持续学习的核心诉求是知识累积所以必须看整个任务序列结束后所有任务的平均表现。而且BWT如果是负的说明存在明显遗忘如果是正值说明新任务反而提升了旧任务表现迁移效应这种情况在相关任务序列中偶尔会出现。另外很多论文还会报告前向迁移FWT用来衡量旧知识对新任务学习的促进作用。但FWT的定义在不同论文里略有差异横向对比时要留个心眼。3. 理论部分不是摆设这篇综述帮我们划出的理论地图3.1 有界记忆下持续学习可以做到什么程度很多入门读者会直接跳过综述的理论章节我觉得这非常可惜。理论部分恰恰回答了持续学习领域一个根本性的问题如果给你无限的计算和存储资源持续学习会不会变得很简单答案可能出乎你的意料。Knoblauch等人在2020年发表了一篇很有冲击力的论文标题就叫“Optimal Continual Learning has No Memory”。他们的结论是在在线学习的框架下如果要求学习器在每一步都能达到接近最优的泛化误差那么学习器需要存储的记忆量会随着任务数量增长而无界增长。通俗点说一个固定容量的系统无论你怎么设计算法都不可能是一个完美的持续学习器。你只能做取舍选择哪些知识值得长期保留哪些知识可以被牺牲。这个结论不是在否定持续学习的研究意义而是在告诉我们持续学习的本质是一个有界资源下的优化问题工程上的核心是“如何用好有限的内存”。3.2 遗忘为何不可避免优化动态与收敛性视角另一个被广泛讨论的理论方向是从优化动态来分析遗忘。对于线性模型研究者可以精确刻画SGD在非平稳任务流上的收敛行为新任务的数据分布会主导梯度方向而旧任务的有效梯度由于只存在于历史数据中会随着时间推移而衰减。这里有一个直觉上的解释很多持续学习场景里旧任务数据不会再出现除非你存了回放缓冲区所以旧任务对梯度的贡献等于零。模型只能依靠当前任务的梯度来更新参数自然会被当前任务“带跑”。正则化方法之所以有效是因为它在损失函数里人为加入了一个“旧任务的影子”——用参数重要性和惩罚项来模拟旧任务数据存在时的约束。但影子毕竟是影子一旦任务数量太多、序列太长正则化的约束强度往往不够。3.3 从信息论看持续学习有限记忆应该存什么综述还花了不少篇幅讨论信息论视角。在有限记忆约束下如何决定哪些样本值得存哪些参数值得保护本质上是一个信息选择问题。比如在回放缓冲区管理中早期从任务里随机抽样本存入缓冲区的算法很简单但效果不稳定。后来研究者发现如果按照样本的多样性或代表性来选——比如优先保留那些离当前模型决策边界最近的样本或者覆盖类别中心的样本——回放效果会明显提升。这些策略背后都能找到信息论的理论支撑在有限容量下保留信息量最大的记忆。我觉得这一节特别适合给做工程的同学读。它不会直接告诉你API怎么调但会帮你建立一种思维方式当你设计一个持续学习系统时先问自己“我的记忆预算花在哪里最值”比盲目堆方法更有效。4. 方法篇的五大赛道哪些真正经得住复现4.1 正则化路线EWC和LwF的启示与局限正则化方法是最早被广泛研究的持续学习路线。EWC的思路是训练新任务时对参数更新加一个二次约束让模型不要大幅改变那些对旧任务“重要”的参数。重要程度用Fisher信息矩阵的对角线来估计。LwF则走了另一条路不约束参数而是约束输出训练新任务时把旧模型在新样本上的输出作为软标签让新模型在适配新任务的同时尽量保持旧任务的输出分布。这其实就是知识蒸馏Knowledge Distillation的思路。这两种方法在短期序列上效果不错也不像回放方法那样需要存储原始样本适合隐私敏感的场景。但我在实际复现中发现它们有几个共同问题EWC对Fisher矩阵的估计方式很敏感不同实现之间结果差异很大。正则化方法一旦任务数超过10个旧任务的保护强度会逐渐衰减遗忘率会快速上升。LwF的蒸馏损失权重需要调参不同任务序列的最佳值差别很大很难一次调好。4.2 回放路线为什么“存旧样本”依然是综合效果最好的思路如果只能选一种持续学习方案落地我大概率会选回放Replay方法。这不是因为它理论多复杂而是因为在绝大多数基准上它简单且有效。经验回放ER的思路极其朴素每个任务训练时抽一部分样本存起来后续任务训练时每个batch里混入一部分旧样本一起计算损失。就这么简单的操作在很多基准上都能超过复杂的正则化方法。后续的改进版本比如DERDark Experience Replay在回放的基础上额外保存了旧模型在回放样本上的logits训练时让新模型同时匹配真实标签和这些历史logits效果进一步提升。A-GEM则利用回放样本来修正梯度方向保证更新不会让旧任务的损失变大。我这里写一个最小可跑的ER训练骨架帮你建立直观感受for task_id, (train_loader, test_loader) in enumerate(task_sequence): model.train() for x, y in train_loader: loss criterion(model(x), y) if memory_buffer is not None: mem_x, mem_y sample_from_buffer(memory_buffer, batch_size32) loss criterion(model(mem_x), mem_y) optimizer.zero_grad() loss.backward() optimizer.step() # 每个任务结束后挑选样本存入缓冲区 update_buffer(model, memory_buffer, train_loader, buffer_size) # 评测所有已见任务 evaluate_all_tasks(model, test_loaders)回放方法的最大争议在于隐私和存储。有些行业的数据不能长期留存回放就会有合规风险。但换个角度看工程上很多团队其实拿到的就是脱敏后的特征数据或嵌入向量这时候回放完全可行。所以在选择技术路线之前先搞清楚你的数据合规边界。4.3 架构路线扩展式、掩膜式与模块化架构方法试图从结构上规避灾难性遗忘。最简单的思路是每来一个任务就扩展新的网络分支比如Progressive Neural Networks新任务可以复用旧任务的特征旧分支的参数完全冻结。这种方法的代价是参数规模随任务数线性增长任务多了之后难以接受。PackNet和HAT走的是另一个方向固定网络容量通过剪枝或注意力掩膜为每个任务分配互不重叠的参数子集。这类方法在任务增量场景下效果好而且推理时可以精确控制每个任务使用的参数但前提是你知道当前样本属于哪个任务。在类别增量场景下HAT这类方法需要改造才能使用。模块化的思路在最近的LLM时代重新焕发生机。比如用LoRA或Prefix Tuning这类参数高效微调方法为每个新任务训练一组轻量级模块把它们挂在同一个预训练模型上。这本质上也是一种架构方法——通过隔离任务参数来消除遗忘。如果你现在要在大模型上做持续指令微调这几乎是首选方案。4.4 表示学习预训练时代的持续学习机会这一条路线是我认为最有前景的方向。它的核心观点是先用大规模数据把模型训练成一个高质量的特征提取器表示学习阶段再做持续学习时的更新只限定在很小的适配器模块上。为什么这条路有效因为灾难性遗忘往往发生在表示层被大幅更新的时候。如果基础表示足够通用能适应各种下游任务那每次新任务来时只需要学习一个轻量级的头部或者适配器旧任务几乎不受影响。InfLoRA等方法的实验结果也印证了这一点在预训练模型上做低秩适配可以比从头训练的回放方法获得更高的平均准确率和更低的遗忘量。更关键的是它不需要存储原始数据只在低秩子空间内更新参数。这对从业者有一个现实启发如果你的业务场景允许尽量先做大规模预训练或迁移学习把通用能力做好再用持续学习思路适配下游任务而不是从零开始做持续学习。4.5 从方法对比表里我看到的几个反直觉结论这篇综述里有一张很大的方法对比表汇总了不同方法在多个基准上的表现。仔细看完这张表有几个结论值得留意很多在论文里声称效果极好的复杂方法在较长的任务序列上优势会消失甚至不如最简单的ER。在类别增量场景下不同方法之间的差距会被显著拉大分类头怎么处理是关键瓶颈。数据增强、学习率调度这类训练细节对结果的影响经常被低估。某些看似“方法改进”带来的提升换个训练配置就消失了。这对复现别人的工作是一个提醒持续学习实验的结果方差很大想得到一个可信的结论最好在一个统一的代码库上做至少5次重复实验取均值和标准差。5. 应用篇从论文任务到真实系统5.1 计算机视觉与多模态场景综述在应用部分首先讨论的就是计算机视觉。图像分类是最常见的验证场景但真实业务里更有参考价值的是语义分割、目标检测和实例分割。以语义分割为例新类别不断出现是自动驾驶和遥感分析里的常态。分割任务的标签是像素级的不同类别的像素分布极不均衡直接做持续学习很容易导致新类别淹没旧类别。综述里提到的很多方法在ImageNet分类上表现不错但换到分割任务上就需要额外的类别平衡策略。多模态场景则更复杂。视觉-语言模型在持续学习时既要保持视觉特征的一致性又要适配新出现的文本概念。如果处理不当微调后模型可能在一些旧概念上出现严重的描述退化。5.2 持续指令微调与NLP大模型时代NLP的持续学习问题变成了一个非常实际的问题你的助手模型必须不断吸收新知识、新指令但你不能让它忘了之前学过的能力。最直接的做法是拿新数据在基座模型上继续微调但你会发现指令遵循能力全面崩塌多轮对话变差各种安全对齐被破坏。这就是持续指令微调要解决的难题。综述里提到的方案思路包括用LoRA等低秩适配器把更新限制在低维子空间。在回放缓冲区里保存旧任务的指令样本每次新任务训练时混入。利用蒸馏保证输出分布不大幅漂移。如果你在业务里做过LLM的领域适配应该能体会到这比传统的图像分类持续学习要复杂得多。对话系统的输出是开放的评测基准也远没有统一所以应用的难度也更大。5.3 强化学习与机器人终身学习的最自然舞台为什么说强化学习和机器人是持续学习最自然的舞台因为智能体面对的环境根本不存在“固定的任务集合”。一个机器人在厨房里学会开冰箱之后还要学会端盘子、擦桌子、洗碗每个技能都是一项独立但需要复用经验的任务。机器人领域常用的方法是模块化技能库每个技能对应一个策略模块新技能到来时先判断能不能复用已有模块不能的话再新增模块并维护一个技能调度器。这本质上就是一种架构式持续学习。模型在真实传感器数据上做增量学习还要应对动态环境噪声难度比离线基准高一个量级。5.4 比较容易忽略的行业应用推荐、医疗与异常检测综述还覆盖了一些容易被纯学术研究者忽略的场景。推荐系统是典型的概念漂移场景用户兴趣随时间变化热门物品不断更替模型需要持续跟进。如果每周全量重训成本高且时效差如果在线增量更新又很容易遗忘长期偏好。医疗影像也很有意思。不同医院、不同型号的扫描仪产生的图像分布有差异一个模型在A医院效果好到了B医院往往需要重新适配。但医疗数据不能随意留存和共享所以无回放或少回放的持续学习方法在这里很有价值。工业异常检测则面临的是“新缺陷类型不断出现”的问题。正规的异常检测模型训练时通常只见过“正常”样本但现实中会出现各种没见过的异常形态。持续学习可以让模型在保留正常样本语义的同时不断学习新的异常模式。6. 实操复现把综述当“地图”快速跑通基线6.1 实验环境和工具库选择Mammoth还是Avalanche读再多的综述不如自己跑一次实验。我建议所有入门持续学习的同学先把一个最简单的回放方法复现出来跑通标准的Split CIFAR-100基准。目前最常用的两个代码库是Mammoth和Avalanche。Mammoth由aimagelab团队维护代码简洁实现了大量baselines包括ER、DER、AGEM、GDUMB等。非常适合快速复现论文结论。Avalanche功能更全面支持的任务设定和基准更多适合做系统性的实验管理。我个人更推荐Mammoth作为第一个入手的代码库因为它的代码结构非常清晰没有过多封装方便你去改动和加入自己的方法。6.2 从零实现一个最小可跑的ER式持续学习流程下面是一个基于PyTorch的简化版ER流程。它不追求完整复现论文里的所有细节但能帮你理解持续学习训练循环的核心逻辑。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader class ERModel(nn.Module): def __init__(self, backbone, num_classes): super().__init__() self.backbone backbone self.fc nn.Linear(backbone.feature_dim, num_classes) def forward(self, x): return self.fc(self.backbone(x)) def er_train(task_stream, buffer, buffer_size2000, epochs5, lr0.01): model ERModel(backboneResNet18(), num_classes10) optimizer optim.SGD(model.parameters(), lrlr, momentum0.9) criterion nn.CrossEntropyLoss() for task_id, (train_dl, test_dl) in enumerate(task_stream): model.train() for epoch in range(epochs): for x, y in train_dl: loss criterion(model(x), y) if len(buffer) 0: mem_x, mem_y buffer.sample(batch_size32) loss criterion(model(mem_x), mem_y) optimizer.zero_grad() loss.backward() optimizer.step() # 任务结束后把当前任务的样本按策略存入缓冲区 buffer.add_samples(train_dl.dataset, max_samples_per_taskbuffer_size // (task_id 1)) # 评测所有已见任务 acc_list [] for seen_task_id, (_, test_dl) in enumerate(task_stream[:task_id1]): acc evaluate(model, test_dl) acc_list.append(acc) print(fTask {task_id} done, avg_acc{sum(acc_list)/len(acc_list):.4f})注意这段代码有几个刻意简化之处backbone需要事先输出一个feature_dim属性buffer的sample方法和add_samples方法需要自己实现常见策略是随机采样或者按类别均衡采样。这些细节叠加起来能够显著影响最终效果。6.3 评测时的三个常见坑跑通代码只是第一步真正在评测阶段容易出问题的地方更多。我自己踩过、也看过别人踩过的坑主要有三个。第一个坑是任务ID泄漏。有些方法在训练时记录了当前任务ID评估时也把任务ID喂给模型让模型选择对应的分类头。这在Task-IL设定下是合法的但如果你想做Class-IL的比较就必须确保评测时不提供任何任务ID信息。很多论文效果虚高就是因为隐式地使用了任务ID。第二个坑是模型容量不公。动态架构方法随着任务数增加会消耗更多参数量而回放方法的模型容量是固定的。如果对比时不报告参数量只比平均准确率那架构方法其实是占了便宜。做对比实验时最好把参数量和FLOPs一起报告出来。第三个坑是回放缓冲区的“泄露”。ER方法通常在每个任务结束后把样本存入缓冲区但一些实现会在当前任务的batch里就直接混入当前任务的样本相当于“提前看到了未来”。这个问题会导致实验结果虚高尤其是当任务序列有重叠类别时。7. 这篇综述没完全解决的问题与我的一点看法7.1 评测基准的标准化仍然不足虽然综述梳理了大量方法和基准但整个领域至今没有一个完全统一的评测协议。不同论文用不同的任务划分方式、不同的骨干网络、不同的优化器、不同的训练轮数导致横向对比非常困难。我个人的判断是未来两三年内如果能出现一套像GLUE之于NLP那样的标准化持续学习评测基准对整个领域的推动会比任何单点方法改进都大。7.2 大规模预训练模型带来的新问题综述里有一部分讨论已经涉及大规模预训练但对预训练模型本身的“持续世界知识更新”着墨不多。现在大模型需要不断吸收新知识比如新的新闻事件、新的产品信息、新的代码库更新但又不能产生灾难性的能力退化。从算力角度看全量微调大模型做持续学习成本太高低秩适配和模块化是更现实的方向。但这类方法的长期累积效果比如学到上千个LoRA模块后如何调度、如何避免模块之间干扰目前还没有特别成熟的答案。7.3 理论到工程的转化仍然不够坦白说这篇综述的理论部分和方法部分之间存在一个不小的落差理论告诉我们持续学习需要无界记忆、遗忘是不可避免的但工程上我们希望找到一个“足够好”的折中方案。这个折中究竟在哪里、如何衡量并没有一个统一的理论框架。好在工程问题通常不追求最优解。如果你手头有一个业务系统需要做持续更新我建议先从最简单的方法试起把ER或LoRA这类轻量方案跑通记录不同任务下的性能变化曲线再逐步增加复杂度。持续学习最终考验的不是你会多少种方法而是你能否在一个具体的约束条件下做出合理的取舍。这是我读完整篇综述之后最大的体会也是我最想分享给你们的一句话。