ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

贝叶斯PEFT破解顺序微调遗忘:LoRA后验继承与不确定度落地

贝叶斯PEFT破解顺序微调遗忘:LoRA后验继承与不确定度落地

做过多任务微调的人大概都有过这种体验:第一个任务训完,验证集指标漂亮得让人想发朋友圈;第二个任务接着训,模型对老任务的记忆就像被橡皮擦擦过一样,准确率断崖式下跌。最早我以为这是全量微调的锅——毕竟动的是整个权重矩阵,参数一改,旧知识自然被覆盖。后来换成LoRA这类参数高效微调,可训练参数从几十亿降到几百万,结果遗忘该来还是来,只是跌得没那么难看而已。这个现象逼着我去找更本质的解释:模型学到的到底是“一组权重数值”,还是“一个关于权重的信念分布”?如果把每个任务学到的知识看成后验分布,那么任务序列学习就变成了一个贝叶斯后验不断更新的过程,遗忘的本质是后验被新数据粗暴地拉走、旧的不确定度信息被丢掉。沿着这条线,贝叶斯参数高效微调(Bayesian PEFT)这套组合拳就有了明确的位置:用PEFT把可训练维度压到能算得动后验的规模,用贝叶斯推断把“学过什么”以分布的形式存下来,再在下一个任务里把它当作先验。这篇文章会把这条链路上的方法选型、数学直觉、显存与算力的实际账、以及我在复现时踩过的坑,一次讲清楚。适合已经玩过LoRA、想往持续学习方向再走一步的读者,也适合被“多任务顺序微调必崩”困扰、想找一个有理论支撑的解法的工程同学。

1. 问题定位:PEFT为什么也挡不住遗忘

1.1 灾难性遗忘在顺序微调里的真实形态

先把这个问题的表现说具体。假设你手上有三个任务:任务A是客服问答风格的对话数据,任务B是代码补全,任务C是结构化信息抽取。单任务微调任何一个,指标都能上去。但如果你按A→B→C的顺序在同一个模型上连续微调,训完C再去测A,通常会看到A的准确率掉十几个点甚至更多,而模型在A上的输出风格会明显向C靠拢——回答会开始带表格、带字段名,哪怕你问的是闲聊。

这个现象的机制并不神秘。梯度下降在任务B上优化时,只关心B的损失曲面,它没有任何机制告诉它“这些参数在A上有特殊意义”。全量微调时,几乎所有权重都在动,旧任务依赖的特征方向被反复冲刷。PEFT的做法是冻结主干,只训练一小撮新增参数(LoRA的低秩增量、Adapter的瓶颈层、Prompt的软向量),相当于给每个任务开一个“旁路”。旁路之间理论上互不干扰,可实际训练中,低秩增量会叠加到同一个主干权重上,多个任务的增量在参数空间里仍然共享同一批自由度;更关键的是,PEFT训练时的优化目标依然是“当前任务的损失最小”,它没有保留任何关于历史任务的信息。参数少只是让冲刷的速度慢一点,方向没变。

我做过一组对比:同样三个任务,全量微调的A任务准确率从92%掉到61%,LoRA(r=8)从92%掉到78%,Adapter从92%掉到74%。看起来PEFT好很多,但78%这个数字在业务上是不能接受的——它意味着每上线一个新能力,就要为老能力付一次回归测试和补救微调的代价。

1.2 低秩约束只改变了遗忘的斜率,没改变方向

很多人对PEFT有一个误解,觉得“参数少所以不容易忘”。这个因果关系是错的。遗忘的根源不是参数数量,而是参数更新方向与旧任务重要方向的重叠。低秩约束做的是把更新限制在一个r维子空间里,这个子空间由当前任务的梯度决定,和历史任务的重要性没有半点关系。如果新任务的最优更新方向恰好落在旧任务的关键子空间上,照样会覆盖。

