生信面试机器学习题精选¶
一句话概述:生信面试中机器学习题集中在监督学习(随机森林/SVM/逻辑回归)、模型评估(AUC/交叉验证)、特征选择和过拟合防控四个方向,重点是理解"什么时候用什么模型"和"怎么判断模型好不好"。
核心知识点速查表¶
| 概念 | 白话解释 |
|---|---|
| 监督学习 | 有标签的数据(如"肿瘤/正常")训练模型 |
| 无监督学习 | 没标签,发现数据本身的结构(如聚类) |
| 过拟合 | 模型记住了训练数据的噪声,泛化能力差 |
| 交叉验证 | 把数据分成K份,轮流做训练和测试 |
| AUC-ROC | 模型区分能力的综合指标(0.5=随机,1=完美) |
| 特征选择 | 从很多特征中挑出最有用的 |
| 正则化 | 给模型加惩罚项防止过拟合 |
| 集成学习 | 组合多个弱模型变成强模型(如随机森林) |
一、监督学习模型(高频考点)¶
逻辑回归(Logistic Regression)¶
# ========== 逻辑回归:最简单的分类模型 ==========
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
import numpy as np
# 模拟数据:50个基因的表达量,预测疾病/健康
np.random.seed(42)
n_samples = 200
n_features = 50
X = np.random.randn(n_samples, n_features) # 特征矩阵
y = (X[:, 0] + X[:, 1] * 0.5 + np.random.randn(n_samples) * 0.5 > 0).astype(int) # 标签
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y # stratify保持类别比例
)
# 训练逻辑回归
lr = LogisticRegression(
penalty='l2', # L2正则化(Ridge)
C=1.0, # 正则化强度(越小越强)
max_iter=1000, # 最大迭代次数
random_state=42
)
lr.fit(X_train, y_train) # 拟合
# 评估
y_pred = lr.predict(X_test) # 预测类别
y_prob = lr.predict_proba(X_test)[:, 1] # 预测概率
print(classification_report(y_test, y_pred)) # 分类报告
print(f"AUC: {roc_auc_score(y_test, y_prob):.3f}") # AUC值
# 面试要点:
# - 逻辑回归输出概率,可以调阈值
# - 系数(coef_)的大小和方向有可解释性
# - L1正则化(Lasso)可以做特征选择(系数变0)
# - L2正则化(Ridge)让系数变小但不为0
随机森林(Random Forest)¶
# ========== 随机森林:生信最常用的ML模型 ==========
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import matplotlib.pyplot as plt
# 训练随机森林
rf = RandomForestClassifier(
n_estimators=500, # 500棵树
max_depth=None, # 树的最大深度(None=不限制)
min_samples_leaf=5, # 叶节点最少样本数
max_features='sqrt', # 每次分裂考虑√n个特征
random_state=42,
n_jobs=-1 # 使用所有CPU核
)
rf.fit(X_train, y_train)
# 交叉验证评估
cv_scores = cross_val_score(rf, X, y, cv=5, # 5折交叉验证
scoring='roc_auc') # AUC评分
print(f"5-fold CV AUC: {cv_scores.mean():.3f} ± {cv_scores.std():.3f}")
# 特征重要性(随机森林最大优势!)
importances = rf.feature_importances_ # 每个特征的重要性
feature_names = [f"Gene_{i}" for i in range(n_features)]
sorted_idx = np.argsort(importances)[::-1][:20] # Top 20
plt.figure(figsize=(10, 6))
plt.barh(range(20),
importances[sorted_idx][::-1], # 水平柱状图
align='center')
plt.yticks(range(20),
[feature_names[i] for i in sorted_idx][::-1])
plt.xlabel('Feature Importance')
plt.title('Top 20 Important Features (Random Forest)')
plt.tight_layout()
plt.savefig("feature_importance.pdf")
# 面试要点:
# 1. 为什么选随机森林?
# - 不需要特征标准化
# - 天然输出特征重要性
# - 不容易过拟合(集成多棵树)
# - 对样本量小、特征多的生信数据特别好
# 2. 缺点:模型是黑盒(单棵树可解释,森林不太好解释)
# 3. 参数调优:n_estimators(越多越好但收益递减),max_depth,min_samples_leaf
SVM(支持向量机)¶
# ========== SVM:小样本分类的利器 ==========
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# SVM需要特征标准化!
svm_pipeline = Pipeline([
('scaler', StandardScaler()), # Z-score标准化
('svm', SVC(
kernel='rbf', # 高斯核函数
C=1.0, # 正则化参数
gamma='scale', # 核函数参数
probability=True, # 输出概率(计算AUC用)
random_state=42
))
])
svm_pipeline.fit(X_train, y_train)
y_prob_svm = svm_pipeline.predict_proba(X_test)[:, 1]
print(f"SVM AUC: {roc_auc_score(y_test, y_prob_svm):.3f}")
# 面试要点:
# - SVM找到最优分割超平面(最大化间隔)
# - 核技巧(kernel trick)把数据映射到高维空间使之线性可分
# - 常用核函数:linear(线性)、rbf(高斯)、poly(多项式)
# - SVM必须做特征标准化(对尺度敏感)
# - 样本少特征多时效果好(如微阵列数据)
二、模型评估(必考)¶
混淆矩阵与评估指标¶
# ========== 模型评估指标 ==========
from sklearn.metrics import (confusion_matrix, accuracy_score,
precision_score, recall_score,
f1_score, roc_curve, auc)
# 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
# [[TN, FP], TN=真阴, FP=假阳(误报)
# [FN, TP]] FN=假阴(漏报), TP=真阳
# 评估指标计算
accuracy = accuracy_score(y_test, y_pred) # 准确率 = (TP+TN)/总数
precision = precision_score(y_test, y_pred) # 精确率 = TP/(TP+FP) "你说阳性的有多少是对的"
recall = recall_score(y_test, y_pred) # 召回率 = TP/(TP+FN) "真阳性中你找到了多少"
f1 = f1_score(y_test, y_pred) # F1 = 2×P×R/(P+R) 精确率和召回率的调和均值
print(f"Accuracy: {accuracy:.3f}")
print(f"Precision: {precision:.3f}")
print(f"Recall: {recall:.3f}")
print(f"F1 Score: {f1:.3f}")
# ROC曲线和AUC
fpr, tpr, thresholds = roc_curve(y_test, y_prob) # FPR, TPR, 阈值
roc_auc = auc(fpr, tpr) # AUC值
plt.figure(figsize=(6, 6))
plt.plot(fpr, tpr, 'b-', label=f'AUC = {roc_auc:.3f}') # ROC曲线
plt.plot([0,1], [0,1], 'r--', label='Random (AUC=0.5)') # 随机线
plt.xlabel('False Positive Rate (1-Specificity)') # X轴
plt.ylabel('True Positive Rate (Sensitivity)') # Y轴
plt.title('ROC Curve')
plt.legend()
plt.savefig("roc_curve.pdf")
# AUC解读:
# AUC = 0.5 → 随机猜(模型没用)
# AUC = 0.7-0.8 → 一般
# AUC = 0.8-0.9 → 好
# AUC = 0.9-1.0 → 很好
# AUC = 1.0 → 完美(可能过拟合!)
# 面试要点:
# - 不平衡数据时准确率不可靠(99%阴性,全猜阴性准确率99%)
# - 不平衡数据用Precision/Recall/F1和PR-AUC更好
# - AUC不受阈值影响,是综合评价指标
交叉验证¶
# ========== 交叉验证:防止过拟合的关键 ==========
from sklearn.model_selection import (cross_val_score,
StratifiedKFold,
LeaveOneOut)
# 5折分层交叉验证(最常用)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(rf, X, y,
cv=skf, # 5折分层
scoring='roc_auc') # AUC评分
print(f"5-fold CV: {cv_scores.mean():.3f} ± {cv_scores.std():.3f}")
# 留一法交叉验证(样本量很小时用,如n<30)
loo = LeaveOneOut()
loo_scores = cross_val_score(rf, X, y,
cv=loo,
scoring='accuracy')
print(f"LOO CV Accuracy: {loo_scores.mean():.3f}")
# 嵌套交叉验证(最严格,同时调参+评估)
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [100, 500],
'max_depth': [5, 10, None],
'min_samples_leaf': [1, 5, 10]
}
# 内层CV:调参
inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
grid_search = GridSearchCV(rf, param_grid,
cv=inner_cv,
scoring='roc_auc',
n_jobs=-1)
# 外层CV:评估
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
nested_scores = cross_val_score(grid_search, X, y,
cv=outer_cv,
scoring='roc_auc')
print(f"Nested CV AUC: {nested_scores.mean():.3f} ± {nested_scores.std():.3f}")
# 面试关键错误(千万别犯):
# ×错误:先用全部数据做特征选择,再交叉验证 → 数据泄露!
# ✓正确:特征选择必须在交叉验证的训练集内部做
# 这叫"信息泄露"(data leakage),是面试最常考的陷阱
三、特征选择(生信特别重要)¶
# ========== 特征选择:从20000个基因中选有用的 ==========
# 方法1:单变量过滤(最快)
from sklearn.feature_selection import SelectKBest, f_classif
selector = SelectKBest(f_classif, k=50) # 选前50个特征
X_selected = selector.fit_transform(X, y) # 选择特征
selected_mask = selector.get_support() # 哪些被选中
print(f"Selected features: {sum(selected_mask)}")
# 方法2:基于模型的特征选择(递归特征消除RFE)
from sklearn.feature_selection import RFE
rfe = RFE(estimator=rf, n_features_to_select=20, step=5)
rfe.fit(X_train, y_train) # 训练
print(f"Selected features: {sum(rfe.support_)}")
print(f"Feature ranking: {rfe.ranking_}") # 排名
# 方法3:L1正则化(Lasso,系数为0的特征被剔除)
from sklearn.linear_model import LogisticRegression
lasso = LogisticRegression(penalty='l1', C=0.1,
solver='saga', max_iter=5000)
lasso.fit(X_train, y_train)
selected = np.where(lasso.coef_[0] != 0)[0] # 非零系数的特征
print(f"Lasso selected: {len(selected)} features")
# 方法4:SHAP值(模型解释+特征重要性,面试加分)
# pip install shap
import shap
explainer = shap.TreeExplainer(rf) # 创建解释器
shap_values = explainer.shap_values(X_test) # 计算SHAP值
# SHAP summary plot(最好的特征重要性可视化)
shap.summary_plot(shap_values[1], X_test,
feature_names=feature_names,
max_display=20)
# 面试要点:
# SHAP优于传统feature_importance的原因:
# 1. 考虑特征间的交互作用
# 2. 给出每个样本的特征贡献方向(正/负)
# 3. 有坚实的博弈论理论基础
四、过拟合与正则化¶
# ========== 过拟合的识别与防控 ==========
# 过拟合的表现:
# 训练集AUC=0.99,测试集AUC=0.65 → 严重过拟合!
# 训练集AUC=0.85,测试集AUC=0.82 → 正常
# 检测过拟合:学习曲线
from sklearn.model_selection import learning_curve
train_sizes, train_scores, test_scores = learning_curve(
rf, X, y,
train_sizes=np.linspace(0.1, 1.0, 10), # 训练集比例
cv=5, # 5折CV
scoring='roc_auc',
n_jobs=-1
)
plt.figure(figsize=(8, 5))
plt.plot(train_sizes, train_scores.mean(axis=1),
'o-', label='Training AUC') # 训练集AUC
plt.plot(train_sizes, test_scores.mean(axis=1),
'o-', label='Validation AUC') # 验证集AUC
plt.xlabel('Training Set Size')
plt.ylabel('AUC')
plt.title('Learning Curve')
plt.legend()
plt.savefig("learning_curve.pdf")
# 防过拟合方法:
# 1. 增加样本量(最根本)
# 2. 减少特征数(特征选择)
# 3. 正则化(L1/L2/ElasticNet)
# 4. 模型简化(降低树深度、减少参数数)
# 5. 早停(early stopping,树的数量)
# 6. 集成学习(随机森林、Bagging)
# 7. dropout(深度学习中)
# 面试要点:
# 生信数据特别容易过拟合因为:
# - 特征数(基因数) >> 样本数(p>>n问题)
# - 例如:20000个基因但只有50个样本
# - 必须做特征选择 + 交叉验证
五、不平衡数据处理¶
# ========== 不平衡数据:生信中很常见 ==========
# 例子:1000个正常 vs 50个肿瘤
from sklearn.utils.class_weight import compute_class_weight
from imblearn.over_sampling import SMOTE # pip install imbalanced-learn
# 方法1:类权重调整
weights = compute_class_weight('balanced',
classes=np.unique(y),
y=y)
rf_balanced = RandomForestClassifier(
class_weight='balanced', # 自动调整权重
n_estimators=500,
random_state=42
)
# 方法2:SMOTE过采样(合成少数类样本)
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
print(f"原始: {np.bincount(y_train)}") # 原始类别分布
print(f"SMOTE后: {np.bincount(y_resampled)}") # 过采样后均衡
# 方法3:使用PR-AUC代替ROC-AUC评估
from sklearn.metrics import average_precision_score
pr_auc = average_precision_score(y_test, y_prob) # PR-AUC
print(f"PR-AUC: {pr_auc:.3f}")
# 面试要点:
# - 不平衡数据用准确率(Accuracy)没用(全猜多数类就很高)
# - 用F1/PR-AUC/MCC代替准确率
# - SMOTE不能在交叉验证外面用(数据泄露!)
# - 正确做法:在CV的每折训练集内部做SMOTE
六、深度学习基础(加分项)¶
# ========== 深度学习在生信中的应用(概念为主) ==========
# 生信中深度学习的应用场景:
applications = {
"蛋白质结构预测": "AlphaFold2(Transformer架构)",
"变异致病性预测": "SpliceAI(卷积神经网络预测剪接位点)",
"基因表达预测": "Enformer(从DNA序列预测表达)",
"单细胞聚类": "scVI/scANVI(变分自编码器VAE)",
"药物发现": "图神经网络(分子图表示)",
"碱基识别(basecalling)": "ONT的Guppy(循环神经网络RNN)",
}
# 面试只需了解概念,不需要手写代码:
# CNN(卷积神经网络)— 适合序列数据(DNA序列 = 一维卷积)
# RNN/LSTM — 适合序列数据(考虑顺序关系)
# Transformer — 自注意力机制,AlphaFold2/GPT的核心
# VAE — 变分自编码器,单细胞降维
# GAN — 生成对抗网络,数据增强
# 什么时候用深度学习 vs 传统ML?
# 传统ML更好的情况:
# - 样本量小(<1000)
# - 需要可解释性
# - 特征已经设计好(表格数据)
# 深度学习更好的情况:
# - 大数据(>10000样本)
# - 原始数据(序列/图像/图结构)
# - 需要自动特征学习
七、常见报错与解决¶
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 训练AUC远高于测试AUC | 过拟合 | 特征选择、正则化、增加样本 |
| AUC=0.5 | 模型没学到任何东西 | 检查标签是否正确,换模型 |
| 交叉验证方差很大 | 样本量太小 | 增加样本或用留一法 |
| 特征太多导致内存不足 | 基因数太多 | 先做预过滤(方差过滤) |
| SMOTE后效果反而变差 | SMOTE在CV外面用了 | 在CV训练集内部用 |
八、面试高频问题¶
Q1:随机森林和梯度提升树(GBDT/XGBoost)的区别?
随机森林:并行训练多棵独立的树,投票决策(Bagging)。不容易过拟合,对超参数不敏感。GBDT/XGBoost:串行训练,每棵新树修正前面的错误(Boosting)。准确率可能更高但更容易过拟合,需要仔细调参。生信中样本少的情况下随机森林更安全。
Q2:什么是数据泄露(Data Leakage)?
训练模型时使用了本不应该知道的信息。常见形式:(1) 用全部数据做特征选择再交叉验证(应该在CV内部选);(2) 测试集的信息混入训练集;(3) 用未来数据预测过去。后果:模型评估结果虚高,实际部署效果很差。
Q3:如何处理高维低样本(p>>n)问题?
生信中很常见(20000基因但50样本)。策略:(1) 特征过滤(去掉低方差基因);(2) L1正则化做特征选择;(3) PCA降维后建模;(4) 用随机森林/SVM等对高维友好的模型;(5) 交叉验证+嵌套验证防过拟合。
Q4:AUC和准确率哪个更重要?
看场景。平衡数据时准确率可以用。不平衡数据时AUC更好(不受类别比例影响)。极端不平衡时用PR-AUC(Precision-Recall AUC)。多分类时用macro-F1。最好同时报告多个指标。
九、速查表¶
# === 机器学习面试速查 ===
# 模型选择
# 小样本+可解释 → 逻辑回归
# 小样本+高维 → 随机森林/SVM
# 大样本+表格 → XGBoost/LightGBM
# 序列/图像 → 深度学习(CNN/RNN)
# 评估指标
# 平衡数据 → Accuracy, AUC
# 不平衡 → F1, PR-AUC, MCC
# 回归 → RMSE, R², MAE
# 防过拟合
# 交叉验证 + 特征选择 + 正则化
# sklearn常用
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.metrics import roc_auc_score, classification_report
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# 标准流程
# 1. 数据预处理(缺失值、标准化)
# 2. 特征选择(过滤/包裹/嵌入式)
# 3. 模型训练(在交叉验证内!)
# 4. 模型评估(AUC + 混淆矩阵)
# 5. 模型解释(SHAP/特征重要性)
参考资料:Hands-On ML (Géron 2023)、scikit-learn文档、Machine Learning in Bioinformatics (Baldi & Brunak)、SHAP论文 (Lundberg 2017)