Fix downstream notebook project paths and unify tutorial links
Browse filesUpdate the five downstream notebooks to resolve paths from the repository layout, use the bundled BioMod checkpoint paths and supported commands, and read actual task outputs. Keep training and evaluation opt-in. Standardize the main README tutorial column to Notebook links. This commit only changes these six files; model weights, paired configuration, datasets, and training source are unchanged.
- BioMod/toturials/cell_annotation.ipynb +331 -338
- BioMod/toturials/drug_combination.ipynb +673 -754
- BioMod/toturials/drug_response_prediction.ipynb +871 -906
- BioMod/toturials/gene_perturbation.ipynb +318 -359
- BioMod/toturials/zero_shot_batch_integration.ipynb +340 -351
- README.md +3 -3
BioMod/toturials/cell_annotation.ipynb
CHANGED
|
@@ -1,338 +1,331 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# Cell annotation
|
| 8 |
-
"\n",
|
| 9 |
-
"
|
| 10 |
-
]
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
"cell_type": "markdown",
|
| 14 |
-
"metadata": {},
|
| 15 |
-
"source": [
|
| 16 |
-
"##
|
| 17 |
-
"\n",
|
| 18 |
-
"The
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
"
|
| 30 |
-
"\n",
|
| 31 |
-
"-
|
| 32 |
-
"
|
| 33 |
-
"-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
"
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
"
|
| 47 |
-
"import
|
| 48 |
-
"import
|
| 49 |
-
"import
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"\n",
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"PROJECT_ROOT
|
| 61 |
-
"\n",
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
-
"\n",
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"\n",
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
"
|
| 72 |
-
"
|
| 73 |
-
"\n",
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"
|
| 77 |
-
"
|
| 78 |
-
"\n",
|
| 79 |
-
"
|
| 80 |
-
"
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
"
|
| 97 |
-
"
|
| 98 |
-
"
|
| 99 |
-
"\n",
|
| 100 |
-
"
|
| 101 |
-
"
|
| 102 |
-
"
|
| 103 |
-
"\n",
|
| 104 |
-
"
|
| 105 |
-
"
|
| 106 |
-
"for
|
| 107 |
-
" print('[FOUND
|
| 108 |
-
]
|
| 109 |
-
},
|
| 110 |
-
{
|
| 111 |
-
"cell_type": "code",
|
| 112 |
-
"execution_count": null,
|
| 113 |
-
"metadata": {},
|
| 114 |
-
"outputs": [],
|
| 115 |
-
"source": [
|
| 116 |
-
"#
|
| 117 |
-
"
|
| 118 |
-
"
|
| 119 |
-
"
|
| 120 |
-
"
|
| 121 |
-
"
|
| 122 |
-
"
|
| 123 |
-
"\n",
|
| 124 |
-
"
|
| 125 |
-
"
|
| 126 |
-
"
|
| 127 |
-
"
|
| 128 |
-
"
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
]
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
"
|
| 153 |
-
"
|
| 154 |
-
"
|
| 155 |
-
"
|
| 156 |
-
"
|
| 157 |
-
"
|
| 158 |
-
"\n",
|
| 159 |
-
"
|
| 160 |
-
"
|
| 161 |
-
"
|
| 162 |
-
"\n",
|
| 163 |
-
"
|
| 164 |
-
"
|
| 165 |
-
"
|
| 166 |
-
"
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
"
|
| 176 |
-
"
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
"
|
| 186 |
-
"
|
| 187 |
-
" str(
|
| 188 |
-
" '--
|
| 189 |
-
" '--
|
| 190 |
-
" '--
|
| 191 |
-
" '--
|
| 192 |
-
" '--
|
| 193 |
-
" '--
|
| 194 |
-
" '--
|
| 195 |
-
" '--
|
| 196 |
-
" '--
|
| 197 |
-
" '--
|
| 198 |
-
" '--
|
| 199 |
-
" '--
|
| 200 |
-
" '--
|
| 201 |
-
" '--
|
| 202 |
-
" '--
|
| 203 |
-
" '--
|
| 204 |
-
" '--
|
| 205 |
-
" '--
|
| 206 |
-
" '--
|
| 207 |
-
" '--
|
| 208 |
-
" '--
|
| 209 |
-
" '--
|
| 210 |
-
"
|
| 211 |
-
"
|
| 212 |
-
" '--
|
| 213 |
-
"
|
| 214 |
-
"
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
"
|
| 224 |
-
"
|
| 225 |
-
"\n",
|
| 226 |
-
"
|
| 227 |
-
"
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
"
|
| 237 |
-
"
|
| 238 |
-
"
|
| 239 |
-
"
|
| 240 |
-
"\n",
|
| 241 |
-
"
|
| 242 |
-
"
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
"
|
| 250 |
-
"
|
| 251 |
-
"
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
"
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
"
|
| 285 |
-
"
|
| 286 |
-
"
|
| 287 |
-
"
|
| 288 |
-
|
| 289 |
-
"
|
| 290 |
-
"
|
| 291 |
-
"\n",
|
| 292 |
-
"
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
"
|
| 298 |
-
"
|
| 299 |
-
"
|
| 300 |
-
"
|
| 301 |
-
|
| 302 |
-
"
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
}
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
"
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
"
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
"pygments_lexer": "ipython3",
|
| 333 |
-
"version": "3.9"
|
| 334 |
-
}
|
| 335 |
-
},
|
| 336 |
-
"nbformat": 4,
|
| 337 |
-
"nbformat_minor": 5
|
| 338 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Cell annotation with BioMod model weights\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"Fine-tune a cell-type classifier on the local Zheng68K dataset using the BioMod model weights. This notebook prepares the paths and command, launches a single-GPU job when explicitly enabled, and reads the classification results."
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "markdown",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"## Project paths\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"Start Jupyter from `BioMod/toturials/`. The setup cell uses `Path(\"../..\").resolve()` to select the repository root, following the project-relative path convention used in scGPT tutorials. All code, data, model weight, and output paths are derived from this root and resolved to absolute paths for the training command. If Jupyter starts at the repository root, change that line to `Path(\".\").resolve()`. For another launch location, set `PROJECT_ROOT` to the checkout root."
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "markdown",
|
| 23 |
+
"metadata": {},
|
| 24 |
+
"source": [
|
| 25 |
+
"## Inputs and workflow\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"- Entry point: `BioMod/cell_annotation/performer_module_finetune.py`.\n",
|
| 28 |
+
"- Dataset: `data/downstream/final_data/Zheng68K.h5ad`.\n",
|
| 29 |
+
"- Model weights and matching configuration: the `.pth` / `.pkl` pair under `weight/0810model/`.\n",
|
| 30 |
+
"- Gene vocabulary, mapping, embeddings, and module definitions: `model_dependencies/`.\n",
|
| 31 |
+
"- Results: `tutorial_runs/cell_annotation_zheng68k_biomod/`.\n",
|
| 32 |
+
"\n",
|
| 33 |
+
"Select a kernel with the project's cell annotation dependencies installed. The script filters cells and genes, aligns input genes to the model vocabulary, and creates stratified training, validation, and test splits. It selects the checkpoint using validation macro-F1, then evaluates that checkpoint on the test split. The input expression values must already use the preprocessing intended for your experiment; this script does not normalize or log-transform them."
|
| 34 |
+
]
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"cell_type": "code",
|
| 38 |
+
"execution_count": null,
|
| 39 |
+
"metadata": {},
|
| 40 |
+
"outputs": [],
|
| 41 |
+
"source": [
|
| 42 |
+
"from pathlib import Path\n",
|
| 43 |
+
"import json\n",
|
| 44 |
+
"import os\n",
|
| 45 |
+
"import shlex\n",
|
| 46 |
+
"import subprocess\n",
|
| 47 |
+
"import sys\n",
|
| 48 |
+
"import tempfile\n",
|
| 49 |
+
"import pandas as pd\n",
|
| 50 |
+
"try:\n",
|
| 51 |
+
" from IPython.display import display\n",
|
| 52 |
+
"except ImportError:\n",
|
| 53 |
+
" def display(obj):\n",
|
| 54 |
+
" print(obj)\n",
|
| 55 |
+
"\n",
|
| 56 |
+
"PROJECT_ROOT = Path('../..').resolve()\n",
|
| 57 |
+
"if not (PROJECT_ROOT / 'BioMod' / 'models_performer_module.py').is_file() or not (PROJECT_ROOT / 'model_dependencies').is_dir():\n",
|
| 58 |
+
" raise FileNotFoundError(\n",
|
| 59 |
+
" 'Project root not found. Start Jupyter from BioMod/toturials, or set '\n",
|
| 60 |
+
" 'PROJECT_ROOT to your repository root (use Path(\".\").resolve() if '\n",
|
| 61 |
+
" 'Jupyter was started at the repository root).'\n",
|
| 62 |
+
" )\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"SOURCE_DIR = PROJECT_ROOT / 'BioMod'\n",
|
| 66 |
+
"ASSET_ROOT = PROJECT_ROOT / 'model_dependencies'\n",
|
| 67 |
+
"SCRIPT = SOURCE_DIR / 'cell_annotation' / 'performer_module_finetune.py'\n",
|
| 68 |
+
"DATA_PATH = PROJECT_ROOT / 'data' / 'downstream' / 'final_data' / 'Zheng68K.h5ad'\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"# Keep the BioMod model weights and their matching configuration together.\n",
|
| 71 |
+
"WEIGHT_DIR = PROJECT_ROOT / 'weight' / '0810model'\n",
|
| 72 |
+
"CONFIG_PKL = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2.pkl'\n",
|
| 73 |
+
"CKPT_PATH = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2_epoch0.pth'\n",
|
| 74 |
+
"MODULE_DATA_DIR = ASSET_ROOT / 'module_data_all'\n",
|
| 75 |
+
"GENE_LIST = ASSET_ROOT / 'selected_genes_27k.txt'\n",
|
| 76 |
+
"GENE_MAP = ASSET_ROOT / 'gene2ens.csv'\n",
|
| 77 |
+
"GENE2VEC = ASSET_ROOT / 'selected_gene2vec_27k.npy'\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"OUTPUT_PARENT = PROJECT_ROOT / 'tutorial_runs'\n",
|
| 80 |
+
"RUN_NAME = 'cell_annotation_zheng68k_biomod'\n",
|
| 81 |
+
"RUN_DIR = OUTPUT_PARENT / RUN_NAME\n",
|
| 82 |
+
"RUN_TRAINING = False\n",
|
| 83 |
+
"\n",
|
| 84 |
+
"print('Python:', sys.executable)\n",
|
| 85 |
+
"print('Repository:', PROJECT_ROOT)\n",
|
| 86 |
+
"print('Dataset:', DATA_PATH)\n",
|
| 87 |
+
"print('Run directory:', RUN_DIR)"
|
| 88 |
+
]
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"cell_type": "code",
|
| 92 |
+
"execution_count": null,
|
| 93 |
+
"metadata": {},
|
| 94 |
+
"outputs": [],
|
| 95 |
+
"source": [
|
| 96 |
+
"required_files = [\n",
|
| 97 |
+
" SCRIPT, DATA_PATH, CONFIG_PKL, CKPT_PATH, GENE_LIST, GENE_MAP, GENE2VEC,\n",
|
| 98 |
+
" SOURCE_DIR / 'models_performer_module.py',\n",
|
| 99 |
+
" MODULE_DATA_DIR / 'merged_modules.json',\n",
|
| 100 |
+
"]\n",
|
| 101 |
+
"required_dirs = [SOURCE_DIR, ASSET_ROOT]\n",
|
| 102 |
+
"missing = [str(p) for p in required_files if not p.is_file()]\n",
|
| 103 |
+
"missing += [str(p) for p in required_dirs if not p.is_dir()]\n",
|
| 104 |
+
"if missing:\n",
|
| 105 |
+
" raise FileNotFoundError('Missing required paths:\\n' + '\\n'.join(missing))\n",
|
| 106 |
+
"for path in required_files:\n",
|
| 107 |
+
" print('[FOUND]', path)"
|
| 108 |
+
]
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"cell_type": "code",
|
| 112 |
+
"execution_count": null,
|
| 113 |
+
"metadata": {},
|
| 114 |
+
"outputs": [],
|
| 115 |
+
"source": [
|
| 116 |
+
"# Configure only the child process; leave the notebook kernel environment unchanged.\n",
|
| 117 |
+
"run_env = os.environ.copy()\n",
|
| 118 |
+
"run_env['PYTHONUNBUFFERED'] = '1'\n",
|
| 119 |
+
"run_env['CUDA_VISIBLE_DEVICES'] = '0'\n",
|
| 120 |
+
"run_env.setdefault('OMP_NUM_THREADS', '8')\n",
|
| 121 |
+
"run_env.setdefault('NCCL_DEBUG', 'WARN')\n",
|
| 122 |
+
"run_env.setdefault('NUMBA_CACHE_DIR', str(Path(tempfile.gettempdir()) / f'biomod_annotation_{os.getpid()}'))\n",
|
| 123 |
+
"pythonpath_parts = [str(PROJECT_ROOT), str(SOURCE_DIR), str(ASSET_ROOT)]\n",
|
| 124 |
+
"if run_env.get('PYTHONPATH'):\n",
|
| 125 |
+
" pythonpath_parts.append(run_env['PYTHONPATH'])\n",
|
| 126 |
+
"run_env['PYTHONPATH'] = os.pathsep.join(pythonpath_parts)\n",
|
| 127 |
+
"print('CUDA_VISIBLE_DEVICES =', run_env['CUDA_VISIBLE_DEVICES'])\n",
|
| 128 |
+
"print('PYTHONPATH =', run_env['PYTHONPATH'])"
|
| 129 |
+
]
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"cell_type": "markdown",
|
| 133 |
+
"metadata": {},
|
| 134 |
+
"source": [
|
| 135 |
+
"## Training settings\n",
|
| 136 |
+
"\n",
|
| 137 |
+
"Review the hyperparameters below before setting `RUN_TRAINING = True` in the setup cell. Adjust batch sizes to your GPU memory. `MAX_CELLS = None` uses all cells that pass filtering; setting an integer requests a subset. The values below configure an example training run and do not establish a benchmark result or a runtime estimate."
|
| 138 |
+
]
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"cell_type": "code",
|
| 142 |
+
"execution_count": null,
|
| 143 |
+
"metadata": {},
|
| 144 |
+
"outputs": [],
|
| 145 |
+
"source": [
|
| 146 |
+
"SEED = 2021\n",
|
| 147 |
+
"EPOCHS = 1\n",
|
| 148 |
+
"PATIENCE = 1\n",
|
| 149 |
+
"MAX_CELLS = 5000\n",
|
| 150 |
+
"\n",
|
| 151 |
+
"BATCH_SIZE = 24\n",
|
| 152 |
+
"EVAL_BATCH_SIZE = 48\n",
|
| 153 |
+
"GRAD_ACCUM_STEPS = 4\n",
|
| 154 |
+
"LR_HEAD = '1e-4'\n",
|
| 155 |
+
"LR_BACKBONE = '1e-4'\n",
|
| 156 |
+
"WEIGHT_DECAY = '1e-2'\n",
|
| 157 |
+
"DROPOUT = '0.2'\n",
|
| 158 |
+
"LABEL_COLUMN = 'celltype'\n",
|
| 159 |
+
"GENE_NAME_COLUMN = 'index'\n",
|
| 160 |
+
"\n",
|
| 161 |
+
"print({\n",
|
| 162 |
+
" 'seed': SEED, 'epochs': EPOCHS, 'patience': PATIENCE,\n",
|
| 163 |
+
" 'max_cells': MAX_CELLS, 'batch_size': BATCH_SIZE,\n",
|
| 164 |
+
" 'eval_batch_size': EVAL_BATCH_SIZE, 'grad_accum_steps': GRAD_ACCUM_STEPS,\n",
|
| 165 |
+
" 'label_column': LABEL_COLUMN, 'gene_name_column': GENE_NAME_COLUMN,\n",
|
| 166 |
+
"})"
|
| 167 |
+
]
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"cell_type": "code",
|
| 171 |
+
"execution_count": null,
|
| 172 |
+
"metadata": {},
|
| 173 |
+
"outputs": [],
|
| 174 |
+
"source": [
|
| 175 |
+
"cmd = [\n",
|
| 176 |
+
" sys.executable, str(SCRIPT),\n",
|
| 177 |
+
" '--device', 'cuda',\n",
|
| 178 |
+
" '--seed', str(SEED),\n",
|
| 179 |
+
" '--data_path', str(DATA_PATH),\n",
|
| 180 |
+
" '--label_column', LABEL_COLUMN,\n",
|
| 181 |
+
" '--gene_name_column', GENE_NAME_COLUMN,\n",
|
| 182 |
+
" '--val_ratio', '0.1',\n",
|
| 183 |
+
" '--test_ratio', '0.2',\n",
|
| 184 |
+
" '--min_genes', '10',\n",
|
| 185 |
+
" '--min_cells', '10',\n",
|
| 186 |
+
" '--config_pkl', str(CONFIG_PKL),\n",
|
| 187 |
+
" '--performer_checkpoint', str(CKPT_PATH),\n",
|
| 188 |
+
" '--module_data_dir', str(MODULE_DATA_DIR),\n",
|
| 189 |
+
" '--gene_list_path', str(GENE_LIST),\n",
|
| 190 |
+
" '--gene_mapping_path', str(GENE_MAP),\n",
|
| 191 |
+
" '--gene2vec_data_path', str(GENE2VEC),\n",
|
| 192 |
+
" '--gene2ens_file', str(GENE_MAP),\n",
|
| 193 |
+
" '--gene2ens_column', 'Ensembl gene ID',\n",
|
| 194 |
+
" '--head_type', 'attn_pool',\n",
|
| 195 |
+
" '--finetune_mode', 'full',\n",
|
| 196 |
+
" '--cls_decoder_layers', '3',\n",
|
| 197 |
+
" '--lr_head', LR_HEAD,\n",
|
| 198 |
+
" '--lr_backbone', LR_BACKBONE,\n",
|
| 199 |
+
" '--batch_size', str(BATCH_SIZE),\n",
|
| 200 |
+
" '--eval_batch_size', str(EVAL_BATCH_SIZE),\n",
|
| 201 |
+
" '--grad_accum_steps', str(GRAD_ACCUM_STEPS),\n",
|
| 202 |
+
" '--epochs', str(EPOCHS),\n",
|
| 203 |
+
" '--patience', str(PATIENCE),\n",
|
| 204 |
+
" '--weight_decay', WEIGHT_DECAY,\n",
|
| 205 |
+
" '--label_smoothing', '0',\n",
|
| 206 |
+
" '--dropout', DROPOUT,\n",
|
| 207 |
+
" '--output_dir', str(OUTPUT_PARENT),\n",
|
| 208 |
+
" '--run_name', RUN_NAME,\n",
|
| 209 |
+
" '--save_test_artifacts',\n",
|
| 210 |
+
"]\n",
|
| 211 |
+
"if MAX_CELLS is not None:\n",
|
| 212 |
+
" cmd.extend(['--max_cells', str(MAX_CELLS)])\n",
|
| 213 |
+
"print('Command:')\n",
|
| 214 |
+
"print(subprocess.list2cmdline(cmd) if os.name == 'nt' else shlex.join(cmd))"
|
| 215 |
+
]
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"cell_type": "code",
|
| 219 |
+
"execution_count": null,
|
| 220 |
+
"metadata": {},
|
| 221 |
+
"outputs": [],
|
| 222 |
+
"source": [
|
| 223 |
+
"log_path = RUN_DIR / 'training.log'\n",
|
| 224 |
+
"if not RUN_TRAINING:\n",
|
| 225 |
+
" print('Training is disabled. Review the paths and settings, then set RUN_TRAINING = True to launch.')\n",
|
| 226 |
+
"else:\n",
|
| 227 |
+
" RUN_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 228 |
+
" Path(run_env['NUMBA_CACHE_DIR']).mkdir(parents=True, exist_ok=True)\n",
|
| 229 |
+
" print('Writing log to:', log_path)\n",
|
| 230 |
+
" with log_path.open('w', encoding='utf-8') as log:\n",
|
| 231 |
+
" process = subprocess.Popen(\n",
|
| 232 |
+
" cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n",
|
| 233 |
+
" text=True, encoding='utf-8', errors='replace', bufsize=1,\n",
|
| 234 |
+
" cwd=str(PROJECT_ROOT), env=run_env,\n",
|
| 235 |
+
" )\n",
|
| 236 |
+
" for line in process.stdout:\n",
|
| 237 |
+
" print(line, end='')\n",
|
| 238 |
+
" log.write(line)\n",
|
| 239 |
+
" return_code = process.wait()\n",
|
| 240 |
+
" if return_code != 0:\n",
|
| 241 |
+
" raise RuntimeError(f'Cell annotation failed with return code {return_code}; see {log_path}')\n",
|
| 242 |
+
" print('Training finished.')"
|
| 243 |
+
]
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"cell_type": "markdown",
|
| 247 |
+
"metadata": {},
|
| 248 |
+
"source": [
|
| 249 |
+
"## Inspect outputs\n",
|
| 250 |
+
"\n",
|
| 251 |
+
"A successful run saves `best_model.pt` and `run_config.json`. With `--save_test_artifacts`, it also writes `metrics_summary.json`, `test_classification_report.txt`, and confusion matrices in TXT, NPY, and CSV formats. The notebook captures the training console output in `training.log`. These files are created in the run directory; the input BioMod model weights are not overwritten. Result cells also work when training is disabled and previously generated outputs are available."
|
| 252 |
+
]
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"cell_type": "code",
|
| 256 |
+
"execution_count": null,
|
| 257 |
+
"metadata": {},
|
| 258 |
+
"outputs": [],
|
| 259 |
+
"source": [
|
| 260 |
+
"if not RUN_DIR.is_dir():\n",
|
| 261 |
+
" print('No run directory yet:', RUN_DIR)\n",
|
| 262 |
+
"else:\n",
|
| 263 |
+
" for path in sorted(RUN_DIR.iterdir()):\n",
|
| 264 |
+
" size_mb = path.stat().st_size / 1024 / 1024 if path.is_file() else 0\n",
|
| 265 |
+
" print(f'{path.name:45s} {size_mb:8.2f} MB')"
|
| 266 |
+
]
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"cell_type": "code",
|
| 270 |
+
"execution_count": null,
|
| 271 |
+
"metadata": {},
|
| 272 |
+
"outputs": [],
|
| 273 |
+
"source": [
|
| 274 |
+
"metrics_path = RUN_DIR / 'metrics_summary.json'\n",
|
| 275 |
+
"if metrics_path.is_file():\n",
|
| 276 |
+
" with metrics_path.open('r', encoding='utf-8') as handle:\n",
|
| 277 |
+
" metrics_summary = json.load(handle)\n",
|
| 278 |
+
" display(metrics_summary)\n",
|
| 279 |
+
"else:\n",
|
| 280 |
+
" print('Metrics are not available yet:', metrics_path)"
|
| 281 |
+
]
|
| 282 |
+
},
|
| 283 |
+
{
|
| 284 |
+
"cell_type": "code",
|
| 285 |
+
"execution_count": null,
|
| 286 |
+
"metadata": {},
|
| 287 |
+
"outputs": [],
|
| 288 |
+
"source": [
|
| 289 |
+
"report_path = RUN_DIR / 'test_classification_report.txt'\n",
|
| 290 |
+
"if report_path.is_file():\n",
|
| 291 |
+
" print(report_path.read_text(encoding='utf-8'))\n",
|
| 292 |
+
"else:\n",
|
| 293 |
+
" print('Classification report is not available yet:', report_path)"
|
| 294 |
+
]
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"cell_type": "code",
|
| 298 |
+
"execution_count": null,
|
| 299 |
+
"metadata": {},
|
| 300 |
+
"outputs": [],
|
| 301 |
+
"source": [
|
| 302 |
+
"conf_path = RUN_DIR / 'test_confusion_matrix_normalized.csv'\n",
|
| 303 |
+
"if conf_path.is_file():\n",
|
| 304 |
+
" display(pd.read_csv(conf_path, index_col=0))\n",
|
| 305 |
+
"else:\n",
|
| 306 |
+
" print('Confusion matrix is not available yet:', conf_path)"
|
| 307 |
+
]
|
| 308 |
+
}
|
| 309 |
+
],
|
| 310 |
+
"metadata": {
|
| 311 |
+
"kernelspec": {
|
| 312 |
+
"display_name": "Python 3",
|
| 313 |
+
"language": "python",
|
| 314 |
+
"name": "python3"
|
| 315 |
+
},
|
| 316 |
+
"language_info": {
|
| 317 |
+
"codemirror_mode": {
|
| 318 |
+
"name": "ipython",
|
| 319 |
+
"version": 3
|
| 320 |
+
},
|
| 321 |
+
"file_extension": ".py",
|
| 322 |
+
"mimetype": "text/x-python",
|
| 323 |
+
"name": "python",
|
| 324 |
+
"nbconvert_exporter": "python",
|
| 325 |
+
"pygments_lexer": "ipython3",
|
| 326 |
+
"version": "3.9"
|
| 327 |
+
}
|
| 328 |
+
},
|
| 329 |
+
"nbformat": 4,
|
| 330 |
+
"nbformat_minor": 5
|
| 331 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BioMod/toturials/drug_combination.ipynb
CHANGED
|
@@ -1,754 +1,673 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# Drug combination synergy prediction
|
| 8 |
-
"\n",
|
| 9 |
-
"This notebook
|
| 10 |
-
"\n",
|
| 11 |
-
"
|
| 12 |
-
"\n",
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
"
|
| 20 |
-
"
|
| 21 |
-
|
| 22 |
-
"\n",
|
| 23 |
-
"
|
| 24 |
-
"\n",
|
| 25 |
-
"
|
| 26 |
-
"
|
| 27 |
-
"
|
| 28 |
-
"
|
| 29 |
-
"
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
-
"
|
| 33 |
-
"
|
| 34 |
-
"\n",
|
| 35 |
-
"
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
"
|
| 52 |
-
"import
|
| 53 |
-
"\n",
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"\n",
|
| 61 |
-
"
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
-
"\n",
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"\n",
|
| 71 |
-
"
|
| 72 |
-
"
|
| 73 |
-
"
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"
|
| 77 |
-
"
|
| 78 |
-
"
|
| 79 |
-
"\n",
|
| 80 |
-
"
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
"\n",
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
-
"
|
| 87 |
-
"\n",
|
| 88 |
-
"
|
| 89 |
-
"
|
| 90 |
-
"
|
| 91 |
-
"\n",
|
| 92 |
-
"
|
| 93 |
-
"
|
| 94 |
-
"
|
| 95 |
-
"
|
| 96 |
-
"
|
| 97 |
-
"
|
| 98 |
-
"
|
| 99 |
-
"\n",
|
| 100 |
-
"
|
| 101 |
-
"print('
|
| 102 |
-
"print('train script :', TRAIN_SCRIPT)\n",
|
| 103 |
-
"print('python :', TRAIN_PYTHON)\n",
|
| 104 |
-
"print('data dir :', DATA_DIR)\n",
|
| 105 |
-
"print('output dir :', OUTPUT_DIR)\n"
|
| 106 |
-
]
|
| 107 |
-
},
|
| 108 |
-
{
|
| 109 |
-
"cell_type": "code",
|
| 110 |
-
"execution_count": null,
|
| 111 |
-
"metadata": {},
|
| 112 |
-
"outputs": [],
|
| 113 |
-
"source": [
|
| 114 |
-
"required_code_and_weight_files = {\n",
|
| 115 |
-
" 'train_script': TRAIN_SCRIPT,\n",
|
| 116 |
-
" 'checkpoint_path': CHECKPOINT_PATH,\n",
|
| 117 |
-
" 'hyper_params_path': HYPER_PARAMS_PATH,\n",
|
| 118 |
-
" 'module_data_dir': MODULE_DATA_DIR,\n",
|
| 119 |
-
" 'gene_list_path': GENE_LIST_PATH,\n",
|
| 120 |
-
" 'gene_mapping_path': GENE_MAPPING_PATH,\n",
|
| 121 |
-
" 'gene2vec_path': GENE2VEC_PATH,\n",
|
| 122 |
-
" 'symbol2ens_path': SYMBOL2ENS_PATH,\n",
|
| 123 |
-
" 'performer_pytorch_cont': PERFORMER_CONT_ROOT,\n",
|
| 124 |
-
"}\n",
|
| 125 |
-
"\n",
|
| 126 |
-
"expected_data_files = {\n",
|
| 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",
|
| 135 |
-
" return pd.DataFrame(\n",
|
| 136 |
-
" [\n",
|
| 137 |
-
" {'name': name, 'path': str(path), 'exists': path.exists()}\n",
|
| 138 |
-
" for name, path in paths.items()\n",
|
| 139 |
-
" ]\n",
|
| 140 |
-
" )\n",
|
| 141 |
-
"\n",
|
| 142 |
-
"code_weight_status = status_table(required_code_and_weight_files)\n",
|
| 143 |
-
"data_status = status_table(expected_data_files)\n",
|
| 144 |
-
"DATA_READY = bool(data_status['exists'].all())\n",
|
| 145 |
-
"\n",
|
| 146 |
-
"display(code_weight_status)\n",
|
| 147 |
-
"display(data_status)\n",
|
| 148 |
-
"print('DATA_READY =', DATA_READY)\n",
|
| 149 |
-
"\n",
|
| 150 |
-
"missing_required = code_weight_status.loc[~code_weight_status['exists'], 'path'].tolist()\n",
|
| 151 |
-
"if missing_required:\n",
|
| 152 |
-
" raise FileNotFoundError('Missing required code/weight assets:\\n' + '\\n'.join(missing_required))\n",
|
| 153 |
-
"\n",
|
| 154 |
-
"if not DATA_READY:\n",
|
| 155 |
-
" print('
|
| 156 |
-
]
|
| 157 |
-
},
|
| 158 |
-
{
|
| 159 |
-
"cell_type": "code",
|
| 160 |
-
"execution_count": null,
|
| 161 |
-
"metadata": {},
|
| 162 |
-
"outputs": [],
|
| 163 |
-
"source": [
|
| 164 |
-
"packages = {\n",
|
| 165 |
-
" 'torch': 'torch',\n",
|
| 166 |
-
" 'torch_geometric': 'torch_geometric',\n",
|
| 167 |
-
" 'rdkit': 'rdkit',\n",
|
| 168 |
-
" 'networkx': 'networkx',\n",
|
| 169 |
-
" 'sklearn': 'scikit-learn',\n",
|
| 170 |
-
" 'pandas': 'pandas',\n",
|
| 171 |
-
" 'numpy': 'numpy',\n",
|
| 172 |
-
"
|
| 173 |
-
"\n",
|
| 174 |
-
"
|
| 175 |
-
"
|
| 176 |
-
"
|
| 177 |
-
"
|
| 178 |
-
"
|
| 179 |
-
"
|
| 180 |
-
"
|
| 181 |
-
"
|
| 182 |
-
"
|
| 183 |
-
"\n",
|
| 184 |
-
"
|
| 185 |
-
"
|
| 186 |
-
"\n",
|
| 187 |
-
"
|
| 188 |
-
"
|
| 189 |
-
"
|
| 190 |
-
"
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
"
|
| 205 |
-
"
|
| 206 |
-
"
|
| 207 |
-
"\n",
|
| 208 |
-
"
|
| 209 |
-
"
|
| 210 |
-
"
|
| 211 |
-
"\n",
|
| 212 |
-
"
|
| 213 |
-
"
|
| 214 |
-
"\n",
|
| 215 |
-
"
|
| 216 |
-
"
|
| 217 |
-
"
|
| 218 |
-
"
|
| 219 |
-
"
|
| 220 |
-
"
|
| 221 |
-
"
|
| 222 |
-
" '--
|
| 223 |
-
" '--
|
| 224 |
-
" '--
|
| 225 |
-
" '--
|
| 226 |
-
" '--
|
| 227 |
-
" '--
|
| 228 |
-
" '--
|
| 229 |
-
" '--
|
| 230 |
-
" '--
|
| 231 |
-
" '--
|
| 232 |
-
" '--
|
| 233 |
-
"
|
| 234 |
-
"\n",
|
| 235 |
-
"
|
| 236 |
-
"
|
| 237 |
-
"
|
| 238 |
-
"
|
| 239 |
-
"\n",
|
| 240 |
-
"
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
"
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
"
|
| 259 |
-
"
|
| 260 |
-
"
|
| 261 |
-
"
|
| 262 |
-
"
|
| 263 |
-
"
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"
|
| 267 |
-
"
|
| 268 |
-
"
|
| 269 |
-
"
|
| 270 |
-
"
|
| 271 |
-
"
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
"
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
"
|
| 290 |
-
"
|
| 291 |
-
"
|
| 292 |
-
"
|
| 293 |
-
"
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
"
|
| 301 |
-
"\n",
|
| 302 |
-
"
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
"
|
| 310 |
-
"
|
| 311 |
-
"\n",
|
| 312 |
-
"
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
"
|
| 320 |
-
"\n",
|
| 321 |
-
"
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
"
|
| 328 |
-
"
|
| 329 |
-
"source": [
|
| 330 |
-
"
|
| 331 |
-
"
|
| 332 |
-
"
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
"
|
| 342 |
-
"
|
| 343 |
-
"
|
| 344 |
-
"
|
| 345 |
-
"
|
| 346 |
-
"
|
| 347 |
-
"
|
| 348 |
-
"
|
| 349 |
-
"
|
| 350 |
-
" '
|
| 351 |
-
"
|
| 352 |
-
"\n",
|
| 353 |
-
"\n",
|
| 354 |
-
"
|
| 355 |
-
"
|
| 356 |
-
"
|
| 357 |
-
"
|
| 358 |
-
"
|
| 359 |
-
"
|
| 360 |
-
"
|
| 361 |
-
"
|
| 362 |
-
"
|
| 363 |
-
"
|
| 364 |
-
"
|
| 365 |
-
"\n",
|
| 366 |
-
"\n",
|
| 367 |
-
"
|
| 368 |
-
"
|
| 369 |
-
"
|
| 370 |
-
"
|
| 371 |
-
"\n",
|
| 372 |
-
"\n",
|
| 373 |
-
"
|
| 374 |
-
"
|
| 375 |
-
"
|
| 376 |
-
"
|
| 377 |
-
"
|
| 378 |
-
"
|
| 379 |
-
"
|
| 380 |
-
"
|
| 381 |
-
"\n",
|
| 382 |
-
"\n",
|
| 383 |
-
"
|
| 384 |
-
"
|
| 385 |
-
"
|
| 386 |
-
"
|
| 387 |
-
"
|
| 388 |
-
"
|
| 389 |
-
"
|
| 390 |
-
"
|
| 391 |
-
"
|
| 392 |
-
"
|
| 393 |
-
"
|
| 394 |
-
"
|
| 395 |
-
"
|
| 396 |
-
"
|
| 397 |
-
"
|
| 398 |
-
"
|
| 399 |
-
"
|
| 400 |
-
" '
|
| 401 |
-
" '
|
| 402 |
-
" '
|
| 403 |
-
" '
|
| 404 |
-
" '
|
| 405 |
-
" '
|
| 406 |
-
" '
|
| 407 |
-
"
|
| 408 |
-
"
|
| 409 |
-
"\n",
|
| 410 |
-
"
|
| 411 |
-
"
|
| 412 |
-
"
|
| 413 |
-
"
|
| 414 |
-
"
|
| 415 |
-
"
|
| 416 |
-
"\n",
|
| 417 |
-
"
|
| 418 |
-
"
|
| 419 |
-
"
|
| 420 |
-
"
|
| 421 |
-
"
|
| 422 |
-
"
|
| 423 |
-
"
|
| 424 |
-
"\n",
|
| 425 |
-
"
|
| 426 |
-
"
|
| 427 |
-
"
|
| 428 |
-
"
|
| 429 |
-
"
|
| 430 |
-
"
|
| 431 |
-
"
|
| 432 |
-
"
|
| 433 |
-
"
|
| 434 |
-
"
|
| 435 |
-
"
|
| 436 |
-
" ax.
|
| 437 |
-
"
|
| 438 |
-
"
|
| 439 |
-
"
|
| 440 |
-
"
|
| 441 |
-
"
|
| 442 |
-
"
|
| 443 |
-
"
|
| 444 |
-
"\n",
|
| 445 |
-
"
|
| 446 |
-
"
|
| 447 |
-
"
|
| 448 |
-
"
|
| 449 |
-
"
|
| 450 |
-
"
|
| 451 |
-
"
|
| 452 |
-
"
|
| 453 |
-
"
|
| 454 |
-
"
|
| 455 |
-
"
|
| 456 |
-
" ax.
|
| 457 |
-
"
|
| 458 |
-
"
|
| 459 |
-
"
|
| 460 |
-
"
|
| 461 |
-
"
|
| 462 |
-
"
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
"
|
| 470 |
-
"\n",
|
| 471 |
-
"
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
"
|
| 478 |
-
"
|
| 479 |
-
"source": [
|
| 480 |
-
"
|
| 481 |
-
"
|
| 482 |
-
"
|
| 483 |
-
"\n",
|
| 484 |
-
"\n",
|
| 485 |
-
"
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
"
|
| 495 |
-
"
|
| 496 |
-
"
|
| 497 |
-
"
|
| 498 |
-
"
|
| 499 |
-
"
|
| 500 |
-
"
|
| 501 |
-
"
|
| 502 |
-
"\n",
|
| 503 |
-
"\n",
|
| 504 |
-
"
|
| 505 |
-
"
|
| 506 |
-
"
|
| 507 |
-
"
|
| 508 |
-
"
|
| 509 |
-
"
|
| 510 |
-
" fold =
|
| 511 |
-
"
|
| 512 |
-
"
|
| 513 |
-
" df
|
| 514 |
-
"
|
| 515 |
-
"
|
| 516 |
-
"\n",
|
| 517 |
-
"\n",
|
| 518 |
-
"
|
| 519 |
-
"
|
| 520 |
-
"\n",
|
| 521 |
-
"
|
| 522 |
-
"
|
| 523 |
-
"
|
| 524 |
-
"
|
| 525 |
-
"
|
| 526 |
-
"
|
| 527 |
-
"
|
| 528 |
-
"
|
| 529 |
-
"
|
| 530 |
-
"
|
| 531 |
-
"
|
| 532 |
-
"
|
| 533 |
-
"
|
| 534 |
-
"
|
| 535 |
-
"\n",
|
| 536 |
-
"
|
| 537 |
-
"
|
| 538 |
-
"
|
| 539 |
-
"
|
| 540 |
-
"
|
| 541 |
-
"
|
| 542 |
-
"
|
| 543 |
-
"
|
| 544 |
-
"
|
| 545 |
-
"
|
| 546 |
-
"
|
| 547 |
-
"
|
| 548 |
-
"
|
| 549 |
-
" ax.
|
| 550 |
-
"
|
| 551 |
-
"
|
| 552 |
-
"
|
| 553 |
-
"\n",
|
| 554 |
-
"
|
| 555 |
-
"
|
| 556 |
-
"
|
| 557 |
-
"
|
| 558 |
-
"
|
| 559 |
-
"
|
| 560 |
-
"
|
| 561 |
-
" ax.
|
| 562 |
-
" ax.
|
| 563 |
-
"
|
| 564 |
-
"
|
| 565 |
-
"
|
| 566 |
-
"
|
| 567 |
-
" ax.
|
| 568 |
-
"
|
| 569 |
-
"
|
| 570 |
-
"
|
| 571 |
-
"\n",
|
| 572 |
-
"
|
| 573 |
-
"
|
| 574 |
-
"
|
| 575 |
-
"
|
| 576 |
-
"
|
| 577 |
-
"
|
| 578 |
-
"
|
| 579 |
-
" ax.
|
| 580 |
-
" ax.
|
| 581 |
-
"
|
| 582 |
-
"
|
| 583 |
-
"
|
| 584 |
-
"
|
| 585 |
-
"
|
| 586 |
-
"
|
| 587 |
-
"
|
| 588 |
-
"
|
| 589 |
-
"\n",
|
| 590 |
-
"
|
| 591 |
-
"
|
| 592 |
-
"
|
| 593 |
-
"
|
| 594 |
-
"
|
| 595 |
-
"
|
| 596 |
-
"
|
| 597 |
-
"
|
| 598 |
-
"
|
| 599 |
-
"
|
| 600 |
-
"
|
| 601 |
-
"
|
| 602 |
-
"
|
| 603 |
-
"
|
| 604 |
-
"
|
| 605 |
-
"
|
| 606 |
-
"
|
| 607 |
-
"
|
| 608 |
-
"
|
| 609 |
-
"
|
| 610 |
-
"
|
| 611 |
-
"
|
| 612 |
-
"
|
| 613 |
-
"
|
| 614 |
-
"
|
| 615 |
-
"
|
| 616 |
-
"
|
| 617 |
-
"
|
| 618 |
-
"
|
| 619 |
-
"\n",
|
| 620 |
-
"\n",
|
| 621 |
-
"
|
| 622 |
-
"
|
| 623 |
-
"
|
| 624 |
-
"
|
| 625 |
-
"
|
| 626 |
-
"
|
| 627 |
-
"
|
| 628 |
-
"
|
| 629 |
-
"
|
| 630 |
-
"
|
| 631 |
-
"
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
"
|
| 639 |
-
"
|
| 640 |
-
"
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
"
|
| 650 |
-
"
|
| 651 |
-
"
|
| 652 |
-
"
|
| 653 |
-
"
|
| 654 |
-
"
|
| 655 |
-
"
|
| 656 |
-
"
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
" ax.set_ylabel('True positive rate')\n",
|
| 675 |
-
" ax.set_xlim(0, 1)\n",
|
| 676 |
-
" ax.set_ylim(0, 1.02)\n",
|
| 677 |
-
" ax.set_aspect('equal', adjustable='box')\n",
|
| 678 |
-
" ax.legend(frameon=False, loc='lower right')\n",
|
| 679 |
-
" ax.set_title('ROC from prediction CSV')\n",
|
| 680 |
-
" despine(ax)\n",
|
| 681 |
-
" fig.tight_layout()\n",
|
| 682 |
-
" save_fig(fig, 'drug_combination_legacy_prediction_roc')\n",
|
| 683 |
-
"\n",
|
| 684 |
-
" fig, ax = plt.subplots(figsize=(3.2, 3.0))\n",
|
| 685 |
-
" for r in legacy_records:\n",
|
| 686 |
-
" ax.plot(r['recall'], r['precision'], linewidth=1.0, label=f'{r[\"path\"].stem}, AUPR={r[\"pr_auc\"]:.3f}')\n",
|
| 687 |
-
" ax.set_xlabel('Recall')\n",
|
| 688 |
-
" ax.set_ylabel('Precision')\n",
|
| 689 |
-
" ax.set_xlim(0, 1)\n",
|
| 690 |
-
" ax.set_ylim(0, 1.02)\n",
|
| 691 |
-
" ax.legend(frameon=False, loc='lower left')\n",
|
| 692 |
-
" ax.set_title('Precision-recall curve')\n",
|
| 693 |
-
" despine(ax)\n",
|
| 694 |
-
" fig.tight_layout()\n",
|
| 695 |
-
" save_fig(fig, 'drug_combination_legacy_prediction_pr_curve')\n",
|
| 696 |
-
"\n",
|
| 697 |
-
" first = legacy_records[0]\n",
|
| 698 |
-
" fig, ax = plt.subplots(figsize=(2.4, 2.3))\n",
|
| 699 |
-
" im = ax.imshow(first['cm'], cmap='Blues')\n",
|
| 700 |
-
" for row in range(first['cm'].shape[0]):\n",
|
| 701 |
-
" for col in range(first['cm'].shape[1]):\n",
|
| 702 |
-
" ax.text(col, row, int(first['cm'][row, col]), ha='center', va='center', fontsize=7)\n",
|
| 703 |
-
" ax.set_xticks([0, 1])\n",
|
| 704 |
-
" ax.set_yticks([0, 1])\n",
|
| 705 |
-
" ax.set_xticklabels(['0', '1'])\n",
|
| 706 |
-
" ax.set_yticklabels(['0', '1'])\n",
|
| 707 |
-
" ax.set_xlabel('Predicted label')\n",
|
| 708 |
-
" ax.set_ylabel('True label')\n",
|
| 709 |
-
" ax.set_title('Confusion matrix')\n",
|
| 710 |
-
" fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)\n",
|
| 711 |
-
" fig.tight_layout()\n",
|
| 712 |
-
" save_fig(fig, 'drug_combination_legacy_confusion_matrix')\n"
|
| 713 |
-
]
|
| 714 |
-
},
|
| 715 |
-
{
|
| 716 |
-
"cell_type": "markdown",
|
| 717 |
-
"metadata": {},
|
| 718 |
-
"source": [
|
| 719 |
-
"## Generated plot files\n",
|
| 720 |
-
"\n",
|
| 721 |
-
"This cell lists the figures and source tables generated by the notebook plotting sections.\n"
|
| 722 |
-
]
|
| 723 |
-
},
|
| 724 |
-
{
|
| 725 |
-
"cell_type": "code",
|
| 726 |
-
"execution_count": null,
|
| 727 |
-
"metadata": {},
|
| 728 |
-
"outputs": [],
|
| 729 |
-
"source": [
|
| 730 |
-
"plot_files = sorted(PLOT_DIR.glob('*'))\n",
|
| 731 |
-
"if plot_files:\n",
|
| 732 |
-
" display(pd.DataFrame([\n",
|
| 733 |
-
" {'file': path.name, 'size_kb': round(path.stat().st_size / 1024, 2), 'path': str(path)}\n",
|
| 734 |
-
" for path in plot_files\n",
|
| 735 |
-
" ]))\n",
|
| 736 |
-
"else:\n",
|
| 737 |
-
" print('No notebook plot files were generated yet.')\n"
|
| 738 |
-
]
|
| 739 |
-
}
|
| 740 |
-
],
|
| 741 |
-
"metadata": {
|
| 742 |
-
"kernelspec": {
|
| 743 |
-
"display_name": "Python 3 (sclong)",
|
| 744 |
-
"language": "python",
|
| 745 |
-
"name": "python3"
|
| 746 |
-
},
|
| 747 |
-
"language_info": {
|
| 748 |
-
"name": "python",
|
| 749 |
-
"pygments_lexer": "ipython3"
|
| 750 |
-
}
|
| 751 |
-
},
|
| 752 |
-
"nbformat": 4,
|
| 753 |
-
"nbformat_minor": 5
|
| 754 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Drug combination synergy prediction with BioMod\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook checks the bundled drug-combination inputs, prepares a command for\n",
|
| 10 |
+
"`BioMod/drug_combination/train_drug_combination_performer_module.py`, and plots\n",
|
| 11 |
+
"available training outputs. It uses the BioMod model weights and configuration.\n",
|
| 12 |
+
"\n",
|
| 13 |
+
"`RUN_TRAINING = False` by default. Set it to `True` only when you are ready to train\n",
|
| 14 |
+
"in the active notebook environment. The validation set selects the checkpoint;\n",
|
| 15 |
+
"this workflow does not produce a separate final test evaluation.\n"
|
| 16 |
+
]
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"cell_type": "markdown",
|
| 20 |
+
"metadata": {},
|
| 21 |
+
"source": [
|
| 22 |
+
"## Project paths and dataset layout\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"Start Jupyter from `BioMod/toturials/`. The path cell uses `Path('../..')` for the\n",
|
| 25 |
+
"repository root and resolves project-relative paths for the training subprocess.\n",
|
| 26 |
+
"If Jupyter starts at the repository root, use `Path('.')` instead. The dataset is\n",
|
| 27 |
+
"already included in the project:\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"```text\n",
|
| 30 |
+
"data/downstream/final_data/drug_combination_synergy/data_summary/\n",
|
| 31 |
+
"|-- new_labels_0_10.csv\n",
|
| 32 |
+
"|-- new_cell_features_954.csv\n",
|
| 33 |
+
"|-- smiles_0_10.csv\n",
|
| 34 |
+
"`-- test_data/\n",
|
| 35 |
+
" |-- independent_input.csv\n",
|
| 36 |
+
" `-- independent_cell_features_954.csv\n",
|
| 37 |
+
"```\n",
|
| 38 |
+
"\n",
|
| 39 |
+
"The historical folder name `test_data` is retained; these files are used as the\n",
|
| 40 |
+
"validation set. The example uses the domain-generalization mode with one run,\n",
|
| 41 |
+
"500 epochs, a batch size of 256, and max pooling. These are example settings,\n",
|
| 42 |
+
"not a verified performance claim for the current weights.\n"
|
| 43 |
+
]
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"cell_type": "code",
|
| 47 |
+
"execution_count": null,
|
| 48 |
+
"metadata": {},
|
| 49 |
+
"outputs": [],
|
| 50 |
+
"source": [
|
| 51 |
+
"from pathlib import Path\n",
|
| 52 |
+
"import importlib.util\n",
|
| 53 |
+
"import os\n",
|
| 54 |
+
"import shlex\n",
|
| 55 |
+
"import subprocess\n",
|
| 56 |
+
"import sys\n",
|
| 57 |
+
"\n",
|
| 58 |
+
"import numpy as np\n",
|
| 59 |
+
"import pandas as pd\n",
|
| 60 |
+
"\n",
|
| 61 |
+
"try:\n",
|
| 62 |
+
" from IPython.display import Image, display\n",
|
| 63 |
+
"except ImportError:\n",
|
| 64 |
+
" Image = None\n",
|
| 65 |
+
" def display(obj):\n",
|
| 66 |
+
" print(obj)\n",
|
| 67 |
+
"\n",
|
| 68 |
+
"# Start Jupyter with BioMod/toturials as the working directory.\n",
|
| 69 |
+
"# If Jupyter starts at the repository root, change this line to Path('.').resolve().\n",
|
| 70 |
+
"PROJECT_ROOT = Path('../..').resolve()\n",
|
| 71 |
+
"SOURCE_DIR = PROJECT_ROOT / 'BioMod'\n",
|
| 72 |
+
"if not (SOURCE_DIR / 'models_performer_module.py').is_file() or not (PROJECT_ROOT / 'model_dependencies').is_dir():\n",
|
| 73 |
+
" raise FileNotFoundError(\n",
|
| 74 |
+
" 'Start Jupyter from BioMod/toturials, or set PROJECT_ROOT to the repository root '\n",
|
| 75 |
+
" \"(use Path('.').resolve() if Jupyter was launched at that root).\"\n",
|
| 76 |
+
" )\n",
|
| 77 |
+
"REPO_ROOT = PROJECT_ROOT\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"DEPENDENCY_ROOT = REPO_ROOT / 'model_dependencies'\n",
|
| 80 |
+
"WEIGHT_DIR = REPO_ROOT / 'weight' / '0810model'\n",
|
| 81 |
+
"MODULE_DATA_DIR = DEPENDENCY_ROOT / 'module_data_all'\n",
|
| 82 |
+
"TRAIN_PYTHON = Path(sys.executable).resolve()\n",
|
| 83 |
+
"PYTHONPATH = os.pathsep.join(\n",
|
| 84 |
+
" [str(SOURCE_DIR), str(DEPENDENCY_ROOT)]\n",
|
| 85 |
+
" + ([os.environ['PYTHONPATH']] if os.environ.get('PYTHONPATH') else [])\n",
|
| 86 |
+
")\n",
|
| 87 |
+
"\n",
|
| 88 |
+
"TASK_ROOT = SOURCE_DIR / 'drug_combination'\n",
|
| 89 |
+
"TRAIN_SCRIPT = TASK_ROOT / 'train_drug_combination_performer_module.py'\n",
|
| 90 |
+
"CHECKPOINT_PATH = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2_epoch0.pth'\n",
|
| 91 |
+
"HYPER_PARAMS_PATH = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2.pkl'\n",
|
| 92 |
+
"GENE_LIST_PATH = DEPENDENCY_ROOT / 'selected_genes_27k.txt'\n",
|
| 93 |
+
"GENE_MAPPING_PATH = DEPENDENCY_ROOT / 'gene2ens.csv'\n",
|
| 94 |
+
"GENE2VEC_PATH = DEPENDENCY_ROOT / 'selected_gene2vec_27k.npy'\n",
|
| 95 |
+
"SYMBOL2ENS_PATH = GENE_MAPPING_PATH\n",
|
| 96 |
+
"PERFORMER_CONT_ROOT = DEPENDENCY_ROOT / 'performer_pytorch_cont'\n",
|
| 97 |
+
"DATA_DIR = REPO_ROOT / 'data/downstream/final_data/drug_combination_synergy/data_summary'\n",
|
| 98 |
+
"RUN_ROOT = REPO_ROOT / 'tutorial_runs/drug_combination'\n",
|
| 99 |
+
"OUTPUT_DIR = RUN_ROOT / 'results'\n",
|
| 100 |
+
"\n",
|
| 101 |
+
"print('repository :', REPO_ROOT)\n",
|
| 102 |
+
"print('train script :', TRAIN_SCRIPT)\n",
|
| 103 |
+
"print('python :', TRAIN_PYTHON)\n",
|
| 104 |
+
"print('data dir :', DATA_DIR)\n",
|
| 105 |
+
"print('output dir :', OUTPUT_DIR)\n"
|
| 106 |
+
]
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"cell_type": "code",
|
| 110 |
+
"execution_count": null,
|
| 111 |
+
"metadata": {},
|
| 112 |
+
"outputs": [],
|
| 113 |
+
"source": [
|
| 114 |
+
"required_code_and_weight_files = {\n",
|
| 115 |
+
" 'train_script': TRAIN_SCRIPT,\n",
|
| 116 |
+
" 'checkpoint_path': CHECKPOINT_PATH,\n",
|
| 117 |
+
" 'hyper_params_path': HYPER_PARAMS_PATH,\n",
|
| 118 |
+
" 'module_data_dir': MODULE_DATA_DIR,\n",
|
| 119 |
+
" 'gene_list_path': GENE_LIST_PATH,\n",
|
| 120 |
+
" 'gene_mapping_path': GENE_MAPPING_PATH,\n",
|
| 121 |
+
" 'gene2vec_path': GENE2VEC_PATH,\n",
|
| 122 |
+
" 'symbol2ens_path': SYMBOL2ENS_PATH,\n",
|
| 123 |
+
" 'performer_pytorch_cont': PERFORMER_CONT_ROOT,\n",
|
| 124 |
+
"}\n",
|
| 125 |
+
"\n",
|
| 126 |
+
"expected_data_files = {\n",
|
| 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",
|
| 135 |
+
" return pd.DataFrame(\n",
|
| 136 |
+
" [\n",
|
| 137 |
+
" {'name': name, 'path': str(path), 'exists': path.exists()}\n",
|
| 138 |
+
" for name, path in paths.items()\n",
|
| 139 |
+
" ]\n",
|
| 140 |
+
" )\n",
|
| 141 |
+
"\n",
|
| 142 |
+
"code_weight_status = status_table(required_code_and_weight_files)\n",
|
| 143 |
+
"data_status = status_table(expected_data_files)\n",
|
| 144 |
+
"DATA_READY = bool(data_status['exists'].all())\n",
|
| 145 |
+
"\n",
|
| 146 |
+
"display(code_weight_status)\n",
|
| 147 |
+
"display(data_status)\n",
|
| 148 |
+
"print('DATA_READY =', DATA_READY)\n",
|
| 149 |
+
"\n",
|
| 150 |
+
"missing_required = code_weight_status.loc[~code_weight_status['exists'], 'path'].tolist()\n",
|
| 151 |
+
"if missing_required:\n",
|
| 152 |
+
" raise FileNotFoundError('Missing required code/weight assets:\\n' + '\\n'.join(missing_required))\n",
|
| 153 |
+
"\n",
|
| 154 |
+
"if not DATA_READY:\n",
|
| 155 |
+
" print('Some dataset files are missing. This notebook will not launch training until RUN_TRAINING=True and DATA_READY=True.')\n"
|
| 156 |
+
]
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"cell_type": "code",
|
| 160 |
+
"execution_count": null,
|
| 161 |
+
"metadata": {},
|
| 162 |
+
"outputs": [],
|
| 163 |
+
"source": [
|
| 164 |
+
"packages = {\n",
|
| 165 |
+
" 'torch': 'torch',\n",
|
| 166 |
+
" 'torch_geometric': 'torch_geometric',\n",
|
| 167 |
+
" 'rdkit': 'rdkit',\n",
|
| 168 |
+
" 'networkx': 'networkx',\n",
|
| 169 |
+
" 'sklearn': 'scikit-learn',\n",
|
| 170 |
+
" 'pandas': 'pandas',\n",
|
| 171 |
+
" 'numpy': 'numpy',\n",
|
| 172 |
+
" 'matplotlib': 'matplotlib',\n",
|
| 173 |
+
" 'scipy': 'scipy',\n",
|
| 174 |
+
" 'einops': 'einops',\n",
|
| 175 |
+
" 'local_attention': 'local-attention',\n",
|
| 176 |
+
"}\n",
|
| 177 |
+
"\n",
|
| 178 |
+
"dependency_rows = []\n",
|
| 179 |
+
"for module_name, package_name in packages.items():\n",
|
| 180 |
+
" dependency_rows.append(\n",
|
| 181 |
+
" {\n",
|
| 182 |
+
" 'module': module_name,\n",
|
| 183 |
+
" 'package': package_name,\n",
|
| 184 |
+
" 'available': importlib.util.find_spec(module_name) is not None,\n",
|
| 185 |
+
" }\n",
|
| 186 |
+
" )\n",
|
| 187 |
+
"\n",
|
| 188 |
+
"dependency_status = pd.DataFrame(dependency_rows)\n",
|
| 189 |
+
"display(dependency_status)\n",
|
| 190 |
+
"\n",
|
| 191 |
+
"missing_packages = dependency_status.loc[~dependency_status['available'], 'package'].tolist()\n",
|
| 192 |
+
"if missing_packages:\n",
|
| 193 |
+
" print('Install missing packages in the active notebook environment:')\n",
|
| 194 |
+
" install_cmd = [str(TRAIN_PYTHON), '-m', 'pip', 'install', *missing_packages]\n",
|
| 195 |
+
" print(subprocess.list2cmdline(install_cmd) if os.name == 'nt' else shlex.join(install_cmd))\n"
|
| 196 |
+
]
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"cell_type": "code",
|
| 200 |
+
"execution_count": null,
|
| 201 |
+
"metadata": {},
|
| 202 |
+
"outputs": [],
|
| 203 |
+
"source": [
|
| 204 |
+
"# Example training parameters.\n",
|
| 205 |
+
"EPOCHS = 500\n",
|
| 206 |
+
"BATCH_SIZE = 256\n",
|
| 207 |
+
"LR = 1e-4\n",
|
| 208 |
+
"N_FOLDS = 1\n",
|
| 209 |
+
"POOL_METHOD = 'max'\n",
|
| 210 |
+
"EVAL_INTERVAL = 5\n",
|
| 211 |
+
"SEED = 42\n",
|
| 212 |
+
"\n",
|
| 213 |
+
"# Domain-generalization mode is the script default. Set to False for k-fold CV.\n",
|
| 214 |
+
"DOMAIN_GENERALIZATION = True\n",
|
| 215 |
+
"FORCE_PREPROCESS = False\n",
|
| 216 |
+
"\n",
|
| 217 |
+
"# Keep False to inspect inputs and commands without starting training.\n",
|
| 218 |
+
"RUN_TRAINING = False\n",
|
| 219 |
+
"\n",
|
| 220 |
+
"cmd = [\n",
|
| 221 |
+
" str(TRAIN_PYTHON), str(TRAIN_SCRIPT),\n",
|
| 222 |
+
" '--checkpoint_path', str(CHECKPOINT_PATH),\n",
|
| 223 |
+
" '--hyper_params_path', str(HYPER_PARAMS_PATH),\n",
|
| 224 |
+
" '--module_data_dir', str(MODULE_DATA_DIR),\n",
|
| 225 |
+
" '--gene_list_path', str(GENE_LIST_PATH),\n",
|
| 226 |
+
" '--gene_mapping_path', str(GENE_MAPPING_PATH),\n",
|
| 227 |
+
" '--gene2vec_path', str(GENE2VEC_PATH),\n",
|
| 228 |
+
" '--symbol2ens_path', str(SYMBOL2ENS_PATH),\n",
|
| 229 |
+
" '--data_dir', str(DATA_DIR),\n",
|
| 230 |
+
" '--output_dir', str(OUTPUT_DIR),\n",
|
| 231 |
+
" '--epochs', str(EPOCHS),\n",
|
| 232 |
+
" '--batch_size', str(BATCH_SIZE),\n",
|
| 233 |
+
" '--lr', str(LR),\n",
|
| 234 |
+
" '--n_folds', str(N_FOLDS),\n",
|
| 235 |
+
" '--pool_method', POOL_METHOD,\n",
|
| 236 |
+
" '--eval_interval', str(EVAL_INTERVAL),\n",
|
| 237 |
+
" '--seed', str(SEED),\n",
|
| 238 |
+
"]\n",
|
| 239 |
+
"\n",
|
| 240 |
+
"if not DOMAIN_GENERALIZATION:\n",
|
| 241 |
+
" cmd.append('--no_domain_gen')\n",
|
| 242 |
+
"if FORCE_PREPROCESS:\n",
|
| 243 |
+
" cmd.append('--force_preprocess')\n",
|
| 244 |
+
"\n",
|
| 245 |
+
"if EVAL_INTERVAL < 1 or EPOCHS < EVAL_INTERVAL:\n",
|
| 246 |
+
" raise ValueError('Use at least one validation interval: EPOCHS >= EVAL_INTERVAL >= 1.')\n",
|
| 247 |
+
"if not DOMAIN_GENERALIZATION and N_FOLDS < 2:\n",
|
| 248 |
+
" raise ValueError('Cross-validation requires N_FOLDS >= 2.')\n",
|
| 249 |
+
"print(subprocess.list2cmdline(cmd) if os.name == 'nt' else shlex.join(cmd))\n"
|
| 250 |
+
]
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"cell_type": "code",
|
| 254 |
+
"execution_count": null,
|
| 255 |
+
"metadata": {},
|
| 256 |
+
"outputs": [],
|
| 257 |
+
"source": [
|
| 258 |
+
"env = os.environ.copy()\n",
|
| 259 |
+
"env['PYTHONPATH'] = PYTHONPATH\n",
|
| 260 |
+
"\n",
|
| 261 |
+
"if RUN_TRAINING:\n",
|
| 262 |
+
" OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 263 |
+
" if not DATA_READY:\n",
|
| 264 |
+
" missing_data = data_status.loc[~data_status['exists'], 'path'].tolist()\n",
|
| 265 |
+
" raise FileNotFoundError('Data are incomplete:\\n' + '\\n'.join(missing_data))\n",
|
| 266 |
+
" result = subprocess.run(\n",
|
| 267 |
+
" cmd,\n",
|
| 268 |
+
" cwd=str(TASK_ROOT),\n",
|
| 269 |
+
" env=env,\n",
|
| 270 |
+
" text=True,\n",
|
| 271 |
+
" stdout=subprocess.PIPE,\n",
|
| 272 |
+
" stderr=subprocess.STDOUT,\n",
|
| 273 |
+
" check=False,\n",
|
| 274 |
+
" )\n",
|
| 275 |
+
" print(result.stdout)\n",
|
| 276 |
+
" if result.returncode != 0:\n",
|
| 277 |
+
" raise RuntimeError(f'Training failed with return code {result.returncode}')\n",
|
| 278 |
+
"else:\n",
|
| 279 |
+
" print('RUN_TRAINING is False, so no training/smokecheck was launched.')\n",
|
| 280 |
+
" print('Set RUN_TRAINING=True after reviewing the inputs, dependencies, and command.')\n"
|
| 281 |
+
]
|
| 282 |
+
},
|
| 283 |
+
{
|
| 284 |
+
"cell_type": "code",
|
| 285 |
+
"execution_count": null,
|
| 286 |
+
"metadata": {},
|
| 287 |
+
"outputs": [],
|
| 288 |
+
"source": [
|
| 289 |
+
"if OUTPUT_DIR.exists():\n",
|
| 290 |
+
" output_files = sorted(OUTPUT_DIR.glob('*'))\n",
|
| 291 |
+
" display(pd.DataFrame(\n",
|
| 292 |
+
" [\n",
|
| 293 |
+
" {\n",
|
| 294 |
+
" 'file': f.name,\n",
|
| 295 |
+
" 'size_mb': round(f.stat().st_size / 1024 / 1024, 3),\n",
|
| 296 |
+
" 'path': str(f),\n",
|
| 297 |
+
" }\n",
|
| 298 |
+
" for f in output_files\n",
|
| 299 |
+
" ]\n",
|
| 300 |
+
" ))\n",
|
| 301 |
+
"else:\n",
|
| 302 |
+
" print('No output directory yet:', OUTPUT_DIR)"
|
| 303 |
+
]
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"cell_type": "markdown",
|
| 307 |
+
"metadata": {},
|
| 308 |
+
"source": [
|
| 309 |
+
"## Output files\n",
|
| 310 |
+
"\n",
|
| 311 |
+
"The training script writes the following files under `OUTPUT_DIR`:\n",
|
| 312 |
+
"\n",
|
| 313 |
+
"- `best_model_fold{i}.pt`: the selected downstream model.\n",
|
| 314 |
+
"- `loss_fold{i}.csv` and `loss_curve_fold{i}.png`: training loss (`epoch`, `avg_loss`).\n",
|
| 315 |
+
"- `metrics_fold{i}.csv`: validation metrics (`epoch`, `AUC`, `PR_AUC`, `ACC`,\n",
|
| 316 |
+
" `BACC`, `PREC`, `TPR`, `KAPPA`, `RECALL`).\n",
|
| 317 |
+
"- `roc_curve_fold{i}.png` and `fpr_tpr_fold{i}.pkl`: the selected validation ROC.\n",
|
| 318 |
+
"- `cell_emb_cache_{pool_method}.npy` and `cell_emb_dim_{pool_method}.txt`: cached\n",
|
| 319 |
+
" cell features when caching is enabled.\n",
|
| 320 |
+
"\n",
|
| 321 |
+
"The following cells use only available local outputs. Missing results are skipped.\n",
|
| 322 |
+
"After changing the weights or input data, use a fresh output directory and set\n",
|
| 323 |
+
"`FORCE_PREPROCESS = True` to rebuild the script's processed-data cache.\n"
|
| 324 |
+
]
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
"cell_type": "markdown",
|
| 328 |
+
"metadata": {},
|
| 329 |
+
"source": [
|
| 330 |
+
"## Data overview plots\n",
|
| 331 |
+
"\n",
|
| 332 |
+
"These plots summarize the drug-combination dataset before training. They are useful for quick notebook demos and GitHub documentation because they do not require a completed model run.\n"
|
| 333 |
+
]
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"cell_type": "code",
|
| 337 |
+
"execution_count": null,
|
| 338 |
+
"metadata": {},
|
| 339 |
+
"outputs": [],
|
| 340 |
+
"source": [
|
| 341 |
+
"import matplotlib\n",
|
| 342 |
+
"matplotlib.use('Agg')\n",
|
| 343 |
+
"import matplotlib.pyplot as plt\n",
|
| 344 |
+
"from matplotlib.ticker import MaxNLocator\n",
|
| 345 |
+
"\n",
|
| 346 |
+
"PLOT_DIR = OUTPUT_DIR / 'notebook_plots'\n",
|
| 347 |
+
"PLOT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 348 |
+
"\n",
|
| 349 |
+
"plt.rcParams.update({\n",
|
| 350 |
+
" 'font.family': 'sans-serif',\n",
|
| 351 |
+
" 'font.sans-serif': ['Arial', 'DejaVu Sans'],\n",
|
| 352 |
+
" 'font.size': 7,\n",
|
| 353 |
+
" 'axes.labelsize': 7,\n",
|
| 354 |
+
" 'axes.titlesize': 8,\n",
|
| 355 |
+
" 'xtick.labelsize': 6,\n",
|
| 356 |
+
" 'ytick.labelsize': 6,\n",
|
| 357 |
+
" 'legend.fontsize': 6,\n",
|
| 358 |
+
" 'axes.linewidth': 0.7,\n",
|
| 359 |
+
" 'pdf.fonttype': 42,\n",
|
| 360 |
+
" 'ps.fonttype': 42,\n",
|
| 361 |
+
" 'svg.fonttype': 'none',\n",
|
| 362 |
+
"})\n",
|
| 363 |
+
"\n",
|
| 364 |
+
"\n",
|
| 365 |
+
"def save_fig(fig, stem: str, dpi: int = 600, show: bool = True):\n",
|
| 366 |
+
" png_path = PLOT_DIR / f'{stem}.png'\n",
|
| 367 |
+
" pdf_path = PLOT_DIR / f'{stem}.pdf'\n",
|
| 368 |
+
" fig.savefig(png_path, dpi=dpi, bbox_inches='tight')\n",
|
| 369 |
+
" fig.savefig(pdf_path, bbox_inches='tight')\n",
|
| 370 |
+
" plt.close(fig)\n",
|
| 371 |
+
" print('Saved:', png_path)\n",
|
| 372 |
+
" print('Saved:', pdf_path)\n",
|
| 373 |
+
" if show and Image is not None:\n",
|
| 374 |
+
" display(Image(filename=str(png_path)))\n",
|
| 375 |
+
" return png_path, pdf_path\n",
|
| 376 |
+
"\n",
|
| 377 |
+
"\n",
|
| 378 |
+
"def despine(ax):\n",
|
| 379 |
+
" ax.spines['top'].set_visible(False)\n",
|
| 380 |
+
" ax.spines['right'].set_visible(False)\n",
|
| 381 |
+
" ax.tick_params(width=0.7, length=2.5)\n",
|
| 382 |
+
"\n",
|
| 383 |
+
"\n",
|
| 384 |
+
"def read_label_table(path: Path) -> pd.DataFrame:\n",
|
| 385 |
+
" df = pd.read_csv(path)\n",
|
| 386 |
+
" lower_map = {str(col).lower(): col for col in df.columns}\n",
|
| 387 |
+
" for required in ['drug1', 'drug2', 'cell', 'label']:\n",
|
| 388 |
+
" if required not in lower_map:\n",
|
| 389 |
+
" raise ValueError(f'{path} is missing a required column similar to {required!r}. Columns: {list(df.columns)}')\n",
|
| 390 |
+
" df = df.rename(columns={lower_map['drug1']: 'drug1', lower_map['drug2']: 'drug2', lower_map['cell']: 'cell', lower_map['label']: 'label'})\n",
|
| 391 |
+
" return df[['drug1', 'drug2', 'cell', 'label']].copy()\n",
|
| 392 |
+
"\n",
|
| 393 |
+
"\n",
|
| 394 |
+
"data_overview = []\n",
|
| 395 |
+
"train_labels = validation_labels = None\n",
|
| 396 |
+
"if expected_data_files['train_labels'].exists():\n",
|
| 397 |
+
" train_labels = read_label_table(expected_data_files['train_labels'])\n",
|
| 398 |
+
" data_overview.append({\n",
|
| 399 |
+
" 'split': 'train',\n",
|
| 400 |
+
" 'rows': len(train_labels),\n",
|
| 401 |
+
" 'positive': int((train_labels['label'] == 1).sum()),\n",
|
| 402 |
+
" 'negative': int((train_labels['label'] == 0).sum()),\n",
|
| 403 |
+
" 'unique_cells': train_labels['cell'].nunique(),\n",
|
| 404 |
+
" 'unique_drug1': train_labels['drug1'].nunique(),\n",
|
| 405 |
+
" 'unique_drug2': train_labels['drug2'].nunique(),\n",
|
| 406 |
+
" 'unique_pairs': train_labels[['drug1', 'drug2']].drop_duplicates().shape[0],\n",
|
| 407 |
+
" })\n",
|
| 408 |
+
"if expected_data_files['validation_labels'].exists():\n",
|
| 409 |
+
" validation_labels = read_label_table(expected_data_files['validation_labels'])\n",
|
| 410 |
+
" data_overview.append({\n",
|
| 411 |
+
" 'split': 'validation',\n",
|
| 412 |
+
" 'rows': len(validation_labels),\n",
|
| 413 |
+
" 'positive': int((validation_labels['label'] == 1).sum()),\n",
|
| 414 |
+
" 'negative': int((validation_labels['label'] == 0).sum()),\n",
|
| 415 |
+
" 'unique_cells': validation_labels['cell'].nunique(),\n",
|
| 416 |
+
" 'unique_drug1': validation_labels['drug1'].nunique(),\n",
|
| 417 |
+
" 'unique_drug2': validation_labels['drug2'].nunique(),\n",
|
| 418 |
+
" 'unique_pairs': validation_labels[['drug1', 'drug2']].drop_duplicates().shape[0],\n",
|
| 419 |
+
" })\n",
|
| 420 |
+
"\n",
|
| 421 |
+
"data_overview_df = pd.DataFrame(data_overview)\n",
|
| 422 |
+
"if data_overview_df.empty:\n",
|
| 423 |
+
" print('No label files found yet, so data overview plots were skipped.')\n",
|
| 424 |
+
"else:\n",
|
| 425 |
+
" data_overview_df.to_csv(PLOT_DIR / 'drug_combination_data_overview.csv', index=False)\n",
|
| 426 |
+
" display(data_overview_df)\n",
|
| 427 |
+
"\n",
|
| 428 |
+
" label_rows = []\n",
|
| 429 |
+
" if train_labels is not None:\n",
|
| 430 |
+
" label_rows.extend([{'split': 'train', 'label': int(k), 'count': int(v)} for k, v in train_labels['label'].value_counts().sort_index().items()])\n",
|
| 431 |
+
" if validation_labels is not None:\n",
|
| 432 |
+
" label_rows.extend([{'split': 'validation', 'label': int(k), 'count': int(v)} for k, v in validation_labels['label'].value_counts().sort_index().items()])\n",
|
| 433 |
+
" label_df = pd.DataFrame(label_rows)\n",
|
| 434 |
+
" label_df.to_csv(PLOT_DIR / 'drug_combination_label_counts.csv', index=False)\n",
|
| 435 |
+
"\n",
|
| 436 |
+
" fig, ax = plt.subplots(figsize=(3.5, 2.4))\n",
|
| 437 |
+
" splits = list(label_df['split'].drop_duplicates())\n",
|
| 438 |
+
" x = np.arange(len(splits))\n",
|
| 439 |
+
" width = 0.34\n",
|
| 440 |
+
" colors = {0: '#4E79A7', 1: '#E15759'}\n",
|
| 441 |
+
" for offset, label in zip([-width / 2, width / 2], [0, 1]):\n",
|
| 442 |
+
" values = [int(label_df[(label_df['split'] == split) & (label_df['label'] == label)]['count'].sum()) for split in splits]\n",
|
| 443 |
+
" ax.bar(x + offset, values, width=width, color=colors[label], edgecolor='#333333', linewidth=0.4, label=f'label {label}')\n",
|
| 444 |
+
" for xpos, value in zip(x + offset, values):\n",
|
| 445 |
+
" ax.text(xpos, value, str(value), ha='center', va='bottom', fontsize=6)\n",
|
| 446 |
+
" ax.set_xticks(x)\n",
|
| 447 |
+
" ax.set_xticklabels(splits, rotation=15, ha='right')\n",
|
| 448 |
+
" ax.set_ylabel('Number of combinations')\n",
|
| 449 |
+
" ax.set_title('Drug-combination label balance')\n",
|
| 450 |
+
" ax.legend(frameon=False)\n",
|
| 451 |
+
" ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n",
|
| 452 |
+
" despine(ax)\n",
|
| 453 |
+
" fig.tight_layout()\n",
|
| 454 |
+
" save_fig(fig, 'drug_combination_label_balance')\n",
|
| 455 |
+
"\n",
|
| 456 |
+
" fig, ax = plt.subplots(figsize=(4.1, 2.5))\n",
|
| 457 |
+
" overview_long = data_overview_df.melt(id_vars='split', value_vars=['unique_cells', 'unique_drug1', 'unique_drug2', 'unique_pairs'], var_name='feature', value_name='count')\n",
|
| 458 |
+
" features = ['unique_cells', 'unique_drug1', 'unique_drug2', 'unique_pairs']\n",
|
| 459 |
+
" x = np.arange(len(features))\n",
|
| 460 |
+
" width = 0.36 if len(splits) > 1 else 0.5\n",
|
| 461 |
+
" palette = ['#59A14F', '#F28E2B']\n",
|
| 462 |
+
" for i, split in enumerate(splits):\n",
|
| 463 |
+
" values = [int(overview_long[(overview_long['split'] == split) & (overview_long['feature'] == feature)]['count'].sum()) for feature in features]\n",
|
| 464 |
+
" ax.bar(x + (i - (len(splits) - 1) / 2) * width, values, width=width, color=palette[i % len(palette)], edgecolor='#333333', linewidth=0.4, label=split)\n",
|
| 465 |
+
" ax.set_xticks(x)\n",
|
| 466 |
+
" ax.set_xticklabels(['cells', 'drug1', 'drug2', 'drug pairs'], rotation=20, ha='right')\n",
|
| 467 |
+
" ax.set_ylabel('Unique count')\n",
|
| 468 |
+
" ax.set_title('Dataset coverage')\n",
|
| 469 |
+
" ax.legend(frameon=False)\n",
|
| 470 |
+
" ax.yaxis.set_major_locator(MaxNLocator(integer=True))\n",
|
| 471 |
+
" despine(ax)\n",
|
| 472 |
+
" fig.tight_layout()\n",
|
| 473 |
+
" save_fig(fig, 'drug_combination_dataset_coverage')\n"
|
| 474 |
+
]
|
| 475 |
+
},
|
| 476 |
+
{
|
| 477 |
+
"cell_type": "markdown",
|
| 478 |
+
"metadata": {},
|
| 479 |
+
"source": [
|
| 480 |
+
"## Result plots from training outputs\n",
|
| 481 |
+
"\n",
|
| 482 |
+
"Plot the saved training loss, validation metric history, and ROC curves. The\n",
|
| 483 |
+
"current script does not export per-example prediction CSV files, so this section\n",
|
| 484 |
+
"does not assume that precision-recall or confusion-matrix inputs are available.\n",
|
| 485 |
+
"Only open ROC pickle files produced by your own trusted training run.\n"
|
| 486 |
+
]
|
| 487 |
+
},
|
| 488 |
+
{
|
| 489 |
+
"cell_type": "code",
|
| 490 |
+
"execution_count": null,
|
| 491 |
+
"metadata": {},
|
| 492 |
+
"outputs": [],
|
| 493 |
+
"source": [
|
| 494 |
+
"import pickle\n",
|
| 495 |
+
"from sklearn import metrics\n",
|
| 496 |
+
"\n",
|
| 497 |
+
"\n",
|
| 498 |
+
"def find_fold_files(pattern: str):\n",
|
| 499 |
+
" return sorted(OUTPUT_DIR.glob(pattern))\n",
|
| 500 |
+
"\n",
|
| 501 |
+
"\n",
|
| 502 |
+
"def load_metrics_files():\n",
|
| 503 |
+
" frames = []\n",
|
| 504 |
+
" for path in find_fold_files('metrics_fold*.csv'):\n",
|
| 505 |
+
" df = pd.read_csv(path)\n",
|
| 506 |
+
" stem = path.stem.replace('metrics_fold', '')\n",
|
| 507 |
+
" try:\n",
|
| 508 |
+
" fold = int(stem)\n",
|
| 509 |
+
" except ValueError:\n",
|
| 510 |
+
" fold = len(frames)\n",
|
| 511 |
+
" df['fold'] = fold\n",
|
| 512 |
+
" df['source_file'] = str(path)\n",
|
| 513 |
+
" frames.append(df)\n",
|
| 514 |
+
" return pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()\n",
|
| 515 |
+
"\n",
|
| 516 |
+
"\n",
|
| 517 |
+
"def load_loss_files():\n",
|
| 518 |
+
" frames = []\n",
|
| 519 |
+
" for path in find_fold_files('loss_fold*.csv'):\n",
|
| 520 |
+
" df = pd.read_csv(path)\n",
|
| 521 |
+
" stem = path.stem.replace('loss_fold', '')\n",
|
| 522 |
+
" try:\n",
|
| 523 |
+
" fold = int(stem)\n",
|
| 524 |
+
" except ValueError:\n",
|
| 525 |
+
" fold = len(frames)\n",
|
| 526 |
+
" df['fold'] = fold\n",
|
| 527 |
+
" frames.append(df)\n",
|
| 528 |
+
" return pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()\n",
|
| 529 |
+
"\n",
|
| 530 |
+
"\n",
|
| 531 |
+
"metrics_df = load_metrics_files()\n",
|
| 532 |
+
"loss_df = load_loss_files()\n",
|
| 533 |
+
"\n",
|
| 534 |
+
"if metrics_df.empty:\n",
|
| 535 |
+
" print('No metrics_fold*.csv files found yet:', OUTPUT_DIR)\n",
|
| 536 |
+
"else:\n",
|
| 537 |
+
" metric_cols = [col for col in ['AUC', 'PR_AUC', 'ACC', 'BACC', 'PREC', 'TPR', 'KAPPA', 'RECALL'] if col in metrics_df.columns]\n",
|
| 538 |
+
" best_rows = metrics_df.sort_values(['fold', 'AUC'], ascending=[True, False]).groupby('fold', as_index=False).head(1)\n",
|
| 539 |
+
" summary_rows = []\n",
|
| 540 |
+
" for metric in metric_cols:\n",
|
| 541 |
+
" values = best_rows[metric].astype(float).to_numpy()\n",
|
| 542 |
+
" summary_rows.append({'metric': metric, 'mean': float(np.mean(values)), 'std': float(np.std(values)), 'n_folds': int(len(values))})\n",
|
| 543 |
+
" summary_df = pd.DataFrame(summary_rows)\n",
|
| 544 |
+
" best_rows.to_csv(PLOT_DIR / 'drug_combination_best_metrics_by_fold.csv', index=False)\n",
|
| 545 |
+
" summary_df.to_csv(PLOT_DIR / 'drug_combination_metric_summary.csv', index=False)\n",
|
| 546 |
+
" display(best_rows)\n",
|
| 547 |
+
" display(summary_df)\n",
|
| 548 |
+
"\n",
|
| 549 |
+
" fig, ax = plt.subplots(figsize=(4.1, 2.6))\n",
|
| 550 |
+
" for metric, color in [('AUC', '#4E79A7'), ('PR_AUC', '#E15759'), ('ACC', '#59A14F'), ('BACC', '#F28E2B')]:\n",
|
| 551 |
+
" if metric in metrics_df.columns:\n",
|
| 552 |
+
" for fold, frame in metrics_df.groupby('fold'):\n",
|
| 553 |
+
" ax.plot(frame['epoch'], frame[metric], linewidth=0.8, alpha=0.65, color=color)\n",
|
| 554 |
+
" mean_by_epoch = metrics_df.groupby('epoch')[metric].mean().reset_index()\n",
|
| 555 |
+
" ax.plot(mean_by_epoch['epoch'], mean_by_epoch[metric], linewidth=1.8, color=color, label=metric)\n",
|
| 556 |
+
" ax.set_xlabel('Epoch')\n",
|
| 557 |
+
" ax.set_ylabel('Score')\n",
|
| 558 |
+
" ax.set_ylim(0, 1.02)\n",
|
| 559 |
+
" ax.set_title('Validation metrics over training')\n",
|
| 560 |
+
" ax.legend(frameon=False, ncol=2)\n",
|
| 561 |
+
" ax.grid(axis='y', linestyle='--', linewidth=0.4, alpha=0.3)\n",
|
| 562 |
+
" ax.set_axisbelow(True)\n",
|
| 563 |
+
" despine(ax)\n",
|
| 564 |
+
" fig.tight_layout()\n",
|
| 565 |
+
" save_fig(fig, 'drug_combination_metric_curves')\n",
|
| 566 |
+
"\n",
|
| 567 |
+
" fig, ax = plt.subplots(figsize=(3.8, 2.5))\n",
|
| 568 |
+
" plot_metrics = [m for m in ['AUC', 'PR_AUC', 'ACC', 'BACC', 'PREC', 'TPR', 'KAPPA', 'RECALL'] if m in best_rows.columns]\n",
|
| 569 |
+
" values = [float(best_rows[m].mean()) for m in plot_metrics]\n",
|
| 570 |
+
" colors = ['#4E79A7', '#E15759', '#59A14F', '#F28E2B', '#76B7B2', '#B07AA1', '#9C755F', '#BAB0AC'][:len(plot_metrics)]\n",
|
| 571 |
+
" bars = ax.bar(np.arange(len(plot_metrics)), values, color=colors, edgecolor='#333333', linewidth=0.4)\n",
|
| 572 |
+
" for bar, value in zip(bars, values):\n",
|
| 573 |
+
" ax.text(bar.get_x() + bar.get_width() / 2, value + 0.015, f'{value:.3f}', ha='center', va='bottom', fontsize=6)\n",
|
| 574 |
+
" ax.set_xticks(np.arange(len(plot_metrics)))\n",
|
| 575 |
+
" ax.set_xticklabels(plot_metrics, rotation=25, ha='right')\n",
|
| 576 |
+
" ax.set_ylim(0, 1.05)\n",
|
| 577 |
+
" ax.set_ylabel('Best-fold score')\n",
|
| 578 |
+
" ax.set_title('Drug-combination prediction summary')\n",
|
| 579 |
+
" ax.grid(axis='y', linestyle='--', linewidth=0.4, alpha=0.3)\n",
|
| 580 |
+
" ax.set_axisbelow(True)\n",
|
| 581 |
+
" despine(ax)\n",
|
| 582 |
+
" fig.tight_layout()\n",
|
| 583 |
+
" save_fig(fig, 'drug_combination_best_metric_summary')\n",
|
| 584 |
+
"\n",
|
| 585 |
+
"if loss_df.empty:\n",
|
| 586 |
+
" print('No loss_fold*.csv files found yet:', OUTPUT_DIR)\n",
|
| 587 |
+
"else:\n",
|
| 588 |
+
" fig, ax = plt.subplots(figsize=(4.1, 2.5))\n",
|
| 589 |
+
" for fold, frame in loss_df.groupby('fold'):\n",
|
| 590 |
+
" ax.plot(frame['epoch'], frame['avg_loss'], linewidth=0.8, alpha=0.65, label=f'fold {fold}')\n",
|
| 591 |
+
" mean_loss = loss_df.groupby('epoch')['avg_loss'].mean().reset_index()\n",
|
| 592 |
+
" ax.plot(mean_loss['epoch'], mean_loss['avg_loss'], linewidth=1.8, color='#222222', label='mean')\n",
|
| 593 |
+
" ax.set_xlabel('Epoch')\n",
|
| 594 |
+
" ax.set_ylabel('Average loss')\n",
|
| 595 |
+
" ax.set_title('Training loss')\n",
|
| 596 |
+
" ax.legend(frameon=False, ncol=2)\n",
|
| 597 |
+
" ax.grid(axis='y', linestyle='--', linewidth=0.4, alpha=0.3)\n",
|
| 598 |
+
" ax.set_axisbelow(True)\n",
|
| 599 |
+
" despine(ax)\n",
|
| 600 |
+
" fig.tight_layout()\n",
|
| 601 |
+
" save_fig(fig, 'drug_combination_loss_curves')\n",
|
| 602 |
+
"\n",
|
| 603 |
+
"roc_files = find_fold_files('fpr_tpr_fold*.pkl')\n",
|
| 604 |
+
"if not roc_files:\n",
|
| 605 |
+
" print('No fpr_tpr_fold*.pkl files found yet:', OUTPUT_DIR)\n",
|
| 606 |
+
"else:\n",
|
| 607 |
+
" fig, ax = plt.subplots(figsize=(3.2, 3.2))\n",
|
| 608 |
+
" roc_rows = []\n",
|
| 609 |
+
" for path in roc_files:\n",
|
| 610 |
+
" with path.open('rb') as handle:\n",
|
| 611 |
+
" fpr, tpr = pickle.load(handle)\n",
|
| 612 |
+
" stem = path.stem.replace('fpr_tpr_fold', '')\n",
|
| 613 |
+
" try:\n",
|
| 614 |
+
" fold = int(stem)\n",
|
| 615 |
+
" except ValueError:\n",
|
| 616 |
+
" fold = len(roc_rows)\n",
|
| 617 |
+
" auc_value = float(metrics.auc(fpr, tpr))\n",
|
| 618 |
+
" roc_rows.append({'fold': fold, 'auc': auc_value})\n",
|
| 619 |
+
" ax.plot(fpr, tpr, linewidth=1.0, label=f'fold {fold}, AUROC={auc_value:.3f}')\n",
|
| 620 |
+
" ax.plot([0, 1], [0, 1], linestyle='--', color='#333333', linewidth=0.8, alpha=0.7)\n",
|
| 621 |
+
" ax.set_xlabel('False positive rate')\n",
|
| 622 |
+
" ax.set_ylabel('True positive rate')\n",
|
| 623 |
+
" ax.set_xlim(0, 1)\n",
|
| 624 |
+
" ax.set_ylim(0, 1.02)\n",
|
| 625 |
+
" ax.set_aspect('equal', adjustable='box')\n",
|
| 626 |
+
" ax.legend(frameon=False, loc='lower right')\n",
|
| 627 |
+
" ax.set_title('ROC curve')\n",
|
| 628 |
+
" despine(ax)\n",
|
| 629 |
+
" fig.tight_layout()\n",
|
| 630 |
+
" save_fig(fig, 'drug_combination_roc_curves')\n",
|
| 631 |
+
" pd.DataFrame(roc_rows).to_csv(PLOT_DIR / 'drug_combination_roc_auc_from_curves.csv', index=False)\n"
|
| 632 |
+
]
|
| 633 |
+
},
|
| 634 |
+
{
|
| 635 |
+
"cell_type": "markdown",
|
| 636 |
+
"metadata": {},
|
| 637 |
+
"source": [
|
| 638 |
+
"## Generated plot files\n",
|
| 639 |
+
"\n",
|
| 640 |
+
"This cell lists the figures and source tables generated by the notebook plotting sections.\n"
|
| 641 |
+
]
|
| 642 |
+
},
|
| 643 |
+
{
|
| 644 |
+
"cell_type": "code",
|
| 645 |
+
"execution_count": null,
|
| 646 |
+
"metadata": {},
|
| 647 |
+
"outputs": [],
|
| 648 |
+
"source": [
|
| 649 |
+
"plot_files = sorted(PLOT_DIR.glob('*'))\n",
|
| 650 |
+
"if plot_files:\n",
|
| 651 |
+
" display(pd.DataFrame([\n",
|
| 652 |
+
" {'file': path.name, 'size_kb': round(path.stat().st_size / 1024, 2), 'path': str(path)}\n",
|
| 653 |
+
" for path in plot_files\n",
|
| 654 |
+
" ]))\n",
|
| 655 |
+
"else:\n",
|
| 656 |
+
" print('No notebook plot files were generated yet.')\n"
|
| 657 |
+
]
|
| 658 |
+
}
|
| 659 |
+
],
|
| 660 |
+
"metadata": {
|
| 661 |
+
"kernelspec": {
|
| 662 |
+
"display_name": "Python 3 (sclong)",
|
| 663 |
+
"language": "python",
|
| 664 |
+
"name": "python3"
|
| 665 |
+
},
|
| 666 |
+
"language_info": {
|
| 667 |
+
"name": "python",
|
| 668 |
+
"pygments_lexer": "ipython3"
|
| 669 |
+
}
|
| 670 |
+
},
|
| 671 |
+
"nbformat": 4,
|
| 672 |
+
"nbformat_minor": 5
|
| 673 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BioMod/toturials/drug_response_prediction.ipynb
CHANGED
|
@@ -1,906 +1,871 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# Drug response prediction
|
| 8 |
-
"\n",
|
| 9 |
-
"This notebook
|
| 10 |
-
"\n",
|
| 11 |
-
"
|
| 12 |
-
"\n",
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
"\n",
|
| 25 |
-
"
|
| 26 |
-
"\n",
|
| 27 |
-
"
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
"
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
"-
|
| 39 |
-
"-
|
| 40 |
-
"-
|
| 41 |
-
"-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"
|
| 62 |
-
"
|
| 63 |
-
"import
|
| 64 |
-
"
|
| 65 |
-
"\n",
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
"\n",
|
| 72 |
-
"
|
| 73 |
-
"
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"\n",
|
| 77 |
-
"
|
| 78 |
-
"
|
| 79 |
-
"
|
| 80 |
-
"
|
| 81 |
-
"\n",
|
| 82 |
-
"
|
| 83 |
-
"
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
-
"\n",
|
| 87 |
-
"
|
| 88 |
-
"
|
| 89 |
-
"
|
| 90 |
-
"
|
| 91 |
-
"
|
| 92 |
-
"
|
| 93 |
-
"
|
| 94 |
-
"
|
| 95 |
-
"
|
| 96 |
-
"
|
| 97 |
-
"
|
| 98 |
-
"
|
| 99 |
-
"
|
| 100 |
-
"
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
"
|
| 110 |
-
"
|
| 111 |
-
"
|
| 112 |
-
"
|
| 113 |
-
"
|
| 114 |
-
"
|
| 115 |
-
"
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
"\n",
|
| 125 |
-
"
|
| 126 |
-
"
|
| 127 |
-
"
|
| 128 |
-
"
|
| 129 |
-
"\n",
|
| 130 |
-
"
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
"
|
| 138 |
-
"\n",
|
| 139 |
-
"
|
| 140 |
-
]
|
| 141 |
-
},
|
| 142 |
-
{
|
| 143 |
-
"cell_type": "
|
| 144 |
-
"
|
| 145 |
-
"
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
"
|
| 149 |
-
"
|
| 150 |
-
"
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
"
|
| 160 |
-
"
|
| 161 |
-
"
|
| 162 |
-
"
|
| 163 |
-
"
|
| 164 |
-
"
|
| 165 |
-
"
|
| 166 |
-
"
|
| 167 |
-
"\n",
|
| 168 |
-
"
|
| 169 |
-
"
|
| 170 |
-
"
|
| 171 |
-
"
|
| 172 |
-
"
|
| 173 |
-
"
|
| 174 |
-
"
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
"
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"
|
| 197 |
-
"
|
| 198 |
-
"\n",
|
| 199 |
-
"
|
| 200 |
-
"
|
| 201 |
-
"
|
| 202 |
-
"
|
| 203 |
-
"
|
| 204 |
-
"
|
| 205 |
-
"
|
| 206 |
-
"\n",
|
| 207 |
-
"display(
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
"
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
"
|
| 225 |
-
"
|
| 226 |
-
"
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
"
|
| 230 |
-
"
|
| 231 |
-
"
|
| 232 |
-
"\n",
|
| 233 |
-
"
|
| 234 |
-
"
|
| 235 |
-
" '--
|
| 236 |
-
" '--
|
| 237 |
-
" '--
|
| 238 |
-
" '--
|
| 239 |
-
" '--
|
| 240 |
-
" '--
|
| 241 |
-
" '--
|
| 242 |
-
" '--
|
| 243 |
-
" '--
|
| 244 |
-
" '--
|
| 245 |
-
" '--
|
| 246 |
-
"
|
| 247 |
-
"
|
| 248 |
-
"
|
| 249 |
-
"\n"
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
"
|
| 256 |
-
"
|
| 257 |
-
"
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
"
|
| 261 |
-
"\n",
|
| 262 |
-
"
|
| 263 |
-
"
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"
|
| 267 |
-
"
|
| 268 |
-
"
|
| 269 |
-
"
|
| 270 |
-
"
|
| 271 |
-
"
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
"
|
| 275 |
-
"
|
| 276 |
-
"
|
| 277 |
-
"
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
"
|
| 281 |
-
"
|
| 282 |
-
"
|
| 283 |
-
"
|
| 284 |
-
"
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
"
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
"
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
"
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
"
|
| 307 |
-
"
|
| 308 |
-
"\n",
|
| 309 |
-
"if
|
| 310 |
-
"
|
| 311 |
-
"\n",
|
| 312 |
-
"
|
| 313 |
-
"
|
| 314 |
-
"
|
| 315 |
-
"
|
| 316 |
-
"
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
"
|
| 322 |
-
"
|
| 323 |
-
"
|
| 324 |
-
"
|
| 325 |
-
|
| 326 |
-
"
|
| 327 |
-
"
|
| 328 |
-
"
|
| 329 |
-
"\n",
|
| 330 |
-
"
|
| 331 |
-
"
|
| 332 |
-
"if
|
| 333 |
-
"
|
| 334 |
-
"
|
| 335 |
-
"
|
| 336 |
-
"
|
| 337 |
-
"
|
| 338 |
-
"\n",
|
| 339 |
-
"
|
| 340 |
-
"
|
| 341 |
-
"
|
| 342 |
-
"
|
| 343 |
-
"
|
| 344 |
-
"
|
| 345 |
-
"
|
| 346 |
-
"
|
| 347 |
-
"
|
| 348 |
-
"
|
| 349 |
-
"\n",
|
| 350 |
-
"meta
|
| 351 |
-
"
|
| 352 |
-
"
|
| 353 |
-
"
|
| 354 |
-
" '
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
"
|
| 362 |
-
"
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
"
|
| 367 |
-
"
|
| 368 |
-
"\n",
|
| 369 |
-
"
|
| 370 |
-
"
|
| 371 |
-
"
|
| 372 |
-
"
|
| 373 |
-
"
|
| 374 |
-
"\n",
|
| 375 |
-
"\n",
|
| 376 |
-
"
|
| 377 |
-
"
|
| 378 |
-
"
|
| 379 |
-
"
|
| 380 |
-
"
|
| 381 |
-
"
|
| 382 |
-
"
|
| 383 |
-
"
|
| 384 |
-
"
|
| 385 |
-
"\n",
|
| 386 |
-
"\n",
|
| 387 |
-
"
|
| 388 |
-
"
|
| 389 |
-
"
|
| 390 |
-
"
|
| 391 |
-
"
|
| 392 |
-
"
|
| 393 |
-
"
|
| 394 |
-
"
|
| 395 |
-
"
|
| 396 |
-
"
|
| 397 |
-
"
|
| 398 |
-
"\n",
|
| 399 |
-
"\n",
|
| 400 |
-
"
|
| 401 |
-
"
|
| 402 |
-
"
|
| 403 |
-
"
|
| 404 |
-
"
|
| 405 |
-
"
|
| 406 |
-
"
|
| 407 |
-
"\n",
|
| 408 |
-
"
|
| 409 |
-
"
|
| 410 |
-
"
|
| 411 |
-
"\n",
|
| 412 |
-
"
|
| 413 |
-
"
|
| 414 |
-
"
|
| 415 |
-
"
|
| 416 |
-
"
|
| 417 |
-
"
|
| 418 |
-
"\n",
|
| 419 |
-
"
|
| 420 |
-
"
|
| 421 |
-
"
|
| 422 |
-
"
|
| 423 |
-
"
|
| 424 |
-
"
|
| 425 |
-
"\n",
|
| 426 |
-
"
|
| 427 |
-
"
|
| 428 |
-
"\n",
|
| 429 |
-
"
|
| 430 |
-
"
|
| 431 |
-
"
|
| 432 |
-
"\n",
|
| 433 |
-
"
|
| 434 |
-
"
|
| 435 |
-
"
|
| 436 |
-
"
|
| 437 |
-
"
|
| 438 |
-
"
|
| 439 |
-
"
|
| 440 |
-
"
|
| 441 |
-
"
|
| 442 |
-
"
|
| 443 |
-
"
|
| 444 |
-
"
|
| 445 |
-
"\n",
|
| 446 |
-
"
|
| 447 |
-
"
|
| 448 |
-
"
|
| 449 |
-
"
|
| 450 |
-
"
|
| 451 |
-
"
|
| 452 |
-
"
|
| 453 |
-
"
|
| 454 |
-
"
|
| 455 |
-
"\n",
|
| 456 |
-
"
|
| 457 |
-
"
|
| 458 |
-
"
|
| 459 |
-
"
|
| 460 |
-
"\n",
|
| 461 |
-
"\n",
|
| 462 |
-
"analysis_df =
|
| 463 |
-
"
|
| 464 |
-
"
|
| 465 |
-
"
|
| 466 |
-
"\n",
|
| 467 |
-
"
|
| 468 |
-
"
|
| 469 |
-
"
|
| 470 |
-
"
|
| 471 |
-
"\n",
|
| 472 |
-
"if
|
| 473 |
-
"
|
| 474 |
-
"
|
| 475 |
-
"
|
| 476 |
-
"
|
| 477 |
-
"
|
| 478 |
-
"
|
| 479 |
-
"
|
| 480 |
-
"
|
| 481 |
-
"
|
| 482 |
-
"
|
| 483 |
-
"
|
| 484 |
-
"
|
| 485 |
-
"\n",
|
| 486 |
-
"
|
| 487 |
-
"
|
| 488 |
-
"display(
|
| 489 |
-
"\n",
|
| 490 |
-
"
|
| 491 |
-
"
|
| 492 |
-
"
|
| 493 |
-
"
|
| 494 |
-
"
|
| 495 |
-
"
|
| 496 |
-
")\n",
|
| 497 |
-
"
|
| 498 |
-
"
|
| 499 |
-
"
|
| 500 |
-
"
|
| 501 |
-
"
|
| 502 |
-
")\n",
|
| 503 |
-
"\n",
|
| 504 |
-
"
|
| 505 |
-
"display(
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
"
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
"
|
| 528 |
-
"
|
| 529 |
-
"
|
| 530 |
-
"
|
| 531 |
-
"
|
| 532 |
-
"\n",
|
| 533 |
-
"
|
| 534 |
-
"
|
| 535 |
-
"\n",
|
| 536 |
-
"
|
| 537 |
-
"
|
| 538 |
-
"
|
| 539 |
-
"
|
| 540 |
-
"\n",
|
| 541 |
-
"
|
| 542 |
-
"
|
| 543 |
-
"
|
| 544 |
-
"
|
| 545 |
-
"
|
| 546 |
-
"
|
| 547 |
-
"ax.
|
| 548 |
-
"ax.
|
| 549 |
-
"ax.
|
| 550 |
-
"ax.
|
| 551 |
-
"ax.
|
| 552 |
-
"
|
| 553 |
-
"
|
| 554 |
-
"
|
| 555 |
-
"
|
| 556 |
-
"\n",
|
| 557 |
-
"
|
| 558 |
-
"
|
| 559 |
-
"
|
| 560 |
-
"
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
"
|
| 568 |
-
"\n"
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
"
|
| 574 |
-
"
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
"
|
| 579 |
-
"\n",
|
| 580 |
-
"
|
| 581 |
-
"
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
"
|
| 591 |
-
"
|
| 592 |
-
"
|
| 593 |
-
"
|
| 594 |
-
"
|
| 595 |
-
"
|
| 596 |
-
"
|
| 597 |
-
"
|
| 598 |
-
"
|
| 599 |
-
"
|
| 600 |
-
" '
|
| 601 |
-
"
|
| 602 |
-
"
|
| 603 |
-
"
|
| 604 |
-
"\n",
|
| 605 |
-
"\n",
|
| 606 |
-
"
|
| 607 |
-
"
|
| 608 |
-
"
|
| 609 |
-
"
|
| 610 |
-
"
|
| 611 |
-
"
|
| 612 |
-
"
|
| 613 |
-
"
|
| 614 |
-
"
|
| 615 |
-
"\n",
|
| 616 |
-
"\n",
|
| 617 |
-
"
|
| 618 |
-
"
|
| 619 |
-
"
|
| 620 |
-
"
|
| 621 |
-
"\n",
|
| 622 |
-
"\n",
|
| 623 |
-
"
|
| 624 |
-
"
|
| 625 |
-
"
|
| 626 |
-
"
|
| 627 |
-
"
|
| 628 |
-
"\n",
|
| 629 |
-
"\n",
|
| 630 |
-
"
|
| 631 |
-
"
|
| 632 |
-
"
|
| 633 |
-
"
|
| 634 |
-
"
|
| 635 |
-
"
|
| 636 |
-
"
|
| 637 |
-
"\n",
|
| 638 |
-
"
|
| 639 |
-
"
|
| 640 |
-
"
|
| 641 |
-
"
|
| 642 |
-
"
|
| 643 |
-
"
|
| 644 |
-
"
|
| 645 |
-
"
|
| 646 |
-
"
|
| 647 |
-
" 0.
|
| 648 |
-
"
|
| 649 |
-
"
|
| 650 |
-
"
|
| 651 |
-
"
|
| 652 |
-
")\n",
|
| 653 |
-
"
|
| 654 |
-
"
|
| 655 |
-
"
|
| 656 |
-
"\n",
|
| 657 |
-
"
|
| 658 |
-
"
|
| 659 |
-
"
|
| 660 |
-
"
|
| 661 |
-
"
|
| 662 |
-
"ax.
|
| 663 |
-
"
|
| 664 |
-
"
|
| 665 |
-
"
|
| 666 |
-
"
|
| 667 |
-
"
|
| 668 |
-
"\n",
|
| 669 |
-
"
|
| 670 |
-
"
|
| 671 |
-
"
|
| 672 |
-
"
|
| 673 |
-
"
|
| 674 |
-
"
|
| 675 |
-
"
|
| 676 |
-
"
|
| 677 |
-
"
|
| 678 |
-
"\n",
|
| 679 |
-
" fig
|
| 680 |
-
"
|
| 681 |
-
"
|
| 682 |
-
"
|
| 683 |
-
"
|
| 684 |
-
"
|
| 685 |
-
"
|
| 686 |
-
"
|
| 687 |
-
" )\n",
|
| 688 |
-
"
|
| 689 |
-
"
|
| 690 |
-
"
|
| 691 |
-
"
|
| 692 |
-
"
|
| 693 |
-
"
|
| 694 |
-
"
|
| 695 |
-
"
|
| 696 |
-
"
|
| 697 |
-
"
|
| 698 |
-
"
|
| 699 |
-
"
|
| 700 |
-
"
|
| 701 |
-
"
|
| 702 |
-
"
|
| 703 |
-
"
|
| 704 |
-
"
|
| 705 |
-
"
|
| 706 |
-
"\n",
|
| 707 |
-
"
|
| 708 |
-
"
|
| 709 |
-
"
|
| 710 |
-
"
|
| 711 |
-
"
|
| 712 |
-
"
|
| 713 |
-
"
|
| 714 |
-
"
|
| 715 |
-
"
|
| 716 |
-
"
|
| 717 |
-
"
|
| 718 |
-
"
|
| 719 |
-
"
|
| 720 |
-
"
|
| 721 |
-
"
|
| 722 |
-
"
|
| 723 |
-
"
|
| 724 |
-
"
|
| 725 |
-
"
|
| 726 |
-
"
|
| 727 |
-
"
|
| 728 |
-
"
|
| 729 |
-
"\n",
|
| 730 |
-
"
|
| 731 |
-
"
|
| 732 |
-
"
|
| 733 |
-
"
|
| 734 |
-
"
|
| 735 |
-
"
|
| 736 |
-
"
|
| 737 |
-
"
|
| 738 |
-
"
|
| 739 |
-
"
|
| 740 |
-
"\n",
|
| 741 |
-
"
|
| 742 |
-
"
|
| 743 |
-
"
|
| 744 |
-
"
|
| 745 |
-
"
|
| 746 |
-
"
|
| 747 |
-
"
|
| 748 |
-
"
|
| 749 |
-
"
|
| 750 |
-
"
|
| 751 |
-
"
|
| 752 |
-
"
|
| 753 |
-
"
|
| 754 |
-
"
|
| 755 |
-
"
|
| 756 |
-
"
|
| 757 |
-
"
|
| 758 |
-
"
|
| 759 |
-
"
|
| 760 |
-
"
|
| 761 |
-
"
|
| 762 |
-
"
|
| 763 |
-
"
|
| 764 |
-
"
|
| 765 |
-
"
|
| 766 |
-
"
|
| 767 |
-
"
|
| 768 |
-
"
|
| 769 |
-
"
|
| 770 |
-
"
|
| 771 |
-
"
|
| 772 |
-
"
|
| 773 |
-
"
|
| 774 |
-
"
|
| 775 |
-
"\n",
|
| 776 |
-
"
|
| 777 |
-
"
|
| 778 |
-
"
|
| 779 |
-
"
|
| 780 |
-
"
|
| 781 |
-
"
|
| 782 |
-
"
|
| 783 |
-
"
|
| 784 |
-
"
|
| 785 |
-
"
|
| 786 |
-
"
|
| 787 |
-
"
|
| 788 |
-
"
|
| 789 |
-
"
|
| 790 |
-
"
|
| 791 |
-
"
|
| 792 |
-
"
|
| 793 |
-
"
|
| 794 |
-
"
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
"
|
| 802 |
-
"
|
| 803 |
-
"
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
"
|
| 813 |
-
"
|
| 814 |
-
"
|
| 815 |
-
"
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
"
|
| 840 |
-
"
|
| 841 |
-
"
|
| 842 |
-
"
|
| 843 |
-
"
|
| 844 |
-
"]\n",
|
| 845 |
-
"\n",
|
| 846 |
-
"
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
"
|
| 858 |
-
|
| 859 |
-
"
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
"
|
| 866 |
-
"
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
" 'plot_cancer_drug_response_nature_color.py',\n",
|
| 873 |
-
" 'plot_drug_response_panel_d.py',\n",
|
| 874 |
-
" 'plot_drug_response_panel_e.py',\n",
|
| 875 |
-
" 'plot_drug_response_panel_f.py',\n",
|
| 876 |
-
" 'plot_drug_response_roc_rewrite.py',\n",
|
| 877 |
-
"]\n",
|
| 878 |
-
"display(pd.DataFrame([\n",
|
| 879 |
-
" {'script': name, 'path': DRAW_DIR / name, 'exists': (DRAW_DIR / name).exists()}\n",
|
| 880 |
-
" for name in draw_scripts\n",
|
| 881 |
-
"]))\n"
|
| 882 |
-
]
|
| 883 |
-
}
|
| 884 |
-
],
|
| 885 |
-
"metadata": {
|
| 886 |
-
"kernelspec": {
|
| 887 |
-
"display_name": "Python 3",
|
| 888 |
-
"language": "python",
|
| 889 |
-
"name": "python3"
|
| 890 |
-
},
|
| 891 |
-
"language_info": {
|
| 892 |
-
"codemirror_mode": {
|
| 893 |
-
"name": "ipython",
|
| 894 |
-
"version": 3
|
| 895 |
-
},
|
| 896 |
-
"file_extension": ".py",
|
| 897 |
-
"mimetype": "text/x-python",
|
| 898 |
-
"name": "python",
|
| 899 |
-
"nbconvert_exporter": "python",
|
| 900 |
-
"pygments_lexer": "ipython3",
|
| 901 |
-
"version": "3.9"
|
| 902 |
-
}
|
| 903 |
-
},
|
| 904 |
-
"nbformat": 4,
|
| 905 |
-
"nbformat_minor": 5
|
| 906 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Drug response prediction with BioMod\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook uses the BioMod model weights, drug graph features, and the bundled\n",
|
| 10 |
+
"GDSC/CCLE response data with `train_drug_performer_module.py`.\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"By default, `RUN_TRAINING = False`: the notebook checks inputs and prepares the\n",
|
| 13 |
+
"training command. Result and plotting cells run only when `predictions.npz`\n",
|
| 14 |
+
"exists in the configured output directory. Set `RUN_TRAINING = True` to train.\n",
|
| 15 |
+
"\n",
|
| 16 |
+
"The validation set selects the checkpoint. Reported metrics describe validation\n",
|
| 17 |
+
"performance; this workflow does not produce a separate final test evaluation.\n"
|
| 18 |
+
]
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"cell_type": "markdown",
|
| 22 |
+
"metadata": {},
|
| 23 |
+
"source": [
|
| 24 |
+
"## Project paths\n",
|
| 25 |
+
"\n",
|
| 26 |
+
"Start Jupyter from `BioMod/toturials/`. The next cell uses `Path('../..')` for the\n",
|
| 27 |
+
"repository root and resolves project-relative paths for the training subprocess.\n",
|
| 28 |
+
"If Jupyter starts at the repository root, use `Path('.')` instead. All inputs come\n",
|
| 29 |
+
"from this project; no private server alias or external reference run is required.\n"
|
| 30 |
+
]
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"cell_type": "markdown",
|
| 34 |
+
"metadata": {},
|
| 35 |
+
"source": [
|
| 36 |
+
"## Inputs and outputs\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"- Training script: `BioMod/drug_response/train_drug_performer_module.py`\n",
|
| 39 |
+
"- Dataset: `data/downstream/final_data/single_drug_response/data/`\n",
|
| 40 |
+
"- BioMod model weights and configuration: `weight/0810model/`\n",
|
| 41 |
+
"- Shared model assets: `model_dependencies/`\n",
|
| 42 |
+
"- Tutorial output: `tutorial_runs/drug_response_prediction/`\n",
|
| 43 |
+
"\n",
|
| 44 |
+
"These paths are relative to the repository in this overview. The command cell\n",
|
| 45 |
+
"prints their resolved absolute paths. The drug graph files are already extracted\n",
|
| 46 |
+
"under `GDSC/drug_graph_feat/`; no ZIP archive or additional omics matrices are\n",
|
| 47 |
+
"required by this training script.\n"
|
| 48 |
+
]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"cell_type": "code",
|
| 52 |
+
"execution_count": null,
|
| 53 |
+
"metadata": {},
|
| 54 |
+
"outputs": [],
|
| 55 |
+
"source": [
|
| 56 |
+
"from pathlib import Path\n",
|
| 57 |
+
"import importlib.util\n",
|
| 58 |
+
"import os\n",
|
| 59 |
+
"import shlex\n",
|
| 60 |
+
"import subprocess\n",
|
| 61 |
+
"import sys\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"import numpy as np\n",
|
| 64 |
+
"import pandas as pd\n",
|
| 65 |
+
"\n",
|
| 66 |
+
"try:\n",
|
| 67 |
+
" from IPython.display import Image, display\n",
|
| 68 |
+
"except ImportError:\n",
|
| 69 |
+
" Image = None\n",
|
| 70 |
+
" def display(obj):\n",
|
| 71 |
+
" print(obj)\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"from scipy.stats import pearsonr\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"# Start Jupyter with BioMod/toturials as the working directory.\n",
|
| 76 |
+
"# If Jupyter starts at the repository root, change this line to Path('.').resolve().\n",
|
| 77 |
+
"PROJECT_ROOT = Path('../..').resolve()\n",
|
| 78 |
+
"SOURCE_DIR = PROJECT_ROOT / 'BioMod'\n",
|
| 79 |
+
"if not (SOURCE_DIR / 'models_performer_module.py').is_file() or not (PROJECT_ROOT / 'model_dependencies').is_dir():\n",
|
| 80 |
+
" raise FileNotFoundError(\n",
|
| 81 |
+
" 'Start Jupyter from BioMod/toturials, or set PROJECT_ROOT to the repository root '\n",
|
| 82 |
+
" \"(use Path('.').resolve() if Jupyter was launched at that root).\"\n",
|
| 83 |
+
" )\n",
|
| 84 |
+
"REPO_ROOT = PROJECT_ROOT\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"DEPENDENCY_ROOT = REPO_ROOT / 'model_dependencies'\n",
|
| 87 |
+
"WEIGHT_DIR = REPO_ROOT / 'weight' / '0810model'\n",
|
| 88 |
+
"MODULE_DATA_DIR = DEPENDENCY_ROOT / 'module_data_all'\n",
|
| 89 |
+
"TRAIN_PYTHON = Path(sys.executable).resolve()\n",
|
| 90 |
+
"PYTHONPATH = os.pathsep.join(\n",
|
| 91 |
+
" [str(SOURCE_DIR), str(DEPENDENCY_ROOT)]\n",
|
| 92 |
+
" + ([os.environ['PYTHONPATH']] if os.environ.get('PYTHONPATH') else [])\n",
|
| 93 |
+
")\n",
|
| 94 |
+
"\n",
|
| 95 |
+
"TASK_ROOT = SOURCE_DIR / 'drug_response'\n",
|
| 96 |
+
"SCRIPT = TASK_ROOT / 'train_drug_performer_module.py'\n",
|
| 97 |
+
"DATA_ROOT = REPO_ROOT / 'data/downstream/final_data/single_drug_response/data'\n",
|
| 98 |
+
"CKPT_PATH = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2_epoch0.pth'\n",
|
| 99 |
+
"CONFIG_PKL = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2.pkl'\n",
|
| 100 |
+
"GENE2VEC = DEPENDENCY_ROOT / 'selected_gene2vec_27k.npy'\n",
|
| 101 |
+
"GENE_MAP = DEPENDENCY_ROOT / 'gene2ens.csv'\n",
|
| 102 |
+
"GENE_LIST = DEPENDENCY_ROOT / 'selected_genes_27k.txt'\n",
|
| 103 |
+
"RESULT_DIR = REPO_ROOT / 'tutorial_runs/drug_response_prediction'\n",
|
| 104 |
+
"\n",
|
| 105 |
+
"paths = pd.DataFrame([\n",
|
| 106 |
+
" {'name': name, 'path': str(path), 'exists': path.exists()}\n",
|
| 107 |
+
" for name, path in {\n",
|
| 108 |
+
" 'repository': REPO_ROOT, 'script': SCRIPT, 'data_root': DATA_ROOT,\n",
|
| 109 |
+
" 'checkpoint': CKPT_PATH, 'config_pkl': CONFIG_PKL,\n",
|
| 110 |
+
" 'module_data_dir': MODULE_DATA_DIR, 'gene_list': GENE_LIST,\n",
|
| 111 |
+
" 'gene_map': GENE_MAP, 'gene2vec': GENE2VEC,\n",
|
| 112 |
+
" 'tutorial_result_dir': RESULT_DIR,\n",
|
| 113 |
+
" }.items()\n",
|
| 114 |
+
"])\n",
|
| 115 |
+
"display(paths)\n"
|
| 116 |
+
]
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"cell_type": "code",
|
| 120 |
+
"execution_count": null,
|
| 121 |
+
"metadata": {},
|
| 122 |
+
"outputs": [],
|
| 123 |
+
"source": [
|
| 124 |
+
"required_files = [\n",
|
| 125 |
+
" SCRIPT, CKPT_PATH, CONFIG_PKL, GENE_LIST, GENE_MAP, GENE2VEC,\n",
|
| 126 |
+
" DATA_ROOT / 'CCLE/GDSC_IC50.csv',\n",
|
| 127 |
+
" DATA_ROOT / 'CCLE/Cell_lines_annotations_20181226.txt',\n",
|
| 128 |
+
" DATA_ROOT / 'CCLE/genomic_expression_561celllines_697genes_demap_features.csv',\n",
|
| 129 |
+
" DATA_ROOT / 'GDSC/1.Drug_listMon Jun 24 09_00_55 2019.csv',\n",
|
| 130 |
+
"]\n",
|
| 131 |
+
"required_dirs = [PROJECT_ROOT, TASK_ROOT, MODULE_DATA_DIR,\n",
|
| 132 |
+
" DEPENDENCY_ROOT / 'performer_pytorch_cont', DATA_ROOT / 'GDSC/drug_graph_feat']\n",
|
| 133 |
+
"missing = [str(path) for path in required_files if not path.is_file()]\n",
|
| 134 |
+
"missing += [str(path) for path in required_dirs if not path.is_dir()]\n",
|
| 135 |
+
"if missing:\n",
|
| 136 |
+
" raise FileNotFoundError('Missing required inputs:\\n' + '\\n'.join(missing))\n",
|
| 137 |
+
"if not any((DATA_ROOT / 'GDSC/drug_graph_feat').glob('*.hkl')):\n",
|
| 138 |
+
" raise FileNotFoundError('No .hkl drug graph files found under GDSC/drug_graph_feat.')\n",
|
| 139 |
+
"print('All required inputs are present. Weight/configuration files have not been loaded.')\n"
|
| 140 |
+
]
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"cell_type": "markdown",
|
| 144 |
+
"metadata": {},
|
| 145 |
+
"source": [
|
| 146 |
+
"## Runtime setup\n",
|
| 147 |
+
"\n",
|
| 148 |
+
"Training uses the active notebook kernel's Python interpreter (`sys.executable`).\n",
|
| 149 |
+
"The dependency check reports missing packages without installing them automatically.\n",
|
| 150 |
+
"Install them in this environment before enabling training.\n"
|
| 151 |
+
]
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"cell_type": "code",
|
| 155 |
+
"execution_count": null,
|
| 156 |
+
"metadata": {},
|
| 157 |
+
"outputs": [],
|
| 158 |
+
"source": [
|
| 159 |
+
"print('Notebook and training Python:', TRAIN_PYTHON)\n",
|
| 160 |
+
"packages = {\n",
|
| 161 |
+
" 'torch': 'torch', 'hickle': 'hickle', 'scipy': 'scipy',\n",
|
| 162 |
+
" 'numpy': 'numpy', 'pandas': 'pandas', 'matplotlib': 'matplotlib',\n",
|
| 163 |
+
" 'sklearn': 'scikit-learn', 'einops': 'einops', 'local_attention': 'local-attention',\n",
|
| 164 |
+
"}\n",
|
| 165 |
+
"dependency_status = pd.DataFrame([\n",
|
| 166 |
+
" {'module': module, 'package': package,\n",
|
| 167 |
+
" 'available': importlib.util.find_spec(module) is not None}\n",
|
| 168 |
+
" for module, package in packages.items()\n",
|
| 169 |
+
"])\n",
|
| 170 |
+
"display(dependency_status)\n",
|
| 171 |
+
"missing_packages = dependency_status.loc[~dependency_status['available'], 'package'].tolist()\n",
|
| 172 |
+
"if missing_packages:\n",
|
| 173 |
+
" install_cmd = [str(TRAIN_PYTHON), '-m', 'pip', 'install', *missing_packages]\n",
|
| 174 |
+
" print(subprocess.list2cmdline(install_cmd) if os.name == 'nt' else shlex.join(install_cmd))\n"
|
| 175 |
+
]
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"cell_type": "markdown",
|
| 179 |
+
"metadata": {},
|
| 180 |
+
"source": [
|
| 181 |
+
"## Inspect input files\n",
|
| 182 |
+
"\n",
|
| 183 |
+
"This cell gives a quick sanity check for the drug-response files without loading the large full matrices into memory."
|
| 184 |
+
]
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"cell_type": "code",
|
| 188 |
+
"execution_count": null,
|
| 189 |
+
"metadata": {},
|
| 190 |
+
"outputs": [],
|
| 191 |
+
"source": [
|
| 192 |
+
"def size_mb(path: Path) -> float:\n",
|
| 193 |
+
" return path.stat().st_size / 1024 / 1024\n",
|
| 194 |
+
"\n",
|
| 195 |
+
"input_summary = []\n",
|
| 196 |
+
"for path in sorted((DATA_ROOT / 'CCLE').glob('*')):\n",
|
| 197 |
+
" if path.is_file():\n",
|
| 198 |
+
" input_summary.append({'file': str(path.relative_to(DATA_ROOT)), 'size_mb': round(size_mb(path), 2)})\n",
|
| 199 |
+
"for path in sorted((DATA_ROOT / 'GDSC').glob('*')):\n",
|
| 200 |
+
" if path.is_file():\n",
|
| 201 |
+
" input_summary.append({'file': str(path.relative_to(DATA_ROOT)), 'size_mb': round(size_mb(path), 2)})\n",
|
| 202 |
+
"\n",
|
| 203 |
+
"display(pd.DataFrame(input_summary))\n",
|
| 204 |
+
"\n",
|
| 205 |
+
"ic50_path = DATA_ROOT / 'CCLE' / 'GDSC_IC50.csv'\n",
|
| 206 |
+
"ic50_preview = pd.read_csv(ic50_path, nrows=5)\n",
|
| 207 |
+
"display(ic50_preview)\n"
|
| 208 |
+
]
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"cell_type": "markdown",
|
| 212 |
+
"metadata": {},
|
| 213 |
+
"source": [
|
| 214 |
+
"## Training command\n",
|
| 215 |
+
"\n",
|
| 216 |
+
"Review the resolved input and output paths below. Outputs are written to this\n",
|
| 217 |
+
"project's tutorial run directory. Change `RESULT_DIR` to a different run directory\n",
|
| 218 |
+
"before launching an experiment whose outputs you want to keep separate.\n"
|
| 219 |
+
]
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"cell_type": "code",
|
| 223 |
+
"execution_count": null,
|
| 224 |
+
"metadata": {},
|
| 225 |
+
"outputs": [],
|
| 226 |
+
"source": [
|
| 227 |
+
"EPOCHS = 300\n",
|
| 228 |
+
"BATCH_SIZE = 32\n",
|
| 229 |
+
"POOL_METHOD = 'max'\n",
|
| 230 |
+
"\n",
|
| 231 |
+
"cmd = [\n",
|
| 232 |
+
" str(TRAIN_PYTHON), str(SCRIPT),\n",
|
| 233 |
+
" '--gpu_id', '0',\n",
|
| 234 |
+
" '--data_path', str(DATA_ROOT),\n",
|
| 235 |
+
" '--checkpoint_path', str(CKPT_PATH),\n",
|
| 236 |
+
" '--hyper_params_path', str(CONFIG_PKL),\n",
|
| 237 |
+
" '--epochs', str(EPOCHS),\n",
|
| 238 |
+
" '--batch_size', str(BATCH_SIZE),\n",
|
| 239 |
+
" '--module_data_dir', str(MODULE_DATA_DIR),\n",
|
| 240 |
+
" '--gene_list_path', str(GENE_LIST),\n",
|
| 241 |
+
" '--gene_mapping_path', str(GENE_MAP),\n",
|
| 242 |
+
" '--symbol2ens_path', str(GENE_MAP),\n",
|
| 243 |
+
" '--gene2vec_path', str(GENE2VEC),\n",
|
| 244 |
+
" '--save_dir', str(RESULT_DIR),\n",
|
| 245 |
+
" '--pool_method', POOL_METHOD,\n",
|
| 246 |
+
"]\n",
|
| 247 |
+
"\n",
|
| 248 |
+
"print('Working directory:', TASK_ROOT)\n",
|
| 249 |
+
"print(subprocess.list2cmdline(cmd) if os.name == 'nt' else shlex.join(cmd))\n"
|
| 250 |
+
]
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"cell_type": "code",
|
| 254 |
+
"execution_count": null,
|
| 255 |
+
"metadata": {},
|
| 256 |
+
"outputs": [],
|
| 257 |
+
"source": [
|
| 258 |
+
"RUN_TRAINING = False\n",
|
| 259 |
+
"env = os.environ.copy()\n",
|
| 260 |
+
"env['PYTHONPATH'] = PYTHONPATH\n",
|
| 261 |
+
"env['PYTHONUNBUFFERED'] = '1'\n",
|
| 262 |
+
"\n",
|
| 263 |
+
"if RUN_TRAINING:\n",
|
| 264 |
+
" RESULT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 265 |
+
" log_path = RESULT_DIR / 'train.log'\n",
|
| 266 |
+
" print('Writing log to:', log_path)\n",
|
| 267 |
+
" with log_path.open('w') as log_file:\n",
|
| 268 |
+
" proc = subprocess.Popen(\n",
|
| 269 |
+
" cmd,\n",
|
| 270 |
+
" cwd=str(TASK_ROOT),\n",
|
| 271 |
+
" env=env,\n",
|
| 272 |
+
" stdout=subprocess.PIPE,\n",
|
| 273 |
+
" stderr=subprocess.STDOUT,\n",
|
| 274 |
+
" text=True,\n",
|
| 275 |
+
" bufsize=1,\n",
|
| 276 |
+
" )\n",
|
| 277 |
+
" assert proc.stdout is not None\n",
|
| 278 |
+
" for line in proc.stdout:\n",
|
| 279 |
+
" print(line, end='')\n",
|
| 280 |
+
" log_file.write(line)\n",
|
| 281 |
+
" return_code = proc.wait()\n",
|
| 282 |
+
" if return_code != 0:\n",
|
| 283 |
+
" raise RuntimeError(f'Training failed with exit code {return_code}')\n",
|
| 284 |
+
"else:\n",
|
| 285 |
+
" print('Training was not launched. Set RUN_TRAINING = True to run the full job.')\n",
|
| 286 |
+
" print('The following cells inspect results only if predictions.npz exists in RESULT_DIR.')\n"
|
| 287 |
+
]
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"cell_type": "markdown",
|
| 291 |
+
"metadata": {},
|
| 292 |
+
"source": [
|
| 293 |
+
"## Inspect outputs\n",
|
| 294 |
+
"\n",
|
| 295 |
+
"Only the configured `RESULT_DIR` is used. The current script saves numerical arrays\n",
|
| 296 |
+
"under the `pred` and `validation` keys in `predictions.npz`. The reader also accepts\n",
|
| 297 |
+
"the historical `preds`, `test`, and `true` key names. Missing results are skipped.\n"
|
| 298 |
+
]
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"cell_type": "code",
|
| 302 |
+
"execution_count": null,
|
| 303 |
+
"metadata": {},
|
| 304 |
+
"outputs": [],
|
| 305 |
+
"source": [
|
| 306 |
+
"ACTIVE_RESULT_DIR = RESULT_DIR\n",
|
| 307 |
+
"pred_path = ACTIVE_RESULT_DIR / 'predictions.npz'\n",
|
| 308 |
+
"RESULTS_READY = pred_path.is_file()\n",
|
| 309 |
+
"if RESULTS_READY:\n",
|
| 310 |
+
" print('Active result directory:', ACTIVE_RESULT_DIR)\n",
|
| 311 |
+
" display(pd.DataFrame([\n",
|
| 312 |
+
" {'file': str(path.relative_to(ACTIVE_RESULT_DIR)), 'size_mb': round(size_mb(path), 3)}\n",
|
| 313 |
+
" for path in sorted(ACTIVE_RESULT_DIR.rglob('*')) if path.is_file()\n",
|
| 314 |
+
" ]))\n",
|
| 315 |
+
"else:\n",
|
| 316 |
+
" print('No predictions.npz found; result analysis and plots will be skipped:', pred_path)\n"
|
| 317 |
+
]
|
| 318 |
+
},
|
| 319 |
+
{
|
| 320 |
+
"cell_type": "code",
|
| 321 |
+
"execution_count": null,
|
| 322 |
+
"metadata": {},
|
| 323 |
+
"outputs": [],
|
| 324 |
+
"source": [
|
| 325 |
+
"meta = pd.DataFrame(columns=['pred', 'true', 'drug_id', 'cell_line_id', 'cancer_type'])\n",
|
| 326 |
+
"pred = true = np.array([], dtype=float)\n",
|
| 327 |
+
"if RESULTS_READY:\n",
|
| 328 |
+
" with np.load(pred_path, allow_pickle=True) as pred_npz:\n",
|
| 329 |
+
" print('Prediction keys:', pred_npz.files)\n",
|
| 330 |
+
" pred_key = next((key for key in ('pred', 'preds') if key in pred_npz.files), None)\n",
|
| 331 |
+
" true_key = next((key for key in ('validation', 'test', 'true') if key in pred_npz.files), None)\n",
|
| 332 |
+
" if pred_key is None or true_key is None:\n",
|
| 333 |
+
" raise ValueError('Prediction file needs pred/preds and validation/test/true arrays.')\n",
|
| 334 |
+
" pred = np.asarray(pred_npz[pred_key], dtype=float).reshape(-1)\n",
|
| 335 |
+
" true = np.asarray(pred_npz[true_key], dtype=float).reshape(-1)\n",
|
| 336 |
+
" if len(pred) != len(true):\n",
|
| 337 |
+
" raise ValueError('Prediction and target lengths do not match.')\n",
|
| 338 |
+
" meta = pd.DataFrame({'pred': pred, 'true': true})\n",
|
| 339 |
+
" for column in ['drug_id', 'cell_line_id', 'cancer_type']:\n",
|
| 340 |
+
" meta[column] = np.asarray(pred_npz[column]).reshape(-1) if column in pred_npz.files else 'NA'\n",
|
| 341 |
+
" finite = np.isfinite(pred) & np.isfinite(true)\n",
|
| 342 |
+
" pred, true = pred[finite], true[finite]\n",
|
| 343 |
+
" RESULTS_READY = bool(len(pred))\n",
|
| 344 |
+
" if RESULTS_READY:\n",
|
| 345 |
+
" pcc = float(pearsonr(pred, true)[0]) if len(pred) >= 2 and np.std(pred) > 0 and np.std(true) > 0 else np.nan\n",
|
| 346 |
+
" summary = pd.DataFrame([{'n_validation': len(pred), 'pcc': pcc,\n",
|
| 347 |
+
" 'mae': float(np.mean(np.abs(pred - true))),\n",
|
| 348 |
+
" 'rmse': float(np.sqrt(np.mean((pred - true) ** 2)))}])\n",
|
| 349 |
+
" display(summary)\n",
|
| 350 |
+
" display(meta.head())\n",
|
| 351 |
+
" else:\n",
|
| 352 |
+
" print('No finite prediction/target pairs; result plots will be skipped.')\n",
|
| 353 |
+
"else:\n",
|
| 354 |
+
" print('Prediction metrics skipped: no output file is available.')\n"
|
| 355 |
+
]
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"cell_type": "code",
|
| 359 |
+
"execution_count": null,
|
| 360 |
+
"metadata": {},
|
| 361 |
+
"outputs": [],
|
| 362 |
+
"source": [
|
| 363 |
+
"if RESULTS_READY:\n",
|
| 364 |
+
" import random\n",
|
| 365 |
+
" from scipy.stats import spearmanr\n",
|
| 366 |
+
"\n",
|
| 367 |
+
" TCGA_LABEL_SET = [\n",
|
| 368 |
+
" 'ALL', 'BLCA', 'BRCA', 'CESC', 'DLBC', 'LIHC', 'LUAD', 'ESCA', 'GBM',\n",
|
| 369 |
+
" 'HNSC', 'KIRC', 'LAML', 'LCML', 'LGG', 'LUSC', 'MESO', 'MM', 'NB',\n",
|
| 370 |
+
" 'OV', 'PAAD', 'SCLC', 'SKCM', 'STAD', 'THCA', 'COAD/READ',\n",
|
| 371 |
+
" ]\n",
|
| 372 |
+
"\n",
|
| 373 |
+
"\n",
|
| 374 |
+
" def pearson_corr(x, y):\n",
|
| 375 |
+
" x = np.asarray(x, dtype=float).reshape(-1)\n",
|
| 376 |
+
" y = np.asarray(y, dtype=float).reshape(-1)\n",
|
| 377 |
+
" mask = np.isfinite(x) & np.isfinite(y)\n",
|
| 378 |
+
" x = x[mask]\n",
|
| 379 |
+
" y = y[mask]\n",
|
| 380 |
+
" if len(x) < 2 or np.std(x) == 0 or np.std(y) == 0:\n",
|
| 381 |
+
" return np.nan\n",
|
| 382 |
+
" return float(np.corrcoef(x, y)[0, 1])\n",
|
| 383 |
+
"\n",
|
| 384 |
+
"\n",
|
| 385 |
+
" def regression_metrics(frame: pd.DataFrame) -> dict:\n",
|
| 386 |
+
" y_true = frame['true'].astype(float).to_numpy()\n",
|
| 387 |
+
" y_pred = frame['pred'].astype(float).to_numpy()\n",
|
| 388 |
+
" return {\n",
|
| 389 |
+
" 'n': int(len(frame)),\n",
|
| 390 |
+
" 'pcc': pearson_corr(y_true, y_pred),\n",
|
| 391 |
+
" 'spearman': float(spearmanr(y_true, y_pred).correlation) if len(frame) >= 2 else np.nan,\n",
|
| 392 |
+
" 'mae': float(np.mean(np.abs(y_pred - y_true))),\n",
|
| 393 |
+
" 'mse': float(np.mean((y_pred - y_true) ** 2)),\n",
|
| 394 |
+
" 'rmse': float(np.sqrt(np.mean((y_pred - y_true) ** 2))),\n",
|
| 395 |
+
" }\n",
|
| 396 |
+
"\n",
|
| 397 |
+
"\n",
|
| 398 |
+
" def reconstruct_validation_metadata(data_root: Path, n_expected: int, ratio: float = 0.95, seed: int = 0) -> pd.DataFrame:\n",
|
| 399 |
+
" \"\"\"Rebuild the validation split metadata using the same deterministic split as the training script.\"\"\"\n",
|
| 400 |
+
" drug_info_file = data_root / 'GDSC' / '1.Drug_listMon Jun 24 09_00_55 2019.csv'\n",
|
| 401 |
+
" cell_line_info_file = data_root / 'CCLE' / 'Cell_lines_annotations_20181226.txt'\n",
|
| 402 |
+
" drug_feature_dir = data_root / 'GDSC' / 'drug_graph_feat'\n",
|
| 403 |
+
" gene_expression_file = data_root / 'CCLE' / 'genomic_expression_561celllines_697genes_demap_features.csv'\n",
|
| 404 |
+
" ic50_file = data_root / 'CCLE' / 'GDSC_IC50.csv'\n",
|
| 405 |
+
"\n",
|
| 406 |
+
" for required in [drug_info_file, cell_line_info_file, drug_feature_dir, gene_expression_file, ic50_file]:\n",
|
| 407 |
+
" if not required.exists():\n",
|
| 408 |
+
" raise FileNotFoundError(required)\n",
|
| 409 |
+
"\n",
|
| 410 |
+
" drug_info = pd.read_csv(drug_info_file)\n",
|
| 411 |
+
" drugid2pubchemid = {\n",
|
| 412 |
+
" str(row.iloc[0]): str(row.iloc[5])\n",
|
| 413 |
+
" for _, row in drug_info.iterrows()\n",
|
| 414 |
+
" if str(row.iloc[5]).isdigit()\n",
|
| 415 |
+
" }\n",
|
| 416 |
+
"\n",
|
| 417 |
+
" cellline2cancertype = {}\n",
|
| 418 |
+
" with cell_line_info_file.open() as handle:\n",
|
| 419 |
+
" for line in handle.readlines()[1:]:\n",
|
| 420 |
+
" parts = line.rstrip('\\n').split('\\t')\n",
|
| 421 |
+
" if len(parts) >= 2:\n",
|
| 422 |
+
" cellline2cancertype[parts[1]] = parts[-1]\n",
|
| 423 |
+
"\n",
|
| 424 |
+
" gexpr_index = set(pd.read_csv(gene_expression_file, usecols=[0]).iloc[:, 0].astype(str))\n",
|
| 425 |
+
" drug_pubchem_ids = {path.stem for path in drug_feature_dir.glob('*.hkl')}\n",
|
| 426 |
+
"\n",
|
| 427 |
+
" ic50 = pd.read_csv(ic50_file, header=0, index_col=0)\n",
|
| 428 |
+
" drug_match_list = [idx for idx in ic50.index if idx.split(':')[1] in drugid2pubchemid]\n",
|
| 429 |
+
" ic50 = ic50.loc[drug_match_list]\n",
|
| 430 |
+
"\n",
|
| 431 |
+
" data_idx = []\n",
|
| 432 |
+
" for each_drug in ic50.index:\n",
|
| 433 |
+
" pubchem_id = drugid2pubchemid[each_drug.split(':')[-1]]\n",
|
| 434 |
+
" if pubchem_id not in drug_pubchem_ids:\n",
|
| 435 |
+
" continue\n",
|
| 436 |
+
" for each_cellline in ic50.columns:\n",
|
| 437 |
+
" if each_cellline not in gexpr_index or each_cellline not in cellline2cancertype:\n",
|
| 438 |
+
" continue\n",
|
| 439 |
+
" value = ic50.loc[each_drug, each_cellline]\n",
|
| 440 |
+
" if pd.isna(value):\n",
|
| 441 |
+
" continue\n",
|
| 442 |
+
" data_idx.append((each_cellline, pubchem_id, float(value), cellline2cancertype[each_cellline]))\n",
|
| 443 |
+
"\n",
|
| 444 |
+
" rng = random.Random(seed)\n",
|
| 445 |
+
" validation_idx = []\n",
|
| 446 |
+
" for each_type in TCGA_LABEL_SET:\n",
|
| 447 |
+
" subtype = [item for item in data_idx if item[-1] == each_type]\n",
|
| 448 |
+
" if not subtype:\n",
|
| 449 |
+
" continue\n",
|
| 450 |
+
" k = max(1, int(ratio * len(subtype)))\n",
|
| 451 |
+
" train_list = rng.sample(subtype, k)\n",
|
| 452 |
+
" validation_idx.extend([item for item in subtype if item not in train_list])\n",
|
| 453 |
+
"\n",
|
| 454 |
+
" reconstructed = pd.DataFrame(validation_idx, columns=['cell_line_id', 'drug_id', 'true_from_metadata', 'cancer_type'])\n",
|
| 455 |
+
" if len(reconstructed) != n_expected:\n",
|
| 456 |
+
" raise ValueError(f'Reconstructed {len(reconstructed)} rows, but predictions contain {n_expected} rows.')\n",
|
| 457 |
+
" return reconstructed\n",
|
| 458 |
+
"\n",
|
| 459 |
+
"\n",
|
| 460 |
+
" analysis_df = meta.copy()\n",
|
| 461 |
+
" analysis_df['pred'] = pd.to_numeric(analysis_df['pred'], errors='coerce')\n",
|
| 462 |
+
" analysis_df['true'] = pd.to_numeric(analysis_df['true'], errors='coerce')\n",
|
| 463 |
+
"\n",
|
| 464 |
+
" metadata_cols = ['drug_id', 'cell_line_id', 'cancer_type']\n",
|
| 465 |
+
" metadata_missing = any(col not in analysis_df.columns for col in metadata_cols) or all(\n",
|
| 466 |
+
" analysis_df[col].astype(str).isin(['NA', 'nan', 'None', '']).all() for col in metadata_cols if col in analysis_df.columns\n",
|
| 467 |
+
" )\n",
|
| 468 |
+
"\n",
|
| 469 |
+
" if metadata_missing:\n",
|
| 470 |
+
" try:\n",
|
| 471 |
+
" recovered_meta = reconstruct_validation_metadata(DATA_ROOT, n_expected=len(analysis_df), ratio=0.95, seed=0)\n",
|
| 472 |
+
" if not np.allclose(analysis_df['true'].to_numpy(dtype=float), recovered_meta['true_from_metadata'].to_numpy(dtype=float), rtol=1e-5, atol=1e-6, equal_nan=True):\n",
|
| 473 |
+
" raise ValueError('Reconstructed targets do not match the prediction rows; metadata will not be assigned.')\n",
|
| 474 |
+
" for col in metadata_cols:\n",
|
| 475 |
+
" analysis_df[col] = recovered_meta[col].to_numpy()\n",
|
| 476 |
+
" print('Recovered validation metadata from the original GDSC/CCLE split.')\n",
|
| 477 |
+
" except Exception as exc:\n",
|
| 478 |
+
" print('Metadata recovery skipped:', repr(exc))\n",
|
| 479 |
+
" for col in metadata_cols:\n",
|
| 480 |
+
" if col not in analysis_df.columns:\n",
|
| 481 |
+
" analysis_df[col] = 'NA'\n",
|
| 482 |
+
" else:\n",
|
| 483 |
+
" print('Using drug/cell/cancer metadata stored in predictions.npz.')\n",
|
| 484 |
+
"\n",
|
| 485 |
+
" analysis_df = analysis_df.replace([np.inf, -np.inf], np.nan).dropna(subset=['pred', 'true']).reset_index(drop=True)\n",
|
| 486 |
+
"\n",
|
| 487 |
+
" overall_metrics = pd.DataFrame([regression_metrics(analysis_df)])\n",
|
| 488 |
+
" display(overall_metrics)\n",
|
| 489 |
+
" display(analysis_df.head())\n",
|
| 490 |
+
"\n",
|
| 491 |
+
" metric_columns = ['pred', 'true']\n",
|
| 492 |
+
" by_cancer = (\n",
|
| 493 |
+
" analysis_df.groupby('cancer_type', dropna=False)[metric_columns]\n",
|
| 494 |
+
" .apply(lambda frame: pd.Series(regression_metrics(frame)))\n",
|
| 495 |
+
" .reset_index()\n",
|
| 496 |
+
" .sort_values('n', ascending=False)\n",
|
| 497 |
+
" )\n",
|
| 498 |
+
" by_drug = (\n",
|
| 499 |
+
" analysis_df.groupby('drug_id', dropna=False)[metric_columns]\n",
|
| 500 |
+
" .apply(lambda frame: pd.Series(regression_metrics(frame)))\n",
|
| 501 |
+
" .reset_index()\n",
|
| 502 |
+
" .sort_values('pcc', ascending=False)\n",
|
| 503 |
+
" )\n",
|
| 504 |
+
"\n",
|
| 505 |
+
" display(by_cancer.head(12))\n",
|
| 506 |
+
" display(by_drug.head(10))\n",
|
| 507 |
+
"else:\n",
|
| 508 |
+
" print('Grouped analysis skipped: no usable predictions are available.')\n"
|
| 509 |
+
]
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"cell_type": "markdown",
|
| 513 |
+
"metadata": {},
|
| 514 |
+
"source": [
|
| 515 |
+
"## Validation scatter plot\n",
|
| 516 |
+
"\n",
|
| 517 |
+
"When predictions exist, this cell saves a PDF and PNG of predicted versus measured\n",
|
| 518 |
+
"drug response under `RESULT_DIR`. No external reference-run directory is used.\n"
|
| 519 |
+
]
|
| 520 |
+
},
|
| 521 |
+
{
|
| 522 |
+
"cell_type": "code",
|
| 523 |
+
"execution_count": null,
|
| 524 |
+
"metadata": {},
|
| 525 |
+
"outputs": [],
|
| 526 |
+
"source": [
|
| 527 |
+
"if RESULTS_READY:\n",
|
| 528 |
+
" import matplotlib.pyplot as plt\n",
|
| 529 |
+
"\n",
|
| 530 |
+
" plt.rcParams.update({\n",
|
| 531 |
+
" 'font.family': 'sans-serif',\n",
|
| 532 |
+
" 'font.sans-serif': ['Arial', 'DejaVu Sans'],\n",
|
| 533 |
+
" 'font.size': 7,\n",
|
| 534 |
+
" 'pdf.fonttype': 42,\n",
|
| 535 |
+
" 'ps.fonttype': 42,\n",
|
| 536 |
+
" })\n",
|
| 537 |
+
"\n",
|
| 538 |
+
" RESULT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 539 |
+
" scatter_png = RESULT_DIR / 'drug_response_prediction_scatter.png'\n",
|
| 540 |
+
" scatter_pdf = RESULT_DIR / 'drug_response_prediction_scatter.pdf'\n",
|
| 541 |
+
"\n",
|
| 542 |
+
" low = float(min(np.min(true), np.min(pred)))\n",
|
| 543 |
+
" high = float(max(np.max(true), np.max(pred)))\n",
|
| 544 |
+
" pad = max(0.05 * (high - low), 0.05)\n",
|
| 545 |
+
" lims = (low - pad, high + pad)\n",
|
| 546 |
+
"\n",
|
| 547 |
+
" fig, ax = plt.subplots(figsize=(3.35, 3.1))\n",
|
| 548 |
+
" ax.scatter(true, pred, s=7, alpha=0.45, linewidths=0, color='#2f6fb0')\n",
|
| 549 |
+
" ax.plot(lims, lims, color='#444444', linewidth=0.8, linestyle='--')\n",
|
| 550 |
+
" ax.set_xlim(lims)\n",
|
| 551 |
+
" ax.set_ylim(lims)\n",
|
| 552 |
+
" ax.set_xlabel('Measured drug response')\n",
|
| 553 |
+
" ax.set_ylabel('Predicted drug response')\n",
|
| 554 |
+
" ax.set_title('Drug response prediction')\n",
|
| 555 |
+
" ax.text(0.03, 0.97, f'PCC = {pcc:.3f}\\nN = {len(pred)}', transform=ax.transAxes, va='top')\n",
|
| 556 |
+
" ax.spines['top'].set_visible(False)\n",
|
| 557 |
+
" ax.spines['right'].set_visible(False)\n",
|
| 558 |
+
" fig.tight_layout()\n",
|
| 559 |
+
" fig.savefig(scatter_png, dpi=300, bbox_inches='tight')\n",
|
| 560 |
+
" fig.savefig(scatter_pdf, bbox_inches='tight')\n",
|
| 561 |
+
" plt.close(fig)\n",
|
| 562 |
+
"\n",
|
| 563 |
+
" print('Saved:', scatter_png)\n",
|
| 564 |
+
" print('Saved:', scatter_pdf)\n",
|
| 565 |
+
" if Image is not None:\n",
|
| 566 |
+
" display(Image(filename=str(scatter_png)))\n",
|
| 567 |
+
"else:\n",
|
| 568 |
+
" print('Scatter plot skipped: no usable predictions are available.')\n"
|
| 569 |
+
]
|
| 570 |
+
},
|
| 571 |
+
{
|
| 572 |
+
"cell_type": "markdown",
|
| 573 |
+
"metadata": {},
|
| 574 |
+
"source": [
|
| 575 |
+
"## Additional validation plots\n",
|
| 576 |
+
"\n",
|
| 577 |
+
"These self-contained plots use the current `predictions.npz`: overall scatter,\n",
|
| 578 |
+
"residuals, per-drug and per-cancer summaries when matching metadata is available,\n",
|
| 579 |
+
"and an exploratory median-threshold ROC. The median-threshold classification is a\n",
|
| 580 |
+
"derived visualization of the regression outputs, not an independent test result.\n",
|
| 581 |
+
"No hardcoded model-comparison scores or external plotting scripts are used.\n"
|
| 582 |
+
]
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"cell_type": "code",
|
| 586 |
+
"execution_count": null,
|
| 587 |
+
"metadata": {},
|
| 588 |
+
"outputs": [],
|
| 589 |
+
"source": [
|
| 590 |
+
"if RESULTS_READY:\n",
|
| 591 |
+
" from matplotlib.ticker import FormatStrFormatter\n",
|
| 592 |
+
"\n",
|
| 593 |
+
" try:\n",
|
| 594 |
+
" from sklearn.metrics import roc_curve, roc_auc_score\n",
|
| 595 |
+
" except Exception as exc:\n",
|
| 596 |
+
" roc_curve = None\n",
|
| 597 |
+
" roc_auc_score = None\n",
|
| 598 |
+
" print('ROC dependencies unavailable:', repr(exc))\n",
|
| 599 |
+
"\n",
|
| 600 |
+
" PAPER_PLOT_DIR = RESULT_DIR / 'drug_response_paper_plots'\n",
|
| 601 |
+
" PAPER_PLOT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 602 |
+
"\n",
|
| 603 |
+
" plt.rcParams.update({\n",
|
| 604 |
+
" 'font.family': 'sans-serif',\n",
|
| 605 |
+
" 'font.sans-serif': ['Arial', 'DejaVu Sans'],\n",
|
| 606 |
+
" 'font.size': 7,\n",
|
| 607 |
+
" 'axes.labelsize': 7,\n",
|
| 608 |
+
" 'axes.titlesize': 8,\n",
|
| 609 |
+
" 'xtick.labelsize': 6,\n",
|
| 610 |
+
" 'ytick.labelsize': 6,\n",
|
| 611 |
+
" 'legend.fontsize': 6,\n",
|
| 612 |
+
" 'axes.linewidth': 0.7,\n",
|
| 613 |
+
" 'pdf.fonttype': 42,\n",
|
| 614 |
+
" 'ps.fonttype': 42,\n",
|
| 615 |
+
" 'svg.fonttype': 'none',\n",
|
| 616 |
+
" })\n",
|
| 617 |
+
"\n",
|
| 618 |
+
"\n",
|
| 619 |
+
" def save_figure(fig, stem: str, dpi: int = 600):\n",
|
| 620 |
+
" png_path = PAPER_PLOT_DIR / f'{stem}.png'\n",
|
| 621 |
+
" pdf_path = PAPER_PLOT_DIR / f'{stem}.pdf'\n",
|
| 622 |
+
" fig.savefig(png_path, dpi=dpi, bbox_inches='tight')\n",
|
| 623 |
+
" fig.savefig(pdf_path, bbox_inches='tight')\n",
|
| 624 |
+
" plt.close(fig)\n",
|
| 625 |
+
" print('Saved:', png_path)\n",
|
| 626 |
+
" print('Saved:', pdf_path)\n",
|
| 627 |
+
" return png_path, pdf_path\n",
|
| 628 |
+
"\n",
|
| 629 |
+
"\n",
|
| 630 |
+
" def despine(ax):\n",
|
| 631 |
+
" ax.spines['top'].set_visible(False)\n",
|
| 632 |
+
" ax.spines['right'].set_visible(False)\n",
|
| 633 |
+
" ax.tick_params(width=0.7, length=2.5)\n",
|
| 634 |
+
"\n",
|
| 635 |
+
"\n",
|
| 636 |
+
" def finite_xy(frame: pd.DataFrame):\n",
|
| 637 |
+
" y_true = pd.to_numeric(frame['true'], errors='coerce').to_numpy(dtype=float)\n",
|
| 638 |
+
" y_pred = pd.to_numeric(frame['pred'], errors='coerce').to_numpy(dtype=float)\n",
|
| 639 |
+
" mask = np.isfinite(y_true) & np.isfinite(y_pred)\n",
|
| 640 |
+
" return y_true[mask], y_pred[mask]\n",
|
| 641 |
+
"\n",
|
| 642 |
+
"\n",
|
| 643 |
+
" # 1) Overall measured-vs-predicted scatter.\n",
|
| 644 |
+
" y_true, y_pred = finite_xy(analysis_df)\n",
|
| 645 |
+
" low = float(min(y_true.min(), y_pred.min()))\n",
|
| 646 |
+
" high = float(max(y_true.max(), y_pred.max()))\n",
|
| 647 |
+
" pad = max(0.05 * (high - low), 0.05)\n",
|
| 648 |
+
" lims = (low - pad, high + pad)\n",
|
| 649 |
+
" metrics = regression_metrics(analysis_df)\n",
|
| 650 |
+
"\n",
|
| 651 |
+
" fig, ax = plt.subplots(figsize=(3.35, 3.15))\n",
|
| 652 |
+
" ax.scatter(y_true, y_pred, s=5, alpha=0.35, linewidths=0, color='#2F6FB0', rasterized=True)\n",
|
| 653 |
+
" ax.plot(lims, lims, color='#333333', linewidth=0.8, linestyle='--')\n",
|
| 654 |
+
" ax.set_xlim(lims)\n",
|
| 655 |
+
" ax.set_ylim(lims)\n",
|
| 656 |
+
" ax.set_xlabel('Measured drug response')\n",
|
| 657 |
+
" ax.set_ylabel('Predicted drug response')\n",
|
| 658 |
+
" ax.set_title('BioMod drug response')\n",
|
| 659 |
+
" ax.text(\n",
|
| 660 |
+
" 0.03, 0.97,\n",
|
| 661 |
+
" f'PCC = {metrics[\"pcc\"]:.3f}\\nRMSE = {metrics[\"rmse\"]:.3f}\\nN = {metrics[\"n\"]}',\n",
|
| 662 |
+
" transform=ax.transAxes,\n",
|
| 663 |
+
" va='top',\n",
|
| 664 |
+
" ha='left',\n",
|
| 665 |
+
" )\n",
|
| 666 |
+
" despine(ax)\n",
|
| 667 |
+
" fig.tight_layout()\n",
|
| 668 |
+
" overall_scatter_png, overall_scatter_pdf = save_figure(fig, 'drug_response_overall_scatter')\n",
|
| 669 |
+
"\n",
|
| 670 |
+
" # 2) Residual distribution.\n",
|
| 671 |
+
" residual = y_pred - y_true\n",
|
| 672 |
+
" fig, ax = plt.subplots(figsize=(3.35, 2.35))\n",
|
| 673 |
+
" ax.hist(residual, bins=45, color='#76B7B2', edgecolor='white', linewidth=0.3)\n",
|
| 674 |
+
" ax.axvline(0, color='#333333', linestyle='--', linewidth=0.8)\n",
|
| 675 |
+
" ax.set_xlabel('Prediction residual')\n",
|
| 676 |
+
" ax.set_ylabel('Number of samples')\n",
|
| 677 |
+
" ax.set_title('Residual distribution')\n",
|
| 678 |
+
" despine(ax)\n",
|
| 679 |
+
" fig.tight_layout()\n",
|
| 680 |
+
" residual_png, residual_pdf = save_figure(fig, 'drug_response_residual_distribution')\n",
|
| 681 |
+
"\n",
|
| 682 |
+
" # 3) Top-5 most precise drugs, among drugs with sufficient validation samples.\n",
|
| 683 |
+
" MIN_SAMPLES_PER_DRUG = 3\n",
|
| 684 |
+
" valid_drug_stats = by_drug[(by_drug['drug_id'].astype(str) != 'NA') & (by_drug['n'] >= MIN_SAMPLES_PER_DRUG)].dropna(subset=['pcc'])\n",
|
| 685 |
+
" if len(valid_drug_stats) > 0:\n",
|
| 686 |
+
" top5_drugs = valid_drug_stats.sort_values('pcc', ascending=False).head(5)['drug_id'].astype(str).tolist()\n",
|
| 687 |
+
" top5_summary = valid_drug_stats[valid_drug_stats['drug_id'].astype(str).isin(top5_drugs)].copy()\n",
|
| 688 |
+
" top5_points = analysis_df[analysis_df['drug_id'].astype(str).isin(top5_drugs)].copy()\n",
|
| 689 |
+
" top5_summary.to_csv(PAPER_PLOT_DIR / 'top5_drug_pcc_summary.csv', index=False)\n",
|
| 690 |
+
" top5_points.to_csv(PAPER_PLOT_DIR / 'top5_drug_selected_points.csv', index=False)\n",
|
| 691 |
+
"\n",
|
| 692 |
+
" fig, ax = plt.subplots(figsize=(3.65, 3.25))\n",
|
| 693 |
+
" palette = plt.get_cmap('tab10')\n",
|
| 694 |
+
" for idx, drug_id in enumerate(top5_drugs):\n",
|
| 695 |
+
" sub = top5_points[top5_points['drug_id'].astype(str) == drug_id]\n",
|
| 696 |
+
" ax.scatter(\n",
|
| 697 |
+
" sub['true'], sub['pred'],\n",
|
| 698 |
+
" s=10, alpha=0.75, linewidths=0,\n",
|
| 699 |
+
" color=palette(idx), label=str(drug_id), rasterized=True,\n",
|
| 700 |
+
" )\n",
|
| 701 |
+
" t_true, t_pred = finite_xy(top5_points)\n",
|
| 702 |
+
" low = float(min(t_true.min(), t_pred.min()))\n",
|
| 703 |
+
" high = float(max(t_true.max(), t_pred.max()))\n",
|
| 704 |
+
" pad = max(0.05 * (high - low), 0.05)\n",
|
| 705 |
+
" lims = (low - pad, high + pad)\n",
|
| 706 |
+
" ax.plot(lims, lims, color='#333333', linewidth=0.8, linestyle='--')\n",
|
| 707 |
+
" ax.set_xlim(lims)\n",
|
| 708 |
+
" ax.set_ylim(lims)\n",
|
| 709 |
+
" ax.set_xlabel('Measured drug response')\n",
|
| 710 |
+
" ax.set_ylabel('Predicted drug response')\n",
|
| 711 |
+
" ax.set_title('Most precise 5 drugs')\n",
|
| 712 |
+
" ax.legend(title='PubChem ID', frameon=False, loc='best', handletextpad=0.3, borderpad=0.2)\n",
|
| 713 |
+
" despine(ax)\n",
|
| 714 |
+
" fig.tight_layout()\n",
|
| 715 |
+
" top5_png, top5_pdf = save_figure(fig, 'drug_response_top5_drugs')\n",
|
| 716 |
+
" else:\n",
|
| 717 |
+
" top5_png = top5_pdf = None\n",
|
| 718 |
+
" print('Top-5 drug panel skipped: drug_id metadata is unavailable or too sparse.')\n",
|
| 719 |
+
"\n",
|
| 720 |
+
" # 4) Per-cancer-type PCC bar plot.\n",
|
| 721 |
+
" valid_cancer = by_cancer[(by_cancer['cancer_type'].astype(str) != 'NA') & (by_cancer['n'] >= 2)].dropna(subset=['pcc']).copy()\n",
|
| 722 |
+
" if len(valid_cancer) > 0:\n",
|
| 723 |
+
" valid_cancer = valid_cancer.sort_values('pcc', ascending=True)\n",
|
| 724 |
+
" valid_cancer.to_csv(PAPER_PLOT_DIR / 'per_cancer_type_metrics.csv', index=False)\n",
|
| 725 |
+
" fig_height = max(2.2, 0.16 * len(valid_cancer) + 0.7)\n",
|
| 726 |
+
" fig, ax = plt.subplots(figsize=(3.7, fig_height))\n",
|
| 727 |
+
" colors = ['#4E79A7' if value < valid_cancer['pcc'].median() else '#59A14F' for value in valid_cancer['pcc']]\n",
|
| 728 |
+
" ax.barh(valid_cancer['cancer_type'].astype(str), valid_cancer['pcc'], color=colors, edgecolor='#333333', linewidth=0.35)\n",
|
| 729 |
+
" ax.set_xlabel('Pearson correlation')\n",
|
| 730 |
+
" ax.set_ylabel('Cancer type')\n",
|
| 731 |
+
" ax.set_xlim(min(0, float(valid_cancer['pcc'].min()) - 0.05), 1.0)\n",
|
| 732 |
+
" ax.xaxis.set_major_formatter(FormatStrFormatter('%.2f'))\n",
|
| 733 |
+
" ax.grid(axis='x', linestyle='--', linewidth=0.4, alpha=0.35)\n",
|
| 734 |
+
" ax.set_axisbelow(True)\n",
|
| 735 |
+
" ax.set_title('Performance by cancer type')\n",
|
| 736 |
+
" despine(ax)\n",
|
| 737 |
+
" fig.tight_layout()\n",
|
| 738 |
+
" cancer_png, cancer_pdf = save_figure(fig, 'drug_response_per_cancer_type_pcc')\n",
|
| 739 |
+
" else:\n",
|
| 740 |
+
" cancer_png = cancer_pdf = None\n",
|
| 741 |
+
" print('Cancer-type PCC plot skipped: cancer_type metadata is unavailable or too sparse.')\n",
|
| 742 |
+
"\n",
|
| 743 |
+
" # 5) Median-threshold ROC, an exploratory view of the regression outputs.\n",
|
| 744 |
+
" if roc_curve is not None and roc_auc_score is not None:\n",
|
| 745 |
+
" threshold = float(np.median(y_true))\n",
|
| 746 |
+
" y_binary = (y_true <= threshold).astype(int)\n",
|
| 747 |
+
" roc_score = -y_pred\n",
|
| 748 |
+
" if len(np.unique(y_binary)) == 2:\n",
|
| 749 |
+
" fpr, tpr, roc_thresholds = roc_curve(y_binary, roc_score)\n",
|
| 750 |
+
" auc_value = float(roc_auc_score(y_binary, roc_score))\n",
|
| 751 |
+
" roc_table = pd.DataFrame({'fpr': fpr, 'tpr': tpr, 'threshold': roc_thresholds})\n",
|
| 752 |
+
" roc_table.to_csv(PAPER_PLOT_DIR / 'drug_response_roc_curve.csv', index=False)\n",
|
| 753 |
+
"\n",
|
| 754 |
+
" fig, ax = plt.subplots(figsize=(3.1, 3.1))\n",
|
| 755 |
+
" ax.plot([0, 1], [0, 1], linestyle='--', color='#333333', linewidth=0.8, alpha=0.7)\n",
|
| 756 |
+
" ax.plot(fpr, tpr, color='#9C6ADE', linewidth=1.1, label=f'AUROC = {auc_value:.3f}')\n",
|
| 757 |
+
" ax.set_xlabel('False positive rate')\n",
|
| 758 |
+
" ax.set_ylabel('True positive rate')\n",
|
| 759 |
+
" ax.set_xlim(0, 1)\n",
|
| 760 |
+
" ax.set_ylim(0, 1)\n",
|
| 761 |
+
" ax.set_aspect('equal', adjustable='box')\n",
|
| 762 |
+
" ax.legend(frameon=False, loc='lower right')\n",
|
| 763 |
+
" ax.set_title('Median-threshold ROC')\n",
|
| 764 |
+
" despine(ax)\n",
|
| 765 |
+
" fig.tight_layout()\n",
|
| 766 |
+
" roc_png, roc_pdf = save_figure(fig, 'drug_response_median_threshold_roc')\n",
|
| 767 |
+
" fig_svg = PAPER_PLOT_DIR / 'drug_response_median_threshold_roc.svg'\n",
|
| 768 |
+
" fig, ax = plt.subplots(figsize=(3.1, 3.1))\n",
|
| 769 |
+
" ax.plot([0, 1], [0, 1], linestyle='--', color='#333333', linewidth=0.8, alpha=0.7)\n",
|
| 770 |
+
" ax.plot(fpr, tpr, color='#9C6ADE', linewidth=1.1, label=f'AUROC = {auc_value:.3f}')\n",
|
| 771 |
+
" ax.set_xlabel('False positive rate')\n",
|
| 772 |
+
" ax.set_ylabel('True positive rate')\n",
|
| 773 |
+
" ax.set_xlim(0, 1)\n",
|
| 774 |
+
" ax.set_ylim(0, 1)\n",
|
| 775 |
+
" ax.set_aspect('equal', adjustable='box')\n",
|
| 776 |
+
" ax.legend(frameon=False, loc='lower right')\n",
|
| 777 |
+
" ax.set_title('Median-threshold ROC')\n",
|
| 778 |
+
" despine(ax)\n",
|
| 779 |
+
" fig.tight_layout()\n",
|
| 780 |
+
" fig.savefig(fig_svg, bbox_inches='tight')\n",
|
| 781 |
+
" plt.close(fig)\n",
|
| 782 |
+
" print('Saved:', fig_svg)\n",
|
| 783 |
+
" else:\n",
|
| 784 |
+
" roc_png = roc_pdf = None\n",
|
| 785 |
+
" print('ROC plot skipped: median split produced a single class.')\n",
|
| 786 |
+
" else:\n",
|
| 787 |
+
" roc_png = roc_pdf = None\n",
|
| 788 |
+
"\n",
|
| 789 |
+
"\n",
|
| 790 |
+
" plot_outputs = [overall_scatter_png, residual_png, top5_png, cancer_png, roc_png]\n",
|
| 791 |
+
" plot_outputs = [path for path in plot_outputs if path is not None]\n",
|
| 792 |
+
" display(pd.DataFrame({'plot': plot_outputs}))\n",
|
| 793 |
+
"else:\n",
|
| 794 |
+
" print('Additional plots skipped: no usable predictions are available.')\n"
|
| 795 |
+
]
|
| 796 |
+
},
|
| 797 |
+
{
|
| 798 |
+
"cell_type": "markdown",
|
| 799 |
+
"metadata": {},
|
| 800 |
+
"source": [
|
| 801 |
+
"## Available figure outputs\n",
|
| 802 |
+
"\n",
|
| 803 |
+
"Display figures that exist under the configured tutorial output directory.\n"
|
| 804 |
+
]
|
| 805 |
+
},
|
| 806 |
+
{
|
| 807 |
+
"cell_type": "code",
|
| 808 |
+
"execution_count": null,
|
| 809 |
+
"metadata": {},
|
| 810 |
+
"outputs": [],
|
| 811 |
+
"source": [
|
| 812 |
+
"figure_candidates = [RESULT_DIR / 'drug_response_prediction_scatter.png']\n",
|
| 813 |
+
"figure_candidates += sorted((RESULT_DIR / 'drug_response_paper_plots').glob('*.png'))\n",
|
| 814 |
+
"available_figures = [path for path in figure_candidates if path.is_file()]\n",
|
| 815 |
+
"if available_figures:\n",
|
| 816 |
+
" for fig_path in available_figures:\n",
|
| 817 |
+
" print(fig_path)\n",
|
| 818 |
+
" if Image is not None:\n",
|
| 819 |
+
" display(Image(filename=str(fig_path)))\n",
|
| 820 |
+
"else:\n",
|
| 821 |
+
" print('No generated figures are available in:', RESULT_DIR)\n"
|
| 822 |
+
]
|
| 823 |
+
},
|
| 824 |
+
{
|
| 825 |
+
"cell_type": "markdown",
|
| 826 |
+
"metadata": {},
|
| 827 |
+
"source": [
|
| 828 |
+
"## Generated files\n",
|
| 829 |
+
"\n",
|
| 830 |
+
"List local files produced by training or the notebook's plotting cells.\n"
|
| 831 |
+
]
|
| 832 |
+
},
|
| 833 |
+
{
|
| 834 |
+
"cell_type": "code",
|
| 835 |
+
"execution_count": null,
|
| 836 |
+
"metadata": {},
|
| 837 |
+
"outputs": [],
|
| 838 |
+
"source": [
|
| 839 |
+
"generated_files = [path for path in sorted(RESULT_DIR.rglob('*')) if path.is_file()]\n",
|
| 840 |
+
"if generated_files:\n",
|
| 841 |
+
" display(pd.DataFrame([\n",
|
| 842 |
+
" {'file': str(path.relative_to(RESULT_DIR)), 'path': str(path)}\n",
|
| 843 |
+
" for path in generated_files\n",
|
| 844 |
+
" ]))\n",
|
| 845 |
+
"else:\n",
|
| 846 |
+
" print('No outputs have been generated yet.')\n"
|
| 847 |
+
]
|
| 848 |
+
}
|
| 849 |
+
],
|
| 850 |
+
"metadata": {
|
| 851 |
+
"kernelspec": {
|
| 852 |
+
"display_name": "Python 3",
|
| 853 |
+
"language": "python",
|
| 854 |
+
"name": "python3"
|
| 855 |
+
},
|
| 856 |
+
"language_info": {
|
| 857 |
+
"codemirror_mode": {
|
| 858 |
+
"name": "ipython",
|
| 859 |
+
"version": 3
|
| 860 |
+
},
|
| 861 |
+
"file_extension": ".py",
|
| 862 |
+
"mimetype": "text/x-python",
|
| 863 |
+
"name": "python",
|
| 864 |
+
"nbconvert_exporter": "python",
|
| 865 |
+
"pygments_lexer": "ipython3",
|
| 866 |
+
"version": "3.9"
|
| 867 |
+
}
|
| 868 |
+
},
|
| 869 |
+
"nbformat": 4,
|
| 870 |
+
"nbformat_minor": 5
|
| 871 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BioMod/toturials/gene_perturbation.ipynb
CHANGED
|
@@ -1,359 +1,318 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# Gene perturbation
|
| 8 |
-
"\n",
|
| 9 |
-
"
|
| 10 |
-
]
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
"cell_type": "markdown",
|
| 14 |
-
"metadata": {},
|
| 15 |
-
"source": [
|
| 16 |
-
"##
|
| 17 |
-
"\n",
|
| 18 |
-
"The
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
"
|
| 30 |
-
"\n",
|
| 31 |
-
"-
|
| 32 |
-
"-
|
| 33 |
-
"-
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
"
|
| 50 |
-
"import
|
| 51 |
-
"import
|
| 52 |
-
"
|
| 53 |
-
"import
|
| 54 |
-
"
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"\n",
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
-
"\n",
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"\n",
|
| 70 |
-
"
|
| 71 |
-
"
|
| 72 |
-
"
|
| 73 |
-
"
|
| 74 |
-
"
|
| 75 |
-
"\n",
|
| 76 |
-
"
|
| 77 |
-
"
|
| 78 |
-
"
|
| 79 |
-
"
|
| 80 |
-
"
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
"
|
| 84 |
-
"\n",
|
| 85 |
-
"
|
| 86 |
-
"
|
| 87 |
-
"
|
| 88 |
-
"RESULT_DIR
|
| 89 |
-
"
|
| 90 |
-
"\n",
|
| 91 |
-
"
|
| 92 |
-
"
|
| 93 |
-
"\n",
|
| 94 |
-
"print('
|
| 95 |
-
"print('
|
| 96 |
-
"print('Run directory:', RESULT_DIR)"
|
| 97 |
-
]
|
| 98 |
-
},
|
| 99 |
-
{
|
| 100 |
-
"cell_type": "code",
|
| 101 |
-
"execution_count": null,
|
| 102 |
-
"metadata": {},
|
| 103 |
-
"outputs": [],
|
| 104 |
-
"source": [
|
| 105 |
-
"required_files = [\n",
|
| 106 |
-
" TRAIN_SCRIPT,\n",
|
| 107 |
-
"
|
| 108 |
-
"
|
| 109 |
-
"
|
| 110 |
-
"
|
| 111 |
-
"
|
| 112 |
-
"
|
| 113 |
-
"
|
| 114 |
-
"
|
| 115 |
-
"
|
| 116 |
-
"
|
| 117 |
-
"
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
"
|
| 125 |
-
"
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
"
|
| 131 |
-
"
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
"
|
| 144 |
-
"
|
| 145 |
-
"
|
| 146 |
-
"
|
| 147 |
-
"
|
| 148 |
-
"
|
| 149 |
-
"
|
| 150 |
-
"
|
| 151 |
-
"
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
"
|
| 161 |
-
"
|
| 162 |
-
"
|
| 163 |
-
"(
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
"
|
| 171 |
-
"
|
| 172 |
-
"
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
"
|
| 182 |
-
"
|
| 183 |
-
"
|
| 184 |
-
"
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
"
|
| 194 |
-
"
|
| 195 |
-
"
|
| 196 |
-
"
|
| 197 |
-
"
|
| 198 |
-
"
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
"
|
| 206 |
-
"\n",
|
| 207 |
-
"
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
"
|
| 223 |
-
"\n",
|
| 224 |
-
"
|
| 225 |
-
"
|
| 226 |
-
"
|
| 227 |
-
"
|
| 228 |
-
"
|
| 229 |
-
" '
|
| 230 |
-
"
|
| 231 |
-
"
|
| 232 |
-
"
|
| 233 |
-
"
|
| 234 |
-
"
|
| 235 |
-
"
|
| 236 |
-
"
|
| 237 |
-
"
|
| 238 |
-
"
|
| 239 |
-
"
|
| 240 |
-
"
|
| 241 |
-
"
|
| 242 |
-
"
|
| 243 |
-
" '
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
"
|
| 251 |
-
"
|
| 252 |
-
"
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
"
|
| 260 |
-
"
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"print('
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
"
|
| 276 |
-
"
|
| 277 |
-
" )\n",
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
"
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
{
|
| 299 |
-
"
|
| 300 |
-
"
|
| 301 |
-
"
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
"
|
| 306 |
-
"
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
"
|
| 311 |
-
"
|
| 312 |
-
"
|
| 313 |
-
"
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
"else:\n",
|
| 320 |
-
" print('params.csv not found yet:', params_path)"
|
| 321 |
-
]
|
| 322 |
-
},
|
| 323 |
-
{
|
| 324 |
-
"cell_type": "code",
|
| 325 |
-
"execution_count": null,
|
| 326 |
-
"metadata": {},
|
| 327 |
-
"outputs": [],
|
| 328 |
-
"source": [
|
| 329 |
-
"if VERBOSE_OUTPUT.exists():\n",
|
| 330 |
-
" with VERBOSE_OUTPUT.open('r', encoding='utf-8', errors='replace') as handle:\n",
|
| 331 |
-
" for _, line in zip(range(20), handle):\n",
|
| 332 |
-
" print(line.rstrip())\n",
|
| 333 |
-
"else:\n",
|
| 334 |
-
" print('Verbose output not found yet:', VERBOSE_OUTPUT)"
|
| 335 |
-
]
|
| 336 |
-
}
|
| 337 |
-
],
|
| 338 |
-
"metadata": {
|
| 339 |
-
"kernelspec": {
|
| 340 |
-
"display_name": "Python 3",
|
| 341 |
-
"language": "python",
|
| 342 |
-
"name": "python3"
|
| 343 |
-
},
|
| 344 |
-
"language_info": {
|
| 345 |
-
"codemirror_mode": {
|
| 346 |
-
"name": "ipython",
|
| 347 |
-
"version": 3
|
| 348 |
-
},
|
| 349 |
-
"file_extension": ".py",
|
| 350 |
-
"mimetype": "text/x-python",
|
| 351 |
-
"name": "python",
|
| 352 |
-
"nbconvert_exporter": "python",
|
| 353 |
-
"pygments_lexer": "ipython3",
|
| 354 |
-
"version": "3.9"
|
| 355 |
-
}
|
| 356 |
-
},
|
| 357 |
-
"nbformat": 4,
|
| 358 |
-
"nbformat_minor": 5
|
| 359 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Gene perturbation prediction with BioMod model weights\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"Train the gene perturbation model on the local Norman dataset with a BioMod backbone. This notebook prepares the project paths, configures the simulation split, launches training when explicitly enabled, and inspects the saved outputs."
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "markdown",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"## Project paths\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"Start Jupyter from `BioMod/toturials/`. The setup cell uses `Path(\"../..\").resolve()` to select the repository root, following the project-relative path convention used in scGPT tutorials. All code, data, model weight, and output paths are derived from this root and resolved to absolute paths for the training command. If Jupyter starts at the repository root, change that line to `Path(\".\").resolve()`. For another launch location, set `PROJECT_ROOT` to the checkout root."
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "markdown",
|
| 23 |
+
"metadata": {},
|
| 24 |
+
"source": [
|
| 25 |
+
"## Inputs and workflow\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"- Entry point: `BioMod/gene_perturbation/train_module.py`.\n",
|
| 28 |
+
"- Dataset parent: `data/downstream/final_data/`; the loader appends `norman/`.\n",
|
| 29 |
+
"- Processed expression: `norman/perturb_processed.h5ad` under that parent.\n",
|
| 30 |
+
"- Split: `simulation`, seed `1`, training-gene fraction `0.75`.\n",
|
| 31 |
+
"- Model weights and matching configuration: the `.pth` / `.pkl` pair under `weight/0810model/`.\n",
|
| 32 |
+
"- Gene vocabulary, mapping, embeddings, and module definitions: `model_dependencies/`.\n",
|
| 33 |
+
"- Results: `tutorial_runs/gene_perturbation_norman_biomod/`.\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"Use a kernel with the project's gene perturbation dependencies installed. The current training entry point initializes NCCL and requires a Linux CUDA environment, including a suitably configured WSL environment. The notebook can prepare paths on Windows, but native Windows cannot run this NCCL training entry point. Use paths valid inside the selected training environment."
|
| 36 |
+
]
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"cell_type": "code",
|
| 40 |
+
"execution_count": null,
|
| 41 |
+
"metadata": {},
|
| 42 |
+
"outputs": [],
|
| 43 |
+
"source": [
|
| 44 |
+
"from pathlib import Path\n",
|
| 45 |
+
"import json\n",
|
| 46 |
+
"import os\n",
|
| 47 |
+
"import shlex\n",
|
| 48 |
+
"import subprocess\n",
|
| 49 |
+
"import sys\n",
|
| 50 |
+
"import tempfile\n",
|
| 51 |
+
"import pandas as pd\n",
|
| 52 |
+
"try:\n",
|
| 53 |
+
" from IPython.display import display\n",
|
| 54 |
+
"except ImportError:\n",
|
| 55 |
+
" def display(obj):\n",
|
| 56 |
+
" print(obj)\n",
|
| 57 |
+
"\n",
|
| 58 |
+
"PROJECT_ROOT = Path('../..').resolve()\n",
|
| 59 |
+
"if not (PROJECT_ROOT / 'BioMod' / 'models_performer_module.py').is_file() or not (PROJECT_ROOT / 'model_dependencies').is_dir():\n",
|
| 60 |
+
" raise FileNotFoundError(\n",
|
| 61 |
+
" 'Project root not found. Start Jupyter from BioMod/toturials, or set '\n",
|
| 62 |
+
" 'PROJECT_ROOT to your repository root (use Path(\".\").resolve() if '\n",
|
| 63 |
+
" 'Jupyter was started at the repository root).'\n",
|
| 64 |
+
" )\n",
|
| 65 |
+
"\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"SOURCE_DIR = PROJECT_ROOT / 'BioMod'\n",
|
| 68 |
+
"ASSET_ROOT = PROJECT_ROOT / 'model_dependencies'\n",
|
| 69 |
+
"TASK_ROOT = SOURCE_DIR / 'gene_perturbation'\n",
|
| 70 |
+
"TRAIN_SCRIPT = TASK_ROOT / 'train_module.py'\n",
|
| 71 |
+
"DATA_DIR = PROJECT_ROOT / 'data' / 'downstream' / 'final_data'\n",
|
| 72 |
+
"DATA_NAME = 'norman'\n",
|
| 73 |
+
"SPLIT = 'simulation'\n",
|
| 74 |
+
"SEED = 1\n",
|
| 75 |
+
"TRAIN_GENE_SET_SIZE = 0.75\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"# Keep the BioMod model weights and their matching configuration together.\n",
|
| 78 |
+
"WEIGHT_DIR = PROJECT_ROOT / 'weight' / '0810model'\n",
|
| 79 |
+
"SCFM_HYPER = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2.pkl'\n",
|
| 80 |
+
"SCFM_CKPT = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2_epoch0.pth'\n",
|
| 81 |
+
"SCFM_GENES = ASSET_ROOT / 'selected_genes_27k.txt'\n",
|
| 82 |
+
"SCFM_GENE2VEC = ASSET_ROOT / 'selected_gene2vec_27k.npy'\n",
|
| 83 |
+
"GENE_MAP = ASSET_ROOT / 'gene2ens.csv'\n",
|
| 84 |
+
"MODULE_DATA_DIR = ASSET_ROOT / 'module_data_all'\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"OUTPUT_PARENT = PROJECT_ROOT / 'tutorial_runs'\n",
|
| 87 |
+
"RUN_NAME = 'gene_perturbation_norman_biomod'\n",
|
| 88 |
+
"RESULT_DIR = OUTPUT_PARENT / RUN_NAME\n",
|
| 89 |
+
"RUN_TRAINING = False\n",
|
| 90 |
+
"SPLIT_FILE = DATA_DIR / DATA_NAME / 'splits' / f'{DATA_NAME}_{SPLIT}_{SEED}_{TRAIN_GENE_SET_SIZE}.pkl'\n",
|
| 91 |
+
"TARGET_H5AD = DATA_DIR / DATA_NAME / 'perturb_processed.h5ad'\n",
|
| 92 |
+
"\n",
|
| 93 |
+
"print('Python:', sys.executable)\n",
|
| 94 |
+
"print('Repository:', PROJECT_ROOT)\n",
|
| 95 |
+
"print('Dataset:', TARGET_H5AD)\n",
|
| 96 |
+
"print('Run directory:', RESULT_DIR)"
|
| 97 |
+
]
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"cell_type": "code",
|
| 101 |
+
"execution_count": null,
|
| 102 |
+
"metadata": {},
|
| 103 |
+
"outputs": [],
|
| 104 |
+
"source": [
|
| 105 |
+
"required_files = [\n",
|
| 106 |
+
" TRAIN_SCRIPT, TARGET_H5AD, SPLIT_FILE, SCFM_HYPER, SCFM_CKPT,\n",
|
| 107 |
+
" SCFM_GENES, SCFM_GENE2VEC, GENE_MAP,\n",
|
| 108 |
+
" SOURCE_DIR / 'models_performer_module.py',\n",
|
| 109 |
+
" MODULE_DATA_DIR / 'merged_modules.json',\n",
|
| 110 |
+
"]\n",
|
| 111 |
+
"required_dirs = [TASK_ROOT, ASSET_ROOT, DATA_DIR / DATA_NAME / 'data_pyg']\n",
|
| 112 |
+
"missing = [str(p) for p in required_files if not p.is_file()]\n",
|
| 113 |
+
"missing += [str(p) for p in required_dirs if not p.is_dir()]\n",
|
| 114 |
+
"if missing:\n",
|
| 115 |
+
" raise FileNotFoundError('Missing required paths:\\n' + '\\n'.join(missing))\n",
|
| 116 |
+
"for path in required_files:\n",
|
| 117 |
+
" print('[FOUND]', path)"
|
| 118 |
+
]
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"cell_type": "markdown",
|
| 122 |
+
"metadata": {},
|
| 123 |
+
"source": [
|
| 124 |
+
"## Runtime setup\n",
|
| 125 |
+
"\n",
|
| 126 |
+
"The current source imports `BioMod.models_performer_module` directly. The child process uses the repository and bundled dependency directories on `PYTHONPATH`; no temporary package or symlink is needed. The notebook leaves the kernel environment unchanged."
|
| 127 |
+
]
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"cell_type": "code",
|
| 131 |
+
"execution_count": null,
|
| 132 |
+
"metadata": {},
|
| 133 |
+
"outputs": [],
|
| 134 |
+
"source": [
|
| 135 |
+
"run_env = os.environ.copy()\n",
|
| 136 |
+
"run_env['CUDA_VISIBLE_DEVICES'] = '0'\n",
|
| 137 |
+
"run_env['PYTHONUNBUFFERED'] = '1'\n",
|
| 138 |
+
"run_env.setdefault('OMP_NUM_THREADS', '1')\n",
|
| 139 |
+
"run_env.setdefault('NUMBA_NUM_THREADS', '1')\n",
|
| 140 |
+
"run_env['NUMBA_DISABLE_JIT'] = '1'\n",
|
| 141 |
+
"run_env.setdefault('NUMBA_CACHE_DIR', str(Path(tempfile.gettempdir()) / f'biomod_perturbation_{os.getpid()}'))\n",
|
| 142 |
+
"run_env['WANDB_MODE'] = 'offline'\n",
|
| 143 |
+
"run_env.setdefault('TORCH_NCCL_ASYNC_ERROR_HANDLING', '1')\n",
|
| 144 |
+
"run_env.pop('NCCL_ASYNC_ERROR_HANDLING', None)\n",
|
| 145 |
+
"run_env.setdefault('NCCL_DEBUG', 'WARN')\n",
|
| 146 |
+
"pythonpath_parts = [str(TASK_ROOT), str(PROJECT_ROOT), str(SOURCE_DIR), str(ASSET_ROOT)]\n",
|
| 147 |
+
"if run_env.get('PYTHONPATH'):\n",
|
| 148 |
+
" pythonpath_parts.append(run_env['PYTHONPATH'])\n",
|
| 149 |
+
"run_env['PYTHONPATH'] = os.pathsep.join(pythonpath_parts)\n",
|
| 150 |
+
"print('CUDA_VISIBLE_DEVICES =', run_env['CUDA_VISIBLE_DEVICES'])\n",
|
| 151 |
+
"print('PYTHONPATH =', run_env['PYTHONPATH'])"
|
| 152 |
+
]
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"cell_type": "code",
|
| 156 |
+
"execution_count": null,
|
| 157 |
+
"metadata": {},
|
| 158 |
+
"outputs": [],
|
| 159 |
+
"source": [
|
| 160 |
+
"# File checks do not load the model weights or deserialize the configuration.\n",
|
| 161 |
+
"print('Backbone source:', SOURCE_DIR / 'models_performer_module.py')\n",
|
| 162 |
+
"print('Bundled Performer:', ASSET_ROOT / 'performer_pytorch_cont')\n",
|
| 163 |
+
"print('Training enabled:', RUN_TRAINING)"
|
| 164 |
+
]
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"cell_type": "markdown",
|
| 168 |
+
"metadata": {},
|
| 169 |
+
"source": [
|
| 170 |
+
"## Training command\n",
|
| 171 |
+
"\n",
|
| 172 |
+
"`train_module.py` initializes distributed training even for one GPU. The command therefore uses `python -m torch.distributed.run --standalone --nproc_per_node=1`. Review the settings below, then set `RUN_TRAINING = True` in the setup cell to launch. The training script selects a checkpoint using validation MSE on the top 20 differentially expressed genes, then evaluates the selected checkpoint on the test split. Batch sizes, epoch count, and learning rate are configurable examples, not a verified benchmark recipe."
|
| 173 |
+
]
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"cell_type": "code",
|
| 177 |
+
"execution_count": null,
|
| 178 |
+
"metadata": {},
|
| 179 |
+
"outputs": [],
|
| 180 |
+
"source": [
|
| 181 |
+
"EPOCHS = 1\n",
|
| 182 |
+
"BATCH_SIZE = 16\n",
|
| 183 |
+
"TEST_BATCH_SIZE = 32\n",
|
| 184 |
+
"HIDDEN_SIZE = 1024\n",
|
| 185 |
+
"ACCUMULATION_STEPS = 8\n",
|
| 186 |
+
"LR = '1e-3'\n",
|
| 187 |
+
"\n",
|
| 188 |
+
"cmd = [\n",
|
| 189 |
+
" sys.executable, '-m', 'torch.distributed.run',\n",
|
| 190 |
+
" '--standalone', '--nnodes=1', '--nproc_per_node=1',\n",
|
| 191 |
+
" str(TRAIN_SCRIPT),\n",
|
| 192 |
+
" '--data_dir', str(DATA_DIR),\n",
|
| 193 |
+
" '--data_name', DATA_NAME,\n",
|
| 194 |
+
" '--split', SPLIT,\n",
|
| 195 |
+
" '--result_dir', str(RESULT_DIR),\n",
|
| 196 |
+
" '--seed', str(SEED),\n",
|
| 197 |
+
" '--epochs', str(EPOCHS),\n",
|
| 198 |
+
" '--batch_size', str(BATCH_SIZE),\n",
|
| 199 |
+
" '--test_batch_size', str(TEST_BATCH_SIZE),\n",
|
| 200 |
+
" '--train_gene_set_size', str(TRAIN_GENE_SET_SIZE),\n",
|
| 201 |
+
" '--hidden_size', str(HIDDEN_SIZE),\n",
|
| 202 |
+
" '--run_name', RUN_NAME,\n",
|
| 203 |
+
" '--scfm_hyper_params_path', str(SCFM_HYPER),\n",
|
| 204 |
+
" '--scfm_ckpt_path', str(SCFM_CKPT),\n",
|
| 205 |
+
" '--scfm_genes_list_path', str(SCFM_GENES),\n",
|
| 206 |
+
" '--gene_mapping_path', str(GENE_MAP),\n",
|
| 207 |
+
" '--scfm_gene2vec_file', str(SCFM_GENE2VEC),\n",
|
| 208 |
+
" '--accumulation_steps', str(ACCUMULATION_STEPS),\n",
|
| 209 |
+
" '--module_data_dir', str(MODULE_DATA_DIR),\n",
|
| 210 |
+
" '--lr', LR,\n",
|
| 211 |
+
"]\n",
|
| 212 |
+
"print('Command:')\n",
|
| 213 |
+
"print(subprocess.list2cmdline(cmd) if os.name == 'nt' else shlex.join(cmd))"
|
| 214 |
+
]
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
"cell_type": "code",
|
| 218 |
+
"execution_count": null,
|
| 219 |
+
"metadata": {},
|
| 220 |
+
"outputs": [],
|
| 221 |
+
"source": [
|
| 222 |
+
"log_path = RESULT_DIR / 'training.log'\n",
|
| 223 |
+
"if not RUN_TRAINING:\n",
|
| 224 |
+
" print('Training is disabled. Review the paths and settings, then set RUN_TRAINING = True to launch.')\n",
|
| 225 |
+
"else:\n",
|
| 226 |
+
" if sys.platform != 'linux':\n",
|
| 227 |
+
" raise RuntimeError('The training script requires Linux with CUDA/NCCL. Use a compatible kernel and project paths.')\n",
|
| 228 |
+
" RESULT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 229 |
+
" Path(run_env['NUMBA_CACHE_DIR']).mkdir(parents=True, exist_ok=True)\n",
|
| 230 |
+
" print('Writing log to:', log_path)\n",
|
| 231 |
+
" with log_path.open('w', encoding='utf-8') as log:\n",
|
| 232 |
+
" process = subprocess.Popen(\n",
|
| 233 |
+
" cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n",
|
| 234 |
+
" text=True, encoding='utf-8', errors='replace', bufsize=1,\n",
|
| 235 |
+
" cwd=str(TASK_ROOT), env=run_env,\n",
|
| 236 |
+
" )\n",
|
| 237 |
+
" for line in process.stdout:\n",
|
| 238 |
+
" print(line, end='')\n",
|
| 239 |
+
" log.write(line)\n",
|
| 240 |
+
" return_code = process.wait()\n",
|
| 241 |
+
" if return_code != 0:\n",
|
| 242 |
+
" raise RuntimeError(f'Gene perturbation training failed with return code {return_code}; see {log_path}')\n",
|
| 243 |
+
" print('Training finished.')"
|
| 244 |
+
]
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"cell_type": "markdown",
|
| 248 |
+
"metadata": {},
|
| 249 |
+
"source": [
|
| 250 |
+
"## Inspect outputs\n",
|
| 251 |
+
"\n",
|
| 252 |
+
"A successful run saves the selected model as `model.pt`, its configuration as `config.pkl`, and the command parameters as `params.csv`. The notebook captures console metrics in `training.log`; the current script does not provide a separate verbose-output argument. These outputs are written to the run directory and do not overwrite the input BioMod model weights. Result cells can also inspect a previous completed run."
|
| 253 |
+
]
|
| 254 |
+
},
|
| 255 |
+
{
|
| 256 |
+
"cell_type": "code",
|
| 257 |
+
"execution_count": null,
|
| 258 |
+
"metadata": {},
|
| 259 |
+
"outputs": [],
|
| 260 |
+
"source": [
|
| 261 |
+
"if not RESULT_DIR.is_dir():\n",
|
| 262 |
+
" print('No run directory yet:', RESULT_DIR)\n",
|
| 263 |
+
"else:\n",
|
| 264 |
+
" for path in sorted(RESULT_DIR.iterdir()):\n",
|
| 265 |
+
" size_mb = path.stat().st_size / 1024 / 1024 if path.is_file() else 0\n",
|
| 266 |
+
" print(f'{path.name:45s} {size_mb:8.2f} MB')"
|
| 267 |
+
]
|
| 268 |
+
},
|
| 269 |
+
{
|
| 270 |
+
"cell_type": "code",
|
| 271 |
+
"execution_count": null,
|
| 272 |
+
"metadata": {},
|
| 273 |
+
"outputs": [],
|
| 274 |
+
"source": [
|
| 275 |
+
"params_path = RESULT_DIR / 'params.csv'\n",
|
| 276 |
+
"if params_path.is_file():\n",
|
| 277 |
+
" display(pd.read_csv(params_path, index_col=0))\n",
|
| 278 |
+
"else:\n",
|
| 279 |
+
" print('Parameters are not available yet:', params_path)"
|
| 280 |
+
]
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"cell_type": "code",
|
| 284 |
+
"execution_count": null,
|
| 285 |
+
"metadata": {},
|
| 286 |
+
"outputs": [],
|
| 287 |
+
"source": [
|
| 288 |
+
"if log_path.is_file():\n",
|
| 289 |
+
" from collections import deque\n",
|
| 290 |
+
" with log_path.open('r', encoding='utf-8', errors='replace') as handle:\n",
|
| 291 |
+
" print(''.join(deque(handle, maxlen=40)))\n",
|
| 292 |
+
"else:\n",
|
| 293 |
+
" print('Training log is not available yet:', log_path)"
|
| 294 |
+
]
|
| 295 |
+
}
|
| 296 |
+
],
|
| 297 |
+
"metadata": {
|
| 298 |
+
"kernelspec": {
|
| 299 |
+
"display_name": "Python 3",
|
| 300 |
+
"language": "python",
|
| 301 |
+
"name": "python3"
|
| 302 |
+
},
|
| 303 |
+
"language_info": {
|
| 304 |
+
"codemirror_mode": {
|
| 305 |
+
"name": "ipython",
|
| 306 |
+
"version": 3
|
| 307 |
+
},
|
| 308 |
+
"file_extension": ".py",
|
| 309 |
+
"mimetype": "text/x-python",
|
| 310 |
+
"name": "python",
|
| 311 |
+
"nbconvert_exporter": "python",
|
| 312 |
+
"pygments_lexer": "ipython3",
|
| 313 |
+
"version": "3.9"
|
| 314 |
+
}
|
| 315 |
+
},
|
| 316 |
+
"nbformat": 4,
|
| 317 |
+
"nbformat_minor": 5
|
| 318 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BioMod/toturials/zero_shot_batch_integration.ipynb
CHANGED
|
@@ -1,351 +1,340 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# Zero-shot batch integration
|
| 8 |
-
"\n",
|
| 9 |
-
"This notebook
|
| 10 |
-
]
|
| 11 |
-
},
|
| 12 |
-
{
|
| 13 |
-
"cell_type": "markdown",
|
| 14 |
-
"metadata": {},
|
| 15 |
-
"source": [
|
| 16 |
-
"##
|
| 17 |
-
"\n",
|
| 18 |
-
"
|
| 19 |
-
"
|
| 20 |
-
"-
|
| 21 |
-
"-
|
| 22 |
-
"-
|
| 23 |
-
"\n",
|
| 24 |
-
"
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
"
|
| 31 |
-
"
|
| 32 |
-
"
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
"import
|
| 36 |
-
"import
|
| 37 |
-
"import
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
"
|
| 41 |
-
"
|
| 42 |
-
"
|
| 43 |
-
"
|
| 44 |
-
"
|
| 45 |
-
"\n",
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"\n",
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"\n",
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
"\n",
|
| 56 |
-
"
|
| 57 |
-
"
|
| 58 |
-
"
|
| 59 |
-
"
|
| 60 |
-
"
|
| 61 |
-
"\n",
|
| 62 |
-
"
|
| 63 |
-
"
|
| 64 |
-
"
|
| 65 |
-
"\n",
|
| 66 |
-
"
|
| 67 |
-
"\n",
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
"
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
"
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
-
"
|
| 87 |
-
"
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
"
|
| 97 |
-
"\n",
|
| 98 |
-
"
|
| 99 |
-
"
|
| 100 |
-
"
|
| 101 |
-
"
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
"
|
| 109 |
-
"
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
"
|
| 114 |
-
"
|
| 115 |
-
"
|
| 116 |
-
"\n",
|
| 117 |
-
"
|
| 118 |
-
"
|
| 119 |
-
"
|
| 120 |
-
"
|
| 121 |
-
"
|
| 122 |
-
"
|
| 123 |
-
"\n"
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
"
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
"
|
| 137 |
-
"
|
| 138 |
-
"
|
| 139 |
-
"
|
| 140 |
-
"
|
| 141 |
-
"
|
| 142 |
-
"
|
| 143 |
-
"
|
| 144 |
-
"
|
| 145 |
-
"\n",
|
| 146 |
-
"
|
| 147 |
-
"
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
"
|
| 165 |
-
"
|
| 166 |
-
" '
|
| 167 |
-
"
|
| 168 |
-
"
|
| 169 |
-
" '
|
| 170 |
-
"
|
| 171 |
-
"
|
| 172 |
-
"
|
| 173 |
-
"
|
| 174 |
-
"
|
| 175 |
-
"
|
| 176 |
-
"
|
| 177 |
-
"
|
| 178 |
-
"
|
| 179 |
-
"
|
| 180 |
-
"
|
| 181 |
-
"
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
"
|
| 189 |
-
"
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
"
|
| 194 |
-
"
|
| 195 |
-
"\n",
|
| 196 |
-
"
|
| 197 |
-
"
|
| 198 |
-
"
|
| 199 |
-
"
|
| 200 |
-
"
|
| 201 |
-
"
|
| 202 |
-
"
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
"
|
| 212 |
-
"\n",
|
| 213 |
-
"
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
"
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
"
|
| 251 |
-
"
|
| 252 |
-
"
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
"
|
| 260 |
-
"\n",
|
| 261 |
-
"
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
"
|
| 272 |
-
"
|
| 273 |
-
"
|
| 274 |
-
"
|
| 275 |
-
"\n",
|
| 276 |
-
"
|
| 277 |
-
"
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
"
|
| 281 |
-
"
|
| 282 |
-
"
|
| 283 |
-
"
|
| 284 |
-
"
|
| 285 |
-
"
|
| 286 |
-
"
|
| 287 |
-
"\n",
|
| 288 |
-
"
|
| 289 |
-
"
|
| 290 |
-
"
|
| 291 |
-
"
|
| 292 |
-
"
|
| 293 |
-
"
|
| 294 |
-
"
|
| 295 |
-
"
|
| 296 |
-
"
|
| 297 |
-
"
|
| 298 |
-
"
|
| 299 |
-
"
|
| 300 |
-
"
|
| 301 |
-
"
|
| 302 |
-
"
|
| 303 |
-
"
|
| 304 |
-
"
|
| 305 |
-
"
|
| 306 |
-
"
|
| 307 |
-
"\n",
|
| 308 |
-
"
|
| 309 |
-
"
|
| 310 |
-
"\n",
|
| 311 |
-
"
|
| 312 |
-
"
|
| 313 |
-
"
|
| 314 |
-
"
|
| 315 |
-
"
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
"
|
| 333 |
-
"
|
| 334 |
-
"
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
"file_extension": ".py",
|
| 342 |
-
"mimetype": "text/x-python",
|
| 343 |
-
"name": "python",
|
| 344 |
-
"nbconvert_exporter": "python",
|
| 345 |
-
"pygments_lexer": "ipython3",
|
| 346 |
-
"version": "3.9"
|
| 347 |
-
}
|
| 348 |
-
},
|
| 349 |
-
"nbformat": 4,
|
| 350 |
-
"nbformat_minor": 5
|
| 351 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Zero-shot batch integration: Kim2020 Lung\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook extracts cell embeddings with the BioMod model weights, evaluates batch integration with the bundled runner, and optionally plots UMAPs. It uses the full `Kim2020_Lung_noMalignant.h5ad` dataset. Evaluation does not train or update the pretrained model.\n"
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "markdown",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"## Project paths and environment\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"Start Jupyter with `BioMod/toturials` as its working directory and select the environment described in [the project README](../../README.md). Paths below are relative to this notebook directory, following the project-relative layout used in scGPT tutorials. `Path.resolve()` turns them into absolute paths for subprocesses. If Jupyter was started at the repository root, set `PROJECT_ROOT = Path('.').resolve()` instead.\n",
|
| 19 |
+
"\n",
|
| 20 |
+
"- Data: `../../data/downstream/final_data/Kim2020_Lung_noMalignant.h5ad`\n",
|
| 21 |
+
"- BioMod model weights and configuration: `../../weight/0810model/`\n",
|
| 22 |
+
"- Shared assets: `../../model_dependencies/`\n",
|
| 23 |
+
"- Runner: `../zero_shot_batch/performer_module_zero_shot.py`\n",
|
| 24 |
+
"- Results: `../../tutorial_runs/zero_shot_kim_lung_no_malignant/`\n",
|
| 25 |
+
"\n",
|
| 26 |
+
"Review the configuration, then set `RUN_EVALUATION = True` to run inference. The runner consumes `adata.X` without normalization or log transformation; check that its expression preprocessing is appropriate for the checkpoint and your experiment. It loads the input dataset into memory before batching inference.\n"
|
| 27 |
+
]
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"cell_type": "code",
|
| 31 |
+
"execution_count": null,
|
| 32 |
+
"metadata": {},
|
| 33 |
+
"outputs": [],
|
| 34 |
+
"source": [
|
| 35 |
+
"from pathlib import Path\n",
|
| 36 |
+
"import importlib.util\n",
|
| 37 |
+
"import os\n",
|
| 38 |
+
"import subprocess\n",
|
| 39 |
+
"import sys\n",
|
| 40 |
+
"\n",
|
| 41 |
+
"PROJECT_ROOT = Path('../..').resolve()\n",
|
| 42 |
+
"if not (PROJECT_ROOT / 'BioMod').is_dir() or not (PROJECT_ROOT / 'model_dependencies').is_dir():\n",
|
| 43 |
+
" raise FileNotFoundError(\n",
|
| 44 |
+
" 'Start Jupyter from BioMod/toturials, or set PROJECT_ROOT to the repository root '\n",
|
| 45 |
+
" \"(use Path('.').resolve() when launched at the repository root).\"\n",
|
| 46 |
+
" )\n",
|
| 47 |
+
"SOURCE_DIR = PROJECT_ROOT / 'BioMod'\n",
|
| 48 |
+
"DEPENDENCIES_DIR = PROJECT_ROOT / 'model_dependencies'\n",
|
| 49 |
+
"SCRIPT = SOURCE_DIR / 'zero_shot_batch' / 'performer_module_zero_shot.py'\n",
|
| 50 |
+
"DATA_PATH = PROJECT_ROOT / 'data' / 'downstream' / 'final_data' / 'Kim2020_Lung_noMalignant.h5ad'\n",
|
| 51 |
+
"WEIGHT_DIR = PROJECT_ROOT / 'weight' / '0810model'\n",
|
| 52 |
+
"CONFIG_PKL = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2.pkl'\n",
|
| 53 |
+
"CKPT_PATH = WEIGHT_DIR / 'mae_tost_70M_75mask_ec2_epoch0.pth'\n",
|
| 54 |
+
"GENE2VEC = DEPENDENCIES_DIR / 'selected_gene2vec_27k.npy'\n",
|
| 55 |
+
"GENE_MAP = DEPENDENCIES_DIR / 'gene2ens.csv'\n",
|
| 56 |
+
"GENE_LIST = DEPENDENCIES_DIR / 'selected_genes_27k.txt'\n",
|
| 57 |
+
"MODULE_DATA_DIR = DEPENDENCIES_DIR / 'module_data_all'\n",
|
| 58 |
+
"OUT_DIR = PROJECT_ROOT / 'tutorial_runs' / 'zero_shot_kim_lung_no_malignant'\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"RUN_EVALUATION = False\n",
|
| 61 |
+
"MAKE_UMAP = False\n",
|
| 62 |
+
"N_CELLS = 'all'\n",
|
| 63 |
+
"BATCH_SIZE = 8\n",
|
| 64 |
+
"POOL_METHOD = 'max'\n",
|
| 65 |
+
"METRICS_MODE = 'scgpt'\n",
|
| 66 |
+
"UMAP_RANDOM_STATE = 0\n",
|
| 67 |
+
"\n",
|
| 68 |
+
"print('Python:', sys.executable)\n",
|
| 69 |
+
"print('Repository root:', PROJECT_ROOT)\n",
|
| 70 |
+
"print('Output directory:', OUT_DIR)\n"
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"cell_type": "code",
|
| 75 |
+
"execution_count": null,
|
| 76 |
+
"metadata": {},
|
| 77 |
+
"outputs": [],
|
| 78 |
+
"source": [
|
| 79 |
+
"required_files = [SCRIPT, DATA_PATH, CONFIG_PKL, CKPT_PATH, GENE2VEC, GENE_MAP, GENE_LIST,\n",
|
| 80 |
+
" MODULE_DATA_DIR / 'merged_modules.json']\n",
|
| 81 |
+
"required_dirs = [DEPENDENCIES_DIR / 'performer_pytorch_cont']\n",
|
| 82 |
+
"missing = [str(p) for p in required_files if not p.is_file()]\n",
|
| 83 |
+
"missing += [str(p) for p in required_dirs if not p.is_dir()]\n",
|
| 84 |
+
"if missing:\n",
|
| 85 |
+
" raise FileNotFoundError('Missing required project paths:\\n' + '\\n'.join(missing))\n",
|
| 86 |
+
"for p in required_files + required_dirs:\n",
|
| 87 |
+
" print('[FOUND]', p)\n"
|
| 88 |
+
]
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"cell_type": "code",
|
| 92 |
+
"execution_count": null,
|
| 93 |
+
"metadata": {},
|
| 94 |
+
"outputs": [],
|
| 95 |
+
"source": [
|
| 96 |
+
"# Configure only the child process; retain the active kernel's Python environment.\n",
|
| 97 |
+
"run_env = os.environ.copy()\n",
|
| 98 |
+
"run_env['PYTHONUNBUFFERED'] = '1'\n",
|
| 99 |
+
"run_env.setdefault('OMP_NUM_THREADS', '8')\n",
|
| 100 |
+
"pythonpath_parts = [str(SOURCE_DIR), str(DEPENDENCIES_DIR)]\n",
|
| 101 |
+
"if run_env.get('PYTHONPATH'):\n",
|
| 102 |
+
" pythonpath_parts.append(run_env['PYTHONPATH'])\n",
|
| 103 |
+
"run_env['PYTHONPATH'] = os.pathsep.join(pythonpath_parts)\n",
|
| 104 |
+
"print('PYTHONPATH:', run_env['PYTHONPATH'])\n"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"cell_type": "code",
|
| 109 |
+
"execution_count": null,
|
| 110 |
+
"metadata": {},
|
| 111 |
+
"outputs": [],
|
| 112 |
+
"source": [
|
| 113 |
+
"# Install project dependencies in the selected kernel environment before evaluation.\n",
|
| 114 |
+
"required_modules = ['numpy', 'pandas', 'torch', 'scanpy', 'scib', 'einops']\n",
|
| 115 |
+
"missing_modules = [name for name in required_modules if importlib.util.find_spec(name) is None]\n",
|
| 116 |
+
"if missing_modules:\n",
|
| 117 |
+
" message = ('Missing dependencies: ' + ', '.join(missing_modules)\n",
|
| 118 |
+
" + '. Follow the installation instructions in the project README.')\n",
|
| 119 |
+
" if RUN_EVALUATION:\n",
|
| 120 |
+
" raise ImportError(message)\n",
|
| 121 |
+
" print(message)\n",
|
| 122 |
+
"else:\n",
|
| 123 |
+
" print('Core evaluation dependencies are available.')\n"
|
| 124 |
+
]
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"cell_type": "code",
|
| 128 |
+
"execution_count": null,
|
| 129 |
+
"metadata": {},
|
| 130 |
+
"outputs": [],
|
| 131 |
+
"source": [
|
| 132 |
+
"cmd = [\n",
|
| 133 |
+
" sys.executable, str(SCRIPT),\n",
|
| 134 |
+
" '--data_path', str(DATA_PATH),\n",
|
| 135 |
+
" '--gene2vec_path', str(GENE2VEC),\n",
|
| 136 |
+
" '--scfm_hyper_params_path', str(CONFIG_PKL),\n",
|
| 137 |
+
" '--scfm_ckpt_path', str(CKPT_PATH),\n",
|
| 138 |
+
" '--module_data_dir', str(MODULE_DATA_DIR),\n",
|
| 139 |
+
" '--gene_mapping_path', str(GENE_MAP),\n",
|
| 140 |
+
" '--scfm_genes_file', str(GENE_LIST),\n",
|
| 141 |
+
" '--output_dir', str(OUT_DIR),\n",
|
| 142 |
+
" '--dataset_preset', 'lung',\n",
|
| 143 |
+
" '--gene_col', 'gene_name',\n",
|
| 144 |
+
" '--batch_key', 'sample',\n",
|
| 145 |
+
" '--label_key', 'cell_type',\n",
|
| 146 |
+
" '--output_key', 'X_sclong_module',\n",
|
| 147 |
+
" '--pool_method', POOL_METHOD,\n",
|
| 148 |
+
" '--metrics_mode', METRICS_MODE,\n",
|
| 149 |
+
" '--n_cells', N_CELLS,\n",
|
| 150 |
+
" '--batch_size', str(BATCH_SIZE),\n",
|
| 151 |
+
" '--min_genes_per_cell', '0',\n",
|
| 152 |
+
" '--min_cells_per_gene', '0',\n",
|
| 153 |
+
"]\n",
|
| 154 |
+
"print('Command arguments:')\n",
|
| 155 |
+
"print(cmd)\n"
|
| 156 |
+
]
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"cell_type": "code",
|
| 160 |
+
"execution_count": null,
|
| 161 |
+
"metadata": {},
|
| 162 |
+
"outputs": [],
|
| 163 |
+
"source": [
|
| 164 |
+
"log_path = OUT_DIR / 'zero_shot.log'\n",
|
| 165 |
+
"if not RUN_EVALUATION:\n",
|
| 166 |
+
" print('Evaluation is disabled. Set RUN_EVALUATION = True and rerun the configuration cells to start.')\n",
|
| 167 |
+
"else:\n",
|
| 168 |
+
" OUT_DIR.mkdir(parents=True, exist_ok=True)\n",
|
| 169 |
+
" with log_path.open('w', encoding='utf-8') as log:\n",
|
| 170 |
+
" process = subprocess.Popen(\n",
|
| 171 |
+
" cmd, cwd=SCRIPT.parent, env=run_env,\n",
|
| 172 |
+
" stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n",
|
| 173 |
+
" text=True, encoding='utf-8', errors='replace', bufsize=1,\n",
|
| 174 |
+
" )\n",
|
| 175 |
+
" for line in process.stdout:\n",
|
| 176 |
+
" print(line, end='')\n",
|
| 177 |
+
" log.write(line)\n",
|
| 178 |
+
" return_code = process.wait()\n",
|
| 179 |
+
" if return_code != 0:\n",
|
| 180 |
+
" raise RuntimeError(f'Evaluation failed with code {return_code}; see {log_path}')\n",
|
| 181 |
+
" print('Evaluation completed:', OUT_DIR)\n"
|
| 182 |
+
]
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"cell_type": "code",
|
| 186 |
+
"execution_count": null,
|
| 187 |
+
"metadata": {},
|
| 188 |
+
"outputs": [],
|
| 189 |
+
"source": [
|
| 190 |
+
"metrics_path = OUT_DIR / 'sclong_module_batch_cell_emb_mode.csv'\n",
|
| 191 |
+
"embedding_path = OUT_DIR / 'sclong_module_batch_cell_emb_mode_ce.npy'\n",
|
| 192 |
+
"metrics = None\n",
|
| 193 |
+
"if metrics_path.is_file():\n",
|
| 194 |
+
" import pandas as pd\n",
|
| 195 |
+
" from IPython.display import display\n",
|
| 196 |
+
" metrics = pd.read_csv(metrics_path)\n",
|
| 197 |
+
" required_columns = {'metric', 'label', 'value'}\n",
|
| 198 |
+
" if not required_columns.issubset(metrics.columns):\n",
|
| 199 |
+
" raise ValueError(f'Unexpected metrics columns: {list(metrics.columns)}')\n",
|
| 200 |
+
" display(metrics)\n",
|
| 201 |
+
"else:\n",
|
| 202 |
+
" print('No evaluation metrics yet:', metrics_path)\n"
|
| 203 |
+
]
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"cell_type": "code",
|
| 207 |
+
"execution_count": null,
|
| 208 |
+
"metadata": {},
|
| 209 |
+
"outputs": [],
|
| 210 |
+
"source": [
|
| 211 |
+
"if metrics is not None:\n",
|
| 212 |
+
" # The bundled scGPT metric mode reports these two aggregates; it does not emit avg_score.\n",
|
| 213 |
+
" avg_metrics = metrics[metrics['metric'].isin(['avg_batch', 'avg_bio'])].copy()\n",
|
| 214 |
+
" if avg_metrics.empty:\n",
|
| 215 |
+
" print('No aggregate metrics in this result; inspect the individual metrics above.')\n",
|
| 216 |
+
" else:\n",
|
| 217 |
+
" display(avg_metrics)\n"
|
| 218 |
+
]
|
| 219 |
+
},
|
| 220 |
+
{
|
| 221 |
+
"cell_type": "code",
|
| 222 |
+
"execution_count": null,
|
| 223 |
+
"metadata": {},
|
| 224 |
+
"outputs": [],
|
| 225 |
+
"source": [
|
| 226 |
+
"print('Metrics:', metrics_path)\n",
|
| 227 |
+
"print('Cell embeddings:', embedding_path)\n",
|
| 228 |
+
"print('Run log:', log_path)\n",
|
| 229 |
+
"print('The runner saves CSV/NPY results. UMAP figures are created by the optional cell below.')\n"
|
| 230 |
+
]
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"cell_type": "markdown",
|
| 234 |
+
"metadata": {},
|
| 235 |
+
"source": [
|
| 236 |
+
"## Optional: plot UMAP and export a PDF/PNG panel\n",
|
| 237 |
+
"\n",
|
| 238 |
+
"After a successful evaluation, set `MAKE_UMAP = True` in the configuration cell. The following cell combines the saved cell embeddings with the input dataset's observation metadata. It requires the full dataset (`N_CELLS = 'all'`) with no cell filtering, as configured above, so the rows retain the input order. Use embeddings from this dataset and this configuration; the runner does not save cell identifiers alongside the NPY array. Subsampled or filtered embeddings require their matching observation metadata and cannot be plotted by this cell.\n",
|
| 239 |
+
"\n",
|
| 240 |
+
"The optional plot requires Scanpy and Matplotlib. It computes UMAP from the embeddings and writes `umap_a4.pdf`, `umap_a4.png`, and `umap.h5ad` to the result directory.\n"
|
| 241 |
+
]
|
| 242 |
+
},
|
| 243 |
+
{
|
| 244 |
+
"cell_type": "code",
|
| 245 |
+
"execution_count": null,
|
| 246 |
+
"id": "b67f908e",
|
| 247 |
+
"metadata": {},
|
| 248 |
+
"outputs": [],
|
| 249 |
+
"source": [
|
| 250 |
+
"if not MAKE_UMAP:\n",
|
| 251 |
+
" print('UMAP export is disabled. Set MAKE_UMAP = True after evaluation.')\n",
|
| 252 |
+
"elif not embedding_path.is_file():\n",
|
| 253 |
+
" print('No cell embeddings yet:', embedding_path)\n",
|
| 254 |
+
"else:\n",
|
| 255 |
+
" if N_CELLS != 'all':\n",
|
| 256 |
+
" raise ValueError('UMAP export requires the full, unfiltered dataset in input row order.')\n",
|
| 257 |
+
" import math\n",
|
| 258 |
+
" import numpy as np\n",
|
| 259 |
+
" import matplotlib.pyplot as plt\n",
|
| 260 |
+
" import scanpy as sc\n",
|
| 261 |
+
" from IPython.display import Image, display\n",
|
| 262 |
+
"\n",
|
| 263 |
+
" source = sc.read_h5ad(DATA_PATH, backed='r')\n",
|
| 264 |
+
" try:\n",
|
| 265 |
+
" obs = source.obs.copy()\n",
|
| 266 |
+
" finally:\n",
|
| 267 |
+
" source.file.close()\n",
|
| 268 |
+
" embeddings = np.load(embedding_path, allow_pickle=False)\n",
|
| 269 |
+
" if embeddings.ndim != 2 or embeddings.shape[0] != len(obs):\n",
|
| 270 |
+
" raise ValueError('Embedding rows do not match the full input dataset.')\n",
|
| 271 |
+
" if not {'sample', 'cell_type'}.issubset(obs.columns):\n",
|
| 272 |
+
" raise KeyError('The input metadata must contain sample and cell_type.')\n",
|
| 273 |
+
" if len(obs) < 3 or not np.isfinite(embeddings).all():\n",
|
| 274 |
+
" raise ValueError('UMAP requires at least three cells and finite embeddings.')\n",
|
| 275 |
+
" adata_umap = sc.AnnData(X=embeddings, obs=obs)\n",
|
| 276 |
+
" sc.pp.neighbors(adata_umap, use_rep='X', n_pcs=None,\n",
|
| 277 |
+
" n_neighbors=min(15, len(obs) - 1), random_state=UMAP_RANDOM_STATE)\n",
|
| 278 |
+
" sc.tl.umap(adata_umap, min_dist=0.5, random_state=UMAP_RANDOM_STATE)\n",
|
| 279 |
+
"\n",
|
| 280 |
+
" def plot_umap_axis(ax, adata, key, title):\n",
|
| 281 |
+
" coords = np.asarray(adata.obsm['X_umap'])\n",
|
| 282 |
+
" values = adata.obs[key].astype('string').fillna('Missing')\n",
|
| 283 |
+
" categories = sorted(values.unique())\n",
|
| 284 |
+
" palette = [plt.get_cmap(name)(i) for name in ['tab20', 'tab20b', 'tab20c']\n",
|
| 285 |
+
" for i in range(plt.get_cmap(name).N)]\n",
|
| 286 |
+
" for index, category in enumerate(categories):\n",
|
| 287 |
+
" mask = (values == category).to_numpy(dtype=bool)\n",
|
| 288 |
+
" ax.scatter(coords[mask, 0], coords[mask, 1], s=2.2,\n",
|
| 289 |
+
" color=palette[index % len(palette)], alpha=0.86,\n",
|
| 290 |
+
" linewidths=0, rasterized=True, label=str(category))\n",
|
| 291 |
+
" ax.set_title(title)\n",
|
| 292 |
+
" ax.set_xticks([])\n",
|
| 293 |
+
" ax.set_yticks([])\n",
|
| 294 |
+
" for spine in ax.spines.values():\n",
|
| 295 |
+
" spine.set_visible(False)\n",
|
| 296 |
+
" ax.set_aspect('equal', adjustable='datalim')\n",
|
| 297 |
+
" ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.025),\n",
|
| 298 |
+
" ncol=max(1, min(5, math.ceil(len(categories) / 10))),\n",
|
| 299 |
+
" frameon=False, markerscale=2.4, fontsize=6)\n",
|
| 300 |
+
"\n",
|
| 301 |
+
" with plt.rc_context({'font.family': 'DejaVu Sans', 'font.size': 7, 'pdf.fonttype': 42}):\n",
|
| 302 |
+
" fig, axes = plt.subplots(1, 2, figsize=(11.69, 8.27))\n",
|
| 303 |
+
" fig.subplots_adjust(left=0.025, right=0.985, bottom=0.30, top=0.90, wspace=0.04)\n",
|
| 304 |
+
" plot_umap_axis(axes[0], adata_umap, 'cell_type', 'Cell type')\n",
|
| 305 |
+
" plot_umap_axis(axes[1], adata_umap, 'sample', 'Sample')\n",
|
| 306 |
+
" fig.suptitle('Zero-shot batch integration | BioMod model weights')\n",
|
| 307 |
+
" a4_pdf = OUT_DIR / 'umap_a4.pdf'\n",
|
| 308 |
+
" a4_png = OUT_DIR / 'umap_a4.png'\n",
|
| 309 |
+
" fig.savefig(a4_pdf)\n",
|
| 310 |
+
" fig.savefig(a4_png, dpi=300)\n",
|
| 311 |
+
" plt.close(fig)\n",
|
| 312 |
+
" adata_umap.write_h5ad(OUT_DIR / 'umap.h5ad')\n",
|
| 313 |
+
" print('Saved:', a4_pdf)\n",
|
| 314 |
+
" print('Saved:', a4_png)\n",
|
| 315 |
+
" display(Image(filename=str(a4_png)))\n"
|
| 316 |
+
]
|
| 317 |
+
}
|
| 318 |
+
],
|
| 319 |
+
"metadata": {
|
| 320 |
+
"kernelspec": {
|
| 321 |
+
"display_name": "Python 3",
|
| 322 |
+
"language": "python",
|
| 323 |
+
"name": "python3"
|
| 324 |
+
},
|
| 325 |
+
"language_info": {
|
| 326 |
+
"codemirror_mode": {
|
| 327 |
+
"name": "ipython",
|
| 328 |
+
"version": 3
|
| 329 |
+
},
|
| 330 |
+
"file_extension": ".py",
|
| 331 |
+
"mimetype": "text/x-python",
|
| 332 |
+
"name": "python",
|
| 333 |
+
"nbconvert_exporter": "python",
|
| 334 |
+
"pygments_lexer": "ipython3",
|
| 335 |
+
"version": "3.9"
|
| 336 |
+
}
|
| 337 |
+
},
|
| 338 |
+
"nbformat": 4,
|
| 339 |
+
"nbformat_minor": 5
|
| 340 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -80,13 +80,13 @@ The following tasks have implementations in this repository. The links identify
|
|
| 80 |
available source code and examples; these workflows have not been validated end
|
| 81 |
to end with the bundled checkpoint.
|
| 82 |
|
| 83 |
-
| Task | Implementation | Notebook
|
| 84 |
| --- | --- | --- |
|
| 85 |
| Cell-type annotation | [Fine-tuning script](BioMod/cell_annotation/performer_module_finetune.py) | [Notebook](BioMod/toturials/cell_annotation.ipynb) |
|
| 86 |
| Zero-shot cell embeddings and batch-effect evaluation | [Embedding and evaluation script](BioMod/zero_shot_batch/performer_module_zero_shot.py) | [Notebook](BioMod/toturials/zero_shot_batch_integration.ipynb) |
|
| 87 |
| Single-drug response regression (IC50) | [Training script](BioMod/drug_response/train_drug_performer_module.py) | [Notebook](BioMod/toturials/drug_response_prediction.ipynb) |
|
| 88 |
-
| Drug-pair synergy classification | [Training script](BioMod/drug_combination/train_drug_combination_performer_module.py) | [
|
| 89 |
-
| Gene perturbation response prediction | [Training script](BioMod/gene_perturbation/train_module.py) | [
|
| 90 |
|
| 91 |
### Pretraining, evaluation, and analysis scripts
|
| 92 |
|
|
|
|
| 80 |
available source code and examples; these workflows have not been validated end
|
| 81 |
to end with the bundled checkpoint.
|
| 82 |
|
| 83 |
+
| Task | Implementation | Notebook |
|
| 84 |
| --- | --- | --- |
|
| 85 |
| Cell-type annotation | [Fine-tuning script](BioMod/cell_annotation/performer_module_finetune.py) | [Notebook](BioMod/toturials/cell_annotation.ipynb) |
|
| 86 |
| Zero-shot cell embeddings and batch-effect evaluation | [Embedding and evaluation script](BioMod/zero_shot_batch/performer_module_zero_shot.py) | [Notebook](BioMod/toturials/zero_shot_batch_integration.ipynb) |
|
| 87 |
| Single-drug response regression (IC50) | [Training script](BioMod/drug_response/train_drug_performer_module.py) | [Notebook](BioMod/toturials/drug_response_prediction.ipynb) |
|
| 88 |
+
| Drug-pair synergy classification | [Training script](BioMod/drug_combination/train_drug_combination_performer_module.py) | [Notebook](BioMod/toturials/drug_combination.ipynb) |
|
| 89 |
+
| Gene perturbation response prediction | [Training script](BioMod/gene_perturbation/train_module.py) | [Notebook](BioMod/toturials/gene_perturbation.ipynb) |
|
| 90 |
|
| 91 |
### Pretraining, evaluation, and analysis scripts
|
| 92 |
|