我用一个更直观的类比:主干权重是一本已经写满笔记的书,PEFT相当于在书页边缘贴便利贴。贴便利贴确实不会擦掉原文,但如果新任务的便利贴贴的位置正好盖住了旧任务的关键批注,信息照样读不到。而且便利贴是贴在同一本书上的,多个任务的便利贴会互相遮挡。

真正有效的解法必须回答两个问题:第一,哪些参数方向对旧任务重要,重要到什么程度;第二,新任务的更新如何在不破坏这些方向的前提下进行。这两个问题天然是贝叶斯式的——前者是后验的精度(方差的倒数),后者是带先验约束的后验更新。这就是为什么我最终转向贝叶斯PEFT:它不是一个新trick,而是把“重要性”和“约束”统一到一个概率框架里。

1.3 贝叶斯视角:把“记住”定义成“后验不漂移”

贝叶斯框架下,模型参数 θ 不是一个确定值,而是一个分布 p(θ|D)。在任务序列 D₁, D₂, …, D_T 上学习,理想过程是:

p(θ|D₁,…,D_T) ∝ p(D_T|θ) · p(θ|D₁,…,D_{T-1})

也就是说,当前任务的后验,等于“用上一轮的后验当作先验”再乘上新任务的似然。这里最妙的一点是:上一轮的后验包含了全部历史信息,而且它以分布的形状记录了“哪些方向已经被数据约束得很紧(方差小)、哪些方向还很自由(方差大)”。当新任务的梯度想去改一个方差很小的方向时,先验会把它拉回去;改方差大的方向则几乎不受惩罚。这比EWC那种手动加一个二次惩罚项要自然得多,因为惩罚的强度和形状都是从数据里推出来的。

所以“记住”在贝叶斯语境下的定义是:历史任务的后验没有被新任务显著扭曲。遗忘度量就可以直接定义成两个后验之间的散度(KL散度、或者精度矩阵的差异),而不只是任务准确率的下降。这个视角还有一个额外好处——模型能输出不确定度。当输入落在所有任务都没覆盖的区域时,后验预测的方差会变大,这给了我们一个天然的“我不知道”信号,对线上系统做拒识或转人工非常有用。

1.4 为什么必须和PEFT绑在一起

纯贝叶斯方法(比如对全模型做Laplace近似)在十亿参数级别根本跑不动。后验协方差矩阵是 d×d,d 是参数维度,即使只存对角也要 d 个浮点数。对7B模型,fp32下光对角就是28GB,还没算Hessian估计的前向反向开销。

PEFT把可训练维度压到 0.1%~1%,这才让贝叶斯近似变得可行。以LoRA r=8、隐藏维度4096、32层为例,可训练参数大约 2.1M,对角Fisher只需要存2.1M个数(fp32约8.4MB),单个任务的后验快照完全可以放进显存甚至内存。这就是“贝叶斯 + PEFT”这个组合的工程合理性:PEFT负责把问题缩到可计算的规模,贝叶斯负责把缩完之后剩下的那部分参数用好。

2. 方法骨架:贝叶斯PEFT的四个关键拼图

2.1 先验设计:从各向同性到任务继承

先验的选择直接决定了遗忘的抑制强度。第一个任务没有历史,通常用各向同性高斯 p(θ) = N(0, σ₀²I),σ₀ 控制初始的“自由度”。这个值不能设太小,否则第一个任务都学不动;也不能太大,否则第一个任务的后验会过于弥散,对后续任务毫无约束力。我的经验是让它和LoRA初始化尺度挂钩——LoRA默认用Kaiming初始化,B矩阵初始为零,A矩阵有小方差,把 σ₀ 设在A矩阵初始标准差的1~3倍之间比较稳妥。

从第二个任务开始,先验不再是各向同性,而是上一轮的后验 p(θ|D_{1:t-1})。如果后验用高斯近似 N(μ_{t-1}, Σ_{t-1}),那么整个更新过程就是高斯先验 + 新任务似然的高斯近似,可以得到解析的、递推的后验更新,不需要每轮从头重算。

