{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# RMMol frozen-embedding sample\n", "\n", "This notebook demonstrates how to load the packaged CSV files, generate RMMol embeddings, and apply the selected downstream heads. It is RMMol-only and does not include baseline model artifacts or local output tables." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "import os, sys, json\n", "import numpy as np\n", "import pandas as pd\n", "from sklearn.ensemble import ExtraTreesClassifier\n", "from sklearn.linear_model import Ridge, RidgeClassifier\n", "from sklearn.metrics import mean_absolute_error, mean_squared_error, roc_auc_score\n", "from sklearn.model_selection import KFold\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import Normalizer, StandardScaler\n", "\n", "RELEASE_DIR = Path(os.environ.get('RMMOL_RELEASE_DIR', Path.cwd()))\n", "if not (RELEASE_DIR / 'dataset').exists():\n", " RELEASE_DIR = Path.cwd().parent\n", "RMMOL_CKPT = os.environ.get('RMMOL_CKPT', '')\n", "RMMOL_TRUE_LOADER_DIR = Path(os.environ.get('RMMOL_TRUE_LOADER_DIR', '/home/sustech_spark/project/virtual cell/FUND/experiments'))\n", "DEVICE = os.environ.get('RMMOL_DEVICE', 'cuda')\n", "BATCH_SIZE = int(os.environ.get('RMMOL_BATCH_SIZE', '128'))\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TASK_SETTINGS = {\n", " \"biophysics\": {\n", " \"bbbp\": \"StandardScaler + RidgeClassifier(alpha=1.0, class_weight=None, solver='lsqr'); five-fold KFold(shuffle=True, random_state=42).\",\n", " \"hiv\": \"Normalizer + RidgeClassifier(alpha=1.6788e-05, class_weight='balanced', solver='lsqr'); train+val fit, test report.\"\n", " },\n", " \"physiological\": {\n", " \"bace\": \"ExtraTreesClassifier(n_estimators=500, criterion='entropy', max_features='sqrt', class_weight='balanced').\",\n", " \"clintox_FDA_APPROVED\": \"l2-normalized RidgeClassifier(alpha=0.0003, class_weight=None).\",\n", " \"tox21_and_sider\": \"Task-specific RidgeClassifier presets are embedded in the Jupyter sample notebook.\"\n", " },\n", " \"physical_chemistry\": {\n", " \"esol\": \"Ridge(alpha=0.003), RMSE.\",\n", " \"freesolv\": \"Ridge(alpha=0.003), RMSE.\",\n", " \"lipo\": \"Ridge(alpha=0.001), RMSE.\"\n", " },\n", " \"quantum_mechanics\": {\n", " \"qm9\": \"StandardScaler + Ridge(alpha=0.01), MAE over 12 QM9 targets.\"\n", " }\n", "}\n", "\n", "PHYSIOLOGICAL_TASK_PRESETS = [\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-ahr\",\n", " \"preset\": \"l2_ridge_a0.0003_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.0003,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-ar-lbd\",\n", " \"preset\": \"std_l2_ridge_a0.003_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.003,\n", " \"class_weight\": null,\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-ar\",\n", " \"preset\": \"pca64_ridge_a100_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 100,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"pca+RidgeClassifier\",\n", " \"n_components\": 64\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-aromatase\",\n", " \"preset\": \"std_l2_ridge_a10_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 10,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-er-lbd\",\n", " \"preset\": \"l2_ridge_a0.0003_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.0003,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-er\",\n", " \"preset\": \"std_l2_ridge_a0.0003_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.0003,\n", " \"class_weight\": null,\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"nr-ppar-gamma\",\n", " \"preset\": \"std_l2_ridge_a0.003_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.003,\n", " \"class_weight\": null,\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"sr-are\",\n", " \"preset\": \"std_l2_ridge_a0.003_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.003,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"sr-atad5\",\n", " \"preset\": \"std_l2_ridge_a0.001_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.001,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"sr-hse\",\n", " \"preset\": \"l2_ridge_a3e-05_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 3e-05,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"sr-mmp\",\n", " \"preset\": \"std_l2_ridge_a0.001_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.001,\n", " \"class_weight\": null,\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"tox21\",\n", " \"task\": \"sr-p53\",\n", " \"preset\": \"std_l2_ridge_a1e-05_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 1e-05,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"blood and lymphatic system disorders\",\n", " \"preset\": \"l2_ridge_a0.3_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.3,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"cardiac disorders\",\n", " \"preset\": \"l2_ridge_a0.3_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.3,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"congenital, familial and genetic disorders\",\n", " \"preset\": \"svd128_ridge_a0.001_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.001,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"svd+RidgeClassifier\",\n", " \"n_components\": 128\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"ear and labyrinth disorders\",\n", " \"preset\": \"l2_ridge_a0.3_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.3,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"endocrine disorders\",\n", " \"preset\": \"minmax_ridge_a0.0001_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.0001,\n", " \"class_weight\": null,\n", " \"head\": \"minmax+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"eye disorders\",\n", " \"preset\": \"l2_ridge_a0.03_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.03,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"gastrointestinal disorders\",\n", " \"preset\": \"l2_ridge_a3_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 3,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"general disorders and administration site conditions\",\n", " \"preset\": \"l2_ridge_a0.03_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.03,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"hepatobiliary disorders\",\n", " \"preset\": \"l2_ridge_a1_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 1,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"immune system disorders\",\n", " \"preset\": \"l2_ridge_a1_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 1,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"infections and infestations\",\n", " \"preset\": \"l2_ridge_a3_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 3,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"injury, poisoning and procedural complications\",\n", " \"preset\": \"l2_ridge_a3_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 3,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"investigations\",\n", " \"preset\": \"std_l2_ridge_a0.1_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 0.1,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"metabolism and nutrition disorders\",\n", " \"preset\": \"l2_ridge_a3_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 3,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"musculoskeletal and connective tissue disorders\",\n", " \"preset\": \"l2_ridge_a0.03_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.03,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"neoplasms benign, malignant and unspecified (incl cysts and polyps)\",\n", " \"preset\": \"l2_ridge_a1e-05_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 1e-05,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"nervous system disorders\",\n", " \"preset\": \"minmax_ridge_a30_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 30,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"minmax+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"pregnancy, puerperium and perinatal conditions\",\n", " \"preset\": \"svd32_ridge_a0.001_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.001,\n", " \"class_weight\": null,\n", " \"head\": \"svd+RidgeClassifier\",\n", " \"n_components\": 32\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"product issues\",\n", " \"preset\": \"l2_ridge_a1000_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 1000,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"psychiatric disorders\",\n", " \"preset\": \"l2_ridge_a1_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 1,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"renal and urinary disorders\",\n", " \"preset\": \"l2_ridge_a0.1_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.1,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"reproductive system and breast disorders\",\n", " \"preset\": \"l2_ridge_a3e-05_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 3e-05,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"respiratory, thoracic and mediastinal disorders\",\n", " \"preset\": \"l2_ridge_a1_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 1,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"skin and subcutaneous tissue disorders\",\n", " \"preset\": \"l2_ridge_a1000_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 1000,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"social circumstances\",\n", " \"preset\": \"l2_ridge_a3_cwbalanced\",\n", " \"preset_params\": {\n", " \"alpha\": 3,\n", " \"class_weight\": \"balanced\",\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"surgical and medical procedures\",\n", " \"preset\": \"std_l2_ridge_a1e-05_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 1e-05,\n", " \"class_weight\": null,\n", " \"head\": \"std_l2+RidgeClassifier\"\n", " }\n", " },\n", " {\n", " \"dataset_key\": \"sider\",\n", " \"task\": \"vascular disorders\",\n", " \"preset\": \"l2_ridge_a0.0003_cwnone\",\n", " \"preset_params\": {\n", " \"alpha\": 0.0003,\n", " \"class_weight\": null,\n", " \"head\": \"l2+RidgeClassifier\"\n", " }\n", " }\n", "]\n", "\n", "print(json.dumps(TASK_SETTINGS, indent=2))\n", "print(f'Loaded {len(PHYSIOLOGICAL_TASK_PRESETS)} Tox21/SIDER task-specific settings')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def encode_rmmol_smiles(smiles):\n", " if not RMMOL_CKPT:\n", " raise RuntimeError('Set RMMOL_CKPT to the downloaded RMMol checkpoint path before encoding.')\n", " sys.path.insert(0, str(RMMOL_TRUE_LOADER_DIR))\n", " from rmmol_true_loader import encode_smiles_true_loader\n", " embeddings, valid_indices, cfg = encode_smiles_true_loader(\n", " list(smiles), checkpoint=Path(RMMOL_CKPT), batch_size=BATCH_SIZE, device=DEVICE\n", " )\n", " return embeddings, np.asarray(valid_indices, dtype=int), cfg\n", "\n", "def read_splits(domain, dataset, prefix):\n", " frames = []\n", " for split in ['train', 'val', 'test']:\n", " frame = pd.read_csv(RELEASE_DIR / 'dataset' / domain / dataset / f'{prefix}_{split}.csv')\n", " frame['_split'] = split\n", " frames.append(frame)\n", " return pd.concat(frames, ignore_index=True)\n", "\n", "def fit_auc(head_factory, x, y, folds):\n", " scores = []\n", " for train_idx, test_idx in folds:\n", " model = head_factory()\n", " model.fit(x[train_idx], y[train_idx])\n", " pred = model.decision_function(x[test_idx])\n", " scores.append(roc_auc_score(y[test_idx], pred))\n", " return float(np.mean(scores)), float(np.std(scores, ddof=0))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Biophysics examples" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bbbp = pd.read_csv(RELEASE_DIR / 'dataset' / 'biophysics' / 'bbbp' / 'bbbp.csv')\n", "x_bbbp, valid_bbbp, _ = encode_rmmol_smiles(bbbp['smiles'])\n", "y_bbbp = bbbp.iloc[valid_bbbp]['p_np'].astype(int).to_numpy()\n", "folds = list(KFold(n_splits=5, shuffle=True, random_state=42).split(x_bbbp))\n", "bbbp_auc, bbbp_std = fit_auc(\n", " lambda: make_pipeline(StandardScaler(), RidgeClassifier(alpha=1.0, class_weight=None, solver='lsqr')),\n", " x_bbbp, y_bbbp, folds\n", ")\n", "print({'dataset': 'bbbp', 'metric': 'ROC-AUC', 'mean': bbbp_auc, 'std': bbbp_std, 'n': len(y_bbbp)})\n", "\n", "hiv = read_splits('biophysics', 'hiv', 'hiv')\n", "x_hiv, valid_hiv, _ = encode_rmmol_smiles(hiv['smiles'])\n", "hiv_valid = hiv.iloc[valid_hiv].reset_index(drop=True)\n", "y_hiv = hiv_valid['HIV_active'].astype(int).to_numpy()\n", "trainval = hiv_valid['_split'].isin(['train', 'val']).to_numpy()\n", "test = hiv_valid['_split'].eq('test').to_numpy()\n", "hiv_head = make_pipeline(Normalizer(), RidgeClassifier(alpha=1.6788e-05, class_weight='balanced', solver='lsqr'))\n", "hiv_head.fit(x_hiv[trainval], y_hiv[trainval])\n", "hiv_auc = roc_auc_score(y_hiv[test], hiv_head.decision_function(x_hiv[test]))\n", "print({'dataset': 'hiv', 'metric': 'ROC-AUC', 'test': float(hiv_auc), 'n_test': int(test.sum())})\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Physical chemistry example" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def regression_cv(domain, dataset, prefix, label, alpha):\n", " frame = read_splits(domain, dataset, prefix)\n", " x, valid, _ = encode_rmmol_smiles(frame['smiles'])\n", " y = frame.iloc[valid][label].astype(float).to_numpy()\n", " folds = KFold(n_splits=5, shuffle=True, random_state=42).split(x)\n", " rmses = []\n", " for train_idx, test_idx in folds:\n", " model = make_pipeline(StandardScaler(), Ridge(alpha=alpha))\n", " model.fit(x[train_idx], y[train_idx])\n", " pred = model.predict(x[test_idx])\n", " rmses.append(mean_squared_error(y[test_idx], pred, squared=False))\n", " return float(np.mean(rmses)), float(np.std(rmses, ddof=0)), len(y)\n", "\n", "esol_rmse, esol_std, esol_n = regression_cv(\n", " 'physical_chemistry', 'esol', 'esol', 'ESOL predicted log solubility in mols per litre', 0.003\n", ")\n", "print({'dataset': 'esol', 'metric': 'RMSE', 'mean': esol_rmse, 'std': esol_std, 'n': esol_n})\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Quantum mechanics example" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "qm9_tasks = ['mu', 'alpha', 'homo', 'lumo', 'gap', 'r2', 'zpve', 'u0', 'u298', 'h298', 'g298', 'cv']\n", "qm9 = read_splits('quantum_mechanics', 'qm9', 'qm9')\n", "x_qm9, valid_qm9, _ = encode_rmmol_smiles(qm9['smiles'])\n", "y_qm9 = qm9.iloc[valid_qm9][qm9_tasks].astype(float).to_numpy()\n", "folds = KFold(n_splits=5, shuffle=True, random_state=42).split(x_qm9)\n", "maes = []\n", "for train_idx, test_idx in folds:\n", " model = make_pipeline(StandardScaler(), Ridge(alpha=0.01))\n", " model.fit(x_qm9[train_idx], y_qm9[train_idx])\n", " pred = model.predict(x_qm9[test_idx])\n", " maes.append(mean_absolute_error(y_qm9[test_idx], pred))\n", "print({'dataset': 'qm9', 'metric': 'MAE', 'mean': float(np.mean(maes)), 'std': float(np.std(maes, ddof=0)), 'n': len(y_qm9)})\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }