17 KiB
17 KiB
AMP®-Parkinson's Disease Progression Prediction (2023)
Competition Brief
竞赛基本信息
- 主办方: AMP (Accelerating Medicines Partnership)
- 时间: 2023年
- 类型: 表格数据/医疗预测
- 数据规模: 小样本数据集
- 评价目标: SMAPE (Symmetric Mean Absolute Percentage Error)
任务描述 预测帕金森病患者的疾病进展情况。使用蛋白质和肽段数据(通过质谱测量脑脊液样本)来预测患者未来的 MDS-UPDRS 评分。
数据特点
- 蛋白质数据: 227个蛋白质特征
- 肽段数据: 来自多个质谱实验
- 时间序列: 每个患者有多次访问记录
- 样本量: 相对较小(小样本竞赛)
- 目标变量: MDS-UPDRS 评分的进展
评价指标 SMAPE (Symmetric Mean Absolute Percentage Error)
- 对称性平均绝对百分比误差
- 范围 [0, 200],越小越好
- 对异常值相对鲁棒
Top Solutions Analysis
1st Place - Connecting Dotts (Dmitry Gordeev et al.)
核心策略
- 模型组合: LightGBM + Neural Network 的简单平均
- 特征工程: 精心设计的蛋白质和肽段聚合特征
- 数据处理: 针对神经网络的标准化和二值化
关键技术细节
-
特征工程
- 蛋白质和肽段的聚合统计量(均值、中位数、标准差)
- 时间序列特征的构造
- 蛋白质-肽段关系特征的提取
-
模型架构
- LightGBM: 梯度提升树模型
- Neural Network: 深度学习模型
- 集成策略: 简单平均
-
数据预处理
- NN专用预处理: 特征缩放
- 特征二值化处理
- 缺失值处理策略
代码要点
# 模型集成示例
final_prediction = (lgb_pred + nn_pred) / 2
2nd Place - No Luck, All Skill
核心策略
- 发布时间: 2023年6月19日
- 强调特征工程的重要性
- 多模型集成策略
关键特征
- 详细的特征工程流程
- 模型融合技术
- 验证策略设计
3rd Place - Hajime Tamura
核心策略
- 发布时间: 2023年5月19日
- 分组策略: 将数据分成两组分别优化
- 简洁的解决方案(三个主要函数)
关键创新
- 数据分组优化
- 针对性模型训练
- 简化流程提升效率
4th/5th Place - Ambrosm (#5: Find the Control Group)
核心策略
- 发布时间: 2023年5月18日
- 控制组识别: 关键创新点
- 利用对照组信息改进预测
关键洞察
- 识别并分离控制组样本
- 针对不同组别使用不同策略
- 提升模型区分度
9th Place - Makotu
核心策略
- 发布时间: 2023年5月18日
- 详细的特征工程和模型调优
13th Place - FNOA
技术要点
- 中等排名的稳定方案
- 实用的特征工程方法
43rd Place - Wojciech Victor Fulmyk (Top 3% Silver)
重要发现
- XGBoost 和 LightGBM 表现不佳
- 强调传统树模型在这个数据集上的局限性
- 探索其他模型方向
技术要点
# 他们的发现表明传统 GBDT 可能不是最佳选择
# 需要考虑其他模型或更复杂的特征工程
89th Place - Giba (Non-Leaky Solution)
核心策略
- 强调无数据泄露的干净方案
- 可复现的验证策略
Common Techniques Across Solutions
1. Feature Engineering Patterns
蛋白质/肽段聚合特征
# 时间聚合
protein_stats = train.groupby('patient_id')['protein'].agg([
'mean', 'median', 'std', 'min', 'max'
])
# 肽段聚合
peptide_stats = train.groupby('patient_id')['peptide'].agg([
'mean', 'count', 'nunique'
])
时间序列特征
- 访问间隔时间
- 进展速度估计
- 基线和随访差异
蛋白质-肽段关系
- 蛋白质包含的肽数量
- 肽段来源的蛋白质信息
2. Model Selection Insights
成功模型
- LightGBM (部分方案)
- Neural Networks / MLP
- 集成方法
需要谨慎的模型
- XGBoost (43rd方案指出效果不佳)
- 纯线性模型
- 单一模型(推荐集成)
3. Validation Strategies
关键原则
- 避免患者级别的数据泄露
- 时间基础的分割
- 分组交叉验证
from sklearn.model_selection import GroupKFold
gkf = GroupKFold(n_splits=5)
for train_idx, val_idx in gkf.split(X, y, groups=patient_ids):
# 训练和验证
4. Data Leakage Prevention
常见陷阱
- 同一患者的多次访问分散在训练/验证集
- 未来信息泄露到训练集
- 蛋白质/肽段测试集信息泄露
预防措施
- 严格的患者级别分割
- 时间有序分割
- 仔细的特征构造审计
Code Templates
Basic Feature Engineering
import pandas as pd
import numpy as np
def create_protein_features(train_proteins, test_proteins):
"""创建蛋白质聚合特征"""
def process(df):
stats = df.groupby('patient_id')['NPX'].agg([
('protein_mean', 'mean'),
('protein_std', 'std'),
('protein_min', 'min'),
('protein_max', 'max')
]).reset_index()
return stats
train_stats = process(train_proteins)
test_stats = process(test_proteins)
return train_stats, test_stats
def create_peptide_features(train_peptides, test_peptides):
"""创建肽段聚合特征"""
def process(df):
stats = df.groupby('patient_id')['PeptideAbundance'].agg([
('peptide_mean', 'mean'),
('peptide_std', 'std'),
('peptide_count', 'count')
]).reset_index()
return stats
train_stats = process(train_peptides)
test_stats = process(test_peptides)
return train_stats, test_stats
def create_time_features(train_clinical, test_clinical):
"""创建时间序列特征"""
def process(df):
df = df.copy()
df['visit_month'] = df['visit_month'].astype(int)
df['pred_month'] = df['visit_month'] + df['updrs_test_month']
# 计算自基线以来的时间
df['months_since_baseline'] = df.groupby('patient_id')['visit_month'].transform(lambda x: x - x.min())
return df
return process(train_clinical), process(test_clinical)
Model Training Template
import lightgbm as lgb
from sklearn.model_selection import GroupKFold
from sklearn.metrics import mean_absolute_error
def smape(y_true, y_pred):
"""SMAPE 评估指标"""
return 100 * np.mean(2 * np.abs(y_pred - y_true) / (np.abs(y_true) + np.abs(y_pred) + 1e-8))
def train_lightgbm(X_train, y_train, groups, params=None):
"""训练 LightGBM 模型"""
if params is None:
params = {
'objective': 'regression',
'metric': 'mae',
'learning_rate': 0.01,
'num_leaves': 31,
'max_depth': -1,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1
}
gkf = GroupKFold(n_splits=5)
models = []
scores = []
for train_idx, val_idx in gkf.split(X_train, y_train, groups=groups):
X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]
y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
train_data = lgb.Dataset(X_tr, label=y_tr)
val_data = lgb.Dataset(X_val, label=y_val, reference=train_data)
model = lgb.train(
params,
train_data,
num_boost_round=10000,
valid_sets=[train_data, val_data],
callbacks=[lgb.early_stopping(100), lgb.log_evaluation(0)]
)
pred = model.predict(X_val)
score = smape(y_val, pred)
models.append(model)
scores.append(score)
print(f'Average SMAPE: {np.mean(scores):.2f}')
return models, scores
# 使用示例
# models, scores = train_lightgbm(X_train, y_train, patient_ids)
Neural Network Template
import tensorflow as tf
from sklearn.preprocessing import StandardScaler
def create_nn_model(input_dim, hidden_units=[256, 128, 64]):
"""创建神经网络模型"""
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(input_dim,)),
])
for units in hidden_units:
model.add(tf.keras.layers.Dense(
units,
activation='relu',
kernel_regularizer=tf.keras.regularizers.l2(0.01)
))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Dense(1, activation='linear'))
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='mae',
metrics=['mae']
)
return model
def train_nn(X_train, y_train, groups, epochs=100, batch_size=32):
"""训练神经网络"""
# 标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
gkf = GroupKFold(n_splits=5)
models = []
scores = []
for train_idx, val_idx in gkf.split(X_train_scaled, y_train, groups=groups):
X_tr, X_val = X_train_scaled[train_idx], X_train_scaled[val_idx]
y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
model = create_nn_model(X_train.shape[1])
early_stop = tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True
)
history = model.fit(
X_tr, y_tr,
validation_data=(X_val, y_val),
epochs=epochs,
batch_size=batch_size,
callbacks=[early_stop],
verbose=0
)
pred = model.predict(X_val).flatten()
score = smape(y_val, pred)
models.append((model, scaler))
scores.append(score)
print(f'Average SMAPE: {np.mean(scores):.2f}')
return models, scores
Ensemble Template
def ensemble_predictions(lgb_models, nn_models, X_test):
"""集成多个模型的预测"""
# LightGBM 预测
lgb_preds = np.mean([model.predict(X_test) for model in lgb_models], axis=0)
# NN 预测(需要标准化)
_, scaler = nn_models[0]
X_test_scaled = scaler.transform(X_test)
nn_preds = np.mean([
model.predict(X_test_scaled).flatten()
for model, _ in nn_models
], axis=0)
# 简单平均
final_pred = (lgb_preds + nn_preds) / 2
return final_pred
Best Practices
1. Data Understanding
蛋白质数据特性
- 227个蛋白质可能来自不同通路
- 部分蛋白质可能高度相关
- 需要探索蛋白质-疾病关系
肽段数据特性
- 肽数量远大于蛋白质数
- 多个肽段可能来自同一蛋白质
- 肽段丰度需要归一化
临床数据特性
- MDS-UPDRS 评分范围 0-260
- 不同子评分(第一部分到第四部分)
- 访问时间间隔不均匀
2. Feature Engineering Guidelines
DOs
- ✅ 创建患者级别的聚合特征
- ✅ 利用时间序列信息
- ✅ 探索蛋白质-肽段关系
- ✅ 考虑蛋白质生物学意义
- ✅ 使用领域知识构造特征
DON'Ts
- ❌ 在测试集上计算统计量
- ❌ 混合不同患者的未来信息
- ❌ 忽略数据的时间顺序
- ❌ 过度使用目标编码(容易泄露)
3. Model Selection Strategy
推荐流程
- 从简单模型开始(线性模型、决策树)
- 尝试 LightGBM(部分方案有效)
- 探索神经网络(1st方案使用)
- 集成多个模型
- 针对性调整超参数
模型选择考虑
- 数据量小 → 简单模型或强正则化
- 特征多 → 特征选择或降维
- 时序特性 → 考虑时间序列模型
- 集成收益 → 尝试模型融合
4. Validation Strategy
推荐方法
# 患者级别的 Group K-Fold
from sklearn.model_selection import GroupKFold
gkf = GroupKFold(n_splits=5)
for fold, (train_idx, val_idx) in enumerate(gkf.split(X, y, groups=patient_ids)):
print(f'Fold {fold + 1}')
# 训练和验证
时间序列分割
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for fold, (train_idx, val_idx) in enumerate(tscv.split(X)):
# 确保验证集在时间上晚于训练集
5. Common Pitfalls
数据泄露
- ❌ 将同一患者的多次访问分散到训练和验证集
- ❌ 在分割前计算全局统计量
- ❌ 使用未来信息预测过去
过拟合
- ❌ 特征过多而样本过少
- ❌ 过度调参导致验证集泄露
- ❌ 复杂模型在小数据集上
评估偏差
- ❌ 使用错误的评估指标
- ❌ 忽略 SMAPE 的对称性
- ❌ 不关注预测的分布特性
6. Domain Knowledge Integration
帕金森病相关
- MDS-UPDRS 评分的临床意义
- 蛋白质标志物的生物学作用
- 疾病进展的非线性特性
蛋白质组学
- 质谱数据的技术变异
- 蛋白质-肽段的定量关系
- 缺失值的含义
7. Hyperparameter Tuning
LightGBM 关键参数
params = {
'learning_rate': 0.01, # 降低学习率
'num_leaves': 31, # 控制复杂度
'max_depth': -1, # 不限制深度
'min_data_in_leaf': 20, # 小数据集增大此值
'feature_fraction': 0.8, # 特征采样
'bagging_fraction': 0.8, # 数据采样
'bagging_freq': 5,
'lambda_l1': 0.1, # L1 正则化
'lambda_l2': 0.1, # L2 正则化
}
神经网络关键参数
# 小数据集推荐
hidden_units = [128, 64, 32] # 减少层数和单元数
dropout_rate = 0.3 # 增加 dropout
l2_reg = 0.01 # L2 正则化
learning_rate = 0.001 # 适中学习率
batch_size = 32 # 小批量
Key Takeaways
-
小样本竞赛特点
- 特征工程比模型复杂度更重要
- 避免过拟合是关键
- 简单模型集成可能优于复杂单模型
-
医疗数据特殊性
- 需要理解领域知识
- 数据泄露风险更高
- 评估指标的临床意义
-
成功的共同点
- 仔细的特征工程
- 严格的验证策略
- 模型集成
- 避免数据泄露
-
需要注意的陷阱
- XGBoost/LightGBM 不是万能的(43rd方案发现)
- 数据泄露容易但难以发现
- 小样本的过拟合风险
-
推荐的学习路径
- 从 1st, 2nd, 3rd 方案学习顶级思路
- 从 5th, 9th 方案学习实用技巧
- 从 43rd 方案学习失败经验
- 综合多个方案形成自己的方法
Resources
Official Writeups
- 1st Place Solution - Connecting Dotts
- 2nd Place Solution - No Luck, All Skill
- 3rd Place Solution - Hajime Tamura
- 5th Place Solution - Ambrosm
- 9th Place Solution - Makotu
- 13th Place Solution - FNOA
- 43rd Place Solution - Wojciech Victor Fulmyk
- 89th Place Solution - Giba (Non-Leaky)