这里有一个工程上的关键取舍:要不要每轮都做完整的后验继承。完整继承意味着精度矩阵 Σ⁻¹ 要参与计算,而精度矩阵在Laplace近似下等于负对数后验的Hessian,包含旧先验精度和新任务Fisher两部分。递推公式是:

Λ_t = Λ_{t-1} + F_t + λI

其中 Λ 是精度矩阵,F_t 是新任务的Fisher信息,λI 是阻尼项。这个式子非常漂亮——历史精度直接累加,不需要回放旧数据。但它的前提是“每个任务的Fisher都能准确估计”,而Fisher估计本身有噪声,累加多了会漂移。所以实践中我一般只保留最近若干个任务的精度,或者对历史精度做指数衰减,相当于给旧知识设一个半衰期。

2.2 后验近似:三条路线怎么选

后验近似有三条主流路线,各自的适用场景差别很大。

Laplace近似是最省事的一种。先用常规优化拿到MAP解 θ*,再在 θ* 处用二阶泰勒展开,把后验近似成高斯 N(θ*, H⁻¹),H 是负对数后验的Hessian。它的优点是训练代码几乎不用改,训完拿验证集跑一遍就能拟合后验;缺点是它是局部近似,如果后验是多峰的,Laplace会严重低估方差。在PEFT场景下这个缺点没那么致命,因为可训练参数少、损失曲面相对平滑,单峰假设基本成立。

变分推断(VI)用一个可参数化的分布 q_φ(θ) 去逼近真实后验,优化ELBO。优点是能表达更复杂的后验形状,而且训练和推断是一体的;缺点是要改训练循环,还要处理KL项和重参数化,调参成本高。在LoRA上做VI,一般是给B矩阵的每个元素配一个高斯(均值+方差两组参数),参数量翻倍,但换来的是更可靠的不确定度。

MC Dropout / 随机权重平均类方法最轻量,训练时开dropout,推理时多次前向取平均。它几乎不值得单独为持续学习去用,因为dropout率本身不是后验方差的可靠估计,而且它没有显式的“历史先验继承”机制,无法解决遗忘。我只在需要快速验证不确定度是否有信号时用它当baseline。

我的实际选型是:Laplace做主力,VI做对照,MC Dropout只做sanity check。原因是Laplace在PEFT上的性价比最高,而且它的精度矩阵有明确的物理意义(Fisher信息),可以直接拿来做参数重要性分析,一鱼两吃。

2.3 Fisher估计与Kronecker分解的算力账

Laplace近似的核心是估计Fisher信息矩阵。完整Fisher是 d×d,PEFT下 d 是 2.1M 量级,完整矩阵需要 2.1M × 2.1M,约 4.4×10¹² 个元素,完全不可能。所以要近似,常见三种粒度:

第一种是对角近似,只保留每个参数自己的方差,忽略参数间相关性。存储 O(d),计算只需要每个参数的梯度平方的滑动平均。这是最省的方式,2.1M参数下fp32存储8.4MB,一个任务一份,100个任务也才840MB。缺点是忽略了相关性,约束力偏弱,遗忘抑制效果打折。

第二种是Kronecker分解(K-FAC),把每层的Fisher近似成两个小矩阵的Kronecker积:F ≈ A ⊗ B。对线性层 y = Wx,A 是输入激活的协方差(维度是输入维度×输入维度),B 是输出梯度的协方差(输出维度×输出维度)。对LoRA来说这里要特别小心维度账。以 W ∈ R^{d×k} 的旁路 B_lora ∈ R^{d×r} 为例,它的“输入”是 r 维的低秩空间,“输出”是 d 维。所以 A 是 r×r(比如8×8,极小),B 是 d×d(4096×4096,约16M个元素,fp32 64MB/层)。这个账一算就发现:对B_lora做完整K-FAC,单层就要64MB,32层就是2GB,多任务完全扛不住。所以实际做法是对 A(r×r那侧)做完整Kronecker,对B那侧退回对角,或者做分块对角(把4096切成若干块,块间独立)。

