🎯核心问题:从头测序的PTM盲区

蛋白质组学中的从头肽段测序(de novo peptide sequencing)是指不依赖任何蛋白质数据库,直接从串联质谱(MS/MS)图谱中推断肽段氨基酸序列。传统数据库搜索(如Mascot、MaxQuant)需要预先指定蛋白质库,无法发现数据库中不存在的蛋白质或非标准修饰。而从头测序虽然理论上不受此限制,但面临一个关键挑战:翻译后修饰(PTM)的开放发现

现有从头测序方法大多在训练时固定了氨基酸词表(20种标准氨基酸+少数已知修饰),推理时无法识别训练集之外的修饰类型。这意味着如果你训练时没有见过磷酸化(+79.9663 Da),模型在推理时遇到磷酸化肽段就会出错——它会试图用标准氨基酸去"硬凑"一个不存在的质量差。

RNovA的核心创新在于:将PTM发现与从头测序解耦为两个阶段。第一阶段(PathSearcher)只预测"质量路径"——即相邻氨基酸之间的质量差——而不直接输出氨基酸序列。第二阶段(SeqFiller)在第一阶段发现的质量差模式基础上,将未知质量差注释为PTM,并扩展词表进行序列补全。这种设计使得模型可以零样本地发现并注释训练时未见过的PTM

关键洞察:PTM在质谱中表现为质量偏移(delta mass)。如果你不要求模型直接输出氨基酸,而是输出"质量路径",那么PTM就只是路径上的一个"异常质量块"——它不需要在训练集中出现过,只需要在聚类后被统计检验识别为显著性模式。

🏗️六阶段流水线全景

RNovA的完整流水线由一个Shell脚本RNovA.sh编排,接受三个参数:输入MGF目录、是否使用UniMod注释、Top-K PTM数量。六个阶段严格顺序执行:

# RNovA.sh 核心流程 ./RNovA.sh <in_dir> <useunimod:true|false> <k> # Stage 1: 生成Decoy光谱(用于FDR估计) python decoy_spectrum_generator.py "$in_dir" "$decoy_dir" # Stage 2: PathSearcher推理(深度学习预测质量路径) python Inference_Node.py "$in_dir"/*.mgf "$decoy_dir"/*.mgf # Stage 3: Node/Path级FDR过滤(1% FDR) python FDR_stage1.py "$in_dir" "$decoy_dir" # Stage 4: 聚类+MSA+PTM发现(输出top-k PTM) topk_ptm=$(python workflow.py "$in_dir"/*_001fdr.csv --topk-ptm "$k" \ | awk -F': ' '/^topk_ptm_annotation: /{print $2}') # Stage 5: SeqFiller推理(用扩展词表补全序列) python Inference_Sequence.py "$in_dir"/*.mgf "$decoy_dir"/*.mgf \ "A;C|UniMod:4;D;E;F;G;H;K;L;M;N;P;Q;R;S;T;V;W;Y;${topk_ptm}" # Stage 6: Sequence级FDR过滤+路径合并 python FDR-stage2.py "$in_dir" "$decoy_dir"

整个流水线的设计哲学是"先粗后细,逐步过滤":PathSearcher给出粗略的质量路径,FDR Stage 1过滤掉低置信度的节点和路径,聚类从多条路径中发现共性PTM模式,SeqFiller用发现的PTM补全氨基酸序列,最后FDR Stage 2在序列层面做最终过滤。

🎭Stage 1:Decoy光谱生成

FDR(False Discovery Rate)估计需要"假数据"作为参照。在蛋白质组学中,这通常通过生成"Decoy光谱"实现——一种保留光谱整体统计特性但打乱生物学信号的人造光谱。RNovA的Decoy生成策略简单而有效:

# decoy_spectrum_generator.py 核心逻辑 sampling_rate = 0.40 # 保留40%原始峰 num_sampling = int(num_peaks * sampling_rate) num_noise = num_peaks - num_sampling # 1. 从原始光谱中随机采样40%的峰 sampling_peaks = random.sample(peak_list, num_sampling) # 2. 从全局峰分布中采样60%的噪声峰 noise_peaks = random.sample(peaks_distr, num_noise) # 3. 合并并按m/z排序 sorted_peaks = sorted(sampling_peaks + noise_peaks, key=lambda x: x[0])

关键设计决策:

生成的Decoy MGF文件命名为{original}.decoy_random0.40.mgf,后续每个阶段都会对Target和Decoy分别推理,通过Target-Decoy竞争估计FDR。

🔍Stage 2:PathSearcher推理

PathSearcher是RNovA的第一阶段深度学习模型。它的输入是MS/MS光谱,输出是质量路径(mass path)——一系列前体质量值,表示肽段中相邻残基之间的累积质量差。

