{ "cells": [ { "cell_type": "markdown", "id": "fb754139", "metadata": {}, "source": [ "# How to do multistart optimizations\n", "\n", "## How to turn on multistart\n", "\n", "Turning on multistart literally just means adding `multistart=True` when you call `maximize` or `minimize` and adding sampling bounds to the params DataFrame. Of course, you can configure every aspect of the multistart optimization if you want. But if you don't, we pick good defaults for you. \n", "\n", "Let's look at the well known \"sphere\" example again:" ] }, { "cell_type": "code", "execution_count": 1, "id": "4a0bf5b4", "metadata": {}, "outputs": [], "source": [ "import estimagic as em\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 2, "id": "d799cf14", "metadata": {}, "outputs": [], "source": [ "def sphere(params):\n", " return params[\"value\"] @ params[\"value\"]" ] }, { "cell_type": "code", "execution_count": 3, "id": "0f3f99dc", "metadata": {}, "outputs": [ { "data": { "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", "
valuesoft_lower_boundsoft_upper_bound
01-510
12-510
23-510
\n", "
" ], "text/plain": [ " value soft_lower_bound soft_upper_bound\n", "0 1 -5 10\n", "1 2 -5 10\n", "2 3 -5 10" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "params = pd.DataFrame()\n", "params[\"value\"] = [1, 2, 3]\n", "params[\"soft_lower_bound\"] = -5\n", "params[\"soft_upper_bound\"] = 10\n", "params" ] }, { "cell_type": "code", "execution_count": 4, "id": "d77fee91", "metadata": {}, "outputs": [ { "data": { "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", "
valuesoft_lower_boundsoft_upper_bound
0-0.0-510
1-0.0-510
20.0-510
\n", "
" ], "text/plain": [ " value soft_lower_bound soft_upper_bound\n", "0 -0.0 -5 10\n", "1 -0.0 -5 10\n", "2 0.0 -5 10" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res = em.minimize(\n", " criterion=sphere,\n", " params=params,\n", " algorithm=\"scipy_lbfgsb\",\n", " multistart=True,\n", ")\n", "res.params.round(5)" ] }, { "cell_type": "markdown", "id": "d88e0ea5", "metadata": {}, "source": [ "## Understanding multistart results\n", "\n", "The ``OptimizeResult`` result object of a multistart optimization has exactly the same structure as ``OptimizeResult`` from a standard optimization but with additional information.\n", "\n", "- `res.multistart_info[\"local_optima\"]` is a list with the results from all optimizations that were performed\n", "- `res.multistart_info[\"start_parameters\"]` is a list with the start parameters from those optimizations \n", "- `res.multistart_info[\"exploration_sample\"]` is a list with parameter vectors at which the criterion function was evaluated in an initial exploration phase. \n", "- `res.multistart_info[\"exploration_results\"]` are the corresponding criterion values. " ] }, { "cell_type": "markdown", "id": "e88747a1", "metadata": {}, "source": [ "## What does multistart mean in estimagic?\n", "\n", "The way we do multistart optimizations is inspired by the [TikTak algorithm](https://github.com/serdarozkan/TikTak). Our multistart optimizations consist of the following steps:\n", "\n", "1. Draw a large sample of parameter vectors, randomly or using a low-discrepancy sequence. The size, sampling method, distribution, and more things can be configured. \n", "2. Evaluate the criterion function in parallel on all parameter vectors.\n", "4. Sort the parameter vectors from best to worst. \n", "5. Run local optimizations. The first local optimization is started from the best parameter vector in the sample. All subsequent ones are started from a convex combination of the currently best known parameter vector and the next sample point. " ] }, { "cell_type": "markdown", "id": "0ee8d218", "metadata": {}, "source": [ "## How to configure mutlistart?\n", "\n", "As you can imagine from the above description, there are many details that can be configured. This can be done by adding a dictionary with `multistart_options` when calling `minimize` or `maximize`. Let's look at an extreme example where we manually set everything to it's default value:" ] }, { "cell_type": "code", "execution_count": 5, "id": "3d76e35c", "metadata": {}, "outputs": [], "source": [ "options = {\n", " # Set the number of points at which criterion is evaluated\n", " # in the exploration phase\n", " \"n_samples\": 10 * len(params),\n", " # Pass in a DataFrame or array with a custom sample\n", " # for the exploration phase.\n", " \"sample\": None,\n", " # Determine number of optimizations, relative to n_samples\n", " \"share_optimizations\": 0.1,\n", " # Determine distribution from which sample is drawn\n", " \"sampling_distribution\": \"uniform\",\n", " # Determine sampling method. Allowed: [\"sobol\", \"random\",\n", " # \"halton\", \"hammersley\", \"korobov\", \"latin_hypercube\"]\n", " \"sampling_method\": \"sobol\",\n", " # Determine how start parameters for local optimizations are\n", " # calculated. Allowed: [\"tiktak\", \"linear\"] or a custom\n", " # function with arguments iteration, n_iterations, min_weight,\n", " # and max_weight\n", " \"mixing_weight_method\": \"tiktak\",\n", " # Determine bounds on mixing weights.\n", " \"mixing_weight_bounds\": (0.1, 0.995),\n", " # Determine after how many re-discoveries of the currently best\n", " # local optimum the multistart optimization converges.\n", " \"convergence.max_discoveries\": 2,\n", " # Determine the maximum relative distance two parameter vectors\n", " # can have to be considered equal for convergence purposes:\n", " \"convergence.relative_params_tolerance\": 0.01,\n", " # Determine how many cores are used\n", " \"n_cores\": 1,\n", " # Determine which batch_evaluator is used:\n", " \"batch_evaluator\": \"joblib\",\n", " # Determine the batch size. It must be larger than n_cores.\n", " # Setting the batch size larger than n_cores allows to reproduce\n", " # the exact results of a highly parallel optimization on a smaller\n", " # machine.\n", " \"batch_size\": 1,\n", " # Set the random seed:\n", " \"seed\": None,\n", " # Set how errors are handled during the exploration phase:\n", " \"exploration_error_handling\": \"continue\",\n", " # Set how errors are handled during the optimization phase:\n", " \"optimization_error_handling\": \"continue\",\n", "}" ] }, { "cell_type": "code", "execution_count": 6, "id": "0664686d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Minimize with 3 free parameters terminated successfully after 6 criterion evaluations, 6 derivative evaluations and 4 iterations.\n", "\n", "The value of criterion improved from 14.0 to 6.38558109434918e-18.\n", "\n", "The multistart_scipy_lbfgsb algorithm reported: CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL\n", "\n", "Independent of the convergence criteria used by multistart_scipy_lbfgsb, the strength of convergence can be assessed by the following criteria:\n", "\n", " one_step five_steps \n", "relative_criterion_change 3.06e-14*** 3.06e-14***\n", "relative_params_change 5.482e-07* 5.482e-07* \n", "absolute_criterion_change 3.06e-15*** 3.06e-15***\n", "absolute_params_change 5.482e-08* 5.482e-08* \n", "\n", "(***: change <= 1e-10, **: change <= 1e-8, *: change <= 1e-5. Change refers to a change between accepted steps. The first column only considers the last step. The second column considers the last five steps.)" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res = em.minimize(\n", " criterion=sphere,\n", " params=params,\n", " algorithm=\"scipy_lbfgsb\",\n", " multistart=True,\n", " multistart_options=options,\n", ")\n", "\n", "res" ] }, { "cell_type": "code", "execution_count": 7, "id": "0160ee53", "metadata": {}, "outputs": [ { "data": { "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", "
valuesoft_lower_boundsoft_upper_bound
0-0.0-510
1-0.0-510
20.0-510
\n", "
" ], "text/plain": [ " value soft_lower_bound soft_upper_bound\n", "0 -0.0 -5 10\n", "1 -0.0 -5 10\n", "2 0.0 -5 10" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res.params.round(5)" ] }, { "cell_type": "code", "execution_count": 8, "id": "fe8ea8e0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Minimize with 3 free parameters terminated successfully after 3 criterion evaluations, 3 derivative evaluations and 2 iterations.\n", " \n", " The scipy_lbfgsb algorithm reported: CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL\n", " \n", " Independent of the convergence criteria used by scipy_lbfgsb, the strength of convergence can be assessed by the following criteria:\n", " \n", " one_step five_steps\n", " relative_criterion_change 15.17 49.8 \n", " relative_params_change 12.32 22.32 \n", " absolute_criterion_change 1.517 4.98 \n", " absolute_params_change 1.232 2.232 \n", " \n", " (***: change <= 1e-10, **: change <= 1e-8, *: change <= 1e-5. Change refers to a change between accepted steps. The first column only considers the last step. The second column considers the last five steps.),\n", " Minimize with 3 free parameters terminated successfully after 3 criterion evaluations, 3 derivative evaluations and 2 iterations.\n", " \n", " The scipy_lbfgsb algorithm reported: CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL\n", " \n", " Independent of the convergence criteria used by scipy_lbfgsb, the strength of convergence can be assessed by the following criteria:\n", " \n", " one_step five_steps\n", " relative_criterion_change 0.4662 14.78 \n", " relative_params_change 2.159 12.16 \n", " absolute_criterion_change 0.04662 1.478 \n", " absolute_params_change 0.2159 1.216 \n", " \n", " (***: change <= 1e-10, **: change <= 1e-8, *: change <= 1e-5. Change refers to a change between accepted steps. The first column only considers the last step. The second column considers the last five steps.)]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res.multistart_info[\"local_optima\"]" ] }, { "cell_type": "code", "execution_count": 9, "id": "43d2f994", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[ value soft_lower_bound soft_upper_bound\n", " 0 -0.3125 -5 10\n", " 1 -2.1875 -5 10\n", " 2 -0.3125 -5 10,\n", " value soft_lower_bound soft_upper_bound\n", " 0 -0.330195 -5 10\n", " 1 -0.330195 -5 10\n", " 2 -1.122663 -5 10]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res.multistart_info[\"start_parameters\"]" ] }, { "cell_type": "code", "execution_count": 10, "id": "fef26723", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[ value soft_lower_bound soft_upper_bound\n", " 0 -0.3125 -5 10\n", " 1 -2.1875 -5 10\n", " 2 -0.3125 -5 10,\n", " value soft_lower_bound soft_upper_bound\n", " 0 -0.330195 -5 10\n", " 1 -0.330195 -5 10\n", " 2 -1.122663 -5 10]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res.multistart_info[\"start_parameters\"]" ] }, { "cell_type": "code", "execution_count": 11, "id": "45ae8c65", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 4.98046875, 8.27636719, 14. , 18.75 ,\n", " 19.04296875, 19.921875 , 21.16699219, 22.92480469,\n", " 29.296875 , 30.76171875, 42.1875 , 48.70605469,\n", " 64.52636719, 66.87011719, 67.45605469, 74.48730469,\n", " 75.65917969, 75.65917969, 79.6875 , 82.32421875,\n", " 87.01171875, 94.921875 , 106.12792969, 110.44921875,\n", " 113.74511719, 120.19042969, 126.85546875, 131.54296875,\n", " 141.796875 , 196.94824219])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "res.multistart_info[\"exploration_results\"]" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.10.8" } }, "nbformat": 4, "nbformat_minor": 5 }