第三种是低秩 + 对角混合,用少量特征向量捕捉主要的相关性方向,剩下的用对角补齐。这是我在显存紧张时的折中方案,效果介于前两者之间。

近似粒度存储(2.1M参数)单任务拟合耗时遗忘抑制适用场景
对角8.4MB秒级中等任务数多、显存紧
完整K-FAC(全层)约2GB分钟级强任务数≤5、单机多卡
混合(A侧K-FAC+B侧对角)约40MB十秒级较强大多数生产场景
低秩+对角约20MB十秒级较强显存敏感

2.4 预测阶段:采样、集成与不确定度

后验拟合完之后,推理有两种用法。第一种是只用后验均值,也就是把 θ* 当普通模型用,贝叶斯只贡献了训练时的正则。第二种是从后验采样K次,对输出做集成。第二种才能拿到不确定度,也是贝叶斯PEFT真正的价值所在。

采样的实现方式是从 N(μ, Σ) 里抽K组参数,每组跑一次前向,得到K组logits。集成的做法有两种:logit空间平均和概率空间平均。我实测下来概率空间平均(先softmax再平均)在分类和生成任务上都更稳,尤其是当不同样本的后验方差差异大时,logit平均容易被某一组极端参数带偏。

不确定度可以用预测熵(总不确定度)和互信息(认知不确定度,等于总不确定度减去各样本平均熵)。互信息高说明“模型没见过这类输入”,这是拒识信号;预测熵高但互信息低说明“数据本身有噪声”,这是另一类问题。这两个信号分开看,比单一熵值有用得多。

代价是推理延迟随K线性增长。K=8时延迟约8倍,这在离线批处理场景可以接受,在线场景要谨慎。我的做法是:默认K=1(只用均值),只有在置信度低于阈值或业务要求必须给置信区间时才启用K=8采样。这样平均延迟只增加很少,但保留了兜底能力。

3. 实操落地:一套可复现的贝叶斯LoRA流程

3.1 环境与代码骨架

依赖很常规:PyTorch、transformers、peft,加上一个做矩阵分解的库(torch本身就够)。核心是自己实现三件事:Fisher估计、后验拟合、采样推理。peft库里的LoRA层可以直接用,只是要在训练结束后把LoRA参数拎出来做后验拟合。

目录结构建议这样组织,方便按任务序列管理后验快照:

bayes_peft/ ├── model/ # 主干加载与LoRA注入 ├── fisher/ # Fisher估计(对角/K-FAC) ├── posterior/ # 后验拟合、序列继承、快照存取 ├── infer/ # 采样集成与不确定度 └── tasks/ # 各任务的训练脚本与数据管线

关键设计是把“后验快照”做成独立文件,每个任务训练完存一份,包含均值、精度(或方差)、以及该任务用到的Fisher近似方式。这样任务序列可以中断、可以回滚,也可以做不同任务组合的实验。

3.2 训练阶段:先MAP,再拟合后验

第一阶段和普通LoRA微调完全一样,用交叉熵或任务对应的损失正常训。这一步拿到MAP解 θ*。注意一个细节:训练时最好固定随机种子并记录数据顺序,因为后验拟合需要在同一批数据上重算梯度,如果数据顺序差太多,Fisher估计会有偏差。

第二阶段拟合后验。以对角近似为例,核心逻辑是遍历校准集(一般从训练集里留一小部分,或用验证集),对每个batch算损失对LoRA参数的梯度,累加梯度平方,最后取平均得到Fisher对角,再和先验精度相加、求逆得到方差。