与传统的从头测序模型(如DeepNovo、Casanovo)不同,PathSearcher不直接预测氨基酸,而是预测"节点"(node):每个节点对应一个质量位置,附带一个置信度分数。一条完整的路径形如:

# PathSearcher输出格式 scan, node_mass, node_score 1, 0.0000;57.0215;147.0684;246.1320;345.1956;..., 0.0000;0.9523;0.8812;0.9101;0.8734;...

其中node_mass是累积质量(前缀质量),node_score是模型对每个节点的置信度。相邻节点质量差即为一个残基或修饰的质量。PathSearcher同时对Target和Decoy光谱进行推理,输出结构完全相同,供下一阶段FDR估计使用。

这种"质量路径"表示的关键优势是修饰无关性:无论一个位置是标准氨基酸还是带修饰的氨基酸,PathSearcher只需要正确预测质量位置,不需要知道具体是什么残基。PTM的识别被推迟到后续的聚类和注释阶段。

📊Stage 3:Node/Path双层FDR过滤

FDR Stage 1是RNovA质量控制的第一个关卡,采用Target-Decoy Competition(TDC)策略在两个层级进行过滤:

3.1 Node级FDR

对每个光谱,比较Target和Decoy路径的逐节点分数。分数更高的一方"获胜",败者被丢弃。所有获胜的节点分数被全局排序,通过二分搜索找到使FDR=1%的分数阈值:

# FDR_stage1.py Node级竞争 t_score = [float(i) for i in target['score'].split(';')][1:-1] d_score = [float(i) for i in decoy['score'].split(';')][1:-1] # Target-Decoy竞争:分数高者获胜 if sum(t_score) > sum(d_score): tagged += [(i, 0) for i in t_score] # 0 = target else: tagged += [(i, 1) for i in d_score] # 1 = decoy # 二分搜索找1% FDR阈值 merged = sorted(tagged, key=lambda t: t[0], reverse=True) tags = [x[1] for x in merged] # FDR = decoy_count / total_count 在阈值以上 while fdr != 0.01: if fdr > 0.01: mid = mid // 2 elif fdr < 0.01: mid = mid + mid // 2 fdr = round(sum(tags[:mid]) / len(tags[:mid]), 2)

3.2 Path级FDR

Node级过滤后,对每条路径计算"过滤后路径分数"——只累加高于Node阈值的节点分数。再次进行Target-Decoy竞争和二分搜索,找到Path级1% FDR阈值。只有同时通过两个阈值的路径才会被保留:

# 双阈值过滤 mask = node_score > threshold_score # Node级过滤 node_mass, node_score = node_mass[mask], node_score[mask] if sum(node_score[1:-1]) < threshold_path_score: # Path级过滤 continue # 丢弃

这种双层过滤的设计理念是:Node级FDR过滤掉低置信度的单个节点,Path级FDR过滤掉整体质量不高的路径。一个路径可能包含几个高置信度节点,但如果大多数节点都很差,整条路径仍然不可信。

🧮Stage 4:聚类与PTM发现

这是RNovA最核心的创新阶段,由workflow.py实现,包含三个子步骤:CD-HIT聚类→MSA填充→PTM发现。

4.1 CD-HIT风格聚类与统计显著性

通过FDR Stage 1的路径被转换为"质量差序列"(相邻节点质量差),然后进行聚类。聚类的目的是找到共享相同PTM模式的肽段群体——如果多条肽段在相同位置都有相似的质量偏移,这个偏移很可能是一个真实的PTM而非随机噪声。

RNovA使用CD-HIT风格的贪心聚类:按序列长度降序排列,每条序列与已有聚类代表(representative)进行比对,如果比对分数统计显著(p ≤ 1e-4)则归入该聚类,否则创建新聚类。

统计显著性检验的关键是零分布(null distribution)的构建。RNovA不假设比对分数服从某个解析分布,而是通过N-gram重采样从真实数据中构建经验零分布:

# null_background_ngram.py 零分布构建 # 1. 从真实质量序列中提取6-15长度的n-gram片段 ngrams_by_len = build_ngram_pools(nodes_mass, min_ngram=6, max_ngram=15) # 2. 对每个有效长度L,生成50,000对合成序列 for L_eff in range(L_min, L_max + 1): for _ in range(target_per_L): # 50,000次 a = sample_seq_from_ngrams(L_eff, ngrams_by_len, rng) b = sample_seq_from_ngrams(len_b, ngrams_by_len, rng) s, _ = score_fn(a, b) # NW比对分数 scores_per_L[L_eff].append(float(s)) # 3. 排序后用于p值查找 scores_per_L_sorted[L_eff] = np.sort(np.array(lst))

N-gram重采样的精妙之处在于:合成序列保留了真实数据的局部质量模式(因为n-gram来自真实序列),但全局序列结构被打乱(因为n-gram是随机拼接的)。这产生了一个合理的零假设:如果两条真实序列的比对分数在这个零分布中极不可能出现(p ≤ 1e-4),那么它们的相似性不太可能由随机导致。

