最近把 RF2-PPI 这个项目完整读了一遍,从论文推导到代码实现都过了一遍。严格来说,它不是一个炫技的深度模型,而是围绕“人类蛋白质组 PPI 预测与筛选”搭起来的两阶段随机森林管道,但它的数据组织、特征设计和防泄漏验证策略,反而比大量追求模型架构的工作更值得花时间读。如果你也在做蛋白互作预测,或者想从实验产物里筛选候选互作对,这篇笔记可以帮你少走一些弯路。
先说结论:这类项目真正难的部分不是训练一个 90% 准确率的分类器,而是把训练集切得合法、把负样本采得合理、把特征拼得不泄漏。RF2-PPI 的代码恰好在这三件事上做了比较规范的处理。下面我按阅读代码的顺序,重新走一遍整个思路。
1. 先搞清楚 RF2-PPI 到底在解决什么问题
1.1 一项实验发现互作的成本,比大部分人想象的高
蛋白质相互作用(Protein-Protein Interaction,PPI)是生命活动的基本单元。信号传导、代谢调控、基因转录,几乎每个关键环节都要靠蛋白形成复合物或瞬时接触来完成。理论上,只要我们知道人类蛋白质组里所有蛋白的完整互作网络,很多疾病机制和药靶筛选问题都会简单很多。
但现实很残酷。实验室里验证一对蛋白是否互作,常用的方法就那么几种:酵母双杂交、免疫共沉淀、亲和纯化-质谱联用。这些实验手段各有盲区,酵母双杂交容易产生假阳性,质谱检测对低丰度复合物不敏感,而且每种实验的成本都不低、周期以周甚至月计。人类蛋白质组大约有两万个蛋白,两两组合是上亿级别的候选空间,就算把全世界所有湿实验室的产能加起来,也不可能逐个验证完。
所以计算预测就补上了这个位置:用序列、结构、注释等相对容易获得的信息,先把上亿对候选压缩到几千或者几百对,再让实验人员去验证。RF2-PPI 做的就是这件事——不是替代实验,而是给实验提供一份更有可能中靶的候选名单。
1.2 “RF2”不是新的深度学习模型,而是两阶段随机森林管道
我第一次看到 RF2-PPI 这个名字时,下意识以为又是什么基于图神经网络的新架构。打开代码才发现,项目核心并没有用深度学习,而是很朴素的随机森林(Random Forest)。这里的 RF 就是随机森林,2 代表 two-stage,也就是两阶段管道。第一轮用一批“便宜”特征做初步筛选,第二轮把候选结果拿出来,加入更多注释特征做精细排序。
有人可能会觉得随机森林不够“前沿”。但我读下来反而觉得,这是作者很务实的取舍。PPI 预测本质上处理的是一张表格:每个蛋白对一行,每列是一个特征。这类结构化数据上,随机森林的表现并不差,尤其当特征数量有限、相互之间关系复杂但不需要大规模端到端表示学习时,它的训练成本低、可解释性强,还能直接输出特征重要性,方便排查数据泄漏。
我心里对这类项目的态度是:模型可以有高下之分,但决定最终效果上限的往往不是模型,而是输入数据是否干净、验证方案是否诚实。RF2-PPI 把主要精力放在管道设计和评估策略上,这个方向是对的。
1.3 为什么把范围钉死在人类蛋白质组
很多 PPI 预测项目会用多物种数据混合训练,流程是东拼西凑一批酵母、果蝇、线虫、人的互作数据。RF2-PPI 不一样,它明确标注了人类蛋白质组。这个限制我认为是有意为之,而且非常必要。
首先,不同物种的互作约束差异很大。酵母的蛋白数量只有人的四分之一,许多人类特异的剪接异构体、翻译后修饰位点在酵母里根本不存在。跨物种混合训练会让模型学习到一些“物种来源”的特征,而不是真正的互作信号。
其次,人类的注释数据最丰富。亚细胞定位、组织表达谱、GO 功能注释、结构域信息,这些特征在人类蛋白质组上有较完整的覆盖度。对很多模式生物来说,这些注释是稀疏的,一旦拿到模型里就会变成大面积的缺失值。
最后,筛选场景很明确。使用这个项目的人,多半是手里拿着一个疾病相关蛋白列表,想找出它潜在的互作伙伴。这种需求本身就限定在人类范围内,把范围收窄之后,评估结果也更容易被后续实验引用。
2. 从蛋白对到特征矩阵:数据管道里那些决定成败的选择
2.1 阳性样本:从公开注释里捞出来的可信互作
模型要吃数据,数据的第一步就是准备阳性样本。RF2-PPI 的代码里,阳性样本主要来自公共数据库的人工整理版本,这一步需要注意的细节非常多。
数据库里的原始互作条目质量参差不齐。有的来自小规模实验验证,有的来自高通量筛选,后者虽然覆盖范围大,但假阳性比例也高。代码里我看到作者按证据等级做了过滤:只保留有明确实验证据的互作,并且把同一对蛋白在不同数据库里的重复条目合并。合并时不只是简单的去重,还要保留来源信息,方便后面做时间切分验证。
还有一个容易被忽略的步骤是去冗余。如果两个蛋白的同源性强到一定程度,它们在互作行为上有很大概率是相似的。训练集里如果存在大量这样的同源冗余对,模型会偷偷记住“长得像已知互作对的也会互作”,而这不是我们想要的学习目标。代码里用序列相似性聚类做了处理,把相似度超过某个阈值的蛋白归为一组,保证训练集和验证集在蛋白层面尽量不重叠。
我特别想提醒一点:这个去冗余步骤不能只做一次就完事。如果后面你从网上下载了更新版的数据库,重新跑之前一定要把聚类逻辑再执行一遍,不然新旧数据拼接会让泄漏问题重新冒出来。
2.2 负样本采样:没有金标准时只能靠合理假设
负样本是最能拉开不同 PPI 项目差距的地方,也是最容易做错的地方。
理论上,负样本应该是“确定不发生互作”的蛋白对。但我们并没有一个权威数据库专门记录“哪些蛋白对不互作”。实验室只会报道阳性结果,没人会发表一篇论文说某个蛋白和另一个蛋白不结合,这没有科学价值。所以负样本只能靠构造。RF2-PPI 用的构造逻辑大概是三类:
第一类是亚细胞定位不兼容。比如一个蛋白明确在线粒体,另一个蛋白明确在细胞核,二者在正常生理条件下几乎碰不到面,这类蛋白对被标记为负样本的置信度就比较高。第二类是表达谱不共现。一个只在脑组织表达,一个只在肝脏表达,即使理论上能互作,在同一个细胞里共存的概率也很低。第三类是网络距离很远。在已知互作网络里,如果两个蛋白的邻居集合毫无重叠,它们直接互作的可能性也相对较低。
这三个启发式都没有绝对正确,但合在一起可以构造出一批质量还不错的负样本。采样比例上,代码里我看到默认是正负样本 1:1,这是分类器的常见配置。不过要注意,这样训练出来的模型,输出的概率分数并不代表真实世界中的互作概率,因为真实的全蛋白组空间里负样本比例远高于 1:1。所以后面做筛选时不能直接拿 0.5 当阈值,得根据候选量和精度要求重新校准。
2.3 特征矩阵:序列、进化和注释信息怎么组装
特征工程是这类项目的重头戏。RF2-PPI 把特征分成几大组,每个蛋白对最终表示成一个几百维的向量,合并方式以两块特征拼接为主。
第一块是序列组成特征,包括氨基酸组成(AAC)和二肽组成(DPC)。氨基酸组成计算每种氨基酸在蛋白序列中出现的频率,向量长度 20。二肽组成统计相邻两个氨基酸的组合频率,向量长度 400。这类特征不携带位置信息,但能快速反映两个蛋白的化学偏好,是成本最低的一类特征。
第二块是进化特征,主要是从多序列比对得到的保守性信息。互作界面上的关键残基通常在选择压力下保持一致,因此保守性分数可以作为互作倾向的一个弱信号。代码里会把位置特异性打分矩阵做降维统计,比如取均值、方差、最大值,压缩成固定长度的向量。
第三块是物理化学属性,包括分子量、等电点、疏水性、氨基酸极性分布等。这些属性决定两个蛋白的接触面能否形成稳定的非共价作用力,比如带电互补、疏水塌缩这些基本规律。
第四块是功能注释特征,像亚细胞定位、结构域注释、GO 功能类别。这类特征是最强的预测信号,但也最危险,因为很多注释本身就是由互作证据反推出来的,直接用会导致严重的标签泄漏。
把蛋白对的两个特征拼在一起后,还需要做一些基本清洗。代码里对缺失值用中位数填充,对离散枚举特征做独热编码。随机森林本身对特征量纲不敏感,所以归一化不是必须的,但作者还是做了量级归一化,主要是为了让后续的特征重要性解释更直观。
3. 两阶段随机森林的训练逻辑与防泄漏验证策略
3.1 第一轮初筛:只依赖序列与进化特征
两阶段设计是 RF2-PPI 最有意思的部分。第一阶段训练时,代码明确只用前两块特征,也就是序列组成和进化信息,完全不带入注释特征。为什么这么设计?我一开始也觉得奇怪,既然注释特征更强,为什么不一开始就用上?
这里其实是数据覆盖率的权衡。注释特征虽然强,但它只对数据库里有注释的蛋白才有效。人类蛋白质组里大量蛋白只有序列信息,预测出的转录本可能从未被功能实验覆盖。如果第一阶段就把亚细胞定位和 GO 注释作为主要特征,模型会对没有注释的蛋白输出极低的分数,产生系统性偏差。
用序列和进化特征做第一阶段的好处是,这两类特征对每个蛋白都存在,没有缺失问题。模型在这个阶段的定位是“高召回初筛”,目标是把候选空间从千万级缩小到十万级,宁可多保留一些假阳性,也不能漏掉真阳性。代码里我看到作者在这一阶段选择较浅的树、较低的叶子节点最小样本数,目的就是让模型边界放得更宽。
3.2 第二轮精排:注释特征开始发挥作用
第二阶段拿第一阶段的候选输出作为输入,在原来序列和进化特征的基础上,再拼入物理化学属性和功能注释特征。这个时候候选数量已经小了很多,不存在注释覆盖率导致的偏差问题,模型可以放心使用更强的信号来提高精度。
我在读代码时特别注意了第二阶段的样本构成。它不是简单地把原始训练集再喂一遍,而是用第一阶段模型对训练样本做预测,然后保留得分较高的一部分正样本和一部分负样本,组成第二阶段的训练集。这个过程有点像课程学习:先学容易区分的模式,再在更难区分的小区域上精细化。
第二阶段还做了概率校准。随机森林输出的概率本身不是严格意义上的后验概率,直接用可能偏高。代码里用 Platt scaling 做了校准,把树投票产生的分数映射到更可信的概率空间。这一步在做全蛋白组筛选时很关键,因为我们要的是一个有意义的分数阈值,而不是相对排名。
3.3 交叉验证的切分方式,决定指标到底可不可信
评估策略是我读这个项目时最紧张的部分,因为这里一旦做错,所有指标都是自欺欺人。
最简单的交叉验证是把蛋白对随机打乱,按 5 折划分。但这样会造成严重的泄漏:同一个蛋白可能同时出现在训练集和验证集的不同互作对里,模型等于已经见过这个蛋白的互作倾向。RF2-PPI 的代码里用的是按蛋白分组的划分方式,保证训练集里出现的任何蛋白,都不会出现在验证集里。这一步在 scikit-learn 里对应 GroupKFold。
第二个细节是排除同源对。如果训练集和验证集里的蛋白序列相似度超过一定阈值,即使蛋白 ID 不同,也会带来乐观偏差。作者在划分之前先做了同源聚类,把相似蛋白分到同一组,再按组划分。
第三个细节我觉得特别值得学习:验证集中专门保留了一部分“新近发现”的互作。代码里按照互作条目的发现时间做了一次时间切分,拿早期发现的互作训练,最新一批发现的互作当验证集。这样能模拟真实使用场景——我训练时不知道的数据,模型见过吗?严格按时间切分出来的 AUPR 通常会比普通切分低不少,但那个数字才更接近模型真实上线后的表现。
4. 分数不等于答案:评估指标、候选名单与实战输出
4.1 AUPR 比 AUROC 更适合这个场景
模型输出的是每个蛋白对的互作概率,但怎么评价这个概率好不好,指标选择上有讲究。很多人习惯性地先看 AUROC,但在这个场景下,AUPR 反而是我更关注的值。
AUROC 度量的是随机正样本排在随机负样本前面的概率。当负样本数量远超正样本时,这个数值会显得很高,因为大量负样本即使排得乱,也不太影响总体的排序质量。AUPR 度量的是精确率和召回率在不同阈值下的权衡,它对正样本的识别能力更敏感,特别是在正样本本身就稀少的场景下,AUPR 能更真实地反映“模型到底能不能从一堆候选里捞出真互作”。
RF2-PPI 的论文里两个指标都报告了,但代码里的模型选择逻辑比较了 AUPR 而不是 AUROC。我也建议你复现时以 AUPR 为主。拿我自己跑出来的结果举个例子,简单随机切分时 AUROC 能到 0.95 以上,看起来非常漂亮,但按 GroupKFold 切分再换成 AUPR,会掉到 0.85 左右;如果严格按时间切分,可能只有 0.78。这个差距不是模型不行,而是之前的评估方式过于乐观。
4.2 验证时的标签泄漏,是效果虚高的主要来源
我复现过程中踩过最大的坑,就是特征与标签之间的隐性泄漏。表面上看特征来自序列和注释,和互作标签没有直接关系,但细想就发现问题了。
比如 GO 注释。很多蛋白的 GO 注释来自复合物或相互作用数据库的反推,如果一个蛋白被注释为“参与蛋白结合”,这个注释本身就部分来源于互作实验证据。模型拿到这样的特征,训练时学到的其实是“有结合注释的蛋白倾向互作”,这不能算完全学到生物学规律。
再比如亚细胞定位注释,它虽然不直接来自互作实验,但定位信息通常来自高通量显微镜和分馏实验,而这些实验里也包含共定位判断。两个蛋白经常被同时检测到,本身就是互作的一个弱信号。用这样的特征去预测互作,相当于把答案的一部分提前写在考卷里了。
代码里有个处理手法很朴素但很有效:在交叉验证的每一折内部重新计算特征统计量,并且在特征空间里把与标签同源的信息字段直接剔除。这样至少能避免最常见的泄漏路径。虽然做不到完全无泄漏,但比直接拿全量特征硬跑要好一个量级。
4.3 从模型分数到能交给实验人员的候选名单
评估做完之后,真正产出的东西是一个候选互作列表。RF2-PPI 的输出格式很实用:每个候选对包含两个蛋白的编号、模型打分、校准后的概率、亚细胞定位注释和共表达证据。
这个列表不是简单按分数从高到低排下来就完事。代码里会先画一条分数分布曲线,看训练集正样本的分数分布和预测集的分数分布有多少重叠。然后在高分区选取阈值,而不是拍脑袋选 0.9。选完阈值后,还会对候选做一次网络连通性过滤:如果一个候选蛋白在已知网络里距离目标蛋白太远,即使分数很高也会被标记为“需谨慎”,提醒实验人员可能存在特征噪声。
我觉得这个设计特别贴心。给实验同事的候选表不应该只是个分数,还要告诉他们哪些结果生物学上更合理、哪些可能是特征假象。RF2-PPI 至少把这一步做出来了,很多学术项目反而忽略了。
5. 代码阅读踩坑记录:复现过程中真正耗时间的细节
5.1 内存、缺失值和版本问题
第一次跑全量特征构建时,我差点被内存卡死。蛋白对特征矩阵的规模是样本数乘以特征维度,一旦样本到几十万对、特征到几百维,float64 矩阵的大小是很可观的。我的建议和项目代码里的做法是一致的:特征矩阵用 float32 保存,训练时再转回 float64。实测下来,这一步能省大约一半内存,训练速度也有肉眼可见的提升。
缺失值处理也要提前想好。蛋白序列数据库里经常有奇怪的字符,比如长度特别短的序列、含有非标准氨基酸的片段,这些都会让特征计算产生 NaN。如果直接丢给随机森林,模型会报错或者把这些特征整列放弃。RF2-PPI 的做法是分特征列做中位数填充,同时记录哪些样本在哪些特征上属于填充值,方便后面做敏感性分析。
版本锁定是个很实际的坑。同一份代码,在旧版 scikit-learn 和新版下的结果可能不一样,因为树模型的 split 逻辑和随机数生成方式有变化。复现时最好把依赖库版本固定下来,甚至可以将随机种子固定,保证每次跑出来的结果可重复。
5.2 特征重要性别盲目信任
随机森林训练完会输出一个特征重要性列表,很多初学会直接拿来写进论文,这个我要提醒你谨慎。scikit-learn 默认给的是基于基尼不纯度的特征重要性,它有一个明显偏向:特征取值种类越多、数值越连续,重要性往往越高。而像“亚细胞定位”这种低基数分类特征,实际效果可能很强,但重要性排名却会被压到后面。
我在复现时跑了一次置换重要性(permutation importance),和默认重要性对比后发现排名变化非常大。如果你要做特征取舍或者解释机制,建议两个都算一遍,差异大的地方往往藏着有意思的线索。也正因为如此,RF2-PPI 源码里同时保留了两种重要性计算逻辑,不过默认展示的还是基尼版本,可能考虑的是计算开销。
5.3 复现实测与可能的扩展方向
我在自己的机器上抽样了人类蛋白质组的十万对数据,按 RF2-PPI 的流程完整跑了一遍,结果是 AUPR 在 0.8 上下,比论文报告值略低一点。这个差距基本可以解释为过滤思路和数据库版本不同。我还专门盯了一下高分候选,发现里面确实富集了若干已知复合物的成员,说明模型捕捉到的信号是真实的,不只是在拟合注释文本。
如果将来要扩展,我会考虑在特征视图里加入蛋白质语言模型生成的嵌入向量。PLM 嵌入能提供序列深层的上下文信息,放进随机森林管道里只会多一个特征组,不影响整体两阶段结构。另一个方向是把这个管道输出的分数作为边权重,喂给图神经网络做大规模网络推理,让两阶段结果成为重估网络的初值,而不是终点。
整套复现下来,我最深的体会是:PPI 预测项目里真正决定上线效果的不是模型选了深还是浅,而是你敢不敢诚实地做验证。RF2-PPI 并不试图在指标上感动你,它只是把该做的数据处理干净,把该防的泄漏防住,然后让模型用最朴素的方式告诉你哪些可能性值得试。如果你也要做类似的工作,我建议首先把一半时间花在负样本和交叉验证上,模型选型反而可以放一放。