import torch from torch import nn def estimate_diag_fisher(model, dataloader, params, device, max_batches=200): fisher = {n: torch.zeros_like(p) for n, p in params.items()} model.eval() n_seen = 0 for i, batch in enumerate(dataloader): if i >= max_batches: break batch = {k: v.to(device) for k, v in batch.items()} model.zero_grad() out = model(**batch) loss = out.loss loss.backward() for n, p in params.items(): if p.grad is not None: fisher[n] += p.grad.detach() ** 2 n_seen += 1 for n in fisher: fisher[n] /= max(n_seen, 1) return fisher def fit_laplace_diag(fisher, prior_precision, damping=1e-3): # 精度 = 历史先验精度 + 当前任务Fisher + 阻尼 precision = {n: prior_precision[n] + fisher[n] + damping for n in fisher} variance = {n: 1.0 / precision[n].clamp(min=1e-8) for n in fisher} return precision, variance

这段代码有几个参数值得单独说。max_batches控制Fisher估计的样本量,我一般取100~200个batch,再多收益递减而耗时线性涨。damping是阻尼项,防止Fisher在某些参数上接近零导致方差爆炸(除零)。阻尼值不能太大,太大会把所有方向的约束都抹平,等于失去了贝叶斯的意义。经验值是按Fisher全局均值的1%~5%来设,比固定常数稳。

3.3 任务切换:后验继承的两种实现

任务之间的衔接是整套流程的核心。有两种实现方式,我都在项目里跑过。

方式一:精度累加(解析递推)。每个任务训练前,把上一轮的精度矩阵加载进来当作先验精度,训练后新精度 = 旧精度 + 新Fisher + 阻尼。优点是零额外计算,缺点是精度只增不减,任务多了之后先验会越来越“硬”,新任务学不动。解决方法是给旧精度乘一个遗忘因子 γ(0.8~0.95),相当于给旧知识设半衰期。

方式二:均值-方差联合继承。不止继承精度,也继承均值,并把上一轮均值作为本轮训练的初始化(而不是重新初始化LoRA)。同时在损失里加一个软约束项,让参数不要偏离先验均值太远:

def bayes_regularized_loss(task_loss, params, prior_mean, prior_var, coef=0.1): reg = 0.0 for n, p in params.items(): reg = reg + (((p - prior_mean[n]) ** 2) / prior_var[n]).sum() return task_loss + coef * reg

这个正则项本质上就是Laplace先验导出的二次惩罚,和EWC形式一样,但方差是从Fisher推出来的、逐参数不同的,而不是全局一个常数。coef我一般从0.05起调,任务差异大时调高,任务相似时调低。实测中系数超过0.5会导致新任务欠拟合,需要配合学习率一起调。

提示:切换任务时一定要把优化器状态(尤其Adam的动量)清掉。Adam的二阶矩是跟着旧任务梯度走的,带着它进新任务会让先验约束被历史动量抵消,这是我在早期最容易忽略的一个坑。

3.4 推理阶段:采样集成的实现细节

推理时从后验采样,代码不复杂但有几个细节决定成败。采样要在参数空间做,且要保证同一批样本用同一组参数(否则batch内不一致会引入噪声)。生成任务还要注意:K组参数各自生成完整序列再集成,不能逐token混着采。

@torch.no_grad() def predict_with_posterior(model, batch, params, mean, var, K=8): logits_list = [] for _ in range(K): # 从对角高斯采样一组LoRA参数 for n, p in params.items(): eps = torch.randn_like(p) p.copy_(mean[n] + eps * var[n].sqrt()) out = model(**batch) logits_list.append(out.logits) # 概率空间平均 probs = torch.stack([torch.softmax(l, dim=-1) for l in logits_list], dim=0) mean_prob = probs.mean(0) pred_entropy = -(mean_prob * (mean_prob + 1e-12).log()).sum(-1) avg_entropy = -(probs * (probs + 1e-12).log()).sum(-1).mean(0) mutual_info = pred_entropy - avg_entropy # 采样完恢复均值参数 for n, p in params.items(): p.copy_(mean[n]) return mean_prob, pred_entropy, mutual_info