比对核心是Mass-Tag Aware Needleman-Wunsch算法(nw_masstag_numba),使用Numba JIT加速。与标准NW不同,它不逐字符匹配,而是允许质量块匹配——一段连续的残基可以被当作一个"块"与另一段的"块"比较,只要总质量差在容差范围内(0.02 Da)。这使得比对可以容忍插入/缺失和修饰:

# nw_masstag_numba 核心思想 # 标准NW: 逐位置匹配 A vs A # Mass-Tag NW: 块匹配 [A, B] vs [C] 如果 mass(A)+mass(B) ≈ mass(C) # 参数 mass_threshold = 0.02 # 质量容差(Da) max_block_mass = 600.0 # 最大块质量(限制窗口大小) MATCH = 2; MISMATCH = -1; GAP_OPEN = -4; GAP_EXTEND = -1

4.2 MSA与Gap填充

聚类完成后,对每个聚类内的序列进行多重序列比对(MSA)。以聚类代表(center)为参考,用Token级NW将所有序列对齐到同一坐标系,然后通过共识投票确定每个位置的共识氨基酸:

# workflow.py MSA与填充 # 1. Token级NW对齐到center for toks in token_seqs: aln_a, aln_b = nw_tokens(center, toks) aligned_token_seqs.append(aln_b) # 2. 共识投票 consensus = build_consensus_bases(aligned_token_seqs) # 3. 单氨基酸填充:质量差→AA(+delta)表示 filled = single_aa_fill(aligned_token_seqs, consensus, mass_zero_tol=0.02, single_aa_mass_tol=0.5, max_abs_delta=150.0) # 4. 折叠块填充:多残基gap修复 filled2 = collapsed_block_fill(filled, block_mass_tol=0.5)

填充过程将"质量-only"的tag(即只知道质量不知道氨基酸的位置)转换为AA(+delta)格式——例如K[42.0106]表示赖氨酸上有一个+42.01 Da的修饰。这个delta mass就是PTM候选。

4.3 PTM发现与注释

从所有填充后的序列中提取PTM候选,统计频率,然后通过DBSCAN一维聚类(eps=0.02 Da)合并质量相近的PTM,最终输出Top-K PTM:

# workflow.py PTM发现 # 1. 统计PTM频率 for p in ptms: if p['delta_mass'] is not None: ptm = p['residue'] + '[' + str(p['delta_mass']) + ']' ptm_freq[ptm] += 1 # 2. DBSCAN聚类合并相近质量 db = DBSCAN1D(eps=0.02, min_samples=1) labels = db.fit_predict(masses) # 同一cluster内的PTM合并为一个,取质量均值 # 3. 按频率排序输出top-k print("topk_ptm_annotation:", ";".join(list(ptm_freq_clustered.keys())[:topk]))

如果启用了UniMod注释(--use-unimod),还会查询UniMod数据库将delta mass映射到已知修饰名称,如K|UniMod:1(乙酰化)。输出的Top-K PTM字符串直接传递给SeqFiller作为扩展词表。

✏️Stage 5:SeqFiller序列补全

SeqFiller是RNovA的第二阶段深度学习模型。它的输入是原始MS/MS光谱加上Stage 4发现的Top-K PTM作为扩展词表。词表格式为分号分隔的氨基酸和PTM:

# SeqFiller词表示例 "A;C|UniMod:4;D;E;F;G;H;K;L;M;N;P;Q;R;S;T;V;W;Y;M[16.019];K[42.011];N[0.984]" # 标准AA + Carbamidomethyl(C) + Stage 4发现的PTM

SeqFiller的任务是将PathSearcher输出的"质量路径"补全为完整的氨基酸序列。它利用PathSearcher已经确定的质量位置作为约束,在每个位置从扩展词表中选择最可能的氨基酸或修饰氨基酸。这种两阶段设计使得SeqFiller可以识别训练时未见过的PTM——只要Stage 4发现了该PTM的质量模式,它就会被加入词表供SeqFiller使用。

SeqFiller同样对Target和Decoy光谱分别推理,输出格式为:

# SeqFiller输出格式 scan, sequence, score 1, A K[42.011] G V M[16.019] L S E, 0.95;0.88;0.91;0.87;0.92;0.85;0.90;0.89

注意序列中的空格分隔——每个token是一个氨基酸或修饰氨基酸,分数是模型对每个token的置信度。

🛡️Stage 6:Sequence级FDR与路径合并

FDR Stage 2是最终的质量控制关卡,结构与Stage 1类似但在氨基酸级别序列级别进行双层FDR过滤,并额外执行路径合并——将SeqFiller的序列输出与PathSearcher的节点质量对齐合并:

6.1 双层FDR

