<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>JuniorTree - Paper Reading</title><description>Reading notes for papers, methods and implementation details.</description><link>https://www.juniortree.com</link><item><title>Will self-driving ‘robot labs’ replace biologists? Paper sparks debate</title><link>https://www.juniortree.com/paper-reading/will-self-driving-robot-labs-replace-biologists</link><guid isPermaLink="true">https://www.juniortree.com/paper-reading/will-self-driving-robot-labs-replace-biologists</guid><description>自驱动的机器人会取代生物学家吗？</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在去年的暑假我去做了开源之夏的实习，然后不知道的朋友可能觉得我做的就是 Agent 的开发，但其实我做的工作其实和 Agent 的关系并不是很大，当时的很多工作叫 Workflow 可能更合理，就是在特定的位置调用一些工具，然后做上下文注入或者是做 RAG，当时我的工作很简单，就是把当时一些主流的平台，比如说 Dify，Coze，FastGPT 这些平台给聚合在一起，通过一个统一的网关进行管理，管理调用，日志等等&lt;/p&gt;
&lt;p&gt;所以我一直认为，这段经历叫作 Agent 开发并不合理，应该叫作基础设施维护，Agent 和大模型都跑在云上，就和人住在房子里一样，自然需要人去打扫卫生，开窗等等，在我读大学的三年中，其中两年半都在做类似的训练或者是工作&lt;/p&gt;
&lt;p&gt;去年5月份的时候，当时我和吉吉在开发一个应用的后端，当时还在用 Claude Sonnet 3.5 好像是，当时 GitHub Copilot 给了 Pro 用户不少的额度，平时拿来做代码补全或者是 Ask 模式绰绰有余，当时 Agent 模式写代码并没有现在这么成熟，更多的时候是旁边有一个窗口，然后你和它对话，它给你代码，然后又复制粘贴进去，我当时印象很深的是，我们在后端用了一个 Redis 作为缓存，但是我当时没有设计好，我觉得缓存的效率即便是命中了似乎效果也不是很好，然后吉吉当时也没有给出很好的解决方案，我就和 Copilot 对话，我说你要给我解决！然后它就给我解决了！而且确实速度比我实现的要快得多！&lt;/p&gt;
&lt;p&gt;另外一个有趣的经历是我参与到一个 MCP 的开发中，去年的11月，当时我就有点感觉到，我在做的一些工作，写脚本也好，写MCP也罢，其实都是在给大模型造工具，到后来我们都不手写代码了，都开始用 Claude Code 或者是 Cursor 来写工具了，更抽象的是在A/提出 SKills 这个概念的时候，里面内置了一个 &lt;code&gt;skills-creater&lt;/code&gt; 这个 Skills，也就是说你可以用一个 SKills 去写另外一个 Skills，这种就有点像编程语言里面的自举了，或者可以理解为，左脚踩右脚上天，Agent 开始自进化了&lt;/p&gt;
&lt;p&gt;今年三月份的时候，Minimax 推出了自己的 M2.7 模型，虽然我非常不喜欢他们，也不喜欢他们的模型，但是在宣传文案里面的一段话吸引了我的注意：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;M2.7 initiates a cycle of model self-evolution: during development, we let the model update its own memory, build dozens of complex skills for RL experiments, and improve its own learning process based on experiment results.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;每一次迭代都会产生一个稍好的模型，然后为下一轮产生质量稍高的训练信号，这就是“递归”的部分--一个循环的输出成为下一个循环的输入，我觉得模型层面的自进化和应用层还是不太一样的，应用层的 Agent 更像是一个越来越会使用工具的工人，模型层的自进化则更像是这个工人开始参与设计工厂本身&lt;/p&gt;
&lt;p&gt;也是非常巧的，我在小红书上刷到了 Minimax 的工程师在聊关于 harness engineering 设计的问题，里面提到的一个概念，叫作「蒸馏自己」，什么叫作蒸馏自己呢？也就是你在做 harness 的过程，就是在不断解构你自己是如何去解决一个问题的过程，举一个小例子，比如说我要做一个能帮我打车的 Agent，这个过程应该是，我先知道我当前和想去的地方，然后打开打车软件，然后我就可以开始找车了，看一下报价，选一下类型，等师傅来，对于 Agent 来说也是这样，第一步需要先确定地点，然后调用 API，请求&lt;/p&gt;
&lt;p&gt;这学期我看了非常多关于 Agent 相关的文章，主要是一些做 AI4S 的东西，比如说什么 PlantGPT 啦，什么 Vibe Coding For 组学啦，但其实对我来说，我觉得这些东西都是比较难变现的，也就是我觉得 AI 泡泡肯定会来的，但是什么时候来，强度会怎么样，不好说&lt;/p&gt;
&lt;p&gt;而且对我来说，最矛盾的是，我觉得大家最终肯定都会被 AI 淘汰的，做具身和机器人的那么多，现在码农焦虑也只是因为 Coding 这个领域有电脑就行，软件工程师是第一批工作成果与物理世界完全解耦的群体，我们的工作输入是字符，输出也是字符，这恰恰是大模型最擅长吞噬的领域，等具身成本降低，机器人学相对更成熟，很多岗位都会被替代&lt;/p&gt;
&lt;p&gt;在今年1月的时候，我有一个非常 crazy 的想法，大模型最本质的也就是去预测 next token，那它到底能不能理解我们现实生活中的生物学和物理学原理呢？所以当时我想做的其实是设计一个比较好的 harness，然后来让大模型来从种子开始来帮我养成一株拟南芥，我们把所有工具，包括开灯、浇水、通风等等都封装成一株工具，然后让大模型在这个 harness 来进行调用，当时是受到这个项目的启发&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.juniortree.com/pic/2026/05/3b19bfd0b55b76369ccccb86d7a9c738.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;但是我们这个项目烂尾了，我觉得烂尾的主要原因有这几个，第一是我自己眼高手低，低估了整个系统的难度，要做软硬件结合并不是一件容易的事情；第二我觉得要让 AI 去写一个 MVP 很容易，但是长期的维护其实是很难的；第三就是 AI 时代开发的心智压力其实是要比传统时代高得多的，以前可能一天写一个接口，你可以慢慢想着后续架构要怎么实现，但是现在可能 AI 5分钟把接口给你写完了，你就得马上投入到下面的思考，决策的时间被极致压缩了&lt;/p&gt;
&lt;p&gt;所以综上，在深入感受和了解到这个行业的一些发展后，我觉得可能去做一些更加「务实」的工作，这种务实有可能因为成本问题，未来很长时间 AI 和具身都没办法轻易取代的，我想去做湿实验，我想去学湿实验的各种操作和技能，我觉得未来更多的这种具身的工作是等待人来实现的，目前的干湿交叉更多的是湿实验工作者通过实验拿到数据，然后由干实验工作者来进行处理，得到某些统计学上的结论；或者是反过来，干实验工作者通过计算机和算法排除了90%的样本，然后剩下10%的样本交给人工来验证&lt;/p&gt;
&lt;p&gt;26年3月 Nature 有一篇文章叫作「Towards end to end automation of AI research」，他们做了计算机领域里面的端到端的研究，让 Agent 在计算机上跑各样各样的测试和模型训练，并且进行评估，我之前的一个愿望是能不能在生物学领域去实现这个，Agent 自主完成干实验，然后又自主去完成湿实验，这样在不断尝试和试错的情况下，可以做到24小时不间断地科研，也许可以极大程度加速药物的研发和生物学的进步&lt;/p&gt;
&lt;p&gt;可以预见的是，在不远的未来，一些非常常见的湿实验管线会被机器人所取代，但是一些可能成本高的、或者是比较小众的实验还是会有人类所主导，其实华大早就开始做了，成本问题而已&lt;/p&gt;
&lt;p&gt;我要跑路了&lt;/p&gt;</content:encoded></item><item><title>Towards end-to-end automation of AI research</title><link>https://www.juniortree.com/paper-reading/towards-end-to-end-automation-of-ai-research</link><guid isPermaLink="true">https://www.juniortree.com/paper-reading/towards-end-to-end-automation-of-ai-research</guid><description>Sakana AI 把想法生成、实验执行、论文写作和自动评审串成完整闭环，展示了 AI Scientist 首次让 AI 生成论文通过标准同行评审初轮。</description><pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import CitationCard from &apos;@/components/mdx/CitationCard.astro&apos;&lt;/p&gt;
&lt;h2&gt;反思&lt;/h2&gt;
&lt;p&gt;首次完全由AI生成的论文通过标准同行评审流程的报告，标志着AI科研自动化的一个里程碑，初次实现端到端的生成&lt;/p&gt;
&lt;p&gt;目前不知道 AI Scientists 生成的课题做的究竟有没有意义，有没有实际解决真实科研中的问题，还是仅仅只是为了发 Paper 说做出来的自动化 token 消耗机&lt;/p&gt;
&lt;p&gt;生物学领域中的端到端测试做还是有点难度，因为存在湿实验，不过如果未来能把这个作为 AI Assistants 我觉得意义比全自动的 AI Scientists 更大，可以先做一个干实验的闭环&lt;/p&gt;
&lt;p&gt;此外上下文管理的实践可以参考，确实做到了非常优秀的上下文管理，并且在长时程任务中取得了不错的效果，需要考虑到 token 的效率，比 OpenClaw 或者是 Hermes 这样所谓的 Self-Planing Agent 的工程化做的好得多，比如要求模型像写“实验日志（experimental journal）”一样记录结果以作为长程记忆 ；在查重和引文阶段，它会反复调用 Semantic Scholar API 进行多达 20 轮的核对和过滤 ，这种将大模型能力嵌入到严谨代码逻辑和外部工具链中的做法，极大地提高了长文本生成的收敛性和可靠性&lt;/p&gt;
&lt;p&gt;另外在 Workflow 中做了一个树搜索节点的尝试，这个非常有意思，并且在文献中指出了随着树节点的增长，最终产出文章的 scores 得到了增长，需要关注的是这个增长是非线性的，也就是说明非常有可能存在着一个“智力的顶峰”，这个顶峰应该是由模型本身的能力来决定的，树搜索只是在模型能力范围内寻找最优解，但无法超越模型本身的能力边界&lt;/p&gt;
&lt;p&gt;需要引入人类科学家的观测，比如说可以利用人类科学家的经验，如果出现了一些明知不好的解决（Tree Node），可以直接砍掉，减少了 Agent 的困惑和探索成本（&lt;strong&gt;Human-in-the-Loop (HITL，人机回环)&lt;/strong&gt; ）&lt;/p&gt;
&lt;h2&gt;研究背景与动机&lt;/h2&gt;
&lt;p&gt;AI辅助科学发现有着悠久的历史，但过去的系统只能自动化科研流程中的单个环节（如发现化学结构、预测蛋白质结构、生成假设、辅助编程等），尽管大语言模型（LLM）的出现拓展了AI在科研中的应用范围，但一个能够自主完成从构思到发表全流程的系统此前尚未实现&lt;/p&gt;
&lt;p&gt;本文的核心动机是：能否构建一个端到端的AI系统，自主完成科研的全部环节——从想法生成、实验执行、数据分析到论文撰写和同行评审&lt;/p&gt;
&lt;p&gt;文章主要是针对深度学习相关的研究，因为其实验可以完全在计算机上完成，对于生物学领域的端到端还缺乏研究&lt;/p&gt;
&lt;h2&gt;系统架构&lt;/h2&gt;
&lt;h3&gt;整体流程&lt;/h3&gt;
&lt;p&gt;主要按照四个阶段进行：&lt;/p&gt;
&lt;p&gt;| 阶段 | 功能 | 关键技术 |
| --- | --- | --- |
| Phase 1: 想法生成（Ideation） | 迭代生成研究方向与假设，构建想法档案库 | LLM变异算子 + Semantic Scholar API 新颖性检查 |
| Phase 2: 实验执行（Experimentation） | 执行实验计划，记录实验日志 | 模板模式 / 无模板模式（含树搜索） |
| Phase 3: 论文撰写（Write-up） | 生成完整的LaTeX格式学术论文 | 分节填充 + 文献检索（20轮） + VLM图文对齐 |
| Phase 4: 自动评审（Review） | 对生成论文进行质量评估 | 5次独立评审 + 元评审（Area Chair角色） |&lt;/p&gt;
&lt;h3&gt;两种实验执行模式&lt;/h3&gt;
&lt;h4&gt;模板模式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;基于人类提供的代码模板（如在Shakespeare数据集上训练小型Transformer）&lt;/li&gt;
&lt;li&gt;使用开源编程助手 Aider 修改代码&lt;/li&gt;
&lt;li&gt;线性顺序执行实验计划，具备自动调试能力（最多4次重试）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;无模板模式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;系统自行从零生成代码&lt;/li&gt;
&lt;li&gt;引入并行化智能体树搜索（Parallelized Agentic Tree Search），大幅增加测试时计算量&lt;/li&gt;
&lt;li&gt;使用多种模型分工协作：
&lt;ul&gt;
&lt;li&gt;o3：想法生成与代码批评&lt;/li&gt;
&lt;li&gt;Claude Sonnet 4：代码生成&lt;/li&gt;
&lt;li&gt;GPT-4o：视觉语言任务&lt;/li&gt;
&lt;li&gt;o4-mini：评审阶段推理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;树搜索实验机制&lt;/h3&gt;
&lt;p&gt;无模板模式的实验阶段采用四阶段树搜索（对应 Fig. 3a）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stage 1 — 初步调查：测试基本可行性
Stage 2 — 超参数调优：优化实验配置
Stage 3 — 研究议程执行：执行主要实验
Stage 4 — 消融实验：评估各组件贡献
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个阶段内部进行独立的树搜索，阶段结束时由LLM评估器选择最优叶节点作为下一阶段的根节点。树中的节点类型包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;常规节点（buggy / non-buggy）
超参数节点（Hyperparameter nodes）
消融节点（Ablation nodes）
复制节点（Replication nodes）—— 不同随机种子
聚合节点（Aggregation nodes）—— 汇总可视化
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fig. 3b 展示了一个真实的树搜索案例（主题：抑制快速学习特征以避免捷径依赖），清晰呈现了从构建color-biased MNIST数据集、调优、引入Waterbirds/CelebA数据集到消融实验的完整探索过程&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.juniortree.com/pic/2026/04/395e246f615c49719b6ead598c4913a3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;VLM集成&lt;/h3&gt;
&lt;p&gt;系统在实验和写作阶段均集成了视觉语言模型（GPT-4o）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实验阶段：批评生成的图表（如标签不清、图例缺失等），反馈用于生成新的调试节点&lt;/li&gt;
&lt;li&gt;写作阶段：检查图文一致性，确保caption准确描述图表内容&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自动评审系统：The Automated Reviewer&lt;/h2&gt;
&lt;h3&gt;设计&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;基于 o4-mini 构建&lt;/li&gt;
&lt;li&gt;遵循 NeurIPS 官方评审指南&lt;/li&gt;
&lt;li&gt;输出结构化评审：数值评分（soundness、presentation、contribution、overall、confidence）+ 优缺点列表 + 接受/拒绝决策&lt;/li&gt;
&lt;li&gt;5次独立评审 + 元评审（Area Chair角色） 的集成机制&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验证结果&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.juniortree.com/pic/2026/04/19bdd8257dc813782644debe99e8778c.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;数据污染的问题&lt;/h3&gt;
&lt;p&gt;测试的评审的语料有可能在模型的预训练时期就包含进去了，因为均衡决策准确率从截断前的69%下降至截断后一年的66%，然而，截断后一年的结果仍与人类审稿人相当（例如66%的均衡准确率），表明潜在污染的影响至多微乎其微&lt;/p&gt;
&lt;h2&gt;实验的一些发现&lt;/h2&gt;
&lt;h3&gt;模型的能力与论文质量成正相关&lt;/h3&gt;
&lt;p&gt;Fig. 1b 显示，随着底层基础模型的发布时间推移（从GPT-4到Sonnet-4/Gemini-2.5），AI Scientist生成论文的质量持续提升&lt;/p&gt;
&lt;p&gt;意味着未来模型的进化趋势将演变成系统输出的质量（搞大模型的是码奸）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.juniortree.com/pic/2026/04/0a94b68d42f725e0608bf1b3f3d331ab.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;计算量与论文质量的正相关&lt;/h3&gt;
&lt;p&gt;Fig. 3c 表明，增加树搜索中的实验节点数量（从~4个到~32个），论文评分从约3.3提升至约3.9，呈现明显的测试时计算量缩放效应&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.juniortree.com/pic/2026/04/0209ad5bdaa4bf3224a9019575a5a300.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;更多节点 = 更充分的实验探索&lt;/h4&gt;
&lt;p&gt;树搜索的核心设计理念是将科研实验建模为一个搜索问题，更多的节点意味着系统能够探索更多的实验路径，从而更有可能找到高质量的方案&lt;/p&gt;
&lt;p&gt;每个阶段都进行独立的树搜索，更多的节点使得每个阶段都有更充分的探索空间&lt;/p&gt;
&lt;h4&gt;选择性剪枝机制放大了节点数量的收益&lt;/h4&gt;
&lt;p&gt;系统并非盲目扩展节点，而是在每个阶段结束时通过LLM评估器选择最优叶节点作为下一阶段的起点，从而剪掉低质量的分支，这意味着：节点越多 → 候选方案越多 → 被选中的最优方案质量越高，从更大的样本池中选择最优解，期望值自然更高&lt;/p&gt;
&lt;h4&gt;与测试时计算缩放（Test-time Compute Scaling）的类比&lt;/h4&gt;
&lt;p&gt;论文将这一现象与更广泛的测试时计算缩放趋势联系起来，无模板系统的设计初衷就是通过增加推理阶段的计算量来提升输出质量，在推理阶段投入更多计算资源（这里体现为更多搜索节点），输出质量会相应提升&lt;/p&gt;
&lt;h3&gt;人类评审实验&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;提交场景：ICLR 2025 的 ICBINB（I Can&apos;t Believe It&apos;s Not Better）Workshop&lt;/li&gt;
&lt;li&gt;流程：经IRB批准，与ICLR领导层和workshop组织者合作；评审者知晓部分论文为AI生成但不知具体是哪些&lt;/li&gt;
&lt;li&gt;结果：3篇提交中，1篇获得平均分6.33（个别分数6/7/6），超过workshop平均接受阈值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;被接受的论文（Fig. 2）题为 &lt;em&gt;&quot;Compositional Regularization: Unexpected Obstacles in Enhancing Neural Network Generalization&quot;&lt;/em&gt;，报告了一个负面结果——组合正则化并未显著提升泛化能力，恰好契合workshop关注&quot;有趣的负面结果&quot;的主题&lt;/p&gt;
&lt;h4&gt;重要限制：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Workshop接受率约70%，远高于主会议的32%&lt;/li&gt;
&lt;li&gt;内部评估认为没有论文达到ICLR主会议的标准&lt;/li&gt;
&lt;li&gt;常见失败模式包括：想法幼稚、实现错误、方法论不够深入、引用幻觉等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;源码阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;仓库地址：https://github.com/SakanaAI/AI-Scientist-v2&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Idea的产生&lt;/h3&gt;
&lt;h4&gt;&lt;strong&gt;输入&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;workshop_description&lt;/code&gt;：从 &lt;code&gt;-workshop-file&lt;/code&gt; 指定的 &lt;code&gt;.md&lt;/code&gt; 读入（领域背景/任务要求）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;idea_fname&lt;/code&gt;：输出 JSON 文件（通常是把 &lt;code&gt;.md&lt;/code&gt; 替换成 &lt;code&gt;.json&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;可选历史 ideas：如果 &lt;code&gt;reload_ideas=True&lt;/code&gt; 且 JSON 已存在，会先加载旧 idea，避免重复&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;生成循环（外层）&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;generate_temp_free_idea(...)&lt;/code&gt; 里有 &lt;code&gt;for gen_idx in range(max_num_generations)&lt;/code&gt;，每一轮生成一个 proposal 候选。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;反思循环（内层）&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;每个 proposal 又会跑 &lt;code&gt;num_reflections&lt;/code&gt; 轮：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 1 轮：用初始生成 prompt（让模型先提出一个新方向）&lt;/li&gt;
&lt;li&gt;第 2~N 轮：用 reflection prompt（让模型自我改进，并结合工具返回结果）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;模型每轮必须输出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ACTION:&lt;/code&gt; 选择动作（比如文献搜索或最终定稿）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ARGUMENTS:&lt;/code&gt; 对应动作参数（JSON）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;工具调用与定稿&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;若 &lt;code&gt;ACTION=SearchSemanticScholar&lt;/code&gt;：调用语义学术搜索工具，拿到文献结果&lt;/li&gt;
&lt;li&gt;若 &lt;code&gt;ACTION=FinalizeIdea&lt;/code&gt;：提交最终 &lt;code&gt;idea&lt;/code&gt; JSON，加入 archive&lt;/li&gt;
&lt;li&gt;最后把所有 ideas 写回 &lt;code&gt;idea_fname&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;特点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;不是“一次性生成”，而是“&lt;strong&gt;生成 -&gt; 查文献 -&gt; 反思 -&gt; 定稿&lt;/strong&gt;”&lt;/li&gt;
&lt;li&gt;有历史 idea 去重（通过 &lt;code&gt;prev_ideas_string&lt;/code&gt; 注入 prompt）&lt;/li&gt;
&lt;li&gt;用严格 ACTION/ARGUMENTS 格式，方便程序自动解析和执行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;上下文管理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;任务级上下文（静态）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;来自 &lt;code&gt;idea.json&lt;/code&gt; 的 &lt;code&gt;Title/Abstract/Short Hypothesis/Experiments/Risk...&lt;/code&gt;，在每个 stage 会被 &lt;code&gt;_curate_task_desc()&lt;/code&gt; 注入不同字段。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;阶段级上下文（策略）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;main_stage_goals&lt;/code&gt; + sub-stage goals。&lt;/p&gt;
&lt;p&gt;stage2 限制“只调参不改架构”、stage3 强调创新、stage4 强调消融&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;树节点上下文（局部因果）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每个 &lt;code&gt;Node&lt;/code&gt; 保存：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;code / plan&lt;/li&gt;
&lt;li&gt;execution output / error&lt;/li&gt;
&lt;li&gt;metrics&lt;/li&gt;
&lt;li&gt;plot &amp;#x26; VLM feedback&lt;/li&gt;
&lt;li&gt;parent/children 关系debug/improve 都直接拿 parent node 作为前文&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;历史摘要上下文（压缩记忆）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每轮 step 前，用 &lt;code&gt;journal.generate_summary()&lt;/code&gt;把历史成功/失败模式压缩成 &lt;code&gt;memory_summary&lt;/code&gt;，注入 &lt;code&gt;_draft/_improve&lt;/code&gt; prompt 里的 &lt;code&gt;Memory&lt;/code&gt; 字段，避免上下文无限膨胀&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;跨阶段继承上下文（best transfer）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AgentManager._get_best_implementation()&lt;/code&gt; 会把上一阶段最佳 node 深拷贝（去掉 parent/children）作为下一阶段起点，保证“继承最优实现”而不是重开&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>BUSCO: assessing genome assembly and annotation completeness with single-copy orthologs</title><link>https://www.juniortree.com/paper-reading/busco-2015</link><guid isPermaLink="true">https://www.juniortree.com/paper-reading/busco-2015</guid><description>BUSCO（Benchmarking Universal Single Copy Orthologs, 基准通用单拷贝同源基因）通过基于进化上对基因内容的预期，来定量评估基因组组装和注释的完整性。</description><pubDate>Sun, 29 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import CitationCard from &apos;@/components/mdx/CitationCard.astro&apos;&lt;/p&gt;
&lt;p&gt;BUSCO（Benchmarking Universal Single-Copy Orthologs, 基准通用单拷贝同源基因）通过基于进化上对基因内容的预期，来定量评估基因组组装和注释的完整性。&lt;/p&gt;
&lt;h2&gt;BUSCO 前基因组组装质量的检验&lt;/h2&gt;
&lt;p&gt;在 BUSCO 普及之前，基因组组装质量的检验主要依赖于&lt;strong&gt;纯技术或统计学指标&lt;/strong&gt;，这些指标主要反映测序或组装方法的特征&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;片段连续性指标（N50）：基因组中至少有一半的序列是组装在长度等于或大于 N50 的片段（contigs）上的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误率与分布评估：&lt;/strong&gt; 包括单碱基错误率（per-base error rates）、插入片段大小分布（insert size distributions），以及通过 k-mer 分布来评估基因组偏差等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;早期的基因评估尝试：&lt;/strong&gt; 在 BUSCO 之前，曾有一种名为 CEGMA（Core Eukaryotic Gene Mapping Approach）的工具，它通过比对 248 个保守的核心真核基因来尝试评估基因组完整性&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;为什么需要 BUSCO&lt;/h2&gt;
&lt;p&gt;传统的以 N50 为代表的统计学指标，无法从“基因内容（gene content）”的维度来评估基因组组装的完整性，作者在论文中提到 N50 和 BUSCO 的相关性很差，这说明即使 N50 很高（序列很长），但是也有可能丢掉关键基因&lt;/p&gt;
&lt;p&gt;因此我们需要 BUSCO 不仅仅是需要基因组能拼得长，更需要生物学上的基因完整度&lt;/p&gt;
&lt;h2&gt;BUSCO 的生物学原理&lt;/h2&gt;
&lt;p&gt;BUSCO 的核心在于利用那些在进化上高度保守、且在绝大多数物种中只以单拷贝形式存在的同源基因&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同源基因 (Orthologs)：&lt;/strong&gt; 指的是在不同物种中，由同一个共同祖先基因经过物种形成（speciation）而演化来的基因&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进化上高度保守：&lt;/strong&gt; 意味着在漫长的进化过程中，这些基因在某个庞大的系统发育分支（如脊椎动物、节肢动物、真菌等）的各个物种里都被完整地保留了下来，它们通常负责非常基础且核心的生命活动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在绝大多数物种中只以单拷贝形式存在：&lt;/strong&gt; 研究表明，这类基因在进化上受到“单拷贝控制（single-copy control）”，通过对数百个物种的基因组进行取样分析，研究人员发现这些基因在&lt;strong&gt;超过 90% 的被测物种中都只存在唯一的一个拷贝&lt;/strong&gt;，真正的基因重复（duplication）在这些基因上极为罕见&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;单拷贝 ≠ 序列保守&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;单拷贝&lt;/strong&gt;（single-copy）描述的是&lt;strong&gt;基因组中的拷贝数&lt;/strong&gt;，而&lt;strong&gt;保守性&lt;/strong&gt;（conservation）描述的是&lt;strong&gt;序列在进化中的变化程度&lt;/strong&gt;，这是两个完全独立的维度&lt;/p&gt;
&lt;p&gt;一个基因可以是：&lt;/p&gt;
&lt;p&gt;|          | 单拷贝                           | 多拷贝                       |
| -------- | -------------------------------- | ---------------------------- |
| 高度保守 | 组蛋白H3（拷贝少但序列几乎不变） | rRNA基因（多拷贝且高度保守） |
| 快速演化 | 很多单拷贝免疫基因               | 嗅觉受体基因家族             |&lt;/p&gt;
&lt;p&gt;单拷贝是&lt;strong&gt;基因组结构&lt;/strong&gt;的描述；保守性是&lt;strong&gt;进化动力学&lt;/strong&gt;的描述，前者可以提示后者（单拷贝基因承受不起功能丧失，可能受强负选择），但绝不等同于后者&lt;/p&gt;
&lt;h2&gt;BUSCO 的算法实现&lt;/h2&gt;
&lt;h3&gt;针对基因组组装序列&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;初步候选定位 (约占总运行时间的 15%)：&lt;/strong&gt; 算法首先调用 &lt;strong&gt;tBLASTn&lt;/strong&gt; 工具，利用预先构建好的 BUSCO 群体共有序列（consensus sequences），在待评估的基因组测序数据中进行全基因组搜索，从而定位出可能包含目标单拷贝基因的候选基因座（genomic loci）&lt;/li&gt;
&lt;li&gt;**基因预测与提取 (约占总运行时间的 80%)：**定位到候选区域后，算法使用 &lt;strong&gt;Augustus&lt;/strong&gt; 软件执行基因预测，在这一步中，算法会在特定的氨基酸 BUSCO 模块谱（block-profiles）的引导下，对这些候选基因座进行基因结构的注释和序列提取&lt;/li&gt;
&lt;li&gt;**核心同源性评估与分类 (约占总运行时间的 5%)：**最后，算法将预测出的基因序列交由 &lt;strong&gt;HMMER 3&lt;/strong&gt; 软件处理，HMMER 3 利用从氨基酸多重比对中建立的隐马尔可夫模型（HMM）谱进行打分，来评估这些匹配到的基因是否为真正的同源基因（即得分是否达到特定 BUSCO 群体的阈值/bitscore cut-offs），并将确认为同源基因的结果分类为“完整（Complete）”或“碎片化（Fragmented）”等指标&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://s3.juniortree.com/pic/2026/03/8788acf94880b4826455aa450f79f3d7.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;针对转录组数据&lt;/h3&gt;
&lt;p&gt;如果评估的是转录组数据，算法会省略 tBLASTn 和 Augustus 的复杂步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;算法采用寻找&lt;strong&gt;最长开放阅读框 (longest open reading frame, ORF)&lt;/strong&gt; 的直接方法来进行初始的基因预测&lt;/li&gt;
&lt;li&gt;随后，直接将预测出的 ORF 序列送入 &lt;strong&gt;HMMER 3&lt;/strong&gt; 流程进行核心同源性评估&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;针对已注释基因集&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;如果输入的数据已经是提取完毕的注释基因集：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;算法会完全跳过初步定位和基因预测步骤，直接把这些基因输入给 &lt;strong&gt;HMMER 3&lt;/strong&gt; 执行核心的同源性评估和分类&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;源代码赏析时间&lt;/h2&gt;
&lt;p&gt;这里的代码是v6的，托管在GitLab上：https://gitlab.com/ezlab/busco&lt;/p&gt;
&lt;p&gt;我比较好奇 HMMER 比对是怎么做的，所以就只看了这一段的代码&lt;/p&gt;
&lt;p&gt;HMMER 这一步比对的不是原始基因组序列，而是先从基因组里预测出来的蛋白序列：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def run_analysis(self):
    &quot;&quot;&quot;
    This function calls all needed steps for running the analysis.
    &quot;&quot;&quot;
    super().run_analysis()
    self._run_prodigal() # 先跑 prodigal
    self.gene_details = self.prodigal_runner.gene_details # 预测蛋白对应的基因坐标信息
    self.run_hmmer(self.prodigal_runner.output_faa) # 将预测出来的蛋白文件再传给 hmmer
    self.hmmer_runner.write_buscos_to_file()
    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步是在组装 HMMER 的命令&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def configure_job(self, busco_id, seq_filename, output_filename):

        hmmer_job = self.create_job()
        hmmer_job.add_parameter(&quot;--domtblout&quot;)
        hmmer_job.add_parameter(os.path.join(self.results_dir, output_filename))
        hmmer_job.add_parameter(&quot;--cpu&quot;)
        hmmer_job.add_parameter(&quot;1&quot;)
        hmmer_job.add_parameter(
            os.path.join(self.lineage_dataset, &quot;hmms&quot;, &quot;{}.hmm&quot;.format(busco_id))
        )
        hmmer_job.add_parameter(seq_filename)
        return hmmer_job
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后组装出来的命令是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hmmsearch \
    --domtblout &amp;#x3C;results_dir/output_filename&gt; \
    --cpu 1 \
    &amp;#x3C;lineage_dataset/hmms/&amp;#x3C;busco_id&gt;.hmm&gt; \
    &amp;#x3C;seq_filename&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BUSCO 给的参考的数据集中会有很多的 hmm 模型，比如我测试使用的是 &lt;code&gt;lepidoptera_odb12&lt;/code&gt; ，解压会给一个 &lt;code&gt;dataset.cfg&lt;/code&gt; ，其中的 &lt;code&gt;number_of_BUSCOs&lt;/code&gt; 就是 hmm 模型的数量，也就是5760个保守基因家族&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name=lepidoptera_odb12
