news 2026/9/18 11:29:56

从灾难性遗忘到持续学习:模型可持续更新实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从灾难性遗忘到持续学习:模型可持续更新实战解析

持续学习(Continual Learning)最近两年在AI从业者圈子里被反复提起,但它并不是什么新概念。核心问题其实很朴素:一个已经训练好的模型,在部署之后遇到新任务、新数据,能不能在不彻底重训、不丢失旧知识的前提下持续更新。直接微调会灾难性遗忘,全部重训又成本太高、数据可能根本拿不到。这篇文章我会从问题根源、三大技术路线、评测协议、真实业务落地、复现踩坑几个维度完整过一遍,把我自己实验和工程落地中验证过的东西、踩过的坑一起写出来,希望能给刚入门持续学习,或者正在考虑把模型改造成“可持续更新”的团队一些真正能用的参考。

1. 灾难性遗忘:持续学习要解决的第一个硬问题

1.1 先复现一次“惨案”:微调模型忘了旧任务

我入坑持续学习,是因为一次非常真实的翻车。当时团队做的是一个图片分类服务,已经在老数据集上训练好了,效果不错。后来新业务来了,我们手头有一批新类别的图片,按常规思路直接在原模型上继续微调。训练过程很顺利,新类别的准确率也确实高,但一上线就发现老类别的召回率从92%掉到了70%出头。业务方直接找上门:你们是不是把老模型改坏了?

这就是持续学习领域最经典的现象:灾难性遗忘(Catastrophic Forgetting)。模型的参数是共享的,新任务的梯度更新会覆盖掉对旧任务重要的参数状态。你在新数据上学得越认真,旧知识被冲刷得越干净。上世纪80年代末McCloskey和Cohen就描述过这个问题,但到今天它依然是部署智能系统的主要障碍。

如果想在本地快速复现,最省事的方式是用MNIST:先用数字0到4训练一个分类器,冻结相关评估逻辑,再用数字5到9去微调整个网络,最后回到0到4的测试集上看准确率。很多新手会惊讶地发现,准确率可能直接从99%跌到50%上下。这个现象不需要复杂的trick,几行PyTorch代码就能看到。

1.2 持续学习和迁移学习、多任务学习到底有什么不一样

很多人在一开始会把持续学习和迁移学习搞混,我当年也绕了弯路。迁移学习的典型场景是“借力”:先用大规模数据预训练,然后到目标任务上微调,它不要求模型在微调后依然保持原来预训练任务的能力。多任务学习则是所有任务的数据都在训练时一次性给齐,模型可以同时看到全部信息,不存在“先学A再学B”的顺序约束。

持续学习夹在两者中间,既要求模型按顺序学习多个任务,又要求在学习新任务时不明显退化旧任务的能力,而且往往伴随着存储限制,旧数据不可能完整保留。换句话说,持续学习关心的是模型在整个生命周期里的表现,而不是某一个阶段的表现。这个区别决定了它的评测方式、算法设计都和前面两种学习范式有本质差异。

这也能解释为什么持续学习特别适合数据即取即用的场景:新数据来了就学,学完还得把旧能力保住。现实业务里绝大多数情况其实都符合这个定义,只是以前很多团队用“定期全量重训”来绕开问题。一旦数据规模变大、数据源不断产生新分布,全量重训的成本就撑不住了。

1.3 为什么现在越来越绕不开:模型更新成了常态而非特例

以前模型的更新节奏是按月、按季度,甚至按年。现在大量系统依赖实时信号,推荐系统每天有新品、新用户行为,风控模型每天要面对新欺诈模式,语音助手每个月要新增技能。模型更新不再是产品发布时的一次性动作,而是日常运营的一部分。这个时候,“只学新忘记旧”就不再只是学术问题,而是直接影响业务指标的工程问题。

我在推荐场景里体会特别深。新品类上线时,模型如果不对新品类做任何微调,新品曝光就会很差;一旦用新品数据重训,老品类的兴趣预测又可能被带偏。持续学习在这里给了一个思路:新旧知识要共存,而不是用新知识覆盖旧知识。理解了这个大背景,后面讲技术路线和工程方案时,你就知道每个方法到底在解决哪一环。

2. 三大技术路线:正则化、回放、动态架构

持续学习的方法论基本可以归成三大类:正则化方法、回放方法、动态架构方法。还有少量工作走的是元学习或者记忆增强的路线,但后两者往往可以嵌入到三大类中理解。

