☰
宽度学习系统BLS增量学习:伪逆更新实现高效流式训练
2026/10/17 13:49:11 网站建设 项目流程

1. 从一篇读书笔记说起:为什么我又翻出了这篇老论文

前阵子整理硬盘,翻到几年前做在线推荐系统时留下的一批读书笔记,其中一篇标题写着“Broad Learning System: An Effective and Efficient Incremental Learning System”。当时我正被一个很现实的问题折磨:模型每天都要吃新数据,但每来一批新样本就全量重训一次,训练窗口越拖越长,线上延迟越来越难看。那段时间我几乎把能想到的增量方案都试了一遍,从最朴素的微调,到各种带正则的增量更新,最后才认真坐下来把这篇讲宽度学习系统(Broad Learning System,后面统一简称 BLS)的论文从头啃到尾。

这篇笔记的核心,其实就一句话:它提出了一种不靠深度堆叠、而是靠“宽度扩展”的增量学习框架,新数据来了不需要把整个网络推倒重来,只需要增量地更新一部分权重,甚至可以用伪逆的增量公式直接算出新解。它解决的问题非常具体——在数据持续流入的场景下,如何既保证模型能吸收新知识,又不让训练成本随数据量线性甚至指数级上涨。适合谁来参考?做在线学习、流式数据、推荐、异常检测、工业设备监测这类“数据永远在变”的工程师,以及想理解增量学习底层数学原理的学生和研究者。

我写这篇笔记的目的不是复述论文,而是把当时踩过的坑、推导时卡住的点、以及后来在真实项目里验证过的做法整理出来。如果你也在为“模型天天重训”发愁,或者单纯想搞懂 BLS 到底凭什么能做到高效增量,那这篇内容应该能帮你省下不少翻论文的时间。

2. 增量学习到底难在哪:先搞清楚问题再谈方案

2.1 全量重训的代价,远比想象中高

很多人一开始会觉得,数据多了就重训呗,反正现在算力便宜。但真正做过线上系统的人都知道,全量重训的代价不只是那几小时 GPU 时间。它至少包含三块隐性成本:第一是训练窗口,数据量从十万涨到千万,训练时间可能从几分钟涨到几小时,这期间模型是“冻结”的,新数据带来的趋势变化完全吸收不进去;第二是存储与回放成本,为了重训你得把历史数据都留着,冷热分层、样本采样、数据版本管理全来了;第三是上线风险,每次全量重训相当于换了一个新模型,效果波动、特征分布漂移、A/B 实验周期,都是实打实的工程负担。

我当年做的一个流式场景,数据每小时新增约 20 万条,如果每小时全量重训,单次训练要 40 分钟以上,等于一半时间模型都在“追赶”。更麻烦的是,历史数据里有很多已经过时的行为模式,全量重训反而会把旧噪声重新学一遍。这就是增量学习要解决的核心矛盾:新知识要吸收,旧知识不能灾难性遗忘,同时计算量不能随数据累积而爆炸。

2.2 灾难性遗忘:增量学习绕不开的坎

说到增量学习,就不得不提灾难性遗忘(Catastrophic Forgetting)。简单类比:你让一个已经学会骑自行车的人去学开汽车,如果训练方式不对,他可能把骑车的平衡感彻底忘了。神经网络也一样,用新数据微调时,权重会朝着新任务方向大幅偏移,导致旧任务性能断崖式下跌。

常见的应对思路有几类:一是正则化类,比如在损失里加一项约束重要权重不要变太多;二是回放类,保留一部分旧样本,和新数据混着训;三是参数隔离类,给不同任务分配不同子网络。这些方法各有各的麻烦:正则化要算 Fisher 信息矩阵,回放要存旧数据且采样策略敏感,参数隔离则容易导致模型越来越臃肿。而 BLS 走的是另一条路——它把网络结构设计成“宽度可扩展”,新数据来了就加新节点,然后用增量伪逆更新输出权重,从数学上绕开了对旧权重的破坏性修改。

2.3 BLS 的定位:不拼深度,拼宽度和增量

BLS 最反直觉的一点是:它不追求“深”。传统深度学习靠堆层数来提升表达能力,BLS 则把网络做成“宽”的——输入经过一组随机映射生成特征节点(Feature Nodes),这些特征节点再经过一组随机映射生成增强节点(Enhancement Nodes),最后所有节点拼在一起,只训练到输出层的权重。因为中间层的映射是随机固定的,不需要反向传播,所以训练本质上变成了解一个线性方程组,可以用伪逆直接求闭式解。