K的选择是个纯工程问题。K=4时不确定度估计已经很粗糙但可用,K=8是精度和延迟的平衡点,K=16以上边际收益很小。真正影响效果的是方差是否校准——如果Fisher估计有偏,采样出来的方差会系统性偏大或偏小,导致互信息信号失真。所以我在上线前一定会做一次校准检查:在验证集上算预测置信度和实际准确率的对应关系,画可靠性图,如果偏差大就用温度缩放修一下。

4. 参数与成本的实账

4.1 显存与存储的估算方法

做方案设计时,先把账算清楚比什么都重要。以7B模型、LoRA r=8、作用于q/k/v/o四个投影层、32层为例:

项目计算方式估算值
可训练参数4层 × 32层 × (4096×8 + 8×4096)约8.4M
对角Fisher存储8.4M × 4字节约34MB
后验均值+方差2 × 34MB约68MB
单任务后验快照均值+方差约68MB
10个任务全保留68MB × 10约680MB
K=8采样推理峰值主干 + 一份参数副本主干显存 + 34MB

这组数字说明一件事:贝叶斯PEFT的额外开销几乎可以忽略,瓶颈从来不在存储,而在Fisher估计的前向反向次数和采样推理的延迟。所以方案的优化重点应该放在“减少采样批次数”和“降低推理K值”上,而不是纠结显存。

4.2 阻尼与遗忘因子的调参经验

这两个参数是整套方法里最需要手调的,而且没有通用最优值。

阻尼damping的作用是数值稳定,取值太大则约束失效。我的调法:先算Fisher全局均值 μ_F,把阻尼设在 0.01μ_F 到 0.05μ_F 之间,然后看第一个任务的遗忘曲线——如果新任务学完后老任务掉得厉害,说明阻尼太小,约束不够;如果新任务本身指标上不去,说明阻尼太大。这个二分过程一般两三轮就能收敛。

遗忘因子 γ 控制旧精度的衰减。γ=1 是完全不遗忘,任务多了会僵化;γ=0.8 意味着大约三个任务之前的约束只剩一半。我的经验是:任务之间差异大(比如从对话到代码)时 γ 取0.9以上,保住旧能力;任务之间相似(同一领域的多个子任务)时 γ 取0.7~0.8,让模型有空间共享表示。这个参数本质上是在“稳定”和“可塑性”之间做权衡,和持续学习里的稳定性-可塑性困境是同一件事。

4.3 采样次数与业务延迟的换算

假设单次前向延迟是 T,K次采样的延迟约是 K·T(batch内可以并行,所以实际是 K 倍计算量而不是 K 倍串行时间)。如果 T=50ms,K=8,那就是400ms,对实时接口已经偏慢。

我的分层策略是:主路径K=1,只跑均值;对均值预测的置信度低于阈值(比如最大概率<0.6)的样本,触发K=8采样重算,用互信息判断是否拒识。实测下来触发比例通常在5%~15%,平均延迟增加不到10%,但把“高置信错误”的比例压下去了一大截。这套策略在业务上比无差别采样划算得多。

5. 踩坑实录与排查清单

5.1 常见问题速查

现象最可能的原因排查与解法
新任务训完老任务暴跌先验精度未生效或阻尼过小打印各参数方差分布,确认新任务参数方差显著小于旧任务;调大阻尼
新任务指标明显偏低先验过强,模型被锁死降低遗忘因子γ,或给新任务参数单独放宽先验
不确定度对所有输入都很高Fisher估计样本太少或方差未校准增加校准batch数;检查温度缩放是否缺失
多任务后验快照越来越大精度只累加未衰减引入遗忘因子,或只保留最近N个任务的精度
采样推理结果不稳定采样参数未在batch内共享确认同一batch所有样本用同一组采样参数
后验均值与训练末参数不一致拟合后未回写均值在fit后显式把MAP解存入均值,避免用采样残留值
生成任务集成后输出重复概率空间平均导致分布尖锐化改用logit平均或对概率做温度平滑

