Name model-selection splits validation in drug tasks
Browse filesUse validation terminology in both drug tasks, tutorials and documentation. Add validation_label_file and validation_cell_file options with legacy aliases, and write the validation prediction field while retaining notebook support for older outputs. Keep data partitions, training behavior, dataset paths, weights and PKL files unchanged.
- BioMod/README.md +2 -2
- BioMod/drug_combination/README.md +18 -6
- BioMod/drug_combination/train_drug_combination_performer_module.py +46 -41
- BioMod/drug_response/train_drug_performer_module.py +21 -21
- BioMod/toturials/drug_combination.ipynb +20 -17
- BioMod/toturials/drug_response_prediction.ipynb +19 -10
BioMod/README.md
CHANGED
|
@@ -15,8 +15,8 @@ files, and plotting helpers.
|
|
| 15 |
| Annotation | Supervised cell-type annotation workflows |
|
| 16 |
| Integration | Zero-shot batch integration and UMAP/scIB-style reporting |
|
| 17 |
| Perturbation | Norman gene-perturbation tutorial and smoke workflow |
|
| 18 |
-
| Drug response | Single-drug response prediction with GDSC/CCLE data |
|
| 19 |
-
| Drug combinations | Drug-combination synergy prediction with
|
| 20 |
| Figures | Notebook-level PDF/PNG/SVG plotting cells for paper and GitHub display |
|
| 21 |
|
| 22 |
## Repository Layout
|
|
|
|
| 15 |
| Annotation | Supervised cell-type annotation workflows |
|
| 16 |
| Integration | Zero-shot batch integration and UMAP/scIB-style reporting |
|
| 17 |
| Perturbation | Norman gene-perturbation tutorial and smoke workflow |
|
| 18 |
+
| Drug response | Single-drug response prediction with GDSC/CCLE data and validation-based model selection |
|
| 19 |
+
| Drug combinations | Drug-combination synergy prediction with validation-based model selection |
|
| 20 |
| Figures | Notebook-level PDF/PNG/SVG plotting cells for paper and GitHub display |
|
| 21 |
|
| 22 |
## Repository Layout
|
BioMod/drug_combination/README.md
CHANGED
|
@@ -27,10 +27,13 @@ data_summary/
|
|
| 27 |
|-- new_cell_features_954.csv # Training cell features (30 cell lines x 954 genes)
|
| 28 |
|-- smiles_0_10.csv # Drug SMILES
|
| 29 |
`-- test_data/
|
| 30 |
-
|-- independent_input.csv #
|
| 31 |
-
`-- independent_cell_features_954.csv #
|
| 32 |
```
|
| 33 |
|
|
|
|
|
|
|
|
|
|
| 34 |
## Dependencies
|
| 35 |
|
| 36 |
- PyTorch
|
|
@@ -106,11 +109,13 @@ python train_drug_combination_performer_module.py \
|
|
| 106 |
| `--hyper_params_path` | Required | Path to the PerformerModule hyper-parameter pkl file |
|
| 107 |
| `--module_data_dir` | None | Directory containing module data files |
|
| 108 |
| `--data_dir` | `./data_summary` | Data directory |
|
|
|
|
|
|
|
| 109 |
| `--batch_size` | 256 | Training batch size |
|
| 110 |
| `--lr` | 1e-4 | Learning rate |
|
| 111 |
| `--epochs` | 100 | Number of training epochs |
|
| 112 |
| `--n_folds` | 5 | Number of folds |
|
| 113 |
-
| `--domain_gen` | True | Domain generalization mode: train on the full training set and
|
| 114 |
| `--eval_interval` | 5 | Evaluate every N epochs |
|
| 115 |
| `--pool_method` | mean | PerformerModule pooling method (`mean`, `max`, or `concat`) |
|
| 116 |
| `--cache_emb` | True | Cache cell embeddings to a `.npy` file |
|
|
@@ -120,11 +125,18 @@ python train_drug_combination_performer_module.py \
|
|
| 120 |
|
| 121 |
Results are saved under `--output_dir` (default: `./results/`):
|
| 122 |
|
| 123 |
-
- `best_model_fold{i}.pt` -
|
| 124 |
-
- `fpr_tpr_fold{i}.pkl` - FPR/TPR curve data for drawing ROC curves
|
| 125 |
- `cell_emb_cache_{pool_method}.npy` - cached PerformerModule cell embeddings
|
| 126 |
|
| 127 |
## Evaluation Metrics
|
| 128 |
|
| 129 |
Every `--eval_interval` epochs, the script evaluates AUC, PR_AUC, ACC, BACC,
|
| 130 |
-
PREC, TPR, and KAPPA, matching scLong.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|-- new_cell_features_954.csv # Training cell features (30 cell lines x 954 genes)
|
| 28 |
|-- smiles_0_10.csv # Drug SMILES
|
| 29 |
`-- test_data/
|
| 30 |
+
|-- independent_input.csv # Validation labels (668 samples)
|
| 31 |
+
`-- independent_cell_features_954.csv # Validation cell features (1020 cell lines x 954 genes)
|
| 32 |
```
|
| 33 |
|
| 34 |
+
The directory name `test_data/` is retained for compatibility. These files are
|
| 35 |
+
used as the validation set for checkpoint selection.
|
| 36 |
+
|
| 37 |
## Dependencies
|
| 38 |
|
| 39 |
- PyTorch
|
|
|
|
| 109 |
| `--hyper_params_path` | Required | Path to the PerformerModule hyper-parameter pkl file |
|
| 110 |
| `--module_data_dir` | None | Directory containing module data files |
|
| 111 |
| `--data_dir` | `./data_summary` | Data directory |
|
| 112 |
+
| `--validation_label_file` | `test_data/independent_input.csv` | Validation labels for checkpoint selection; legacy alias: `--test_label_file` |
|
| 113 |
+
| `--validation_cell_file` | `test_data/independent_cell_features_954.csv` | Validation cell features; legacy alias: `--test_cell_file` |
|
| 114 |
| `--batch_size` | 256 | Training batch size |
|
| 115 |
| `--lr` | 1e-4 | Learning rate |
|
| 116 |
| `--epochs` | 100 | Number of training epochs |
|
| 117 |
| `--n_folds` | 5 | Number of folds |
|
| 118 |
+
| `--domain_gen` | True | Domain generalization mode: train on the full training set and select checkpoints using a separate validation set |
|
| 119 |
| `--eval_interval` | 5 | Evaluate every N epochs |
|
| 120 |
| `--pool_method` | mean | PerformerModule pooling method (`mean`, `max`, or `concat`) |
|
| 121 |
| `--cache_emb` | True | Cache cell embeddings to a `.npy` file |
|
|
|
|
| 125 |
|
| 126 |
Results are saved under `--output_dir` (default: `./results/`):
|
| 127 |
|
| 128 |
+
- `best_model_fold{i}.pt` - model weights selected by the highest validation AUC for each fold
|
| 129 |
+
- `fpr_tpr_fold{i}.pkl` - validation FPR/TPR curve data for drawing ROC curves
|
| 130 |
- `cell_emb_cache_{pool_method}.npy` - cached PerformerModule cell embeddings
|
| 131 |
|
| 132 |
## Evaluation Metrics
|
| 133 |
|
| 134 |
Every `--eval_interval` epochs, the script evaluates AUC, PR_AUC, ACC, BACC,
|
| 135 |
+
PREC, TPR, and KAPPA on the validation set, matching scLong's metric definitions.
|
| 136 |
+
The checkpoint with the highest validation AUC is saved for each fold. In
|
| 137 |
+
`--no_domain_gen` mode, each held-out fold serves as the validation set.
|
| 138 |
+
|
| 139 |
+
This naming update preserves the data, splits, checkpoint-selection behavior,
|
| 140 |
+
output filenames, metric column names, and legacy dataset cache IDs. It does not
|
| 141 |
+
create or evaluate an independent final test set; the reported values are
|
| 142 |
+
validation results used during model selection, not final test estimates.
|
BioMod/drug_combination/train_drug_combination_performer_module.py
CHANGED
|
@@ -610,10 +610,14 @@ def main():
|
|
| 610 |
default=os.path.join(SCRIPT_DIR, 'data_summary'))
|
| 611 |
parser.add_argument('--label_file', type=str, default='new_labels_0_10.csv')
|
| 612 |
parser.add_argument('--cell_file', type=str, default='new_cell_features_954.csv')
|
| 613 |
-
parser.add_argument('--
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
|
| 618 |
# Training args
|
| 619 |
parser.add_argument('--batch_size', type=int, default=256)
|
|
@@ -621,7 +625,7 @@ def main():
|
|
| 621 |
parser.add_argument('--epochs', type=int, default=100)
|
| 622 |
parser.add_argument('--n_folds', type=int, default=5)
|
| 623 |
parser.add_argument('--domain_gen', action='store_true', default=True,
|
| 624 |
-
help='Domain generalization: train full set,
|
| 625 |
parser.add_argument('--no_domain_gen', dest='domain_gen', action='store_false')
|
| 626 |
parser.add_argument('--eval_interval', type=int, default=5)
|
| 627 |
parser.add_argument('--output_dir', type=str,
|
|
@@ -651,12 +655,12 @@ def main():
|
|
| 651 |
train_cell_df = load_cell_features(train_cell_file)
|
| 652 |
print_sys(f'Loaded train cell features: {train_cell_df.shape}')
|
| 653 |
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
print_sys(f'Loaded
|
| 657 |
|
| 658 |
# Combine all cell lines and remove duplicates
|
| 659 |
-
all_cell_df = pd.concat([train_cell_df,
|
| 660 |
all_cell_df = all_cell_df[~all_cell_df.index.duplicated(keep='first')]
|
| 661 |
print_sys(f'Total unique cell lines: {len(all_cell_df)}')
|
| 662 |
|
|
@@ -691,13 +695,13 @@ def main():
|
|
| 691 |
train_labels = pd.read_csv(train_label_path)
|
| 692 |
print_sys(f'Train labels: {len(train_labels)} samples')
|
| 693 |
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
print_sys(f'
|
| 697 |
|
| 698 |
all_smiles = set(
|
| 699 |
train_labels['drug1'].tolist() + train_labels['drug2'].tolist()
|
| 700 |
-
+
|
| 701 |
)
|
| 702 |
smile_graph = {}
|
| 703 |
failed = []
|
|
@@ -737,20 +741,21 @@ def main():
|
|
| 737 |
cell_emb_dict=cell_emb_dict, smile_graph=smile_graph,
|
| 738 |
)
|
| 739 |
|
| 740 |
-
#
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
|
|
|
| 745 |
|
| 746 |
-
|
| 747 |
root=args.data_dir, dataset='test_drug1_performer_module',
|
| 748 |
-
xd=
|
| 749 |
cell_emb_dict=cell_emb_dict, smile_graph=smile_graph,
|
| 750 |
)
|
| 751 |
-
|
| 752 |
root=args.data_dir, dataset='test_drug2_performer_module',
|
| 753 |
-
xd=
|
| 754 |
cell_emb_dict=cell_emb_dict, smile_graph=smile_graph,
|
| 755 |
)
|
| 756 |
|
|
@@ -766,33 +771,33 @@ def main():
|
|
| 766 |
print_sys(f'{"=" * 60}')
|
| 767 |
|
| 768 |
if args.domain_gen:
|
| 769 |
-
# Domain generalization: train on full
|
| 770 |
d1_train_data = train_d1_dataset
|
| 771 |
-
|
| 772 |
d2_train_data = train_d2_dataset
|
| 773 |
-
|
| 774 |
else:
|
| 775 |
# Standard k-fold cross-validation
|
| 776 |
-
|
| 777 |
train_num = (
|
| 778 |
random_num[: pot * fold_i] + random_num[pot * (fold_i + 1) :]
|
| 779 |
)
|
| 780 |
d1_train_data = train_d1_dataset[train_num]
|
| 781 |
-
|
| 782 |
d2_train_data = train_d2_dataset[train_num]
|
| 783 |
-
|
| 784 |
|
| 785 |
d1_loader_train = DataLoader(
|
| 786 |
d1_train_data, batch_size=args.batch_size, shuffle=None, pin_memory=True,
|
| 787 |
)
|
| 788 |
-
|
| 789 |
-
|
| 790 |
)
|
| 791 |
d2_loader_train = DataLoader(
|
| 792 |
d2_train_data, batch_size=args.batch_size, shuffle=None, pin_memory=True,
|
| 793 |
)
|
| 794 |
-
|
| 795 |
-
|
| 796 |
)
|
| 797 |
|
| 798 |
model = GCNNet(cell_emb_dim=cell_emb_dim).to(device)
|
|
@@ -819,7 +824,7 @@ def main():
|
|
| 819 |
loss_history.append(avg_loss)
|
| 820 |
|
| 821 |
if epoch % args.eval_interval == 0:
|
| 822 |
-
T, S, Y = predict(model, device,
|
| 823 |
m = evaluate_metrics(T, S, Y)
|
| 824 |
metric_history.append({'epoch': epoch, **m})
|
| 825 |
|
|
@@ -830,10 +835,10 @@ def main():
|
|
| 830 |
args.output_dir, f'best_model_fold{fold_i}.pt'
|
| 831 |
)
|
| 832 |
torch.save(model.state_dict(), save_path)
|
| 833 |
-
print_sys(f'** New best AUROC: {best_auc:.4f} (saved)')
|
| 834 |
|
| 835 |
print_sys(
|
| 836 |
-
f'Epoch {epoch} | '
|
| 837 |
f'AUROC: {m["AUC"]:.4f} PR_AUC: {m["PR_AUC"]:.4f} '
|
| 838 |
f'ACC: {m["ACC"]:.4f} BACC: {m["BACC"]:.4f} '
|
| 839 |
f'PREC: {m["PREC"]:.4f} TPR: {m["TPR"]:.4f} '
|
|
@@ -856,12 +861,12 @@ def main():
|
|
| 856 |
fig.savefig(os.path.join(args.output_dir, f'loss_curve_fold{fold_i}.png'), dpi=150)
|
| 857 |
plt.close(fig)
|
| 858 |
|
| 859 |
-
# Save metrics history
|
| 860 |
if metric_history:
|
| 861 |
metric_df = pd.DataFrame(metric_history)
|
| 862 |
metric_df.to_csv(os.path.join(args.output_dir, f'metrics_fold{fold_i}.csv'), index=False)
|
| 863 |
|
| 864 |
-
# Plot ROC curve (using
|
| 865 |
fig, ax = plt.subplots(figsize=(6, 6))
|
| 866 |
ax.plot(best_fpr, best_tpr, linewidth=2, label=f'AUROC = {best_auc:.4f}')
|
| 867 |
ax.plot([0, 1], [0, 1], 'k--', linewidth=1, alpha=0.5)
|
|
@@ -869,7 +874,7 @@ def main():
|
|
| 869 |
ax.set_ylim([0, 1.02])
|
| 870 |
ax.set_xlabel('False Positive Rate')
|
| 871 |
ax.set_ylabel('True Positive Rate')
|
| 872 |
-
ax.set_title(f'ROC Curve - Fold {fold_i + 1}')
|
| 873 |
ax.legend(loc='lower right')
|
| 874 |
ax.grid(True, alpha=0.3)
|
| 875 |
fig.tight_layout()
|
|
@@ -881,13 +886,13 @@ def main():
|
|
| 881 |
with open(fpr_tpr_path, 'wb') as fp:
|
| 882 |
pickle.dump((best_fpr, best_tpr), fp)
|
| 883 |
|
| 884 |
-
print_sys(f'Fold {fold_i + 1} best AUROC: {best_auc:.4f}')
|
| 885 |
all_fold_aucs.append(best_auc)
|
| 886 |
|
| 887 |
# Summary
|
| 888 |
print_sys(f'\n{"=" * 60}')
|
| 889 |
-
print_sys(f'All folds AUROC: {all_fold_aucs}')
|
| 890 |
-
print_sys(f'Mean AUROC: {np.mean(all_fold_aucs):.4f} +/- {np.std(all_fold_aucs):.4f}')
|
| 891 |
print_sys('Training complete.')
|
| 892 |
|
| 893 |
|
|
|
|
| 610 |
default=os.path.join(SCRIPT_DIR, 'data_summary'))
|
| 611 |
parser.add_argument('--label_file', type=str, default='new_labels_0_10.csv')
|
| 612 |
parser.add_argument('--cell_file', type=str, default='new_cell_features_954.csv')
|
| 613 |
+
parser.add_argument('--validation_label_file', '--test_label_file',
|
| 614 |
+
dest='validation_label_file', type=str,
|
| 615 |
+
default='test_data/independent_input.csv',
|
| 616 |
+
help='Validation labels used for checkpoint selection; --test_label_file is a legacy alias')
|
| 617 |
+
parser.add_argument('--validation_cell_file', '--test_cell_file',
|
| 618 |
+
dest='validation_cell_file', type=str,
|
| 619 |
+
default='test_data/independent_cell_features_954.csv',
|
| 620 |
+
help='Validation cell features; --test_cell_file is a legacy alias')
|
| 621 |
|
| 622 |
# Training args
|
| 623 |
parser.add_argument('--batch_size', type=int, default=256)
|
|
|
|
| 625 |
parser.add_argument('--epochs', type=int, default=100)
|
| 626 |
parser.add_argument('--n_folds', type=int, default=5)
|
| 627 |
parser.add_argument('--domain_gen', action='store_true', default=True,
|
| 628 |
+
help='Domain generalization: train full set, validate on a separate set for checkpoint selection')
|
| 629 |
parser.add_argument('--no_domain_gen', dest='domain_gen', action='store_false')
|
| 630 |
parser.add_argument('--eval_interval', type=int, default=5)
|
| 631 |
parser.add_argument('--output_dir', type=str,
|
|
|
|
| 655 |
train_cell_df = load_cell_features(train_cell_file)
|
| 656 |
print_sys(f'Loaded train cell features: {train_cell_df.shape}')
|
| 657 |
|
| 658 |
+
validation_cell_file = os.path.join(args.data_dir, args.validation_cell_file)
|
| 659 |
+
validation_cell_df = load_cell_features(validation_cell_file)
|
| 660 |
+
print_sys(f'Loaded validation cell features: {validation_cell_df.shape}')
|
| 661 |
|
| 662 |
# Combine all cell lines and remove duplicates
|
| 663 |
+
all_cell_df = pd.concat([train_cell_df, validation_cell_df])
|
| 664 |
all_cell_df = all_cell_df[~all_cell_df.index.duplicated(keep='first')]
|
| 665 |
print_sys(f'Total unique cell lines: {len(all_cell_df)}')
|
| 666 |
|
|
|
|
| 695 |
train_labels = pd.read_csv(train_label_path)
|
| 696 |
print_sys(f'Train labels: {len(train_labels)} samples')
|
| 697 |
|
| 698 |
+
validation_label_path = os.path.join(args.data_dir, args.validation_label_file)
|
| 699 |
+
validation_labels = pd.read_csv(validation_label_path)
|
| 700 |
+
print_sys(f'Validation labels: {len(validation_labels)} samples')
|
| 701 |
|
| 702 |
all_smiles = set(
|
| 703 |
train_labels['drug1'].tolist() + train_labels['drug2'].tolist()
|
| 704 |
+
+ validation_labels['drug1'].tolist() + validation_labels['drug2'].tolist()
|
| 705 |
)
|
| 706 |
smile_graph = {}
|
| 707 |
failed = []
|
|
|
|
| 741 |
cell_emb_dict=cell_emb_dict, smile_graph=smile_graph,
|
| 742 |
)
|
| 743 |
|
| 744 |
+
# Validation datasets (separate set used for checkpoint selection)
|
| 745 |
+
# Preserve legacy test_* cache IDs to reuse existing preprocessed datasets.
|
| 746 |
+
drug1_validation = validation_labels['drug1'].tolist()
|
| 747 |
+
drug2_validation = validation_labels['drug2'].tolist()
|
| 748 |
+
cells_validation = validation_labels['cell'].tolist()
|
| 749 |
+
labels_validation = validation_labels['label'].tolist()
|
| 750 |
|
| 751 |
+
validation_d1_dataset = TestbedDataset(
|
| 752 |
root=args.data_dir, dataset='test_drug1_performer_module',
|
| 753 |
+
xd=drug1_validation, xt=cells_validation, y=labels_validation,
|
| 754 |
cell_emb_dict=cell_emb_dict, smile_graph=smile_graph,
|
| 755 |
)
|
| 756 |
+
validation_d2_dataset = TestbedDataset(
|
| 757 |
root=args.data_dir, dataset='test_drug2_performer_module',
|
| 758 |
+
xd=drug2_validation, xt=cells_validation, y=labels_validation,
|
| 759 |
cell_emb_dict=cell_emb_dict, smile_graph=smile_graph,
|
| 760 |
)
|
| 761 |
|
|
|
|
| 771 |
print_sys(f'{"=" * 60}')
|
| 772 |
|
| 773 |
if args.domain_gen:
|
| 774 |
+
# Domain generalization: train on full training set, select on validation set
|
| 775 |
d1_train_data = train_d1_dataset
|
| 776 |
+
d1_validation_data = validation_d1_dataset
|
| 777 |
d2_train_data = train_d2_dataset
|
| 778 |
+
d2_validation_data = validation_d2_dataset
|
| 779 |
else:
|
| 780 |
# Standard k-fold cross-validation
|
| 781 |
+
validation_num = random_num[pot * fold_i : pot * (fold_i + 1)]
|
| 782 |
train_num = (
|
| 783 |
random_num[: pot * fold_i] + random_num[pot * (fold_i + 1) :]
|
| 784 |
)
|
| 785 |
d1_train_data = train_d1_dataset[train_num]
|
| 786 |
+
d1_validation_data = train_d1_dataset[validation_num]
|
| 787 |
d2_train_data = train_d2_dataset[train_num]
|
| 788 |
+
d2_validation_data = train_d2_dataset[validation_num]
|
| 789 |
|
| 790 |
d1_loader_train = DataLoader(
|
| 791 |
d1_train_data, batch_size=args.batch_size, shuffle=None, pin_memory=True,
|
| 792 |
)
|
| 793 |
+
d1_loader_validation = DataLoader(
|
| 794 |
+
d1_validation_data, batch_size=args.batch_size, shuffle=None, pin_memory=True,
|
| 795 |
)
|
| 796 |
d2_loader_train = DataLoader(
|
| 797 |
d2_train_data, batch_size=args.batch_size, shuffle=None, pin_memory=True,
|
| 798 |
)
|
| 799 |
+
d2_loader_validation = DataLoader(
|
| 800 |
+
d2_validation_data, batch_size=args.batch_size, shuffle=None, pin_memory=True,
|
| 801 |
)
|
| 802 |
|
| 803 |
model = GCNNet(cell_emb_dim=cell_emb_dim).to(device)
|
|
|
|
| 824 |
loss_history.append(avg_loss)
|
| 825 |
|
| 826 |
if epoch % args.eval_interval == 0:
|
| 827 |
+
T, S, Y = predict(model, device, d1_loader_validation, d2_loader_validation)
|
| 828 |
m = evaluate_metrics(T, S, Y)
|
| 829 |
metric_history.append({'epoch': epoch, **m})
|
| 830 |
|
|
|
|
| 835 |
args.output_dir, f'best_model_fold{fold_i}.pt'
|
| 836 |
)
|
| 837 |
torch.save(model.state_dict(), save_path)
|
| 838 |
+
print_sys(f'** New best validation AUROC: {best_auc:.4f} (saved)')
|
| 839 |
|
| 840 |
print_sys(
|
| 841 |
+
f'Epoch {epoch} | Validation | '
|
| 842 |
f'AUROC: {m["AUC"]:.4f} PR_AUC: {m["PR_AUC"]:.4f} '
|
| 843 |
f'ACC: {m["ACC"]:.4f} BACC: {m["BACC"]:.4f} '
|
| 844 |
f'PREC: {m["PREC"]:.4f} TPR: {m["TPR"]:.4f} '
|
|
|
|
| 861 |
fig.savefig(os.path.join(args.output_dir, f'loss_curve_fold{fold_i}.png'), dpi=150)
|
| 862 |
plt.close(fig)
|
| 863 |
|
| 864 |
+
# Save validation metrics history (retain the existing CSV schema)
|
| 865 |
if metric_history:
|
| 866 |
metric_df = pd.DataFrame(metric_history)
|
| 867 |
metric_df.to_csv(os.path.join(args.output_dir, f'metrics_fold{fold_i}.csv'), index=False)
|
| 868 |
|
| 869 |
+
# Plot validation ROC curve (using the selected model's predictions)
|
| 870 |
fig, ax = plt.subplots(figsize=(6, 6))
|
| 871 |
ax.plot(best_fpr, best_tpr, linewidth=2, label=f'AUROC = {best_auc:.4f}')
|
| 872 |
ax.plot([0, 1], [0, 1], 'k--', linewidth=1, alpha=0.5)
|
|
|
|
| 874 |
ax.set_ylim([0, 1.02])
|
| 875 |
ax.set_xlabel('False Positive Rate')
|
| 876 |
ax.set_ylabel('True Positive Rate')
|
| 877 |
+
ax.set_title(f'Validation ROC Curve - Fold {fold_i + 1}')
|
| 878 |
ax.legend(loc='lower right')
|
| 879 |
ax.grid(True, alpha=0.3)
|
| 880 |
fig.tight_layout()
|
|
|
|
| 886 |
with open(fpr_tpr_path, 'wb') as fp:
|
| 887 |
pickle.dump((best_fpr, best_tpr), fp)
|
| 888 |
|
| 889 |
+
print_sys(f'Fold {fold_i + 1} best validation AUROC: {best_auc:.4f}')
|
| 890 |
all_fold_aucs.append(best_auc)
|
| 891 |
|
| 892 |
# Summary
|
| 893 |
print_sys(f'\n{"=" * 60}')
|
| 894 |
+
print_sys(f'All folds validation AUROC: {all_fold_aucs}')
|
| 895 |
+
print_sys(f'Mean validation AUROC: {np.mean(all_fold_aucs):.4f} +/- {np.std(all_fold_aucs):.4f}')
|
| 896 |
print_sys('Training complete.')
|
| 897 |
|
| 898 |
|
BioMod/drug_response/train_drug_performer_module.py
CHANGED
|
@@ -471,18 +471,18 @@ def metadata_generate(
|
|
| 471 |
|
| 472 |
|
| 473 |
def data_split(data_idx: list, ratio: float = 0.95):
|
| 474 |
-
"""
|
| 475 |
-
train_idx,
|
| 476 |
for each_type in TCGA_LABEL_SET:
|
| 477 |
data_subtype_idx = [item for item in data_idx if item[-1] == each_type]
|
| 478 |
if len(data_subtype_idx) == 0:
|
| 479 |
continue
|
| 480 |
k = max(1, int(ratio * len(data_subtype_idx)))
|
| 481 |
train_list = random.sample(data_subtype_idx, k)
|
| 482 |
-
|
| 483 |
train_idx += train_list
|
| 484 |
-
|
| 485 |
-
return train_idx,
|
| 486 |
|
| 487 |
|
| 488 |
def feature_extract(
|
|
@@ -924,18 +924,18 @@ def main():
|
|
| 924 |
seed=args.shuffle_seed,
|
| 925 |
)
|
| 926 |
|
| 927 |
-
# 3)
|
| 928 |
print_sys("\n[Step 3/6] Splitting dataset...")
|
| 929 |
-
train_idx,
|
| 930 |
-
print_sys(f"Train samples: {len(train_idx)},
|
| 931 |
|
| 932 |
# 4) Extract features
|
| 933 |
print_sys("\n[Step 4/6] Extracting features...")
|
| 934 |
X_drug_train, Y_train, train_cancer_info, train_index_list = feature_extract(
|
| 935 |
train_idx, drug_feature, args.max_atoms
|
| 936 |
)
|
| 937 |
-
|
| 938 |
-
|
| 939 |
)
|
| 940 |
|
| 941 |
# 5) Build a data set
|
|
@@ -943,8 +943,8 @@ def main():
|
|
| 943 |
train_ds = DrugPerformerModuleDataset(
|
| 944 |
X_drug_train, Y_train, train_index_list, cell_emb_dict
|
| 945 |
)
|
| 946 |
-
|
| 947 |
-
|
| 948 |
)
|
| 949 |
|
| 950 |
drug_dim = X_drug_train[0][0].shape[-1]
|
|
@@ -957,8 +957,8 @@ def main():
|
|
| 957 |
num_workers=num_workers,
|
| 958 |
pin_memory=torch.cuda.is_available(),
|
| 959 |
)
|
| 960 |
-
|
| 961 |
-
|
| 962 |
batch_size=args.batch_size,
|
| 963 |
shuffle=False,
|
| 964 |
num_workers=num_workers,
|
|
@@ -1002,7 +1002,7 @@ def main():
|
|
| 1002 |
|
| 1003 |
for epoch in range(1, args.epochs + 1):
|
| 1004 |
train_loss = train_epoch(model, train_loader, optimizer, criterion, device)
|
| 1005 |
-
preds, trues, pcc = eval_epoch(model,
|
| 1006 |
|
| 1007 |
scheduler.step()
|
| 1008 |
current_lr = optimizer.param_groups[0]["lr"]
|
|
@@ -1010,7 +1010,7 @@ def main():
|
|
| 1010 |
print_sys(
|
| 1011 |
f"Epoch {epoch:03d}/{args.epochs} | "
|
| 1012 |
f"train_loss={train_loss:.4f} | "
|
| 1013 |
-
f"
|
| 1014 |
f"lr={current_lr:.6f}"
|
| 1015 |
)
|
| 1016 |
|
|
@@ -1021,7 +1021,7 @@ def main():
|
|
| 1021 |
best_epoch = epoch
|
| 1022 |
patience_counter = 0
|
| 1023 |
|
| 1024 |
-
# Save the best model
|
| 1025 |
model_save_path = os.path.join(args.save_dir, "best_model.pth")
|
| 1026 |
torch.save(
|
| 1027 |
{
|
|
@@ -1035,7 +1035,7 @@ def main():
|
|
| 1035 |
},
|
| 1036 |
model_save_path,
|
| 1037 |
)
|
| 1038 |
-
print_sys(f" → Best model saved! PCC improved to {best_pcc:.4f}")
|
| 1039 |
else:
|
| 1040 |
patience_counter += 1
|
| 1041 |
if patience_counter >= args.patience:
|
|
@@ -1049,12 +1049,12 @@ def main():
|
|
| 1049 |
print_sys("Training completed!")
|
| 1050 |
print_sys("=" * 70)
|
| 1051 |
print_sys(f"Best epoch: {best_epoch}")
|
| 1052 |
-
print_sys(f"Best PCC: {best_pcc:.4f}")
|
| 1053 |
|
| 1054 |
if best_pred is not None:
|
| 1055 |
result_path = os.path.join(args.save_dir, "predictions.npz")
|
| 1056 |
-
np.savez(result_path, pred=best_pred,
|
| 1057 |
-
print_sys(f"
|
| 1058 |
|
| 1059 |
print_sys(f"Best model saved to: {os.path.join(args.save_dir, 'best_model.pth')}")
|
| 1060 |
print_sys("=" * 70)
|
|
|
|
| 471 |
|
| 472 |
|
| 473 |
def data_split(data_idx: list, ratio: float = 0.95):
|
| 474 |
+
"""Return training and validation splits stratified by cancer type."""
|
| 475 |
+
train_idx, validation_idx = [], []
|
| 476 |
for each_type in TCGA_LABEL_SET:
|
| 477 |
data_subtype_idx = [item for item in data_idx if item[-1] == each_type]
|
| 478 |
if len(data_subtype_idx) == 0:
|
| 479 |
continue
|
| 480 |
k = max(1, int(ratio * len(data_subtype_idx)))
|
| 481 |
train_list = random.sample(data_subtype_idx, k)
|
| 482 |
+
validation_list = [item for item in data_subtype_idx if item not in train_list]
|
| 483 |
train_idx += train_list
|
| 484 |
+
validation_idx += validation_list
|
| 485 |
+
return train_idx, validation_idx
|
| 486 |
|
| 487 |
|
| 488 |
def feature_extract(
|
|
|
|
| 924 |
seed=args.shuffle_seed,
|
| 925 |
)
|
| 926 |
|
| 927 |
+
# 3) Split training and validation data; validation is used for model selection
|
| 928 |
print_sys("\n[Step 3/6] Splitting dataset...")
|
| 929 |
+
train_idx, validation_idx = data_split(data_idx)
|
| 930 |
+
print_sys(f"Train samples: {len(train_idx)}, Validation samples: {len(validation_idx)}")
|
| 931 |
|
| 932 |
# 4) Extract features
|
| 933 |
print_sys("\n[Step 4/6] Extracting features...")
|
| 934 |
X_drug_train, Y_train, train_cancer_info, train_index_list = feature_extract(
|
| 935 |
train_idx, drug_feature, args.max_atoms
|
| 936 |
)
|
| 937 |
+
X_drug_validation, Y_validation, validation_cancer_info, validation_index_list = feature_extract(
|
| 938 |
+
validation_idx, drug_feature, args.max_atoms
|
| 939 |
)
|
| 940 |
|
| 941 |
# 5) Build a data set
|
|
|
|
| 943 |
train_ds = DrugPerformerModuleDataset(
|
| 944 |
X_drug_train, Y_train, train_index_list, cell_emb_dict
|
| 945 |
)
|
| 946 |
+
validation_ds = DrugPerformerModuleDataset(
|
| 947 |
+
X_drug_validation, Y_validation, validation_index_list, cell_emb_dict
|
| 948 |
)
|
| 949 |
|
| 950 |
drug_dim = X_drug_train[0][0].shape[-1]
|
|
|
|
| 957 |
num_workers=num_workers,
|
| 958 |
pin_memory=torch.cuda.is_available(),
|
| 959 |
)
|
| 960 |
+
validation_loader = DataLoader(
|
| 961 |
+
validation_ds,
|
| 962 |
batch_size=args.batch_size,
|
| 963 |
shuffle=False,
|
| 964 |
num_workers=num_workers,
|
|
|
|
| 1002 |
|
| 1003 |
for epoch in range(1, args.epochs + 1):
|
| 1004 |
train_loss = train_epoch(model, train_loader, optimizer, criterion, device)
|
| 1005 |
+
preds, trues, pcc = eval_epoch(model, validation_loader, device)
|
| 1006 |
|
| 1007 |
scheduler.step()
|
| 1008 |
current_lr = optimizer.param_groups[0]["lr"]
|
|
|
|
| 1010 |
print_sys(
|
| 1011 |
f"Epoch {epoch:03d}/{args.epochs} | "
|
| 1012 |
f"train_loss={train_loss:.4f} | "
|
| 1013 |
+
f"validation_pcc={pcc:.4f} | "
|
| 1014 |
f"lr={current_lr:.6f}"
|
| 1015 |
)
|
| 1016 |
|
|
|
|
| 1021 |
best_epoch = epoch
|
| 1022 |
patience_counter = 0
|
| 1023 |
|
| 1024 |
+
# Save the best model selected by validation PCC
|
| 1025 |
model_save_path = os.path.join(args.save_dir, "best_model.pth")
|
| 1026 |
torch.save(
|
| 1027 |
{
|
|
|
|
| 1035 |
},
|
| 1036 |
model_save_path,
|
| 1037 |
)
|
| 1038 |
+
print_sys(f" → Best model saved! Validation PCC improved to {best_pcc:.4f}")
|
| 1039 |
else:
|
| 1040 |
patience_counter += 1
|
| 1041 |
if patience_counter >= args.patience:
|
|
|
|
| 1049 |
print_sys("Training completed!")
|
| 1050 |
print_sys("=" * 70)
|
| 1051 |
print_sys(f"Best epoch: {best_epoch}")
|
| 1052 |
+
print_sys(f"Best validation PCC: {best_pcc:.4f}")
|
| 1053 |
|
| 1054 |
if best_pred is not None:
|
| 1055 |
result_path = os.path.join(args.save_dir, "predictions.npz")
|
| 1056 |
+
np.savez(result_path, pred=best_pred, validation=best_true)
|
| 1057 |
+
print_sys(f"Validation predictions saved to: {result_path}")
|
| 1058 |
|
| 1059 |
print_sys(f"Best model saved to: {os.path.join(args.save_dir, 'best_model.pth')}")
|
| 1060 |
print_sys("=" * 70)
|
BioMod/toturials/drug_combination.ipynb
CHANGED
|
@@ -8,7 +8,10 @@
|
|
| 8 |
"\n",
|
| 9 |
"This notebook prepares a runnable demo command for `drug_combination/train_drug_combination_performer_module.py` using configurable absolute paths.\n",
|
| 10 |
"\n",
|
| 11 |
-
"The drug-combination data are not present in the current project yet, so `RUN_TRAINING` is set to `False` by default. After placing `data_summary/` under the task directory, switch `RUN_TRAINING = True` in the command cell to start training."
|
|
|
|
|
|
|
|
|
|
| 12 |
]
|
| 13 |
},
|
| 14 |
{
|
|
@@ -124,8 +127,8 @@
|
|
| 124 |
" 'train_labels': DATA_DIR / 'new_labels_0_10.csv',\n",
|
| 125 |
" 'train_cell_features': DATA_DIR / 'new_cell_features_954.csv',\n",
|
| 126 |
" 'drug_smiles': DATA_DIR / 'smiles_0_10.csv',\n",
|
| 127 |
-
" '
|
| 128 |
-
" '
|
| 129 |
"}\n",
|
| 130 |
"\n",
|
| 131 |
"def status_table(paths):\n",
|
|
@@ -378,7 +381,7 @@
|
|
| 378 |
"\n",
|
| 379 |
"\n",
|
| 380 |
"data_overview = []\n",
|
| 381 |
-
"train_labels =
|
| 382 |
"if expected_data_files['train_labels'].exists():\n",
|
| 383 |
" train_labels = read_label_table(expected_data_files['train_labels'])\n",
|
| 384 |
" data_overview.append({\n",
|
|
@@ -391,17 +394,17 @@
|
|
| 391 |
" 'unique_drug2': train_labels['drug2'].nunique(),\n",
|
| 392 |
" 'unique_pairs': train_labels[['drug1', 'drug2']].drop_duplicates().shape[0],\n",
|
| 393 |
" })\n",
|
| 394 |
-
"if expected_data_files['
|
| 395 |
-
"
|
| 396 |
" data_overview.append({\n",
|
| 397 |
-
" 'split': '
|
| 398 |
-
" 'rows': len(
|
| 399 |
-
" 'positive': int((
|
| 400 |
-
" 'negative': int((
|
| 401 |
-
" 'unique_cells':
|
| 402 |
-
" 'unique_drug1':
|
| 403 |
-
" 'unique_drug2':
|
| 404 |
-
" 'unique_pairs':
|
| 405 |
" })\n",
|
| 406 |
"\n",
|
| 407 |
"data_overview_df = pd.DataFrame(data_overview)\n",
|
|
@@ -414,8 +417,8 @@
|
|
| 414 |
" label_rows = []\n",
|
| 415 |
" if train_labels is not None:\n",
|
| 416 |
" label_rows.extend([{'split': 'train', 'label': int(k), 'count': int(v)} for k, v in train_labels['label'].value_counts().sort_index().items()])\n",
|
| 417 |
-
" if
|
| 418 |
-
" label_rows.extend([{'split': '
|
| 419 |
" label_df = pd.DataFrame(label_rows)\n",
|
| 420 |
" label_df.to_csv(PLOT_DIR / 'drug_combination_label_counts.csv', index=False)\n",
|
| 421 |
"\n",
|
|
@@ -748,4 +751,4 @@
|
|
| 748 |
},
|
| 749 |
"nbformat": 4,
|
| 750 |
"nbformat_minor": 5
|
| 751 |
-
}
|
|
|
|
| 8 |
"\n",
|
| 9 |
"This notebook prepares a runnable demo command for `drug_combination/train_drug_combination_performer_module.py` using configurable absolute paths.\n",
|
| 10 |
"\n",
|
| 11 |
+
"The drug-combination data are not present in the current project yet, so `RUN_TRAINING` is set to `False` by default. After placing `data_summary/` under the task directory, switch `RUN_TRAINING = True` in the command cell to start training.\n",
|
| 12 |
+
"\n",
|
| 13 |
+
"The validation set is used for checkpoint selection. Reported metrics describe\n",
|
| 14 |
+
"validation performance; this workflow does not produce a separate final test evaluation.\n"
|
| 15 |
]
|
| 16 |
},
|
| 17 |
{
|
|
|
|
| 127 |
" 'train_labels': DATA_DIR / 'new_labels_0_10.csv',\n",
|
| 128 |
" 'train_cell_features': DATA_DIR / 'new_cell_features_954.csv',\n",
|
| 129 |
" 'drug_smiles': DATA_DIR / 'smiles_0_10.csv',\n",
|
| 130 |
+
" 'validation_labels': DATA_DIR / 'test_data' / 'independent_input.csv',\n",
|
| 131 |
+
" 'validation_cell_features': DATA_DIR / 'test_data' / 'independent_cell_features_954.csv',\n",
|
| 132 |
"}\n",
|
| 133 |
"\n",
|
| 134 |
"def status_table(paths):\n",
|
|
|
|
| 381 |
"\n",
|
| 382 |
"\n",
|
| 383 |
"data_overview = []\n",
|
| 384 |
+
"train_labels = validation_labels = None\n",
|
| 385 |
"if expected_data_files['train_labels'].exists():\n",
|
| 386 |
" train_labels = read_label_table(expected_data_files['train_labels'])\n",
|
| 387 |
" data_overview.append({\n",
|
|
|
|
| 394 |
" 'unique_drug2': train_labels['drug2'].nunique(),\n",
|
| 395 |
" 'unique_pairs': train_labels[['drug1', 'drug2']].drop_duplicates().shape[0],\n",
|
| 396 |
" })\n",
|
| 397 |
+
"if expected_data_files['validation_labels'].exists():\n",
|
| 398 |
+
" validation_labels = read_label_table(expected_data_files['validation_labels'])\n",
|
| 399 |
" data_overview.append({\n",
|
| 400 |
+
" 'split': 'validation',\n",
|
| 401 |
+
" 'rows': len(validation_labels),\n",
|
| 402 |
+
" 'positive': int((validation_labels['label'] == 1).sum()),\n",
|
| 403 |
+
" 'negative': int((validation_labels['label'] == 0).sum()),\n",
|
| 404 |
+
" 'unique_cells': validation_labels['cell'].nunique(),\n",
|
| 405 |
+
" 'unique_drug1': validation_labels['drug1'].nunique(),\n",
|
| 406 |
+
" 'unique_drug2': validation_labels['drug2'].nunique(),\n",
|
| 407 |
+
" 'unique_pairs': validation_labels[['drug1', 'drug2']].drop_duplicates().shape[0],\n",
|
| 408 |
" })\n",
|
| 409 |
"\n",
|
| 410 |
"data_overview_df = pd.DataFrame(data_overview)\n",
|
|
|
|
| 417 |
" label_rows = []\n",
|
| 418 |
" if train_labels is not None:\n",
|
| 419 |
" label_rows.extend([{'split': 'train', 'label': int(k), 'count': int(v)} for k, v in train_labels['label'].value_counts().sort_index().items()])\n",
|
| 420 |
+
" if validation_labels is not None:\n",
|
| 421 |
+
" label_rows.extend([{'split': 'validation', 'label': int(k), 'count': int(v)} for k, v in validation_labels['label'].value_counts().sort_index().items()])\n",
|
| 422 |
" label_df = pd.DataFrame(label_rows)\n",
|
| 423 |
" label_df.to_csv(PLOT_DIR / 'drug_combination_label_counts.csv', index=False)\n",
|
| 424 |
"\n",
|
|
|
|
| 751 |
},
|
| 752 |
"nbformat": 4,
|
| 753 |
"nbformat_minor": 5
|
| 754 |
+
}
|
BioMod/toturials/drug_response_prediction.ipynb
CHANGED
|
@@ -8,7 +8,10 @@
|
|
| 8 |
"\n",
|
| 9 |
"This notebook is a tutorial version of the drug-response launcher based on `train_drug_performer_module.py`. It uses the weight0407 PerformerModule backbone, drug graph features, and CCLE/GDSC response data.\n",
|
| 10 |
"\n",
|
| 11 |
-
"By default, the notebook does not relaunch the full 300-epoch training job. It checks the inputs, builds the exact command, reads the existing `weight0407/drug` outputs, computes metrics, and writes a small tutorial scatter plot. To retrain, set `RUN_TRAINING = True` in the training cell."
|
|
|
|
|
|
|
|
|
|
| 12 |
]
|
| 13 |
},
|
| 14 |
{
|
|
@@ -325,13 +328,19 @@
|
|
| 325 |
"print('prediction keys:', pred_npz.files)\n",
|
| 326 |
"\n",
|
| 327 |
"pred = pred_npz['pred'] if 'pred' in pred_npz.files else pred_npz['preds']\n",
|
| 328 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
"\n",
|
| 330 |
"pcc = pearsonr(pred, true)[0]\n",
|
| 331 |
"mae = np.mean(np.abs(pred - true))\n",
|
| 332 |
"rmse = np.sqrt(np.mean((pred - true) ** 2))\n",
|
| 333 |
"summary = pd.DataFrame([{\n",
|
| 334 |
-
" '
|
| 335 |
" 'pcc': pcc,\n",
|
| 336 |
" 'mae': mae,\n",
|
| 337 |
" 'rmse': rmse,\n",
|
|
@@ -388,8 +397,8 @@
|
|
| 388 |
" }\n",
|
| 389 |
"\n",
|
| 390 |
"\n",
|
| 391 |
-
"def
|
| 392 |
-
" \"\"\"Rebuild the
|
| 393 |
" drug_info_file = data_root / 'GDSC' / '1.Drug_listMon Jun 24 09_00_55 2019.csv'\n",
|
| 394 |
" cell_line_info_file = data_root / 'CCLE' / 'Cell_lines_annotations_20181226.txt'\n",
|
| 395 |
" drug_feature_dir = data_root / 'GDSC' / 'drug_graph_feat'\n",
|
|
@@ -435,16 +444,16 @@
|
|
| 435 |
" data_idx.append((each_cellline, pubchem_id, float(value), cellline2cancertype[each_cellline]))\n",
|
| 436 |
"\n",
|
| 437 |
" rng = random.Random(seed)\n",
|
| 438 |
-
"
|
| 439 |
" for each_type in TCGA_LABEL_SET:\n",
|
| 440 |
" subtype = [item for item in data_idx if item[-1] == each_type]\n",
|
| 441 |
" if not subtype:\n",
|
| 442 |
" continue\n",
|
| 443 |
" k = max(1, int(ratio * len(subtype)))\n",
|
| 444 |
" train_list = rng.sample(subtype, k)\n",
|
| 445 |
-
"
|
| 446 |
"\n",
|
| 447 |
-
" reconstructed = pd.DataFrame(
|
| 448 |
" if len(reconstructed) != n_expected:\n",
|
| 449 |
" raise ValueError(f'Reconstructed {len(reconstructed)} rows, but predictions contain {n_expected} rows.')\n",
|
| 450 |
" return reconstructed[['drug_id', 'cell_line_id', 'cancer_type']]\n",
|
|
@@ -462,10 +471,10 @@
|
|
| 462 |
"\n",
|
| 463 |
"if metadata_missing:\n",
|
| 464 |
" try:\n",
|
| 465 |
-
" recovered_meta =
|
| 466 |
" for col in metadata_cols:\n",
|
| 467 |
" analysis_df[col] = recovered_meta[col].to_numpy()\n",
|
| 468 |
-
" print('Recovered
|
| 469 |
" except Exception as exc:\n",
|
| 470 |
" print('Metadata recovery skipped:', repr(exc))\n",
|
| 471 |
" for col in metadata_cols:\n",
|
|
|
|
| 8 |
"\n",
|
| 9 |
"This notebook is a tutorial version of the drug-response launcher based on `train_drug_performer_module.py`. It uses the weight0407 PerformerModule backbone, drug graph features, and CCLE/GDSC response data.\n",
|
| 10 |
"\n",
|
| 11 |
+
"By default, the notebook does not relaunch the full 300-epoch training job. It checks the inputs, builds the exact command, reads the existing `weight0407/drug` outputs, computes metrics, and writes a small tutorial scatter plot. To retrain, set `RUN_TRAINING = True` in the training cell.\n",
|
| 12 |
+
"\n",
|
| 13 |
+
"The validation set is used for checkpoint selection. Reported metrics describe\n",
|
| 14 |
+
"validation performance; this workflow does not produce a separate final test evaluation.\n"
|
| 15 |
]
|
| 16 |
},
|
| 17 |
{
|
|
|
|
| 328 |
"print('prediction keys:', pred_npz.files)\n",
|
| 329 |
"\n",
|
| 330 |
"pred = pred_npz['pred'] if 'pred' in pred_npz.files else pred_npz['preds']\n",
|
| 331 |
+
"# Prefer the validation key; accept historical output files as well.\n",
|
| 332 |
+
"if 'validation' in pred_npz.files:\n",
|
| 333 |
+
" true = pred_npz['validation']\n",
|
| 334 |
+
"elif 'test' in pred_npz.files:\n",
|
| 335 |
+
" true = pred_npz['test']\n",
|
| 336 |
+
"else:\n",
|
| 337 |
+
" true = pred_npz['true']\n",
|
| 338 |
"\n",
|
| 339 |
"pcc = pearsonr(pred, true)[0]\n",
|
| 340 |
"mae = np.mean(np.abs(pred - true))\n",
|
| 341 |
"rmse = np.sqrt(np.mean((pred - true) ** 2))\n",
|
| 342 |
"summary = pd.DataFrame([{\n",
|
| 343 |
+
" 'n_validation': len(pred),\n",
|
| 344 |
" 'pcc': pcc,\n",
|
| 345 |
" 'mae': mae,\n",
|
| 346 |
" 'rmse': rmse,\n",
|
|
|
|
| 397 |
" }\n",
|
| 398 |
"\n",
|
| 399 |
"\n",
|
| 400 |
+
"def reconstruct_validation_metadata(data_root: Path, n_expected: int, ratio: float = 0.95, seed: int = 0) -> pd.DataFrame:\n",
|
| 401 |
+
" \"\"\"Rebuild the validation split metadata using the same deterministic split as the training script.\"\"\"\n",
|
| 402 |
" drug_info_file = data_root / 'GDSC' / '1.Drug_listMon Jun 24 09_00_55 2019.csv'\n",
|
| 403 |
" cell_line_info_file = data_root / 'CCLE' / 'Cell_lines_annotations_20181226.txt'\n",
|
| 404 |
" drug_feature_dir = data_root / 'GDSC' / 'drug_graph_feat'\n",
|
|
|
|
| 444 |
" data_idx.append((each_cellline, pubchem_id, float(value), cellline2cancertype[each_cellline]))\n",
|
| 445 |
"\n",
|
| 446 |
" rng = random.Random(seed)\n",
|
| 447 |
+
" validation_idx = []\n",
|
| 448 |
" for each_type in TCGA_LABEL_SET:\n",
|
| 449 |
" subtype = [item for item in data_idx if item[-1] == each_type]\n",
|
| 450 |
" if not subtype:\n",
|
| 451 |
" continue\n",
|
| 452 |
" k = max(1, int(ratio * len(subtype)))\n",
|
| 453 |
" train_list = rng.sample(subtype, k)\n",
|
| 454 |
+
" validation_idx.extend([item for item in subtype if item not in train_list])\n",
|
| 455 |
"\n",
|
| 456 |
+
" reconstructed = pd.DataFrame(validation_idx, columns=['cell_line_id', 'drug_id', 'true_from_metadata', 'cancer_type'])\n",
|
| 457 |
" if len(reconstructed) != n_expected:\n",
|
| 458 |
" raise ValueError(f'Reconstructed {len(reconstructed)} rows, but predictions contain {n_expected} rows.')\n",
|
| 459 |
" return reconstructed[['drug_id', 'cell_line_id', 'cancer_type']]\n",
|
|
|
|
| 471 |
"\n",
|
| 472 |
"if metadata_missing:\n",
|
| 473 |
" try:\n",
|
| 474 |
+
" recovered_meta = reconstruct_validation_metadata(DATA_ROOT, n_expected=len(analysis_df), ratio=0.95, seed=0)\n",
|
| 475 |
" for col in metadata_cols:\n",
|
| 476 |
" analysis_df[col] = recovered_meta[col].to_numpy()\n",
|
| 477 |
+
" print('Recovered validation metadata from the original GDSC/CCLE split.')\n",
|
| 478 |
" except Exception as exc:\n",
|
| 479 |
" print('Metadata recovery skipped:', repr(exc))\n",
|
| 480 |
" for col in metadata_cols:\n",
|