这个设计带来的直接好处就是增量更新极其自然。新数据来了,你不需要动中间那些随机映射,只需要把新数据对应的节点激活值拼到原来的矩阵后面,然后用伪逆的增量公式更新输出权重。整个过程没有梯度下降、没有学习率调参、没有反向传播,计算量主要花在矩阵运算上,而且可以做到“来一批更新一次”。这就是它标题里“Effective and Efficient”的底气所在。

3. BLS 的核心结构拆解:三层结构,一次伪逆

3.1 特征节点:把输入映射到高维随机空间

BLS 的第一层是特征节点。给定输入数据 X,它做的是 Z = φ(XW_e + β_e),其中 W_e 和 β_e 是随机生成并固定的权重和偏置,φ 通常用线性或稀疏表示。你可以把这步理解成“用一堆随机投影把原始特征打散到更高维的空间”,类似随机森林里随机选特征的感觉,但这里是连续的线性映射加激活。

为什么用随机映射而不是学习出来的映射?因为一旦映射固定,整个网络的前半部分就不需要训练了,这直接把问题从非凸优化降级成了线性最小二乘。随机映射的理论依据来自随机向量函数链接网络(RVFL)和极限学习机(ELM)那一脉,核心思想是:只要随机映射的维度足够高,高维空间里的线性可分性就足够好,不需要反复调那些中间层。

实际操作中,特征节点组数和每组节点数是两个关键超参。我一般会先设 10 组、每组 20 到 50 个节点,然后根据验证集效果微调。组数太少,表达能力不够;组数太多,伪逆计算量上升,而且容易过拟合。这里有个经验:特征节点总数建议控制在输入维度的 2 到 5 倍之间,太小欠拟合,太大收益递减。

3.2 增强节点:在特征基础上再做一次非线性扩展

第二层是增强节点。它把第一层生成的特征节点 Z 再经过一次随机映射,得到 H = ξ(ZWh + βh),ξ 通常用 tanh 或 sigmoid 这类非线性函数。增强节点的作用是增加非线性表达能力,相当于在特征空间里再做一次“扭曲”,让最终拼出来的大矩阵 [Z|H] 更容易线性分离。

这里有个容易踩的坑:很多人以为增强节点越多越好,其实不然。增强节点和特征节点之间存在信息冗余,如果增强节点太多,伪逆求解时矩阵条件数会变差,数值稳定性下降。我的做法是增强节点总数不超过特征节点总数的 1.5 倍,并且优先保证特征节点的质量。另外,增强节点的随机权重建议用较小的方差初始化,否则 tanh 容易饱和,梯度信息(虽然这里不用梯度)对应的激活值会集中在两端,降低区分度。

3.3 输出层:伪逆求解,一步到位

最后一层是输出层。把特征节点和增强节点拼接成一个大矩阵 A = [Z|H],输出权重 W 通过求解 A W = Y 的最小二乘解得到,即 W = A⁺ Y,其中 A⁺ 是 A 的 Moore-Penrose 伪逆。如果 AᵀA 可逆,伪逆可以写成 (AᵀA)⁻¹Aᵀ;实际计算中为了数值稳定,通常用奇异值分解(SVD)或者带正则的岭回归形式 W = (AᵀA + λI)⁻¹AᵀY。

这一步是整个 BLS 的灵魂。因为中间层固定,训练变成了一个凸的线性最小二乘问题,有闭式解,不需要迭代。这意味着训练速度极快,而且解是全局最优的(在给定随机映射的前提下)。我实测过,在几万样本、几千节点的规模下,一次伪逆求解在普通 CPU 上也就几秒到几十秒,比反向传播快一个数量级。

注意:伪逆求解对矩阵条件数敏感,如果 A 的列之间高度相关,求逆会不稳定。加正则项 λ 是必须的,λ 一般取 1e-8 到 1e-3 之间,具体看数据尺度。

4. 增量更新怎么实现:伪逆的增量公式才是重点

4.1 新数据来了,为什么不用重算整个伪逆

假设原来已经有矩阵 A_n(n 个样本对应的节点激活),输出权重 W_n = A_n⁺ Y_n。现在来了一批新数据,对应的节点激活是 A_m,标签是 Y_m。最笨的做法是把 A_n 和 A_m 拼起来重新求伪逆,但这样计算量随历史数据累积而增长,失去了增量的意义。

