<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Perry&#39;s Galaxy</title>
  
  <subtitle>Biology · Computation · Curiosity</subtitle>
  <link href="https://perry.apay.eu.cc/atom.xml" rel="self"/>
  
  <link href="https://perry.apay.eu.cc/"/>
  <updated>2026-10-06T09:30:03.000Z</updated>
  <id>https://perry.apay.eu.cc/</id>
  
  <author>
    <name>Perry</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Bulk RNA-seq 分析入门（三）：QC 图、功能富集与有边界的解释</title>
    <link href="https://perry.apay.eu.cc/posts/rna2603/"/>
    <id>https://perry.apay.eu.cc/posts/rna2603/</id>
    <published>2026-10-06T09:30:03.000Z</published>
    <updated>2026-10-06T09:30:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>有了差异表达表，分析还没有结束。下一步不是立刻从 GO 结果里找一句最符合预期的话，而是回头确认样本、模型和候选是否值得解释，再把统计结果放回原来的生物学问题中。</p><p>本篇继续使用上一章的公开 <code>airway</code> 计数数据及保存的 <code>airway_bundle.rds</code>，只演示 bulk RNA-seq 的样本检查与功能分析。它不是单细胞教程，也不提供针对个人的医疗解释。下面代码是供读者运行的学习脚本，本机没有执行，因此文中没有虚构的 PCA 分离效果、富集通路名单或显著性数值。</p><h2 id="1-QC-应在检验前做，结果出来后还要复核"><a href="#1-QC-应在检验前做，结果出来后还要复核" class="headerlink" title="1. QC 应在检验前做，结果出来后还要复核"></a>1. QC 应在检验前做，结果出来后还要复核</h2><p>这篇把画图安排在统计学习练习之后，是为了复用上一章保存的对象，<strong>不意味着正式项目应先差异检验、后审查样本</strong>。实际工作应先确认原始读段和计数层面的质量，发现身份存疑、失败文库或明显异常时暂停；建模后再用诊断检查模型和候选是否出现新的问题。</p><p>我习惯给异常记录分三格：观察到什么、有哪些可能原因、准备怎样验证。比如某个样本的分配到基因的计数总量偏低，可能与测序深度、rRNA、比对、参考或建库有关，不能直接写成“这个样本生物学上表达更低”。先看第一篇的处理报告，再核对原始实验记录，远比直接删样本更有解释力。</p><p>MultiQC 可以连接上游多个工具的线索；计数层面的样本图则检查输入矩阵与元数据之间是否一致。这两类 QC 互相补充，不互相替代。尤其要注意 <code>colSums(counts(dds))</code> 是进入当前矩阵的基因计数和，不是原始 FASTQ 的总 reads，也不是必然等于比对 fragments。<a href="https://nf-co.re/rnaseq/3.27.0/docs/output">nf-core&#x2F;rnaseq 3.27.0 输出定义</a>、<a href="https://docs.seqera.io/multiqc/">MultiQC 官方概览</a></p><h2 id="2-探索图回答的是结构，不自动回答显著性"><a href="#2-探索图回答的是结构，不自动回答显著性" class="headerlink" title="2. 探索图回答的是结构，不自动回答显著性"></a>2. 探索图回答的是结构，不自动回答显著性</h2><p>直接对原始计数做欧氏距离，高表达基因和深度差异可能主导图。用于探索时通常采用方差稳定化等变换，便于观察样本间结构；差异检验仍使用计数模型，并不把转换值再塞回原始计数入口。VST 与样本距离、PCA 的关系可参照 <a href="https://bioconductor.org/packages/release/workflows/vignettes/rnaseqGene/inst/doc/rnaseqGene.html">Bioconductor 官方 RNA-seq 工作流</a>。</p><p>PCA 上处理组分开，是整体表达结构的一种观察，不是每个基因都差异表达的证明；处理组没有分开，也不自动否定局部效应。应同时按来源、批次、时间等真实信息标记，辨认最大的变异来自哪里。<code>blind = FALSE</code> 使用已有设计进行变换，也不会把来源或批次从矩阵里凭空删除。</p><p>热图也需要声明选基因的方法。先用显著基因筛选，再展示两组明显分开，不能把这种图当成独立证明。本练习选高变异基因作探索并进行行中心化：颜色表示某基因在各样本相对其平均值的变化，不表示不同基因间的绝对表达量高低。</p><h2 id="3-完整学习脚本第一段：读取-bundle-并保存图"><a href="#3-完整学习脚本第一段：读取-bundle-并保存图" class="headerlink" title="3. 完整学习脚本第一段：读取 bundle 并保存图"></a>3. 完整学习脚本第一段：读取 bundle 并保存图</h2><p>需要已经具备上一章的 R&#x2F;Bioconductor 依赖；后半段还需要 <code>clusterProfiler</code>、<code>AnnotationDbi</code> 和 <code>org.Hs.eg.db</code>。代码只检查依赖，不自动安装。<code>org.Hs.eg.db</code> 适用于这个人类数据例子，不能不改物种就用于小鼠、植物或微生物。准备依赖的正规入口仍是 <a href="https://www.bioconductor.org/install/">Bioconductor 安装说明</a>。</p><p>将本节与下一节的两个 R 代码块按顺序放入同一个 <code>airway_qc_enrichment_learning.R</code> 文件。运行方式为 <code>Rscript airway_qc_enrichment_learning.R &quot;上一章生成的完整路径/airway_bundle.rds&quot;</code>。输出创建在当前学习工作目录下一个带唯一名称的新目录，Rscript 退出后仍保留，不覆盖上一章文件。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">needed <span class="operator">&lt;-</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;DESeq2&quot;</span><span class="punctuation">,</span> <span class="string">&quot;SummarizedExperiment&quot;</span><span class="punctuation">,</span> <span class="string">&quot;clusterProfiler&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="string">&quot;AnnotationDbi&quot;</span><span class="punctuation">,</span> <span class="string">&quot;org.Hs.eg.db&quot;</span><span class="punctuation">)</span></span><br><span class="line">available <span class="operator">&lt;-</span> vapply<span class="punctuation">(</span>needed<span class="punctuation">,</span> requireNamespace<span class="punctuation">,</span> logical<span class="punctuation">(</span><span class="number">1</span><span class="punctuation">)</span><span class="punctuation">,</span> quietly <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span><span class="operator">!</span><span class="built_in">all</span><span class="punctuation">(</span>available<span class="punctuation">)</span><span class="punctuation">)</span> stop<span class="punctuation">(</span><span class="string">&quot;Prepare packages first: &quot;</span><span class="punctuation">,</span></span><br><span class="line">                         paste<span class="punctuation">(</span>needed<span class="punctuation">[</span><span class="operator">!</span>available<span class="punctuation">]</span><span class="punctuation">,</span> collapse <span class="operator">=</span> <span class="string">&quot;, &quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">suppressPackageStartupMessages<span class="punctuation">(</span><span class="punctuation">&#123;</span></span><br><span class="line">  library<span class="punctuation">(</span>DESeq2<span class="punctuation">)</span></span><br><span class="line">  library<span class="punctuation">(</span>SummarizedExperiment<span class="punctuation">)</span></span><br><span class="line">  library<span class="punctuation">(</span>clusterProfiler<span class="punctuation">)</span></span><br><span class="line">  library<span class="punctuation">(</span>org.Hs.eg.db<span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">)</span></span><br><span class="line">args <span class="operator">&lt;-</span> commandArgs<span class="punctuation">(</span>trailingOnly <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span><span class="built_in">length</span><span class="punctuation">(</span>args<span class="punctuation">)</span> <span class="operator">!=</span> <span class="number">1L</span> <span class="operator">||</span> <span class="operator">!</span>file.exists<span class="punctuation">(</span>args<span class="punctuation">[</span><span class="number">1</span><span class="punctuation">]</span><span class="punctuation">)</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  stop<span class="punctuation">(</span><span class="string">&quot;Usage: Rscript airway_qc_enrichment_learning.R AIRWAY_BUNDLE_PATH&quot;</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line">bundle <span class="operator">&lt;-</span> readRDS<span class="punctuation">(</span>args<span class="punctuation">[</span><span class="number">1</span><span class="punctuation">]</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>identical<span class="punctuation">(</span>bundle<span class="operator">$</span>input<span class="punctuation">,</span> <span class="string">&quot;Public airway teaching dataset&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">dds <span class="operator">&lt;-</span> bundle<span class="operator">$</span>dds</span><br><span class="line">res <span class="operator">&lt;-</span> bundle<span class="operator">$</span>res</span><br><span class="line">stopifnot<span class="punctuation">(</span>inherits<span class="punctuation">(</span>dds<span class="punctuation">,</span> <span class="string">&quot;DESeqDataSet&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span> inherits<span class="punctuation">(</span>res<span class="punctuation">,</span> <span class="string">&quot;DESeqResults&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          identical<span class="punctuation">(</span>rownames<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>res<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          identical<span class="punctuation">(</span>colnames<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>colData<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">out <span class="operator">&lt;-</span> tempfile<span class="punctuation">(</span><span class="string">&quot;perry-airway-qc-&quot;</span><span class="punctuation">,</span> tmpdir <span class="operator">=</span> getwd<span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>dir.create<span class="punctuation">(</span>out<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">qc <span class="operator">&lt;-</span> data.frame<span class="punctuation">(</span></span><br><span class="line">  sample_id <span class="operator">=</span> colnames<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">  condition <span class="operator">=</span> colData<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="operator">$</span>condition<span class="punctuation">,</span></span><br><span class="line">  subject <span class="operator">=</span> colData<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="operator">$</span>subject<span class="punctuation">,</span></span><br><span class="line">  modeled_gene_count_sum <span class="operator">=</span> colSums<span class="punctuation">(</span>counts<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">  modeled_genes_with_nonzero_count <span class="operator">=</span> colSums<span class="punctuation">(</span>counts<span class="punctuation">(</span>dds<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">  size_factor <span class="operator">=</span> sizeFactors<span class="punctuation">(</span>dds<span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>qc<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;sample_count_qc.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">vsd <span class="operator">&lt;-</span> varianceStabilizingTransformation<span class="punctuation">(</span>dds<span class="punctuation">,</span> blind <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">png<span class="punctuation">(</span>file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;pca.png&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span> width <span class="operator">=</span> <span class="number">1200</span><span class="punctuation">,</span> height <span class="operator">=</span> <span class="number">850</span><span class="punctuation">,</span> res <span class="operator">=</span> <span class="number">120</span><span class="punctuation">)</span></span><br><span class="line">print<span class="punctuation">(</span>plotPCA<span class="punctuation">(</span>vsd<span class="punctuation">,</span> intgroup <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;subject&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">dev.off<span class="punctuation">(</span><span class="punctuation">)</span></span><br><span class="line">vmat <span class="operator">&lt;-</span> assay<span class="punctuation">(</span>vsd<span class="punctuation">)</span></span><br><span class="line">distances <span class="operator">&lt;-</span> as.matrix<span class="punctuation">(</span>dist<span class="punctuation">(</span>t<span class="punctuation">(</span>vmat<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">png<span class="punctuation">(</span>file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;sample_distance.png&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span> width <span class="operator">=</span> <span class="number">1100</span><span class="punctuation">,</span> height <span class="operator">=</span> <span class="number">1100</span><span class="punctuation">)</span></span><br><span class="line">heatmap<span class="punctuation">(</span>distances<span class="punctuation">,</span> symm <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">,</span> scale <span class="operator">=</span> <span class="string">&quot;none&quot;</span><span class="punctuation">,</span></span><br><span class="line">         main <span class="operator">=</span> <span class="string">&quot;Airway teaching data: sample distance&quot;</span><span class="punctuation">)</span></span><br><span class="line">dev.off<span class="punctuation">(</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">variances <span class="operator">&lt;-</span> apply<span class="punctuation">(</span>vmat<span class="punctuation">,</span> <span class="number">1</span><span class="punctuation">,</span> var<span class="punctuation">)</span></span><br><span class="line">top <span class="operator">&lt;-</span> head<span class="punctuation">(</span>order<span class="punctuation">(</span>variances<span class="punctuation">,</span> decreasing <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="number">30L</span><span class="punctuation">)</span></span><br><span class="line">centered <span class="operator">&lt;-</span> sweep<span class="punctuation">(</span>vmat<span class="punctuation">[</span>top<span class="punctuation">,</span> <span class="punctuation">,</span> drop <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                  rowMeans<span class="punctuation">(</span>vmat<span class="punctuation">[</span>top<span class="punctuation">,</span> <span class="punctuation">,</span> drop <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">]</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="string">&quot;-&quot;</span><span class="punctuation">)</span></span><br><span class="line">png<span class="punctuation">(</span>file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;high_variance_genes.png&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span> width <span class="operator">=</span> <span class="number">1200</span><span class="punctuation">,</span> height <span class="operator">=</span> <span class="number">1200</span><span class="punctuation">)</span></span><br><span class="line">heatmap<span class="punctuation">(</span>centered<span class="punctuation">,</span> scale <span class="operator">=</span> <span class="string">&quot;none&quot;</span><span class="punctuation">,</span> Colv <span class="operator">=</span> <span class="literal">NA</span><span class="punctuation">,</span></span><br><span class="line">         main <span class="operator">=</span> <span class="string">&quot;High-variance genes: row-centered VST&quot;</span><span class="punctuation">)</span></span><br><span class="line">dev.off<span class="punctuation">(</span><span class="punctuation">)</span></span><br><span class="line">capture.output<span class="punctuation">(</span>sessionInfo<span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> file <span class="operator">=</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;sessionInfo.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p>读图时先检查轴、颜色和样本顺序，再看聚类。样本距离图中的“近”，依赖所用的变换、特征与距离定义；不能把它直接解释成两位供者在其他生物学层面更相似。若一个样本的位置意外，先查身份、处理、文库与参考记录，再形成排除决定，并评估排除是否破坏配对或造成混杂。</p><h2 id="4-ORA-的背景是选择机会，不是随便找的一张基因表"><a href="#4-ORA-的背景是选择机会，不是随便找的一张基因表" class="headerlink" title="4. ORA 的背景是选择机会，不是随便找的一张基因表"></a>4. ORA 的背景是选择机会，不是随便找的一张基因表</h2><p>过度代表分析（ORA）问的是：相对于一个明确背景，候选列表里某类注释是否更常见？背景应代表此次分析中有机会成为候选、且能进入相应注释检验的基因，不应自动等于全基因组，也不能只取显著基因。否则你改变了“原本有多少机会选中这类基因”的基准，结论可能被这种选择改变。<a href="https://www.geneontology.org/docs/go-enrichment-analysis/">Gene Ontology 官方富集说明</a></p><p>本例明确规定：候选规则基于本次普通检验的有限 <code>padj</code>；背景取拥有有限 <code>pvalue</code> 和 <code>padj</code>、同时具有 BP 注释的全部基因，包含不显著基因。被预过滤排除或因异常而未检验的基因不进入背景；独立过滤后没有 <code>padj</code> 的基因在本例也不属于这次 FDR 候选选择空间。正式分析可以有不同的合格基因定义，但必须说明规则，并同步应用到背景和前景，不能在得到通路结果后随意扩大背景。</p><p>这里分别考虑正、负效应的候选，而不是把两个方向都塞进一个列表后宣称通路“上调”。为了让示例可检查，代码只运行正效应列表；读者可以按同一背景构造负效应列表另做分析，并明确那是另一个候选集合。Gene Ontology 注释也有版本与覆盖限制，必须记录注释库日期、未映射基因与纳入数量。</p><h2 id="5-完整学习脚本第二段：明确背景的-GO-与完整排序的-GSEA"><a href="#5-完整学习脚本第二段：明确背景的-GO-与完整排序的-GSEA" class="headerlink" title="5. 完整学习脚本第二段：明确背景的 GO 与完整排序的 GSEA"></a>5. 完整学习脚本第二段：明确背景的 GO 与完整排序的 GSEA</h2><p>下面沿用同一个 R 文件中的 <code>dds</code>、<code>res</code> 与 <code>out</code>。选择 ENSEMBL 作为 keyType，避免为了演示而将所有 ID 转成容易发生多对一的符号。正式数据若带版本号，应先核对来源，做有记录的转换并检查转换后的重复，不能盲目删后缀。</p><p><code>enrichGO</code> 不传 <code>universe</code> 时会使用数据库背景，因此这里显式传入经过检查的背景。函数参数与输出约定见 <a href="https://raw.githubusercontent.com/YuLab-SMU/clusterProfiler/master/man/enrichGO.Rd">作者维护的 enrichGO 文档</a>。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">ids <span class="operator">&lt;-</span> rownames<span class="punctuation">(</span>res<span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>ids<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>grepl<span class="punctuation">(</span><span class="string">&quot;^ENSG[0-9]+$&quot;</span><span class="punctuation">,</span> ids<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">orgdb <span class="operator">&lt;-</span> org.Hs.eg.db</span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="string">&quot;ENSEMBL&quot;</span> <span class="operator">%in%</span> AnnotationDbi<span class="operator">::</span>keytypes<span class="punctuation">(</span>orgdb<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">known <span class="operator">&lt;-</span> intersect<span class="punctuation">(</span>ids<span class="punctuation">,</span> AnnotationDbi<span class="operator">::</span>keys<span class="punctuation">(</span>orgdb<span class="punctuation">,</span> keytype <span class="operator">=</span> <span class="string">&quot;ENSEMBL&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">length</span><span class="punctuation">(</span>known<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">)</span></span><br><span class="line">annotation <span class="operator">&lt;-</span> AnnotationDbi<span class="operator">::</span>select<span class="punctuation">(</span></span><br><span class="line">  orgdb<span class="punctuation">,</span> keys <span class="operator">=</span> known<span class="punctuation">,</span> keytype <span class="operator">=</span> <span class="string">&quot;ENSEMBL&quot;</span><span class="punctuation">,</span></span><br><span class="line">  columns <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;GOALL&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ONTOLOGYALL&quot;</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">)</span></span><br><span class="line">bp_rows <span class="operator">&lt;-</span> <span class="operator">!</span><span class="built_in">is.na</span><span class="punctuation">(</span>annotation<span class="operator">$</span>GOALL<span class="punctuation">)</span> <span class="operator">&amp;</span> <span class="operator">!</span><span class="built_in">is.na</span><span class="punctuation">(</span>annotation<span class="operator">$</span>ONTOLOGYALL<span class="punctuation">)</span> <span class="operator">&amp;</span></span><br><span class="line">           annotation<span class="operator">$</span>ONTOLOGYALL <span class="operator">==</span> <span class="string">&quot;BP&quot;</span></span><br><span class="line">bp_ids <span class="operator">&lt;-</span> unique<span class="punctuation">(</span>annotation<span class="operator">$</span>ENSEMBL<span class="punctuation">[</span>bp_rows<span class="punctuation">]</span><span class="punctuation">)</span></span><br><span class="line">fdr_eligible <span class="operator">&lt;-</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>res<span class="operator">$</span>pvalue<span class="punctuation">)</span> <span class="operator">&amp;</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>res<span class="operator">$</span>padj<span class="punctuation">)</span></span><br><span class="line">universe <span class="operator">&lt;-</span> intersect<span class="punctuation">(</span>ids<span class="punctuation">[</span>fdr_eligible<span class="punctuation">]</span><span class="punctuation">,</span> bp_ids<span class="punctuation">)</span></span><br><span class="line">candidate_up <span class="operator">&lt;-</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>res<span class="operator">$</span>padj<span class="punctuation">)</span> <span class="operator">&amp;</span> res<span class="operator">$</span>padj <span class="operator">&lt;</span> <span class="number">0.05</span> <span class="operator">&amp;</span></span><br><span class="line">                <span class="built_in">is.finite</span><span class="punctuation">(</span>res<span class="operator">$</span>log2FoldChange<span class="punctuation">)</span> <span class="operator">&amp;</span> res<span class="operator">$</span>log2FoldChange <span class="operator">&gt;</span> <span class="number">0</span></span><br><span class="line">foreground <span class="operator">&lt;-</span> intersect<span class="punctuation">(</span>ids<span class="punctuation">[</span>candidate_up<span class="punctuation">]</span><span class="punctuation">,</span> universe<span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">length</span><span class="punctuation">(</span>universe<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>foreground <span class="operator">%in%</span> universe<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">writeLines<span class="punctuation">(</span>universe<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;ora_background_ensembl.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">writeLines<span class="punctuation">(</span>foreground<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;ora_positive_foreground_ensembl.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">writeLines<span class="punctuation">(</span>setdiff<span class="punctuation">(</span>ids<span class="punctuation">,</span> known<span class="punctuation">)</span><span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;not_in_annotation_keyspace.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">capture.output<span class="punctuation">(</span>orgdb<span class="punctuation">,</span> file <span class="operator">=</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;annotation_database_info.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">audit <span class="operator">&lt;-</span> data.frame<span class="punctuation">(</span></span><br><span class="line">  definition <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;modeled_genes&quot;</span><span class="punctuation">,</span> <span class="string">&quot;finite_p_and_padj&quot;</span><span class="punctuation">,</span> <span class="string">&quot;BP_ORA_background&quot;</span><span class="punctuation">,</span></span><br><span class="line">                 <span class="string">&quot;positive_FDR_candidates_in_background&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">  n <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="built_in">length</span><span class="punctuation">(</span>ids<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">sum</span><span class="punctuation">(</span>fdr_eligible<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">length</span><span class="punctuation">(</span>universe<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">length</span><span class="punctuation">(</span>foreground<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>audit<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;enrichment_input_audit.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span><span class="built_in">length</span><span class="punctuation">(</span>foreground<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  ora <span class="operator">&lt;-</span> enrichGO<span class="punctuation">(</span>gene <span class="operator">=</span> foreground<span class="punctuation">,</span> universe <span class="operator">=</span> universe<span class="punctuation">,</span> OrgDb <span class="operator">=</span> orgdb<span class="punctuation">,</span></span><br><span class="line">                   keyType <span class="operator">=</span> <span class="string">&quot;ENSEMBL&quot;</span><span class="punctuation">,</span> ont <span class="operator">=</span> <span class="string">&quot;BP&quot;</span><span class="punctuation">,</span> pAdjustMethod <span class="operator">=</span> <span class="string">&quot;BH&quot;</span><span class="punctuation">,</span></span><br><span class="line">                   pvalueCutoff <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span> qvalueCutoff <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                   minGSSize <span class="operator">=</span> <span class="number">10</span><span class="punctuation">,</span> maxGSSize <span class="operator">=</span> <span class="number">500</span><span class="punctuation">,</span> readable <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">  ora_table <span class="operator">&lt;-</span> as.data.frame<span class="punctuation">(</span>ora<span class="punctuation">)</span></span><br><span class="line">  write.table<span class="punctuation">(</span>ora_table<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;ora_positive_BP_returned_terms.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">               sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span> <span class="keyword">else</span> <span class="punctuation">&#123;</span></span><br><span class="line">  message<span class="punctuation">(</span><span class="string">&quot;No foreground genes: ORA skipped, not an enrichment result&quot;</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># A separate eligibility rule for preranked GSEA, not the ORA foreground.</span></span><br><span class="line"><span class="comment"># Keep finite test statistics even if independent filtering made padj NA.</span></span><br><span class="line">rank_ok <span class="operator">&lt;-</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>res<span class="operator">$</span>stat<span class="punctuation">)</span> <span class="operator">&amp;</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>res<span class="operator">$</span>pvalue<span class="punctuation">)</span> <span class="operator">&amp;</span> ids <span class="operator">%in%</span> bp_ids</span><br><span class="line">ranking <span class="operator">&lt;-</span> setNames<span class="punctuation">(</span>res<span class="operator">$</span>stat<span class="punctuation">[</span>rank_ok<span class="punctuation">]</span><span class="punctuation">,</span> ids<span class="punctuation">[</span>rank_ok<span class="punctuation">]</span><span class="punctuation">)</span></span><br><span class="line">ranking <span class="operator">&lt;-</span> sort<span class="punctuation">(</span>ranking<span class="punctuation">,</span> decreasing <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">length</span><span class="punctuation">(</span>ranking<span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="number">10L</span><span class="punctuation">,</span> <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span><span class="built_in">names</span><span class="punctuation">(</span>ranking<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>data.frame<span class="punctuation">(</span>gene_id <span class="operator">=</span> <span class="built_in">names</span><span class="punctuation">(</span>ranking<span class="punctuation">)</span><span class="punctuation">,</span> Wald_stat <span class="operator">=</span> unname<span class="punctuation">(</span>ranking<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;gsea_ranked_genes.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line"><span class="comment"># Use gseGO&#x27;s fixed default seed; preserve the actual package version.</span></span><br><span class="line">gsea <span class="operator">&lt;-</span> gseGO<span class="punctuation">(</span>geneList <span class="operator">=</span> ranking<span class="punctuation">,</span> OrgDb <span class="operator">=</span> orgdb<span class="punctuation">,</span> keyType <span class="operator">=</span> <span class="string">&quot;ENSEMBL&quot;</span><span class="punctuation">,</span></span><br><span class="line">               ont <span class="operator">=</span> <span class="string">&quot;BP&quot;</span><span class="punctuation">,</span> minGSSize <span class="operator">=</span> <span class="number">10</span><span class="punctuation">,</span> maxGSSize <span class="operator">=</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">               pvalueCutoff <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span> pAdjustMethod <span class="operator">=</span> <span class="string">&quot;BH&quot;</span><span class="punctuation">,</span></span><br><span class="line">               verbose <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> seed <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>as.data.frame<span class="punctuation">(</span>gsea<span class="punctuation">)</span><span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;gsea_BP_returned_terms.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">saveRDS<span class="punctuation">(</span><span class="built_in">list</span><span class="punctuation">(</span>background <span class="operator">=</span> universe<span class="punctuation">,</span> foreground <span class="operator">=</span> foreground<span class="punctuation">,</span></span><br><span class="line">             ranking <span class="operator">=</span> ranking<span class="punctuation">,</span> gsea <span class="operator">=</span> gsea<span class="punctuation">,</span></span><br><span class="line">             gsea_seed_setting <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">,</span></span><br><span class="line">             contrast <span class="operator">=</span> bundle<span class="operator">$</span>contrast<span class="punctuation">,</span> dataset <span class="operator">=</span> bundle<span class="operator">$</span>input<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">         file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;enrichment_bundle.rds&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">message<span class="punctuation">(</span><span class="string">&quot;Teaching QC/enrichment outputs: &quot;</span><span class="punctuation">,</span> normalizePath<span class="punctuation">(</span>out<span class="punctuation">)</span><span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p><code>pvalueCutoff = 1</code> 与 <code>qvalueCutoff = 1</code> 是为了减少结果展示前的阈值截断，方便学习查看函数返回的表，并不把返回的所有行都标成显著；函数仍可能因注释、集合大小或不可用统计量不返回某些条目。报告候选富集时要另行声明校正后的阈值和所检查的条目范围。空表也是可能结果，不能编造一个通路名让练习显得成功。</p><p>代码使用该人类数据例子可识别的基因 ID；它的检查刻意严格，不是可直接接收任意物种矩阵的万能脚本。<code>AnnotationDbi::select</code> 返回一对多注释是正常现象，因为一个基因可以属于多个 GO 条目；不应把这些注释行误当成多个独立基因。注释查询接口与映射关系可参考 <a href="https://bioconductor.org/packages/release/bioc/vignettes/AnnotationDbi/inst/doc/IntroToAnnotationPackages.pdf">AnnotationDbi 官方说明</a>。</p><h2 id="6-GSEA-不只使用显著基因，也不靠随机抖动修饰排名"><a href="#6-GSEA-不只使用显著基因，也不靠随机抖动修饰排名" class="headerlink" title="6. GSEA 不只使用显著基因，也不靠随机抖动修饰排名"></a>6. GSEA 不只使用显著基因，也不靠随机抖动修饰排名</h2><p>ORA 需要先定义候选集合；预排序 GSEA 则使用具有可用统计量的完整合格排序列表，不先截成显著基因。本例采用带方向的 Wald 统计量，越靠顶部越支持 treated 相对 control 的正方向，底部支持负方向。不能用绝对值排名后还把 NES 解释成处理上调&#x2F;下调；也不要用不带符号的 p 值排名期待保留方向。<a href="https://docs.gsea-msigdb.org/GSEA/GSEA_User_Guide/">GSEA 官方排序与方向说明</a></p><p>排序中要有唯一基因 ID、有限数值和明确方向；注释与排序 keyType 要一致。遇到很多并列排名，先检查数据分辨率、过滤和排序统计量，并保留软件警告，不要为了没有警告而随意添加随机噪声。基因集合版本、集合大小范围和随机设置都应记录；不同版本返回不同结果时，有这些记录才可以调查。<a href="https://raw.githubusercontent.com/YuLab-SMU/clusterProfiler/master/man/gseGO.Rd">作者维护的 gseGO 参数文档</a></p><p>还要区分预排序方法和基于样本标签置换的标准 GSEA，它们使用的信息和假设不同；不能认为本例会自动解决小样本、配对或基因相关性带来的所有问题。复杂研究需要根据设计选择适合的基因集方法，富集分析本身也不能补救不可信的差异表达输入。</p><h2 id="7-富集不是“通路已激活”，显著不是“机制已证明”"><a href="#7-富集不是“通路已激活”，显著不是“机制已证明”" class="headerlink" title="7. 富集不是“通路已激活”，显著不是“机制已证明”"></a>7. 富集不是“通路已激活”，显著不是“机制已证明”</h2><p>假设一个候选列表富集到与免疫相关的条目，谨慎的表述是：在这个候选定义、背景和注释版本下，这类基因出现得比背景预期更集中。它没有自动证明免疫功能增强，也没有说明改变发生在哪种细胞。bulk 样本的变化可能来自细胞组成、细胞状态或二者同时改变，单靠这一张表不能区分。</p><p>同样，GSEA 的正 NES 表示某个集合倾向排序顶部，不等于所有成员都增加，更不等于生化通路的净活性增强。应查看贡献基因、它们的方向与效应、集合里的抑制&#x2F;促进关系和实验背景，再决定可以提出什么后续假设。选择一个预期相关的条目作图，也不能隐藏同时检验的其他条目。</p><p>GO 的多个条目可能共享很多基因。几十个相关名称不一定是几十个独立发现，图上的气泡数量更不是机制数量。解释时可以按主题整理冗余条目，但保存原始表、规则和贡献基因，避免整理过程只留下最符合故事的词。对机制结论，还需要独立干预、时间顺序、蛋白或功能层面的证据，而不是继续给同一批差异基因换一个富集工具。</p><h2 id="8-把结果写成可检查的四句话"><a href="#8-把结果写成可检查的四句话" class="headerlink" title="8. 把结果写成可检查的四句话"></a>8. 把结果写成可检查的四句话</h2><p>第一句说明样本、实验单位与对比方向。第二句说明预处理、过滤、设计公式、QC 和任何排除。第三句报告效应与不确定性，并说明 FDR、候选规则、ORA 背景或 GSEA 排序方法。第四句区分已观察到的结构、统计支持的关联和仍需验证的机制解释。</p><p>例如可以先写“本练习使用公开 airway 数据，比较 treated 相对 control，并在设计中考虑来源配对”，再根据实际运行的结果填入检查与数字。不要把“运行完成”“文件存在”写成“结果正确”，也不要从代码模板自动生成尚未观察到的生物学结论。</p><h2 id="9-三个值得自己动手的自检"><a href="#9-三个值得自己动手的自检" class="headerlink" title="9. 三个值得自己动手的自检"></a>9. 三个值得自己动手的自检</h2><p>先数一数 ORA 背景与前景的大小，并检查前景确实是背景子集。然后只在教学副本中把背景错误地改为前景，观察为什么这改变了问题；不要将错误实验作为正式分析。最后把 GSEA 排序方向反过来，查同一个条目的方向如何随对比改变，并写出其含义，而不是只看显著性是否保留。</p><p>也可以把上一章 <code>padj</code> 为 NA 的行按原因归类，比较它们是否有 p 值和统计量。理解这些行为什么不被同一规则纳入 ORA，却可能仍在合格 GSEA 排名中，比机械删掉所有 NA 更有帮助。</p><p>至此，一个完整的入门交付包包括：原始输入与元数据、处理和参考版本、QC 与模型记录、全量差异表、明确的候选&#x2F;背景&#x2F;排名文件、注释版本、图与解释边界。能重新生成它们、能说明每个选择，比只拥有一张漂亮的火山图更接近可信分析。</p><p>上一章：<a href="/posts/rna2602/">DESeq2 设计公式与对比</a>；课程起点：<a href="/posts/rna2601/">实验设计、FASTQ 与计数</a>。</p><h2 id="参考与继续阅读"><a href="#参考与继续阅读" class="headerlink" title="参考与继续阅读"></a>参考与继续阅读</h2><ul><li><a href="https://bioconductor.org/packages/release/workflows/vignettes/rnaseqGene/inst/doc/rnaseqGene.html">Bioconductor：官方 RNA-seq 探索与差异表达工作流</a></li><li><a href="https://www.geneontology.org/docs/go-enrichment-analysis/">Gene Ontology：富集、背景与结果解释</a></li><li><a href="https://www.bioconductor.org/packages/release/bioc/html/clusterProfiler.html">clusterProfiler：官方包说明</a></li><li><a href="https://raw.githubusercontent.com/YuLab-SMU/clusterProfiler/master/man/enrichGO.Rd">clusterProfiler 作者文档：enrichGO</a></li><li><a href="https://raw.githubusercontent.com/YuLab-SMU/clusterProfiler/master/man/gseGO.Rd">clusterProfiler 作者文档：gseGO</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/AnnotationDbi/inst/doc/IntroToAnnotationPackages.pdf">AnnotationDbi：注释与 ID 查询</a></li><li><a href="https://docs.gsea-msigdb.org/GSEA/GSEA_User_Guide/">GSEA 官方用户指南</a></li></ul>]]></content>
    
    
    <summary type="html">承接 airway 差异表达练习，绘制样本 QC 与探索图，明确 ORA 背景和 GSEA 排序输入，避免把富集、聚类或显著性过度解释为机制。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="生物信息学" scheme="https://perry.apay.eu.cc/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
    
    <category term="质量控制" scheme="https://perry.apay.eu.cc/tags/%E8%B4%A8%E9%87%8F%E6%8E%A7%E5%88%B6/"/>
    
    <category term="RNA-seq" scheme="https://perry.apay.eu.cc/tags/RNA-seq/"/>
    
    <category term="GO富集" scheme="https://perry.apay.eu.cc/tags/GO%E5%AF%8C%E9%9B%86/"/>
    
    <category term="GSEA" scheme="https://perry.apay.eu.cc/tags/GSEA/"/>
    
  </entry>
  
  <entry>
    <title>Bulk RNA-seq 分析入门（二）：DESeq2 设计公式、对比与差异表达</title>
    <link href="https://perry.apay.eu.cc/posts/rna2602/"/>
    <id>https://perry.apay.eu.cc/posts/rna2602/</id>
    <published>2026-10-06T09:30:02.000Z</published>
    <updated>2026-10-06T09:30:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇把 FASTQ 处理到来源明确的计数或定量结果。这一篇往前走一步：如何从一个计数矩阵得到“比较了谁、考虑了什么因素、结果有多不确定”都能够说清楚的差异表达表？</p><p>本文只讨论 bulk RNA-seq，不是单细胞分析。本篇练习使用 Bioconductor <code>airway</code> 包附带的公开计数对象，不需要先下载原始 FASTQ。它包含四个气道平滑肌细胞来源的处理与未处理样本，适合演示配对关系。数据背景来自 <a href="https://bioconductor.org/packages/release/data/experiment/vignettes/airway/inst/doc/airway.html">airway 官方说明</a>。这里学习统计流程，不将演示结果当成新的实验发现或医疗建议。</p><p>代码按 2026-10-06 核验的官方 API 组织，DESeq2 官方教程标注的包版本为 <code>1.52.0</code>。读者实际运行要记录自己的版本，不能只写“用了 DESeq2”。本机未执行下列 R 分析，因此本文不提供杜撰的差异基因数量、p 值或结果图片。</p><h2 id="1-先确认输入，而不是先调用-DESeq"><a href="#1-先确认输入，而不是先调用-DESeq" class="headerlink" title="1. 先确认输入，而不是先调用 DESeq"></a>1. 先确认输入，而不是先调用 DESeq</h2><p>最容易让整个分析失去意义的错误，是把样本标签与计数列错配。一个矩阵有六列，元数据也有六行，这只是尺寸相同，不是身份相同。必须先检查 ID 唯一、双方集合相同，再按 ID 重排，最后检查顺序完全一致。</p><p>第二个检查是输入单位。普通的计数矩阵入口要求非负、有限、整数的原始计数，不接受 TPM、FPKM、VST、rlog 或已经做过库大小缩放的数值。发现小数时应追问生成方法，不能为了通过构造函数而直接四舍五入。Salmon 估计计数的小数可以走 <code>tximport</code> 的专门入口，和 TPM 伪装计数不是同一回事。<a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html#why-un-normalized-counts">DESeq2 计数输入说明</a></p><p>第三个检查是元数据与实验设计。没有足够独立生物学重复、关键标签存疑或明显失败文库时，应暂停检验。已经知道的配对关系不能因为“不加这个变量也能跑”就忽略；batch 也不能因为 PCA 看起来有影响就随意追加，而应先追查真实实验记录以及它是否可与处理因素分别估计。</p><h2 id="2-设计公式是一张解释承诺，不是装饰"><a href="#2-设计公式是一张解释承诺，不是装饰" class="headerlink" title="2. 设计公式是一张解释承诺，不是装饰"></a>2. 设计公式是一张解释承诺，不是装饰</h2><p>考虑三个原创的假设问题：独立来源的处理&#x2F;对照比较可写 <code>~ condition</code>；两组都分布在各批次且批次效应可估计时可写 <code>~ batch + condition</code>；同一个来源各自提供处理与对照样本时可写 <code>~ subject + condition</code>。这些公式回答的问题不一样，不能只以哪张火山图更漂亮为选择标准。</p><p>本篇的 airway 练习采用 <code>~ subject + condition</code>，将来源之间的稳定差别和我们感兴趣的处理差别区分开。这个加性模型估计一个共同的处理系数，不估计来源与处理的交互；它不能证明各来源的真实效应相同，也不能涵盖所有重复测量问题。复杂时间序列、多层随机效应或处理交互需要另外设计模型；不要把两个条件的配对模板复制到所有实验上。<a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html#multi-factor-designs">DESeq2 官方配对与多因素分析入口</a></p><p>若所有对照都来自 B1、所有处理都来自 B2，那么 <code>batch</code> 与 <code>condition</code> 携带同一份分组信息。<code>~ batch + condition</code> 无法告诉我们变化究竟来自处理还是批次，这不是把 batch 变量删掉就解决了。设计矩阵的满秩检查可以发现不可分别估计的参数，但“满秩”也不保证混杂完全消失或样本量足够，只是必要的数学检查。</p><h2 id="3-完整练习：公开计数、配对模型与结果保存"><a href="#3-完整练习：公开计数、配对模型与结果保存" class="headerlink" title="3. 完整练习：公开计数、配对模型与结果保存"></a>3. 完整练习：公开计数、配对模型与结果保存</h2><p>运行前需要可用的 R 与相容的 Bioconductor 环境，并已经具备 <code>airway</code>、<code>SummarizedExperiment</code>、<code>DESeq2</code>、<code>apeglm</code>。本段检查依赖是否存在，不会自动安装。依赖缺失时先根据 <a href="https://www.bioconductor.org/install/">Bioconductor 官方安装说明</a>准备独立环境，确认安装权限、联网条件与版本兼容，再运行练习。</p><p>将下面代码保存为 <code>airway_deseq2_learning.R</code>，在自己的学习工作目录运行 <code>Rscript airway_deseq2_learning.R</code>。它只读取包自带的公开数据，在当前工作目录下创建一个带唯一名称的全新结果目录，不覆盖已有目录，也不会因 Rscript 退出而随会话临时目录消失。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line">required <span class="operator">&lt;-</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;airway&quot;</span><span class="punctuation">,</span> <span class="string">&quot;SummarizedExperiment&quot;</span><span class="punctuation">,</span> <span class="string">&quot;DESeq2&quot;</span><span class="punctuation">,</span> <span class="string">&quot;apeglm&quot;</span><span class="punctuation">)</span></span><br><span class="line">available <span class="operator">&lt;-</span> vapply<span class="punctuation">(</span>required<span class="punctuation">,</span> requireNamespace<span class="punctuation">,</span> logical<span class="punctuation">(</span><span class="number">1</span><span class="punctuation">)</span><span class="punctuation">,</span> quietly <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span><span class="operator">!</span><span class="built_in">all</span><span class="punctuation">(</span>available<span class="punctuation">)</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  stop<span class="punctuation">(</span><span class="string">&quot;Prepare compatible R/Bioconductor packages first: &quot;</span><span class="punctuation">,</span></span><br><span class="line">       paste<span class="punctuation">(</span>required<span class="punctuation">[</span><span class="operator">!</span>available<span class="punctuation">]</span><span class="punctuation">,</span> collapse <span class="operator">=</span> <span class="string">&quot;, &quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line">suppressPackageStartupMessages<span class="punctuation">(</span><span class="punctuation">&#123;</span></span><br><span class="line">  library<span class="punctuation">(</span>airway<span class="punctuation">)</span></span><br><span class="line">  library<span class="punctuation">(</span>SummarizedExperiment<span class="punctuation">)</span></span><br><span class="line">  library<span class="punctuation">(</span>DESeq2<span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">)</span></span><br><span class="line">data<span class="punctuation">(</span><span class="string">&quot;airway&quot;</span><span class="punctuation">,</span> package <span class="operator">=</span> <span class="string">&quot;airway&quot;</span><span class="punctuation">)</span></span><br><span class="line">cts <span class="operator">&lt;-</span> assay<span class="punctuation">(</span>airway<span class="punctuation">,</span> <span class="string">&quot;counts&quot;</span><span class="punctuation">)</span></span><br><span class="line">meta <span class="operator">&lt;-</span> as.data.frame<span class="punctuation">(</span>colData<span class="punctuation">(</span>airway<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">is.matrix</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">is.numeric</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">is.finite</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span>cts <span class="operator">&gt;=</span> <span class="number">0</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>cts <span class="operator">==</span> <span class="built_in">floor</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span>cts <span class="operator">&lt;=</span> .Machine<span class="operator">$</span>integer.max<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>rownames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          setequal<span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta <span class="operator">&lt;-</span> meta<span class="punctuation">[</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> <span class="punctuation">,</span> drop <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">]</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>identical<span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta<span class="operator">$</span>subject <span class="operator">&lt;-</span> factor<span class="punctuation">(</span>meta<span class="operator">$</span>cell<span class="punctuation">)</span></span><br><span class="line">meta<span class="operator">$</span>condition <span class="operator">&lt;-</span> factor<span class="punctuation">(</span>meta<span class="operator">$</span>dex<span class="punctuation">,</span></span><br><span class="line">                         levels <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;untrt&quot;</span><span class="punctuation">,</span> <span class="string">&quot;trt&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">                         labels <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;control&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="operator">!</span><span class="built_in">anyNA</span><span class="punctuation">(</span>meta<span class="operator">$</span>subject<span class="punctuation">)</span><span class="punctuation">,</span> <span class="operator">!</span><span class="built_in">anyNA</span><span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          nlevels<span class="punctuation">(</span>meta<span class="operator">$</span>subject<span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="number">3L</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span>table<span class="punctuation">(</span>meta<span class="operator">$</span>subject<span class="punctuation">,</span> meta<span class="operator">$</span>condition<span class="punctuation">)</span> <span class="operator">==</span> <span class="number">1L</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">design_formula <span class="operator">&lt;-</span> <span class="operator">~</span> subject <span class="operator">+</span> condition</span><br><span class="line">design_matrix <span class="operator">&lt;-</span> model.matrix<span class="punctuation">(</span>design_formula<span class="punctuation">,</span> data <span class="operator">=</span> meta<span class="punctuation">)</span></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span>qr<span class="punctuation">(</span>design_matrix<span class="punctuation">)</span><span class="operator">$</span>rank <span class="operator">!=</span> ncol<span class="punctuation">(</span>design_matrix<span class="punctuation">)</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  stop<span class="punctuation">(</span><span class="string">&quot;Design is not full rank: review confounding and unused levels&quot;</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span>nrow<span class="punctuation">(</span>design_matrix<span class="punctuation">)</span> <span class="operator">&lt;=</span> ncol<span class="punctuation">(</span>design_matrix<span class="punctuation">)</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span><br><span class="line">  stop<span class="punctuation">(</span><span class="string">&quot;No residual degrees of freedom for this teaching model&quot;</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br><span class="line">storage.mode<span class="punctuation">(</span>cts<span class="punctuation">)</span> <span class="operator">&lt;-</span> <span class="string">&quot;integer&quot;</span></span><br><span class="line">dds <span class="operator">&lt;-</span> DESeqDataSetFromMatrix<span class="punctuation">(</span>countData <span class="operator">=</span> cts<span class="punctuation">,</span> colData <span class="operator">=</span> meta<span class="punctuation">,</span></span><br><span class="line">                             design <span class="operator">=</span> design_formula<span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># A declared, condition-independent teaching prefilter.</span></span><br><span class="line">smallest_group <span class="operator">&lt;-</span> <span class="built_in">min</span><span class="punctuation">(</span>table<span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">keep <span class="operator">&lt;-</span> rowSums<span class="punctuation">(</span>counts<span class="punctuation">(</span>dds<span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="number">10L</span><span class="punctuation">)</span> <span class="operator">&gt;=</span> smallest_group</span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">any</span><span class="punctuation">(</span>keep<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">dds <span class="operator">&lt;-</span> dds<span class="punctuation">[</span>keep<span class="punctuation">,</span> <span class="punctuation">]</span></span><br><span class="line">dds <span class="operator">&lt;-</span> DESeq<span class="punctuation">(</span>dds<span class="punctuation">)</span></span><br><span class="line">res <span class="operator">&lt;-</span> results<span class="punctuation">(</span>dds<span class="punctuation">,</span> contrast <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">,</span> <span class="string">&quot;control&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">               alpha <span class="operator">=</span> <span class="number">0.05</span><span class="punctuation">)</span></span><br><span class="line">coef_name <span class="operator">&lt;-</span> <span class="string">&quot;condition_treated_vs_control&quot;</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>coef_name <span class="operator">%in%</span> resultsNames<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">res_shrunk <span class="operator">&lt;-</span> lfcShrink<span class="punctuation">(</span>dds<span class="punctuation">,</span> coef <span class="operator">=</span> coef_name<span class="punctuation">,</span> type <span class="operator">=</span> <span class="string">&quot;apeglm&quot;</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">tab <span class="operator">&lt;-</span> data.frame<span class="punctuation">(</span>gene_id <span class="operator">=</span> rownames<span class="punctuation">(</span>res<span class="punctuation">)</span><span class="punctuation">,</span> as.data.frame<span class="punctuation">(</span>res<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">                  row.names <span class="operator">=</span> <span class="literal">NULL</span><span class="punctuation">,</span> check.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">tab<span class="operator">$</span>log2FC_shrunk <span class="operator">&lt;-</span> res_shrunk<span class="operator">$</span>log2FoldChange<span class="punctuation">[</span></span><br><span class="line">  match<span class="punctuation">(</span>tab<span class="operator">$</span>gene_id<span class="punctuation">,</span> rownames<span class="punctuation">(</span>res_shrunk<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"><span class="punctuation">]</span></span><br><span class="line">tab<span class="operator">$</span>has_test_pvalue <span class="operator">&lt;-</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>tab<span class="operator">$</span>pvalue<span class="punctuation">)</span></span><br><span class="line">tab<span class="operator">$</span>has_adjusted_pvalue <span class="operator">&lt;-</span> <span class="built_in">is.finite</span><span class="punctuation">(</span>tab<span class="operator">$</span>padj<span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line">out <span class="operator">&lt;-</span> tempfile<span class="punctuation">(</span><span class="string">&quot;perry-airway-de-&quot;</span><span class="punctuation">,</span> tmpdir <span class="operator">=</span> getwd<span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>dir.create<span class="punctuation">(</span>out<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">saveRDS<span class="punctuation">(</span>cts<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;input_raw_counts.rds&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>meta<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;sample_metadata.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> col.names <span class="operator">=</span> <span class="literal">NA</span><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>tab<span class="punctuation">[</span>order<span class="punctuation">(</span>tab<span class="operator">$</span>padj<span class="punctuation">,</span> na.last <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">             file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;treated_vs_control.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">saveRDS<span class="punctuation">(</span><span class="built_in">list</span><span class="punctuation">(</span>dds <span class="operator">=</span> dds<span class="punctuation">,</span> res <span class="operator">=</span> res<span class="punctuation">,</span> res_shrunk <span class="operator">=</span> res_shrunk<span class="punctuation">,</span></span><br><span class="line">             metadata <span class="operator">=</span> meta<span class="punctuation">,</span> design <span class="operator">=</span> design_formula<span class="punctuation">,</span></span><br><span class="line">             contrast <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">,</span> <span class="string">&quot;control&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">             input <span class="operator">=</span> <span class="string">&quot;Public airway teaching dataset&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">         file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;airway_bundle.rds&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">capture.output<span class="punctuation">(</span>sessionInfo<span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> file <span class="operator">=</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;sessionInfo.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">capture.output<span class="punctuation">(</span>resultsNames<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">,</span> file <span class="operator">=</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;coefficients.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">message<span class="punctuation">(</span><span class="string">&quot;Teaching outputs saved to: &quot;</span><span class="punctuation">,</span> normalizePath<span class="punctuation">(</span>out<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">message<span class="punctuation">(</span><span class="string">&quot;Keep the printed bundle directory path for lesson 3.&quot;</span><span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p>这里的预过滤规则是在最小条件组样本数那么多的样本中，计数至少为 10。它是为了教学声明的一种规则，不是所有物种、测序深度和实验的唯一正确选择，也不是先看对比结果再把不喜欢的基因删掉。保存的原始计数没有因过滤而消失，结果表则对应进入模型的基因。</p><p>运行结束后，结果目录保留在你启动 Rscript 时的当前学习工作目录里。保存打印出的完整目录路径，其中的 <code>airway_bundle.rds</code> 将作为下一篇的输入；不要移动或重命名后还使用旧路径。这里 <code>tempfile()</code> 只是生成一个未被占用的目录名称，实际位置明确指定为 <code>getwd()</code>，并非 R 会话自动清理的临时目录。</p><h2 id="4-如何读一行结果：效应与证据要分开"><a href="#4-如何读一行结果：效应与证据要分开" class="headerlink" title="4. 如何读一行结果：效应与证据要分开"></a>4. 如何读一行结果：效应与证据要分开</h2><p><code>baseMean</code> 描述归一化计数的平均水平；<code>log2FoldChange</code> 是该对比的效应估计；<code>lfcSE</code> 表达其标准误；<code>stat</code>、<code>pvalue</code> 和 <code>padj</code> 是相应统计检验与多重比较处理的产物。这里的方向固定为 treated 相对 control，正值表示模型下处理组更高，负值表示更低。将分子分母互换，效应符号随之改变，所以导出的表必须附有对比说明。<a href="https://www.bioconductor.org/packages/release/bioc/manuals/DESeq2/man/DESeq2.pdf">DESeq2 结果字段手册</a></p><p>如果一个基因估计 log2FC 为 1，表示该模型中的相对倍数约为 2；这只是算术含义，不意味着这个基因一定显著，更不意味着它是整个表里最重要的基因。另一个基因可以有很小的效应，却因为信息更充分而得到较小 p 值。解释时应同时看效应、精度、表达水平和研究问题，而不是把单个数字当作综合评分。</p><p><code>padj &lt; 0.05</code> 是在特定模型、数据和假设下控制多重检验错误的一种判定规则，不是“这个基因有 95% 的概率是真的”，也不是任意一个选中基因有恰好 5% 假阳性概率。阈值之外的基因也不自动等于无变化。BH 等多重比较方法的定义可参阅 <a href="https://stat.ethz.ch/R-manual/R-devel/library/stats/html/p.adjust.html">R 官方 p.adjust 文档</a>。</p><h2 id="5-shrink、筛选与效应阈值检验不是同一件事"><a href="#5-shrink、筛选与效应阈值检验不是同一件事" class="headerlink" title="5. shrink、筛选与效应阈值检验不是同一件事"></a>5. shrink、筛选与效应阈值检验不是同一件事</h2><p>当一个基因信息少或变异较大时，未经收缩的倍数估计可能很极端。<code>lfcShrink</code> 提供更稳定的效应估计，常用于可视化和效应排序。这不等于重新定义了本篇普通 Wald 检验的原假设，也不是把所有小效应变成“没有生物学意义”。本练习明确保留原检验的 <code>pvalue/padj</code>，另列 <code>log2FC_shrunk</code>，避免读者不知道某一列来自哪一步。收缩的模型依据可阅读 <a href="https://link.springer.com/article/10.1186/s13059-014-0550-8">DESeq2 原始方法论文</a>。</p><p>“普通检验 padj 小于阈值，并且收缩后绝对 log2FC 大于 1”可以作为声明过的候选展示规则，却不等价于已经检验“真实绝对效应大于 1”。若问题确实是超过一个预先指定的最小效应，应使用相应的效应阈值检验，例如：</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在完整练习后继续运行：检验 |log2FC| 是否超过预定阈值1。</span></span><br><span class="line"><span class="comment"># 它产生新的检验结果，不能混称为上面的普通差异检验。</span></span><br><span class="line">res_min_effect <span class="operator">&lt;-</span> results<span class="punctuation">(</span></span><br><span class="line">  dds<span class="punctuation">,</span></span><br><span class="line">  contrast <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">,</span> <span class="string">&quot;control&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">  lfcThreshold <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  altHypothesis <span class="operator">=</span> <span class="string">&quot;greaterAbs&quot;</span><span class="punctuation">,</span></span><br><span class="line">  alpha <span class="operator">=</span> <span class="number">0.05</span></span><br><span class="line"><span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p>默认 <code>DESeq()</code> 足够用于这个常规 bulk 学习入口。不要看到某篇单细胞性能教程，就盲目切换到 <code>glmGamPoi</code> 或同时改多项参数。任何拟合方式、离群值处理和阈值变更，都应说明对应的数据特征与验证依据，而不只是追求运行更快或基因更多。</p><h2 id="6-Salmon-输入的替代入口：不把-TPM-当成计数"><a href="#6-Salmon-输入的替代入口：不把-TPM-当成计数" class="headerlink" title="6. Salmon 输入的替代入口：不把 TPM 当成计数"></a>6. Salmon 输入的替代入口：不把 TPM 当成计数</h2><p>如果上一篇实际采用 Salmon 定量，建议从每样本 <code>quant.sf</code> 导入，而不是手动抽出 TPM 交给 <code>DESeqDataSetFromMatrix()</code>。下面是有真实文件之后的替代入口模板，不需要和 airway 练习同时运行。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># metadata.tsv: first column sample_id; other columns include condition.</span></span><br><span class="line"><span class="comment"># tx2gene.tsv: two named columns, transcript_id and gene_id, from the same reference.</span></span><br><span class="line">library<span class="punctuation">(</span>tximport<span class="punctuation">)</span></span><br><span class="line">library<span class="punctuation">(</span>DESeq2<span class="punctuation">)</span></span><br><span class="line">meta <span class="operator">&lt;-</span> read.delim<span class="punctuation">(</span><span class="string">&quot;metadata.tsv&quot;</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span> check.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta<span class="operator">$</span>condition <span class="operator">&lt;-</span> factor<span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">,</span> levels <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;control&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="operator">!</span><span class="built_in">anyNA</span><span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">files <span class="operator">&lt;-</span> setNames<span class="punctuation">(</span>file.path<span class="punctuation">(</span><span class="string">&quot;salmon&quot;</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">,</span> <span class="string">&quot;quant.sf&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">all</span><span class="punctuation">(</span>file.exists<span class="punctuation">(</span>files<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">tx2gene <span class="operator">&lt;-</span> read.delim<span class="punctuation">(</span><span class="string">&quot;tx2gene.tsv&quot;</span><span class="punctuation">,</span> check.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>identical<span class="punctuation">(</span><span class="built_in">names</span><span class="punctuation">(</span>tx2gene<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;transcript_id&quot;</span><span class="punctuation">,</span> <span class="string">&quot;gene_id&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span><span class="built_in">anyNA</span><span class="punctuation">(</span>tx2gene<span class="punctuation">)</span><span class="punctuation">,</span> <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>tx2gene<span class="operator">$</span>transcript_id<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">txi <span class="operator">&lt;-</span> tximport<span class="punctuation">(</span>files<span class="punctuation">,</span> type <span class="operator">=</span> <span class="string">&quot;salmon&quot;</span><span class="punctuation">,</span> tx2gene <span class="operator">=</span> tx2gene<span class="punctuation">,</span></span><br><span class="line">                 countsFromAbundance <span class="operator">=</span> <span class="string">&quot;no&quot;</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>identical<span class="punctuation">(</span>colnames<span class="punctuation">(</span>txi<span class="operator">$</span>counts<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">dds <span class="operator">&lt;-</span> DESeqDataSetFromTximport<span class="punctuation">(</span>txi<span class="punctuation">,</span> colData <span class="operator">=</span> meta<span class="punctuation">,</span> design <span class="operator">=</span> <span class="operator">~</span> condition<span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p>这条路线通过完整对象向 DESeq2 传递估计计数与长度相关信息；不要又手动对矩阵做一次长度缩放，造成重复校正。<code>countsFromAbundance</code> 的不同选项有不同下游约定，本文只展示 <code>no</code> 加专门构造器的一条路线。转录本映射缺失、版本号不匹配、一个转录本映到多个基因等必须调查并记录；模板的结构校验不能替代参考来源审计。<a href="https://bioconductor.org/packages/release/bioc/vignettes/tximport/inst/doc/tximport.html">tximport 官方方法说明</a></p><p><code>~ condition</code> 仅用于已经确认的简单独立设计。真实配对或批次实验应携带相应字段、检查设计并替换公式，不能因为换了导入入口就忘了配对关系。3′ 标签型 RNA-seq 的定量和长度处理也可能不同，应查相应协议，而不是把本模板视为所有 RNA 测序的标准答案。</p><h2 id="7-看到-NA-和报错，先判断原因"><a href="#7-看到-NA-和报错，先判断原因" class="headerlink" title="7. 看到 NA 和报错，先判断原因"></a>7. 看到 NA 和报错，先判断原因</h2><p>结果出现 NA，不应直接全部改为 1 或删除。全零、异常计数或独立过滤可能造成不同字段不可用，含义不一样。需要分别记录是否拥有检验 p 值和调整后 p 值，再按定义决定候选集或富集背景。报出非满秩时，优先检查完全混杂、没有样本的因子水平和冗余协变量，而不是不断删变量直到成功。</p><p>如果两组没有明确分开，也不要立即重做模型。样本差异可能主要由来源、细胞组成、时间或其他真实因素驱动。先回到原始 QC 与元数据，再解释探索图。计数模型加入 batch 与在展示图上去除 batch 是不同的事情；后一种视觉处理不能创造前一种设计里缺失的信息。</p><h2 id="8-本篇的自检与交付"><a href="#8-本篇的自检与交付" class="headerlink" title="8. 本篇的自检与交付"></a>8. 本篇的自检与交付</h2><p>在教学副本中，读入元数据后、按 ID 对齐之前插入 <code>meta &lt;- meta[rev(rownames(meta)), , drop = FALSE]</code>。保留随后按 ID 对齐的那行时，顺序检查应通过，因为程序已经正确恢复匹配；再注释掉 <code>meta &lt;- meta[colnames(cts), , drop = FALSE]</code>，顺序检查才应阻止继续。这是在验证对齐逻辑，不是在修改真实分组。把 <code>condition</code> 的参考水平反过来，检查 <code>resultsNames()</code> 与你指定的对比方向有何区别。构造全部对照在 B1、处理在 B2 的元数据，检查设计矩阵秩，练习解释为什么这是设计问题。</p><p>正式交付至少应有输入来源、过滤规则、模型公式、参考水平、对比清单、完整结果表、NA 的处理说明、实际会话版本与 QC 记录。保留整张表，而不只保留显著基因；未来改变阈值或重查某个候选时，才不必依赖截图猜测。</p><p>上一章：<a href="/posts/rna2601/">实验设计、FASTQ 与计数</a>；下一章：<a href="/posts/rna2603/">QC 图、功能富集与结果解释</a>。</p><h2 id="官方与原始方法参考"><a href="#官方与原始方法参考" class="headerlink" title="官方与原始方法参考"></a>官方与原始方法参考</h2><ul><li><a href="https://bioconductor.org/packages/release/data/experiment/vignettes/airway/inst/doc/airway.html">airway：公开教学数据与实验背景</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html">DESeq2：官方分析教程</a></li><li><a href="https://www.bioconductor.org/packages/release/bioc/manuals/DESeq2/man/DESeq2.pdf">DESeq2：函数手册</a></li><li><a href="https://link.springer.com/article/10.1186/s13059-014-0550-8">Love、Huber 与 Anders，2014：DESeq2 原始论文</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/tximport/inst/doc/tximport.html">tximport：导入与长度处理</a></li><li><a href="https://stat.ethz.ch/R-manual/R-devel/library/stats/html/p.adjust.html">R：多重比较调整</a></li><li><a href="https://www.bioconductor.org/install/">Bioconductor：安装与版本兼容</a></li></ul>]]></content>
    
    
    <summary type="html">用官方 airway 公开计数数据练习配对 DESeq2 分析，检查样本匹配与设计矩阵，区分统计检验、效应量、FDR 和 log2FC 收缩。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="生物信息学" scheme="https://perry.apay.eu.cc/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
    
    <category term="实验设计" scheme="https://perry.apay.eu.cc/tags/%E5%AE%9E%E9%AA%8C%E8%AE%BE%E8%AE%A1/"/>
    
    <category term="RNA-seq" scheme="https://perry.apay.eu.cc/tags/RNA-seq/"/>
    
    <category term="DESeq2" scheme="https://perry.apay.eu.cc/tags/DESeq2/"/>
    
    <category term="tximport" scheme="https://perry.apay.eu.cc/tags/tximport/"/>
    
  </entry>
  
  <entry>
    <title>Bulk RNA-seq 分析入门（一）：从实验设计、FASTQ 到可信的计数表</title>
    <link href="https://perry.apay.eu.cc/posts/rna2601/"/>
    <id>https://perry.apay.eu.cc/posts/rna2601/</id>
    <published>2026-10-06T09:30:01.000Z</published>
    <updated>2026-10-06T09:30:01.000Z</updated>
    
    <content type="html"><![CDATA[<p>RNA-seq 分析的起点不是某条比对命令，而是一句话：我想比较什么样的生物学对象，比较能够排除哪些其他解释？先把这句话说清楚，后面的文件、模型与图才会围绕同一个问题组织起来。</p><p>这是三篇连续教程的第一篇，讨论常规 <strong>bulk RNA-seq</strong>：每个文库代表一个组织、细胞群体或其他整体样本。它不是单细胞教程，不能直接套到带有细胞条形码和 UMI 的单细胞矩阵上。本文中的六样本设计是原创的假设练习；命令只是准备好真实数据之后的执行模板，写作过程中没有在本机下载 FASTQ、安装软件或运行测序流程。</p><p>读完这一篇，应该能交付样本表、参考文件清单、处理参数、质控记录，以及来源明确的计数或定量结果。下一篇再用官方公开的小数据集练习差异表达，而不是让入门电脑先承担完整人类基因组比对。</p><h2 id="1-先画出实验单位，而不是先数文件"><a href="#1-先画出实验单位，而不是先数文件" class="headerlink" title="1. 先画出实验单位，而不是先数文件"></a>1. 先画出实验单位，而不是先数文件</h2><p>假设我们研究某种培养细胞在处理前后是否出现转录变化。三个独立培养来源进入对照组，另三个独立培养来源进入处理组，这里共有六个生物学重复。若同一个培养来源被分装后重复建库，重复的文库提供的是技术层面的信息，不能自动变成多个独立生物学重复。测了两个 lane、得到 R1 和 R2、把一个样本重复测序，也都不会把生物学样本数量翻倍。</p><p>我会先在纸上给每个样本写四项：它来自谁、接受什么处理、在哪个批次建库、与其他样本共享什么来源。这样可以提早发现两个危险情况：两组其实各只有一个独立来源；或者所有对照都在第一批处理、所有处理都在第二批处理。第一种缺少组内变异信息，第二种把处理与批次完全绑在一起。再复杂的统计软件也不能凭空补出缺失的实验信息。</p><p>“每组至少三个”可以作为入门设计练习的起点，却不是通用功效保证。正式实验应根据预期效应、变异、研究目标和资源做功效评估，并尽量在处理、提取、建库与测序环节平衡或随机化分组。技术重复的处理方式以及配对设计也要提前记录，不应看到结果以后才决定。关于实验单位与伪重复，可参阅 <a href="https://journals.plos.org/plosbiology/article?id=10.1371/journal.pbio.2005282">Lazic 等人的原始方法文章</a>。</p><h2 id="2-一张表给流水线，一张表给科学问题"><a href="#2-一张表给流水线，一张表给科学问题" class="headerlink" title="2. 一张表给流水线，一张表给科学问题"></a>2. 一张表给流水线，一张表给科学问题</h2><p>流水线样本表负责告诉程序文件在哪里；生物学元数据负责告诉分析者这些文件代表什么。两张表通过稳定的 <code>sample_id</code> 连接，不能靠“应该是这个顺序”连接。</p><p>下面是六个假设样本的元数据。<code>subject_id</code> 在这里表示独立来源，不是患者姓名；所有字段都只是教学标签。B1 与 B2 都有两个条件，但分配并不完全均衡，正式设计还可以进一步改善。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sample_id,subject_id,condition,batch</span><br><span class="line">C1,demo_01,control,B1</span><br><span class="line">C2,demo_02,control,B2</span><br><span class="line">C3,demo_03,control,B1</span><br><span class="line">T1,demo_04,treated,B1</span><br><span class="line">T2,demo_05,treated,B2</span><br><span class="line">T3,demo_06,treated,B1</span><br></pre></td></tr></table></figure><p>本系列固定参考 <code>nf-core/rnaseq 3.27.0</code> 文档，不用“最新版”代替版本记录。该版本 FASTQ 样本表必需的四列是 <code>sample,fastq_1,fastq_2,strandedness</code>。双端填写 R1&#x2F;R2，单端的 <code>fastq_2</code> 留空。同一样本多个 lane 可以重复使用同一个 <code>sample</code> 标识，但不同生物学样本不能因此被意外合并。未知链特异性可设 <code>auto</code>，并在输出后复查推断与协议是否一致。<a href="https://nf-co.re/rnaseq/3.27.0/docs/usage#samplesheet-input">3.27.0 官方样本表说明</a></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sample,fastq_1,fastq_2,strandedness</span><br><span class="line">C1,/data/fastq/C1_R1.fastq.gz,/data/fastq/C1_R2.fastq.gz,auto</span><br><span class="line">C2,/data/fastq/C2_R1.fastq.gz,/data/fastq/C2_R2.fastq.gz,auto</span><br><span class="line">C3,/data/fastq/C3_R1.fastq.gz,/data/fastq/C3_R2.fastq.gz,auto</span><br><span class="line">T1,/data/fastq/T1_R1.fastq.gz,/data/fastq/T1_R2.fastq.gz,auto</span><br><span class="line">T2,/data/fastq/T2_R1.fastq.gz,/data/fastq/T2_R2.fastq.gz,auto</span><br><span class="line">T3,/data/fastq/T3_R1.fastq.gz,/data/fastq/T3_R2.fastq.gz,auto</span><br></pre></td></tr></table></figure><p><code>/data/fastq/</code> 是需要自行替换的路径，博客并未提供这些 FASTQ。真实元数据还应携带组织、采样时间、建库方式、poly(A) 富集或 rRNA 去除、读长、性别等与研究有关的字段。只填写实际已知的信息，不要用猜测补空格。涉及人类数据时，先确认数据使用范围；未经允许不要把 FASTQ、真实身份映射或临床字段上传到云服务及公开仓库。</p><h2 id="3-完整小练习：仅检查设计，不处理测序数据"><a href="#3-完整小练习：仅检查设计，不处理测序数据" class="headerlink" title="3. 完整小练习：仅检查设计，不处理测序数据"></a>3. 完整小练习：仅检查设计，不处理测序数据</h2><p>下面的 Python 标准库代码可以单独保存为 <code>check_rnaseq_design.py</code>，使用 <code>python check_rnaseq_design.py</code> 执行。它只读取代码里的教学 CSV，不访问磁盘中的实验数据、不安装包、不生成差异基因。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"><span class="keyword">import</span> io</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> Counter, defaultdict</span><br><span class="line"></span><br><span class="line">text = <span class="string">&quot;&quot;&quot;sample_id,subject_id,condition,batch</span></span><br><span class="line"><span class="string">C1,demo_01,control,B1</span></span><br><span class="line"><span class="string">C2,demo_02,control,B2</span></span><br><span class="line"><span class="string">C3,demo_03,control,B1</span></span><br><span class="line"><span class="string">T1,demo_04,treated,B1</span></span><br><span class="line"><span class="string">T2,demo_05,treated,B2</span></span><br><span class="line"><span class="string">T3,demo_06,treated,B1</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line">rows = <span class="built_in">list</span>(csv.DictReader(io.StringIO(text)))</span><br><span class="line">required = &#123;<span class="string">&quot;sample_id&quot;</span>, <span class="string">&quot;subject_id&quot;</span>, <span class="string">&quot;condition&quot;</span>, <span class="string">&quot;batch&quot;</span>&#125;</span><br><span class="line"><span class="keyword">if</span> <span class="keyword">not</span> rows <span class="keyword">or</span> <span class="keyword">not</span> required.issubset(rows[<span class="number">0</span>]):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Missing metadata fields&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">any</span>(<span class="keyword">not</span> row[field].strip() <span class="keyword">for</span> row <span class="keyword">in</span> rows <span class="keyword">for</span> field <span class="keyword">in</span> required):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Blank metadata field&quot;</span>)</span><br><span class="line">ids = [row[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> row <span class="keyword">in</span> rows]</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(ids) != <span class="built_in">len</span>(<span class="built_in">set</span>(ids)):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Duplicate sample_id&quot;</span>)</span><br><span class="line">groups = defaultdict(<span class="built_in">set</span>)</span><br><span class="line">batches = defaultdict(<span class="built_in">set</span>)</span><br><span class="line"><span class="keyword">for</span> row <span class="keyword">in</span> rows:</span><br><span class="line">    <span class="keyword">if</span> row[<span class="string">&quot;condition&quot;</span>] <span class="keyword">not</span> <span class="keyword">in</span> &#123;<span class="string">&quot;control&quot;</span>, <span class="string">&quot;treated&quot;</span>&#125;:</span><br><span class="line">        <span class="keyword">raise</span> ValueError(<span class="string">&quot;Unexpected condition&quot;</span>)</span><br><span class="line">    groups[row[<span class="string">&quot;condition&quot;</span>]].add(row[<span class="string">&quot;subject_id&quot;</span>])</span><br><span class="line">    batches[row[<span class="string">&quot;batch&quot;</span>]].add(row[<span class="string">&quot;condition&quot;</span>])</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">set</span>(groups) != &#123;<span class="string">&quot;control&quot;</span>, <span class="string">&quot;treated&quot;</span>&#125;:</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Both conditions are required&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">any</span>(<span class="built_in">len</span>(subjects) &lt; <span class="number">3</span> <span class="keyword">for</span> subjects <span class="keyword">in</span> groups.values()):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;This teaching design requires 3 independent sources per group&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">any</span>(n &gt; <span class="number">1</span> <span class="keyword">for</span> n <span class="keyword">in</span> Counter(row[<span class="string">&quot;subject_id&quot;</span>] <span class="keyword">for</span> row <span class="keyword">in</span> rows).values()):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Repeated source: review pairing or technical replication&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">all</span>(<span class="built_in">len</span>(conditions) == <span class="number">1</span> <span class="keyword">for</span> conditions <span class="keyword">in</span> batches.values()):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Batch and condition may be fully confounded; review design&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Teaching design check passed&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Independent sources:&quot;</span>, &#123;k: <span class="built_in">len</span>(v) <span class="keyword">for</span> k, v <span class="keyword">in</span> groups.items()&#125;)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Conditions represented in each batch:&quot;</span>, <span class="built_in">dict</span>(batches))</span><br></pre></td></tr></table></figure><p>这段检查故意只接受“每个来源仅出现一次”的教学非配对设计。真实配对数据会被它要求人工复查，而不是被直接判定为坏数据。批次检查也是保守提示，不代替正式设计矩阵的秩检查。第二篇会展示配对建模与完整秩检查。</p><p>可以做三个自检：把 T3 的来源改成 T2 的来源；把全部 C 样本放到 B1、全部 T 样本放到 B2；把 C2 的 ID 改成 C1。观察程序在哪一步停止，并用自己的话解释停止原因。这个练习的价值是让“样本表错误”在昂贵的运算开始前被发现。</p><h2 id="4-参考基因组与注释必须是一套可追溯的东西"><a href="#4-参考基因组与注释必须是一套可追溯的东西" class="headerlink" title="4. 参考基因组与注释必须是一套可追溯的东西"></a>4. 参考基因组与注释必须是一套可追溯的东西</h2><p>FASTA 告诉比对器参考序列是什么，GTF 告诉计数或转录本构建过程哪些区间属于哪些基因和转录本。二者不能只因为都写着“human”就视为匹配。需要核对物种、组装版本、注释发布版本、染色体命名、是否包含替代 contig，以及转录本 ID 是否与定量索引一致。</p><p>一个经常被忽略的错误是：比对使用带 <code>chr</code> 的 FASTA，注释却使用不带 <code>chr</code> 的 contig；另一个是用某个发布版本的转录组建索引，却拿另一个版本的转录本到基因对应表做汇总。程序有时可以继续运行，但有些读段或转录本已经悄悄失去了正确对应关系。不要在不理解来源的情况下统一删掉 ID 后缀，或者将缺失映射全部填成同名基因。</p><p>我建议保存一份参考清单，列出来源网址、下载日期、组装版本、注释版本、文件校验值、索引构建命令及工具版本。后面换参考文件时，把它当成一次新的分析决定，而不只是“更新了文件”。相关参数可在 <a href="https://nf-co.re/rnaseq/3.27.0/parameters#reference-genome-options">nf-core&#x2F;rnaseq 3.27.0 参数页</a>逐项核对。</p><h2 id="5-从-FASTQ-到计数，各步骤究竟改变了什么"><a href="#5-从-FASTQ-到计数，各步骤究竟改变了什么" class="headerlink" title="5. 从 FASTQ 到计数，各步骤究竟改变了什么"></a>5. 从 FASTQ 到计数，各步骤究竟改变了什么</h2><p>可以把流程记成一条有审计点的链：原始 FASTQ → 原始读段检查 → 必要的接头处理 → 比对或转录本定量 → 样本汇总 QC → 基因级输入检查。每个箭头都应该能回答“输入是什么、参数是什么、输出在哪里、为什么允许进入下一步”。</p><p>STAR 基因组比对保留读段的基因组位置，方便检查剪接、比对分布和其他 BAM 层面的指标；Salmon 估计转录本丰度，再依据转录本到基因的映射汇总。它们不是“哪个名字更高级”的关系，选择要对应目标、资源和实验协议。普通初学者可以先使用固定的标准工作流，掌握每个产物，再决定是否需要改变流程。<a href="https://nf-co.re/rnaseq/3.27.0/docs/usage#alignment-options">nf-core 的比对和定量选项</a></p><p>下面模板选择 <code>star_salmon</code>。这不是对本机资源的可运行承诺。执行前需要可用的 Nextflow、与该版本要求匹配的 Java、Docker 或经管理员批准的容器运行环境、足够的 CPU&#x2F;内存与磁盘，并确认镜像和参考下载权限。人类全基因组 STAR 工作不能按“普通小脚本”估计内存；具体资源应按参考、索引与集群政策规划。Nextflow 安装与运行前提以 <a href="https://docs.seqera.io/nextflow/install">官方说明</a>为准。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Linux/HPC 执行模板：先替换路径并完成环境、权限和资源检查。</span></span><br><span class="line"><span class="comment"># 会读取真实FASTQ并产生大量文件；不是本篇已执行的练习。</span></span><br><span class="line">nextflow run nf-core/rnaseq \</span><br><span class="line">  -r 3.27.0 \</span><br><span class="line">  -profile docker \</span><br><span class="line">  --input /data/project/metadata/samplesheet.csv \</span><br><span class="line">  --fasta /data/reference/genome.fa \</span><br><span class="line">  --gtf /data/reference/annotation.gtf \</span><br><span class="line">  --aligner star_salmon \</span><br><span class="line">  --outdir /data/project/results/rnaseq_v1 \</span><br><span class="line">  -work-dir /data/project/work/rnaseq_v1 \</span><br><span class="line">  -with-report /data/project/nextflow_report.html \</span><br><span class="line">  -with-trace /data/project/nextflow_trace.tsv \</span><br><span class="line">  -with-timeline /data/project/nextflow_timeline.html</span><br></pre></td></tr></table></figure><p>首次运行应使用新的结果目录，记录完整命令与退出状态。在 HPC 上不要在登录节点启动大任务；采用站点批准的执行器和资源配置。<code>-resume</code> 是恢复符合缓存条件的任务，不是自动确认旧参考、旧参数与新实验相容。正式分析还应记录实际 Nextflow、容器版本和镜像标识；只有 <code>-r</code> 固定了流程版本，其他外部配置也需要追踪。</p><h2 id="6-QC-不是追求所有按钮都变绿"><a href="#6-QC-不是追求所有按钮都变绿" class="headerlink" title="6. QC 不是追求所有按钮都变绿"></a>6. QC 不是追求所有按钮都变绿</h2><p>第一层看原始读段：数量、长度、质量下降位置、接头、过度代表序列。第二层看处理后信息：比对率、唯一&#x2F;多重比对、rRNA 或其他污染、链特异性、插入片段、基因体覆盖与定量分配。第三层看样本之间：同一实验的样本是否有异常深度、异常分布，是否与建库记录一致。</p><p>RNA 读段来自不均匀表达的转录本，重复序列的解释不能机械照搬均匀覆盖的 DNA 测序。接头、低复杂度、过度扩增等也需要结合协议与其他证据区分，不要因一个警告就随意删掉全部重复读段。FastQC 提供的是待解释的线索，而不是替分析者完成决定。<a href="https://www.bioinformatics.babraham.ac.uk/projects/fastqc/">FastQC 官方概览</a>、<a href="https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/3%20Analysis%20Modules/9%20Overrepresented%20Sequences.html">官方过度代表序列说明</a></p><p>MultiQC 把多个工具的报告集中展示，方便发现某个样本与同批其他样本不一致；它并不会替代实验协议、阈值依据和异常原因调查。<a href="https://docs.seqera.io/multiqc/">MultiQC 官方说明</a></p><p>不要在这里给自己设置一个跨物种、跨协议的万能比对率阈值。应预先约定可接受范围和调查流程：发现问题后先查文件配对、参考、链特异性与污染，再结合原始记录判断是否重做。确有失败文库可以排除，但理由应与预先约定的 QC 依据相符，不能只因为排除后分组更好看。</p><h2 id="7-什么可以交给-DESeq2，什么不可以"><a href="#7-什么可以交给-DESeq2，什么不可以" class="headerlink" title="7. 什么可以交给 DESeq2，什么不可以"></a>7. 什么可以交给 DESeq2，什么不可以</h2><p>基因整数计数、Salmon 的估计计数、TPM、VST 是不同对象。整数计数可以通过计数矩阵入口进入 DESeq2；Salmon 转录本估计通常用 <code>tximport</code> 或 <code>tximeta</code> 的专门入口，保留长度相关信息。TPM、FPKM、对数表达或 VST 值不能直接冒充原始计数。把 TPM 四舍五入也不会把它变回测序计数。<a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html#input-data">DESeq2 输入说明</a></p><p>查看 Salmon 结果时，至少保留原始每样本 <code>quant.sf</code>、定量索引来源与 <code>tx2gene</code> 映射。计数矩阵必须附带生成方法；不能从几个名为 <code>counts</code> 的文件里凭名字选一个。nf-core 的输出包含原始估计计数、TPM、长度和不同缩放产物，选择下游入口时需要查看准确含义，而不是把所有列统称为“表达量”。<a href="https://nf-co.re/rnaseq/3.27.0/docs/output">3.27.0 输出文件说明</a></p><p>最终的交接包应含样本元数据、参考清单、原始计数或完整定量文件、流程与软件版本、QC 报告、异常处理记录，并明确哪些样本允许进入差异分析。若生物重复不足、关键标签存疑或处理与批次完全混杂，正确结果可能是“暂不进行差异检验”，不是强行产出火山图。</p><h2 id="8-本篇结束时的自查清单"><a href="#8-本篇结束时的自查清单" class="headerlink" title="8. 本篇结束时的自查清单"></a>8. 本篇结束时的自查清单</h2><ul><li>我能说清实验单位，并区分生物学重复、技术重复、lane 和双端读段。</li><li>两张样本表使用相同稳定 ID，参考、索引和注释有精确来源。</li><li>链特异性有协议依据或经过推断复核，而不是从文件名猜测。</li><li>我知道每个输出是计数、估计丰度还是转换值，没有将 TPM 交给计数模型。</li><li>每次排除与重跑都有原因、版本和日志，可以追溯到原始输入。</li></ul><p>下一篇：<a href="/posts/rna2602/">DESeq2 设计公式、对比与可复查的差异表达练习</a>。</p><h2 id="参考与继续阅读"><a href="#参考与继续阅读" class="headerlink" title="参考与继续阅读"></a>参考与继续阅读</h2><ul><li><a href="https://nf-co.re/rnaseq/3.27.0/docs/usage">nf-core&#x2F;rnaseq 3.27.0：用法</a></li><li><a href="https://nf-co.re/rnaseq/3.27.0/parameters">nf-core&#x2F;rnaseq 3.27.0：参数</a></li><li><a href="https://nf-co.re/rnaseq/3.27.0/docs/output">nf-core&#x2F;rnaseq 3.27.0：输出</a></li><li><a href="https://docs.seqera.io/nextflow/install">Nextflow：安装与运行前提</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html">DESeq2：官方分析教程</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/tximport/inst/doc/tximport.html">tximport：转录本定量导入</a></li></ul>]]></content>
    
    
    <summary type="html">先弄清实验单位、重复和批次，再准备样本表与匹配的参考文件，用固定版本的 nf-core/rnaseq 理解 FASTQ 到基因计数的完整流程。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="生物信息学" scheme="https://perry.apay.eu.cc/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
    
    <category term="质量控制" scheme="https://perry.apay.eu.cc/tags/%E8%B4%A8%E9%87%8F%E6%8E%A7%E5%88%B6/"/>
    
    <category term="RNA-seq" scheme="https://perry.apay.eu.cc/tags/RNA-seq/"/>
    
    <category term="样本元数据" scheme="https://perry.apay.eu.cc/tags/%E6%A0%B7%E6%9C%AC%E5%85%83%E6%95%B0%E6%8D%AE/"/>
    
    <category term="nf-core" scheme="https://perry.apay.eu.cc/tags/nf-core/"/>
    
  </entry>
  
  <entry>
    <title>ATAC-seq 入门（三）：从统一区域计数到差异可及性</title>
    <link href="https://perry.apay.eu.cc/posts/atac2603/"/>
    <id>https://perry.apay.eu.cc/posts/atac2603/</id>
    <published>2026-10-06T09:20:03.000Z</published>
    <updated>2026-10-06T09:20:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一章得到峰集合，并不意味着差异分析已经完成。“对照没有叫到峰，处理叫到了峰”是峰识别结果的比较；“在考虑深度、批次与重复变异后，这个区域的信号是否有统计支持的变化”才是差异可及性的问题。前者可能由测序深度或阈值差异造成，不能代替后者。</p><p>本文继续讨论 <strong>bulk paired-end ATAC-seq</strong>，沿用两组各三个独立生物学重复、非配对的学习设计。唯一可以直接运行的小练习使用人工数据，只检查结构和计数约定；真实 BAM 计数及 DESeq2 部分是资料准备完成后的模板，本文没有运行真实测序分析，不提供杜撰的差异区域数量、p 值或图。</p><span id="more"></span><h2 id="一、先过-QC-门，再问差异"><a href="#一、先过-QC-门，再问差异" class="headerlink" title="一、先过 QC 门，再问差异"></a>一、先过 QC 门，再问差异</h2><p>统计软件不会因为样本被错标、两组使用不同过滤标准而自动阻止分析。开始之前，要回看比对、重复率、线粒体比例、片段长度结构、TSS 富集、FRiP 与重复一致性，并确认每项指标使用的输入和单位。阈值需要结合协议、组织与实验背景；不能把一个固定数当作所有文库的通行证。</p><p>QC 失败、身份存疑或有效生物学重复不足时，先调查，必要时只保留描述性结果。不要为了让 PCA 分组漂亮而删样本；排除应有独立的技术证据、记录，并重新检查排除后的设计。把同一个样本的 lane、R1&#x2F;R2 或 pooled BAM 当作新增重复，不能增加独立实验单位。</p><p>本例的三个重复是教学约定，不是充分功效保证。真实设计还要根据变异、目标效应和研究目的考虑样本量；只有一个来源每组一次时，不应套模板产生看似正式的显著性结论。<a href="https://bioconductor.org/packages/release/bioc/vignettes/DiffBind/inst/doc/DiffBind.pdf">DiffBind 官方教程</a>将计数、质量检查、设计与归一化分开讨论，也适用于理解 ATAC 数据的这些边界。</p><h2 id="二、每个样本必须站在同一张区域地图上"><a href="#二、每个样本必须站在同一张区域地图上" class="headerlink" title="二、每个样本必须站在同一张区域地图上"></a>二、每个样本必须站在同一张区域地图上</h2><p>比较矩阵的行应来自一个固定的候选区域集合，列对应所有保留的独立样本。先根据明确规则建立 consensus regions，再在这些相同坐标上对所有样本计数。某个样本没有在该处叫到峰，也要在同一区域读取它的计数，而不是将该格直接填成零或缺失。</p><p>沿用 <code>nf-core/atacseq 2.1.2</code> 的 Bowtie2、narrow peaks 学习路线，可在 <code>bowtie2/merged_library/macs2/narrow_peak/consensus/</code> 查看统一区域及相关计数材料。但这里的流程输出是起点，不是对所有实验设计的完整统计承诺。<a href="https://nf-co.re/atacseq/2.1.2/docs/output">固定版本输出说明</a></p><p>要保存候选集合的生成规则、原始峰来源、重复支持要求和最终 BED 校验值。合并区间会改变宽度和计数机会；改成固定宽度的 summit 窗口则是另一个分析定义，需要重新计数并记录。不能让处理组用自己的峰宽、对照组用另一套峰宽，再把两个表按最近位置拼起来。</p><p>参考 FASTA、BAM header、BED、blacklist 和后续注释必须属于同一组装并使用一致的 contig 名称。<code>chr1</code> 与 <code>1</code> 不匹配不是小排版问题。BED 的零起点半开区间与 SAF 的坐标约定也不能混用；转换时保留 region ID，并用已知小区间验证边界。blacklist、线粒体与低质量比对过滤应采用同一规则，记录删除了什么；不能只在某一组移除可疑区域。</p><h2 id="三、一条双端片段不能被-R1-和-R2-算成两次"><a href="#三、一条双端片段不能被-R1-和-R2-算成两次" class="headerlink" title="三、一条双端片段不能被 R1 和 R2 算成两次"></a>三、一条双端片段不能被 R1 和 R2 算成两次</h2><p>本章选择片段级计数：同一有效 read pair 在一个区域内贡献一次，不将两端各算一个观察。不要将切点数、覆盖碱基总和、CPM、bigWig 信号或峰的富集分数混称为原始片段计数。沿用上一章的片段主线，不再额外给 BAM 叠加 Tn5 shift；另做切点或 footprint 分析时使用单独派生文件及明确校正记录。</p><p>“计数为整数”只是必要检查，不能证明计数单位正确。一个长片段可能命中多个区域；应在分析前决定是排除歧义分配、唯一分配，还是采用明确记录的多区域策略。本章的严格教学约定是：排除歧义分配，保留的片段只归一个 region，从而不会跨行重复累计。真实方法可以不同，但所有样本必须一致，不能临时为某个区域选择最有利的规则。</p><p>特别注意固定流程配置中的 <code>-O --fracOverlap 0.2</code>：<code>-O</code> 允许多区域分配，<code>--fracOverlap</code> 是重叠比例要求，不等于分数权重开关。两者不能证明一个片段只出现于一行；是否产生分数计数还要核对其他参数与实际输出。不要盲目四舍五入小数，也不要把整数多重分配误称为唯一分配。<a href="https://github.com/nf-core/atacseq/blob/2.1.2/conf/modules.config">2.1.2 计数配置源码</a>、<a href="https://subread.sourceforge.net/featureCounts.html">featureCounts 作者说明</a></p><p>下面是<strong>准备完成后才可运行的重新计数模板</strong>，不是复刻上面的流程默认值。它面向已过滤、索引完整且参考一致的双端 BAM；使用支持这些参数的 Subread 版本并记录 <code>featureCounts -v</code>。所有占位输入必须预先核验。<code>-p --countReadPairs</code> 明确片段计数，<code>-B -C</code> 排除指定不完整或异常配对，未加 <code>-O</code> 或 <code>--fraction</code>，歧义多区域分配不保留。实际重叠判据依据 featureCounts 的配对比对语义，不应声称它必然覆盖 insert 内的每个碱基。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># PREPARED INPUTS ONLY. No installation, alignment or peak calling here.</span></span><br><span class="line"><span class="comment"># consensus.saf has one unique region ID per region; coordinates validated.</span></span><br><span class="line"><span class="comment"># All six BAMs are independent samples, already filtered with the same policy.</span></span><br><span class="line">atac_count_dir=<span class="string">&quot;<span class="subst">$(mktemp -d <span class="string">&quot;<span class="variable">$PWD</span>/atac-count-XXXXXX&quot;</span>)</span>&quot;</span> || <span class="built_in">exit</span> 1</span><br><span class="line">featureCounts -T 4 -F SAF -s 0 \</span><br><span class="line">  -p --countReadPairs -B -C \</span><br><span class="line">  -a /data/project/consensus.saf \</span><br><span class="line">  -o <span class="string">&quot;<span class="variable">$atac_count_dir</span>/counts.featureCounts.txt&quot;</span> \</span><br><span class="line">  /data/project/bam/CONTROL_REP1.bam \</span><br><span class="line">  /data/project/bam/CONTROL_REP2.bam \</span><br><span class="line">  /data/project/bam/CONTROL_REP3.bam \</span><br><span class="line">  /data/project/bam/TREATMENT_REP1.bam \</span><br><span class="line">  /data/project/bam/TREATMENT_REP2.bam \</span><br><span class="line">  /data/project/bam/TREATMENT_REP3.bam</span><br></pre></td></tr></table></figure><p>这段只为本次计数创建全新目录，不删除任何数据。需检查退出状态、summary 中的成功与失败分配原因，并保留日志及参数。输出含注释列和 BAM 路径列名；导出下游矩阵时只保留计数列，按明确的一对一 BAM→sample ID 对照重命名，不能直接将整张 featureCounts 表当作数值矩阵。<a href="https://subread.sourceforge.net/SubreadUsersGuide.pdf">Subread 用户手册</a>解释了双端计数、重叠分配和 SAF 输入的细节。</p><h2 id="四、可直接运行的小练习：身份、边界和计数审计"><a href="#四、可直接运行的小练习：身份、边界和计数审计" class="headerlink" title="四、可直接运行的小练习：身份、边界和计数审计"></a>四、可直接运行的小练习：身份、边界和计数审计</h2><p>以下只需要 Python 3 标准库，不读真实文件、不安装软件、不写磁盘。三个区域和十八个数都是人为构造，只用于验证结构。我们故意打乱元数据的顺序，程序按 sample ID 恢复对应关系。人工片段台账中每个 pair 只分配一个区域；重复写入同一 pair 会被拒绝。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> Counter, defaultdict</span><br><span class="line"></span><br><span class="line">samples = [<span class="string">&quot;CONTROL_REP1&quot;</span>, <span class="string">&quot;CONTROL_REP2&quot;</span>, <span class="string">&quot;CONTROL_REP3&quot;</span>,</span><br><span class="line">           <span class="string">&quot;TREATMENT_REP1&quot;</span>, <span class="string">&quot;TREATMENT_REP2&quot;</span>, <span class="string">&quot;TREATMENT_REP3&quot;</span>]</span><br><span class="line">conditions = [<span class="string">&quot;control&quot;</span>] * <span class="number">3</span> + [<span class="string">&quot;treated&quot;</span>] * <span class="number">3</span></span><br><span class="line">batches = [<span class="string">&quot;day1&quot;</span>, <span class="string">&quot;day2&quot;</span>, <span class="string">&quot;day1&quot;</span>, <span class="string">&quot;day1&quot;</span>, <span class="string">&quot;day2&quot;</span>, <span class="string">&quot;day2&quot;</span>]</span><br><span class="line">metadata = [<span class="built_in">dict</span>(sample_id=s, biological_id=<span class="string">f&quot;demo_<span class="subst">&#123;i + <span class="number">1</span>&#125;</span>&quot;</span>,</span><br><span class="line">                 condition=c, batch=b, qc_pass=<span class="literal">True</span>)</span><br><span class="line">            <span class="keyword">for</span> i, (s, c, b) <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="built_in">zip</span>(samples, conditions, batches))]</span><br><span class="line">metadata = <span class="built_in">list</span>(<span class="built_in">reversed</span>(metadata))</span><br><span class="line">regions = [(<span class="string">&quot;region_01&quot;</span>, <span class="string">&quot;chr1&quot;</span>, <span class="number">100</span>, <span class="number">200</span>),</span><br><span class="line">           (<span class="string">&quot;region_02&quot;</span>, <span class="string">&quot;chr1&quot;</span>, <span class="number">400</span>, <span class="number">500</span>),</span><br><span class="line">           (<span class="string">&quot;region_03&quot;</span>, <span class="string">&quot;chr2&quot;</span>, <span class="number">100</span>, <span class="number">200</span>)]</span><br><span class="line">counts = [[<span class="number">10</span>, <span class="number">11</span>, <span class="number">9</span>, <span class="number">14</span>, <span class="number">12</span>, <span class="number">13</span>],</span><br><span class="line">          [<span class="number">20</span>, <span class="number">18</span>, <span class="number">21</span>, <span class="number">19</span>, <span class="number">22</span>, <span class="number">20</span>],</span><br><span class="line">          [<span class="number">5</span>, <span class="number">6</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">6</span>]]</span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(samples) == <span class="built_in">len</span>(<span class="built_in">set</span>(samples))</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(metadata) == <span class="built_in">len</span>(samples)</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(&#123;r[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> metadata&#125;) == <span class="built_in">len</span>(metadata)</span><br><span class="line"><span class="keyword">assert</span> &#123;r[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> metadata&#125; == <span class="built_in">set</span>(samples)</span><br><span class="line">by_sample = &#123;r[<span class="string">&quot;sample_id&quot;</span>]: r <span class="keyword">for</span> r <span class="keyword">in</span> metadata&#125;</span><br><span class="line">aligned = [by_sample[s] <span class="keyword">for</span> s <span class="keyword">in</span> samples]</span><br><span class="line"><span class="keyword">assert</span> [r[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> aligned] == samples</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(r[<span class="string">&quot;qc_pass&quot;</span>] <span class="keyword">is</span> <span class="literal">True</span> <span class="keyword">for</span> r <span class="keyword">in</span> aligned)</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(&#123;r[<span class="string">&quot;biological_id&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> aligned&#125;) == <span class="built_in">len</span>(aligned)</span><br><span class="line"><span class="keyword">assert</span> &#123;r[<span class="string">&quot;condition&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> aligned&#125; == &#123;<span class="string">&quot;control&quot;</span>, <span class="string">&quot;treated&quot;</span>&#125;</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(n &gt;= <span class="number">3</span> <span class="keyword">for</span> n <span class="keyword">in</span> Counter(r[<span class="string">&quot;condition&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> aligned).values())</span><br><span class="line">batch_conditions = defaultdict(<span class="built_in">set</span>)</span><br><span class="line"><span class="keyword">for</span> row <span class="keyword">in</span> aligned:</span><br><span class="line">    batch_conditions[row[<span class="string">&quot;batch&quot;</span>]].add(row[<span class="string">&quot;condition&quot;</span>])</span><br><span class="line"><span class="comment"># This is a conservative teaching check, not a general design-rank test.</span></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(v == &#123;<span class="string">&quot;control&quot;</span>, <span class="string">&quot;treated&quot;</span>&#125; <span class="keyword">for</span> v <span class="keyword">in</span> batch_conditions.values())</span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(&#123;r[<span class="number">0</span>] <span class="keyword">for</span> r <span class="keyword">in</span> regions&#125;) == <span class="built_in">len</span>(regions)</span><br><span class="line"><span class="keyword">for</span> region_id, chrom, start, end <span class="keyword">in</span> regions:</span><br><span class="line">    <span class="keyword">assert</span> region_id <span class="keyword">and</span> chrom <span class="keyword">in</span> &#123;<span class="string">&quot;chr1&quot;</span>, <span class="string">&quot;chr2&quot;</span>&#125;</span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">type</span>(start) <span class="keyword">is</span> <span class="built_in">int</span> <span class="keyword">and</span> <span class="built_in">type</span>(end) <span class="keyword">is</span> <span class="built_in">int</span> <span class="keyword">and</span> <span class="number">0</span> &lt;= start &lt; end</span><br><span class="line"><span class="keyword">for</span> i, a <span class="keyword">in</span> <span class="built_in">enumerate</span>(regions):</span><br><span class="line">    <span class="keyword">for</span> b <span class="keyword">in</span> regions[i + <span class="number">1</span>:]:</span><br><span class="line">        <span class="keyword">assert</span> <span class="keyword">not</span> (a[<span class="number">1</span>] == b[<span class="number">1</span>] <span class="keyword">and</span> a[<span class="number">2</span>] &lt; b[<span class="number">3</span>] <span class="keyword">and</span> b[<span class="number">2</span>] &lt; a[<span class="number">3</span>])</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(counts) == <span class="built_in">len</span>(regions)</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(<span class="built_in">len</span>(row) == <span class="built_in">len</span>(samples) <span class="keyword">for</span> row <span class="keyword">in</span> counts)</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(<span class="built_in">type</span>(n) <span class="keyword">is</span> <span class="built_in">int</span> <span class="keyword">and</span> n &gt;= <span class="number">0</span> <span class="keyword">for</span> row <span class="keyword">in</span> counts <span class="keyword">for</span> n <span class="keyword">in</span> row)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Synthetic, already uniquely assigned pairs; not a BAM parser.</span></span><br><span class="line">assignments = []</span><br><span class="line"><span class="keyword">for</span> (region_id, *_), row <span class="keyword">in</span> <span class="built_in">zip</span>(regions, counts):</span><br><span class="line">    <span class="keyword">for</span> sample, n <span class="keyword">in</span> <span class="built_in">zip</span>(samples, row):</span><br><span class="line">        assignments.extend((sample, <span class="string">f&quot;<span class="subst">&#123;region_id&#125;</span>_pair_<span class="subst">&#123;j&#125;</span>&quot;</span>, region_id)</span><br><span class="line">                           <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(n))</span><br><span class="line">pair_keys = [(s, pair_id) <span class="keyword">for</span> s, pair_id, region_id <span class="keyword">in</span> assignments]</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(pair_keys) == <span class="built_in">len</span>(<span class="built_in">set</span>(pair_keys)), <span class="string">&quot;Repeated pair assignment&quot;</span></span><br><span class="line">recounted = Counter((region_id, s) <span class="keyword">for</span> s, pair_id, region_id <span class="keyword">in</span> assignments)</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(recounted[(region_id, s)] == n</span><br><span class="line">           <span class="keyword">for</span> (region_id, *_), row <span class="keyword">in</span> <span class="built_in">zip</span>(regions, counts)</span><br><span class="line">           <span class="keyword">for</span> s, n <span class="keyword">in</span> <span class="built_in">zip</span>(samples, row))</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;PASS: 3 synthetic regions, 6 independent sample IDs, matched metadata&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Synthetic matrix column totals:&quot;</span>,</span><br><span class="line">      <span class="built_in">dict</span>(<span class="built_in">zip</span>(samples, [<span class="built_in">sum</span>(row[j] <span class="keyword">for</span> row <span class="keyword">in</span> counts) <span class="keyword">for</span> j <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">6</span>)])))</span><br></pre></td></tr></table></figure><p>检查输出应包括 <code>PASS</code> 和列总量 <code>35、35、34、38、41、39</code>。这些数是代码练习答案，不是深度、FRiP 或生物学效应。这里的区间断言也仅检查这三个教学区域，不能代替真实参考、blacklist 与 BAM 的审计。</p><p>试着复制一条 assignment，重复片段断言应失败；把一个计数改成 <code>2.5</code>，整数断言应失败；删除一个元数据样本，集合匹配应失败。最后将所有对照改为 day1、处理改为 day2，批次断言应拒绝继续。这些练习帮助区分“程序可以算”与“比较有意义”。</p><h2 id="五、设计公式必须对应真实实验单位"><a href="#五、设计公式必须对应真实实验单位" class="headerlink" title="五、设计公式必须对应真实实验单位"></a>五、设计公式必须对应真实实验单位</h2><p>本例的 day1 包含 CONTROL_REP1、CONTROL_REP3、TREATMENT_REP1，day2 包含其他三个样本，两组都出现在两个批次。采用 <code>~ batch + condition</code> 的加性模型，估计考虑批次后的共同处理系数，不估计批次×处理交互。两组各三个不同来源，因此不加入一个每样本唯一的 subject 因子；那会耗尽可估计的信息。</p><p>若真实实验是同一供者各有处理与对照，则应携带供者身份并审查 <code>~ subject + condition</code> 等配对设计，而不是照抄本例。若所有对照来自 day1、所有处理来自 day2，处理与批次完全混杂；删掉 batch 变量只会隐藏问题，不能创造可识别的处理效应。设计矩阵满秩是必要条件，不保证样本量充分、不存在残余混杂或因果解释成立。</p><p>下面的 R 模板仅接收已完成上述审计的<strong>真实、全量统一区域原始整数片段矩阵</strong>。不要把三行人工数据接入它来计算科学显著性。准备 <code>raw_region_counts.tsv</code>（第一列 region ID，其余列为 sample ID）与 <code>metadata.tsv</code>（第一列 sample ID，另有 <code>biological_id、condition、batch、qc_pass</code>；QC 合格写 <code>TRUE</code>）。已有相容 R&#x2F;Bioconductor 与 DESeq2 时才可运行，不自动安装。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># PREPARED REAL DATA ONLY: never feed the three-region teaching matrix here.</span></span><br><span class="line"><span class="keyword">if</span> <span class="punctuation">(</span><span class="operator">!</span>requireNamespace<span class="punctuation">(</span><span class="string">&quot;DESeq2&quot;</span><span class="punctuation">,</span> quietly <span class="operator">=</span> <span class="literal">TRUE</span><span class="punctuation">)</span><span class="punctuation">)</span> stop<span class="punctuation">(</span><span class="string">&quot;Prepare DESeq2 first&quot;</span><span class="punctuation">)</span></span><br><span class="line">suppressPackageStartupMessages<span class="punctuation">(</span>library<span class="punctuation">(</span>DESeq2<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">cts <span class="operator">&lt;-</span> as.matrix<span class="punctuation">(</span>read.delim<span class="punctuation">(</span><span class="string">&quot;raw_region_counts.tsv&quot;</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">                           check.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta <span class="operator">&lt;-</span> read.delim<span class="punctuation">(</span><span class="string">&quot;metadata.tsv&quot;</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="number">1</span><span class="punctuation">,</span> check.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">is.numeric</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">is.finite</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>cts <span class="operator">&gt;=</span> <span class="number">0</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span>cts <span class="operator">==</span> <span class="built_in">floor</span><span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>cts <span class="operator">&lt;=</span> .Machine<span class="operator">$</span>integer.max<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          nrow<span class="punctuation">(</span>cts<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">,</span> ncol<span class="punctuation">(</span>cts<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">nzchar</span><span class="punctuation">(</span>rownames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">nzchar</span><span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>rownames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span> <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          setequal<span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;biological_id&quot;</span><span class="punctuation">,</span> <span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;batch&quot;</span><span class="punctuation">,</span> <span class="string">&quot;qc_pass&quot;</span><span class="punctuation">)</span> <span class="operator">%in%</span> <span class="built_in">names</span><span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta <span class="operator">&lt;-</span> meta<span class="punctuation">[</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> <span class="punctuation">,</span> drop <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">]</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>identical<span class="punctuation">(</span>colnames<span class="punctuation">(</span>cts<span class="punctuation">)</span><span class="punctuation">,</span> rownames<span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span><span class="built_in">anyNA</span><span class="punctuation">(</span>meta<span class="punctuation">)</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>meta<span class="operator">$</span>qc_pass <span class="operator">==</span> <span class="literal">TRUE</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">nzchar</span><span class="punctuation">(</span>trimws<span class="punctuation">(</span>meta<span class="operator">$</span>biological_id<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span><span class="built_in">nzchar</span><span class="punctuation">(</span>trimws<span class="punctuation">(</span>meta<span class="operator">$</span>batch<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="operator">!</span>anyDuplicated<span class="punctuation">(</span>meta<span class="operator">$</span>biological_id<span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span>meta<span class="operator">$</span>condition <span class="operator">%in%</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;control&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta<span class="operator">$</span>condition <span class="operator">&lt;-</span> factor<span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">,</span> levels <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;control&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">meta<span class="operator">$</span>batch <span class="operator">&lt;-</span> factor<span class="punctuation">(</span>meta<span class="operator">$</span>batch<span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>nlevels<span class="punctuation">(</span>meta<span class="operator">$</span>batch<span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="number">2L</span><span class="punctuation">,</span> <span class="built_in">all</span><span class="punctuation">(</span>table<span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="number">3L</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">          <span class="built_in">all</span><span class="punctuation">(</span>table<span class="punctuation">(</span>meta<span class="operator">$</span>batch<span class="punctuation">,</span> meta<span class="operator">$</span>condition<span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">design_formula <span class="operator">&lt;-</span> <span class="operator">~</span> batch <span class="operator">+</span> condition</span><br><span class="line">x <span class="operator">&lt;-</span> model.matrix<span class="punctuation">(</span>design_formula<span class="punctuation">,</span> data <span class="operator">=</span> meta<span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>qr<span class="punctuation">(</span>x<span class="punctuation">)</span><span class="operator">$</span>rank <span class="operator">==</span> ncol<span class="punctuation">(</span>x<span class="punctuation">)</span><span class="punctuation">,</span> nrow<span class="punctuation">(</span>x<span class="punctuation">)</span> <span class="operator">&gt;</span> ncol<span class="punctuation">(</span>x<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">storage.mode<span class="punctuation">(</span>cts<span class="punctuation">)</span> <span class="operator">&lt;-</span> <span class="string">&quot;integer&quot;</span></span><br><span class="line">dds <span class="operator">&lt;-</span> DESeqDataSetFromMatrix<span class="punctuation">(</span>cts<span class="punctuation">,</span> colData <span class="operator">=</span> meta<span class="punctuation">,</span> design <span class="operator">=</span> design_formula<span class="punctuation">)</span></span><br><span class="line">keep <span class="operator">&lt;-</span> rowSums<span class="punctuation">(</span>counts<span class="punctuation">(</span>dds<span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="number">10L</span><span class="punctuation">)</span> <span class="operator">&gt;=</span> <span class="built_in">min</span><span class="punctuation">(</span>table<span class="punctuation">(</span>meta<span class="operator">$</span>condition<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">any</span><span class="punctuation">(</span>keep<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">dds <span class="operator">&lt;-</span> dds<span class="punctuation">[</span>keep<span class="punctuation">,</span> <span class="punctuation">]</span></span><br><span class="line">stopifnot<span class="punctuation">(</span><span class="built_in">all</span><span class="punctuation">(</span>rowSums<span class="punctuation">(</span>counts<span class="punctuation">(</span>dds<span class="punctuation">)</span><span class="punctuation">)</span> <span class="operator">&gt;</span> <span class="number">0L</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Run this default normalization only after reviewing section 6&#x27;s assumptions.</span></span><br><span class="line">dds <span class="operator">&lt;-</span> DESeq<span class="punctuation">(</span>dds<span class="punctuation">)</span></span><br><span class="line">res <span class="operator">&lt;-</span> results<span class="punctuation">(</span>dds<span class="punctuation">,</span> contrast <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">,</span> <span class="string">&quot;control&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span> alpha <span class="operator">=</span> <span class="number">0.05</span><span class="punctuation">)</span></span><br><span class="line">tab <span class="operator">&lt;-</span> data.frame<span class="punctuation">(</span>region_id <span class="operator">=</span> rownames<span class="punctuation">(</span>res<span class="punctuation">)</span><span class="punctuation">,</span> as.data.frame<span class="punctuation">(</span>res<span class="punctuation">)</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">NULL</span><span class="punctuation">)</span></span><br><span class="line">out <span class="operator">&lt;-</span> tempfile<span class="punctuation">(</span><span class="string">&quot;perry-atac-de-&quot;</span><span class="punctuation">,</span> tmpdir <span class="operator">=</span> getwd<span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">stopifnot<span class="punctuation">(</span>dir.create<span class="punctuation">(</span>out<span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">write.table<span class="punctuation">(</span>tab<span class="punctuation">,</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;treated_vs_control_all_regions.tsv&quot;</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">            sep <span class="operator">=</span> <span class="string">&quot;\t&quot;</span><span class="punctuation">,</span> row.names <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">,</span> <span class="built_in">quote</span> <span class="operator">=</span> <span class="literal">FALSE</span><span class="punctuation">)</span></span><br><span class="line">saveRDS<span class="punctuation">(</span><span class="built_in">list</span><span class="punctuation">(</span>dds <span class="operator">=</span> dds<span class="punctuation">,</span> res <span class="operator">=</span> res<span class="punctuation">,</span> metadata <span class="operator">=</span> meta<span class="punctuation">,</span> design <span class="operator">=</span> design_formula<span class="punctuation">,</span></span><br><span class="line">             contrast <span class="operator">=</span> <span class="built_in">c</span><span class="punctuation">(</span><span class="string">&quot;condition&quot;</span><span class="punctuation">,</span> <span class="string">&quot;treated&quot;</span><span class="punctuation">,</span> <span class="string">&quot;control&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">,</span></span><br><span class="line">        file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;atac_bundle.rds&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">capture.output<span class="punctuation">(</span>sessionInfo<span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> file <span class="operator">=</span> file.path<span class="punctuation">(</span>out<span class="punctuation">,</span> <span class="string">&quot;sessionInfo.txt&quot;</span><span class="punctuation">)</span><span class="punctuation">)</span></span><br><span class="line">message<span class="punctuation">(</span><span class="string">&quot;Prepared-data outputs saved to: &quot;</span><span class="punctuation">,</span> normalizePath<span class="punctuation">(</span>out<span class="punctuation">)</span><span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p>本模板主动要求交叉批次例子和独立来源，不是所有设计的通用入口。预过滤的计数阈值与最小组样本数是预先声明的教学选择，应结合真实深度重新评估。保存的结果表对应进入模型的全部区域，而不是只保存显著行；原始计数、区域坐标和生成记录仍需另行归档。函数的计数入口、因子水平和显式 contrast 规则可与<a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html">DESeq2 官方教程</a>逐项核对。</p><h2 id="六、归一化不是全局可及性测量仪"><a href="#六、归一化不是全局可及性测量仪" class="headerlink" title="六、归一化不是全局可及性测量仪"></a>六、归一化不是全局可及性测量仪</h2><p>原始 counts 与测序深度有关，不能仅凭一列总数较大就宣称染色质整体更开放。常规的相对归一化依赖一组用于确定尺度的特征及其变化分布；大片区域同方向变化、处理引起细胞组成改变或建库效率改变时，尺度选择尤其需要审查。</p><p>如果研究问题是每个细胞的全局可及性变化，普通测序数据的相对组成不能自动提供绝对尺度。需要在实验阶段考虑可验证的外部控制、兼容协议的 spike-in、细胞数量与实验效率记录，或有充分依据的稳定参考区域；不能事后指定几个“看起来不变”的峰就声称问题已经解决。背景区域归一化也有自己的假设，不保证所有背景都稳定。DiffBind 提供多种归一化路线，选择工具本身不会消除这些判断。</p><p>应在结果解释前声明尺度依据，查看 QC、size factors、MA 图与其他诊断，必要时做有理由的敏感性分析。不能试遍归一化方式，只保留最符合预期的一套；更不能先对 counts 做 CPM，再把它送入原始计数入口。这一节也是上面的默认 DESeq2 模板的使用门槛，不满足时应暂停而不是硬跑。</p><h2 id="七、看效应、精度和候选规则，不只看一颗星"><a href="#七、看效应、精度和候选规则，不只看一颗星" class="headerlink" title="七、看效应、精度和候选规则，不只看一颗星"></a>七、看效应、精度和候选规则，不只看一颗星</h2><p>本例固定 treated 相对 control 的方向：正 log2FC 表示在声明的模型与归一化尺度下信号更高。效应、标准误、p 值与 FDR 调整后的值回答不同问题；不显著不等于没有变化，低计数的大倍数也不自动值得优先解释。NA 可能与低信息、过滤或异常值有关，应追查原因，不能全部替换成零。</p><p><code>padj &lt; 0.05</code> 是声明模型下的多重检验规则，不是某个区域有“95% 概率真实”。普通检验后再按效应大小筛候选，不等于已经检验了“真实效应超过这个阈值”。可参考<a href="/posts/rna2602/">RNA-seq 第二章的设计与结果解释</a>，其中统计概念能够借鉴，但 RNA 的计数单位与 ATAC 的归一化问题不能直接互换。</p><p>常见误区还有：峰出现／消失被当成定量差异；pooled BAM 被当成多个重复；图上不同自动纵轴被当成效应证据；整数矩阵被误当成正确片段矩阵。这些错误都可能得到漂亮结果，因而需要在结果之前检查，而不是靠图的美观程度判断。</p><h2 id="八、从区域到机制，还有几道需要跨越的桥"><a href="#八、从区域到机制，还有几道需要跨越的桥" class="headerlink" title="八、从区域到机制，还有几道需要跨越的桥"></a>八、从区域到机制，还有几道需要跨越的桥</h2><p>motif 富集表示某类序列模式相对明确背景更集中，不证明对应转录因子在该条件下实际结合或激活。motif 家族相似、区域长度和 GC 背景都需要考虑；应记录候选与背景，而不是把一个富集名称当作功能结论。</p><p>最近基因注释只是位置上的关联，不等于已确认的调控靶点。远端区域可能作用于其他基因；ATAC 与 RNA 的方向一致可以帮助提出假设，却仍可能受到细胞组成、时间和共同上游因素影响。二者叠加并不会自动升级为因果机制，需要更具体的定位、时序、结合或干预证据。</p><p>最后的交付应能说清：比较哪几个独立实验单位、在哪套统一区域上按什么规则计数、QC 与归一化为何可信、设计和对比是什么，以及哪些结论只是待验证的解释。可重查的原始矩阵、区域 BED、元数据、参数、版本、完整表和排除记录，比只有一张火山图更有用。</p><p>上一章：<a href="/posts/atac2602/">从 FASTQ 到可审计的峰集合</a>；课程起点：<a href="/posts/atac2601/">实验设计与质控口径</a>。</p><h2 id="官方参考与继续阅读"><a href="#官方参考与继续阅读" class="headerlink" title="官方参考与继续阅读"></a>官方参考与继续阅读</h2><ul><li><a href="https://nf-co.re/atacseq/2.1.2/docs/output">nf-core&#x2F;atacseq 2.1.2：consensus、计数与 QC 输出</a></li><li><a href="https://github.com/nf-core/atacseq/blob/2.1.2/conf/modules.config">nf-core&#x2F;atacseq 2.1.2：流程参数配置源码</a></li><li><a href="https://subread.sourceforge.net/featureCounts.html">Subread：featureCounts 作者说明</a></li><li><a href="https://subread.sourceforge.net/SubreadUsersGuide.pdf">Subread：用户手册及配对、重叠、注释格式约定</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/DiffBind/inst/doc/DiffBind.pdf">Bioconductor：DiffBind 官方教程，尤其归一化章节</a></li><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html">Bioconductor：DESeq2 官方计数输入、设计与 contrast 教程</a></li></ul>]]></content>
    
    
    <summary type="html">用纯教学矩阵练习样本匹配与片段计数检查，理解 bulk ATAC-seq 的统一峰集合、批次设计、归一化假设和差异结果的解释边界。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="生物信息学" scheme="https://perry.apay.eu.cc/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
    
    <category term="ATAC-seq" scheme="https://perry.apay.eu.cc/tags/ATAC-seq/"/>
    
    <category term="差异可及性" scheme="https://perry.apay.eu.cc/tags/%E5%B7%AE%E5%BC%82%E5%8F%AF%E5%8F%8A%E6%80%A7/"/>
    
  </entry>
  
  <entry>
    <title>ATAC-seq 入门（二）：从 FASTQ 到可审计的峰集合</title>
    <link href="https://perry.apay.eu.cc/posts/atac2602/"/>
    <id>https://perry.apay.eu.cc/posts/atac2602/</id>
    <published>2026-10-06T09:20:02.000Z</published>
    <updated>2026-10-06T09:20:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>本篇继续讨论 <strong>bulk paired-end ATAC-seq</strong>，不覆盖 scATAC-seq。示例命令是教学模板，没有在本站执行测序分析，也没有真实实验结果。先完成<a href="/posts/atac2601/">实验设计与质控口径</a>中的输入核对，再考虑运行流程。</p><p>这一篇的目标，不是记住一长串命令，而是让每个输出都能回答三个问题：它由什么输入产生，经过哪些转换，下一步允许拿它做什么。</p><span id="more"></span><h2 id="一、把流程画成数据转换"><a href="#一、把流程画成数据转换" class="headerlink" title="一、把流程画成数据转换"></a>一、把流程画成数据转换</h2><p>双端 FASTQ 保存两端读段；比对后的 BAM 保存读段与参考坐标的关系；过滤后的 BAM 决定后续使用哪些有效信息；峰文件描述信号富集的区域；bigWig 用于浏览连续信号；统一区域计数表才是差异分析的输入候选。它们不是同一种数据的不同扩展名。</p><p>最容易出问题的地方，是只记住“最后生成了 peaks”，却不知道比较使用的是去重复前还是去重复后的 BAM，或将展示用的归一化信号误当成统计模型需要的原始计数。流程追踪要贯穿整个目录，而不是在结果末尾补一句“默认参数”。</p><p>本篇固定使用 <code>nf-core/atacseq 2.1.2</code>。固定的是学习示例的版本，不代表声称它永远是最新或适合所有协议。升级版本时应重新核对输入、过滤步骤、峰识别和计数参数，不能只改版本号。<a href="https://nf-co.re/atacseq/2.1.2/docs/usage">固定版本使用文档</a></p><h2 id="二、运行之前，输入和资源都要明确"><a href="#二、运行之前，输入和资源都要明确" class="headerlink" title="二、运行之前，输入和资源都要明确"></a>二、运行之前，输入和资源都要明确</h2><p>需要上一章的样本表、配对且完整的 gzip FASTQ、同一组装的 FASTA&#x2F;GTF、匹配的 blacklist&#x2F;TSS 注释，以及实际确认的线粒体名称。这里假设两组各有三个独立生物学重复，并以相同长度的双端 reads 测序。不同读长、不同参考和不同建库方案需要重新制定配置。</p><p>运行环境应有兼容该版本的 Nextflow、Java 和可用容器运行时。下面选用 Linux／WSL／集群环境中的 Docker profile，只是示例；受管理的计算集群应使用其允许的容器和调度配置。不要将它复制到尚未检查容器权限、磁盘和内存的环境里直接启动。大型参考建立索引及中间 BAM 可能占用较多资源，不能从“代码只有几行”推断任务很轻。</p><p>下面所有 <code>/data</code> 和 <code>/refs</code> 文件都是占位路径。<code>GRCh38</code> 是本例的参考选择，不是要求所有项目换成人类数据。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">nextflow run nf-core/atacseq -r 2.1.2 \</span><br><span class="line">  -profile docker \</span><br><span class="line">  --input /data/samplesheet.csv \</span><br><span class="line">  --outdir /data/results/atacseq-2.1.2 \</span><br><span class="line">  --fasta /refs/GRCh38.fa \</span><br><span class="line">  --gtf /refs/GRCh38.annotation.gtf \</span><br><span class="line">  --blacklist /refs/GRCh38.blacklist.bed \</span><br><span class="line">  --tss_bed /refs/GRCh38.tss.bed \</span><br><span class="line">  --mito_name chrM \</span><br><span class="line">  --read_length 150 \</span><br><span class="line">  --aligner bowtie2 \</span><br><span class="line">  --narrow_peak \</span><br><span class="line">  --min_reps_consensus 2 \</span><br><span class="line">  --skip_merge_replicates</span><br></pre></td></tr></table></figure><p><code>-r</code> 与 <code>-profile</code> 属于 Nextflow 选项，使用单横线；<code>--input</code> 等属于流程参数，使用双横线。本例显式选择 narrow peaks，并关闭将同组生物学重复合并的附加分支，保留独立样本用于比较。<code>--min_reps_consensus 2</code> 是这个三重复教学设计的候选区域支持规则，需要按实际重复质量评估，不是通用最优值。<code>--read_length</code> 与有效基因组大小的处理有关；若显式提供 <code>--macs_gsize</code>，需要记录其来源和依据，而不是随便抄一个物种缩写。<a href="https://nf-co.re/atacseq/2.1.2/parameters">2.1.2 参数定义</a></p><p>不要为使峰数量增加就开启 <code>--keep_dups</code>、<code>--keep_multi_map</code> 或 <code>--keep_mito</code>。改变保留规则会改变可比性，必须有协议与研究问题上的理由。也不要为了省时间跳过关键 QC，然后把缺失的报告当成“没有异常”。</p><h2 id="三、理解每个步骤在保护什么"><a href="#三、理解每个步骤在保护什么" class="headerlink" title="三、理解每个步骤在保护什么"></a>三、理解每个步骤在保护什么</h2><p>接头剪切解决的是文库片段短于测序读长时的接头残留等问题，不是把所有警告修成绿色。比对把序列放到参考坐标上，但多重映射仍可能产生解释歧义。重复标记帮助识别可能重复测到的分子；它不能仅凭一个百分比判断整个实验是否失败。</p><p>技术 lane 可以合并到对应生物学样本，生物学重复则应保留独立身份。过滤还涉及配对关系、线粒体和不可靠区域。若两端之一被过滤，剩下的孤立 read 不能继续假装是一条完整片段。每一步的日志应回答“保留了多少、排除了多少、按什么规则排除”，否则看到有效数据减少时很难定位原因。</p><p>浏览信号轨道时，应核对归一化方式并让比较图使用一致的纵轴。自动缩放后的两个轨道都看起来很高，不意味着强度一样；局部位置也不能替代全局质控。峰识别进一步把连续信号变成区域集合，这一步受到深度、背景、参数和候选区域规则影响，因此峰数量不是独立于流程的自然常数。</p><h2 id="四、Tn5-校正只做一次，而且先分清数据模型"><a href="#四、Tn5-校正只做一次，而且先分清数据模型" class="headerlink" title="四、Tn5 校正只做一次，而且先分清数据模型"></a>四、Tn5 校正只做一次，而且先分清数据模型</h2><p>ATAC-seq 常提到 Tn5 插入位点校正，但“做 ATAC 就在所有 BAM 上加 shift”是危险的简化。首先问下游想要的是<strong>真实双端片段</strong>，还是<strong>校正后的插入切点</strong>。这两个对象服务于不同的统计与可视化问题。</p><p>固定版本的 MACS2 模块使用 <code>2.2.7.1</code>，双端数据默认选择 <code>BAMPE</code>，从两端信息使用片段。本文沿用这条片段级峰识别路线，<strong>不另加外部 Tn5 shift，也不叠加单端切点路线的 <code>--shift/--extsize</code> 组合</strong>。<a href="https://github.com/nf-core/atacseq/blob/2.1.2/modules/nf-core/macs2/callpeak/main.nf">2.1.2 MACS2 模块源码</a></p><p>如果另做 footprinting 或切点轨道，应从明确、未重复校正的来源生成单独的派生文件，记录谁负责校正、何时校正、使用什么工具与坐标约定。deepTools 的 <code>alignmentSieve --ATACshift</code> 有明确的双端位移定义；它不能被无差别加到本文主线中，更不能与已校正输入重复使用。<a href="https://deeptools.readthedocs.io/en/develop/content/tools/alignmentSieve.html">alignmentSieve 文档</a></p><p>另一个常见错误，是把 <code>--nomodel --shift -100 --extsize 200</code> 当成“完成 Tn5 校正”的同义词。它描述的是另一种峰识别时的信号扩展方式，不等于插入位置校正；BAMPE 又有自己的片段语义。现代 MACS 官方文档明确区分这些输入模型，阅读时也应注意它讨论的是 MACS3，而本文流程固定的是 MACS2，不能借此悄悄换掉实现。<a href="https://macs3-project.github.io/MACS/docs/callpeak.html">MACS 输入模式与参数说明</a></p><h2 id="五、结果目录应该能被解释"><a href="#五、结果目录应该能被解释" class="headerlink" title="五、结果目录应该能被解释"></a>五、结果目录应该能被解释</h2><p>选择 Bowtie2 后，下游文件位于 <code>bowtie2/</code>。本例最值得寻找的是以下几类文件；具体前缀依样本和峰类型而定，不应硬猜完整文件名：</p><table><thead><tr><th>输出位置</th><th>阅读用途</th></tr></thead><tbody><tr><td><code>fastqc/</code>、<code>trimgalore/</code></td><td>原始和剪切后的质量信息与日志</td></tr><tr><td><code>bowtie2/merged_library/</code></td><td>独立生物学样本对应的过滤 BAM，留意 <code>.mLb.clN.</code> 标识</td></tr><tr><td><code>bowtie2/merged_library/macs2/narrow_peak/</code></td><td>单样本峰及峰相关 QC</td></tr><tr><td>上述目录的 <code>consensus/</code></td><td>统一区域 BED／SAF 和计数结果</td></tr><tr><td><code>multiqc/narrow_peak/</code></td><td>汇总报告及可追查的统计数据</td></tr></tbody></table><p>需要检查过滤前后 BAM 时，应预先决定是否保存中间比对文件；未保存的中间文件不应被文章或脚本假定存在。<code>merged_library</code> 在这里对应技术数据合并后的生物学样本，不等于把一组独立样本混成一个统计重复。<a href="https://nf-co.re/atacseq/2.1.2/docs/output">2.1.2 输出说明</a></p><p>同时保存输入清单、运行命令、参数文件、软件版本、日志、参考校验值和必要的工作流信息。<code>-resume</code> 可以复用符合缓存条件的步骤，但它不是允许改动参考和参数却仍沿用旧结果标签的理由。改变方法后，结果标签和记录也必须能区分。</p><h2 id="六、用四条假设片段理解-FRiP"><a href="#六、用四条假设片段理解-FRiP" class="headerlink" title="六、用四条假设片段理解 FRiP"></a>六、用四条假设片段理解 FRiP</h2><p>以下练习完全是人工构造的区间：四条已经通过约定过滤的核基因组片段，两段不重叠的峰。使用 BED 的零起点、半开区间，片段只要至少命中一个峰就计一次。这个定义便于理解，但不是声称它复现了流程中的 FRiP 实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">fragments = [</span><br><span class="line">    (<span class="string">&quot;chr1&quot;</span>, <span class="number">100</span>, <span class="number">160</span>),</span><br><span class="line">    (<span class="string">&quot;chr1&quot;</span>, <span class="number">180</span>, <span class="number">230</span>),</span><br><span class="line">    (<span class="string">&quot;chr1&quot;</span>, <span class="number">500</span>, <span class="number">550</span>),</span><br><span class="line">    (<span class="string">&quot;chr2&quot;</span>, <span class="number">100</span>, <span class="number">140</span>),</span><br><span class="line">]</span><br><span class="line">peaks = [(<span class="string">&quot;chr1&quot;</span>, <span class="number">90</span>, <span class="number">170</span>), (<span class="string">&quot;chr1&quot;</span>, <span class="number">175</span>, <span class="number">240</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">overlaps</span>(<span class="params">a, b</span>):</span><br><span class="line">    <span class="keyword">return</span> a[<span class="number">0</span>] == b[<span class="number">0</span>] <span class="keyword">and</span> a[<span class="number">1</span>] &lt; b[<span class="number">2</span>] <span class="keyword">and</span> b[<span class="number">1</span>] &lt; a[<span class="number">2</span>]</span><br><span class="line"></span><br><span class="line">hits = <span class="built_in">sum</span>(<span class="built_in">any</span>(overlaps(fragment, peak) <span class="keyword">for</span> peak <span class="keyword">in</span> peaks)</span><br><span class="line">           <span class="keyword">for</span> fragment <span class="keyword">in</span> fragments)</span><br><span class="line">denominator = <span class="built_in">len</span>(fragments)</span><br><span class="line"><span class="built_in">print</span>(&#123;<span class="string">&quot;fragments_in_peaks&quot;</span>: hits,</span><br><span class="line">       <span class="string">&quot;usable_fragments&quot;</span>: denominator,</span><br><span class="line">       <span class="string">&quot;fragment_FRiP&quot;</span>: hits / denominator&#125;)</span><br><span class="line"><span class="keyword">assert</span> hits == <span class="number">2</span> <span class="keyword">and</span> denominator == <span class="number">4</span></span><br></pre></td></tr></table></figure><p>预期是两条命中、四条作为分母，得到 <code>0.5</code>；这只是练习答案，不是文库质量结果。把第一段峰复制一份，答案仍应为两条，因为 <code>any</code> 保证同一片段不会重复累计。把第三条片段也包进一个新增峰，比例就会变化，说明 FRiP 既受数据影响，也受峰集合影响。</p><p>固定流程的 FRiP 模块使用映射 reads 的统计口径，而不是本练习的独立片段口径，还带有其重叠规则。因此不能把练习值与报告数值直接比较。真正记录时写清分子、分母、输入 BAM、过滤状态与峰集合；报告中不同工具的相似名称也不保证同一单位。<a href="https://github.com/nf-core/atacseq/blob/2.1.2/modules/local/frip_score.nf">流程 FRiP 模块源码</a></p><h2 id="七、常见“运行成功但分析不成立”"><a href="#七、常见“运行成功但分析不成立”" class="headerlink" title="七、常见“运行成功但分析不成立”"></a>七、常见“运行成功但分析不成立”</h2><p>样本名错误，可能把技术重复当成生物学重复；染色体命名不一致，可能让 TSS 或 blacklist 没有正确参与分析；组间使用不同过滤标准，可能制造技术差异；把 pooled BAM 当成多个独立样本，可能夸大置信度。这些问题并不一定触发软件错误。</p><p>还有两种解释层面的陷阱：把峰附近的最近基因当成确定靶基因，把 motif 富集当成该转录因子实际结合。峰注释和序列富集是生成待检验假说的入口，不是机制证据的终点。即便暂时只做浏览，也要将观察与推断分开写。</p><p>本篇的验收应是：样本映射无误、参考一致、过滤链可追踪、QC 可以定位到样本、独立重复仍被保留、峰和计数文件的单位明确。若样本质量不足或批次比较不可识别，可以保留描述性结果，但应停止差异推断。下一篇将使用这些材料学习<a href="/posts/atac2603/">差异可及性：从统一峰集合到明确统计问题</a>。</p>]]></content>
    
    
    <summary type="html">使用固定版本 nf-core/atacseq 理解 bulk 双端数据的处理链，区分片段峰识别与切点分析，并练习正确解释 FRiP。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="生物信息学" scheme="https://perry.apay.eu.cc/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
    
    <category term="ATAC-seq" scheme="https://perry.apay.eu.cc/tags/ATAC-seq/"/>
    
    <category term="Nextflow" scheme="https://perry.apay.eu.cc/tags/Nextflow/"/>
    
    <category term="可复现分析" scheme="https://perry.apay.eu.cc/tags/%E5%8F%AF%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>ATAC-seq 入门（一）：先设计比较，再看质量控制</title>
    <link href="https://perry.apay.eu.cc/posts/atac2601/"/>
    <id>https://perry.apay.eu.cc/posts/atac2601/</id>
    <published>2026-10-06T09:20:01.000Z</published>
    <updated>2026-10-06T09:20:01.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一份面向 <strong>bulk、双端测序（paired-end）ATAC-seq</strong> 的学习教程，不适用于把单细胞 ATAC-seq 按同一流程直接处理。文中的样本名称和问题场景均为教学假设，不代表本站作者完成过相应实验，也没有真实样本的分析结果。</p><p>本系列采用已核验的 <code>nf-core/atacseq 2.1.2</code> 作为流程学习入口。第一篇不急着运行峰识别，而是回答一个更早的问题：手里的数据，是否真的能够支持想做的比较？</p><span id="more"></span><h2 id="一、先把“开放”说准确"><a href="#一、先把“开放”说准确" class="headerlink" title="一、先把“开放”说准确"></a>一、先把“开放”说准确</h2><p>ATAC-seq 利用转座酶对相对可及的染色质区域进行标记，随后从测序片段的分布观察染色质可及性。它提供的是一类测量信号，不是所有调控结论的直接答案。原始方法论文是理解实验原理的合适起点，而不是一份可以无条件照搬到所有组织的参数表。<a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC3959825/">原始 ATAC-seq 方法论文</a></p><p>例如，某个区域在处理组中信号增高，可以暂时表述为“该区域的相对可及性发生变化”。此时还不能直接写成“某转录因子结合增强”，更不能凭最近的基因名称写成“这个基因导致了表型”。测量、统计比较、机制解释，是三层不同的问题。</p><p>开始前，建议先写四句话：比较的细胞或组织是什么；处理条件是什么；独立样本来自哪里；准备控制哪些已知混杂因素。如果这四句话写不清，后面的软件即使正常结束，也可能只得到一个漂亮但难以解释的结果目录。</p><h2 id="二、生物学重复不是多几个-FASTQ"><a href="#二、生物学重复不是多几个-FASTQ" class="headerlink" title="二、生物学重复不是多几个 FASTQ"></a>二、生物学重复不是多几个 FASTQ</h2><p>假设准备比较 <code>CONTROL</code> 与 <code>TREATMENT</code>，各有三个独立培养并单独制备文库的样本。这是每组三个生物学重复。如果同一个文库被分到两个测序 lane，两个 lane 增加的是技术测量，不是独立样本数。把它们当成两个重复，会夸大统计上的信息量。</p><p>在固定的 <code>2.1.2</code> 版本里，同一实验组使用相同 <code>sample</code> 标签，以 <code>replicate</code> 区分生物学重复；同一生物学样本的重复测序使用相同的两个标签。务必按该版本的输入语义填写，不要凭“每行就是一个独立样本”的直觉处理。<a href="https://nf-co.re/atacseq/2.1.2/docs/usage">nf-core 2.1.2 输入说明</a></p><p>下面是结构示例，路径只是占位符，必须换成实际可访问的文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sample,fastq_1,fastq_2,replicate</span><br><span class="line">CONTROL,/data/C1_R1.fastq.gz,/data/C1_R2.fastq.gz,1</span><br><span class="line">CONTROL,/data/C2_R1.fastq.gz,/data/C2_R2.fastq.gz,2</span><br><span class="line">CONTROL,/data/C3_R1.fastq.gz,/data/C3_R2.fastq.gz,3</span><br><span class="line">TREATMENT,/data/T1_R1.fastq.gz,/data/T1_R2.fastq.gz,1</span><br><span class="line">TREATMENT,/data/T2_R1.fastq.gz,/data/T2_R2.fastq.gz,2</span><br><span class="line">TREATMENT,/data/T3_R1.fastq.gz,/data/T3_R2.fastq.gz,3</span><br></pre></td></tr></table></figure><p>流程输入表之外，还需要独立的实验元数据表。建议保存 <code>sample_id、condition、batch、donor、library_id、lane、protocol、read_length</code>，并注明哪些字段未知。<code>sample_id</code> 可以采用流程输出的 <code>CONTROL_REP1</code> 等标识，但不能让一个样本在计数表、实验记录和质控表里出现三个不一致的名字。</p><p>三个重复只是这个教学设计的选择，不是任何项目都足够的承诺。样本异质性、预期效应和研究目标都会影响所需的独立样本数。ENCODE 的标准强调生物学重复与重复间一致性；这些要求比“把深度补足就行”更接近比较问题的核心。<a href="https://www.encodeproject.org/atac-seq/">ENCODE ATAC-seq 数据标准</a></p><h2 id="三、批次问题必须在比较前暴露"><a href="#三、批次问题必须在比较前暴露" class="headerlink" title="三、批次问题必须在比较前暴露"></a>三、批次问题必须在比较前暴露</h2><p>考虑两个假设设计。设计甲把所有对照在第一天建库、所有处理在第二天建库；设计乙在两天里都安排对照和处理。甲的处理条件与建库批次完全重合。发现组间差异后，无法仅凭现有数据判断来自处理还是建库日。把 <code>batch</code> 加进公式，不能凭空补出缺失的比较信息。</p><p>设计乙至少提供了同批次内的组间比较机会，但依然需要记录供体、细胞比例、建库步骤等因素。批次交叉不是免检证书。尤其是 bulk 数据，细胞组成变化也可能改变可及性图谱，不能将这种混合信号自动解释为同一种细胞内部的调控变化。</p><p>下面的标准库练习只使用内存中的虚构元数据，不读取测序文件，也不需要安装生信软件。它检查样本标识、重复数与批次交叉情况：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> Counter, defaultdict</span><br><span class="line"></span><br><span class="line">rows = [</span><br><span class="line">    (<span class="string">&quot;CONTROL_REP1&quot;</span>, <span class="string">&quot;control&quot;</span>, <span class="string">&quot;day1&quot;</span>),</span><br><span class="line">    (<span class="string">&quot;CONTROL_REP2&quot;</span>, <span class="string">&quot;control&quot;</span>, <span class="string">&quot;day2&quot;</span>),</span><br><span class="line">    (<span class="string">&quot;CONTROL_REP3&quot;</span>, <span class="string">&quot;control&quot;</span>, <span class="string">&quot;day1&quot;</span>),</span><br><span class="line">    (<span class="string">&quot;TREATMENT_REP1&quot;</span>, <span class="string">&quot;treatment&quot;</span>, <span class="string">&quot;day1&quot;</span>),</span><br><span class="line">    (<span class="string">&quot;TREATMENT_REP2&quot;</span>, <span class="string">&quot;treatment&quot;</span>, <span class="string">&quot;day2&quot;</span>),</span><br><span class="line">    (<span class="string">&quot;TREATMENT_REP3&quot;</span>, <span class="string">&quot;treatment&quot;</span>, <span class="string">&quot;day2&quot;</span>),</span><br><span class="line">]</span><br><span class="line">ids = [sid <span class="keyword">for</span> sid, _, _ <span class="keyword">in</span> rows]</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(ids) == <span class="built_in">len</span>(<span class="built_in">set</span>(ids)), <span class="string">&quot;Duplicate biological sample ID&quot;</span></span><br><span class="line">counts = Counter(condition <span class="keyword">for</span> _, condition, _ <span class="keyword">in</span> rows)</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">set</span>(counts) == &#123;<span class="string">&quot;control&quot;</span>, <span class="string">&quot;treatment&quot;</span>&#125;, <span class="string">&quot;Both comparison conditions are required&quot;</span></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(n &gt;= <span class="number">2</span> <span class="keyword">for</span> n <span class="keyword">in</span> counts.values()), <span class="string">&quot;No replicated comparison&quot;</span></span><br><span class="line">by_batch = defaultdict(<span class="built_in">set</span>)</span><br><span class="line"><span class="keyword">for</span> _, condition, batch <span class="keyword">in</span> rows:</span><br><span class="line">    by_batch[batch].add(condition)</span><br><span class="line"><span class="keyword">for</span> batch, conditions <span class="keyword">in</span> <span class="built_in">sorted</span>(by_batch.items()):</span><br><span class="line">    <span class="built_in">print</span>(batch, <span class="built_in">sorted</span>(conditions))</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">all</span>(<span class="built_in">len</span>(conditions) == <span class="number">1</span> <span class="keyword">for</span> conditions <span class="keyword">in</span> by_batch.values()):</span><br><span class="line">    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Condition may be completely confounded with batch&quot;</span>)</span><br></pre></td></tr></table></figure><p>把所有对照改成 <code>day1</code>、所有处理改成 <code>day2</code>，观察程序为什么停止。这个简化检查只能提示一种风险，不能代替第三篇中的设计矩阵满秩检查，更不能替代实验设计审查。</p><h2 id="四、参考文件是一套坐标系统"><a href="#四、参考文件是一套坐标系统" class="headerlink" title="四、参考文件是一套坐标系统"></a>四、参考文件是一套坐标系统</h2><p>至少需要匹配的基因组 FASTA 与基因注释，同时准备适用的 blacklist 和用于 TSS 统计的注释。不要把“人类参考”当成足够明确的版本说明：GRCh37 与 GRCh38 的坐标不能直接混用；同一组装的不同补丁、附加 contig 和染色体命名也需要核对。</p><p>建议把参考包写成一张清单：组装名称、FASTA 来源和校验值、GTF 发布版本、blacklist 的版本和适用组装、TSS 文件的生成规则、线粒体 contig 名称。<code>chr1</code> 与 <code>1</code> 不一致，可能让区域相交悄悄变成空集；线粒体叫 <code>chrM</code> 还是 <code>MT</code>，应从实际 FASTA 中确认，而不是凭记忆填。</p><p>blacklist 的作用是标记容易产生异常富集或不可靠解释的区域，不是删除“不符合预期”的结果。TSS 文件则定义统计中心的位置：基因、转录本的取舍以及重复 TSS 的处理，都可能影响富集曲线。换了参考定义，旧报告里的阈值就未必能原样比较。流程提供相关参考参数，但参数存在不等于参考文件已经匹配。<a href="https://nf-co.re/atacseq/2.1.2/parameters">nf-core 2.1.2 参考参数</a></p><h2 id="五、质控是一条证据链，不是一个总分"><a href="#五、质控是一条证据链，不是一个总分" class="headerlink" title="五、质控是一条证据链，不是一个总分"></a>五、质控是一条证据链，不是一个总分</h2><p>先看原始 reads 的质量分布、接头和 R1&#x2F;R2 完整性，再看比对与过滤损失，最后看信号结构和重复关系。高比对率不必然代表好文库：大量线粒体片段、重复片段或者背景片段也可以成功比对。只报告最终百分比，会隐藏信息在什么步骤丢失。</p><p>建议分层记录以下内容，并为每个数字写清输入文件和分母：</p><table><thead><tr><th>检查层</th><th>需要观察的内容</th><th>不应直接推出的结论</th></tr></thead><tbody><tr><td>原始数据</td><td>接头、质量、读段数、配对是否完整</td><td>reads 多就有统计功效</td></tr><tr><td>比对与过滤</td><td>核基因组映射、重复、线粒体、有效片段</td><td>比对率高就能进入差异分析</td></tr><tr><td>信号结构</td><td>TSS 曲线、片段长度分布、峰中富集</td><td>单个指标过线就是合格</td></tr><tr><td>重复关系</td><td>相似度、异常样本、批次聚类、峰一致性</td><td>聚类符合预期就没有混杂</td></tr></tbody></table><p>FRiP 需要特别小心。“峰内 reads &#x2F; 可用 mapped reads”与“至少命中一个峰的独立片段 &#x2F; 可用独立片段”不是同一个统计口径。还要说明峰集合来自单样本还是统一集合、是否去重复、是否去线粒体、采用何种重叠规则。若两个报告的分母不同，不能简单用数值大小评价文库优劣。TSS enrichment 也依赖所用注释、窗口和背景归一化定义。<a href="https://www.encodeproject.org/data-standards/terms/">ENCODE 质控术语</a></p><p>ENCODE 给出了特定数据标准下的深度、FRiP、TSS 及重复一致性要求，可以作为参照，而不是所有样本的通用裁决器。组织、协议、参考注释及分析口径不同，阈值的适用性需要另行评估；既不能把边界值当万能通行证，也不能任意降低标准来保住想要的结果。</p><h2 id="六、假设异常样本，怎样处理才有依据"><a href="#六、假设异常样本，怎样处理才有依据" class="headerlink" title="六、假设异常样本，怎样处理才有依据"></a>六、假设异常样本，怎样处理才有依据</h2><p>设想一个处理组重复线粒体比例偏高、TSS 曲线平坦，同时与同组另外两个样本不一致。第一步不是删掉它，而是查配对关系、参考命名、实验记录和过滤日志。可以先排除文件错配、文库标识错置或参数差异。若问题仍在，记录哪些证据支持其质量不足，再按事先约定的规则作决定。</p><p>不能因为它使差异峰变少而删除，也不能只因 PCA 上距离远就断言它失败。如果剔除后某组不再有可用生物学重复，或者剩余样本令条件与批次完全混杂，就应停止该差异比较。还能保留描述性质控和探索图，但不要发布伪装成可靠推断的显著峰列表。</p><h2 id="七、这一篇应留下什么"><a href="#七、这一篇应留下什么" class="headerlink" title="七、这一篇应留下什么"></a>七、这一篇应留下什么</h2><p>第一篇的输出不是峰，而是可审查的分析起点：输入文件清单、独立样本元数据、参考包清单、质控口径表和预先记录的继续／暂停条件。也应写下尚不知道的事项，例如供体关系未确认、某样本建库日期缺失，而不是用猜测填满表格。</p><p>进入下一篇前，自问：我能解释生物学重复与 lane 的区别吗？每个样本的 R1&#x2F;R2 是否对应？参考文件是否采用同一坐标？各组是否有足够的可用独立样本？批次是否允许区分条件效应？FRiP 和 TSS 的定义是否已记录？如果任一关键问题未解决，先补证据，再运行更复杂的统计。</p><p>下一篇：<a href="/posts/atac2602/">从 FASTQ 到可审计的峰集合</a>。最后一篇会把“统一区域、统一计数、明确模型”连起来：<a href="/posts/atac2603/">差异可及性不是比较峰数量</a>。</p>]]></content>
    
    
    <summary type="html">面向 bulk paired-end ATAC-seq，先理清生物学重复、参考文件、批次与质控口径，再决定数据能否进入差异分析。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="生物信息学" scheme="https://perry.apay.eu.cc/tags/%E7%94%9F%E7%89%A9%E4%BF%A1%E6%81%AF%E5%AD%A6/"/>
    
    <category term="ATAC-seq" scheme="https://perry.apay.eu.cc/tags/ATAC-seq/"/>
    
    <category term="实验设计" scheme="https://perry.apay.eu.cc/tags/%E5%AE%9E%E9%AA%8C%E8%AE%BE%E8%AE%A1/"/>
    
    <category term="质量控制" scheme="https://perry.apay.eu.cc/tags/%E8%B4%A8%E9%87%8F%E6%8E%A7%E5%88%B6/"/>
    
  </entry>
  
  <entry>
    <title>生信方法入门：把 RNA-seq 学习项目整理成可以复查的目录</title>
    <link href="https://perry.apay.eu.cc/posts/learn2603/"/>
    <id>https://perry.apay.eu.cc/posts/learn2603/</id>
    <published>2026-10-06T09:10:03.000Z</published>
    <updated>2026-10-06T09:10:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>生信分析中的困扰不总是“软件不会用”。几周后重新打开项目，可能已经记不清哪张表是输入、哪次运行修改了参数、某个样本为什么被排除。目录结构不能替代方法学，但能为查清这些问题提供基础。</p><p>这篇只搭建一个教学项目骨架，用六个虚构样本和三行人工计数练习元数据检查与日志记录。不下载真实 FASTQ，不调用比对或差异分析软件，不生成任何可用于解释真实生物学问题的结论。</p><h2 id="1-将来源、决定和结果分开"><a href="#1-将来源、决定和结果分开" class="headerlink" title="1. 将来源、决定和结果分开"></a>1. 将来源、决定和结果分开</h2><p>建议把原始输入、样本信息、配置、脚本、结果与日志分别保存。目录名字本身没有统一标准，关键是每一层职责清楚，其他人可以看懂。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">project/</span><br><span class="line">  inputs/raw/        真实输入的存放位置；本练习留空</span><br><span class="line">  references/        参考文件与版本说明；本练习留空</span><br><span class="line">  metadata/          样本表</span><br><span class="line">  config/            参数与处理约定</span><br><span class="line">  workflow/          正式脚本及执行入口</span><br><span class="line">  results/           可重新生成的产物</span><br><span class="line">  logs/              运行与检查记录</span><br><span class="line">  env/               运行环境信息</span><br></pre></td></tr></table></figure><p>不要把未处理文件和经过过滤的文件都叫作 <code>final.csv</code>。也不要一边改输入，一边重跑同一个结果目录，却没有记录输入何时发生变化。</p><p>元数据是输入的一部分，不是可有可无的备注。至少需要稳定的样本 ID、实验条件、个体或生物学重复关系；视实验设计还需要批次、组织、采样时间、建库协议及其他协变量。真实患者信息不应直接写进公开仓库，匿名化标识也需要按适用的数据管理要求维护映射。</p><h2 id="2-完整示例：创建全新的教学项目"><a href="#2-完整示例：创建全新的教学项目" class="headerlink" title="2. 完整示例：创建全新的教学项目"></a>2. 完整示例：创建全新的教学项目</h2><p>代码只使用 Python 标准库。保存为 <code>create_learning_project.py</code>，执行 <code>python create_learning_project.py</code>。程序使用 <code>tempfile</code> 创建新目录，所有写入都位于该目录；结束后保留文件供检查，不自动删除或访问现有项目。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> platform</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> tempfile</span><br><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime, timezone</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line">root = Path(tempfile.mkdtemp(prefix=<span class="string">&quot;perry-rnaseq-&quot;</span>))</span><br><span class="line"><span class="keyword">for</span> relative <span class="keyword">in</span> [<span class="string">&quot;inputs/raw&quot;</span>, <span class="string">&quot;references&quot;</span>, <span class="string">&quot;metadata&quot;</span>, <span class="string">&quot;config&quot;</span>, <span class="string">&quot;workflow&quot;</span>,</span><br><span class="line">                 <span class="string">&quot;results&quot;</span>, <span class="string">&quot;logs&quot;</span>, <span class="string">&quot;env&quot;</span>]:</span><br><span class="line">    (root / relative).mkdir(parents=<span class="literal">True</span>, exist_ok=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">samples = [</span><br><span class="line">    &#123;<span class="string">&quot;sample_id&quot;</span>: <span class="string">&quot;C1&quot;</span>, <span class="string">&quot;subject_id&quot;</span>: <span class="string">&quot;demo_01&quot;</span>, <span class="string">&quot;condition&quot;</span>: <span class="string">&quot;control&quot;</span>, <span class="string">&quot;batch&quot;</span>: <span class="string">&quot;B1&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;sample_id&quot;</span>: <span class="string">&quot;C2&quot;</span>, <span class="string">&quot;subject_id&quot;</span>: <span class="string">&quot;demo_02&quot;</span>, <span class="string">&quot;condition&quot;</span>: <span class="string">&quot;control&quot;</span>, <span class="string">&quot;batch&quot;</span>: <span class="string">&quot;B1&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;sample_id&quot;</span>: <span class="string">&quot;C3&quot;</span>, <span class="string">&quot;subject_id&quot;</span>: <span class="string">&quot;demo_03&quot;</span>, <span class="string">&quot;condition&quot;</span>: <span class="string">&quot;control&quot;</span>, <span class="string">&quot;batch&quot;</span>: <span class="string">&quot;B1&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;sample_id&quot;</span>: <span class="string">&quot;T1&quot;</span>, <span class="string">&quot;subject_id&quot;</span>: <span class="string">&quot;demo_04&quot;</span>, <span class="string">&quot;condition&quot;</span>: <span class="string">&quot;treated&quot;</span>, <span class="string">&quot;batch&quot;</span>: <span class="string">&quot;B1&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;sample_id&quot;</span>: <span class="string">&quot;T2&quot;</span>, <span class="string">&quot;subject_id&quot;</span>: <span class="string">&quot;demo_05&quot;</span>, <span class="string">&quot;condition&quot;</span>: <span class="string">&quot;treated&quot;</span>, <span class="string">&quot;batch&quot;</span>: <span class="string">&quot;B1&quot;</span>&#125;,</span><br><span class="line">    &#123;<span class="string">&quot;sample_id&quot;</span>: <span class="string">&quot;T3&quot;</span>, <span class="string">&quot;subject_id&quot;</span>: <span class="string">&quot;demo_06&quot;</span>, <span class="string">&quot;condition&quot;</span>: <span class="string">&quot;treated&quot;</span>, <span class="string">&quot;batch&quot;</span>: <span class="string">&quot;B1&quot;</span>&#125;,</span><br><span class="line">]</span><br><span class="line">sample_ids = [record[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> record <span class="keyword">in</span> samples]</span><br><span class="line">metadata_path = root / <span class="string">&quot;metadata/samples.tsv&quot;</span></span><br><span class="line"><span class="keyword">with</span> metadata_path.<span class="built_in">open</span>(<span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    writer = csv.DictWriter(handle, fieldnames=<span class="built_in">list</span>(samples[<span class="number">0</span>]), delimiter=<span class="string">&quot;\t&quot;</span>)</span><br><span class="line">    writer.writeheader()</span><br><span class="line">    writer.writerows(samples)</span><br><span class="line"></span><br><span class="line">counts_path = root / <span class="string">&quot;inputs/toy_counts.tsv&quot;</span></span><br><span class="line">rows = [</span><br><span class="line">    [<span class="string">&quot;teaching_gene_A&quot;</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">20</span>, <span class="number">21</span>, <span class="number">22</span>],</span><br><span class="line">    [<span class="string">&quot;teaching_gene_B&quot;</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">4</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">6</span>],</span><br><span class="line">    [<span class="string">&quot;teaching_gene_C&quot;</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>],</span><br><span class="line">]</span><br><span class="line"><span class="keyword">with</span> counts_path.<span class="built_in">open</span>(<span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    writer = csv.writer(handle, delimiter=<span class="string">&quot;\t&quot;</span>)</span><br><span class="line">    writer.writerow([<span class="string">&quot;gene_id&quot;</span>, *sample_ids])</span><br><span class="line">    writer.writerows(rows)</span><br><span class="line"></span><br><span class="line">config = &#123;</span><br><span class="line">    <span class="string">&quot;schema_version&quot;</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="string">&quot;data_kind&quot;</span>: <span class="string">&quot;artificial_teaching_counts&quot;</span>,</span><br><span class="line">    <span class="string">&quot;metadata&quot;</span>: <span class="string">&quot;metadata/samples.tsv&quot;</span>,</span><br><span class="line">    <span class="string">&quot;counts&quot;</span>: <span class="string">&quot;inputs/toy_counts.tsv&quot;</span>,</span><br><span class="line">    <span class="string">&quot;reference_release&quot;</span>: <span class="literal">None</span>,</span><br><span class="line">    <span class="string">&quot;strandedness&quot;</span>: <span class="string">&quot;not_applicable_to_this_toy_table&quot;</span>,</span><br><span class="line">    <span class="string">&quot;analysis&quot;</span>: <span class="string">&quot;column_totals_only; no differential testing&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">(root / <span class="string">&quot;config/project.json&quot;</span>).write_text(</span><br><span class="line">    json.dumps(config, indent=<span class="number">2</span>, ensure_ascii=<span class="literal">False</span>) + <span class="string">&quot;\n&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Read the files back: validation must check written input, not only Python variables.</span></span><br><span class="line"><span class="keyword">with</span> metadata_path.<span class="built_in">open</span>(encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    records = <span class="built_in">list</span>(csv.DictReader(handle, delimiter=<span class="string">&quot;\t&quot;</span>))</span><br><span class="line"><span class="keyword">with</span> counts_path.<span class="built_in">open</span>(encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    table = <span class="built_in">list</span>(csv.reader(handle, delimiter=<span class="string">&quot;\t&quot;</span>))</span><br><span class="line"></span><br><span class="line">metadata_ids = [record[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> record <span class="keyword">in</span> records]</span><br><span class="line">count_ids = table[<span class="number">0</span>][<span class="number">1</span>:]</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(<span class="built_in">set</span>(metadata_ids)) == <span class="built_in">len</span>(metadata_ids), <span class="string">&quot;Duplicate sample ID&quot;</span></span><br><span class="line"><span class="keyword">assert</span> metadata_ids == count_ids, <span class="string">&quot;Metadata and count columns differ in order&quot;</span></span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">all</span>(record[<span class="string">&quot;condition&quot;</span>] <span class="keyword">in</span> &#123;<span class="string">&quot;control&quot;</span>, <span class="string">&quot;treated&quot;</span>&#125; <span class="keyword">for</span> record <span class="keyword">in</span> records)</span><br><span class="line">gene_ids = [row[<span class="number">0</span>] <span class="keyword">for</span> row <span class="keyword">in</span> table[<span class="number">1</span>:]]</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(<span class="built_in">set</span>(gene_ids)) == <span class="built_in">len</span>(gene_ids), <span class="string">&quot;Duplicate gene ID&quot;</span></span><br><span class="line">totals = [<span class="number">0</span>] * <span class="built_in">len</span>(count_ids)</span><br><span class="line"><span class="keyword">for</span> row <span class="keyword">in</span> table[<span class="number">1</span>:]:</span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">len</span>(row) == <span class="built_in">len</span>(count_ids) + <span class="number">1</span>, <span class="string">&quot;Wrong number of columns&quot;</span></span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">all</span>(value.isdecimal() <span class="keyword">for</span> value <span class="keyword">in</span> row[<span class="number">1</span>:]), <span class="string">&quot;Toy counts must be non-negative integers&quot;</span></span><br><span class="line">    <span class="keyword">for</span> index, value <span class="keyword">in</span> <span class="built_in">enumerate</span>(row[<span class="number">1</span>:]):</span><br><span class="line">        totals[index] += <span class="built_in">int</span>(value)</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> (root / <span class="string">&quot;results/toy_column_totals.tsv&quot;</span>).<span class="built_in">open</span>(<span class="string">&quot;w&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    writer = csv.writer(handle, delimiter=<span class="string">&quot;\t&quot;</span>)</span><br><span class="line">    writer.writerow([<span class="string">&quot;sample_id&quot;</span>, <span class="string">&quot;toy_column_total&quot;</span>])</span><br><span class="line">    writer.writerows(<span class="built_in">zip</span>(count_ids, totals))</span><br><span class="line"></span><br><span class="line">manifest = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> relative <span class="keyword">in</span> [<span class="string">&quot;metadata/samples.tsv&quot;</span>, <span class="string">&quot;inputs/toy_counts.tsv&quot;</span>, <span class="string">&quot;config/project.json&quot;</span>]:</span><br><span class="line">    manifest[relative] = hashlib.sha256((root / relative).read_bytes()).hexdigest()</span><br><span class="line">(root / <span class="string">&quot;config/input_sha256.json&quot;</span>).write_text(</span><br><span class="line">    json.dumps(manifest, indent=<span class="number">2</span>) + <span class="string">&quot;\n&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span></span><br><span class="line">)</span><br><span class="line">environment = &#123;<span class="string">&quot;python&quot;</span>: sys.version, <span class="string">&quot;platform&quot;</span>: platform.platform()&#125;</span><br><span class="line">(root / <span class="string">&quot;env/python_environment.json&quot;</span>).write_text(</span><br><span class="line">    json.dumps(environment, indent=<span class="number">2</span>) + <span class="string">&quot;\n&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span></span><br><span class="line">)</span><br><span class="line">log = &#123;</span><br><span class="line">    <span class="string">&quot;recorded_at_utc&quot;</span>: datetime.now(timezone.utc).isoformat(),</span><br><span class="line">    <span class="string">&quot;status&quot;</span>: <span class="string">&quot;PASS&quot;</span>,</span><br><span class="line">    <span class="string">&quot;samples&quot;</span>: <span class="built_in">len</span>(count_ids),</span><br><span class="line">    <span class="string">&quot;genes&quot;</span>: <span class="built_in">len</span>(gene_ids),</span><br><span class="line">    <span class="string">&quot;analysis&quot;</span>: <span class="string">&quot;teaching input validation and column totals only&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">(root / <span class="string">&quot;logs/check.json&quot;</span>).write_text(json.dumps(log, indent=<span class="number">2</span>) + <span class="string">&quot;\n&quot;</span>, encoding=<span class="string">&quot;utf-8&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Project:&quot;</span>, root)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;PASS: samples=6 genes=3&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;Toy column totals:&quot;</span>, <span class="built_in">dict</span>(<span class="built_in">zip</span>(count_ids, totals)))</span><br></pre></td></tr></table></figure><p><code>assert</code> 在这里用于小练习的内部检查。运行时不要使用 Python 的 <code>-O</code> 优化选项，否则这些检查会被移除；正式流水线可以改成显式条件与异常，让校验成为不可跳过的执行步骤。</p><h2 id="3-核对几个真正有意义的结果"><a href="#3-核对几个真正有意义的结果" class="headerlink" title="3. 核对几个真正有意义的结果"></a>3. 核对几个真正有意义的结果</h2><p>程序应给出六个样本、三个教学基因，列和依次为 C1&#x3D;16、C2&#x3D;19、C3&#x3D;19、T1&#x3D;28、T2&#x3D;31、T3&#x3D;31。它们只是人工整数的算术结果，不是测序深度质量判断，也不是差异表达证据。</p><p>打开 <code>metadata/samples.tsv</code> 与 <code>inputs/toy_counts.tsv</code>，确认样本顺序一致。真实工作中，一张表按照字母排序，另一张保持原始顺序，如果直接按位置拼接，可能悄悄把标签分配给错误样本。应按样本 ID 对齐并检查，不要只比较列数。</p><p>还要打开 <code>logs/check.json</code>，检查状态是否为 PASS；打开 <code>config/project.json</code>，确认它明确说明“教学计数、只求列和”。这能避免后来的人将演示产物误认为完成了真实分析。</p><p>SHA-256 用于检查记录的文件内容是否发生变化。它不证明数据来源可信，也不证明分析正确；如果文件变化，就需要说明为什么变化，并更新对应运行记录，而不是悄悄重写旧清单。算法接口见 <a href="https://docs.python.org/3/library/hashlib.html">Python 的 hashlib 官方文档</a>。</p><h2 id="4-目录准备好了，还缺哪些真实分析信息"><a href="#4-目录准备好了，还缺哪些真实分析信息" class="headerlink" title="4. 目录准备好了，还缺哪些真实分析信息"></a>4. 目录准备好了，还缺哪些真实分析信息</h2><p>正式 RNA-seq 流程通常还需要数据来源与授权、读长与单端&#x2F;双端信息、链特异性、质控规则、参考基因组及注释的精确版本、软件版本、线程和参数，以及计数或定量的具体方法。把这些记录在配置和环境文件中，比把软件名字写进 README 更具体。</p><p>原始整数计数与 TPM 不是可以任意互换的输入。本篇没有进行差异检验；如继续学习 DESeq2，需要按它要求的计数输入和实验设计构造对象，而不是把 TPM、VST 或 rlog 值当作原始计数交进去。基于转录本定量结果的导入也有专门流程，不能靠四舍五入 TPM 代替。详见 <a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html">DESeq2 官方教程</a>。</p><p>本例的六个 <code>subject_id</code> 都是不同的虚构个体。如果真实数据来自配对设计、重复时间点或复杂批次，样本表与统计设计都必须体现这些关系。复制一个模板不会自动得到正确的设计矩阵。</p><h2 id="5-环境文件和日志应该怎样补全"><a href="#5-环境文件和日志应该怎样补全" class="headerlink" title="5. 环境文件和日志应该怎样补全"></a>5. 环境文件和日志应该怎样补全</h2><p>示例只记录了 Python 与操作系统，不包含真实生信软件清单。以后引入依赖时，可以在独立环境中安装并记录实际版本；Python 的环境隔离可参考 <a href="https://docs.python.org/3/library/venv.html">venv 官方文档</a>。虚拟环境目录本身通常不是需要直接复制给其他人的交付物，版本、安装说明和复建方法更重要。</p><p>如果使用 R，应保留实际会话的 <code>sessionInfo()</code>；如果使用命令行工具，要记录完整命令、工作目录、起止时间、退出码，以及标准输出和错误日志。容器或锁定依赖有助于减少环境差异，但它们不能代替参考数据版本和分析参数。</p><p>将本次运行的脚本副本保存进 <code>workflow/</code>，并在自己的说明中记录执行入口。上面的校验清单只包含三个教学输入文件，没有自动跟踪后来添加的脚本；添加脚本或参考文件后，应扩展清单，而不是误以为它已经覆盖整个项目。</p><h2 id="6-常见误区与下一步练习"><a href="#6-常见误区与下一步练习" class="headerlink" title="6. 常见误区与下一步练习"></a>6. 常见误区与下一步练习</h2><p>常见误区包括：结果表只有截图没有原始表；同一个样本在不同文件里采用不同名字；把技术重复当成独立生物学重复；只记“用了最新参考基因组”；把实际失败的运行与成功结果混进同一个日志目录。</p><p>可复查还需要解释性记录：排除某个样本的原因是什么？为什么改参数？新的运行相较旧运行改变了哪些输入？应写出判断依据，不只写“重新运行后正常”。</p><p>先做一个低风险练习：只在新教学目录里，把计数表的 T1 与 T2 两列交换，再执行下面的只读校验，确认样本顺序检查能够发现变化。再把一个样本 ID 改成重复值，观察唯一性检查是否触发。不要在真实输入上做这样的破坏性测试。</p><p>将检查保存为 <code>check_saved_project.py</code>，以创建脚本打印的完整目录为唯一参数运行。不要为了检查改过的文件而重跑创建脚本：它会生成另一个新目录，而不是读取你编辑的那一个。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> csv</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(sys.argv) != <span class="number">2</span>:</span><br><span class="line">    <span class="keyword">raise</span> SystemExit(<span class="string">&quot;Usage: python check_saved_project.py PROJECT_DIRECTORY&quot;</span>)</span><br><span class="line">root = Path(sys.argv[<span class="number">1</span>]).resolve(strict=<span class="literal">True</span>)</span><br><span class="line"><span class="keyword">with</span> (root / <span class="string">&quot;metadata/samples.tsv&quot;</span>).<span class="built_in">open</span>(encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    samples = <span class="built_in">list</span>(csv.DictReader(handle, delimiter=<span class="string">&quot;\t&quot;</span>))</span><br><span class="line"><span class="keyword">with</span> (root / <span class="string">&quot;inputs/toy_counts.tsv&quot;</span>).<span class="built_in">open</span>(encoding=<span class="string">&quot;utf-8&quot;</span>, newline=<span class="string">&quot;&quot;</span>) <span class="keyword">as</span> handle:</span><br><span class="line">    counts = <span class="built_in">list</span>(csv.reader(handle, delimiter=<span class="string">&quot;\t&quot;</span>))</span><br><span class="line">ids = [sample[<span class="string">&quot;sample_id&quot;</span>] <span class="keyword">for</span> sample <span class="keyword">in</span> samples]</span><br><span class="line"><span class="keyword">assert</span> <span class="built_in">len</span>(ids) == <span class="built_in">len</span>(<span class="built_in">set</span>(ids)), <span class="string">&quot;Duplicate sample ID&quot;</span></span><br><span class="line"><span class="keyword">assert</span> ids == counts[<span class="number">0</span>][<span class="number">1</span>:], <span class="string">&quot;Sample columns do not match metadata order&quot;</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;PASS: sample IDs are unique and ordered correctly&quot;</span>)</span><br></pre></td></tr></table></figure><p>最后给项目写一份很短的 README：这是教学数据还是正式数据、如何重新生成结果、成功时应检查哪些文件、哪些步骤尚未运行。这四句话能够阻止很多误解，也为之后接入真实 RNA-seq 工作流保留清晰边界。</p><h2 id="官方参考"><a href="#官方参考" class="headerlink" title="官方参考"></a>官方参考</h2><ul><li><a href="https://bioconductor.org/packages/release/bioc/vignettes/DESeq2/inst/doc/DESeq2.html">DESeq2：计数输入、实验设计与差异表达分析</a></li><li><a href="https://docs.python.org/3/library/venv.html">Python：venv 环境隔离与创建</a></li><li><a href="https://docs.python.org/3/library/hashlib.html">Python：hashlib 与 SHA-256</a></li></ul>]]></content>
    
    
    <summary type="html">从纯教学计数表开始，分离输入与结果，检查样本元数据、记录配置及校验值，建立可复查的项目骨架。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="可复现分析" scheme="https://perry.apay.eu.cc/tags/%E5%8F%AF%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/"/>
    
    <category term="RNA-seq" scheme="https://perry.apay.eu.cc/tags/RNA-seq/"/>
    
    <category term="项目组织" scheme="https://perry.apay.eu.cc/tags/%E9%A1%B9%E7%9B%AE%E7%BB%84%E7%BB%87/"/>
    
    <category term="样本元数据" scheme="https://perry.apay.eu.cc/tags/%E6%A0%B7%E6%9C%AC%E5%85%83%E6%95%B0%E6%8D%AE/"/>
    
  </entry>
  
  <entry>
    <title>深度学习学习笔记：按个体划分数据，避免评估被信息泄漏污染</title>
    <link href="https://perry.apay.eu.cc/posts/learn2602/"/>
    <id>https://perry.apay.eu.cc/posts/learn2602/</id>
    <published>2026-10-06T09:10:02.000Z</published>
    <updated>2026-10-06T09:10:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>一个模型获得很高的测试分数，不一定说明它学会了希望学习的规律。它也可能认出了同一个人的重复记录，利用了未来信息，或者通过预处理提前看到了测试集。</p><p>在生物数据中，“一行”经常不是一个独立个体。一个人可以贡献多个时间点，一个供体可以贡献很多细胞，一个样本可以拆成许多片段。划分数据前，需要先回答：以后模型面对的新对象是什么？</p><p>这里选择的任务是泛化到未见过的个体，所以按照个体 ID 分组。示例全部使用人工生成的数字，不涉及真实受试者，不给出任何疾病预测结论；模型分数只用于检查程序能否运行。</p><h2 id="1-从评价对象倒推划分单位"><a href="#1-从评价对象倒推划分单位" class="headerlink" title="1. 从评价对象倒推划分单位"></a>1. 从评价对象倒推划分单位</h2><p>如果未来要预测新供体，同一供体的记录不应同时出现在训练集和测试集。如果要评估未来时间点，随机混合过去与未来可能不合适。如果要推广到另一家中心，还需要按中心或外部队列进行额外评估。</p><p>按个体分组解决的是一种依赖结构，不会自动解决时间泄漏、批次混杂、标签错误和中心差异。一个 ID 列也不能替代研究设计。</p><p>下面用 <code>GroupShuffleSplit</code>，它按组生成索引；参数比例对应组的比例，而不是保证相同比例的记录数。组大小不等时，两者会不同。它也不保证标签分层，所以划分后仍要检查每一部分的类别分布。见 <a href="https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupShuffleSplit.html">scikit-learn 的 GroupShuffleSplit 文档</a>。</p><h2 id="2-安装与运行边界"><a href="#2-安装与运行边界" class="headerlink" title="2. 安装与运行边界"></a>2. 安装与运行边界</h2><p>示例需要 Python、NumPy、scikit-learn 与 PyTorch，使用 CPU 和小张量，不下载数据集。下面的安装命令在自己新建的空练习目录中执行；若当前目录已有 <code>.venv</code>，不要直接覆盖它。如果已有兼容环境，也不需要重新安装。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">python -m venv .venv</span><br><span class="line"><span class="built_in">source</span> .venv/bin/activate</span><br><span class="line">python -m pip install numpy scikit-learn torch</span><br></pre></td></tr></table></figure><p>这是 Linux&#x2F;WSL 的激活写法。PyTorch 的具体安装方式会随平台变化，本文不要求 CUDA；记录你实际安装的版本，不把“相同随机种子”理解为跨设备、跨版本逐位相同。</p><p>将下一段保存为 <code>grouped_training.py</code>，执行 <code>python grouped_training.py</code>。代码只在内存中使用模拟数据，不覆盖磁盘上的模型或数据文件。</p><h2 id="3-完整代码：先分组，再拟合预处理"><a href="#3-完整代码：先分组，再拟合预处理" class="headerlink" title="3. 完整代码：先分组，再拟合预处理"></a>3. 完整代码：先分组，再拟合预处理</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> copy</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> sklearn</span><br><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">from</span> torch <span class="keyword">import</span> nn</span><br><span class="line"><span class="keyword">from</span> torch.utils.data <span class="keyword">import</span> DataLoader, TensorDataset</span><br><span class="line"><span class="keyword">from</span> sklearn.model_selection <span class="keyword">import</span> GroupShuffleSplit</span><br><span class="line"><span class="keyword">from</span> sklearn.preprocessing <span class="keyword">import</span> StandardScaler</span><br><span class="line"></span><br><span class="line">seed = <span class="number">17</span></span><br><span class="line">rng = np.random.default_rng(seed)</span><br><span class="line">torch.manual_seed(seed)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 24 synthetic people; each contributes 3 records with 6 features.</span></span><br><span class="line">groups = np.repeat(np.arange(<span class="number">24</span>), <span class="number">3</span>)</span><br><span class="line">y = (groups % <span class="number">2</span>).astype(np.int64)</span><br><span class="line">X = rng.normal(size=(<span class="built_in">len</span>(groups), <span class="number">6</span>)).astype(np.float32)</span><br><span class="line">X[:, <span class="number">0</span>] += y * <span class="number">0.8</span>  <span class="comment"># An artificial signal, not a biological relationship.</span></span><br><span class="line"></span><br><span class="line">outer = GroupShuffleSplit(n_splits=<span class="number">1</span>, test_size=<span class="number">0.25</span>, random_state=seed)</span><br><span class="line">development, test = <span class="built_in">next</span>(outer.split(X, y, groups))</span><br><span class="line">inner = GroupShuffleSplit(n_splits=<span class="number">1</span>, test_size=<span class="number">1</span> / <span class="number">3</span>, random_state=seed + <span class="number">1</span>)</span><br><span class="line">train_local, valid_local = <span class="built_in">next</span>(</span><br><span class="line">    inner.split(X[development], y[development], groups[development])</span><br><span class="line">)</span><br><span class="line">train, valid = development[train_local], development[valid_local]</span><br><span class="line"></span><br><span class="line">splits = &#123;<span class="string">&quot;train&quot;</span>: train, <span class="string">&quot;valid&quot;</span>: valid, <span class="string">&quot;test&quot;</span>: test&#125;</span><br><span class="line">group_sets = &#123;name: <span class="built_in">set</span>(groups[index]) <span class="keyword">for</span> name, index <span class="keyword">in</span> splits.items()&#125;</span><br><span class="line"><span class="keyword">assert</span> <span class="keyword">not</span> (group_sets[<span class="string">&quot;train&quot;</span>] &amp; group_sets[<span class="string">&quot;valid&quot;</span>])</span><br><span class="line"><span class="keyword">assert</span> <span class="keyword">not</span> (group_sets[<span class="string">&quot;train&quot;</span>] &amp; group_sets[<span class="string">&quot;test&quot;</span>])</span><br><span class="line"><span class="keyword">assert</span> <span class="keyword">not</span> (group_sets[<span class="string">&quot;valid&quot;</span>] &amp; group_sets[<span class="string">&quot;test&quot;</span>])</span><br><span class="line"><span class="keyword">for</span> name, index <span class="keyword">in</span> splits.items():</span><br><span class="line">    labels = np.bincount(y[index], minlength=<span class="number">2</span>).tolist()</span><br><span class="line">    <span class="built_in">print</span>(name, <span class="string">&quot;groups=&quot;</span>, <span class="built_in">len</span>(group_sets[name]), <span class="string">&quot;rows=&quot;</span>, <span class="built_in">len</span>(index), <span class="string">&quot;labels=&quot;</span>, labels)</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">min</span>(labels) == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">raise</span> ValueError(<span class="string">&quot;A split has only one class; revise the evaluation design.&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;PASS: no person occurs in more than one split&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Only training records estimate the scaler&#x27;s parameters.</span></span><br><span class="line">scaler = StandardScaler().fit(X[train])</span><br><span class="line">scaled = &#123;name: scaler.transform(X[index]).astype(np.float32)</span><br><span class="line">          <span class="keyword">for</span> name, index <span class="keyword">in</span> splits.items()&#125;</span><br><span class="line"><span class="keyword">assert</span> np.allclose(scaled[<span class="string">&quot;train&quot;</span>].mean(axis=<span class="number">0</span>), <span class="number">0</span>, atol=<span class="number">1e-5</span>)</span><br><span class="line"></span><br><span class="line">train_data = TensorDataset(torch.from_numpy(scaled[<span class="string">&quot;train&quot;</span>]), torch.from_numpy(y[train]))</span><br><span class="line">loader = DataLoader(train_data, batch_size=<span class="number">12</span>, shuffle=<span class="literal">True</span>,</span><br><span class="line">                    generator=torch.Generator().manual_seed(seed), num_workers=<span class="number">0</span>)</span><br><span class="line">model = nn.Sequential(nn.Linear(<span class="number">6</span>, <span class="number">16</span>), nn.ReLU(), nn.Dropout(<span class="number">0.2</span>), nn.Linear(<span class="number">16</span>, <span class="number">2</span>))</span><br><span class="line">optimizer = torch.optim.Adam(model.parameters(), lr=<span class="number">0.01</span>)</span><br><span class="line">criterion = nn.CrossEntropyLoss()</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">evaluate</span>(<span class="params">name</span>):</span><br><span class="line">    model.<span class="built_in">eval</span>()</span><br><span class="line">    features = torch.from_numpy(scaled[name])</span><br><span class="line">    labels = torch.from_numpy(y[splits[name]])</span><br><span class="line">    <span class="keyword">with</span> torch.no_grad():</span><br><span class="line">        logits = model(features)</span><br><span class="line">        loss = criterion(logits, labels).item()</span><br><span class="line">        accuracy = (logits.argmax(dim=<span class="number">1</span>) == labels).<span class="built_in">float</span>().mean().item()</span><br><span class="line">    <span class="keyword">return</span> loss, accuracy</span><br><span class="line"></span><br><span class="line">best_loss, best_state, best_epoch = <span class="built_in">float</span>(<span class="string">&quot;inf&quot;</span>), <span class="literal">None</span>, <span class="literal">None</span></span><br><span class="line"><span class="keyword">for</span> epoch <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1</span>, <span class="number">41</span>):</span><br><span class="line">    model.train()</span><br><span class="line">    <span class="keyword">for</span> features, labels <span class="keyword">in</span> loader:</span><br><span class="line">        optimizer.zero_grad()</span><br><span class="line">        loss = criterion(model(features), labels)</span><br><span class="line">        loss.backward()</span><br><span class="line">        optimizer.step()</span><br><span class="line">    valid_loss, _ = evaluate(<span class="string">&quot;valid&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> valid_loss &lt; best_loss:</span><br><span class="line">        best_loss = valid_loss</span><br><span class="line">        best_state = copy.deepcopy(model.state_dict())</span><br><span class="line">        best_epoch = epoch</span><br><span class="line"></span><br><span class="line"><span class="keyword">assert</span> best_state <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">model.load_state_dict(best_state)</span><br><span class="line">test_loss, test_accuracy = evaluate(<span class="string">&quot;test&quot;</span>)  <span class="comment"># Final test evaluation: once.</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;selected_epoch=&quot;</span>, best_epoch)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;test_loss=<span class="subst">&#123;test_loss:<span class="number">.4</span>f&#125;</span> test_accuracy=<span class="subst">&#123;test_accuracy:<span class="number">.4</span>f&#125;</span>&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;versions:&quot;</span>, <span class="string">&quot;numpy&quot;</span>, np.__version__, <span class="string">&quot;sklearn&quot;</span>, sklearn.__version__, <span class="string">&quot;torch&quot;</span>, torch.__version__)</span><br></pre></td></tr></table></figure><p>此处标准化只是演示连续特征的预处理边界，不是建议把所有 RNA-seq 计数直接交给这个网络。真实数据的表示方式、批次处理及任务标签需要单独确定。</p><h2 id="4-应该检查什么输出"><a href="#4-应该检查什么输出" class="headerlink" title="4. 应该检查什么输出"></a>4. 应该检查什么输出</h2><p>这段数据设计固定了每个个体三行，所以三部分的规模应是：训练 12 个个体、36 行；验证 6 个个体、18 行；测试 6 个个体、18 行。还应打印 <code>PASS: no person occurs in more than one split</code>。</p><p>类别计数、选中轮次、测试损失与准确率由实际运行产生。本文不预设一个漂亮分数，也不把人工数据的表现解释为真实任务中的性能。改变平台或依赖版本后，训练结果可能变化；分组不重叠和只在训练集拟合标准化的原则不应改变。</p><p><code>state_dict()</code> 中的张量可能继续随训练变化，因此这里对最佳状态做深拷贝，而不是仅保存一个引用。验证集负责选轮次，最终测试集没有进入这条选择过程。</p><p>评估时同时使用 <code>model.eval()</code> 和不计算梯度的上下文：前者调整 Dropout 等模块行为，后者避免构建训练用的梯度图。训练模式与评估模式的基本用法见 <a href="https://docs.pytorch.org/tutorials/beginner/basics/optimization_tutorial.html">PyTorch 的参数优化教程</a>。</p><h2 id="5-泄漏不只发生在划分这一行"><a href="#5-泄漏不只发生在划分这一行" class="headerlink" title="5. 泄漏不只发生在划分这一行"></a>5. 泄漏不只发生在划分这一行</h2><p>先对全部样本计算均值、筛选特征，再拆训练集，会让测试数据影响处理规则。正确顺序是先划分，在训练数据上拟合，再把同一个变换应用于验证与测试；交叉验证中的每一折也必须重新拟合。参见 <a href="https://scikit-learn.org/stable/common_pitfalls.html#data-leakage">scikit-learn 对数据泄漏的说明</a>。</p><p>同样危险的操作还有：用结局发生后的变量预测结局；将同一患者的记录通过不同 ID 引入不同集合；反复查看测试集后挑学习率；在全部数据上做有监督特征筛选；把数据增强产生的近重复样本分到两边。</p><p>这里只计算记录级准确率。一个个体有多条记录时，不应把这些记录当成相互独立的个体来估计不确定性。是否先聚合个体预测、如何按个体重采样，以及需要什么指标，取决于应用目标和研究设计。</p><p>准确率也不是唯一尺度。类别不平衡时，需要查看混淆矩阵、各类召回等；若使用概率，则还应考虑概率校准。阈值的选择需要发生在开发阶段，不能观察最终测试标签后再调整。</p><h2 id="6-练习：审计流程，不追求更高的数字"><a href="#6-练习：审计流程，不追求更高的数字" class="headerlink" title="6. 练习：审计流程，不追求更高的数字"></a>6. 练习：审计流程，不追求更高的数字</h2><p>第一项练习是将三个集合的个体 ID 打印出来，手工核对交集为空。第二项是改成每人不同数量的记录，观察“组比例”和“行比例”如何分离。</p><p>第三项是检查一份自己的模型笔记：哪些步骤调用了 <code>fit</code>？这些步骤看到的是哪个集合？如果更改预处理、模型结构或训练轮次，是否又查看了测试集？把答案写成表格，比只记录最后一个分数更能说明实验边界。</p><p>不要为了让测试分数上升，反复随机划分直到遇到好看的结果。若需要评估划分敏感性，应预先确定重复划分或分组交叉验证方案，并按方案完整报告，而不是筛选最好的一次。</p><h2 id="官方参考"><a href="#官方参考" class="headerlink" title="官方参考"></a>官方参考</h2><ul><li><a href="https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupShuffleSplit.html">scikit-learn：GroupShuffleSplit 分组划分</a></li><li><a href="https://scikit-learn.org/stable/common_pitfalls.html#data-leakage">scikit-learn：预处理与数据泄漏</a></li><li><a href="https://docs.pytorch.org/tutorials/beginner/basics/optimization_tutorial.html">PyTorch：训练循环与评估模式</a></li></ul>]]></content>
    
    
    <summary type="html">用纯模拟数据搭建按个体分组的训练、验证和测试流程，检查预处理边界，并用验证集选择模型。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="模型评估" scheme="https://perry.apay.eu.cc/tags/%E6%A8%A1%E5%9E%8B%E8%AF%84%E4%BC%B0/"/>
    
    <category term="数据划分" scheme="https://perry.apay.eu.cc/tags/%E6%95%B0%E6%8D%AE%E5%88%92%E5%88%86/"/>
    
    <category term="PyTorch" scheme="https://perry.apay.eu.cc/tags/PyTorch/"/>
    
    <category term="数据泄漏" scheme="https://perry.apay.eu.cc/tags/%E6%95%B0%E6%8D%AE%E6%B3%84%E6%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Linux 脚本安全实践：让一个小脚本可检查、可失败、可重跑</title>
    <link href="https://perry.apay.eu.cc/posts/learn2601/"/>
    <id>https://perry.apay.eu.cc/posts/learn2601/</id>
    <published>2026-10-06T09:10:01.000Z</published>
    <updated>2026-10-06T09:10:01.000Z</updated>
    
    <content type="html"><![CDATA[<p>一个好脚本不只是“这次运行成功”。它还应该告诉我们：处理了什么输入、在哪里写了结果、遇到异常是否停下，以及第二次运行会不会覆盖第一次的文件。</p><p>这篇从一个非常小的计数任务开始。脚本只在自己刚创建的临时目录里生成三行教学数据，不读取真实测序文件，不移动用户文件，也不自动删除任何目录。先建立这样的安全边界，再把思路迁移到正式工作流。</p><h2 id="1-先约定脚本的职责"><a href="#1-先约定脚本的职责" class="headerlink" title="1. 先约定脚本的职责"></a>1. 先约定脚本的职责</h2><p>我们的任务是读取两列教学 TSV：第一列样本名，第二列非负整数。结果包括有效样本数和计数之和。</p><p>输入范围有意很窄：样本名只能包含英文字母、数字、下划线或短横线；计数是十进制整数；两列都不能为空。它不是通用 CSV 解析器，也不是对真实表达矩阵进行生物学分析。</p><p>如果以后需要处理带引号、空字段、复杂注释或数十万列的数据，应该使用 Python&#x2F;R 的表格读取工具，不能假定一个 <code>read</code> 循环可以处理所有格式。</p><p>正式任务还需要补充三个问题：如果某个样本失败，其他样本是否继续？哪些文件可以覆盖？结果怎么判定完整？这些属于分析策略，应当在写循环之前决定，而不是等错误发生后临时猜测。</p><h2 id="2-四个值得保留的习惯"><a href="#2-四个值得保留的习惯" class="headerlink" title="2. 四个值得保留的习惯"></a>2. 四个值得保留的习惯</h2><p>路径变量通常放在双引号里，避免空格被拆成多个参数。需要把字符串原样写出时，使用 <code>printf &#39;%s\n&#39; &quot;$value&quot;</code>，让格式串与数据分开。双引号与未引用变量的行为差别可查阅 <a href="https://www.gnu.org/software/bash/manual/html_node/Quoting.html">Bash 的引用规则</a>。</p><p>运行前明确输出目录。这里用 <code>mktemp -d</code> 创建新的目录，避免把“已有工作目录”误当成“本次练习目录”；不要用 <code>mktemp -u</code> 只猜一个名字。相关创建语义见 <a href="https://www.gnu.org/s/coreutils/manual/html_node/mktemp-invocation.html">GNU Coreutils 的 mktemp 文档</a>。</p><p>对预期内的失败显式处理，例如文件不存在或字段非法。对意外失败保留退出码和行号。只有“看到错误文字”不够：上游流程通常依据退出码决定是否继续。</p><p>尽量让每次运行产生独立结果。调试时保留失败现场，往往比写一个自动清理所有东西的退出钩子更有帮助。下面的程序结束后会打印练习目录，供你核对；它不会替你清理。</p><h2 id="3-完整练习：只在新的临时目录里工作"><a href="#3-完整练习：只在新的临时目录里工作" class="headerlink" title="3. 完整练习：只在新的临时目录里工作"></a>3. 完整练习：只在新的临时目录里工作</h2><p>需要 Linux 或 WSL 中的 Bash 与 GNU Coreutils。请用编辑器将代码保存为 <code>safe_counts.sh</code>；保留 LF 换行，再执行 <code>bash safe_counts.sh</code>。不要把 <code>bash</code> 换成 <code>sh</code>：示例使用了 Bash 专属语法。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="built_in">set</span> -Eeuo pipefail</span><br><span class="line"><span class="built_in">umask</span> 077</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">on_error</span></span>() &#123;</span><br><span class="line">  <span class="built_in">local</span> status=$?</span><br><span class="line">  <span class="built_in">printf</span> <span class="string">&#x27;ERROR: status=%s line=%s\n&#x27;</span> <span class="string">&quot;<span class="variable">$status</span>&quot;</span> <span class="string">&quot;<span class="variable">$1</span>&quot;</span> &gt;&amp;2</span><br><span class="line">  <span class="built_in">exit</span> <span class="string">&quot;<span class="variable">$status</span>&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">trap</span> <span class="string">&#x27;on_error &quot;$LINENO&quot;&#x27;</span> ERR</span><br><span class="line"></span><br><span class="line">task_dir=$(<span class="built_in">mktemp</span> -d --tmpdir perry-shell.XXXXXX)</span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;Practice directory: %s\n&#x27;</span> <span class="string">&quot;<span class="variable">$task_dir</span>&quot;</span></span><br><span class="line"><span class="built_in">cd</span> -- <span class="string">&quot;<span class="variable">$task_dir</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">mkdir</span> -- <span class="string">&#x27;input files&#x27;</span> <span class="string">&#x27;results&#x27;</span> <span class="string">&#x27;logs&#x27;</span></span><br><span class="line">input_file=<span class="string">&quot;<span class="variable">$task_dir</span>/input files/counts.tsv&quot;</span></span><br><span class="line">result_file=<span class="string">&quot;<span class="variable">$task_dir</span>/results/summary.tsv&quot;</span></span><br><span class="line">log_file=<span class="string">&quot;<span class="variable">$task_dir</span>/logs/run.log&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;sample\tcount\nS1\t12\nS2\t0\nS3\t7\n&#x27;</span> &gt; <span class="string">&quot;<span class="variable">$input_file</span>&quot;</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;START\nworkdir=%s\nbash=%s\n&#x27;</span> <span class="string">&quot;<span class="variable">$task_dir</span>&quot;</span> <span class="string">&quot;<span class="variable">$BASH_VERSION</span>&quot;</span> &gt; <span class="string">&quot;<span class="variable">$log_file</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [[ ! -s <span class="string">&quot;<span class="variable">$input_file</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">printf</span> <span class="string">&#x27;Input is missing or empty: %s\n&#x27;</span> <span class="string">&quot;<span class="variable">$input_file</span>&quot;</span> &gt;&amp;2</span><br><span class="line">  <span class="built_in">exit</span> 2</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">sample_count=0</span><br><span class="line">total=0</span><br><span class="line">line_number=0</span><br><span class="line">record_pattern=$<span class="string">&#x27;^([A-Za-z0-9_-]+)\t([0-9]&#123;1,6&#125;)$&#x27;</span></span><br><span class="line"><span class="keyword">while</span> IFS= <span class="built_in">read</span> -r record || [[ -n <span class="string">&quot;<span class="variable">$&#123;record:-&#125;</span>&quot;</span> ]]; <span class="keyword">do</span></span><br><span class="line">  line_number=$((line_number + <span class="number">1</span>))</span><br><span class="line">  <span class="keyword">if</span> [[ <span class="string">&quot;<span class="variable">$line_number</span>&quot;</span> -eq 1 ]]; <span class="keyword">then</span></span><br><span class="line">    <span class="keyword">if</span> [[ <span class="string">&quot;<span class="variable">$record</span>&quot;</span> != $<span class="string">&#x27;sample\tcount&#x27;</span> ]]; <span class="keyword">then</span></span><br><span class="line">      <span class="built_in">printf</span> <span class="string">&#x27;Unexpected header\n&#x27;</span> &gt;&amp;2</span><br><span class="line">      <span class="built_in">exit</span> 2</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">    <span class="built_in">continue</span></span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">  <span class="keyword">if</span> [[ ! <span class="string">&quot;<span class="variable">$record</span>&quot;</span> =~ <span class="variable">$record_pattern</span> ]]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">printf</span> <span class="string">&#x27;Invalid teaching record at line %s\n&#x27;</span> <span class="string">&quot;<span class="variable">$line_number</span>&quot;</span> &gt;&amp;2</span><br><span class="line">    <span class="built_in">exit</span> 2</span><br><span class="line">  <span class="keyword">fi</span></span><br><span class="line">  sample=<span class="variable">$&#123;BASH_REMATCH[1]&#125;</span></span><br><span class="line">  count=<span class="variable">$&#123;BASH_REMATCH[2]&#125;</span></span><br><span class="line">  <span class="comment"># 10# makes zero-padded input decimal, not an octal literal.</span></span><br><span class="line">  value=$((<span class="number">10</span>#<span class="variable">$count</span>))</span><br><span class="line">  sample_count=$((sample_count + <span class="number">1</span>))</span><br><span class="line">  total=$((total + value))</span><br><span class="line"><span class="keyword">done</span> &lt; <span class="string">&quot;<span class="variable">$input_file</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [[ <span class="string">&quot;<span class="variable">$sample_count</span>&quot;</span> -eq 0 ]]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">printf</span> <span class="string">&#x27;No data records\n&#x27;</span> &gt;&amp;2</span><br><span class="line">  <span class="built_in">exit</span> 2</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;samples\ttotal\n%s\t%s\n&#x27;</span> <span class="string">&quot;<span class="variable">$sample_count</span>&quot;</span> <span class="string">&quot;<span class="variable">$total</span>&quot;</span> &gt; <span class="string">&quot;<span class="variable">$result_file</span>&quot;</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;SUCCESS\nsamples=%s\ntotal=%s\n&#x27;</span> <span class="string">&quot;<span class="variable">$sample_count</span>&quot;</span> <span class="string">&quot;<span class="variable">$total</span>&quot;</span> &gt;&gt; <span class="string">&quot;<span class="variable">$log_file</span>&quot;</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;CHECK: samples=%s total=%s\n&#x27;</span> <span class="string">&quot;<span class="variable">$sample_count</span>&quot;</span> <span class="string">&quot;<span class="variable">$total</span>&quot;</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;Result: %s\nLog: %s\n&#x27;</span> <span class="string">&quot;<span class="variable">$result_file</span>&quot;</span> <span class="string">&quot;<span class="variable">$log_file</span>&quot;</span></span><br><span class="line"><span class="built_in">printf</span> <span class="string">&#x27;Practice files are retained; no automatic deletion was performed.\n&#x27;</span></span><br></pre></td></tr></table></figure><p>它特意创建了含空格的 <code>input files</code> 目录。如果去掉路径变量外侧的双引号，程序就可能把一个路径拆成两个参数。这种小范围练习比直接拿真实项目试错更容易理解引用规则。</p><p>读取时先保留整行，再用固定的正则约定两列，避免把连续制表符错误地压成一个字段分隔符。每条计数最多六位数字，只为避免把任意长的字符串直接送入 Bash 整数运算；shell 算术不是无限精度计算器。读取条件还保留了文件末尾没有换行时的非空记录，避免最后一行被悄悄漏掉。</p><h2 id="4-检查输出，而不是只看终端有没有报错"><a href="#4-检查输出，而不是只看终端有没有报错" class="headerlink" title="4. 检查输出，而不是只看终端有没有报错"></a>4. 检查输出，而不是只看终端有没有报错</h2><p>临时目录尾部的随机字符每次不同，但关键检查行应为：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CHECK: samples=3 total=19</span><br></pre></td></tr></table></figure><p>将终端打印的完整 <code>Result</code> 路径复制给 <code>sed -n &#39;1,5p&#39;</code>，应看到下面两行。路径需要放在双引号里；不要凭记忆拼接另一个临时目录。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">samples  total</span><br><span class="line">3        19</span><br></pre></td></tr></table></figure><p>上面的展示用空格方便阅读，文件实际用制表符分列。日志结尾应该出现 <code>SUCCESS</code>、<code>samples=3</code> 与 <code>total=19</code>。有结果文件但没有成功标记时，不应把它默认为完整产物。</p><p>更重要的是检查输入：三条记录是否正是 S1、S2、S3？有没有意外多读了一行？本例只累计计数，不检测重复样本名；正式任务需要额外验证唯一性。明确知道程序尚未检查什么，本身就是安全实践的一部分。</p><h2 id="5-set-e-与-pipefail-不是万能保险"><a href="#5-set-e-与-pipefail-不是万能保险" class="headerlink" title="5. set -e 与 pipefail 不是万能保险"></a>5. <code>set -e</code> 与 <code>pipefail</code> 不是万能保险</h2><p><code>-u</code> 帮助发现未设置变量；<code>pipefail</code> 让管道中较早命令的非零退出也能影响整个管道状态；<code>-e</code> 在部分失败场景中终止脚本。但 <code>if</code> 条件、某些 <code>&amp;&amp;</code>&#x2F;<code>||</code> 链和其他上下文有例外，不能把它理解成“所有错误一定会立即停机”。具体条件应查 <a href="https://www.gnu.org/s/bash/manual/bash.html#The-Set-Builtin">Bash 的 set 内建命令说明</a>。</p><p>还要注意 <code>head</code> 提前退出的问题。一个长输出被 <code>head -n 10</code> 截断时，上游程序可能收到 SIGPIPE；打开 <code>pipefail</code> 后，整条管道可能呈现非零状态，即使你只是想看前十行。这不代表输入文件必然损坏，也不应该习惯性加上 <code>|| true</code> 隐藏所有异常。</p><p>只预览文本文件，可以直接用 <code>sed -n &#39;1,10p&#39; &quot;$input_file&quot;</code>；它不依靠另一个进程提前关闭管道。需要做“是否匹配”的判断时，把 <code>grep</code> 的退出状态放进明确的 <code>if</code>，区分未匹配与真正读取失败。对大型二进制数据或外部分析工具，先理解工具自身的退出码约定。</p><p>另一个小坑是 <code>((counter++))</code>：当旧值是零时，算术命令的状态可能让 <code>-e</code> 触发。上面的脚本使用普通赋值 <code>counter=$((counter + 1))</code>，避免把计数的真假值混进错误处理。</p><h2 id="6-常见误区与练习"><a href="#6-常见误区与练习" class="headerlink" title="6. 常见误区与练习"></a>6. 常见误区与练习</h2><p>不要在未核对路径时批量重命名或删除；不要把任意文件名拼进一段 <code>eval</code>；不要一边读取原始文件，一边覆盖它；不要因为脚本有日志，就把未检查的结果称为可复现。</p><p>本例的 <code>ERR</code> 钩子提供诊断，不负责回滚已经写出的文件；它也不承诺覆盖所有信号与语法错误。可以先用 <code>bash -n safe_counts.sh</code> 做语法检查，但语法通过不等于逻辑正确。</p><p>完成后做三个小练习：把 S2 的计数改成 <code>005</code>，确认结果按十进制解释；在你自己的脚本副本里将一行计数改成 <code>abc</code>，确认程序拒绝输入；连续运行两次，核对两次目录不同且第一次的结果还在。</p><p>最后写下自己的检查清单：输入格式、样本唯一性、输出位置、覆盖策略、成功标记。把这些问题带到下一次真实分析里，比背下一长串 shell 命令更有用。</p><h2 id="官方参考"><a href="#官方参考" class="headerlink" title="官方参考"></a>官方参考</h2><ul><li><a href="https://www.gnu.org/software/bash/manual/html_node/Quoting.html">GNU Bash：引用规则</a></li><li><a href="https://www.gnu.org/s/bash/manual/bash.html#The-Set-Builtin">GNU Bash：set 内建命令与错误选项</a></li><li><a href="https://www.gnu.org/s/coreutils/manual/html_node/mktemp-invocation.html">GNU Coreutils：mktemp 创建临时目录</a></li></ul>]]></content>
    
    
    <summary type="html">用只处理临时教学目录的 Bash 小练习，理解引用、输入校验、错误处理、pipefail 与日志边界。</summary>
    
    
    
    <category term="方法分享" scheme="https://perry.apay.eu.cc/categories/%E6%96%B9%E6%B3%95%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="可复现分析" scheme="https://perry.apay.eu.cc/tags/%E5%8F%AF%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/"/>
    
    <category term="Linux" scheme="https://perry.apay.eu.cc/tags/Linux/"/>
    
    <category term="Bash" scheme="https://perry.apay.eu.cc/tags/Bash/"/>
    
    <category term="安全实践" scheme="https://perry.apay.eu.cc/tags/%E5%AE%89%E5%85%A8%E5%AE%9E%E8%B7%B5/"/>
    
  </entry>
  
  <entry>
    <title>给结论留一点空间：关于证据和不确定性的阅读笔记</title>
    <link href="https://perry.apay.eu.cc/posts/thought2603/"/>
    <id>https://perry.apay.eu.cc/posts/thought2603/</id>
    <published>2026-10-06T09:00:03.000Z</published>
    <updated>2026-10-06T09:00:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一篇供讨论的思考札记。所有案例均为假设情境，不是实际研究结果；内容讨论通用的证据阅读方法，不提供医疗判断或建议。</p><p>读论文时，一个困难并不是完全看不懂，而是看懂之后太快形成了一个更强的结论。图里出现了两组差异，就觉得机制已经被证明；模型给出较高的预测分数，就觉得它解释了原因；某项检验没有达到阈值，又把它概括为“没有作用”。</p><p>也许更有帮助的阅读习惯，是暂时把结论放慢半步：先说清看到了什么，再问这些观察最多能够支持什么。</p><h2 id="观察、推断和解释，不要写在同一格里"><a href="#观察、推断和解释，不要写在同一格里" class="headerlink" title="观察、推断和解释，不要写在同一格里"></a>观察、推断和解释，不要写在同一格里</h2><p>假设一篇文章比较了两个条件下某个测量指标。图上可以直接读到的，是测得数值的分布、差异和不确定性。把它写成“在这些样本与测量条件下，观察到这样的变化”，比立即写成“这个因素决定了变化”更准确。</p><p>进一步说变化与某个因素有关，需要检查比较对象、分组方法和其他可能解释。若要主张因果机制，还需要相应设计或干预证据；一幅图的视觉说服力，不能替代这些条件。</p><p>可以把阅读笔记分成三格：</p><table><thead><tr><th>层次</th><th>写什么</th><th>避免什么</th></tr></thead><tbody><tr><td>直接观察</td><td>指标、样本范围、比较方向</td><td>把推测混进图上读数</td></tr><tr><td>基于证据的推断</td><td>推断依赖哪些设计和假设</td><td>忽略替代解释</td></tr><tr><td>尚未验证的解释</td><td>下一步需要怎样的证据</td><td>当作作者已证明的机制</td></tr></tbody></table><p>这不是要求每篇论文都回答所有层次，而是让自己的概括不要悄悄跳过一层。</p><h2 id="在数样本以前，先问独立性"><a href="#在数样本以前，先问独立性" class="headerlink" title="在数样本以前，先问独立性"></a>在数样本以前，先问独立性</h2><p>假设同一份样本产生了很多读段，或者同一个对象被测量很多次。数据点数量确实增加了，但它们是否代表同样数量的独立实验单位，需要根据采样和实验设计判断。</p><p>不能仅凭表格很长，就把每行都视为独立重复。对于这类问题，Lazic 等人的方法文章讨论了实验单位、观测单位及伪重复之间的区别，可作为核对设计的入口：<a href="https://journals.plos.org/plosbiology/article?id=10.1371/journal.pbio.2005282">What exactly is ‘N’ in cell culture and animal experiments?</a></p><p>阅读时可以依次问：处理分配给了谁？哪些观测共享同一个来源？分析有没有考虑这种结构？文中的 n 表示对象、样本、孔、读段，还是重复测量次数？</p><p>问题的重点不在于找一个尽可能大的数字，而在于数字是否对应研究结论所需的独立信息。</p><h2 id="“没有发现差异”仍需要说明范围"><a href="#“没有发现差异”仍需要说明范围" class="headerlink" title="“没有发现差异”仍需要说明范围"></a>“没有发现差异”仍需要说明范围</h2><p>假设一个比较没有得到足够明确的差异证据。可能确实不存在值得关注的变化，也可能当前数据噪声较大、样本不足，或者测量与问题没有充分对应。究竟更接近哪种解释，需要看效应估计、区间、设计与分析假设。</p><p>因此，“在当前数据和分析下，没有获得明确支持”通常比“已经证明完全没有影响”更谨慎。反过来，也不能借不确定性无限维护一个想法，声称只要再收集数据就一定能得到想要的结果。</p><p>美国统计协会关于 p 值的声明强调，单个阈值不应替代科学判断；p 值不直接表示假设为真的概率，也不衡量效应大小或结果重要性。阅读时应结合设计、估计和完整报告，而非仅把结果分成两类。见 <a href="https://www.amstat.org/asa/files/pdfs/P-ValueStatement.pdf">ASA 官方声明</a>。</p><p>对于需要证明“足够接近”或“不超过某个实际重要差异”的问题，也应先明确那个界限和合适的分析方案，而不是直接把一次不显著检验改名为“等效”。</p><h2 id="一个好看的预测结果，回答的可能只是预测问题"><a href="#一个好看的预测结果，回答的可能只是预测问题" class="headerlink" title="一个好看的预测结果，回答的可能只是预测问题"></a>一个好看的预测结果，回答的可能只是预测问题</h2><p>再设想一个模型能区分两类样本。这个结果首先支持的是：在所用数据、划分方式与评价设置下，模型具有相应的区分表现。它不自动回答模型依赖了什么信息，也不自动证明某个特征具有因果作用。</p><p>如果训练集与测试集共享来源，或者预处理使用了全部数据，漂亮的指标还可能回答了一个比预期更容易的问题。评价一个模型时，先检查数据划分和可用信息，再讨论架构与分数。</p><p>这也提醒阅读者：作者提出的问题、实验实际回答的问题，以及自己想应用的问题，可能不是同一个。应用之前，应把这三者并排写一次。</p><h2 id="允许笔记保留“不确定”"><a href="#允许笔记保留“不确定”" class="headerlink" title="允许笔记保留“不确定”"></a>允许笔记保留“不确定”</h2><p>不确定并不等于没有进展。它可以被拆成很具体的任务：补充材料里有没有分组规则？置信区间覆盖多大的范围？另一种解释有没有被对照实验排除？这个结论能否推广到不同来源的数据？</p><p>与其用一句“还需要更多研究”结束，不如写出缺的是哪种证据。明确缺口以后，后续阅读才知道该寻找什么，而不是只寻找更多支持原观点的句子。</p><h2 id="下次读到一个重要结论，检查这六项"><a href="#下次读到一个重要结论，检查这六项" class="headerlink" title="下次读到一个重要结论，检查这六项"></a>下次读到一个重要结论，检查这六项</h2><ol><li>结论对应哪张图、哪组样本和哪种测量？</li><li>比较对象与实验单位是什么？</li><li>观察、推断和机制解释有没有被混写？</li><li>效应有多大，不确定范围有多宽？</li><li>阴性或不一致结果是否被如实报告？</li><li>换一个来源或条件，结论还需要哪些证据？</li></ol><p>谨慎不是把每个结论都稀释成“也许”。更有建设性的做法，是给结论配上清楚的范围、明确的依据，以及下一步可以检查的疑问。这样保留下来的不确定性，才有机会成为继续理解的起点。</p>]]></content>
    
    
    <summary type="html">观察、推断和因果解释需要不同的证据。用假设案例讨论实验单位、阴性结果和阅读时可执行的检查步骤。</summary>
    
    
    
    <category term="研究笔记" scheme="https://perry.apay.eu.cc/categories/%E7%A0%94%E7%A9%B6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="思考札记" scheme="https://perry.apay.eu.cc/tags/%E6%80%9D%E8%80%83%E6%9C%AD%E8%AE%B0/"/>
    
    <category term="证据与推断" scheme="https://perry.apay.eu.cc/tags/%E8%AF%81%E6%8D%AE%E4%B8%8E%E6%8E%A8%E6%96%AD/"/>
    
  </entry>
  
  <entry>
    <title>读论文的思路：从一个问题走到一条可检查的证据链</title>
    <link href="https://perry.apay.eu.cc/posts/thought2602/"/>
    <id>https://perry.apay.eu.cc/posts/thought2602/</id>
    <published>2026-10-06T09:00:02.000Z</published>
    <updated>2026-10-06T09:00:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一篇供讨论的阅读方法札记。文中的论文情境均为假设案例，不是对某篇真实论文的评价，也不代表个人做过的实验或研究经历。</p><p>一篇论文从第一页开始读，不一定就能顺着它的逻辑走到最后。摘要告诉人结论，结果展示很多图，方法铺开大量细节；读到末尾时，可能记得几个术语，却说不清作者究竟跨过了哪几个证据台阶。</p><p>一种值得尝试的思路，是不要先把论文当作一串需要全部记住的文字，而是当作一个需要重新搭起来的论证：<strong>问题是什么，作者怎样把问题变成可测量的任务，观察到了什么，哪些证据允许形成结论，还有哪些部分没有被回答。</strong></p><p>读懂不等于同意。能够准确重建作者的论证，再判断它的力度，比一开始就寻找亮点或漏洞更有帮助。</p><h2 id="第一轮：先找到研究问题，不急着收集所有细节"><a href="#第一轮：先找到研究问题，不急着收集所有细节" class="headerlink" title="第一轮：先找到研究问题，不急着收集所有细节"></a>第一轮：先找到研究问题，不急着收集所有细节</h2><p>第一轮可以阅读摘要、引言末尾、主要图题和讨论中的限制部分。目的不是给论文打分，而是写出一个暂定的研究地图。</p><p>先用一句话说明问题。比如一篇假设的计算生物学论文，试图根据序列预测某种分子测量。它的核心问题可能不是“深度学习能不能用于生物学”，而是“仅凭这段序列，能否预测某个明确条件下的特征，并在没有见过的区域上保持一定表现”。</p><p>这句话立即带出几个需要回到正文核对的变量：序列窗口有多长？特征是什么？测量来自哪些条件？没有见过是什么意思？作者需要的是预测、解释，还是因果机制？</p><p>第一轮结束时只留下五项：研究对象、已有瓶颈、输入、输出、作者最重要的主张。不确定的地方用问号保留。此时没有必要查清每个网络层或缓冲液成分，否则细节很容易把问题本身遮住。</p><p>还可以做一个阅读优先级判断：这篇论文与当前问题的连接在哪里？是需要它的原理、评价方式、数据资源，还是一个具体操作？目的不同，第二轮需要投入最多时间的部分也不同。</p><h2 id="第二轮：从结论倒推到证据，再按图重建顺序"><a href="#第二轮：从结论倒推到证据，再按图重建顺序" class="headerlink" title="第二轮：从结论倒推到证据，再按图重建顺序"></a>第二轮：从结论倒推到证据，再按图重建顺序</h2><p>第二轮围绕主要图展开，但不要仅复述每个子图的颜色和趋势。每张图应尽量回答四个问题：它测试哪条主张，比较的对象是什么，观察单位是什么，这个比较能排除哪些替代解释。</p><p>继续前面的假设案例。图一展示模型在留出数据上的表现，直接支持的是所用划分和评价下的预测能力；图二比较不同输入窗口，帮助讨论上下文信息的作用；图三展示某些序列改动后的预测变化，提供模型响应的线索。三类证据不应该被合成一句“模型已经证明调控机制”。</p><p>读到一张很漂亮的图时，可以先去掉作者的概括，只写能直接从坐标和比较中确认的内容。再把作者的结论加回来，检查中间需要哪些假设。若结论依赖正文别处的实验，补上那张图的位置；若证据没有覆盖到，就在笔记里写明尚未连接。</p><p>阴性、不一致或只在部分条件成立的结果也应进入地图。它们可能限制推广范围，也可能揭示方法依赖的条件。不应只把支持中心结论的图留下，把难解释的图归为“不重要”。</p><p>第二轮的产物不是一篇漂亮摘要，而是一张有对应关系的清单：主张一由哪些图支持，主张二依赖什么设计，哪些句子目前只是合理解释。</p><h2 id="第三轮：沿着关键证据进入方法，检查能否重新搭起来"><a href="#第三轮：沿着关键证据进入方法，检查能否重新搭起来" class="headerlink" title="第三轮：沿着关键证据进入方法，检查能否重新搭起来"></a>第三轮：沿着关键证据进入方法，检查能否重新搭起来</h2><p>第三轮再深入方法和补充材料。不是所有细节都要同时追完；先抓住会改变结论可信度的节点。</p><p>对于计算论文，可以检查数据来源、标签定义、样本筛选、训练验证测试划分、预处理发生在划分前还是后、基线是否使用相同信息、超参数怎样选择。若两组数据共享对象或来源，尤其要确认作者怎样避免关联信息跨越评价边界。</p><p>对于实验论文，可以检查处理施加给谁、哪些测量共享来源、对照是否对应主要问题、重复的层级是什么，以及测量指标能支持到哪一层解释。不要只记录一个 n，而要记录 n 在该设计中代表什么。</p><p>方法还应连接到可复现入口：公开代码在哪里，数据是否可以取得，版本和关键参数是否明确，原文与仓库实现有没有可能不同。没有找到资源就如实写“没有找到明确入口”，不要用同领域的代码代替作者的配套实现。</p><p>若准备实际复现，第一步可以是一个最小检查，而不是立即跑完整工程。比如先确认能否读取一份输入、产生预期格式、理解一个评价函数。环境安装、数据使用权限、运算资源和敏感信息边界，应在执行前另行确认。</p><p>第三轮结束时，应该知道哪些细节已经足够解释主要证据，哪些必须继续查，哪些对于当前阅读目的可以暂时放下。</p><h2 id="把作者的路线与自己的想法分开"><a href="#把作者的路线与自己的想法分开" class="headerlink" title="把作者的路线与自己的想法分开"></a>把作者的路线与自己的想法分开</h2><p>阅读很容易产生新的研究设想，但新想法应有自己的位置。可以在笔记末尾增加“如果改变一个条件”的小节：换一个组织或数据来源会怎样，标签更稀疏时能否使用，评价目标改变后哪些设计要重新考虑。</p><p>这些是从论文获得的启发，不是论文已经证实的结果。尤其不应把自己的逻辑重构写成作者真实的心理过程。可以说“这个设计可以理解为把困难拆成两个任务”，不要说“作者当时一定先想到了某件事”。</p><p>保留区分以后，讨论反而更具体：大家可以分别评价原有证据是否成立，以及新设想值得怎样检验，不必在同一句话里争论不同层次的问题。</p><h2 id="一份可以直接使用的阅读笔记模板"><a href="#一份可以直接使用的阅读笔记模板" class="headerlink" title="一份可以直接使用的阅读笔记模板"></a>一份可以直接使用的阅读笔记模板</h2><p>下面的模板可以复制到自己的 Markdown 笔记中。每一项允许写“未确认”，但尽量附上下一步核对位置。</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 论文阅读笔记</span></span><br><span class="line"></span><br><span class="line"><span class="section">## 0. 文献信息与阅读目的</span></span><br><span class="line"><span class="bullet">-</span> 题名 / 作者 / 年份 / DOI：</span><br><span class="line"><span class="bullet">-</span> 原文和补充材料入口：</span><br><span class="line"><span class="bullet">-</span> 这次为什么读它：</span><br><span class="line"><span class="bullet">-</span> 读的是正式版、预印本还是修订版：</span><br><span class="line"></span><br><span class="line"><span class="section">## 1. 问题与研究地图（第一轮）</span></span><br><span class="line"><span class="bullet">-</span> 作者试图回答的具体问题：</span><br><span class="line"><span class="bullet">-</span> 已有方法卡在哪里：</span><br><span class="line"><span class="bullet">-</span> 输入、输出和研究对象：</span><br><span class="line"><span class="bullet">-</span> 作者最重要的主张：</span><br><span class="line"><span class="bullet">-</span> 目前无法解释的术语或条件：</span><br><span class="line"></span><br><span class="line"><span class="section">## 2. 主张与证据（第二轮）</span></span><br><span class="line">| 主张 | 对应图表 | 直接观察 | 比较与对照 | 还依赖什么假设 |</span><br><span class="line">| --- | --- | --- | --- | --- |</span><br><span class="line">| | | | | |</span><br><span class="line"><span class="bullet">-</span> 阴性或不一致结果：</span><br><span class="line"><span class="bullet">-</span> 观察、推断和机制解释分别是什么：</span><br><span class="line"></span><br><span class="line"><span class="section">## 3. 方法与边界（第三轮）</span></span><br><span class="line"><span class="bullet">-</span> 样本 / 实验单位 / 重复结构：</span><br><span class="line"><span class="bullet">-</span> 数据划分、预处理与泄漏检查：</span><br><span class="line"><span class="bullet">-</span> 基线、评价指标与参数选择：</span><br><span class="line"><span class="bullet">-</span> 关键对照是否充分：</span><br><span class="line"><span class="bullet">-</span> 结论在哪些条件下成立，哪里不能直接推广：</span><br><span class="line"></span><br><span class="line"><span class="section">## 4. 复现准备</span></span><br><span class="line"><span class="bullet">-</span> 明确公开的代码 / 数据地址：</span><br><span class="line"><span class="bullet">-</span> 软件版本与关键配置：</span><br><span class="line"><span class="bullet">-</span> 使用权限、数据敏感性与资源要求：</span><br><span class="line"><span class="bullet">-</span> 最小可检查步骤与预期输出：</span><br><span class="line"><span class="bullet">-</span> 当前阻碍，不假装已经解决：</span><br><span class="line"></span><br><span class="line"><span class="section">## 5. 我的理解与下一步</span></span><br><span class="line"><span class="bullet">-</span> 用自己的话解释主要证据链：</span><br><span class="line"><span class="bullet">-</span> 与作者结论不同的个人推断：</span><br><span class="line"><span class="bullet">-</span> 一个可以验证的新问题：</span><br><span class="line"><span class="bullet">-</span> 下一份资料或下一次检查：</span><br></pre></td></tr></table></figure><h2 id="读完以后，做一次离开原文的检查"><a href="#读完以后，做一次离开原文的检查" class="headerlink" title="读完以后，做一次离开原文的检查"></a>读完以后，做一次离开原文的检查</h2><p>关掉论文，尝试回答：如果只能保留两张图，哪两张最能连接问题和结论？如果拿掉一个对照，哪条推断会变弱？如果把输入数据换一个来源，最先需要重新检查哪一步？</p><p>回答困难并不说明之前白读了。它往往指出证据链中尚未连好的位置。回到那个位置，比重新从头做一次长篇摘录更有效率。</p><p>三轮阅读也不是每篇论文都必须完成的仪式。第一轮可以帮助决定是否继续；第二轮帮助形成准确概括；第三轮服务于深入判断和复现准备。真正需要保留下来的，是问题、证据、方法和边界之间清楚的连接，而不是一种看起来很认真、却无法再次使用的阅读过程。</p>]]></content>
    
    
    <summary type="html">用三轮阅读与一份可复制模板，把研究问题、实验方法、关键证据、解释边界和复现入口连起来。</summary>
    
    
    
    <category term="研究笔记" scheme="https://perry.apay.eu.cc/categories/%E7%A0%94%E7%A9%B6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="思考札记" scheme="https://perry.apay.eu.cc/tags/%E6%80%9D%E8%80%83%E6%9C%AD%E8%AE%B0/"/>
    
    <category term="证据链" scheme="https://perry.apay.eu.cc/tags/%E8%AF%81%E6%8D%AE%E9%93%BE/"/>
    
  </entry>
  
  <entry>
    <title>理解，还是收藏：怎样判断知识真的留下来了</title>
    <link href="https://perry.apay.eu.cc/posts/thought2601/"/>
    <id>https://perry.apay.eu.cc/posts/thought2601/</id>
    <published>2026-10-06T09:00:01.000Z</published>
    <updated>2026-10-06T09:00:01.000Z</updated>
    
    <content type="html"><![CDATA[<p>这是一篇供讨论的思考札记。文中的学习情境是假设案例，不是个人经历或实验结果。</p><p>打开一篇教程，觉得讲得很好；收藏一个代码仓库，打算之后仔细看；把几张漂亮的流程图放进笔记。这些动作都没有错。问题只在于，做完这些之后，容易产生一种模糊的完成感：材料已经保存，仿佛知识也已经掌握。</p><p>收藏解决的是“以后去哪里找”，理解解决的是“现在为什么这样做”。两者相互支持，却不能互相代替。</p><h2 id="看懂，不一定能够重新做出判断"><a href="#看懂，不一定能够重新做出判断" class="headerlink" title="看懂，不一定能够重新做出判断"></a>看懂，不一定能够重新做出判断</h2><p>假设正在学习一个从 FASTQ 到表达矩阵的教程。文章中依次出现质控、定量、汇总等步骤，每个工具都有命令。沿着页面阅读时，顺序似乎很自然；把命令复制到终端，也可能得到输出。</p><p>但如果换成另一种文库类型，或者输入样本的命名方式发生变化，原来的顺畅感可能突然消失。此时真正缺少的未必是更多命令，而是几条连接步骤的解释：输入是什么，输出是什么，这一步要回答哪个问题，什么情况下不能照搬。</p><p>所以，检验理解时不妨把“能不能跟着完成”改成“能不能解释一个选择”。例如：为什么先检查样本与文件的对应关系？质控报告出现警告以后，为什么不应只凭警告颜色决定删除样本？表达矩阵里的行和列分别代表什么？</p><p>这些问题比复述工具名称更接近可以复用的知识。</p><h2 id="给一条知识设置三个出口"><a href="#给一条知识设置三个出口" class="headerlink" title="给一条知识设置三个出口"></a>给一条知识设置三个出口</h2><p>一个值得尝试的办法，是让每次阅读都产生三种不同的输出。</p><p><strong>第一种是解释。</strong> 暂时关掉原文，用几句话说明概念，避免只替换同义词。如果必须保留术语，就补上一句它在当前问题中的作用。解释不必优美，但应该可以被检查。</p><p><strong>第二种是变化。</strong> 主动改变一个条件，问原结论是否仍成立。比如把一份带表头的文件改成没有表头，原先的“行数等于样本数”是否还能使用？把训练集准确率很高改成验证集准确率很低，是否还能说模型已经学好了？</p><p><strong>第三种是操作。</strong> 构造一个很小、结果可以预先判断的例子。学习文本处理时用五行数据；学习张量形状时只放两个样本；学习一个公式时先代入能手算的数字。这里的目的不是制造漂亮输出，而是让错误暴露得更清楚。</p><p>三个出口不必每次都完整完成。它们的共同作用，是把被动接收的材料转成可以回答、修改和检查的对象。</p><h2 id="笔记不应只是原文的影子"><a href="#笔记不应只是原文的影子" class="headerlink" title="笔记不应只是原文的影子"></a>笔记不应只是原文的影子</h2><p>把一篇文章缩短成另一篇文章，当然有整理价值，但也容易把原文中的思路连同未察觉的疑问一起搬过去。笔记可以多保留一栏：<strong>这段内容改变了哪个判断？</strong></p><p>例如，读完“标准输出和标准错误”后，笔记不只是两个定义，还可以写：以后保存程序结果时，要区分数据输出和报错日志。读完“训练、验证与测试的职责”后，可以写：比较超参数应使用验证集，不能反复查看测试结果再挑模型。</p><p>这类记录将概念连接到行为。下次遇到问题，看到的就不是一块静态知识，而是一条可以重新审视的决策理由。</p><p>同时，应把“暂时能解释”与“已经熟练”分开。刚阅读后复述很容易，隔几天后重新解释、换一个例子再操作，才更能发现哪些连接还不牢固。发现忘记并不说明笔记失败，恰好说明它提供了下一次学习的定位信息。</p><h2 id="不需要把所有东西都学成专家水平"><a href="#不需要把所有东西都学成专家水平" class="headerlink" title="不需要把所有东西都学成专家水平"></a>不需要把所有东西都学成专家水平</h2><p>收藏并非低质量行为。有些资料只需要知道存在，有些概念只需要能查，有些方法才需要独立完成。真正有用的是明确深度，而不是对每一条知识都提出同样苛刻的要求。</p><p>可以给材料加三个简单标签：</p><ul><li><strong>入口</strong>：知道它能解决哪类问题，暂时不展开；</li><li><strong>理解</strong>：能解释主要逻辑和适用边界；</li><li><strong>实践</strong>：有最小练习、检查方式和待解决问题。</li></ul><p>这不是学习等级证书，也不应演变成新的打卡负担。它只是提醒：一个链接被保存以后，并不会自动从“入口”变成“实践”。</p><h2 id="下一次读完教程，可以只做这四件事"><a href="#下一次读完教程，可以只做这四件事" class="headerlink" title="下一次读完教程，可以只做这四件事"></a>下一次读完教程，可以只做这四件事</h2><ol><li>写出这篇教程要回答的一个具体问题。</li><li>关掉页面，用自己的话解释其中一个关键选择。</li><li>改变一个输入条件，判断原方法哪里需要调整。</li><li>留下一个小练习，以及明确的输出检查方式。</li></ol><p>如果还有余力，再记一条仍不确定的地方。这样的笔记可能比长篇摘录短，却更容易在下一次工作中派上用场。</p><p>值得讨论的不是“要不要收藏”，而是：保存材料以后，是否为它安排过一次离开原文、独立思考的机会。</p>]]></content>
    
    
    <summary type="html">收藏提供入口，理解带来判断。用解释、改变条件与最小练习，检查一段知识是否真的可以复用。</summary>
    
    
    
    <category term="研究笔记" scheme="https://perry.apay.eu.cc/categories/%E7%A0%94%E7%A9%B6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="思考札记" scheme="https://perry.apay.eu.cc/tags/%E6%80%9D%E8%80%83%E6%9C%AD%E8%AE%B0/"/>
    
    <category term="学习方法" scheme="https://perry.apay.eu.cc/tags/%E5%AD%A6%E4%B9%A0%E6%96%B9%E6%B3%95/"/>
    
    <category term="知识管理" scheme="https://perry.apay.eu.cc/tags/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>92.ProCapNet：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad92005c/"/>
    <id>https://perry.apay.eu.cc/posts/ad92005c/</id>
    <published>2026-10-04T12:01:32.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:092 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Dissecting the cis-regulatory syntax of transcription initiation with deep learning</p><p><strong>期刊与年份：</strong> bioRxiv，2024。<a href="https://doi.org/10.1101/2024.05.28.596138">论文原文</a>。</p><p><strong>阅读版本：</strong> bioRxiv, 2024-11-21。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>研究问题：</strong> DNA 序列如何决定转录启动的强度与起始位点，并通过 motif 排列、竞争及染色质状态产生不同细胞中的启动模式？</p><h2 id="用更接近过程的读数，拆开表达的混合原因"><a href="#用更接近过程的读数，拆开表达的混合原因" class="headerlink" title="用更接近过程的读数，拆开表达的混合原因"></a>用更接近过程的读数，拆开表达的混合原因</h2><p>我对研究思路形成的推测是：直接预测稳态RNA虽然实用，却把转录启动、暂停、剪接和降解混在同一目标里，难以确认某个motif影响哪一步。作者选择PRO-cap这一新生RNA起点测量，把问题缩小为局部DNA怎样决定启动强度和起始位置。这是监督的序列到分子读数模型，疾病关联是后续应用方向，正文主图没有直接证明AD机制。</p><p>ProCapNet用2,114 bp DNA预测中央1 kb两条链的逐碱基概率及合计log计数。BPNet式八层扩张卷积、512通道提供非线性局部上下文；多项分布损失负责起点与方向，计数均方误差负责总量。两链共用一次softmax，使模型不仅选择链内位置，还能预测正负链间读段比例。窗口比输出宽避免边缘零填充伪特征。</p><p>训练使用PRO-cap峰和开放但缺乏启动的DNase背景，按染色体七折划分。这种负例针对性地要求模型区分开放与启动，而不是只学哪些区域开放。低可比对碱基在训练形状损失中屏蔽，评估却仍纳入；这对重复序列是实质边界。作者没有用本文疾病标签训练，也没有将后面的启动子&#x2F;增强子标签作为网络分类目标。</p><h2 id="Figure-1：以碱基分辨率预测转录起始"><a href="#Figure-1：以碱基分辨率预测转录起始" class="headerlink" title="Figure 1：以碱基分辨率预测转录起始"></a>Figure 1：以碱基分辨率预测转录起始</h2><p><a href="/img/dl-literature/092/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-01.webp" alt="Figure 1" width="1300" height="1343" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 4 页，Figure 1。点击图片可查看原图。</em></p><p>A把强度与位置分别定义，B用启动子和附近增强子说明同一架构能处理两类元件，C&#x2F;D在未见染色体量化，而E检验成绩是否仅由某类强信号位点带动。实验重复提供可达到程度的参照，平均轨迹是较弱基线；模型与重复间仍有差距，局部序列不包含远端和细胞状态的全部信息。覆盖越低，测量分布越随机，E的逐例JSD归一化正是为降低这种混杂。归一化后不能将不同定义的JSD数值直接拼到其他论文排行榜。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>A</td><td>ProCapNet 输入局部 DNA 序列，同时预测 PRO-cap 的总起始计数与逐碱基正&#x2F;负链分布；前者回答启动多强，后者回答在哪里、向哪个方向启动。</td></tr><tr><td>B</td><td>两个位点的实测 PRO-cap、预测轨迹及两项任务的序列贡献分数对照；预测能追踪峰的位置，贡献分数指向 Inr、TATA 和转录因子等候选序列元件。</td></tr><tr><td>C</td><td>七折留出染色体测试中总计数预测与实测的相关性，评估训练未见位点的起始强度泛化。</td></tr><tr><td>D</td><td>逐碱基分布预测的 JSD 累积分布，并与相关对照比较；JSD 越小越好，不能按相关系数“越大越好”的方向阅读。</td></tr><tr><td>E</td><td>按 ENCODE 调控元件类别分层的归一化 JSD，比较不同启动子&#x2F;增强子等位点的分布预测；归一化用于减少覆盖深度混杂，多数类别保持一定预测能力。</td></tr></tbody></table><h2 id="Figure-2：预测贡献识别启动-motif-及其定位规律"><a href="#Figure-2：预测贡献识别启动-motif-及其定位规律" class="headerlink" title="Figure 2：预测贡献识别启动 motif 及其定位规律"></a>Figure 2：预测贡献识别启动 motif 及其定位规律</h2><p><a href="/img/dl-literature/092/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-02.webp" alt="Figure 2" width="1322" height="1349" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 6 页，Figure 2。点击图片可查看原图。</em></p><p>A的PWM、CWM、贡献幅度与起始轨迹分别回答序列长什么样、模型使用哪些碱基、贡献有多大、起点在哪里，不能只看等高的logo判断motif重要性。B按方向对齐后显示TATA约在起点上游、Inr更贴起点，C再揭示NRF1和ETS内部可能含隐蔽Inr。这说明同一TF样序列在不同上下文可承担不同功能。motif实例要求序列与归因双重匹配，过滤许多仅有序列相似的命中；高覆盖注释仍是模型支持，不是逐个位点已完成真实扰动。正文还发现与TE重叠的长模式中，较短子片段贡献突出，为重复副本功能分化提供候选线索。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>A</td><td>TF-MODISCO 从高贡献片段提取重复 motif，综合展示 PWM、贡献加权 CWM、贡献幅度、平均起始轨迹及出现频率。序列匹配与实际模型贡献需要分开，模型在绝大多数活跃位点找到可预测 motif。</td></tr><tr><td>B</td><td>把各 motif 实例相对 PRO-cap 峰顶的位置按方向排列；TATA 与起始位点有偏移，Inr 更直接贴近起点，不同元件具有不同方向&#x2F;定位关系。</td></tr><tr><td>C</td><td>NRF1 与 ETS 的子模式中出现 Inr 样序列贡献，提示一个转录因子 motif 可同时包含影响起始定位的局部信息。</td></tr></tbody></table><h2 id="Figure-3：启动子和增强子的共同词汇与复杂度差异"><a href="#Figure-3：启动子和增强子的共同词汇与复杂度差异" class="headerlink" title="Figure 3：启动子和增强子的共同词汇与复杂度差异"></a>Figure 3：启动子和增强子的共同词汇与复杂度差异</h2><p><a href="/img/dl-literature/092/figure-03.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-03.webp" alt="Figure 3" width="1300" height="732" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 8 页，Figure 3。点击图片可查看原图。</em></p><p>A用监督模型学到的表示做PCA，再叠加元件标签；它没有直接用这些标签训练，却不能因此把整个ProCapNet称无监督模型。B&#x2F;C&#x2F;D从密度、种类和匹配强度解释分离，E用仅启动子训练再预测增强子检验共享语法的迁移。启动子与增强子可以共享词汇又有组成差别，并不矛盾。CWM余弦相似是作者采用的亲和力代理，不是实验结合常数。增强子端出现计数校准偏差，还说明预测排序一致与绝对强度可迁移是不同标准。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>A</td><td>模型内部表示的 PCA 将启动子与远端增强子分开；说明网络即使没有直接训练这类标签，也学到了可区分两者的序列模式。</td></tr><tr><td>B</td><td>比较预测相关 motif 实例数，启动子总体比远端增强子含有更多起始相关元件。</td></tr><tr><td>C</td><td>比较各 motif 至少出现一次的比例；启动子偏富集 SP1&#x2F;BRE、ETS、NFY 等，增强子偏富集 AP1、TATA，显示共享词汇中的组成偏好。</td></tr><tr><td>D</td><td>比较 motif 实例与 CWM 的匹配强度，启动子通常更强；此为序列&#x2F;贡献匹配代理，不能当作直接实验测得的结合亲和力。</td></tr><tr><td>E</td><td>只用启动子训练的模型与全位点训练模型在留出位点的计数预测比较；两者高度一致，增强子区间有校准偏差，支持启动子学到的起始逻辑能够一定程度迁移到增强子。</td></tr></tbody></table><h2 id="Figure-4：MYC-启动子中逐元件虚拟修改与起点竞争"><a href="#Figure-4：MYC-启动子中逐元件虚拟修改与起点竞争" class="headerlink" title="Figure 4：MYC 启动子中逐元件虚拟修改与起点竞争"></a>Figure 4：MYC 启动子中逐元件虚拟修改与起点竞争</h2><p><a href="/img/dl-literature/092/figure-04.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-04.webp" alt="Figure 4" width="1336" height="968" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 10 页，Figure 4。点击图片可查看原图。</em></p><p>MYC实例把同一背景中的右Inr、左右TATA、SP1&#x2F;BRE、TCT替换和隐蔽反向Inr分别修改，统一纵轴避免缩放制造视觉效果。右起点被破坏时左起点预测增加，反向起点增强又压低左起点，提示非对称竞争。真正值得注意的是重算归因后邻近元件贡献也改变，模型表示的作用依赖其他元件。所有修改都是计算模拟，选择的替换序列还可能引入未识别新模式；因此应以多种替换和实测突变检验稳健性，不能仅凭单例宣称发现真实PIC竞争机制。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>原始序列（无字母）</td><td>同一 MYC 启动子展示实测、模型预测和贡献轨迹，标出两个正向起点及隐蔽反向起点，作为所有序列修改的基线；纵轴尺度保持一致。</td></tr><tr><td>Right Inr Ablated</td><td>破坏下游右侧 Inr 后该起点预测信号下降，左侧起点增强；提示起始信号会在邻近位点间重新分配。</td></tr><tr><td>Right TATA Ablated</td><td>破坏右侧 TATA 也削弱下游起点并增强上游起点，与右侧 Inr 的计算破坏一起支持两起点竞争的模型解释。</td></tr><tr><td>Left TATA Ablated</td><td>破坏左侧 TATA 降低上游起点，但下游起点没有对称的同等变化；起点之间的影响具有方向不对称性。</td></tr><tr><td>Right SP1&#x2F;BRE Ablated</td><td>破坏右侧 SP1&#x2F;BRE 使右侧预测起始强度降低，显示除核心起始元件外，邻近转录因子相关元件也影响该位点活性。</td></tr><tr><td>Right Inr Turned TCT</td><td>把右侧 Inr 改为 TCT 模式，预测信号的位置&#x2F;强度随之改变；直接检验核心起始序列类型变化的影响。</td></tr><tr><td>Cryptic Inr Improved</td><td>增强隐蔽反向 Inr 后反向起始信号增加，上游正向起点减弱而下游正向起点基本不变；进一步展示邻近起点之间的竞争和重分配。</td></tr></tbody></table><h2 id="Figure-5：系统性-motif-破坏区分起始强度与位置作用"><a href="#Figure-5：系统性-motif-破坏区分起始强度与位置作用" class="headerlink" title="Figure 5：系统性 motif 破坏区分起始强度与位置作用"></a>Figure 5：系统性 motif 破坏区分起始强度与位置作用</h2><p><a href="/img/dl-literature/092/figure-05.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-05.webp" alt="Figure 5" width="1318" height="1389" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 11 页，Figure 5。点击图片可查看原图。</em></p><p>A&#x2F;B将单例扩展为全体motif实例，并用重复随机替换降低某个替换序列的偶然影响。C上方是含motif位点实测量，下方是破坏后的预测效应，两者排名不同，说明观察富集容易被启动子或增强子组成混杂。D比较归一化起点概率，某个位置丢失后概率必然分配到其他位置；这在数学上并不自动代表其他位置的绝对起始量增加，还需结合计数头。Inr&#x2F;TATA可能显著改变局部起点而对全窗口总量影响较小，表明只用gene-level RNA会漏掉调控形式变化。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>A</td><td>在各位点逐个破坏 motif 实例，重复估计预测变化的计算实验流程；将单一 MYC 示例推广到全体 PRO-cap 峰。</td></tr><tr><td>B</td><td>代表性位点的破坏前后预测轨迹：Inr、YY1、CTCF 等元件造成的变化位置和重新分配模式不同，说明只看总信号会遗漏局部效应。</td></tr><tr><td>C</td><td>上方展示含有各 motif 位点的实测信号分布，下方展示破坏 motif 后预测总计数的倍数改变；二者区分观察关联与模型反事实效应。Inr&#x2F;TATA 等的局部起点效应可以很强，但总起始量中位数变化不一定大。</td></tr><tr><td>D</td><td>对所有 motif 实例汇总破坏前后的逐位置分布变化；定位 motif 更集中影响局部起点，部分激活因子对更广区域信号有影响。</td></tr></tbody></table><h2 id="Figure-6：稳态转录数据训练的模型学到了什么"><a href="#Figure-6：稳态转录数据训练的模型学到了什么" class="headerlink" title="Figure 6：稳态转录数据训练的模型学到了什么"></a>Figure 6：稳态转录数据训练的模型学到了什么</h2><p><a href="/img/dl-literature/092/figure-06.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-06.webp" alt="Figure 6" width="1318" height="1172" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 13 页，Figure 6。点击图片可查看原图。</em></p><p>A先展示PRO-cap与RAMPAGE测量有关但不相同，B–D再显示局部序列模型对二者作出比原始数据更相近的预测。E&#x2F;F&#x2F;G检验这种一致来自何处，同时暴露起点测量偏好不同；RAMPAGE的帽子与模板切换流程可能造成G起点偏好。作者据此推测局部模型主要提取启动成分，这是一种模型和测量共同支持的解释，不能称已经精确分离每条RNA的降解效应。对AD公共数据，可借稳态读数提出启动假说，但模型插补不等于目标脑细胞已经测到新生转录。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>A</td><td>PRO-cap 起始信号与 RAMPAGE 稳态转录信号的实测总量相关；二者有共同信息，但并非同一测量，实际相关约 0.67。</td></tr><tr><td>B</td><td>仅用 RAMPAGE 训练的 RAMPAGE-Net 在留出峰上预测 RAMPAGE 计数的表现；稳态信号较难从局部序列完全解释。</td></tr><tr><td>C</td><td>两种模型在 PRO-cap 峰上的计数预测高度一致，相关约 0.96；暗示 RAMPAGE 模型更容易学到其中由局部序列决定的起始成分。</td></tr><tr><td>D</td><td>RAMPAGE-Net 预测与真实 PRO-cap 信号比较，仍有较高相关，支持其序列可解释成分与起始活动相联系。</td></tr><tr><td>E</td><td>一个启动子的两种实测信号、模型预测、贡献分数和保守性轨迹；模型解释比两种原始信号更相似，可定位共同的起始序列元件。</td></tr><tr><td>F</td><td>对 PRO-cap 和 RAMPAGE 检出的读段 5′ 端序列汇总 PPM&#x2F;PWM；比较两种实验识别起点的序列偏好及差异。</td></tr><tr><td>G</td><td>在所有 PRO-cap 峰的 1 kb 窗口内比较两模型贡献分数相关性，分别评估位置分布和总计数任务；检验共同序列规律是否广泛存在。</td></tr></tbody></table><h2 id="Figure-7：跨细胞起始规律与细胞特异染色质信息"><a href="#Figure-7：跨细胞起始规律与细胞特异染色质信息" class="headerlink" title="Figure 7：跨细胞起始规律与细胞特异染色质信息"></a>Figure 7：跨细胞起始规律与细胞特异染色质信息</h2><p><a href="/img/dl-literature/092/figure-07.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/092/figure-07.webp" alt="Figure 7" width="1294" height="1506" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 15 页，Figure 7。点击图片可查看原图。</em></p><p>A的本细胞测试与B的所有细胞峰并集测试回答不同问题：模型在本细胞活跃峰上很好，却可能在只于别的细胞活跃处仍预测启动。C&#x2F;D用目标细胞的染色质信息检验缺失的状态因素，E把参考细胞模型与目标细胞实测染色质组合。拟合采用留出染色体，并包含二阶交互项，不能只称简单单变量线性校准。染色质补充改善说明局部启动能力和实际细胞活性有区别；组蛋白与启动之间的关联还不能单凭拟合确定因果顺序。使用目标细胞实测状态后，也不能再宣称从DNA零样本获得完整细胞特异性。</p><table><thead><tr><th>子图</th><th>讲解</th></tr></thead><tbody><tr><td>A</td><td>在多个细胞类型分别训练&#x2F;测试模型，比较计数与位置分布表现；局部序列能够在不同细胞中支持起始预测。</td></tr><tr><td>B</td><td>上三角为细胞间实测信号相关，下三角为各细胞模型预测相关，对角线为本细胞预测与实测；模型预测跨细胞更相似，说明大量序列决定的信息相对通用，不能因此声称细胞差异不存在。</td></tr><tr><td>C</td><td>用 ProCapNet 预测加不同染色质测量拟合 K562 PRO-cap；加入组蛋白修饰或开放染色质等信息改善拟合，补充局部序列未解释的细胞背景。</td></tr><tr><td>D</td><td>在实测与预测的联合位点分布中叠加组蛋白&#x2F;DNase 信号；观察模型预测相近却实际活性不同的位点是否可由染色质状态区分。</td></tr><tr><td>E</td><td>用另一细胞模型的序列预测再结合目标细胞的染色质测量拟合目标 PRO-cap；支持通用序列规律与细胞特异染色质信息共同决定起始活性。</td></tr></tbody></table><h2 id="阅读提醒"><a href="#阅读提醒" class="headerlink" title="阅读提醒"></a>阅读提醒</h2><ul><li>来源为用户提供的 2024-11-21 bioRxiv 版本。Figure 4&#x2F;5 的 motif 修改均为计算模拟，显示模型学到的调控关系，需要与真实突变验证区分。</li></ul><h2 id="我会怎样将起点变化转成TE课题"><a href="#我会怎样将起点变化转成TE课题" class="headerlink" title="我会怎样将起点变化转成TE课题"></a>我会怎样将起点变化转成TE课题</h2><p>一个具体方向是检验TE副本附近变异是否主要改变转录起点选择，而不是总RNA量。先在有可信起点读数的公共数据建立基准，将TE副本与同家族、GC和可比对性相近的对照匹配，比较等位替换对正负链分配、起点位置和总量的三种影响，再用独立CAGE&#x2F;PRO-cap或等位数据验证。若脑细胞没有对应读数，只能把细胞系发现作为候选规则，不能直接称AD结果。</p><p>对有限显存，我会先用公开模型提取局部表示或进行等位前向推理，研究清楚一种细胞类型、一类TE家族，再决定训练。创新可落在“哪些副本保留祖先起始语法，哪些副本经变异改写起点，以及病理相关细胞环境是否允许它生效”，但演化年龄、拷贝同源性和可比对性必须分别控制。相似副本随机分到训练测试会造成同源泄漏，跨家族或跨染色体检验应作为独立挑战。</p><p>我的感悟是，模型的可解释性不只来自架构透明，还来自目标读数、负例和验证的设计。ProCapNet保留非线性，Puffin对motif作用加入结构约束，两者代表不同取舍；这里的比较带有作者方法视角，需要在统一细胞、数据和评价目标下判断，不能引用一句评论就宣布一种范式全面失效。对于自己的AD研究，最可迁移的是先找能够区分机制的中间读数，再选择允许该机制被检验的模型。</p>]]></content>
    
    
    <summary type="html">深入读懂ProCapNet：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="调控基因组" scheme="https://perry.apay.eu.cc/tags/%E8%B0%83%E6%8E%A7%E5%9F%BA%E5%9B%A0%E7%BB%84/"/>
    
    <category term="DNA序列模型" scheme="https://perry.apay.eu.cc/tags/DNA%E5%BA%8F%E5%88%97%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>91.ChromBPNet：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad92005b/"/>
    <id>https://perry.apay.eu.cc/posts/ad92005b/</id>
    <published>2026-10-04T12:01:31.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:091 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> ChromBPNet: bias factorized, base-resolution deep learning models of chromatin accessibility reveal cis-regulatory sequence syntax, transcription factor footprints and regulatory variants</p><p><strong>期刊与年份：</strong> bioRxiv，2024。<a href="https://doi.org/10.1101/2024.12.25.630221">论文原文</a>。</p><p><strong>阅读版本：</strong> bioRxiv预印本，2025-01-08版本。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 分解测序酶偏差后能否恢复真实TF足迹、调控语法和变异效应？</p><h2 id="问题先来自解释失败，模型才围绕偏差重新设计"><a href="#问题先来自解释失败，模型才围绕偏差重新设计" class="headerlink" title="问题先来自解释失败，模型才围绕偏差重新设计"></a>问题先来自解释失败，模型才围绕偏差重新设计</h2><p>我对研究起点的推测是：如果一个ATAC模型能准确预测每个碱基的切割分布，却在归因中不断发现Tn5偏好，它可能擅长预测实验，而没有正确解释细胞调控。作者从计数头与形状头发现的motif差异出发，将技术偏差明确建成独立模块。这属于测量过程驱动的监督建模：训练标签是ATAC或DNase读段，不是疾病状态，也不是无监督疾病分型。</p><p>模型输入2,114 bp DNA，预测中央1 kb的无链方向逐碱基概率与总覆盖量。512通道扩张卷积让局部motif与附近组合共享表示，形状用多项负对数似然，总量用log计数均方误差；两种目标分别回答读段落在哪里、有多少。浅层128通道偏差模型先在低信号、GC匹配非峰区域学习，冻结后再训练调控分支。校正后的输出来自移除偏差分支，而不是把所有原始轨迹先平滑成一个新标签。</p><p>这套分解也有依赖条件：背景区域不能混入太多弱开放峰，否则会把真实TF规则交给偏差模型。作者用motif归因检查并逐步调整非峰覆盖阈值。方法采用ATAC正链+4、负链−4的切割位点处理，与常见+4&#x2F;−5设置不同；复现时不能无意混用两者。本解读基于2025年1月8日的预印本版本，证据强度按该份PDF评估。</p><h2 id="Figure-1：高分辨率开放度模型受到Tn5序列偏差干扰"><a href="#Figure-1：高分辨率开放度模型受到Tn5序列偏差干扰" class="headerlink" title="Figure 1：高分辨率开放度模型受到Tn5序列偏差干扰"></a>Figure 1：高分辨率开放度模型受到Tn5序列偏差干扰</h2><p><a href="/img/dl-literature/091/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-01.webp" alt="Figure 1" width="1383" height="1424" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 25 页，Figure 1。点击图片可查看原图。</em></p><p>a先把总量与形状分开，是后续诊断成立的必要条件。b在同一未见位点同时展示实测、预测和两头归因，c&#x2F;d证明预测确实有能力，e&#x2F;f&#x2F;g再揭示能力背后的特征并不一致：计数主要对应TF，而形状显著携带Tn5偏好。关键结论不是模型整体无用，而是低JSD不能独自保证生物解释正确；测序酶可以提供很强的预测信号。对TE序列，这尤其重要，因为某类重复的碱基组成可能同时改变酶偏好和真实结合，直接将归因富集称为TE功能会混淆两者。</p><p>主图：PDF 第 25 页；完整图注：第 26 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>2114bp序列预测总计数和1kb逐碱基切割概率两种输出，定义计数头与形状头。</td></tr><tr><td>b</td><td>未见区域实测、预测及归因，计数归因突出TF而形状归因还包含Tn5偏好伪特征。</td></tr><tr><td>c</td><td>未见染色体K562峰计数预测对实测，验证总体开放量准确性。</td></tr><tr><td>d</td><td>预测与实测形状JSD对重复及随机／均值基线，展示较好形状预测不代表没有酶偏差。</td></tr><tr><td>e</td><td>计数头发现的主要模体匹配已知TF，显示其功能可解释性。</td></tr><tr><td>f</td><td>形状头模体常为Tn5偏好或扭曲TF模式，定位污染来源。</td></tr><tr><td>g</td><td>计数与形状归因seqlet频率比较，说明Tn5模式主导未经校正的形状解释。</td></tr></tbody></table><h2 id="Figure-2：分解酶偏差与真实TF代码"><a href="#Figure-2：分解酶偏差与真实TF代码" class="headerlink" title="Figure 2：分解酶偏差与真实TF代码"></a>Figure 2：分解酶偏差与真实TF代码</h2><p><a href="/img/dl-literature/091/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-02.webp" alt="Figure 2" width="1346" height="1776" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 27 页，Figure 2。点击图片可查看原图。</em></p><p>a&#x2F;c先核对偏差模型究竟学到了什么，d给出冻结与残差学习，b&#x2F;e&#x2F;f&#x2F;g用实际轨迹、motif比例及边际足迹比较多种校正。只有去掉Tn5模式却保留TF模式，才能支持分解方向正确。边际足迹是把motif插入许多背景序列再平均预测，降低其他motif共现影响；它是计算反事实，不能写成直接测量的蛋白占位。CTCF与ZBTB7A等实例还提醒我们，增加某个motif未必都增加开放度。背景染色质偏差优于裸DNA偏差，也说明实验环境会改变测量过程，换协议后应重新做偏差诊断。</p><p>主图：PDF 第 27 页；完整图注：第 28 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>在无开放峰的背景染色质训练Tn5偏差模型，定义独立偏差学习。</td></tr><tr><td>b</td><td>同一区域不同校正方案的轨迹与归因，ChromBPNet配BPNet偏差模型产生清楚潜在足迹，并去掉伪Tn5特征。</td></tr><tr><td>c</td><td>背景模型发现的多种Tn5模体，证明它学习的是酶序列偏好。</td></tr><tr><td>d</td><td>冻结预训练偏差模型、训练残差TF子模型的结构，说明因子分解方式。</td></tr><tr><td>e</td><td>各校正方案的seqlet类型比较，完整BPNet偏差模型才能有效避免Tn5主导。</td></tr><tr><td>f</td><td>Tn5模体边际足迹在不同模型中的对照，检验酶偏差是否被消除。</td></tr><tr><td>g</td><td>真实TF模体边际足迹比较，校正后形状不再被Tn5偏好干扰。</td></tr></tbody></table><h2 id="Figure-3：校正后ATAC与DNase结果更加一致"><a href="#Figure-3：校正后ATAC与DNase结果更加一致" class="headerlink" title="Figure 3：校正后ATAC与DNase结果更加一致"></a>Figure 3：校正后ATAC与DNase结果更加一致</h2><p><a href="/img/dl-literature/091/figure-03.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-03.webp" alt="Figure 3" width="1338" height="1763" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 29 页，Figure 3。点击图片可查看原图。</em></p><p>a把同一位点的ATAC与DNase并排，b–d从单例扩展到轨迹、归因和motif实例的整体一致性，e–h再问校正有没有过度抹掉实验特征。两者足迹高度更一致，而DNase仍更窄，意味着方法尝试消除酶序列偏好，同时保留测量分辨率差异。这是比两条曲线看起来相似更严格的检验。不过相同序列、相近细胞状态和共同分析步骤也会增加一致性，它们不能替代真实结合扰动。</p><p>主图：PDF 第 29 页；完整图注：第 30 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>同一K562位点ATAC／DNase真实、预测和归因，校正后都突出AP1／SP1代码。</td></tr><tr><td>b</td><td>两实验轨迹间JSD分布，校正提高跨实验一致性。</td></tr><tr><td>c</td><td>不同计数／形状归因JSD，形状归因经校正明显更一致。</td></tr><tr><td>d</td><td>两实验的TF模体实例频率相似，说明共用调控代码被恢复。</td></tr><tr><td>e</td><td>模体足迹校正前后比较，展示酶差异去除后的TF形状。</td></tr><tr><td>f</td><td>边际足迹宽度和高度的定义，解释后续量化。</td></tr><tr><td>g</td><td>DNase足迹仍比ATAC窄，说明校正并不抹平两实验的真实分辨率差别。</td></tr><tr><td>h</td><td>两实验的足迹高度高度相关，支持共同的TF结合强度信息。</td></tr></tbody></table><h2 id="Figure-4：低覆盖数据中恢复足迹与模体"><a href="#Figure-4：低覆盖数据中恢复足迹与模体" class="headerlink" title="Figure 4：低覆盖数据中恢复足迹与模体"></a>Figure 4：低覆盖数据中恢复足迹与模体</h2><p><a href="/img/dl-literature/091/figure-04.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-04.webp" alt="Figure 4" width="1337" height="1580" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 31 页，Figure 4。点击图片可查看原图。</em></p><p>a–d是从同一572M读段实验降采样并分别训练，检验随着覆盖降低哪些信号先丢失。总体轨迹尚稳定时，稀有motif已可能召回下降，因此少量读段下得到清晰预测不代表稀有调控模式同样可靠。满深度模型是参照，不是无噪声真值，多个深度也不是独立生物队列。做AD罕见细胞或供体伪总体时，我会同时报告读段深度、motif稳定性和变异效应稳定性，不仅展示平滑轨迹；模型去噪产生的形状也不能伪装成该供体实际测到的足迹。</p><p>主图：PDF 第 31 页；完整图注：第 32 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>逐步降采样时实测轨迹变差，模型校正轨迹与归因仍较稳定，展示去噪能力。</td></tr><tr><td>b</td><td>满深度对降采样的JSD，25M读段仍接近，5M才明显下降。</td></tr><tr><td>c</td><td>相对满深度模体实例的召回，低深度仍保留多数模体但罕见模体在5M丢失。</td></tr><tr><td>d</td><td>不同深度的代表TF足迹，检查局部形状保持情况。</td></tr></tbody></table><h2 id="Figure-5：紧凑模体词典与协同组合"><a href="#Figure-5：紧凑模体词典与协同组合" class="headerlink" title="Figure 5：紧凑模体词典与协同组合"></a>Figure 5：紧凑模体词典与协同组合</h2><p><a href="/img/dl-literature/091/figure-05.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-05.webp" alt="Figure 5" width="1334" height="1676" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 33 页，Figure 5。点击图片可查看原图。</em></p><p>a&#x2F;b定义各细胞的紧凑motif词典，c通过组合、间距与方向扰动询问FOS–TEAD是否超出单motif效应相加，d–g再将可及性归因与TF ChIP模型归因联系起来。固定6 bp间距的协同主要来自模型反事实与独立ChIP建模支持，不能把每一种组合都写成已经完成CRISPR验证。d–g的参照是另一个由ChIP训练的BPNet归因，并非直接的结合常数；共享建模偏差仍需留意。对TE家族，可检验其共识序列是否保留特定间距组合，再看功能副本是否偏离共识，而不是只数motif出现次数。</p><p>主图：PDF 第 33 页；完整图注：第 34 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>不同细胞系最主要十个计数模体，展示细胞特异调控词典。</td></tr><tr><td>b</td><td>未经／经过偏差校正的足迹，后者显露细胞类型差异。</td></tr><tr><td>c</td><td>FOS-TEAD组合在固定6bp间距表现超加和协同，展示模型能发现精细模体语法。</td></tr><tr><td>d</td><td>HepG2 LDLR附近ATAC与多TF ChIP归因一致，连接开放预测与实际TF结合。</td></tr><tr><td>e</td><td>CTCF位点ATAC模型与CTCF ChIP模型归因相关，验证功能实例定位。</td></tr><tr><td>f</td><td>ChromBPNet归因比总体ATAC／DNase计数及TOBIAS分数更贴近CTCF ChIP归因。</td></tr><tr><td>g</td><td>多细胞、多TF重复f的比较，检验这种结合信息优势是否普遍。</td></tr></tbody></table><h2 id="Figure-6：跨实验、祖源与细胞类型的变异效应"><a href="#Figure-6：跨实验、祖源与细胞类型的变异效应" class="headerlink" title="Figure 6：跨实验、祖源与细胞类型的变异效应"></a>Figure 6：跨实验、祖源与细胞类型的变异效应</h2><p><a href="/img/dl-literature/091/figure-06.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-06.webp" alt="Figure 6" width="1336" height="1431" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 35 页，Figure 6。点击图片可查看原图。</em></p><p>a–k逐层增加外部检验：QTL分类、带符号效应、单例机制、欧洲与非洲数据、个体内等位偏倚、祖源模型一致性，最后到小胶质细胞和平滑肌。总量log倍数变化、形状JSD和活跃等位分位分别刻画方向、形状改变与背景可及性，合并分数的用途也要区分排序和定量。作者修正Enformer原来过大输出范围后，其局部QTL分类明显改善；因此不能概括成小模型在所有任务都击败大模型。h需考虑等位比对偏差，i是参考序列训练的群体模型之间比较，不能称为个人基因组已完全泛化。j支持脑细胞局部可及性预测，尚未建立AD发病或进展因果。</p><p>主图：PDF 第 35 页；完整图注：第 36 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>约鲁巴LCL dsQTL的精确率—召回率，ChromBPNet优于gkmSVM且接近Enformer。</td></tr><tr><td>b</td><td>dsQTL实测效应对预测log倍数变化，检验定量方向与强度。</td></tr><tr><td>c</td><td>rs11242436两等位轨迹及归因，显示AP1模体破坏及邻近AP1协同效应。</td></tr><tr><td>d</td><td>欧洲LCL caQTL的模型分类比较，检验跨祖源及ATAC应用。</td></tr><tr><td>e</td><td>欧洲caQTL实测对预测效应，验证效应量。</td></tr><tr><td>f</td><td>非洲LCL caQTL分类比较，检验另一祖源集合。</td></tr><tr><td>g</td><td>非洲caQTL实测对预测效应，验证量化一致性。</td></tr><tr><td>h</td><td>非洲杂合位点ATAC等位偏倚对预测效应，提供个体内等位比较。</td></tr><tr><td>i</td><td>不同祖源参考LCL训练模型的预测效应相关矩阵，检验调控效应跨背景稳定性。</td></tr><tr><td>j</td><td>小胶质细胞caQTL对单细胞伪总体训练模型预测，检查原代脑细胞应用。</td></tr><tr><td>k</td><td>冠状动脉平滑肌caQTL对匹配伪总体模型预测，检查另一原代环境。</td></tr></tbody></table><h2 id="Figure-7：TF结合、报告实验、复杂性状与罕见病变异"><a href="#Figure-7：TF结合、报告实验、复杂性状与罕见病变异" class="headerlink" title="Figure 7：TF结合、报告实验、复杂性状与罕见病变异"></a>Figure 7：TF结合、报告实验、复杂性状与罕见病变异</h2><p><a href="/img/dl-literature/091/figure-07.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/091/figure-07.webp" alt="Figure 7" width="1312" height="1540" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 37 页，Figure 7。点击图片可查看原图。</em></p><p>a–c用SPI1结合QTL问可及性效应能否反映先锋TF作用，d跨报告实验检验更直接的序列活动，e将效应排序与精细定位联系，f给血液性状实例，g&#x2F;h才走到罕见神经发育候选与小鼠胚胎报告。证据逐层增强，但报告系统的活性变化仍不等于人类内源靶基因、细胞功能和疾病表型全部成立。g中的NR2F1抑制解释及JARID2靶向关系应作为支持程度不同的机制假说。版本核对还有两处文字冲突：a正文一次写SP1而图注写SPI1，f正文使用K562而图注写GM12878；此处保留图示任务与候选机制，不能据冲突文字补造确定细胞证据。</p><p>主图：PDF 第 37 页；完整图注：第 38 页。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>rs5764238的G等位产生强SPI1模体，不同模型归因一致，解释先锋TF结合QTL。</td></tr><tr><td>b</td><td>SPI1 bQTL的分类曲线，比较ChromBPNet与Enformer。</td></tr><tr><td>c</td><td>SPI1等位ChIP效应对预测开放度效应，验证结合与开放的联系。</td></tr><tr><td>d</td><td>多增强子／启动子MPRA饱和突变效应及模型比较，ChromBPNet在IRF4、SORT1、HNF4A、MSMB、HBG1等更优。</td></tr><tr><td>e</td><td>高预测效应变异在红细胞GWAS精细定位候选中的富集，随PIP和效应阈值增加而增强。</td></tr><tr><td>f</td><td>红细胞体积关联rs11553699的G等位破坏GATA模体，提出具体调控机制。</td></tr><tr><td>g</td><td>神经发育障碍患者变异hs2599的G等位产生抑制NR2F1模体并预测降低开放度，给出疾病相关候选机制。</td></tr><tr><td>h</td><td>E11.5转基因小鼠A／G增强子报告实验，G等位在多个脑区活性降低，与g的预测一致。</td></tr></tbody></table><p>阅读说明：实际阅读版本为bioRxiv预印本，2025年1月8日，DOI 10.1101&#x2F;2024.12.25.630221；讲解依据这份PDF的正文7幅图。</p><h2 id="将创新放在测量与机制之间"><a href="#将创新放在测量与机制之间" class="headerlink" title="将创新放在测量与机制之间"></a>将创新放在测量与机制之间</h2><p>对AD的TE候选，我最愿意迁移的是两阶段流程：先用细胞类型伪总体建立并检验酶偏差分支，再训练调控模型，锁定后比较TE与匹配非TE变异的有符号局部效应。TE家族、GC、可比对性、峰强度和TSS距离都需要控制；无法唯一比对的副本不能因为模型预测漂亮就被计作实测支持。随后用独立caQTL、等位数据或MPRA验证方向，最后才连接靶基因与病理。</p><p>本模型是约600万参数的局部CNN，适合作为有限显存方案的候选，但实际5060 16 GB训练速度与batch仍应做小规模实测，不能从参数少直接承诺完整流程无障碍。小胶质细胞已有公共数据和本篇基准，可先复现一个细胞类型再展开。真正可发表的问题是某类TE背景是否系统性影响局部调控、或现有模型为何在这些背景失败，而不是简单把ChromBPNet换名字。</p><p>最深的感悟是，解释工具可以参与模型诊断，而不只是预测后的装饰。作者先发现归因中的测量混杂，再改变训练结构，并以第二种酶、QTL和报告实验检验；这个循环值得模仿。但它主要解释局部染色质规则，远端表达、病理环境和跨细胞状态仍是开放边界，不能靠扩大结论措辞跨过去。</p><p>局部效应验证时还需统一参考组装与等位方向，避免坐标转换使符号反转。</p>]]></content>
    
    
    <summary type="html">深入读懂ChromBPNet：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="调控基因组" scheme="https://perry.apay.eu.cc/tags/%E8%B0%83%E6%8E%A7%E5%9F%BA%E5%9B%A0%E7%BB%84/"/>
    
    <category term="DNA序列模型" scheme="https://perry.apay.eu.cc/tags/DNA%E5%BA%8F%E5%88%97%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>90.PersonalExpressionFineTuning：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad92005a/"/>
    <id>https://perry.apay.eu.cc/posts/ad92005a/</id>
    <published>2026-10-04T12:01:30.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:090 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Fine-tuning sequence-to-expression models on personal genome and transcriptome data</p><p><strong>期刊与年份：</strong> Genome Biology，2026。<a href="https://doi.org/10.1186/s13059-026-04091-1">论文原文</a>。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 个人基因组与表达数据微调能改善哪些泛化任务，仍有哪些限制？</p><h2 id="从失败基准走向“个人数据微调是否能补上缺口”"><a href="#从失败基准走向“个人数据微调是否能补上缺口”" class="headerlink" title="从失败基准走向“个人数据微调是否能补上缺口”"></a>从失败基准走向“个人数据微调是否能补上缺口”</h2><p>我对问题形成的推测是：前两篇基准指出参考序列模型常读错个人表达差异，最直接的修复想法就是让模型在训练时看到这些差异。本文将这一自然想法做成可检验研究，同时把“新的人”“新的人群”和“新基因”分开。最重要的结果不是微调后一个数字提高，而是提高发生在哪里、仍不能泛化到哪里。</p><p>作者用Geuvadis 421人的配对WGS与LCL RNA，微调Enformer。输入缩短为TSS周围49,152 bp，两单倍型生成个人预测；主模型随机选同一基因的两人，拟合标准化表达差值，损失为差值均方误差。它借用对比学习的思想，却仍是使用表达标签的监督学习，并非无监督训练。配对消除了同一基因的平均表达偏移，让梯度集中于个体差异；配对数增加也不等于独立供体变多。</p><p>输出通过中央十个bin的注意力池化与线性头汇总，全部模型权重默认参与微调。卷积与Transformer仍提供预训练的局部及远程表示，末端映射适配RNA任务。对照还包括单样本回归、配对分类、几种基因型线性模型和随机初始化。疾病或病理标签没有进入任务，讨论“个人转录组”也不意味着能预测AD进展。研究范式是冻结划分后的监督微调与泛化审计。</p><h2 id="Figure-1：以个人表达差值微调Enformer"><a href="#Figure-1：以个人表达差值微调Enformer" class="headerlink" title="Figure 1：以个人表达差值微调Enformer"></a>Figure 1：以个人表达差值微调Enformer</h2><p><a href="/img/dl-literature/090/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-01.webp" alt="Figure 1" width="925" height="387" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 3 页，Figure 1。点击图片可查看原图。</em></p><p>无字母流程图的关键是先固定同一基因，再随机选两个人，而非比较两个基因平均表达。两人序列往往只差少量变异，差值目标因此鼓励网络利用这些位置。但个人表达还包含非遗传因素，目标不能被理解为纯粹遗传真值。基因间方差被标准化，避免少数高方差基因支配损失；这一处理需要在实际复现中记录，不能把标准化值当绝对RNA数量。</p><p>我认为图1是一项清楚的目标设计，而不是自动解决因果识别的方法。两人同时携带许多连锁变异，预测差值正确仍可能靠相关代理。要知道网络是否找到功能位点，必须看后续图4的独立线索与未见基因测试。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>全图（无字母）</td><td>同一基因随机取两个人的基因组和标准化表达，以预测表达差值的均方误差训练，直接让模型学习个体变异的作用。</td></tr></tbody></table><h2 id="Figure-2：已见基因、新人群与未见基因评估"><a href="#Figure-2：已见基因、新人群与未见基因评估" class="headerlink" title="Figure 2：已见基因、新人群与未见基因评估"></a>Figure 2：已见基因、新人群与未见基因评估</h2><p><a href="/img/dl-literature/090/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-02.webp" alt="Figure 2" width="925" height="704" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 4 页，Figure 2。点击图片可查看原图。</em></p><p>a建立三个互不重叠基因组：200随机个体划分基因、200欧洲训练／Yoruba测试基因、670未见基因；还以不同染色体避免区域泄漏。b显示微调对已见基因的新供体明显改善，跨人群稍弱，未见基因未改善。这意味着模型更会解释已经见过的调控变化，并未形成普遍可迁移的新语法。</p><p>随机个体留出大多是已见常见变异的新组合，不是全部新变异测试。跨人群改变等位频率与LD，也改变可见变异，因此不能只称“同一任务换样本”。线性模型与微调在相同人数、输入范围和划分比较，使收益具有可解释对照。0.28和0.17是跨基因平均的个人相关，不能写成28%或17%的病例分类准确率。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>随机个体划分、人群划分及未见基因三套评估，严格区分新组合、未见祖源及全新序列任务。</td></tr><tr><td>b</td><td>微调模型对基线的平均个体间相关：随机划分约0.28、未见人群约0.17；对已见基因有提升，未见基因未改善。</td></tr></tbody></table><h2 id="Figure-3：冻结、混合训练与预训练消融"><a href="#Figure-3：冻结、混合训练与预训练消融" class="headerlink" title="Figure 3：冻结、混合训练与预训练消融"></a>Figure 3：冻结、混合训练与预训练消融</h2><p><a href="/img/dl-literature/090/figure-03.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-03.webp" alt="Figure 3" width="925" height="901" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 7 页，Figure 3。点击图片可查看原图。</em></p><p>a冻结CNN、Transformer或二者，性能只轻度变化，说明本数据下末端映射调整承担了相当部分收益；但冻结实验不能证明所有内部特征完全不变或无关。b同时加入原Enformer数据或MPRA没有明显改善，排查遗忘与序列多样性不足的简单解释。MPRA只有短片段且背景不同，结果不能扩展为“功能数据永远无用”。c随机初始化较差，说明预训练表示仍重要。整组消融告诉我们，利用已有语法和重新学会通用语法是两件事。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>冻结CNN、Transformer等组件后性能仅轻度下降，说明微调主要调整末端MLP映射。</td></tr><tr><td>b</td><td>个人数据与原Enformer或MPRA混合训练的表现，检验额外功能数据能否改善泛化。</td></tr><tr><td>c</td><td>从随机权重开始训练的表现较差，验证原预训练表示的作用。</td></tr></tbody></table><h2 id="Figure-4：因果变异优先级能力"><a href="#Figure-4：因果变异优先级能力" class="headerlink" title="Figure 4：因果变异优先级能力"></a>Figure 4：因果变异优先级能力</h2><p><a href="/img/dl-literature/090/figure-04.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-04.webp" alt="Figure 4" width="925" height="435" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 8 页，Figure 4。点击图片可查看原图。</em></p><p>a以MAGE统计精细定位的高PIP对低PIP位点比较，限定为各模型训练时都见过的变异，并在同一基因中选背景。因此它检验的是已见基因内的候选优先级，并未证明对未见罕见变异有同等能力。高PIP仍是统计证据，不是每个变异的实验因果确认。</p><p>b在DNase足迹中检查驱动变异富集，并控制TSS距离，因为模型偏向启动子、足迹也密集于启动子。去掉这一步会把位置偏差误当机制解释。足迹富集支持预训练的调控信息被利用；实际TF占据还受切割偏好及检测方法影响，不能把一个重叠点当直接结合验证。这里预训练模型本身也有较好富集，显示总体表达不准与找不到重要序列并不等价。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>用同一基因的高PIP（&gt;0.9）与低PIP（&lt;0.01）eQTL比较ROC，配对微调模型的AUROC最高（约0.85）。这里以统计精细定位作为参照，尚未逐个实验确认变异的因果作用。</td></tr><tr><td>b</td><td>控制TSS距离后的驱动变异DNase足迹富集，配对微调最高、原Enformer接近，支持预训练调控代码有用。</td></tr></tbody></table><h2 id="Figure-5：深度预测近似变异线性加和"><a href="#Figure-5：深度预测近似变异线性加和" class="headerlink" title="Figure 5：深度预测近似变异线性加和"></a>Figure 5：深度预测近似变异线性加和</h2><p><a href="/img/dl-literature/090/figure-05.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-05.webp" alt="Figure 5" width="925" height="428" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 10 页，Figure 5。点击图片可查看原图。</em></p><p>左右两图以ISM权重乘变异剂量近似深度网络，预测相关几乎相同，说明个人变异在本数据范围主要呈加和。它没有否定网络在motif内部学习非线性，也没有证明生物调控本质上线性。个人常见变异通常较稀疏，当前人数和读数可能不足以识别强交互。深度模型如果不能在相同测试上超过线性近似，研究就应解释它额外提供何种机制或新变异能力，而不只以模型复杂命名。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>左：随机划分基因</td><td>深度模型与用ISM作为权重的变异剂量线性近似相关几乎相同，说明个体变异作用主要可加。</td></tr><tr><td>右：人群划分基因</td><td>跨人群也表现接近，提示当前性能收益未明显依赖变异间强非线性交互。</td></tr></tbody></table><h2 id="Figure-6：常见与罕见变异的预测贡献"><a href="#Figure-6：常见与罕见变异的预测贡献" class="headerlink" title="Figure 6：常见与罕见变异的预测贡献"></a>Figure 6：常见与罕见变异的预测贡献</h2><p><a href="/img/dl-literature/090/figure-06.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-06.webp" alt="Figure 6" width="925" height="635" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 10 页，Figure 6。点击图片可查看原图。</em></p><p>逐步替换低频次要等位为主要等位，再观察预测下降，将输入中的遗传信息作有针对性的计算剥离。常见变异贡献最大，罕见变异少量补充，频率来自训练供体以避免偷看测试频率。删除某类等位同时改变真实单倍型，结果是模型依赖性实验，不等同于逐个罕见变异因果效应。对临床新发变异能力，仍需单独外部任务与足够可靠标签。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>全图（无字母）</td><td>逐步把低于MAF阈值的次要等位替为主要等位后，个体预测相关下降；常见变异贡献最大，稀有变异有少量额外贡献。</td></tr></tbody></table><h2 id="Figure-7：训练个体数量的缩放"><a href="#Figure-7：训练个体数量的缩放" class="headerlink" title="Figure 7：训练个体数量的缩放"></a>Figure 7：训练个体数量的缩放</h2><p><a href="/img/dl-literature/090/figure-07.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/090/figure-07.webp" alt="Figure 7" width="925" height="381" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 11 页，Figure 7。点击图片可查看原图。</em></p><p>配对模型与BLUP的训练人数缩放相近，已见基因随人数增长改善、未见基因不稳定改善。扩大供体能改善已见统计关系，却未自动跨越序列任务泛化瓶颈。误差来自重新抽样和模型重复，不是每个基因独立的个人置信区间。BLUP没有未见基因预测分支，图中空缺不应写成其在这项任务获得零分。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>左：配对微调</td><td>已见基因随训练人数增加而提高，未见基因表现不随人数稳定改善，显示泛化瓶颈。</td></tr><tr><td>右：BLUP</td><td>线性模型对已见基因的缩放与微调类似；该方法没有可应用于未见基因的对应预测。</td></tr></tbody></table><h2 id="对有限显存与AD课题的启发"><a href="#对有限显存与AD课题的启发" class="headerlink" title="对有限显存与AD课题的启发"></a>对有限显存与AD课题的启发</h2><p>本文主要实验用了八张A5000或TITAN RTX，不能直接承诺5060 16 GB可复现同等吞吐。但冻结主干的消融为小资源方案提供根据：预计算固定模型表示，训练较小输出头，再与全微调或线性剂量模型做明确比较。资源节省本身不是创新，目标是在独立脑数据和TE候选中得到更可信的方向或泛化。</p><p>我会先将任务限定为某一脑细胞类型、已见调控模块的新供体预测，再把未见TE家族或未见基因作为独立挑战。若疾病标签仅用于后验联系，应锁定模型后再分析，不能借病理调参又宣称无监督发现。所有个人两单倍型、配对样本与同一供体应整体划分，近邻TE副本还要考虑同源泄漏。</p><p>最深的启发是：微调可以修正预测，却不一定带来可迁移机制。只有当模型在此前没有见过的位点、变异或队列里仍能正确解释方向，才有理由说它学到了新的调控规律。这个开放缺口比简单模仿论文加一层网络更适合作为研究问题。</p><p>实际实施时还应保存每次供体划分和随机种子，用统一测试集比较所有消融，防止不同划分使架构改进与抽样运气混在一起。</p>]]></content>
    
    
    <summary type="html">深入读懂PersonalExpressionFineTuning：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="模型评估" scheme="https://perry.apay.eu.cc/tags/%E6%A8%A1%E5%9E%8B%E8%AF%84%E4%BC%B0/"/>
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="个人基因组" scheme="https://perry.apay.eu.cc/tags/%E4%B8%AA%E4%BA%BA%E5%9F%BA%E5%9B%A0%E7%BB%84/"/>
    
    <category term="基因表达" scheme="https://perry.apay.eu.cc/tags/%E5%9F%BA%E5%9B%A0%E8%A1%A8%E8%BE%BE/"/>
    
  </entry>
  
  <entry>
    <title>89.SegmentNT-ContextBias：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad920059/"/>
    <id>https://perry.apay.eu.cc/posts/ad920059/</id>
    <published>2026-10-04T12:01:29.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:089 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Systematic contextual biases in SegmentNT potentially relevant to other nucleotide transformer models</p><p><strong>期刊与年份：</strong> Nucleic Acids Research，2026。<a href="https://doi.org/10.1093/nar/gkag704">论文原文</a>。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 输入长度和碱基在窗口中的位置会怎样系统影响SegmentNT输出？</p><h2 id="当输入切片本身可以改变同一个碱基的答案"><a href="#当输入切片本身可以改变同一个碱基的答案" class="headerlink" title="当输入切片本身可以改变同一个碱基的答案"></a>当输入切片本身可以改变同一个碱基的答案</h2><p>我对问题形成过程的推测是，作者把基础模型常见使用习惯转成了可实验的问题：从基因组切下一段序列，模型返回每个碱基的注释概率，这个概率是否会随窗口长短或碱基所处位置变化？如果同一DNA、同一碱基仅因切片偏移就出现差异，后续变异评分或新注释发现可能混入输入构造效应。论文重点是冻结SegmentNT进行上下文稳健性审计，并未训练一个新的AD模型。</p><p>SegmentNT以Nucleotide Transformer的6-mer表示和分割网络预测逐碱基、多标签基因组特征。本篇仅检查外显子和内含子两个输出，不是全部14类。它们分别预测，概率不必相加为1：一个位置可涉及重叠转录本，独立输出与互斥softmax的含义不同。作者对五个常见基因和一个基因间对照做详细扫描，再以五个额外基因及更多碱基验证，不能将其样本规模写成全基因组调查。</p><p>输入从24到24,576 nt，逐碱基滑动使同一位置处于首、中、末或全部窗口位置。评估真值采用RefSeq外显子注释，并与Ensembl异构体及实际长读RNA线索比较。参照注释有用，却不是组织特异实测剪接概率。尤其APOE出现只是作为与AD有关的代表位点；本文没有比较患者病理、AD病例对照或疾病变异效应。</p><h2 id="Figure-1：检验输入长度与碱基位置偏差的研究设计"><a href="#Figure-1：检验输入长度与碱基位置偏差的研究设计" class="headerlink" title="Figure 1：检验输入长度与碱基位置偏差的研究设计"></a>Figure 1：检验输入长度与碱基位置偏差的研究设计</h2><p><a href="/img/dl-literature/089/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/089/figure-01.webp" alt="Figure 1" width="1244" height="1287" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 4 页，Figure 1。点击图片可查看原图。</em></p><p>a界定测试基因与负对照，b通过同一序列的不同切法建立控制，c区分每个碱基首中末轨迹与少数固定碱基的全部位置扫描。这样可以把基因本身不同和输入位置不同分开。窗口偏移也会改变进入模型的远端上下文，因而边界恶化不能全部归于模型内部周期性；中心小幅周期与缺少侧翼的大尺度效应需分别看。基因间对照匹配APOE的大致长度与SINE比例，有助排查一般重复含量，却不代表已经检验各种TE家族。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>选择APOE、EGFR、TNF、TP53、VEGFA及基因间对照，定义五个代表位点而非全基因组调查。</td></tr><tr><td>b</td><td>11种输入长度以逐碱基滑动窗口生成概率，说明如何把相同碱基置于不同上下文位置。</td></tr><tr><td>c-i</td><td>分析首／中／末位置及五个单碱基在整个窗口的位置，区分两种位置评估。</td></tr><tr><td>c-ii</td><td>示意原始概率和性能的解释分析，连接上下文偏差与实际注释准确性。</td></tr></tbody></table><h2 id="Figure-2：APOE的预测取决于在窗口中的位置"><a href="#Figure-2：APOE的预测取决于在窗口中的位置" class="headerlink" title="Figure 2：APOE的预测取决于在窗口中的位置"></a>Figure 2：APOE的预测取决于在窗口中的位置</h2><p><a href="/img/dl-literature/089/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/089/figure-02.webp" alt="Figure 2" width="1244" height="1283" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 6 页，Figure 2。点击图片可查看原图。</em></p><p>a、b显示同一APOE外显子在窗口边缘与中心的原始概率分布不同。c、d通过Z标准化统一阈值解释，e、f再用外显子减内含子得分比较。重要的是，单调标准化不会凭空提高同一轨迹的排序AUC；它主要调整概率尺度和阈值解释。外显子减内含子则组合两种输出，可能改变排序。两者的作用不能混写成“校正后模型学会新生物学”。</p><p>98%以上的中心分类准确度是相对于这一个基因的注释与所用阈值，不能作为所有人类注释的准确率。某处概率与替代外显子重合可以提出假说，尚不能仅凭模型证明实际组织使用这个异构体。与注释冲突时需同时检查漏注、反链与预测错误。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>同一APOE碱基处于首、中、末位置时的外显子概率轨迹明显不同，中心较符合注释。</td></tr><tr><td>b</td><td>真实外显子概率分布比较，中心较高、末端较低，首端波动较大。</td></tr><tr><td>c</td><td>概率标准化后对实际注释的定位，中心准确率约98.5%，高于首末位置。</td></tr><tr><td>d</td><td>标准化概率ROC及AUC，检验位置差异对区分外显子／内含子的影响。</td></tr><tr><td>e</td><td>以外显子减内含子概率归一化的轨迹，检查另一校正方式。</td></tr><tr><td>f</td><td>对应减法模型ROC，比较概率解释方式。</td></tr></tbody></table><h2 id="Figure-3：四个额外基因的位置偏差"><a href="#Figure-3：四个额外基因的位置偏差" class="headerlink" title="Figure 3：四个额外基因的位置偏差"></a>Figure 3：四个额外基因的位置偏差</h2><p><a href="/img/dl-literature/089/figure-03.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/089/figure-03.webp" alt="Figure 3" width="1244" height="1278" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 7 页，Figure 3。点击图片可查看原图。</em></p><p>a至d检验不同基因、链方向和异构体是否重复出现相同位置效应。EGFR附近一些峰对应负链EGFR-AS1，提示模型输出是基因组特征，并不天然限定于读者眼前的某个正链基因。TP53多异构体扩展超过经典边界，VEGFA的内含子保留与较低外显子概率又提示注释层次复杂。</p><p>这张图不能被读成模型已经发现新的组织特异剪接。作者给出的实际异构体表达资料是外部对照，模型没有接收该组织状态；外显子概率高低更不是该异构体在脑中使用比例。某些低概率原因尚不明确，这种保留未知比编造一个剪接机制更准确。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>EGFR概率和异构体注释，部分信号来自负链EGFR-AS1，说明不能只按单基因／单链解释。</td></tr><tr><td>b</td><td>TNF概率与两异构体，检验同样的位置偏差是否复现。</td></tr><tr><td>c</td><td>TP53负链基因及多异构体概率，展示基因边界之外的转录本也影响解释。</td></tr><tr><td>d</td><td>VEGFA多异构体及保留内含子注释，检查与表达主转录本不同的信号；部分外显子概率较低原因尚未明确。</td></tr></tbody></table><h2 id="Figure-4：上下文越长通常越准确，但收益饱和"><a href="#Figure-4：上下文越长通常越准确，但收益饱和" class="headerlink" title="Figure 4：上下文越长通常越准确，但收益饱和"></a>Figure 4：上下文越长通常越准确，但收益饱和</h2><p><a href="/img/dl-literature/089/figure-04.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/089/figure-04.webp" alt="Figure 4" width="1244" height="1288" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 9 页，Figure 4。点击图片可查看原图。</em></p><p>a到e保持APOE任务，增加上下文长度，展示极短序列基本无区分、较长序列逐渐恢复外显子结构。它回答的是上下文对这两个注释任务是否有用，不能直接推出预测AD风险或远程增强子只需同样长度。输入更长不仅带来信息，也可能更接近训练分布，若要证明信息来源需要进一步删除或替换侧翼对照。</p><p>模型在替代外显子附近的波动仍存在，说明长度增长没有保证所有位置稳定。一个长窗口能够给整体分类很高AUC，同时在个别重要边界处产生不稳定；高总体成绩不能排除关键位点的技术敏感性。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>24nt输入的APOE概率近似无规律，难以区分外显子和内含子。</td></tr><tr><td>b</td><td>192nt输入开始有结构但仍不清楚，展示极短上下文不足。</td></tr><tr><td>c</td><td>768nt输入出现清楚区分，AUC达到约0.9972，显示主要改善。</td></tr><tr><td>d</td><td>3072nt概率更稳定，也保留可变剪接外显子信号。</td></tr><tr><td>e</td><td>24576nt的概率接近明确分类，展示长输入的精细稳定化收益。</td></tr></tbody></table><h2 id="Figure-5：长度收益的边际变化"><a href="#Figure-5：长度收益的边际变化" class="headerlink" title="Figure 5：长度收益的边际变化"></a>Figure 5：长度收益的边际变化</h2><p><a href="/img/dl-literature/089/figure-05.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/089/figure-05.webp" alt="Figure 5" width="1192" height="1317" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 10 页，Figure 5。点击图片可查看原图。</em></p><p>a看平均外显子概率，b看排序分类能力。概率继续靠近1不等于分类改善等幅增加，这正是作者比较两种读数的原因。约3,072 nt后AUC收益较小，约6,144 nt后平均概率趋平台，二者不矛盾，因为衡量目标不同。</p><p>我会把3 kb视为值得测试的节省计算起点，而非通用最佳长度。它来自少数基因、外显子／内含子和固定模型版本；增强子、重复元件、长程相互作用可能需要不同范围。资源有限时先做长度消融，再以实际任务性能决定，比按模型最大输入直接训练更合理。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>五基因中心位置平均外显子概率随长度增加，约6144nt后趋平台。</td></tr><tr><td>b</td><td>中心位置AUC大部分改善约在3072nt完成，因此平均概率持续增加不代表分类能力同等改善。</td></tr></tbody></table><h2 id="Figure-6：同一碱基随输入偏移产生24nt周期"><a href="#Figure-6：同一碱基随输入偏移产生24nt周期" class="headerlink" title="Figure 6：同一碱基随输入偏移产生24nt周期"></a>Figure 6：同一碱基随输入偏移产生24nt周期</h2><p><a href="/img/dl-literature/089/figure-06.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/089/figure-06.webp" alt="Figure 6" width="1244" height="1276" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 11 页，Figure 6。点击图片可查看原图。</em></p><p>a、b对固定碱基扫描全部窗口位置，先看到边缘不稳与中心振荡；c、d每隔6 nt取值仍残留四token周期，e、f每隔24 nt取值才使中心同相位轨迹近似平滑。这个逐层采样设计区分了6-mer分词与更长周期，说明简单固定6 nt对齐不足以完全消除输出波动。</p><p>24 nt周期与分词、U-Net、位置编码的关联属于作者提出的可能解释，尚未通过重新训练或模块消融确定起源。每隔24 nt画出的线平滑，也不意味着不同相位的预测已相同。选择“最好相位”再评价会引入选择偏差，应预先固定或平均相位并报告波动；窗口位移不是DNA变异，不能把其概率变化解释为生物效应。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>APOE第850碱基在24576nt窗口每个位置的外显子／内含子概率，中心较稳定但仍周期振荡，边缘更差。</td></tr><tr><td>b</td><td>局部放大看似6nt周期，展示token移位产生的概率波动。</td></tr><tr><td>c</td><td>每隔6nt取值仍有四token周期，说明并非单纯6nt模式。</td></tr><tr><td>d</td><td>c局部放大，清楚显示残余周期。</td></tr><tr><td>e</td><td>每隔24nt取值的全范围曲线，中心周期被消除但两端仍波动。</td></tr><tr><td>f</td><td>对应中心局部放大，验证24nt对齐使同组概率近似平滑；此偏差说明窗口选择本身能改变输出。</td></tr></tbody></table><h2 id="对TE与AD候选，先要求结果经得住切片改变"><a href="#对TE与AD候选，先要求结果经得住切片改变" class="headerlink" title="对TE与AD候选，先要求结果经得住切片改变"></a>对TE与AD候选，先要求结果经得住切片改变</h2><p>这篇给我的具体启发是，为变异评分加入一个小型稳健性基准：参考和替代等位使用完全相同长度、位置与侧翼，在多个预定偏移下成对计算差值，报告平均效应、方向一致性和范围。否则原始概率差可能主要由输入位置变化产生。要检查是否在重复序列和TE边界更明显，需另外设计家族与长度匹配的数据，本文不能直接回答。</p><p>一个可做的纯计算课题是比较不同小DNA表示模型在TE家族与非TE区域的切片稳定性，再与外部可及性或变异功能标签连接。创新不应止于发现周期，而应判断它是否改变候选排名、方向或模块归属，并证明一种修正方式在完全留出的区域减少错误而不损害真实信号。</p><p>我也会谨慎使用本篇极小P值：相邻碱基和相邻窗口高度依赖，海量窗口不是海量独立样本。概率分布差异可以很显著，却仍需效应幅度和跨区域复现来判断重要性。标准化是解释层面的工具，不是重新校准致病概率；多标签注释模型输出也不应在未经验证时被当作临床风险。</p><p>实际部署还应记录输入长度与偏移规则，以便他人复现同一个碱基的分数。默认参数变化若不记入元数据，后续比较不同模型或不同队列时就可能把切片差异误当成生物差异。</p>]]></content>
    
    
    <summary type="html">深入读懂SegmentNT-ContextBias：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="模型评估" scheme="https://perry.apay.eu.cc/tags/%E6%A8%A1%E5%9E%8B%E8%AF%84%E4%BC%B0/"/>
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="调控基因组" scheme="https://perry.apay.eu.cc/tags/%E8%B0%83%E6%8E%A7%E5%9F%BA%E5%9B%A0%E7%BB%84/"/>
    
    <category term="DNA序列模型" scheme="https://perry.apay.eu.cc/tags/DNA%E5%BA%8F%E5%88%97%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>88.PersonalTranscriptome-Huang：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad920058/"/>
    <id>https://perry.apay.eu.cc/posts/ad920058/</id>
    <published>2026-10-04T12:01:28.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:088 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Personal transcriptome variation is poorly explained by current genomic deep learning models</p><p><strong>期刊与年份：</strong> Nature Genetics，2023。<a href="https://doi.org/10.1038/s41588-023-01574-w">论文原文</a>。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 当前序列到表达模型的跨基因能力是否能够解释个人转录组变异？</p><h2 id="同一个问题，换队列与模型检验是否仍成立"><a href="#同一个问题，换队列与模型检验是否仍成立" class="headerlink" title="同一个问题，换队列与模型检验是否仍成立"></a>同一个问题，换队列与模型检验是否仍成立</h2><p>我对研究问题形成的推测是：序列到表达模型已经能解释参考基因组上不同基因的强弱，可这并不直接证明它读懂了个人遗传差异。作者选择有配对个人基因组和转录组的Geuvadis，把同一问题扩展到四个架构、不同输入范围和不同训练读数。这与ROSMAP皮层基准互补：若LCL中也出现相似落差，就不容易把失败仅归于老年脑组织或某一个模型。</p><p>本篇使用421位供体的分相WGS与淋巴母细胞系RNA，关注已具有显著cis-eQTL的3,259基因。每人两条单倍型分别替换SNV后输入，预测取平均，不包含indel。Enformer与Basenji2直接输出CAGE等多轨迹，ExPecto先预测染色质再线性映射到RNA，Xpresso是较短启动子上下文的CNN表达模型。它们都未以本文421人的个体表达重新训练，因而检验的是既有参考序列模型的迁移能力。</p><p>基准同时计算参考序列对跨基因中位表达、每人跨基因，以及每基因跨供体的相关。这样的设计把统计问题清楚分开。PrediXcan式弹性网在本文数据上按供体交叉验证训练，限制输入范围与Enformer相近，提供“这些DNA差异中至少有多少可预测信号”的对照；它不承担未见基因或任意新变异的泛化。论文不是新疾病分类网络，而是以任务对齐为核心的监督模型能力审计。</p><h2 id="Figure-1：跨基因与跨个体表达预测的落差"><a href="#Figure-1：跨基因与跨个体表达预测的落差" class="headerlink" title="Figure 1：跨基因与跨个体表达预测的落差"></a>Figure 1：跨基因与跨个体表达预测的落差</h2><p><a href="/img/dl-literature/088/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/088/figure-01.webp" alt="Figure 1" width="1325" height="610" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 2 页，Figure 1。点击图片可查看原图。</em></p><p>a用矩阵行列说明两个评估方向，是理解全部结果的前提。不同基因之间平均表达差距很大，模型只要学到启动子类别就可能获得良好跨基因相关；同一基因内供体差异较小，且由遗传、环境和测量混合影响，是另一项更细任务。b在相同队列比较四模型与线性基线，c展示相关分布，不只用均值遮住正负两端。跨个体均值接近零并不意味着每个基因都完全没有信号，但显著负向预测同样不能称“方向正确”。</p><p>d中的SLFN5与SNHG5分别展示成功与相反方向实例，避免只用成功案例替代总体基准。四个模型虽都含卷积，其训练RNA或CAGE读数、输入范围与输出汇总不同；读数匹配对照说明这些差异没有消除主要落差，却也不支持单凭绝对成绩归因某一个架构最差。图中误差线表示供体或基因集合上的分布，不是独立重复训练造成的不确定性，不能拿来当每个预测值的置信区间。</p><p>分析集合预先选择有显著eQTL的基因，相当于富集存在遗传表达信号的任务。因而即使在这样的有利集合表现有限，也构成有意义警示；但它不是全转录组任意基因的无偏总体平均。只把SNV纳入个人序列还意味着结构变异或indel贡献不在本次直接检验范围。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>同一人的不同基因对同一基因的不同个人评估，明确两种相关性回答不同问题。</td></tr><tr><td>b</td><td>四种深度模型的参考、跨基因、跨个体表现，以及PrediXcan个体预测，深度模型跨基因强但跨个体有限。</td></tr><tr><td>c</td><td>Enformer跨基因相关和跨个体相关分布，展示个体预测可接近零甚至为负。</td></tr><tr><td>d</td><td>SLFN5正相关与SNHG5负相关实例，说明不能只看相关绝对值判断变异效应正确。</td></tr></tbody></table><h2 id="Figure-2：模型之间及与实测的效应方向不一致"><a href="#Figure-2：模型之间及与实测的效应方向不一致" class="headerlink" title="Figure 2：模型之间及与实测的效应方向不一致"></a>Figure 2：模型之间及与实测的效应方向不一致</h2><p><a href="/img/dl-literature/088/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/088/figure-02.webp" alt="Figure 2" width="1326" height="922" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 3 页，Figure 2。点击图片可查看原图。</em></p><p>a按SNHG5主要eQTL剂量给个人点着色，并列四模型。三个模型给出与实测相反的趋势，Xpresso却为正，说明某个基因失败并不一定意味着它本身无法从序列预测。参考和替代等位的虚线又把个体预测压缩成具体遗传对照，但top eQTL可能与真正功能变异处于LD；这张图显示方向问题，并未逐一完成因果定位。</p><p>b看模型间逐基因相关，沿正对角与负对角都出现密集点。模型可能共同发现有遗传差异的基因，却对方向分歧。因此“多个模型都认为重要”可以帮助优先排序，却不能省略效应方向验证。平均模型只有轻微改善，也不表示再加入任意模型就能消除错误；相关模型的共享训练偏差可能一起保留。</p><p>c分别检查eQTL显著性、TSS距离、实际表达和预测变异幅度，排查失败是否只发生于弱遗传信号或低表达基因。强eQTL仍可能负相关，近端位点也有错误，意味着远程调控建模与方向正确是两个需要分别改进的问题。图里的趋势不足以证明失败源头完全是哪一个分子过程，作者将局部TF作用还是增强子到基因映射提出为后续可区分假说，而非已经确定结论。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>四模型对SNHG5的个人预测按主要eQTL剂量着色，三个模型方向与实测相反而Xpresso为正。</td></tr><tr><td>b</td><td>Enformer对其他模型的逐基因个体相关散点，既有同方向也有反方向模式，说明模型间并非一致。</td></tr><tr><td>c</td><td>Enformer相关与eQTL强度、TSS距离、实际表达及预测变异系数的四组关系，即使强eQTL基因也可能负相关。</td></tr></tbody></table><p>阅读说明：本篇正文仅Figure 1和Figure 2；全文出现的Extended Data Fig. 3&#x2F;4等不属于正文主图，候选自动提取中的3和4已排除。</p><h2 id="对我的研究：把错误分解到哪一层"><a href="#对我的研究：把错误分解到哪一层" class="headerlink" title="对我的研究：把错误分解到哪一层"></a>对我的研究：把错误分解到哪一层</h2><p>我会将这篇与ROSMAP基准并读，形成两步诊断。第一步，TE附近变异是否被模型预测为改变局部可及性或结合，而且方向被外部caQTL、等位数据或MPRA支持？第二步，这个局部变化是否能被正确映射到目标基因表达？若第一步好、第二步差，需要改进调控连线；若第一步也差，就不能只扩大远端上下文来解释失败。</p><p>这一分解可以转成纯计算课题：固定未见供体和未见基因组区域，分别比较TE与匹配非TE候选在局部效应方向和目标表达方向上的表现。匹配需包含家族、GC、距离、可及性及可比对性，且不应先看疾病关联再选最有利子集。结果若发现某类TE序列共享方向错误，可进一步以motif与上下文扰动定位计算失败来源；它首先是模型机制问题，只有经独立生物数据支持后才上升到AD调控机制。</p><p>公开Geuvadis可作为流程开发基准，但LCL中的规则不能原样称为脑细胞证据。论文按欧洲人群eQTL选基因，供体却覆盖欧洲及非洲背景；祖源、LD和细胞系状态可能影响泛化，迁移研究应分层核对。把同一供体的两个单倍型或相邻窗口随机切到训练与测试会泄漏个人与序列背景，不能用大批预测样本数掩盖只有数百独立供体的事实。</p><p>我的感悟是，基准论文的深度来自一个简单而必要的问题：模型的目标是否就是我们最终想解释的变化？对AD，我们也应区分遗传造成的风险、病理造成的表达改变，以及细胞比例变化。一个能够预测bulk表达的模型不一定解释病因，更不一定预测疾病进展；每跨一层都需要与该层对应的新证据。</p><p>关于研究资源，我会先下载作者公开的预测表与脚本复现评估，再选择少量基因做自己的前向推理，而不是一开始重复四模型全部大规模计算。核对TSS、链方向、参考组装和分相是第一步；Xpresso的非对称输入尤其需要按负链正确反向互补。公开结果可降低试验成本，但用于最终新模型评价时仍要明确哪些数据曾用于方法选择。</p><p>最后，方向错误并不能靠人为翻转所有负相关基因的模型输出来解决。那样已经使用实测表达挑选基因与符号，变成监督校准；若要提出校准方法，应在开发供体学习规则，在独立测试供体评估，并报告未见基因能否泛化。读者看到相关绝对值时需要知道其符号是否事后选择，这直接关系到模型有没有预测增减的实际能力。</p><p>另一个必要记录是每个基因实际可测表达的噪声上限，避免把不同读数可靠性的差别全归为模型失效。</p>]]></content>
    
    
    <summary type="html">深入读懂PersonalTranscriptome-Huang：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="模型评估" scheme="https://perry.apay.eu.cc/tags/%E6%A8%A1%E5%9E%8B%E8%AF%84%E4%BC%B0/"/>
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="个人基因组" scheme="https://perry.apay.eu.cc/tags/%E4%B8%AA%E4%BA%BA%E5%9F%BA%E5%9B%A0%E7%BB%84/"/>
    
    <category term="基因表达" scheme="https://perry.apay.eu.cc/tags/%E5%9F%BA%E5%9B%A0%E8%A1%A8%E8%BE%BE/"/>
    
  </entry>
  
  <entry>
    <title>87.PersonalExpressionBenchmark-Sasse：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad920057/"/>
    <id>https://perry.apay.eu.cc/posts/ad920057/</id>
    <published>2026-10-04T12:01:27.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:087 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Benchmarking of deep neural networks for predicting personal gene expression from DNA sequence highlights shortcomings</p><p><strong>期刊与年份：</strong> Nature Genetics，2023。<a href="https://doi.org/10.1038/s41588-023-01524-6">论文原文</a>。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 参考基因组训练模型能否真正预测不同个人的表达差异及变异作用方向？</p><h2 id="从漂亮的跨基因预测里提出一个反问"><a href="#从漂亮的跨基因预测里提出一个反问" class="headerlink" title="从漂亮的跨基因预测里提出一个反问"></a>从漂亮的跨基因预测里提出一个反问</h2><p>我对研究思路形成的推测是：如果模型学到了调控逻辑，就应该能在固定基因处解释不同人的DNA为何造成表达差异；然而传统基准通常比较不同基因的平均表达。这两个任务看似都叫“预测表达”，实际变化来源完全不同。作者利用839位ROSMAP供体的配对WGS与皮层RNA，把测试单位从基因位置改成人。论文的贡献不是发布更大模型，而是设计直接挑战个体遗传解释能力的基准。</p><p>Enformer原本输入参考基因组附近长DNA，输出CAGE、ATAC等多种实验轨迹，利用基因组各位置之间的差异训练。本文将每位供体的两条分相序列分别输入，再平均表达预测。为适配皮层RNA-seq，作者还在预训练输出上训练弹性网映射，其标签来自GTEx的跨基因平均表达；这不是在839人WGS上微调整个Enformer。它可以改善读数匹配，却没有直接教网络同一位点的变异方向。</p><p>比较模型PrediXcan则用GTEx个体基因型与表达训练每个基因的弹性网。它只能处理训练中可用变异、缺乏任意新序列解释，却适合本项个体表达任务。这说明“泛用序列模型”与“针对个体变异模型”各有能力边界。基准范式是冻结已有模型、重新定义评估单位，再结合归因追查失败原因。数据来自AD研究队列，并不使这篇成为预测AD诊断或病理进展的模型；研究终点仍是个体间皮层表达。</p><h2 id="Figure-1：跨基因准确性不等于个人表达准确性"><a href="#Figure-1：跨基因准确性不等于个人表达准确性" class="headerlink" title="Figure 1：跨基因准确性不等于个人表达准确性"></a>Figure 1：跨基因准确性不等于个人表达准确性</h2><p><a href="/img/dl-literature/087/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/087/figure-01.webp" alt="Figure 1" width="1130" height="1124" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 2 页，Figure 1。点击图片可查看原图。</em></p><p>a与c是全篇最值得理解的对照：a比较不同基因组位置，c固定一个位置改变个体DNA。b中跨基因相关较高，可能反映网络能区分强与弱启动子等平均规律；它不能为c的个体变异任务提供直接保证。d以DDX11展示真实成功案例，839个点对应839人，说明模型在特定高遗传度位点确能捕获表达差异。e检查哪些变异驱动这一预测，并与精细定位的单个候选一致，把成功从散点关联推进到可解释遗传信号。</p><p>但DDX11不能代替全局评价。它的遗传度与较清晰单驱动结构有利于预测，选择这样的实例有助说明可能性，却不说明普遍性。e的ISM是在模型中改变一个碱基的输出差，不是实际编辑实验；“延伸抑制motif”仍是机制假说。两个等位序列取平均也默认一种汇总方式，未独立解决细胞组成、转录后加工、环境和疾病状态对bulk RNA的影响。</p><p>我认为这张图的教育作用在于先保留模型确实能做好的任务和位点，再提出更严格问题。它没有因为新的评估失败就否认参考基因组预测，而是要求每项能力在对应数据变化上被验证。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>参考基因组不同区域作为训练样本的示意，解释Enformer原任务。</td></tr><tr><td>b</td><td>ROSMAP皮层839人的平均表达对跨基因预测，检查传统总体性能。</td></tr><tr><td>c</td><td>固定一个位点、比较不同个人的评估示意，定义更直接的个体变异测试。</td></tr><tr><td>d</td><td>DDX11在839人中的预测对实际表达，提供个体层面实例。</td></tr><tr><td>e</td><td>DDX11附近个人SNV的ISM效应按等位频率着色，检查哪些变异主导表达预测。</td></tr></tbody></table><h2 id="Figure-2：个体间表达预测的方向与驱动变异"><a href="#Figure-2：个体间表达预测的方向与驱动变异" class="headerlink" title="Figure 2：个体间表达预测的方向与驱动变异"></a>Figure 2：个体间表达预测的方向与驱动变异</h2><p><a href="/img/dl-literature/087/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/087/figure-02.webp" alt="Figure 2" width="1325" height="835" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 3 页，Figure 2。点击图片可查看原图。</em></p><p>a将6,825基因逐个按人与人之间相关评价：平均相关约0.01，显著基因中有195个方向相反。显著负相关意味着模型输出稳定地沿错误方向变化，不是“也有预测能力，所以可以忽略符号”。b的GSTM3是这种失败的直观实例。c与个人遗传数据训练的PrediXcan对照，表明简单模型在合适任务上可以优于大型序列网络；比较集合受PrediXcan可用基因限制，不能把这一结果扩展为所有新变异或所有组织任务的排序。</p><p>d、e追踪方向错误。ISM与输入梯度先近似个人表达预测，再定位少数主驱动SNV；多数基因只由几个变异支配模型差异，错误方向因此可以传递到全基因表达。这里“驱动”严格指驱动模型输出，不自动指生物因果变异。与边际eQTL方向不一致是警示证据，边际eQTL本身受LD影响；反过来，边际效应很大的位点却没有模型效应，也不能单凭这一点判断网络错了。理想核对仍需精细定位或独立扰动。</p><p>f显示许多模型驱动靠近TSS。这提示长输入不等于学会使用远端调控，motif语法也可能未被充分学习。作者没有发现一种简单统一的错误motif，因此不能归纳成“模型把某一个TF全部读反”。敏感性分析涉及输出映射、移位与正反链平均、因果候选子集，降低了仅因轨迹选择或输入对齐出现结论的可能。研究给出了失败现象及证据链，而关于新的训练数据能否彻底解决，仍是后续待测假说。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>6825个基因的个体间相关对统计显著性，检查高平均表达预测是否转化为真实个体差异。</td></tr><tr><td>b</td><td>GSTM3个人表达预测对实测，展示可出现方向错误的强关联实例。</td></tr><tr><td>c</td><td>逐基因Enformer与PrediXcan相关比较，检验通用序列模型对个体训练线性模型的差异。</td></tr><tr><td>d</td><td>GSTM3所有SNV的ISM对eQTL效应，标出驱动SNV并检验符号一致性，解释错误方向来源。</td></tr><tr><td>e</td><td>支持方向的驱动SNV比例对模型个体间相关，连接逐变异错误与整基因表达错误。</td></tr><tr><td>f</td><td>正／负相关基因驱动SNV在TSS附近的数量与位置，检查模型的驱动定位偏好。</td></tr></tbody></table><h2 id="我对AD与TE模型的直接启发"><a href="#我对AD与TE模型的直接启发" class="headerlink" title="我对AD与TE模型的直接启发"></a>我对AD与TE模型的直接启发</h2><p>若想研究AD中TE附近WGS变异，这篇提醒我先确定最终问题是什么。预测一个TE所在峰比另一个峰更开放，与预测同一TE副本在不同供体中因等位差异而更开放，是两个任务。前者高相关不能替后者背书；更不能再跨一步宣称模型揭示病理进展。我们应同时报告跨位点性能、同位点跨供体性能，以及变异方向是否正确。</p><p>一个可执行的纯计算设计是冻结现有序列模型，对脑相关公共队列的候选位点比较个人等位预测与实际供体表达或可及性。只在有足够遗传变异和测量可靠性的位点做方向评估，并用匹配非TE背景、线性基因型模型和简单序列模型作基线。若数据权限尚未获得，不能把ROSMAP文件名或摘要中的人数当已经拥有的训练样本。外部MPRA可以核对局部方向，但其报告体系与内源个体表达仍需区分。</p><p>这篇也让我调整“创新”的标准：严谨揭示当前模型在一个生物学必要任务中的失效，本身可以形成重要研究；不一定要先堆更大模型。一个小网络若经过供体与基因组区域双重留出，在TE变异方向上稳定改善，并能解释改善来自何种上下文，比只在随机切分上提高几个百分点更有价值。</p><p>无监督路线同样要面对这个检验。发现TE调控模块后，应先锁定模块和特征，再在未参与训练的供体或独立队列中检验病理关联；不能利用AD标签反复选择潜空间，随后仍称疾病信号“完全无监督发现”。模块可能主要编码细胞组成、批次或参考序列规律，这些解释需要作为明确对照排查。论文没有证明无监督一定更好，它要求的是目标、划分和结论对齐。</p><p>在评估设计上，疾病队列里的bulk表达还会混入细胞组成与病理相关变化；基因型模型不应被要求解释所有非遗传方差，但也不能用这个理由忽略方向错误。应先以有可靠cis遗传度或精细定位信号的基因建立正对照，再分别检查神经元、胶质细胞和整体组织背景。遗传度估计与模型参数调优也应限于开发数据，最终测试只做一次锁定评价。这样才能分清数据没有足够遗传信号，还是模型把已有信号读错。变异方向的检验还应事先规定等位编码，避免正负号由参考／替代定义互换而产生假错误。</p>]]></content>
    
    
    <summary type="html">深入读懂PersonalExpressionBenchmark-Sasse：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="模型评估" scheme="https://perry.apay.eu.cc/tags/%E6%A8%A1%E5%9E%8B%E8%AF%84%E4%BC%B0/"/>
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="个人基因组" scheme="https://perry.apay.eu.cc/tags/%E4%B8%AA%E4%BA%BA%E5%9F%BA%E5%9B%A0%E7%BB%84/"/>
    
    <category term="基因表达" scheme="https://perry.apay.eu.cc/tags/%E5%9F%BA%E5%9B%A0%E8%A1%A8%E8%BE%BE/"/>
    
  </entry>
  
  <entry>
    <title>86.inDelphi：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad920056/"/>
    <id>https://perry.apay.eu.cc/posts/ad920056/</id>
    <published>2026-10-04T12:01:26.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:086 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Predictable and precise template-free CRISPR editing of pathogenic variants</p><p><strong>期刊与年份：</strong> Nature，2018。<a href="https://doi.org/10.1038/s41586-018-0686-x">论文原文</a>。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 无模板Cas9修复结果能否准确预测，并用于精确纠正致病变异？</p><h2 id="从“修复很随机”转向“预测分布是否偏向一个结果”"><a href="#从“修复很随机”转向“预测分布是否偏向一个结果”" class="headerlink" title="从“修复很随机”转向“预测分布是否偏向一个结果”"></a>从“修复很随机”转向“预测分布是否偏向一个结果”</h2><p>我的推测是，这篇的突破来自改变问题表述：Cas9后出现许多产物，并不意味着每种产物的概率不可预测；如果某段序列使一个修复结果占优势，就可以选择这段序列做精确编辑。作者因此没有要求神经网络把所有细胞变成同一结果，而是预测修复产物分布，并寻找天然序列所支持的高精确候选。疾病相关的微重复恰好为这种思路提供用途：删除额外重复的一份，可能恢复参考序列而不需要外加模板。</p><p>inDelphi是机制约束的混合模型。它列举微同源介导的候选删除，用微同源长度、GC与删除长度学习评分，再将竞争候选归一化；无微同源删除用另一神经模块按长度建模。单碱基插入则由k近邻模块结合切口碱基及删除倾向预测。神经网络并没有替代全部生物学流程，输出也不是任意长度、任意结构变异，而是主要切口附近+1至−60 bp产物。无微同源删除部分首先预测长度集合，不等于完全确定每一种位置的基因型。</p><p>监督来自整合sgRNA—靶序列文库的真实测序，删除模块联合学习删除长度与微同源基因型频率，插入与删除按竞争比例结合。局部序列决定相对偏好，细胞背景还影响总体插入／删除比例。研究范式是机制分解的监督预测加候选设计验证，与仅从motif推断修复结果的规则工具不同；也不是无监督模型。其问题意识值得借鉴：先定义可预测的局部任务，明确哪些复杂事件在任务范围外。</p><h2 id="Figure-1：DNA修复数据支持inDelphi设计"><a href="#Figure-1：DNA修复数据支持inDelphi设计" class="headerlink" title="Figure 1：DNA修复数据支持inDelphi设计"></a>Figure 1：DNA修复数据支持inDelphi设计</h2><p><a href="/img/dl-literature/086/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/086/figure-01.webp" alt="Figure 1" width="646" height="1014" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 2 页，Figure 1。点击图片可查看原图。</em></p><p>a把设计靶序列与对应向导绑定，提供可系统变化的训练实验。b与c比较文库和真实内源位点的修复组成，检验人为短序列背景是否仍覆盖主要事件。它们相近支持文库用途，却不能保证所有内源长程染色质和细胞状态均被保留。d的微同源修复过程解释为何模型列举候选并计算相对竞争，而不是只给每个motif独立分数。e将这一机制落实成三模块及可核对的产物表。</p><p>这里最值得读透的是“模型覆盖率”和“预测精度”必须分开。主要插缺覆盖多数测到事件，仍留下长插入、复杂事件和更大重排；即便范围内预测很准，也不能据此声称一次编辑所有风险都可预测。文库长度与测序方法共同限定了可见事件，超出窗口的损伤不会因为模型没列出就不存在。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>整合向导—靶序列文库测量Cas9修复产物，定义大规模训练实验。</td></tr><tr><td>b</td><td>小鼠胚胎干细胞文库产物的微同源删除、无微同源删除和插入比例，描述修复组成。</td></tr><tr><td>c</td><td>HEK293内源位点的对应组成，检验文库是否代表真实基因组。</td></tr><tr><td>d</td><td>微同源末端连接的切除、退火、去瓣和连接过程，解释删除的序列决定因素。</td></tr><tr><td>e</td><td>模型依据微同源、长度、GC和切口附近碱基预测每种+1到−60插缺频率，说明它预测分布而非单一结果。</td></tr></tbody></table><h2 id="Figure-2：单碱基插入的上下文规则"><a href="#Figure-2：单碱基插入的上下文规则" class="headerlink" title="Figure 2：单碱基插入的上下文规则"></a>Figure 2：单碱基插入的上下文规则</h2><p><a href="/img/dl-literature/086/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/086/figure-02.webp" alt="Figure 2" width="663" height="567" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 3 页，Figure 2。点击图片可查看原图。</em></p><p>a、b分别检验插入碱基是什么与插入发生多少，两者并非同一变量。经常复制PAM前−4位置的碱基提供局部模板线索，A或T背景还偏向较高插入频率。c以简单回归展示位置规律，目的是解释而非代替最终模型。d、e在几个固定低微同源背景中系统改变切口附近碱基，比全基因组统计关联更接近序列干预证据：其余背景保持，产物比例随局部变化而变。</p><p>但这种实验只覆盖特定短背景，不能把某个双碱基组合变成在任何染色质环境都成立的硬规则。低微同源条件也降低了删除竞争，所见插入高比例应连同背景一起解释。我认为这张图说明，合理的实验设计可以让小模型学到可验证的规律；数据变化的方式往往比网络深度更关键。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>插入碱基身份对PAM前第−4位碱基的关系，说明插入常复制局部序列信息。</td></tr><tr><td>b</td><td>不同−4位碱基的单碱基插入频率，检验碱基环境影响。</td></tr><tr><td>c</td><td>预测插入频率的序列logo，定位重要上下文碱基。</td></tr><tr><td>d</td><td>低微同源背景中系统改变−5到−2位的插入频率，直接验证邻近序列作用。</td></tr><tr><td>e</td><td>变化−4／−3位后的频率比较，检查特定双碱基组合规律。</td></tr></tbody></table><h2 id="Figure-3：内源编辑结果预测与高精确位点选择"><a href="#Figure-3：内源编辑结果预测与高精确位点选择" class="headerlink" title="Figure 3：内源编辑结果预测与高精确位点选择"></a>Figure 3：内源编辑结果预测与高精确位点选择</h2><p><a href="/img/dl-literature/086/figure-03.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/086/figure-03.webp" alt="Figure 3" width="650" height="891" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 4 页，Figure 3。点击图片可查看原图。</em></p><p>a先量化内源事件是否落在模型输出空间，b再看基因型，c看长度，d看阅读框。这种由细到粗的评估避免只报一个总相关。预测长度准确不自动意味着恢复了完整参考序列；保持阅读框也不自动保证蛋白功能。d与已有微同源工具比较说明纳入插入与其他删除的重要性，e则验证模型选中的高插入位点优于背景，真正回答工具是否改善实验选择。</p><p>f的全基因组“precision-50”比例是基于模型与特定细胞背景的推算，并不是把全基因组所有位点逐个实验后的成功率。precision分母是主要编辑产物，没有纳入未编辑DNA和任务范围外事件。对疾病候选来说，它适合先筛选，再以实际位点测序确认；不能把这一分数直接称为治疗效率。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>HEK293、HCT116、K562中模型覆盖的修复产物比例，检验主要结果是否包含在任务内。</td></tr><tr><td>b</td><td>各位点具体基因型频率的预测相关，中位数约0.83–0.87，验证分布精度。</td></tr><tr><td>c</td><td>插缺长度频率相关，中位数约0.79–0.85，检验较粗粒度结果。</td></tr><tr><td>d</td><td>阅读框频率预测对实测，inDelphi较仅微同源方法更一致。</td></tr><tr><td>e</td><td>模型选择高单碱基插入位点在U2OS和HEK293T的实测频率超过对应基线，验证设计用途。</td></tr><tr><td>f</td><td>全人基因组向导最常见插入／删除的预测频率分布，估计可获得高单一产物精确度的位点数量。</td></tr></tbody></table><h2 id="Figure-4：无模板精确纠正致病变异"><a href="#Figure-4：无模板精确纠正致病变异" class="headerlink" title="Figure 4：无模板精确纠正致病变异"></a>Figure 4：无模板精确纠正致病变异</h2><p><a href="/img/dl-literature/086/figure-04.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/086/figure-04.webp" alt="Figure 4" width="1296" height="485" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 5 页，Figure 4。点击图片可查看原图。</em></p><p>a利用重复序列给修复提供可恢复参考的删除路径；b比较精确基因型恢复，c比较阅读框恢复，明确两种终点。d通过修复基因缺失或抑制相关通路改变结果分配，为不同末端修复路径竞争提供干预支持，但在实验细胞提高精确度不等于在患者中同样操作已经安全。e、f进一步检查LDLR相关报告细胞的GFP与LDL摄取，使证据从序列正确推进到功能恢复。</p><p>LDLR功能实验使用导入的全长构建，不能与所有疾病位点的文库纠正混为相同强度。论文还在HPS1与ATP7A患者来源成纤维细胞内源位点得到序列纠正，这比合成文库更接近实际疾病背景，但并未成为全身疾病治疗或长期临床效果证据。每一个层次都各有意义；把它们分开反而能看出作者怎样逐步提高验证难度。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>a</td><td>LDLR重复变异通过Cas9末端修复回到野生型序列，示例实测为编辑产物中的65%。</td></tr><tr><td>b</td><td>致病等位序列预测／实测野生型基因型纠正频率，检验精确序列恢复。</td></tr><tr><td>c</td><td>野生型阅读框恢复频率预测／实测，区分恢复框架与恢复完整序列。</td></tr><tr><td>d</td><td>模型预测纠正≥50%的位点在不同修复基因背景的实测频率，检验高精确候选。</td></tr><tr><td>e</td><td>LDLR重复报告细胞编辑前后的流式GFP与LDL摄取，检查修复是否恢复功能。</td></tr><tr><td>f</td><td>对应显微荧光图，验证功能恢复；频率主要以已编辑产物为分母，不能直接理解为全部细胞的纠正率。</td></tr></tbody></table><h2 id="我会学它的机制分解，不把局部可预测误当全局可控制"><a href="#我会学它的机制分解，不把局部可预测误当全局可控制" class="headerlink" title="我会学它的机制分解，不把局部可预测误当全局可控制"></a>我会学它的机制分解，不把局部可预测误当全局可控制</h2><p>对AD的TE研究，可以借鉴“列举有生物学意义的候选状态，再学习竞争概率”。例如在同一家族TE副本中，先定义与调控相关的motif组合和细胞状态，再判断哪些背景影响其相对使用，而不是给所有重复片段一个泛化的异常分数。这需要明确可观察标签和独立验证；不能从TE序列有微同源就推断它导致AD。</p><p>更直接的用途在后续内源验证：如果要删去TE片段或修改附近候选变异，必须预先考虑真实修复产物分布。名义上的“删除一个元件”可能产生许多基因型，若只测向导丰度，不测实际产物，就容易把混合结果写成一个干净的因果干预。对大片段TE删除，这篇+1至−60 bp的模型范围尤其不足，需另行验证结构事件。</p><p>它也提供一个可在普通显存上工作的创新方向：用小网络结合机制特征建立一个狭窄而清楚的问题，并把新增数据用来挑战预测。真正有说服力的创新不一定是参数规模增长，而可以是把过去以为随机的现象变成可预注册、可复现的定量问题。原文实验未随机化或盲法，患者来源验证样本有限；这些局限不会抹去结果，但应保留在解释边界中，避免将方法研究写成普遍可用的治疗方案。</p><p>模型输出的是实验测得分布的近似，重复同一位点测序增加读深不会增加独立靶点数量。因此评估泛化时应留出靶点或序列背景，并把技术读段数、细胞数和独立实验次数分开报告。</p>]]></content>
    
    
    <summary type="html">深入读懂inDelphi：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="CRISPR" scheme="https://perry.apay.eu.cc/tags/CRISPR/"/>
    
    <category term="基因编辑" scheme="https://perry.apay.eu.cc/tags/%E5%9F%BA%E5%9B%A0%E7%BC%96%E8%BE%91/"/>
    
  </entry>
  
  <entry>
    <title>85.BE-Hive：原图、模型逻辑与研究范式</title>
    <link href="https://perry.apay.eu.cc/posts/ad920055/"/>
    <id>https://perry.apay.eu.cc/posts/ad920055/</id>
    <published>2026-10-04T12:01:25.000Z</published>
    <updated>2026-10-05T11:41:38.000Z</updated>
    
    <content type="html"><![CDATA[<!-- dl-literature-import:085 --><style>#article-container td,#article-container th{white-space:normal;overflow-wrap:anywhere}#article-container td:first-child,#article-container th:first-child{width:22%}</style><p><strong>论文题名：</strong> Determinants of Base Editing Outcomes from Target Library Analysis and Machine Learning</p><p><strong>期刊与年份：</strong> Cell，2020。<a href="https://doi.org/10.1016/j.cell.2020.05.037">论文原文</a>。</p><p>范围为正文全部编号主图及其子图，包含流程示意图。Extended Data 和 Supplementary Figures 不在本次范围。页码指阅读时所用 PDF 的文件页码。</p><p><a href="/series/dl-disease-dna-92/">返回专题总目录</a></p><p><strong>本篇问题：</strong> 碱基编辑的效率、副产物和精确纠正结果能否由序列预测并通过工程改进？</p><h2 id="问题从实验失败的细节里长出来"><a href="#问题从实验失败的细节里长出来" class="headerlink" title="问题从实验失败的细节里长出来"></a>问题从实验失败的细节里长出来</h2><p>我的推测是，作者真正要解决的不是再提高一个编辑效率分数，而是实验人员的选择困境：目标碱基在典型窗口内，却可能产生邻近旁观者编辑；窗口较宽的编辑器反而可能在某些序列更精确；少见的颠换副产物也可能成为所需产物。仅用位置规则无法决定最佳编辑器与sgRNA。论文因此先设计覆盖丰富上下文的整合靶序列文库，再让模型学习结果分布，并利用这些规律改造蛋白。</p><p>BE-Hive并不是一个包办所有任务的深度网络。效率模型是梯度提升回归树，输入序列位置、二核苷酸、GC及熔解温度等特征，预测归一化编辑效率；旁观者模型才是深条件自回归网络。它先编码每个可编辑碱基及其左右局部序列，再按顺序预测各位置结果，条件包括前面已生成的编辑状态，用KL散度拟合实际结果分布。不同编辑器与细胞背景对应训练条件，不能假设一个任意新编辑器名称输入后就会可靠泛化。</p><p>这个结构适合组合输出：多个底物的状态可以依赖，独立单点概率相乘会漏掉共同编辑或互斥。模型同时把总编辑概率与编辑后条件产物概率分开，两者相乘才得到目标产物在全部读段中的预测比例。后者通常更稳定，前者还受递送、表达和实验批次影响。研究范式是高通量扰动数据驱动的监督概率建模，再以设计实验和蛋白工程闭环验证。疾病位点为应用对象，不是模型学习疾病致病机制的标签。</p><h2 id="Figure-1：大规模碱基编辑靶位点实验"><a href="#Figure-1：大规模碱基编辑靶位点实验" class="headerlink" title="Figure 1：大规模碱基编辑靶位点实验"></a>Figure 1：大规模碱基编辑靶位点实验</h2><p><a href="/img/dl-literature/085/figure-01.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-01.webp" alt="Figure 1" width="943" height="1525" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 3 页，Figure 1。点击图片可查看原图。</em></p><p>A先把sgRNA与对应靶序列配对整合，测序直接观察多种真实产物；这是预测模型可以成立的数据基础。整合文库控制了序列组合并扩大覆盖，不能完整保留每个原位基因组的染色质背景。B比较多种编辑器的位置谱，典型与非典型突变分开，显示“窗口”并不是硬开关。热图按每种活动的最大值归一化，颜色很深不一定表示该副产物在全部读段里占比很高。因而需要结合绝对效率和相同读数分母理解。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>把成千上万sgRNA—靶序列整合到细胞，编辑、选择并测序，定义高通量训练数据。</td></tr><tr><td>B</td><td>不同编辑器沿protospacer的典型与非典型突变活性热图，展示编辑窗口及副产物的位置依赖。</td></tr></tbody></table><h2 id="Figure-2：编辑结果的序列偏好与插缺"><a href="#Figure-2：编辑结果的序列偏好与插缺" class="headerlink" title="Figure 2：编辑结果的序列偏好与插缺"></a>Figure 2：编辑结果的序列偏好与插缺</h2><p><a href="/img/dl-literature/085/figure-02.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-02.webp" alt="Figure 2" width="948" height="1524" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 5 页，Figure 2。点击图片可查看原图。</em></p><p>A至D用简单回归权重表示局部序列偏好，适合解释上下文促进或抑制编辑，但逐位独立logo并不是最终自回归模型的全部规则。E、F讨论插缺位置、长度与重复来源，为副产物生成路径提供线索；G再比较编辑与插缺比。作者专门用未处理文库排查合成与PCR产生的一碱基错误，并对背景与批次作校正。若没有这些对照，罕见副产物很容易被技术噪声制造出来。高编辑／插缺比也不等于其他旁观者替换或脱靶都安全，这个指标的范围必须限定。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>典型C→T或A→G效率的序列logo，正负权重表示促进或降低编辑。</td></tr><tr><td>B</td><td>CBE引起G·C→A·T转换的序列偏好，检查不同底物环境。</td></tr><tr><td>C</td><td>CBE胞嘧啶颠换的序列偏好，定位非典型产物的上下文。</td></tr><tr><td>D</td><td>ABE意外胞嘧啶编辑的序列偏好，描述跨底物副作用。</td></tr><tr><td>E</td><td>插缺频率按位置和长度的热图，检查副产物的空间分布。</td></tr><tr><td>F</td><td>插入长度与重复次数热图，分析插入形成模式。</td></tr><tr><td>G</td><td>碱基编辑与插缺比率分布，比较编辑器的产物纯度。</td></tr></tbody></table><h2 id="Figure-3：BE-Hive效率与旁观者编辑模型"><a href="#Figure-3：BE-Hive效率与旁观者编辑模型" class="headerlink" title="Figure 3：BE-Hive效率与旁观者编辑模型"></a>Figure 3：BE-Hive效率与旁观者编辑模型</h2><p><a href="/img/dl-literature/085/figure-03.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-03.webp" alt="Figure 3" width="965" height="1526" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 7 页，Figure 3。点击图片可查看原图。</em></p><p>A把预测变成具体实验选择，B、C展示效率树模型的任务与泛化，D、E再展示条件产物分布的深度模型。两种相关系数服务不同问题，不应合称同一个“90%准确率”。F比较成对底物共同或分别编辑的失衡程度，检验自回归是否真正捕获联合依赖，而不只把每个碱基概率排对。失衡还受局部序列偏好影响，不能直接视为纯粹酶过程性测量。G展示DNA与氨基酸层面的结果，因为不同DNA组合有时翻译成同样蛋白序列；这为选择任务提供更贴近用途的终点。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>实验设计决策树，把编辑器及目标选择连接到所需结果。</td></tr><tr><td>B</td><td>预测编辑效率Z分数的模型结构，定义效率任务。</td></tr><tr><td>C</td><td>未见靶点的预测对实测效率，检验泛化。</td></tr><tr><td>D</td><td>条件自回归模型依据序列、向导、编辑器与细胞类型预测每一种旁观者编辑结果频率。</td></tr><tr><td>E</td><td>未见靶点旁观者结果频率的预测表现，验证不止能预测总体效率。</td></tr><tr><td>F</td><td>两底物一起或分别编辑的失衡评分预测对实测，检验模型学到联合编辑依赖。</td></tr><tr><td>G</td><td>BE-Hive网页DNA与氨基酸编辑结果展示，说明如何用预测支持实验选择。</td></tr></tbody></table><h2 id="Figure-4：致病等位基因的精确纠正"><a href="#Figure-4：致病等位基因的精确纠正" class="headerlink" title="Figure 4：致病等位基因的精确纠正"></a>Figure 4：致病等位基因的精确纠正</h2><p><a href="/img/dl-literature/085/figure-04.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-04.webp" alt="Figure 4" width="948" height="1524" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 9 页，Figure 4。点击图片可查看原图。</em></p><p>A将模型预测与新疾病位点文库实测精确度比较；B、C关注旁观者底物多时还能否精确纠正，D提供窗口内单一底物的参照。E、F固定部分底物位置与数量仍见结果差异，说明上下文信息提供了位置规则之外的贡献。G、H中最佳编辑器随目标位置和序列改变，支持逐位点优化，而非统一使用窗口最窄的工具。</p><p>关键分母是“已编辑读段”，不是所有细胞或所有DNA。文库中将致病等位恢复为参考序列也并未测得患者表型恢复。论文选择的疾病SNV注释来自当时数据库版本，在本篇应作为原始应用集合描述；不能把全部历史注释当成当前无争议临床结论。模型纠正序列的能力和疾病机制判断仍是两项工作。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>疾病SNV的预测对实测纠正精确度，验证模型选择能力。</td></tr><tr><td>B</td><td>不同编辑器对多底物及全部疾病位点的纠正频率，分析窗口位置及可用范围。</td></tr><tr><td>C</td><td>包含旁观者底物的疾病位点纠正频率，检验邻近可编辑碱基带来的挑战。</td></tr><tr><td>D</td><td>BE4在C6且无其他旁观者底物的位点结果，给出简单条件参照。</td></tr><tr><td>E</td><td>BE4纠正疾病SNV的结果及预测／真实精确度，检验CBE应用。</td></tr><tr><td>F</td><td>ABE对应疾病SNV纠正及精确度，检验另一编辑类别。</td></tr><tr><td>G</td><td>第5位疾病底物不同编辑器的纠正，观察位点偏好。</td></tr><tr><td>H</td><td>第7位的对应比较，说明最佳编辑器依赖底物位置与上下文。</td></tr></tbody></table><h2 id="Figure-5：用胞嘧啶颠换纠正变异"><a href="#Figure-5：用胞嘧啶颠换纠正变异" class="headerlink" title="Figure 5：用胞嘧啶颠换纠正变异"></a>Figure 5：用胞嘧啶颠换纠正变异</h2><p><a href="/img/dl-literature/085/figure-05.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-05.webp" alt="Figure 5" width="780" height="1520" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 12 页，Figure 5。点击图片可查看原图。</em></p><p>A提出不同C到A、G、T产物的上下文偏好，B用模型挑选富集颠换位点再实验，是从相关规律走向预测设计的验证。C检验颠换提高是否同时增加插缺，两者未表现明显关系，为产物分配而非简单提高损伤量的解释提供支持，但还不直接解析全部修复通路。D、E刻意区分DNA精确度与氨基酸精确度，F分别验证两种预测。蛋白精确度较高可能来自同义副产物归并；同义改变仍可能影响剪接或调控，不能因此一概称无害。此处高精确度属于特定选中靶点集合，不能代表任意颠换位点。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>C分别编辑到A、G、T的纯度序列logo，识别产物类型偏好。</td></tr><tr><td>B</td><td>普通靶点与BE-Hive挑选高颠换靶点的纯度比较，检验模型设计的实验收益。</td></tr><tr><td>C</td><td>碱基编辑／插缺比对颠换纯度，检查提高颠换是否伴随更多插缺。</td></tr><tr><td>D</td><td>DNA基因型精确度对氨基酸精确度，说明不同DNA副产物有时仍产生相同蛋白结果。</td></tr><tr><td>E</td><td>疾病颠换SNV的DNA和氨基酸纠正精确度，评估可用纠正范围。</td></tr><tr><td>F</td><td>颠换纠正预测对实测，分别以DNA及蛋白结果验证模型。</td></tr></tbody></table><h2 id="Figure-6：改造脱氨酶提高颠换纯度"><a href="#Figure-6：改造脱氨酶提高颠换纯度" class="headerlink" title="Figure 6：改造脱氨酶提高颠换纯度"></a>Figure 6：改造脱氨酶提高颠换纯度</h2><p><a href="/img/dl-literature/085/figure-06.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-06.webp" alt="Figure 6" width="1060" height="1157" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 14 页，Figure 6。点击图片可查看原图。</em></p><p>A、B利用酶家族保守位点与结构比对提出工程候选；C看产物纯度，D同时看总体效率，体现工程需要多终点比较。eA3A相关T31A变体提高部分目标的颠换纯度，却带来平均效率下降，若只看C会漏掉实际产量代价。E、F将工程产物放回疾病位点并比较预测与实测，闭合了“规则、改造、应用”链条。保守位点的功能推断因此有真实突变实验支持，但关于磷酸化及修复蛋白如何传递影响的完整机制，仍不能仅凭同源位置和产物变化全部确定。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>腺嘌呤／胞嘧啶脱氨酶家族树，为结构改造选择保守位点。</td></tr><tr><td>B</td><td>AID、A3A、APOBEC1结构比对标出同源T27／S38，定义突变位置。</td></tr><tr><td>C</td><td>eA3A-BE4及位点突变版本的颠换纯度，检验工程改造。</td></tr><tr><td>D</td><td>同一编辑器集合的总体效率，检查纯度改变是否损失活性。</td></tr><tr><td>E</td><td>改造编辑器纠正疾病颠换SNV的DNA／氨基酸精确度，验证应用效果。</td></tr><tr><td>F</td><td>对应预测对实测纠正精确度，检验工程编辑器的结果可预测性。</td></tr></tbody></table><h2 id="Figure-7：减少非期望颠换与编辑窗口权衡"><a href="#Figure-7：减少非期望颠换与编辑窗口权衡" class="headerlink" title="Figure 7：减少非期望颠换与编辑窗口权衡"></a>Figure 7：减少非期望颠换与编辑窗口权衡</h2><p><a href="/img/dl-literature/085/figure-07.webp" style="display:block;text-decoration:none"><img class="nolazyload" src="/img/dl-literature/085/figure-07.webp" alt="Figure 7" width="729" height="1520" style="display:block;max-width:100%;width:auto;height:auto;object-fit:contain;margin:0 auto" loading="lazy" decoding="async"></a></p><p><em>原图来源：所用 PDF 文件第 15 页，Figure 7。点击图片可查看原图。</em></p><p>A至D改进BE4，E至H改进eA3A，分别检查颠换、位置谱、上下文偏好和效率，防止一个指标改善靠牺牲其他指标获得。I以Pareto前沿呈现编辑窗口与产物纯度的权衡：宽窗口可能提高可编辑范围，却增加旁观者；窄窗口不能保证所有位点最精确。图中的单碱基精确度含模拟任务结果，并非每一个候选都已经有相同体内干预。我的理解是，工程优劣必须与目标位点和所需产物一起评估；“最佳编辑器”本来就不是一个脱离任务的固定名称。</p><table><thead><tr><th>子图</th><th>讲的是什么，以及如何理解</th></tr></thead><tbody><tr><td>A</td><td>BE4保守位点突变后的颠换频率，检验EA-BE4降低副产物。</td></tr><tr><td>B</td><td>EA-BE4与BE4在HEK293T的活性位置谱，检查编辑窗口。</td></tr><tr><td>C</td><td>EA-BE4效率序列偏好，说明改造后的上下文依赖。</td></tr><tr><td>D</td><td>EA-BE4与BE4总体效率比较，检查减少副产物的活性代价。</td></tr><tr><td>E</td><td>eA3A-BE5对eA3A-BE4颠换频率，检验另一改造方案。</td></tr><tr><td>F</td><td>两者HEK293T活性位置谱，比较窗口和副产物位置。</td></tr><tr><td>G</td><td>eA3A-BE5效率的序列偏好，描述编辑规律。</td></tr><tr><td>H</td><td>eA3A-BE5对eA3A-BE4效率，检查改造保持活性情况。</td></tr><tr><td>I</td><td>颠换纯度、窗口大小与模拟单碱基精确度的Pareto前沿，说明不存在对所有目标都最佳的编辑器。</td></tr></tbody></table><h2 id="可迁移的是任务分解与实验闭环"><a href="#可迁移的是任务分解与实验闭环" class="headerlink" title="可迁移的是任务分解与实验闭环"></a>可迁移的是任务分解与实验闭环</h2><p>这篇对AD研究最直接的用途在验证阶段：候选变异需要做内源编辑时，先用工具比较sgRNA和旁观者结果，减少“名义上改了一个位点，实际上改了一组位点”的解释困难。它不能从编辑容易与否推断一个变异是否导致AD。TE重复背景还可能带来向导多位点匹配，本文的局部产物模型并不负责完整基因组脱靶评估。</p><p>纯计算研究也可以借用任务分解：分别建模一个TE副本是否处于可及状态，以及条件于可及时的调控模式，再检查是否比直接预测疾病标签更可解释。但这种分解需数据支持其稳定性，不能只因论文采用就默认成立。另一个具体启发是，在生成模型里报告概率覆盖：BE-Hive只探索最可能的产物组合，并把未覆盖概率保守视为不利结果。对我们的候选排序，同样应记录不确定和未支持的部分，而不是把缺失证据自动当无风险。</p><p>这套旁观者网络并不巨大，文中还指出可变形状批次使GPU相对CPU提速有限。它提醒我，可行创新常来自准确的目标定义、控制数据噪声和可验证的输出结构；无需先拥有能容纳超大模型的显卡。</p>]]></content>
    
    
    <summary type="html">深入读懂BE-Hive：正文原图与全部子图、模型输入输出、研究思路、验证证据和阅读启发。</summary>
    
    
    
    <category term="文章研读" scheme="https://perry.apay.eu.cc/categories/%E6%96%87%E7%AB%A0%E7%A0%94%E8%AF%BB/"/>
    
    
    <category term="文献阅读" scheme="https://perry.apay.eu.cc/tags/%E6%96%87%E7%8C%AE%E9%98%85%E8%AF%BB/"/>
    
    <category term="深度学习" scheme="https://perry.apay.eu.cc/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
    
    <category term="逐图解读" scheme="https://perry.apay.eu.cc/tags/%E9%80%90%E5%9B%BE%E8%A7%A3%E8%AF%BB/"/>
    
    <category term="CRISPR" scheme="https://perry.apay.eu.cc/tags/CRISPR/"/>
    
    <category term="基因编辑" scheme="https://perry.apay.eu.cc/tags/%E5%9F%BA%E5%9B%A0%E7%BC%96%E8%BE%91/"/>
    
  </entry>
  
</feed>
