4、特征选择与降维:过滤式、包裹式与嵌入式方法

做ADMET预测的时候,你有没有遇到过这种情况?特征搞了上百个,模型跑得贼慢,精度还上不去。我刚开始做这个方向时也踩过这个坑——一股脑把所有分子描述符全扔进XGBoost,结果调参调得头皮发麻。后来才明白,特征选择不只是“减维度”,更是帮模型聚焦真正重要的信号。

这一章咱们就聊透三种主流的特征选择套路。说白了,过滤式、包裹式、嵌入式,它们各有脾气,用对了场景事半功倍。

4.1 为什么特征选择对ADMET预测如此重要?

ADMET数据本身就很“脏”。分子描述符之间经常高度相关,比如logP和某些疏水指数,相关性能到0.9以上。冗余特征会引入噪声,让XGBoost的分裂点被带偏。我早年一个肝毒性预测项目,原始特征300多个,做完过滤还剩80个,模型AUC直接从0.82跳到了0.87。你看,不是特征越多越好。

降维则解决另一个问题:特征空间太大,样本量又少(ADMET数据常见几百到几千个分子),容易过拟合。PCA、t-SNE这些线性/非线性方法也能帮忙,但咱们重点还是聊和监督任务结合更紧的特征选择。

4.2 过滤式方法 (Filter Methods)

过滤式方法,说白了就是“先筛再练”。不依赖于任何模型,直接用统计指标给每个特征打分,然后挑得分高的。速度快,适合海量特征的初筛。

常用指标我列个表,项目里经常用到:

方法适用场景优点缺点
方差过滤 (VarianceThreshold)特征值太“平”的剔除极快,无参数依赖没考虑与目标变量的关系
互信息 (Mutual Information)连续/离散特征与分类目标能捕捉非线性关系对离散化敏感
卡方检验 (Chi²)分类特征与分类目标经典,解释性强只适用于非负计数特征
皮尔森相关系数连续特征与连续目标计算简单只捕捉线性关系
我的小贴士: 在ADMET数据里,互信息往往是性价比最高的。我记得有一次做口服生物利用度预测,用互信息筛掉了一大堆冗余拓扑描述符,后面模型训练时间直接砍半。
避坑注意: 相关性 ≠ 因果。我曾经碰到过两个特征互信息很高,但实际都是因为某个隐变量(比如分子量)驱动的。所以过滤完最好再用包裹式或嵌入式验证一下。

4.3 包裹式方法 (Wrapper Methods)

包裹式方法,是把特征子集当“搜索任务”来跑。用一个模型(比如XGBoost本身)评估不同的特征组合,选表现最好的。代表方法就是递归特征消除(RFE)。

它的好处:直接优化最终性能。坏处:慢。特征多的时候,你可能要跑几十轮交叉验证,时间成本吃不消。

我通常只在特征数已经降到了50以下时,才用包裹式做最后的精细筛选。下面是个RFE配合XGBoost的代码片段:

from sklearn.feature_selection import RFECV
import xgboost as xgb

model = xgb.XGBClassifier(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=4,
    subsample=0.8,
    colsample_bytree=0.8,
    use_label_encoder=False,
    eval_metric='logloss'
)

selector = RFECV(
    estimator=model,
    step=1,
    cv=5,
    scoring='roc_auc',
    min_features_to_select=10,
    n_jobs=-1
)

selector.fit(X_train, y_train)
print("最优特征数:", selector.n_features_)
print("选中的特征索引:", selector.support_)

注意step别设太大,不然容易跳过好的组合。我当年设step=5做过一次,结果漏掉了4个关键特征,后来发现了恨不得抽自己。

4.4 嵌入式方法 (Embedded Methods)

嵌入式方法,直接在模型训练过程中内嵌特征选择。XGBoost自带的特征重要性就属于这一类。你训练完之后,调个feature_importances_属性,就能看到每个特征的重要性分数。

XGBoost提供了三种重要性指标:

  • gain:该特征在被使用时带来的平均信息增益。最常用,也最稳定。
  • cover:该特征覆盖的样本数量。偏向于选择出现频率高的特征,但可能忽略小众但关键的特征。
  • frequency:简单计数该特征被用作分裂的次数。容易受树结构影响,我一般不用这个做最终筛选。

我个人的习惯:先用gain排序,取top N个特征,再和过滤式的候选集取交集,这样出来的特征既稳定又有区分度。

import xgboost as xgb

model = xgb.XGBClassifier(
    n_estimators=200,
    max_depth=5,
    learning_rate=0.1
)
model.fit(X_train, y_train)

importance = model.feature_importances_
feature_names = list(X_train.columns)

# 按gain排序
sorted_idx = np.argsort(importance)[::-1]
print("Top 10 features (by gain):")
for i in range(10):
    print(f"{feature_names[sorted_idx[i]]}: {importance[sorted_idx[i]]:.4f}")
实战要点: 嵌入式方法最妙的地方在于,它和XGBoost的调参是联动的。比如你发现某个特征重要性很高,但模型还是欠拟合,可以试着调高subsample或colsample_bytree,让模型更多地看到这个特征。相反,如果某个特征重要性在多次训练中忽高忽低,那它很可能是个噪声,可以放心删掉。

4.5 三种方法对比

维度过滤式包裹式嵌入式
计算成本低高中等
与模型关系无依赖依赖模型内嵌在模型里
特征间关系独立评估考虑组合考虑组合
过拟合风险低高(需交叉验证)中等(受正则化影响)
典型应用阶段初步粗筛精细调优直接产出最终特征集

4.6 实战建议:如何在ADMET项目中组合使用

一般我走三步:

  1. 用过滤式做减法:先用互信息或方差过滤,把特征数从几百砍到50以内,耗时几分钟。
  2. 用嵌入式做排序:跑一版XGBoost看gain,选前20-30个关键特征,同时检查有没有明显的共线性。
  3. 用包裹式做微调:对于特别重要的任务(比如专利申请里的毒性预测),再用RFE在选出的特征上跑一遍,确保没有漏掉好组合。
个人心得: 大多数ADMET项目到第二步就够用了。只有当你发现模型拟合得很好、但验证集波动大时,才需要启动包裹式。别一上来就全自动调优,先把基础打扎实。
特征选择方法决策流程图 原始特征集(≥100) 特征 > 50 ? 过滤式(互信息/方差) 快速粗筛,降到 30-50 特征 嵌入式 / 包裹式 RFE / XGBoost importance 最终特征子集(5-30个) 送给XGBoost调参 常用组合:过滤式粗筛 → 嵌入式排序 → (可选)包裹式精选

嗯,到这里三种方法基本讲透了。下次你做ADMET项目,拿到原始描述符别急着开练——先想想今天聊的这几步。把特征选好,调参才能调出真功夫。