BLS 的增量更新利用的是伪逆的秩一更新思想推广到块更新。核心公式是:新的输出权重可以写成旧权重加上一个修正项,修正项只依赖于新数据对应的节点激活和旧伪逆。具体推导涉及分块矩阵求逆引理,这里不展开全部数学,但结论很清晰:更新一次的计算量只和新数据的规模以及节点总数有关,和历史数据量无关。这就是它“Efficient”的关键。

4.2 增量更新的两种模式:加样本和加节点

BLS 的增量其实有两种:一种是增量样本,即节点结构不变,只来新数据;另一种是增量节点,即发现模型表达能力不够,增加新的特征节点或增强节点。两种模式对应不同的更新公式。

增量样本的更新相对简单,就是上面说的伪逆块更新。增量节点则更复杂一些,因为新增节点会改变矩阵 A 的列数,需要重新计算伪逆,但可以利用旧伪逆做低秩修正,避免从头算。论文里给出了具体的递推公式,我当时的笔记里把这两种模式分别记成了“横向增量”和“纵向增量”,横向加样本,纵向加节点。

实际项目中,我更多用的是增量样本模式,因为节点结构一旦定下来,频繁改结构会带来工程复杂度。但如果你发现模型在新数据上持续欠拟合,那就该考虑增量节点了。

4.3 一个可复现的增量更新流程

下面是我整理的一个最小可复现流程,用伪代码表示,方便你直接对照实现:

# 初始化阶段 X_init, Y_init = load_initial_data() Z = feature_mapping(X_init) # 随机映射,固定 H = enhancement_mapping(Z) # 随机映射,固定 A = concat([Z, H], axis=1) W = pinv(A) @ Y_init # 初始输出权重 # 增量阶段,每来一批新数据 for X_new, Y_new in stream: Z_new = feature_mapping(X_new) H_new = enhancement_mapping(Z_new) A_new = concat([Z_new, H_new], axis=1) # 利用旧伪逆做增量更新,具体公式见论文 W = incremental_pinv_update(A, A_new, W, Y_new) A = concat([A, A_new], axis=0) # 可选,用于后续更新

这里有个工程细节:A 要不要一直保留?如果保留,内存会随数据增长;如果不保留,后续增量更新需要旧伪逆,而旧伪逆可以从 A 和 W 反推。我的做法是只保留旧伪逆和旧权重,不保留全部历史激活矩阵,这样内存占用是常数级的,非常适合流式场景。

5. 实测对比:BLS 增量 vs 全量重训 vs 微调

5.1 实验设置与数据说明

为了验证 BLS 增量更新的实际效果,我当时在一个模拟的流式分类任务上做了对比。数据是分批到达的,每批 5000 条,共 20 批,总计 10 万条。特征维度 200,类别 10 类。对比对象有三个:一是每批全量重训一个多层感知机;二是用新数据微调同一个多层感知机;三是 BLS 增量更新。评价指标包括每批更新后的准确率、累计训练时间、以及旧类别上的遗忘程度。

需要说明的是,这个实验是模拟环境下的对比,不代表所有真实场景,但足以看出趋势。所有方法都做了基本的超参调优,尽量保证公平。

5.2 准确率与遗忘程度对比

方法最终准确率旧类别准确率(遗忘程度)累计训练时间
全量重训 MLP91.2%90.8%(几乎无遗忘)约 6.5 小时
微调 MLP87.5%62.3%(严重遗忘)约 0.8 小时
BLS 增量90.1%88.7%(轻微遗忘)约 12 分钟

从表里能看出几个关键点。微调虽然快,但旧类别准确率掉得厉害,灾难性遗忘非常明显。全量重训效果最好,但时间成本是 BLS 的 30 多倍。BLS 增量在准确率上只比全量重训低 1 个百分点左右,但训练时间只有它的三十分之一,而且旧类别遗忘程度远小于微调。这个 trade-off 在流式场景里是非常划算的。

5.3 训练时间随数据量的变化趋势

更值得关注的是时间随数据量的变化。全量重训的时间随累计数据量近似线性增长,到第 20 批时单次训练已经要 20 多分钟。微调的时间基本恒定,但代价是遗忘。BLS 增量的单次更新时间从最初的几秒缓慢增长到十几秒,增长主要来自节点总数不变但伪逆更新涉及的矩阵维度略增,整体远低于全量重训。

