深入解析与应用:Delphi-2M 健康轨迹预测模型的开源实践与研究(下)

第三章:开发者实践指南:Delphi-2M 的编程应用
本章将深入代码层面,提供一个从零到一的实践路径。
3.1 环境准备与依赖安装
# 1. 克隆官方仓库
git clone https://github.com/gerstung-lab/Delphi.git
cd Delphi# 2. 创建并激活独立的 Python 环境(强烈推荐)
# 使用 conda 可以更好地管理环境依赖
conda create -n delphi_env python=3.10 -y
conda activate delphi_env# 3. 安装项目依赖
# requirements.txt 包含了 PyTorch, numpy, pandas, scikit-learn, tqdm, matplotlib 等
pip install -r requirements.txt# 验证安装
python -c "import torch; print(f'PyTorch version: {torch.__version__}');"
注意事项:
- CUDA 版本:
requirements.txt通常不会指定 CUDA 版本的 PyTorch。请根据你的 GPU 和驱动版本,从 PyTorch 官网 获取合适的安装命令,例如pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118。 - 硬件要求:虽然可以在 CPU 上运行,但训练一个完整规模的 Delphi-2M(尤其是
n_layer=12, n_embd=768)需要至少一张拥有 12GB 以上显存的 GPU。对于入门和调试,可以先用较小的模型配置。
3.2 数据准备:从原始记录到模型输入
这是最关键也最耗时的一步。模型要求一个高度结构化的二进制格式,我们必须将原始的、关系型的 EHR 数据转换为此格式。
3.2.1 核心数据格式:[patient_id, age_days, token_id]
如前所述,所有数据需要最终聚合成一个 np.uint32 类型的二维数组,并保存为 .bin 文件。每一行代表一个健康事件,包含三列:
| 列名 | 类型 | 描述 | 示例 |
|---|---|---|---|
patient_id | np.uint32 | 患者的唯一标识符 | 12345 |
age_days | np.uint32 | 事件发生时的年龄,以天为单位 | 18250 (约 50 岁) |
token_id | np.uint32 | 事件的唯一 Token ID | 456 |
强制约束:
- 按
patient_id排序:整个文件必须按patient_id分块。同一个患者的所有事件记录必须物理上连续存放。这是train.py中高效批处理的基础。 - 患者内部按时间排序:在每个患者的事件块内部,事件必须按
age_days严格递增排列。
3.2.2 事件映射与二进制文件生成:一个完整的 Python 脚本草稿
假设你的原始数据存储在两个 CSV 文件中:
patients.csv:patient_id, date_of_birth, genderevents.csv:patient_id, event_date, event_type, code
event_type 可以是 'ICD10', 'BMI', 'SMOKING' 等。code 是对应的编码或值。
import pandas as pd
import numpy as np
from datetime import datetime
import os# --- 1. 定义 Token 映射表 ---
# 这是一个简化的例子,你需要根据你的数据和 delphi_labels_chapters_colours_icd.csv
# 来构建一个完整的、一致的映射
# 注意:token_id 从 1 开始,0 保留给 padding
token_to_id = {'PAD': 0,'MALE': 3,'FEMALE': 4,'BMI_NORMAL': 5,'BMI_OVERWEIGHT': 6,'BMI_OBESE': 7,'SMOKING_NEVER': 8,'SMOKING_FORMER': 9,'SMOKING_CURRENT': 10,# 添加你需要的所有 ICD-10 编码'I10': 123, # 高血压'E11': 456, # 糖尿病'J45': 789, # 哮喘# ...
}
# 生成反向映射用于创建 labels.csv
id_to_token = {v: k for k, v in token_to_id.items()}
max_vocab_size = max(token_to_id.values()) + 1# --- 2. 读取和预处理原始数据 ---
print("Reading raw data...")
patients_df = pd.read_csv('patients.csv', parse_dates=['date_of_birth'])
events_df = pd.read_csv('events.csv', parse_dates=['event_date'])# 将患者信息合并到事件表
full_df = pd.merge(events_df, patients_df, on='patient_id')# --- 3. 转换为 [patient_id, age_days, token_id] 格式 ---
print("Transforming events...")
transformed_records = []for _, row in full_df.iterrows():patient_id = row['patient_id']# 计算年龄(天)age_days = (row['event_date'] - row['date_of_birth']).days# 根据 event_type 映射到 token_idtoken_id = Noneevent_type = row['event_type']code = row['code']if event_type == 'GENDER':token_id = token_to_id.get(code.upper())elif event_type == 'BMI':# 假设 code 是 BMI 数值,需要分档bmi_val = float(code)if bmi_val < 25: token_id = token_to_id.get('BMI_NORMAL')