2.1 正则化路线:给参数打上“重要程度”标签

正则化方法的思路最直观:既然灾难性遗忘来自参数被新任务覆盖,那就想办法告诉优化器“哪些参数对旧任务很重要,别动它们”。

最出名的是EWC(Elastic Weight Consolidation)。它用Fisher信息矩阵给每个参数估计一个重要度,训练新任务时在损失函数里加一个惩罚项,重要度高的参数如果偏离原来的值,就要付很大的代价。你可以把它理解成给模型里“老员工”的工位上了锁,新人可以随便用空着的工位,但别去动老员工的椅子。

我在实际复现EWC时,有一个体会:Fisher矩阵的估计质量直接影响结果。标准做法是从旧任务的数据里采样,计算梯度平方的期望。但如果采样的数据不够、采样分布有偏,Fisher矩阵就不可靠,惩罚项甚至可能起反作用。所以正则化方法看起来轻量,其实对数据处理的要求并不小。

同一路线里还有SI(Synaptic Intelligence),它在训练过程中追踪每个参数对loss变化的路径积分,相当于动态统计每个参数累计的重要性,不像EWC需要额外的Fisher估计步骤,实现上更平滑。另外LwF(Learning without Forgetting)走的是知识蒸馏路线:训练新任务时,让模型的旧任务输出保持稳定,不直接限制参数,而是限制输出分布。它的效果和蒸馏温度关系很大,温度太低约束太弱,太高又会影响新任务的学习能力。

正则化方法的优点是模型结构不变、内存开销小、不需要存旧数据。缺点是当任务数量很多时,累积的惩罚约束会让模型参数越来越僵硬,新任务的拟合能力会变差。它适合任务数量不多、数据隐私要求严格的场景。

2.2 回放路线:让模型“偶尔重温”旧数据

回放方法可能是所有持续学习方法里性价比最高的,也是最容易被低估的。它的思路简单得让人怀疑:训练新任务时,顺手把旧任务的一小部分样本混进来一起训练。就是这么朴素的操作,在很多评测上效果都超过了花哨的正则化方法。

最简单的baseline就是Experience Replay(ER):维护一个固定大小的缓冲区,里面存一部分旧任务样本,每个batch里混合新数据和旧数据一起更新。别看不起这个baseline,它在多个持续学习基准上的表现都相当能打。我自己的经验是,任何新方法出来,先拿ER做对照,至少能过滤掉一半“我们效果比baseline好”的虚假结论。

在ER之上还有GEM和A-GEM。GEM把旧样本的梯度作为约束条件,要求新任务的参数更新不能增加旧样本的loss,本质是一个带不等式约束的优化问题。A-GEM则做了简化,只有当新任务梯度和旧任务梯度冲突时才做梯度投影。这两种方法的有效性其实很依赖旧样本缓冲区里的样本质量。边界模糊、类别不平衡时,约束方向本身就有偏,效果也不稳。

再进阶一点是DER(Dark Experience Replay)。它把知识蒸馏和回放结合起来:不仅在缓冲区里存旧样本,还存模型在旧样本上的logits输出。训练新任务时,要求模型在当前样本上的输出尽量接近存下来的历史logits。这个“暗经验”相当于把旧模型的决策边界信息也保存下来了,效果比单纯存标签好很多。但它对两个超参数α和β比较敏感,调参成本不低。

回放方法的优势是稳定、直观、可控性强,也是工业落地时最常被选用的路线。硬伤在于存储和隐私:旧样本要么占存储,要么因为数据合规要求根本不能保存。为了绕开这个限制,有工作提出了生成式回放,用生成模型(如GAN、扩散模型)在训练新任务时合成旧样本。但合成数据分布和真实分布之间有偏差,长期累积之后偏差会放大,这是一个需要专门解决的工程细节。

2.3 动态架构路线:请新的“员工”而不是改造老员工

动态架构方法走的是另一条路:既然改老参数容易出错,我干脆为每个新任务分配独立的参数空间,任务之间尽量不共享参数。

Progressive Neural Networks(PNN)是这类工作的代表。每学一个新任务,就新加一列网络层,同时建立从旧列到新列的侧向连接,把旧任务提取过的特征传给新任务。这样旧任务参数完全不动,不会遗忘,新任务还能借用旧知识。问题也很明显:模型体积随任务数量线性增长,推理时要跑多个分支,成本和复杂度都比较高。

PackNet的做法更节省空间:它先训练完整网络,然后通过剪枝把不重要的参数置零,最后用掩码固定这些位置,腾出参数空间给下一个任务。相比PNN,PackNet让模型容量增长更可控,但剪枝比例和每阶段能容纳多少新知识需要精细设计,否则新任务的容量会很快耗尽。

近几年在大模型背景下,动态架构的思路换了新形态,最有代表性的是基于Prompt的持续学习,比如L2P、DualPrompt。它利用预训练Transformer强大的特征表示,为每个任务学习一组轻量的可学习prompt,新任务只学新prompt,冻结主干模型。这样遗忘问题天然被绕开,而且每个新任务的额外参数很小。这类方法在视觉、NLP的连续学习基准上都有不错表现,但它极度依赖预训练模型的表示质量,如果下游任务和预训练分布差异太大,效果会打折扣。换句话说,它是“站在预训练的肩膀上”绕过灾难性遗忘,并没有真正解决基础模型从头学起时的遗忘问题。

动态架构的优点是几乎不遗忘,任务边界明确时效果最好。缺点是参数量增长、推理链路变复杂、工程化成本高。它最适合的是任务数量可控、任务边界清晰的场景,比如给一个对话系统按模块增加新技能。

2.4 三类方法怎么选:一个来自工程实践的快速对比

如果项目要选型,我会先看三个约束:能不能存旧数据、任务边界清不清楚、模型体积容不容忍增长。这里给一个我用于内部快速决策的对比表。

对比维度正则化方法回放方法动态架构
是否存储旧数据不需要需要不需要
额外内存开销中(缓冲区)高(参数扩展)
任务边界假设
遗忘控制能力很高
实现复杂度
典型代表EWC、SI、LwFER、A-GEM、DERPNN、PackNet、L2P
适合场景隐私严格、任务少数据可存、追求稳定任务边界清晰、硬件充裕

我个人的默认建议是:如果没有任何特殊限制,先用ER或者带蒸馏的回放方法,把准确率和遗忘率跑出来做基线,再决定要不要上更复杂的方案。很多团队一上来就用EWC或者L2P,结果发现调参成本和存储成本还不如老老实实做回放。真实场景里“简单有效”比“论文惊艳”重要得多。

3. 评测基准与评估协议:论文千千万,结果怎么比

持续学习论文看起来很热闹,但复现之后常常发现不同工作之间的结果没有可比性。这个问题不是细节,而是评测协议和基准设置不统一导致的。

3.1 常用数据集和拆分方式

持续学习最常用的是MNIST、CIFAR-10、CIFAR-100、ImageNet这类视觉数据集,再通过不同的拆分方式构造任务序列。

Permuted-MNIST是最简单的:对每个任务,对MNIST图像做不同的固定像素置换,让网络面对同分布但完全不同输入的任务。它主要测试模型在输入分布变化下的适应能力,难度偏低。

Split-MNIST则是把MNIST的10个类别切分成五个二分类任务,依次学习。这种设定更接近真实场景,因为任务是按类别划分的。Split-CIFAR-100把100类分成10个任务或者20个任务,难度比MNIST高不少,是目前比较通行的Class-IL评测设计。

如果追求更接近真实世界的评测,还有CORe50(包含50个类别、多个不同采集场景)、DomainNet(跨域分类)、持续机器学习的流式基准CLOC等。我建议入门时先从Split-MNIST和Split-CIFAR-100入手,把问题机制弄清楚,再上更复杂的数据集。不要一上来就选太难的数据集,否则你很难判断算法效果差到底是因为方法不行,还是因为某个评测坑没绕开。

3.2 Task-IL、Domain-IL、Class-IL:三种协议别搞混

评测协议不统一是持续学习领域最容易被忽略的点。van de Ven和Tolias在2019年的工作把主流评测方式归纳为三种场景:

Task-IL(Task Incremental Learning)里,训练和测试时都提供任务ID,模型只要解决当前任务即可,测试时需要知道属于哪个任务。这种场景最简单,比如Permuted-MNIST就给每个任务一个明确标识,模型甚至可以按ID走不同的输出头。

Domain-IL(Domain Incremental Learning)里,任务ID不提供,输入分布会变化,但输出标签集合不变。比如对不同画风做同一组类别分类。模型必须从输入里推断当前域,难度中等。