我当时的笔记里写了一句:如果你的场景允许 1% 左右的准确率损失,换取 30 倍以上的训练加速和可接受的遗忘水平,BLS 增量是非常值得尝试的方案。当然,如果任务对准确率极其敏感,那还是老老实实全量重训或者用更复杂的增量策略。

6. 踩坑记录与常见问题排查

6.1 伪逆数值不稳定怎么办

这是我最开始遇到的最头疼的问题。表现是增量更新几次后,输出权重突然变得很大,预测结果全乱。排查下来是矩阵 A 的条件数太大,伪逆求解放大了数值误差。解决办法有三个:一是加正则项,把伪逆换成岭回归形式;二是对输入做标准化,让各列尺度一致;三是控制节点总数,避免列之间高度相关。我后来固定用带正则的伪逆,λ 取 1e-6,基本没再出现过数值爆炸。

6.2 增量更新后效果反而变差

有时候新数据来了,更新完发现验证集准确率不升反降。常见原因有两个:一是新数据本身噪声大或者分布异常,直接更新把噪声学进去了;二是新旧数据尺度不一致,导致伪逆更新时旧信息被过度覆盖。我的应对策略是对新数据做异常检测和标准化,并且设置一个更新阈值——如果新数据上的损失超过某个范围,就跳过这次更新或者降低更新权重。这个阈值可以根据历史损失的均值和方差来定,比如超过均值加三倍标准差就触发保护。

6.3 节点数怎么定才不拍脑袋

节点数包括特征节点组数、每组节点数、增强节点组数、每组节点数,四个超参。新手最容易随便设一组然后效果不好就放弃。我的经验是分两步:先固定增强节点为特征节点的一半,然后网格搜索特征节点总数,范围从输入维度的 1 倍到 5 倍;找到最佳特征节点数后,再微调增强节点数。另外,每组节点数不宜太少,比如每组少于 10 个节点,随机映射的多样性不够,效果会明显下降。我一般每组至少 20 个。

6.4 常见问题速查表

问题现象可能原因排查与解决
权重数值爆炸伪逆数值不稳定加正则项,标准化输入,减少节点数
增量后准确率下降新数据噪声或分布异常异常检测,设置更新阈值,降低更新权重
旧类别遗忘严重更新步长过大减小更新权重,或混入少量旧数据
训练速度没提升节点数过多或实现未用增量公式检查是否每次重算伪逆,减少节点数
验证集效果差节点数不足或映射方差不当增加节点数,调整随机映射方差

提示:增量学习不是万能药,它适合数据持续流入且对训练时效要求高的场景。如果数据是静态的,或者对准确率要求极高,全量训练仍然是更稳妥的选择。

7. 这套东西还能怎么用:几个延展方向

BLS 增量学习的思路不只适用于分类。我后来把它用在了几个不同的场景里,效果都还不错。一个是流式异常检测,正常数据不断流入,模型增量更新正常模式,异常来的时候重构误差或预测偏差就会突增;另一个是在线推荐,用户行为实时到达,用 BLS 增量更新用户偏好模型,比每小时全量重训响应快得多;还有一个是工业设备监测,传感器数据持续产生,增量更新可以捕捉设备状态的缓慢漂移。

如果你想把 BLS 和深度学习结合,一个可行的方向是用 BLS 做特征提取的前端,后面接一个小的全连接网络做微调,这样既有 BLS 的增量效率,又有深度模型的表达能力。不过这样会引入反向传播,增量更新的闭式解优势就没了,需要权衡。

另外,BLS 的随机映射部分其实可以用自编码器或者 PCA 来初始化,而不是完全随机,这样在相同节点数下表达能力更强。我试过用 PCA 初始化特征映射,收敛更快,但增量更新时 PCA 本身也需要更新,复杂度上去了。所以如果追求极致简单,纯随机映射就够了;如果追求效果,可以在这块做文章。

最后分享一个小技巧:增量更新时,可以给新数据一个小于 1 的权重,相当于在伪逆更新里加一个衰减因子。这样新数据的影响不会一次性完全覆盖旧知识,遗忘更平滑。这个权重我一般设在 0.7 到 0.9 之间,具体看新数据的可信度。可信度高就接近 1,可信度低就调小。这个技巧在论文里没写,是我在实际调参中试出来的,对缓解遗忘挺管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询