与Stage 1相同,先在氨基酸级别(每个token的分数)进行Target-Decoy竞争和1% FDR阈值搜索,再在序列级别(token分数之和)进行第二次过滤。不同之处在于分数中可能包含-inf(表示模型对该token无预测),需要过滤:

# FDR-stage2.py 处理-inf t_score = [float(i) for i in target['score'].split(';') if i != '-inf'] d_score = [float(i) for i in decoy['score'].split(';') if i != '-inf']

6.2 路径合并

这是Stage 6独有的步骤。SeqFiller输出的序列是氨基酸级别的,而PathSearcher输出的节点是质量级别的。合并的目的是用PathSearcher的高置信度节点来验证SeqFiller的序列

# FDR-stage2.py 路径合并 # 1. 计算SeqFiller序列的前缀质量 prefix_mass = ResidueOnlyPeptide(seq).prefix_mass # 2. 与PathSearcher节点质量匹配(容差0.02 Da) merge_flag = (np.abs(prefix_mass[None,:] - path_fdr_result[i][:,None]) < 0.02).any(0) # 3. 按节点边界将序列分块 for a, b, c in zip(merge_flag, seq, score): if a: # 节点边界:分块结束 new_seq.append(seq_temp) new_score.append(score_temp) seq_temp = [] else: # 非边界:继续累积 seq_temp.append(b)

合并后,每个序列被分成多个"块"(block),每个块对应PathSearcher的一个高置信度节点。如果一个块的SeqFiller分数低于AA级FDR阈值,该块会被替换为质量-only表示(只保留总质量,不指定氨基酸),表示"这段序列我们不自信,只知道它的质量"。最终输出中,高置信度块以氨基酸序列呈现,低置信度块以质量值呈现,形成一种部分解析的肽段序列

💡关键工程决策

9.1 为什么用N-gram零分布而非解析分布?

传统的统计检验假设数据服从某个已知分布(如正态分布),然后计算p值。但质谱质量差序列的比对分数没有已知的解析分布——它取决于氨基酸组成、修饰类型、仪器分辨率等多种因素。N-gram重采样从数据本身构建零分布,不依赖任何分布假设,更加稳健。

9.2 为什么用Numba而非多进程?

聚类阶段需要数万次NW比对,是计算瓶颈。RNovA选择用Numba JIT编译而非Python多进程,原因是:

9.3 为什么DBSCAN聚类PTM而不是固定质量窗口?

不同PTM的delta mass精度不同(如氧化+15.995 vs 磷酸化+79.966),固定窗口可能导致过度合并或遗漏。DBSCAN基于密度自动发现聚类,eps=0.02 Da的容差与质谱仪器的质量精度一致,既不会过度合并不同修饰,也不会将同一修饰的测量噪声分成多类。

9.4 为什么40% Decoy采样率?

采样率过高(如80%),Decoy与Target太相似,FDR估计偏保守(低估真实FDR);采样率过低(如20%),Decoy信号太弱,Target-Decoy竞争失去区分力。40%是一个经验平衡点——保留足够峰维持光谱统计特性,同时破坏足够信号使Decoy不含真实的肽段信息。

⚠️局限与展望

未来方向可能包括:(1) 端到端联合训练PathSearcher和SeqFiller;(2) 用Transformer替代CD-HIT聚类;(3) 引入迁移学习从其他蛋白质组学数据预训练零分布;(4) 支持未知修饰的从头质量注释(不依赖UniMod)。

📄项目信息

# RNovA 流水线结构 RNovA/ ├── RNovA.sh # 六阶段编排脚本 ├── decoy_spectrum_generator.py # Stage 1: Decoy生成 ├── RNovA_PathSearcher_Inference/ # Stage 2: PathSearcher模型 │ └── Inference_Node.py ├── FDR_stage1.py # Stage 3: Node/Path FDR ├── workflow.py # Stage 4: 聚类+MSA+PTM发现 ├── src/ │ ├── clustering.py # CD-HIT聚类 + Mass-Tag NW │ ├── null_background_ngram.py # N-gram零分布构建 │ ├── alignment.py # Token级NW + Gap填充 │ ├── fill_PTM.py # UniMod注释 │ └── DBSCAN1D.py # PTM质量聚类 ├── RNovA_SeqFiller_Inference/ # Stage 5: SeqFiller模型 │ └── Inference_Sequence.py └── FDR-stage2.py # Stage 6: Sequence FDR + 合并 # 运行 ./RNovA.sh ./data/mgf/ true 5

本文基于 RNovA 代码库的技术分析,旨在为蛋白质组学研究者和AI开发者提供系统级理解。RNovA 展示了将深度学习与传统统计方法(Target-Decoy FDR、经验零分布、CD-HIT聚类)结合的优雅设计——在保持严格质控的同时实现零样本PTM发现。