Class-IL(Class Incremental Learning)是现实挑战最大的协议:新任务引入的是新类别,测试时也不知道类别属于哪个旧任务。模型必须在所有见过的类别里做统一分类。这种设置下,最终分类器的决策边界会面临严重的新旧类别不平衡问题,算法性能下降也最明显。

这个区别很重要,因为某些方法在Task-IL下表现极好,到Class-IL下就露馅。我看到不少论文会在Task-IL下做主要实验,再顺带报告Class-IL结果。读论文时如果只盯着“准确率95%”这种数字,而不去看协议,很容易被误导。

评测协议测试时是否给任务ID输出标签集合难度典型数据集
Task-IL每个任务独立Permuted-MNIST
Domain-IL不给不变Rotated-MNIST
Class-IL不给不断扩展Split-CIFAR-100

3.3 核心指标:平均准确率、后向迁移、前向迁移

持续学习最基础的指标是平均准确率(ACC),即在学完所有任务后,对所有已见任务的测试准确率求平均。不同论文可能报告“平均增量准确率”或者“最终平均准确率”,前者会包含学习过程中间状态的累计平均值,后者只看最终模型,两者数字差异可能很大。

除了准确率,常用的还有后向迁移(Backward Transfer,BWT)和前向迁移(Forward Transfer,FWT)。BWT衡量学习新任务后,旧任务性能的变化,负值越大说明遗忘越严重。FWT衡量学新任务对还没学的任务有没有帮助。很多论文会先报ACC再报BWT,我建议看持续学习效果时重点看BWT,因为遗忘才是持续学习的核心矛盾。

另外有一个容易被忽略的坑:任务序列的顺序会影响结果。A任务先学B任务后学,和顺序反过来,同一套算法表现可能差不少。评估一个持续学习算法时,最好在多个随机任务顺序下跑多次,取平均和方差,而不是只报一个固定顺序下的结果。我在自己的实验里发现,这个方差往往比不同算法之间的差距还要大。没有方差报告的结果,你看得再认真也很难判断真假。

4. 持续学习在真实业务里的样子

持续学习论文里常出现的benchmark毕竟是玩具,真实业务里会遇到更残酷的约束。这里总结几个我见过、做过的真实场景,以及持续学习在其中扮演的角色。

4.1 模型要一直学新知识,又不能把老客户忘了

我在推荐和广告场景里看到的持续学习需求最集中。推荐模型的训练数据是用户行为日志,用户的兴趣会随着时间、季节、热点事件变化,同时不断有新的内容品类和商品池进来。如果模型不做更新,新品冷启动会越来越差;如果频繁微调,容易对上一天的短期兴趣过度拟合,长期兴趣反而被遗忘。

一个比较成熟的落地方式是:维护一个回放缓冲区,里面保存不同时间段、不同关键品类的代表性样本,训练时混合实时样本和缓冲样本一起更新。这本质上就是ER的思路,但工程实现时要处理标签偏移、样本权重、样本过期等问题。比如某个爆品的样本权重如果太大,一不小心就会把模型的注意力全部带走。

风控场景更有意思。欺诈模式是不断翻新的,昨天有效的规则今天可能就失效了,但旧的风险类型也不能忽略。持续学习在这里被用来让模型在识别新型欺诈的同时不掉旧类型。隐私和合规压力很大,旧样本往往不能保存太久,所以很多团队会优先考虑正则化方法,或者用蒸馏式的回放替代原始样本存储。

4.2 边缘设备和机器人:算力和存储都有限

边缘设备是持续学习另一个典型场景,因为它们天然只能看到流式数据,而且全量重训不太现实。机器人要在新环境里学会操作新物体,但又不能忘了在家里已经学会的技能。自动驾驶在进入新城市、新季节、新天气时也需要增量适应,同时旧区域的表现不能崩。

这类场景的难点在于设备存储和算力都受限制,回放缓冲区不可能太大,动态架构又可能让模型体积超限。我在嵌入式环境里见过一个做法:在设备上只保留轻量的EWC惩罚项,定期在云端做一次全量重训再压缩回设备。这相当于把持续学习和联邦训练、模型压缩组合起来用。组合方案往往比单一方法更符合真实硬件约束,这也是工程和论文最大的区别。

4.3 NLP与对话系统:插件式技能更新

NLP领域持续学习通常叫lifelong learning。对话系统是一个很典型的场景,产品会不断新增技能,比如查天气、订餐、闲聊,每个技能可以看成一个任务,模型要保证新增技能时不破坏已上线的对话能力。

大模型时代持续学习的概念有了一些变化。预训练语言模型的强表示能力让很多持续学习任务可以通过轻量适配模块或prompt完成,新技能变成新增少量参数,遗忘问题大大缓解。但大模型持续学习要处理的是另一种遗忘:指令微调阶段学新指令任务时,会遗忘掉预训练阶段积累的通用知识和指令跟随能力。这个问题甚至在训练业界最强模型时也会遇到,很多团队会用“在预训练数据里混入一部分指令数据”来做平衡,这本质上也是一种回放,只不过回放的是通用语料。

4.4 工程落地时真正要付出的额外代价

持续学习不是银弹,工程落地时它带来的额外成本往往被忽视。

首先是版本管理变复杂。模型不再是一个静态产物,而是不断在线上演进的实体。每次更新后,你都需要评估旧任务表现,这意味着你要长期保存一套覆盖历史任务分布的评估集。做持续学习的团队,评估成本往往比训练成本还高。

其次是监控体系必须跟上。如果没有数据分布漂移检测,你很难判断“该学新任务了”还是“该停止更新了”。我见过不止一个团队,因为没做漂移监控,让模型在新分布异常时持续学习,结果把线上模型越学越偏。

第三是回滚机制。持续更新意味着每次更新都可能引入未知问题,模型回滚能力必须和算法一起设计。回滚不仅仅是恢复旧版本参数,还要考虑旧版本是否真的能处理当前线上分布,有时候旧版本比持续更新后的版本更适合当前环境。

5. 复现持续学习算法时的踩坑记录

这一章写一点点真实的复现经验。持续学习论文看起来简单,真正动手做,坑比想象中多。

5.1 评测设置不统一带来的“假结果”

我第一次复现EWC时,按照论文给的超参设置跑Permuted-MNIST,效果一直上不去。后来发现是Fisher矩阵的采样样本数差了一个数量级。论文里常用的Fisher样本数范围可能是几百到几千,如果我用很少的样本去估计,结果就会很不稳定。这个参数论文很容易一笔带过,但实际影响非常大。

另外一个坑是任务顺序。Permuted-MNIST如果某个置换恰好把两张图映射得非常像,模型在相邻任务之间的迁移效果就会变化。复现时如果只跑一个固定顺序,很难判断结果到底来自算法还是运气。现在我养成的习惯是固定多个随机种子、多个任务顺序,把均值和标准差一起报告,否则只看一个数字很容易下错误结论。

5.2 超参数比算法本身更敏感

持续学习算法普遍对超参数敏感,其中回放缓冲区大小、正则化系数、蒸馏温度最影响结果。

比如EWC的正则化系数λ。λ太小,惩罚不起作用,模型照样遗忘;λ太大,新任务学不进去,准确率上不来。这个范围往往跨越好几个数量级,不做网格搜索很难找到合适的点。

DER的α和β超参调节更是磨人。α控制分类cross-entropy的权重,β控制蒸馏loss的权重,两者比例不对,模型要么遗忘得厉害,要么新任务学不进去。这也是为什么我说任何新方法都要先和ER、EWC这样的简单baseline做对照,如果你调的复杂算法性能只是勉强超过ER,那说明它带来的不稳定收益很可能不值得工程上的额外复杂度。

5.3 从玩具benchmark到真实数据差了多远

玩具benchmark和真实数据之间存在三个明显的断层。

第一个断层是数据分布漂移的方式。benchmark里的任务边界清清楚楚,每个任务内部是独立同分布的。真实数据往往是渐变式漂移,旧分布不会彻底消失,新分布也不会一夜之间全部出现。算法在任务边界分明的测试集上表现好,不等于在渐变漂移数据上也有用。

第二个断层是类别平衡。Split-CIFAR-100每个任务内部类别是均衡的,真实数据基本都是长尾分布,新类别往往一开始样本极少,后面又爆发性增长。这种样本波动对持续学习算法的稳定性要求很高。

第三个断层是标签噪声。benchmark的标签干净,真实业务里标签经常是弱监督或者自动生成的,本身包含错误。持续学习系统如果在可信度不高的标签上学习,错误知识会被固化甚至放大。所以我在真实项目中做持续学习时,会先把不确定样本过滤出来,定期人工复核,而不是全部进模型。

6. 下一步方向:哪些趋势值得关注

