{ "cells": [ { "cell_type": "markdown", "id": "c8135598", "metadata": {}, "source": [ "# Checkpointing and Persistence\n", "\n", "This notebook shows how to keep a genetic search reproducible and recoverable. Long-running searches should be able to write intermediate checkpoints, save the fitted search object, and load it again later for inspection or prediction.\n", "\n", "## Menu\n", "\n", "1. [Create a compact classification problem](#Create-a-compact-classification-problem)\n", "2. [Configure a search with checkpoints](#Configure-a-search-with-checkpoints)\n", "3. [Fit with optimizer controls](#Fit-with-optimizer-controls)\n", "4. [Inspect checkpoint contents](#Inspect-checkpoint-contents)\n", "5. [Save and reload the fitted search](#Save-and-reload-the-fitted-search)\n", "6. [Clean up generated files](#Clean-up-generated-files)\n", "7. [Practical notes](#Practical-notes)" ] }, { "cell_type": "markdown", "id": "702e16ce", "metadata": {}, "source": [ "## Create a compact classification problem\n", "\n", "The breast cancer dataset is small enough for a tutorial, but it still has enough features for the optimizer to make meaningful choices." ] }, { "cell_type": "code", "id": "bb33c789", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:25:36.742392Z", "iopub.status.busy": "2026-06-20T05:25:36.741935Z", "iopub.status.idle": "2026-06-20T05:25:46.772882Z", "shell.execute_reply": "2026-06-20T05:25:46.771545Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:40:42.486293Z", "start_time": "2026-06-20T18:40:39.002109Z" } }, "source": [ "from pathlib import Path\n", "\n", "import pandas as pd\n", "from sklearn.datasets import load_breast_cancer\n", "from sklearn.ensemble import RandomForestClassifier\n", "from sklearn.metrics import accuracy_score, balanced_accuracy_score, roc_auc_score\n", "from sklearn.model_selection import StratifiedKFold, train_test_split\n", "\n", "from sklearn_genetic import (\n", " EvolutionConfig,\n", " GASearchCV,\n", " OptimizationConfig,\n", " PopulationConfig,\n", " RuntimeConfig,\n", ")\n", "from sklearn_genetic.callbacks import ConsecutiveStopping, ModelCheckpoint, TimerStopping\n", "from sklearn_genetic.plots import plot_fitness_evolution\n", "from sklearn_genetic.schedules import ExponentialAdapter, InverseAdapter\n", "from sklearn_genetic.space import Categorical, Continuous, Integer\n", "\n", "data = load_breast_cancer(as_frame=True)\n", "X = data.data\n", "y = data.target\n", "\n", "X_train, X_test, y_train, y_test = train_test_split(\n", " X,\n", " y,\n", " test_size=0.25,\n", " stratify=y,\n", " random_state=42,\n", ")\n", "\n", "X_train.shape, X_test.shape" ], "outputs": [ { "data": { "text/plain": [ "((426, 30), (143, 30))" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 1 }, { "cell_type": "markdown", "id": "2b14ee47", "metadata": {}, "source": [ "## Configure a search with checkpoints\n", "\n", "`ModelCheckpoint` writes the current estimator configuration and logbook during the fit. This is useful for long runs, interrupted sessions, or for storing the generation history next to experiment artifacts.\n", "\n", "The regular `save` and `load` methods are different: they persist the fitted `GASearchCV` object after training, so it can be restored later for prediction and analysis." ] }, { "cell_type": "code", "id": "998e173d", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:25:46.776327Z", "iopub.status.busy": "2026-06-20T05:25:46.775815Z", "iopub.status.idle": "2026-06-20T05:25:46.782960Z", "shell.execute_reply": "2026-06-20T05:25:46.781921Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:40:42.509363800Z", "start_time": "2026-06-20T18:40:42.489295700Z" } }, "source": [ "artifact_dir = Path(\"ga_artifacts\")\n", "artifact_dir.mkdir(exist_ok=True)\n", "\n", "checkpoint_path = artifact_dir / \"breast_cancer_ga_checkpoint.pkl\"\n", "saved_search_path = artifact_dir / \"breast_cancer_ga_search.pkl\"\n", "\n", "param_grid = {\n", " \"n_estimators\": Integer(40, 160),\n", " \"max_depth\": Integer(2, 12),\n", " \"min_samples_leaf\": Integer(1, 8),\n", " \"max_features\": Continuous(0.25, 1.0),\n", " \"criterion\": Categorical([\"gini\", \"entropy\", \"log_loss\"]),\n", " \"class_weight\": Categorical([None, \"balanced\"]),\n", "}\n", "\n", "warm_start_configs = [\n", " {\n", " \"n_estimators\": 80,\n", " \"max_depth\": 5,\n", " \"min_samples_leaf\": 2,\n", " \"max_features\": 0.7,\n", " \"criterion\": \"gini\",\n", " \"class_weight\": None,\n", " }\n", "]\n", "\n", "callbacks = [\n", " ModelCheckpoint(checkpoint_path),\n", " ConsecutiveStopping(generations=6, metric=\"fitness_best\"),\n", " TimerStopping(total_seconds=180),\n", "]" ], "outputs": [], "execution_count": 2 }, { "cell_type": "markdown", "id": "77e7b2bf", "metadata": {}, "source": [ "## Fit with optimizer controls\n", "\n", "This example combines persistence with smart initial populations, warm-start seeds, adaptive probabilities, local refinement, diversity control, fitness sharing, caching, and automatic parallel execution." ] }, { "cell_type": "code", "id": "8fa639bc", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:25:46.785468Z", "iopub.status.busy": "2026-06-20T05:25:46.785122Z", "iopub.status.idle": "2026-06-20T05:28:43.557430Z", "shell.execute_reply": "2026-06-20T05:28:43.555958Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.307018Z", "start_time": "2026-06-20T18:40:42.510361500Z" } }, "source": [ "cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)\n", "\n", "search = GASearchCV(\n", " estimator=RandomForestClassifier(random_state=42, n_jobs=1),\n", " cv=cv,\n", " scoring=\"roc_auc\",\n", " param_grid=param_grid,\n", " evolution_config=EvolutionConfig(\n", " population_size=14,\n", " generations=10,\n", " crossover_probability=ExponentialAdapter(initial_value=0.85, end_value=0.45, adaptive_rate=0.08),\n", " mutation_probability=InverseAdapter(initial_value=0.18, end_value=0.55, adaptive_rate=0.12),\n", " keep_top_k=4,\n", " ),\n", " population_config=PopulationConfig(\n", " initializer=\"smart\",\n", " warm_start_configs=warm_start_configs,\n", " ),\n", " runtime_config=RuntimeConfig(\n", " use_cache=True,\n", " parallel_backend=\"auto\",\n", " n_jobs=-1,\n", " verbose=False,\n", " ),\n", " optimization_config=OptimizationConfig(\n", " local_search=True,\n", " local_search_top_k=2,\n", " local_search_steps=2,\n", " local_search_radius=0.12,\n", " diversity_control=True,\n", " diversity_threshold=0.2,\n", " diversity_stagnation_generations=3,\n", " diversity_mutation_boost=1.8,\n", " random_immigrants_fraction=0.15,\n", " fitness_sharing=True,\n", " sharing_radius=0.25,\n", " ),\n", " refit=True,\n", ")\n", "\n", "search.fit(X_train, y_train, callbacks=callbacks)\n", "search.best_params_, search.best_score_" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "Checkpoint save in ga_artifacts\\breast_cancer_ga_checkpoint.pkl\n", "INFO: TimerStopping callback met its criteria\n", "INFO: Stopping the algorithm\n" ] }, { "data": { "text/plain": [ "({'n_estimators': 42,\n", " 'max_depth': 8,\n", " 'min_samples_leaf': 4,\n", " 'max_features': 0.8576422839762377,\n", " 'criterion': 'gini',\n", " 'class_weight': None},\n", " 0.9904600381598474)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 3 }, { "cell_type": "code", "id": "4372ce43", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:43.563691Z", "iopub.status.busy": "2026-06-20T05:28:43.562971Z", "iopub.status.idle": "2026-06-20T05:28:43.634385Z", "shell.execute_reply": "2026-06-20T05:28:43.632233Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.393837400Z", "start_time": "2026-06-20T18:43:49.319535400Z" } }, "source": [ "y_pred = search.predict(X_test)\n", "y_proba = search.predict_proba(X_test)[:, 1]\n", "\n", "pd.Series(\n", " {\n", " \"accuracy\": accuracy_score(y_test, y_pred),\n", " \"balanced_accuracy\": balanced_accuracy_score(y_test, y_pred),\n", " \"roc_auc\": roc_auc_score(y_test, y_proba),\n", " }\n", ").to_frame(\"test_score\")" ], "outputs": [ { "data": { "text/plain": [ " test_score\n", "accuracy 0.951049\n", "balanced_accuracy 0.945597\n", "roc_auc 0.993082" ], "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
test_score
accuracy0.951049
balanced_accuracy0.945597
roc_auc0.993082
\n", "
" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 4 }, { "cell_type": "markdown", "id": "c2863a70", "metadata": {}, "source": [ "The fit statistics summarize how much work was done. They are useful when comparing runs with different population sizes, smart initialization, caching, local search, or diversity controls." ] }, { "cell_type": "code", "id": "4966cacc", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:43.639202Z", "iopub.status.busy": "2026-06-20T05:28:43.638620Z", "iopub.status.idle": "2026-06-20T05:28:43.651899Z", "shell.execute_reply": "2026-06-20T05:28:43.649796Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.418110200Z", "start_time": "2026-06-20T18:43:49.394837900Z" } }, "source": [ "pd.Series(search.fit_stats_).to_frame(\"value\")" ], "outputs": [ { "data": { "text/plain": [ " value\n", "evaluated_candidates 298\n", "unique_candidates 293\n", "cross_validate_calls 293\n", "cache_hits 5\n", "duplicate_candidates 0\n", "skipped_invalid_candidates 0\n", "population_parallel_batches 12\n", "population_serial_batches 0\n", "random_immigrants 15\n", "local_refinement_candidates 4" ], "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
value
evaluated_candidates298
unique_candidates293
cross_validate_calls293
cache_hits5
duplicate_candidates0
skipped_invalid_candidates0
population_parallel_batches12
population_serial_batches0
random_immigrants15
local_refinement_candidates4
\n", "
" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 5 }, { "cell_type": "code", "id": "a289fbd3", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:43.655612Z", "iopub.status.busy": "2026-06-20T05:28:43.655275Z", "iopub.status.idle": "2026-06-20T05:28:43.673572Z", "shell.execute_reply": "2026-06-20T05:28:43.671716Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.447131100Z", "start_time": "2026-06-20T18:43:49.419113400Z" } }, "source": [ "history = pd.DataFrame(search.history)\n", "history[\n", " [\n", " \"gen\",\n", " \"fitness_best\",\n", " \"fitness\",\n", " \"fitness_max\",\n", " \"unique_individual_ratio\",\n", " \"genotype_diversity\",\n", " \"stagnation_generations\",\n", " ]\n", "].tail()" ], "outputs": [ { "data": { "text/plain": [ " gen fitness_best fitness fitness_max unique_individual_ratio \\\n", "6 6 0.988481 0.986634 0.987951 0.785714 \n", "7 7 0.990460 0.987086 0.990460 0.785714 \n", "8 8 0.990460 0.986912 0.988905 0.714286 \n", "9 9 0.990460 0.986813 0.988128 0.785714 \n", "10 10 0.990460 0.986846 0.989612 0.714286 \n", "\n", " genotype_diversity stagnation_generations \n", "6 0.320513 5 \n", "7 0.410256 0 \n", "8 0.294872 1 \n", "9 0.410256 2 \n", "10 0.320513 4 " ], "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
genfitness_bestfitnessfitness_maxunique_individual_ratiogenotype_diversitystagnation_generations
660.9884810.9866340.9879510.7857140.3205135
770.9904600.9870860.9904600.7857140.4102560
880.9904600.9869120.9889050.7142860.2948721
990.9904600.9868130.9881280.7857140.4102562
10100.9904600.9868460.9896120.7142860.3205134
\n", "
" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 6 }, { "cell_type": "code", "id": "15bc45a4", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:43.678187Z", "iopub.status.busy": "2026-06-20T05:28:43.677503Z", "iopub.status.idle": "2026-06-20T05:28:44.391992Z", "shell.execute_reply": "2026-06-20T05:28:44.390178Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.722207900Z", "start_time": "2026-06-20T18:43:49.449640100Z" } }, "source": [ "plot_fitness_evolution(search)" ], "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/plain": [ "
" ], "image/png": "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" }, "metadata": {}, "output_type": "display_data", "jetTransient": { "display_id": null } } ], "execution_count": 7 }, { "cell_type": "markdown", "id": "2c51d49c", "metadata": {}, "source": [ "## Inspect checkpoint contents\n", "\n", "The checkpoint callback stores a dictionary with two top-level keys: `estimator_state` and `logbook`. The state is intentionally lightweight and focuses on the search configuration; the logbook contains the generation records." ] }, { "cell_type": "code", "id": "4f2cb5de", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:44.399670Z", "iopub.status.busy": "2026-06-20T05:28:44.398903Z", "iopub.status.idle": "2026-06-20T05:28:44.413593Z", "shell.execute_reply": "2026-06-20T05:28:44.411469Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.775939700Z", "start_time": "2026-06-20T18:43:49.724206500Z" } }, "source": [ "checkpoint = ModelCheckpoint(checkpoint_path).load()\n", "\n", "checkpoint.keys(), len(checkpoint[\"logbook\"])" ], "outputs": [ { "data": { "text/plain": [ "(dict_keys(['estimator_state', 'logbook']), 11)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 8 }, { "cell_type": "code", "id": "9424a174", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:44.417826Z", "iopub.status.busy": "2026-06-20T05:28:44.417075Z", "iopub.status.idle": "2026-06-20T05:28:44.424933Z", "shell.execute_reply": "2026-06-20T05:28:44.423302Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.796541900Z", "start_time": "2026-06-20T18:43:49.777341300Z" } }, "source": [ "sorted(checkpoint[\"estimator_state\"].keys())" ], "outputs": [ { "data": { "text/plain": [ "['algorithm',\n", " 'crossover_probability',\n", " 'cv',\n", " 'diversity_control',\n", " 'diversity_mutation_boost',\n", " 'diversity_stagnation_generations',\n", " 'diversity_threshold',\n", " 'estimator',\n", " 'fitness_sharing',\n", " 'generations',\n", " 'local_search',\n", " 'local_search_radius',\n", " 'local_search_steps',\n", " 'local_search_top_k',\n", " 'mutation_probability',\n", " 'param_grid',\n", " 'population_size',\n", " 'random_immigrants_fraction',\n", " 'scoring',\n", " 'sharing_alpha',\n", " 'sharing_radius']" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 9 }, { "cell_type": "markdown", "id": "52ee647e", "metadata": {}, "source": [ "## Save and reload the fitted search\n", "\n", "Use `save` after fitting when you want to preserve the full fitted search object. Reloading into a fresh `GASearchCV` instance restores the fitted estimator, history, best parameters, and prediction methods." ] }, { "cell_type": "code", "id": "79aaf7e7", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:44.438612Z", "iopub.status.busy": "2026-06-20T05:28:44.437684Z", "iopub.status.idle": "2026-06-20T05:28:44.588331Z", "shell.execute_reply": "2026-06-20T05:28:44.586520Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.867748600Z", "start_time": "2026-06-20T18:43:49.798047900Z" } }, "source": [ "search.save(saved_search_path)\n", "\n", "restored_search = GASearchCV(\n", " estimator=RandomForestClassifier(random_state=42, n_jobs=1),\n", " cv=cv,\n", " scoring=\"roc_auc\",\n", " param_grid=param_grid,\n", ")\n", "restored_search.load(saved_search_path)\n", "\n", "restored_predictions = restored_search.predict(X_test)\n", "\n", "pd.Series(\n", " {\n", " \"same_predictions\": (restored_predictions == y_pred).all(),\n", " \"restored_accuracy\": accuracy_score(y_test, restored_predictions),\n", " \"restored_best_score\": restored_search.best_score_,\n", " }\n", ").to_frame(\"value\")" ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "GASearchCV model successfully saved to ga_artifacts\\breast_cancer_ga_search.pkl\n", "GASearchCV model successfully loaded from ga_artifacts\\breast_cancer_ga_search.pkl\n" ] }, { "data": { "text/plain": [ " value\n", "same_predictions True\n", "restored_accuracy 0.951049\n", "restored_best_score 0.99046" ], "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
value
same_predictionsTrue
restored_accuracy0.951049
restored_best_score0.99046
\n", "
" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 10 }, { "cell_type": "markdown", "id": "efa3de77", "metadata": {}, "source": [ "## Clean up generated files\n", "\n", "Keep this cell commented when you want to inspect the artifacts after the notebook finishes." ] }, { "cell_type": "code", "id": "24bb09e6", "metadata": { "execution": { "iopub.execute_input": "2026-06-20T05:28:44.594162Z", "iopub.status.busy": "2026-06-20T05:28:44.592998Z", "iopub.status.idle": "2026-06-20T05:28:44.600980Z", "shell.execute_reply": "2026-06-20T05:28:44.599075Z" }, "ExecuteTime": { "end_time": "2026-06-20T18:43:49.917271500Z", "start_time": "2026-06-20T18:43:49.871747800Z" } }, "source": [ "# for path in [checkpoint_path, saved_search_path]:\n", "# path.unlink(missing_ok=True)\n", "# artifact_dir.rmdir()" ], "outputs": [], "execution_count": 11 }, { "cell_type": "markdown", "id": "21fb1b34", "metadata": {}, "source": [ "## Practical notes\n", "\n", "- Use `ModelCheckpoint` for progress recovery and audit trails during a fit.\n", "- Use `save` and `load` for fitted search objects that need to be reused for prediction or later analysis.\n", "- Store checkpoints outside temporary notebook directories for long runs.\n", "- Keep `random_state` fixed across the estimator, splitter, and search inputs when you need repeatable artifacts." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.14" } }, "nbformat": 4, "nbformat_minor": 5 }