domain=eukaryota
creation_date=2025-07-01
number_of_BUSCOs=5760
number_of_species=79
ncbi_taxid=7088
max_intron=130000
max_seq_len=160000
species=fly
OrthoDB_version=12.0
dataset_version=01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以真正的流程应该是：&lt;/p&gt;
&lt;p&gt;基因组 → 预测蛋白组 → 用每个 BUSCO 的 HMM 去搜这些蛋白 → 判断这个 BUSCO 是完整、重复、碎片化还是缺失&lt;/p&gt;
&lt;p&gt;输入基因组：&lt;code&gt;contig1&lt;/code&gt;，&lt;code&gt;contig2&lt;/code&gt;，&lt;code&gt;contig3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;基因预测后得到蛋白：P1, P2, P3, P4, P5, P6&lt;/p&gt;
&lt;p&gt;BUSCO 数据集里有 4 个 marker：B1, B2, B3, B4&lt;/p&gt;
&lt;p&gt;然后分别搜索：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;B1.hmm 去搜 P1..P6，发现 P2 是完整命中 -&gt; B1 = Single-copy&lt;/li&gt;
&lt;li&gt;B2.hmm 去搜 P1..P6，发现 P3 和 P5 都是完整命中 -&gt; B2 = Duplicated&lt;/li&gt;
&lt;li&gt;B3.hmm 去搜 P1..P6，只发现 P4 命中了半截 -&gt; B3 = Fragmented&lt;/li&gt;
&lt;li&gt;B4.hmm 去搜 P1..P6，一个都没找到 -&gt; B4 = Missing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里是解析表格的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parse_hmmer_output(self, filename, busco_query):

    processed_genes = []
    matched_genes = []
    records = defaultdict(list)

    data = []
    with open(filename, &quot;r&quot;) as f:
        lines = f.readlines()

    for line in lines:
        if line.startswith(&quot;#&quot;): # 跳过注释行
            continue
        else:
            values = line.split() # 把每一行拆成字段
            description = &quot;&quot;.join(values[22:])
            row_data = tuple(values[:22]) + (description,)
            data.append(row_data)

    arr = np.array(
        data,
        dtype=[
            (&quot;target_name&quot;, &quot;U500&quot;),
            (&quot;target_accession&quot;, &quot;U500&quot;),
            (&quot;tlen&quot;, &quot;i4&quot;),
            (&quot;query_name&quot;, &quot;U500&quot;),
            (&quot;query_accession&quot;, &quot;U500&quot;),
            (&quot;qlen&quot;, &quot;i4&quot;),
            (&quot;eval&quot;, &quot;f8&quot;),
            (&quot;score&quot;, &quot;f8&quot;),
            (&quot;bias&quot;, &quot;f8&quot;),
            (&quot;dom_num&quot;, &quot;i4&quot;),
            (&quot;ndom&quot;, &quot;i4&quot;),
            (&quot;c-eval&quot;, &quot;f8&quot;),
            (&quot;i-eval&quot;, &quot;f8&quot;),
            (&quot;dom_score&quot;, &quot;f8&quot;),
            (&quot;dom_bias&quot;, &quot;f8&quot;),
            (&quot;hmm_coord_from&quot;, &quot;i4&quot;),
            (&quot;hmm_coord_to&quot;, &quot;i4&quot;),
            (&quot;ali_coord_from&quot;, &quot;i4&quot;),
            (&quot;ali_coord_to&quot;, &quot;i4&quot;),
            (&quot;env_coord_from&quot;, &quot;i4&quot;),
            (&quot;env_coord_to&quot;, &quot;i4&quot;),
            (&quot;acc&quot;, &quot;f8&quot;),
            (&quot;description&quot;, &quot;U500&quot;),
        ],
    )

    target_hits_sorted, hmm_match_lengths = self.sort_hmmer_results(arr) # 排好序的基因ID列表以及每个基因的HMM匹配长度

    for target in target_hits_sorted:
        all_domain_hits = arr[arr[&quot;target_name&quot;] == target]
        gene_id = self.get_gene_id(target)
        score = all_domain_hits[0][&quot;score&quot;]

        if not self.mode == &quot;proteins&quot; and not target in processed_genes:
            if self._check_overlap(matched_genes, gene_id):
                continue

        # Extract frame information (present in transcriptome mode)
        frame = (
            all_domain_hits[0][&quot;description&quot;]
            if &quot;frame&quot; in all_domain_hits[0][&quot;description&quot;]
            else None
        )
        # Store bitscore matches for each gene match. If match below cutoff, discard.
        if score &amp;#x3C; float(self.cutoff_dict[busco_query][&quot;score&quot;]):
            continue

        hmm_coords = [
            (hit[&quot;hmm_coord_from&quot;], hit[&quot;hmm_coord_to&quot;]) for hit in all_domain_hits
        ]
        env_coords = [
            (hit[&quot;env_coord_from&quot;], hit[&quot;env_coord_to&quot;]) for hit in all_domain_hits
        ]

        records[gene_id].append(
            {
                &quot;hmm_matched_length&quot;: hmm_match_lengths[target],
                &quot;hmm_profile_length&quot;: all_domain_hits[0][&quot;qlen&quot;],
                &quot;hmm_coords&quot;: hmm_coords,
                &quot;env_coords&quot;: env_coords,
                &quot;score&quot;: score,
                &quot;frame&quot;: frame,
                &quot;orig gene ID&quot;: target,
                &quot;ref gene ID&quot;: target,
            }
        )

        if gene_id not in matched_genes:
            matched_genes.append(gene_id)
            processed_genes.append(target)
    return records
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;计算蛋白的 HMM 覆盖总长度：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def sum_hmm_len(self, hmm_coords):
    hmm_regions_sorted = sorted(hmm_coords, key=lambda x: x[0]) # 按照坐标对的第一个元素（起始位置）进行升序排序
    hmm_regions_used = []
    for region in hmm_regions_sorted:
        for used in hmm_regions_used:
            if (
                used[0] &amp;#x3C;= region[0] &amp;#x3C;= used[1]
            ):  # if any new coord contained in previous region, expand if necessary
                if region[1] &gt; used[1]:
                    used[1] = region[1]
                break
        else:
            hmm_regions_used.append(list(region))

    return sum([region[1] - region[0] + 1 for region in hmm_regions_used]) # 按照闭区间返回结果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后是对 match 到的进行排序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def _sort_matches(self, matched_record, busco_query):
	# 元组的初始化...
	if self.config.get(&quot;busco_run&quot;, &quot;datasets_version&quot;) == &quot;odb10&quot;:
		# odb10数据集（使用统计学z-score方法）
	else:
	 # odb12及以后数据集（使用百分比方法）
	 if size &gt;= 0.8 * profile_length:
      complete
   else:
      fragment
	return (
            busco_complete,
            busco_vlarge,
            busco_fragment,
            matched_genes_complete,
            matched_genes_vlarge,
            matched_genes_fragment,
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里会把命中分为三堆：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;is_complete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is_very_large&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is_fragment&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假设得到的是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;is_complete = {
  &quot;B1&quot;: {
    &quot;P1&quot;: [{&quot;bitscore&quot;: 200, &quot;length&quot;: 461}]
  },
  &quot;B2&quot;: {
    &quot;P3&quot;: [{&quot;bitscore&quot;: 180, &quot;length&quot;: 430}],
    &quot;P4&quot;: [{&quot;bitscore&quot;: 170, &quot;length&quot;: 421}]
  }
}
is_very_large = {
  &quot;B5&quot;: {
    &quot;P9&quot;: [{&quot;bitscore&quot;: 210, &quot;length&quot;: 530}]
  }
}
is_fragment = {
  &quot;B3&quot;: {
    &quot;P5&quot;: [{&quot;bitscore&quot;: 130, &quot;length&quot;: 181}],
    &quot;P6&quot;: [{&quot;bitscore&quot;: 108, &quot;length&quot;: 210}]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意思是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;B1 有 1 个完整命中 P1&lt;/li&gt;
&lt;li&gt;B2 有 2 个完整命中 P3/P4&lt;/li&gt;
&lt;li&gt;B5 有 1 个 very_large 命中 P9&lt;/li&gt;
&lt;li&gt;B3 有 2 个 fragment 命中 P5/P6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第 2 步：&lt;code&gt;filter()&lt;/code&gt; 之后&lt;/p&gt;
&lt;p&gt;这里会去重，并去掉低分命中。&lt;/p&gt;
&lt;p&gt;假设 B3 里 P6 被删掉了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;is_complete = {
  &quot;B1&quot;: {
    &quot;P1&quot;: [{&quot;bitscore&quot;: 200, &quot;length&quot;: 461}]
  },
  &quot;B2&quot;: {
    &quot;P3&quot;: [{&quot;bitscore&quot;: 180, &quot;length&quot;: 430}],
    &quot;P4&quot;: [{&quot;bitscore&quot;: 170, &quot;length&quot;: 421}]
  }
}
is_very_large = {
  &quot;B5&quot;: {
    &quot;P9&quot;: [{&quot;bitscore&quot;: 210, &quot;length&quot;: 530}]
  }
}
is_fragment = {
  &quot;B3&quot;: {
    &quot;P5&quot;: [{&quot;bitscore&quot;: 130, &quot;length&quot;: 181}]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第 3 步：&lt;code&gt;consolidate_busco_lists()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;它做的事其实很简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先看 &lt;code&gt;is_complete&lt;/code&gt; 和 &lt;code&gt;is_very_large&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
\text{一个 BUSCO 只剩 1 个命中} \to \text{single_copy_buscos}
$$&lt;/p&gt;
&lt;p&gt;$$
\text{一个 BUSCO 剩多个命中} \to \text{multi_copy_buscos}
$$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;再看 &lt;code&gt;is_fragment&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果一个 BUSCO 有多个 &lt;code&gt;fragment&lt;/code&gt;，只保留得分最高的那个&lt;/li&gt;
&lt;li&gt;然后放进 &lt;code&gt;fragmented_buscos&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，上面的数据会变成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;single_copy_buscos = {
  &quot;B1&quot;: {
    &quot;P1&quot;: [{&quot;bitscore&quot;: 200, &quot;length&quot;: 461}]
  },
  &quot;B5&quot;: {
    &quot;P9&quot;: [{&quot;bitscore&quot;: 210, &quot;length&quot;: 530}]
  }
}
multi_copy_buscos = {
  &quot;B2&quot;: {
    &quot;P3&quot;: [{&quot;bitscore&quot;: 180, &quot;length&quot;: 430}],
    &quot;P4&quot;: [{&quot;bitscore&quot;: 170, &quot;length&quot;: 421}]
  }
}
fragmented_buscos = {
  &quot;B3&quot;: {
    &quot;P5&quot;: [{&quot;bitscore&quot;: 130, &quot;length&quot;: 181}]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终指标的计算，把每个 BUSCO 家族最终分到的类别做&lt;strong&gt;计数和百分比&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 结果做的结构化
def record_results(self):
    self._get_busco_percentages()
    extra = &quot;,E:{}%&quot;.format(self.e_percent) if self.e_percent else &quot;&quot;
    self.one_line_summary_raw = (
        &quot;C:{}%[S:{}%,D:{}%],F:{}%,M:{}%,n:{}{}\t{}\n&quot;.format(
            self.complete_percent,
            self.s_percent,
            self.d_percent,
            self.f_percent,
            self.missing_percent,
            self.total_buscos,
            extra,
            &quot;   &quot;,
        )
    )
    self.one_line_summary = &quot;Results:\t{}&quot;.format(self.one_line_summary_raw)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def _get_busco_percentages(self):
    self.single_copy = len(self.single_copy_buscos)  # 单拷贝 BUSCO 家族数
    self.multi_copy = len(self.multi_copy_buscos)  # 重复 BUSCO 家族数
    self.only_fragments = len(self.fragmented_buscos)  # 碎片化 BUSCO 家族数
    self.total_buscos = len(self.cutoff_dict) # 总 BUSCO 家族数
    self.num_missing = (
        self.total_buscos - self.single_copy - self.multi_copy - self.only_fragments
    ) # 缺失 BUSCO 家族数

    # Get percentage of each kind of BUSCO match
    self.s_percent = abs(round((self.single_copy / self.total_buscos) * 100, 1))
    self.d_percent = abs(round((self.multi_copy / self.total_buscos) * 100, 1))
    self.f_percent = abs(round((self.only_fragments / self.total_buscos) * 100, 1))
    self.complete_percent = abs(
        round(((self.single_copy + self.multi_copy) / self.total_buscos) * 100, 1)
    )
    self.missing_percent = abs(
        round((self.num_missing / self.total_buscos) * 100, 1)
    )

    if self.miniprot_pipeline:
        self._get_stop_codon_percent_and_avg_identity()
    else:
        self.e_percent = 0
        self.avg_identity = None
        self.complete_stop_codon_count = 0

    return
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 BUSCO 本质上回答的是：“这份基因组里，预期应该存在的保守基因家族，找回了多少？”&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;看了论文和源码，感觉豁然开朗，单从&lt;strong&gt;底层计算工具&lt;/strong&gt;的角度来看，BUSCO 确实没有发明全新的序列比对或基因预测算法，它本质上是一个&lt;strong&gt;整合了现有优秀软件的自动化分析流水线（Pipeline）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BUSCO 的伟大之处和核心专有贡献并不在于“写了一个新搜索代码”，而在于它提供了“标尺”和“规则”&lt;/strong&gt;， 可以说，没有 BUSCO 团队前期的海量生物学工作，这些工具根本不知道该去寻找什么&lt;/p&gt;
&lt;p&gt;研究团队并没有让工具去瞎找，而是基于 OrthoDB 数据库，对数百个物种的基因组进行了极其庞大的比较基因组学分析，他们大海捞针般地筛选出了在超过 90% 的物种中都保持单拷贝的同源基因，并经过严格的唯一性和保守性过滤，最终为不同的进化分支“量身定制”了专属的测试集（例如脊椎动物的 3023 个基因、节肢动物的 2675 个基因、真菌的 1438 个基因等）&lt;/p&gt;
&lt;p&gt;虽然 BUSCO 调用了 HMMER 3 软件，但 HMMER 3 只是一个计算器。BUSCO 团队为这成千上万个单拷贝基因构建了基于氨基酸多重比对的&lt;strong&gt;隐马尔可夫模型谱&lt;/strong&gt;，并且为每一个 BUSCO 基因群设定了&lt;strong&gt;专属的特异性得分阈值（bitscore cut-offs）&lt;/strong&gt;，只有当 HMMER 3 算出的得分满足了 BUSCO 设定的这个严苛阈值，才会被判定为真正的同源基因&lt;/p&gt;
&lt;p&gt;对于结果 &lt;strong&gt;&lt;code&gt;C[D], F, M, n&lt;/code&gt;&lt;/strong&gt; ，在统计学上给出了明确的定义&lt;/p&gt;
&lt;p&gt;另外比起 N50 这种统计学算法或者理论，BUSCO 结合了生物学意义，给出了可解释性的理论&lt;/p&gt;</content:encoded></item></channel></rss>