持续学习这几年变化很快,信息量很大,我给你整理几个我认为值得投入注意力的方向。

6.1 生成式回放与数据合成

严格控制数据隐私的行业对生成式回放一直有很强的需求。早期用GAN生成旧任务样本,最大的问题是生成质量不够,样本分布一旦失真,回放效果就会退化。扩散模型成熟后,合成样本质量大幅提升,生成的旧样本甚至可以在一些情况下代替真实样本用于回放。

但这不代表可以无脑用。我建议如果要走生成式回放路线,先验证生成的样本在旧任务上的分布距离,再决定要不要替换真实样本。很多团队第一步就翻车,是因为生成器本身需要训练才能达到足够的质量,而这个训练过程在持续学习框架里会有额外的时间成本和工程复杂度。

6.2 Prompt持续学习与大模型

预训练模型加可学习prompt的方法,本质上是用预训练模型的通用表征绕开了灾难性遗忘。它的一个重要优点是可以无痛扩展到很多下游任务:新任务只加一组prompt,参数成本很低。

这个方向的适用条件也很明确:你得有一个足够强的预训练模型,能够覆盖你所有任务的特征分布。如果你做的任务离预训练分布太远,prompt方法就会很吃力。我在实际项目中用prompt做多语种分类的增量,效果很好;但换成非常垂直的医学图像任务,效果就一般了。所以选技术路线时,不要因为某个方法在论文里看着高级就直接套用。

6.3 评估体系规范化的呼声

持续学习领域现在最大的问题不是缺方法,而是缺可信的对比方法。很多论文各自用不同的数据集、不同的任务顺序、不同的评测协议,导致读者无法判断新方法到底是真改进还是在特定设置下“刷分”。

近两年社区已经在推动更统一的基准,要求报告多次运行的平均值和方差,要求同时报告传统指标和遗忘指标。我个人的态度是,团队在选择持续学习算法时,不要只看论文的表,最好自己搭一套标准化评测,把自己候选的算法、基线、真实数据分布都放上去跑。这比追逐热门算法更可靠。

6.4 我对持续学习落地优先级的一点个人建议

如果让我给正在考虑做持续学习的团队排优先级,我会这样建议。

先问自己是不是真的需要持续学习。如果业务节奏允许定期全量重训,那就不要为了技术潮流增加复杂度。全量重训配合良好的数据管线,在很多场景下依然是最稳定、最可控的方案。

如果确实需要持续更新,先从回放类的简单方案开始。一个ER加合理的数据分布监控,已经能覆盖很多实际场景。论文里看起来性能很强的复杂算法,往往伴随着调参难、工程复杂、收益不稳定等问题,不是首选。

再往后,如果产品对模型体积和成本敏感,再考虑正则化路线;如果任务边界清晰且需要严格的数据隐私保护,动态架构路线可能是更好的选择。

持续学习这个概念虽然听起来高大上,但实际上它解决的是工程里非常朴素的问题:模型如何在不断变化的世界里长期保持有用。先把问题定义清楚,再选择匹配的算法,比直接套用某个模型更关键。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 11:29:15

古法编程面临末法?程序员AI转型路线与自评指南

2026年刚开年,"古法编程"突然成了科技圈的热词。起因很偶然:有人分享了一个"桌面工具箱"的开发过程,里面没有AI生成的痕迹,所有代码一行一行手敲,窗口布局全靠手工算坐标,连日志都是自…

作者头像 李华
网站建设 2026/9/18 11:27:44

Roselia「Lehre der Rose」DAY2制作拆解:曲序、灯光与PA

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 11:27:32

Dijkstra算法介绍以及C++实现

一、Dijkstra 算法简介 Dijkstra(迪杰斯特拉)算法是一种单源最短路径算法,用于在带非负权重的图中,计算某个起点到其它所有顶点的最短距离。它由荷兰计算机科学家 Edsger W. Dijkstra 在 1956 年提出。 基本思想 Dijkstra 算法的核心思路是贪心策略: 每次从当前未访问的…

作者头像 李华
网站建设 2026/9/18 11:26:55

RS232/RS485/TTL与串口服务器选型实战:从电平原理到NCOM880T配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 11:24:50

PDFPatcher:免费解除 PDF 复制打印限制的工具箱

PDFPatcher:免费解除 PDF 复制打印限制的工具箱 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱,可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档,探查文档结构,提取图片、转成图片等等 项目地址: https://gitcode.…

作者头像 李华