5.2 几个不写在论文里的实操经验

第一条,Fisher估计的校准集要覆盖任务的真实分布,而不是只用训练集末尾。我早期图省事,直接拿训练集最后100个batch算Fisher,结果这些样本恰好是长尾难例,Fisher被少数参数主导,后验方差形状严重偏斜,导致后续任务的约束方向完全错位。后来改成从训练集分层抽样,覆盖各长度、各类型,遗忘抑制效果立刻稳定了。原因很直白:Fisher是“平均意义下的曲率”,采样分布不对,曲率估计就不对。

第二条,LoRA的A、B矩阵要用不同强度的先验。B矩阵初始为零、A矩阵有小随机值,两者在训练中的角色不对称。实测中对B矩阵(输出侧)施加更强的先验约束、对A矩阵(输入侧)放松,效果比一视同仁好。直觉是B决定了“往哪个方向写回主干”,改动它的破坏性更大;A更像特征选择器,容错空间更大。这个不对称先验没有理论保证,但在我的几个任务序列上都稳定复现了收益,值得作为调参起点。

第三条,不要在训练中途做后验继承。后验拟合必须在任务训练完全结束后做,因为训练过程中的Fisher是随参数漂移的,中途继承会把噪声当成信号。同样,跨任务的后验继承也应该是“上一个任务完全结束”这个原子操作,不要和当前任务的训练交错,否则精度矩阵的递推关系会被破坏。

第四条,评估遗忘要用后验散度而不只是准确率。准确率下降可能是数据分布差异导致的,不一定是后验漂移。我现在的做法是同时记录两个指标:任务准确率矩阵(第i个任务训完后在第j个任务上的表现),以及相邻任务后验之间的KL散度(对角近似下可以直接解析算)。如果KL散度很小但准确率掉了,说明问题在数据侧;如果KL散度很大,那才是真的遗忘,需要回头调先验强度。

5.3 效果评估与消融设计

评估持续学习,光看最终平均准确率是不够的,至少要看三个量:最终平均准确率、反向迁移(BWT,衡量老任务被新任务影响的程度)、以及不确定度校准误差(ECE)。BWT的计算方式是所有任务对 (i<T) 的 (R_{T,i} − R_{i,i}) 的平均,负数越大说明遗忘越严重。

消融实验我一般做四组:纯LoRA无贝叶斯(baseline)、对角Laplace、混合K-FAC、以及去掉先验继承只保留后验采样。这四组能分别回答“贝叶斯有没有用”“近似粒度影响多大”“遗忘抑制是来自先验还是来自采样集成”三个问题。实测中先验继承贡献了大部分遗忘抑制收益,采样集成主要贡献的是不确定度质量和少量准确率提升,两者作用不同,不能混为一谈。

6. 往后可以怎么扩展

这套框架有两个方向我觉得值得继续挖。一个是把任务身份也贝叶斯化。现在的方法默认任务边界已知(知道什么时候切换先验),但真实场景往往是任务边界模糊的流式数据。可以对“当前属于哪个任务”也建模一个后验,用新样本的似然做在线更新,后验熵高时自动放宽先验,实现无监督的边界检测。

另一个是后验的压缩与共享。多个任务的后验如果高度相似,没必要各存一份,可以做后验的聚类或低秩合并,把存储和计算再降一个量级。这在任务数上百的场景下会有实际价值。

我自己在项目里最深的体会是:贝叶斯PEFT不是一个“加个模块就完事”的技术,它改变的其实是训练流程的组织方式——你得接受“训练不是一个任务一个任务地跑完就完”,而是要维护一份持续演化的后验状态。这份状态管理做得好,遗忘就是可控的;做得不好,贝叶斯也只是给遗忘加了一层好看的包装。流程上的纪律比算法本身更重要,这是踩了大半年坑之后最实在的结论。

返回列表