传统机器学习方法在相互作用预测中的应用

说实话,刚接触相互作用预测这个领域时,我第一反应是:这不就是分类问题吗?蛋白和药物结合不结合,说白了就是二分类。后来踩了不少坑才明白,这里面的特征工程、数据不平衡、模型选择,每步都有学问。

今天我们就来聊聊传统机器学习方法。虽然现在深度学习火得不行,但传统方法在某些场景下依然能打——尤其当你手里就几百个样本时,别膨胀,老老实实上随机森林可能比什么GNN都靠谱。

核心思路:从特征到决策

传统机器学习的套路很固定:先提取特征,再训练分类器。蛋白质相互作用也好,药物-靶标结合也罢,本质上都一样。我习惯把流程画成下面这样:

传统机器学习在相互作用预测中的流程 输入数据 特征提取 特征选择 模型训练 评估优化 预测输出 注意:特征选择可以直接用模型内置方法(如树模型的特征重要性),也可以独立做(如卡方检验、互信息)

你看这个流程,从数据到输出,环环相扣。每个环节都有坑,我一个个说。

特征工程:这步做不好,后面全白搭

相互作用预测的特征,一般分三类:

  • 序列特征:氨基酸组成、二肽频率、进化信息(PSSM)
  • 结构特征:表面残基、结合口袋描述符、疏水性分布
  • 组合特征:对接分数、共同邻居、网络拓扑

记得我第一次做PPI预测,直接堆了3000多维特征。跑随机森林时电脑直接卡死,最后发现一堆冗余特征,互信息相关性0.98的那种。从那以后我养成了一个习惯:先做个快速的特征相关性分析。

小技巧:对于离散型特征,用卡方检验;连续型特征用皮尔森相关系数。把相关性>0.95的合并或删除,模型速度能快30%还不掉精度。

常用算法有哪些?

传统方法里,我用的最多的就是这几种:

算法 适合场景 我的经验
随机森林 特征多、样本少 默认参数往往就不错,但注意树的数量别超过500,收益递减
支持向量机 样本量中等、边界清晰 记得做特征缩放,不然结果很惨
XGBoost / LightGBM 大数据量、需要可解释性 特征重要性可以帮你做筛选,比盲目降维靠谱
逻辑回归 基线模型、特征少 输出概率值方便做阈值调优,我常用于快速验证

特征编码:把生物信息变成数字

蛋白序列不能直接丢进模型。得先编码。常见的编码方式我整理了下:

# 常见序列编码示例(Python伪代码)

# 1. 独热编码 (One-hot)
def one_hot_aa(seq):
    aa_dict = {'A':0,'R':1,'N':2,'D':3,'C':4,'E':5,'Q':6,'G':7,
               'H':8,'I':9,'L':10,'K':11,'M':12,'F':13,'P':14,
               'S':15,'T':16,'W':17,'Y':18,'V':19}
    encoding = np.zeros((len(seq), 20))
    for i, aa in enumerate(seq):
        encoding[i, aa_dict[aa]] = 1
    return encoding.flatten()

# 2. 二肽组成 (Dipeptide Composition)
def dipeptide_comp(seq):
    dipeptides = [seq[i:i+2] for i in range(len(seq)-1)]
    all_combinations = [a+b for a in 'ACDEFGHIKLMNPQRSTVWY' 
                        for b in 'ACDEFGHIKLMNPQRSTVWY']
    freq = {k:0 for k in all_combinations}
    for d in dipeptides:
        if d in freq:
            freq[d] += 1
    total = sum(freq.values())
    return [freq[k]/total for k in all_combinations]

# 3. 拟氨基酸组成 (Pseudo Amino Acid Composition)
# 这部分代码较长,需要额外计算理化属性,此处略

⚠️ 我曾经犯过一个错:直接用全序列one-hot编码去训练SVM,维度爆炸到几万维。后来改用了二肽组成+PSSM降维,效果反而更好。所以不要无脑堆特征,要结合模型容量来选。

数据不平衡:正负样本比例可能1:100

在相互作用预测里,负样本往往远多于正样本。比如药物-靶标预测,已知结合的对数可能只有几百,而所有可能的组合是几百万。你想想看,模型要是全预测为负样本,准确率能高达99%以上,但那有个屁用。

解决办法我常用这三种:

  • 下采样:随机抽取和正样本一样多的负样本。简单有效,但会丢失信息。
  • SMOTE过采样:用插值生成合成正样本。我试过在PPI数据集上用,AUC提升0.05左右。
  • 调整类别权重:给正样本更高的惩罚。很多模型(如XGBoost、随机森林)都支持scale_pos_weight参数。
# XGBoost 处理不平衡示例
import xgboost as xgb

# 假设正样本数量为1000,负样本为10000
scale = 10000 / 1000  # 约10

params = {
    'objective': 'binary:logistic',
    'scale_pos_weight': scale,
    'max_depth': 6,
    'eta': 0.1
}

model = xgb.train(params, dtrain, num_boost_round=200)

关键指标别只看准确率。用AUC-ROC、Precision-Recall曲线、F1-score来评估。我个人习惯用PR曲线,因为正样本占比小的时候,PR曲线比ROC更敏感。

模型评估与调参

交叉验证是必须的。但要注意一点:相互作用预测里,同一蛋白质的不同对之间可能存在共享信息,直接用随机划分会导致数据泄露。我建议用留蛋白交叉验证(Leave-One-Protein-Out)。举个例子:

from sklearn.model_selection import GroupKFold

# 假设我们有protein_ids对应每个样本
groups = df['protein_id'].values
gkf = GroupKFold(n_splits=5)

for train_idx, test_idx in gkf.split(X, y, groups):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    # ... 训练评估

这样做的好处是,模型在训练时从未见过测试集中的蛋白质,更接近真实场景。

一个完整的小例子

咱们把上面这些串起来。假设我们要预测两个蛋白是否相互作用。用随机森林+二肽特征:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

# 假设已提取好特征矩阵X (n_samples, n_features)
# 和标签y (n_samples,)

rf = RandomForestClassifier(n_estimators=300, 
                             max_depth=12,
                             class_weight='balanced',
                             random_state=42)

# 五折交叉验证
scores = cross_val_score(rf, X, y, cv=5, scoring='roc_auc')
print(f"平均AUC: {scores.mean():.3f} ± {scores.std():.3f}")

# 训练最终模型
rf.fit(X, y)

# 输出特征重要性前10
importances = rf.feature_importances_
indices = np.argsort(importances)[::-1][:10]
print("Top 10 重要特征索引:", indices)

这个例子虽然简单,但包含了特征、模型、验证全套流程。实际项目中我还会加一步网格搜索调参,不过起步先用默认参数跑通再说。

从我个人经验出发,调参时不要同时调太多参数。先固定树的数量,调max_depth和min_samples_leaf,再调特征选择方式。一步一动,记录日志,别靠感觉乱改。

传统方法的局限性

说了这么多好处,也得提一句缺点。传统方法在特征工程上太依赖人工设计了。你花三个月设计的特征,可能不如一个CNN自动学出来的好。而且当数据量达到十万级以上时,模型容量和训练速度都开始吃紧。

不过——在数据稀缺、需要可解释性、或者要快速出baseline的时候,随机森林和XGBoost依然是利器。别被潮流裹挟,选工具前先看看你的数据长什么样。

嗯,传统方法就聊到这儿。记住一句话:先跑通,再调优,最后再考虑上不上深度。