From 6b8f34e7d4695b558c637b28cd6dff67ae35af7e Mon Sep 17 00:00:00 2001 From: Andreas Mueller Date: Mon, 14 Mar 2022 19:37:52 +0000 Subject: [PATCH] Merged PR 333: Initial MlosCore Implementation Adds basic `register` and `suggest` APIs for a couple of optimizer backends: `Skopt`, `Emukit`, `Random`. Includes example notebook for starters. Limited unit testing. Basic function documentation. Azure DevOps CI pipelines for `pylint` and `pytest` via the `mlos_core` `conda` `environment.yml`. Related work items: #274, #279 --- .gitignore | 4 + .pylintrc | 15 + .vscode/settings.json | 30 + Makefile | 68 ++ Notebooks/BayesianOptimization.ipynb | 1038 +++++++++++++++++ README.md | 41 + azure-pipelines.yml | 36 + environment.yml | 29 + mlos_core/__init__.py | 5 + mlos_core/optimizers/__init__.py | 18 + mlos_core/optimizers/bayesian_optimizers.py | 191 +++ mlos_core/optimizers/optimizer.py | 105 ++ mlos_core/optimizers/random_optimizer.py | 55 + .../test/bayesian_optimizers_test.py | 3 + mlos_core/optimizers/test/optimizer_test.py | 35 + .../optimizers/test/random_optimizer_test.py | 3 + mlos_core/runners.py | 46 + mlos_core/spaces/__init__.py | 67 ++ pytest.ini | 8 + setup.py | 27 + 20 files changed, 1824 insertions(+) create mode 100644 .pylintrc create mode 100644 .vscode/settings.json create mode 100644 Makefile create mode 100644 Notebooks/BayesianOptimization.ipynb create mode 100644 azure-pipelines.yml create mode 100644 environment.yml create mode 100644 mlos_core/__init__.py create mode 100644 mlos_core/optimizers/__init__.py create mode 100644 mlos_core/optimizers/bayesian_optimizers.py create mode 100644 mlos_core/optimizers/optimizer.py create mode 100644 mlos_core/optimizers/random_optimizer.py create mode 100644 mlos_core/optimizers/test/bayesian_optimizers_test.py create mode 100644 mlos_core/optimizers/test/optimizer_test.py create mode 100644 mlos_core/optimizers/test/random_optimizer_test.py create mode 100644 mlos_core/runners.py create mode 100644 mlos_core/spaces/__init__.py create mode 100644 pytest.ini create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index b484629ef4..085ae64c74 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,7 @@ cython_debug/ # vim swap files .*.swp + +.conda-env.build-stamp +.pylint.build-stamp +.pytest.build-stamp diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..b3451eac25 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,15 @@ +# vim: set ft=dosini: + +[MAIN] + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=9.5 + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=132 + +[MESSAGE CONTROL] + +disable=no-else-return diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..9f88d34589 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "python.defaultInterpreterPath": "${env:HOME}/.conda/envs/mlos_core/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.testing.pytestEnabled": true, + "cSpell.ignoreWords": [ + "Skopt", + "conda", + "configspace", + "emukit", + "gpbo", + "ipykernel", + "iterrows", + "jupyterlab", + "matplotlib", + "mlos", + "nsmallest", + "numpy", + "pylint", + "pyplot", + "pytest", + "scikit", + "scipy", + "seaborn", + "setuptools", + "tolist", + "xlabel", + "ylabel" + ] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..d3403afddd --- /dev/null +++ b/Makefile @@ -0,0 +1,68 @@ +CONDA_DEFAULT_ENV := mlos_core +PYTHON_FILES := $(shell find mlos_core/ -type f -name '*.py' 2>/dev/null) + +.PHONY: all +all: check test dist doc + +.PHONY: conda-env +conda-env: .conda-env.build-stamp + +.conda-env.build-stamp: environment.yml setup.py + conda env list -q | grep -q "^${CONDA_DEFAULT_ENV} " || conda env create -q -f environment.yml + conda env update -q -n ${CONDA_DEFAULT_ENV} --prune -f environment.yml + touch .conda-env.build-stamp + +.PHONY: check +check: pylint + +.PHONY: pylint +pylint: conda-env .pylint.build-stamp + +.pylint.build-stamp: $(PYTHON_FILES) .pylintrc + conda run -n ${CONDA_DEFAULT_ENV} pylint -j0 mlos_core + touch .pylint.build-stamp + +.PHONY: test +test: pytest + +.PHONY: pytest +pytest: conda-env .pytest.build-stamp + +# FIXME: There's an issue with pytest-xdist not reaping children when +# pytest-timeout fails which we're currently using because somehow pytest is +# causing module imports to hang. +# pytest -n auto --cov=mlos_core --cov-report=xml mlos_core/ +.pytest.build-stamp: $(PYTHON_FILES) pytest.ini + conda run -n ${CONDA_DEFAULT_ENV} pytest --cov=mlos_core --cov-report=xml mlos_core/ + touch .pytest.build-stamp + +.PHONY: dist +dist: bdist_wheel + +.PHONY: bdist_wheel +bdist_wheel: conda-env dist/mlos_core-*-py3-none-any.whl + +dist/mlos_core-*-py3-none-any.whl: setup.py $(PYTHON_FILES) + conda run -n ${CONDA_DEFAULT_ENV} python3 setup.py bdist_wheel + +.PHONY: doc +doc: + # TODO + @false + +.PHONY: clean-check +clean-check: + rm -f .pylint.build-stamp + +.PHONY: clean-test +clean-test: + rm -f .pytest.build-stamp + +.PHONY: dist-clean +dist-clean: + rm -rf build dist + +.PHONY: clean +clean: clean-check clean-test dist-clean + rm -f .conda-dev.build-stamp + #rm -rf mlos_core.egg-info diff --git a/Notebooks/BayesianOptimization.ipynb b/Notebooks/BayesianOptimization.ipynb new file mode 100644 index 0000000000..9ea750b287 --- /dev/null +++ b/Notebooks/BayesianOptimization.ipynb @@ -0,0 +1,1038 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bayesian Optimization Example Using `mlos_core`" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "# Define a fake \"performance\" function.\n", + "# In an actual application, we would not have access to this function directly.\n", + "# Instead, we could only measure the outcome by running an experiment, such as timing\n", + "# a particular run of the system.\n", + "def f(x):\n", + " return (6*x-2)**2*np.sin(12*x-4)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Objective (i.e. performance)')" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# define a domain to evaluate\n", + "line = np.linspace(0, 1)\n", + "# evaluate the function\n", + "values = f(line)\n", + "# plot the function\n", + "plt.plot(line, values)\n", + "plt.xlabel(\"Input (parameter)\")\n", + "plt.ylabel(\"Objective (i.e. performance)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "x, Type: UniformFloat, Range: [0.0, 1.0], Default: 0.5" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import ConfigSpace as CS\n", + "\n", + "# Start defining a ConfigurationSpace for the Optimizer to search.\n", + "input_space = CS.ConfigurationSpace(seed=1234)\n", + "\n", + "# Add a single continuous input dimension between 0 and 1.\n", + "input_space.add_hyperparameter(CS.UniformFloatHyperparameter(name='x', lower=0, upper=1))" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "import mlos_core" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "# Choose an optimizer.\n", + "\n", + "#optimizer = mlos_core.optimizers.RandomOptimizer(input_space)\n", + "\n", + "# can optionally select a different base estimator for some optimizer backends\n", + "#optimizer = mlos_core.optimizers.SkoptOptimizer(input_space, base_estimator='gp')\n", + "\n", + "optimizer = mlos_core.optimizers.EmukitOptimizer(input_space)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "EmukitOptimizer(parameter_space=Configuration space object:\n", + " Hyperparameters:\n", + " x, Type: UniformFloat, Range: [0.0, 1.0], Default: 0.5\n", + ")" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Inspect the chosen optimizer\n", + "optimizer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can run the actual optimization which will carry out the steps outlined above." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.749606 \n", + " 0 -5.990244\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.85683 \n", + " 0 -0.01203\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.399354 \n", + " 0 0.111714\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.931236 \n", + " 0 10.013951\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.304241 \n", + " 0 -0.010422\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.763036 \n", + " 0 -6.002567\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.161687 \n", + " 0 -0.936365\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.402348 \n", + " 0 0.126318\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.137106 \n", + " 0 -0.981619\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.308416 \n", + " 0 -0.006584\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.651748 \n", + " 0 -2.293312\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.0 \n", + " 0 3.02721\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757222 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757268 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757265 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n" + ] + } + ], + "source": [ + "def run_optimization(optimizer):\n", + " # get a new config value suggestion to try from the optimizer.\n", + " suggested_value = optimizer.suggest()\n", + " # suggested value are dictionary-like, keys are input space parameter names\n", + " # evaluate target function\n", + " target_value = f(suggested_value['x'])\n", + " print(suggested_value, \"\\n\", target_value)\n", + " optimizer.register(suggested_value, target_value)\n", + "\n", + "# run for some iterations\n", + "n_iterations = 15\n", + "for i in range(n_iterations):\n", + " run_optimization(optimizer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After 15 iterations, the model is likely to have captured the general shape, but probably not have found the actual optimum:" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "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", + " \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", + "
xscore
00.749606-5.990244
00.856830-0.012030
00.3993540.111714
00.93123610.013951
00.304241-0.010422
00.763036-6.002567
00.161687-0.936365
00.4023480.126318
00.137106-0.981619
00.308416-0.006584
00.651748-2.293312
00.0000003.027210
00.757222-6.020740
00.757268-6.020740
00.757265-6.020740
\n", + "
" + ], + "text/plain": [ + " x score\n", + "0 0.749606 -5.990244\n", + "0 0.856830 -0.012030\n", + "0 0.399354 0.111714\n", + "0 0.931236 10.013951\n", + "0 0.304241 -0.010422\n", + "0 0.763036 -6.002567\n", + "0 0.161687 -0.936365\n", + "0 0.402348 0.126318\n", + "0 0.137106 -0.981619\n", + "0 0.308416 -0.006584\n", + "0 0.651748 -2.293312\n", + "0 0.000000 3.027210\n", + "0 0.757222 -6.020740\n", + "0 0.757268 -6.020740\n", + "0 0.757265 -6.020740" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "optimizer.get_observations()" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# evaluate the surrogate function\n", + "#\n", + "surrogate_predictions = optimizer.surrogate_predict(line.reshape(-1, 1))\n", + "\n", + "# plot the observations\n", + "#\n", + "observations = optimizer.get_observations()\n", + "plt.scatter(observations.x, observations.score, label='observed points')\n", + "\n", + "# plot the true function (usually unknown)\n", + "#\n", + "plt.plot(line, values, label='true function')\n", + "\n", + "# plot the surrogate\n", + "#\n", + "# alpha = optimizer_config.experiment_designer_config.confidence_bound_utility_function_config.alpha\n", + "# t_values = t.ppf(1 - alpha / 2.0, surrogate_predictions['predicted_value_degrees_of_freedom'])\n", + "# ci_radii = t_values * np.sqrt(surrogate_predictions['predicted_value_variance'])\n", + "# value = surrogate_predictions['predicted_value']\n", + "plt.plot(line, surrogate_predictions, label='surrogate predictions')\n", + "#plt.fill_between(line, value - ci_radii, value + ci_radii, alpha=.1)\n", + "#plt.plot(line, -optimizer.experiment_designer.utility_function(optimization_problem.construct_feature_dataframe(pd.DataFrame({'x': line}))), ':', label='utility_function')\n", + "plt.ylabel(\"Objective function f (performance)\")\n", + "plt.xlabel(\"Input variable\")\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can find the best value according to the current surrogate with the ``optimum`` method:" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
xscore
00.757265-6.02074
\n", + "
" + ], + "text/plain": [ + " x score\n", + "0 0.757265 -6.02074" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "optimizer.get_best_observation()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can run more iterations to improve the surrogate model and the optimum that is found:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757268 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757279 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757249 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757263 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757251 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757199 \n", + " 0 -6.020739\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756909 \n", + " 0 -6.020678\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757341 \n", + " 0 -6.020735\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757308 \n", + " 0 -6.020738\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757588 \n", + " 0 -6.020679\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757245 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.75723 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757232 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757198 \n", + " 0 -6.020739\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757247 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756861 \n", + " 0 -6.02066\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757269 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757272 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756987 \n", + " 0 -6.020704\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757005 \n", + " 0 -6.020708\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.75734 \n", + " 0 -6.020736\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756433 \n", + " 0 -6.020385\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.543304 \n", + " 0 0.924707\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.758033 \n", + " 0 -6.020411\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756176 \n", + " 0 -6.020128\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757255 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756916 \n", + " 0 -6.020681\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757262 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757289 \n", + " 0 -6.020739\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.758055 \n", + " 0 -6.020392\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.758255 \n", + " 0 -6.020198\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.756999 \n", + " 0 -6.020707\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756967 \n", + " 0 -6.020698\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756791 \n", + " 0 -6.020629\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757265 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757843 \n", + " 0 -6.020551\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757404 \n", + " 0 -6.020727\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757109 \n", + " 0 -6.02073\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.75725 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.75664 \n", + " 0 -6.020543\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757261 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.758124 \n", + " 0 -6.02033\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757954 \n", + " 0 -6.020474\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.757142 \n", + " 0 -6.020734\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.75725 \n", + " 0 -6.02074\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756811 \n", + " 0 -6.020638\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756975 \n", + " 0 -6.0207\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.757143 \n", + " 0 -6.020734\n", + "Name: x, dtype: float64\n", + " x\n", + "0 0.756874 \n", + " 0 -6.020665\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n", + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " x\n", + "0 0.756967 \n", + " 0 -6.020698\n", + "Name: x, dtype: float64\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " /home/bpkroth/.conda/envs/mlos_core/lib/python3.9/site-packages/paramz/transformations.py:111: RuntimeWarning:overflow encountered in expm1\n" + ] + } + ], + "source": [ + "# run for more iterations\n", + "n_iterations = 50\n", + "for i in range(n_iterations):\n", + " run_optimization(optimizer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is some improvement in the optimum:" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
xscore
00.757249-6.02074
\n", + "
" + ], + "text/plain": [ + " x score\n", + "0 0.757249 -6.02074" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "optimizer.get_best_observation()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now visualize the surrogate model and optimization process again. The points are colored according to the iteration number, with dark blue points being early in the process and yellow points being later. You can see that at the end of the optimization, the points start to cluster around the optimum." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 53, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaIAAAEWCAYAAAAkUJMMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAA/7klEQVR4nO3deXhU5fXA8e/JRkJYQiBA2CM7yCogCiqLFLGgYtGKGyqKtrVarbZa25/W1pbW2rq0LigormgtItgqKIiIomxhRyBA2CUQCISQfc7vj7nBgFkmy8ydmZzP88wzM3fuve+5kMzJfe97zyuqijHGGOOWCLcDMMYYU7dZIjLGGOMqS0TGGGNcZYnIGGOMqywRGWOMcZUlImOMMa6yRGSMMcZVloiMMca4yhKRMcYYV1kiMqaaRKSjiBwRkf7O+1YiclhEhrkbmTGhRazEjzHVJyK3AfcC5wDvAetV9T53ozImtFgiMqaGRGQukAIoMFBV810OyZiQYl1zxtTci8DZwDOWhIypOjsjMqYGRKQBsBb4FBgD9FLVI+5GZUxosURkTA2IyHSgoapeLSLTgARVvdrtuIwJJdY1Z0w1icjlwCXAHc6ie4H+InKde1EZE3rsjMgYY4yr7IzIGGOMqywRGWOMcZUlImOMMa6yRGSMMcZVUW4HUBUREREaFxfndhjGGBNSTp48qaoatCceIZWI4uLiyMnJcTsMY4wJKSKS63YMFQnaDGmMMaZusERkjDHGVX5PRCLSVkQ+FZHNIrJRRO52lieKyMciss15buLvWIwxxgQfv1dWEJFkIFlVV4tIQ2AVcAVwE3BEVaeKyANAE1X9dUX7io+P1zOvERUWFrJ3717y8vL8Er8JT7GxsbRp04bo6Gi3QzHG70TkpKrGux1HeQJe4kdE3gf+6TyGqeoBJ1ktVtWuFW1bViLauXMnDRs2pGnTpoiI3+I24UNVyczMJDs7m5SUFLfDMcbvgj0RBfQakYh0APoBXwMtVPUAgPPcvDr7zMvLsyRkqkREaNq0qZ1FGxMkApaInHlb/gP8QlWPV2G7KSKyUkRWFhUVlbdOLUVp6gr7mTEmeAQkEYlINN4k9IaqznYWH3S65EquI2WUta2qTlPVAao6ICoqpG57MsaYmss7BvMfgsztbkfiN37/Zhfvn57Tgc2q+vdSH80FJgFTnef3a6O9efPm1cZuThk3blyt7q86nnzySaZMmUL9+vV93ubzzz/njjvuIDo6mmXLlhHKFSluvfVW7r33Xnr06FHuOnPmzKFLly4VrmNMSNr+KSz7J3QbC007uh2NXwTiFGMIcAOwXkTWOMt+gzcBvSMik4HdwFUBiCUkPfnkk1x//fVVSkRvvPEG9913HzfffLMfI/tOcXExkZGRftn3Sy+9VOk6c+bMYezYsZaIQkB1/1gMhj8KXbFtAcQmQJuBbkfiN37vmlPVpaoqqtpbVfs6j/+paqaqjlTVzs7zEX/H4i+vvvoqvXv3pk+fPtxwww0A7Nq1i5EjR9K7d29GjhzJ7t27Abjpppt49913T23boEEDABYvXsywYcOYMGEC3bp147rrrkNVefrpp9m/fz/Dhw9n+PDh32t74cKF9OvXj169enHLLbeQn5/PSy+9xDvvvMOjjz7Kddd9f7LQxx57jK5du3LxxRczceJE/va3vwEwbNgwVq5cCcDhw4fp0KED4E0y999/PwMHDqR379688MILp2IePnw41157Lb169eJ3v/sdTz311Kl2HnroIZ5++unT2k5PT6dbt25MmjSJ3r17M2HCBE6ePFnusZwZV4MGDXjooYfo06cPgwcP5uDBg3z55ZfMnTuX+++/n759+7J9+3aefvppevToQe/evbnmmmuq8t9pTPDweLyJqNPFEBm+lyasskINbdy4kccee4xFixaxdu3aU1/Ed955JzfeeCPr1q3juuuu46677qp0X6mpqTz55JNs2rSJHTt28MUXX3DXXXfRqlUrPv30Uz799NPT1s/Ly+Omm27i7bffZv369RQVFfHcc89x6623ctlll/H444/zxhtvnLbNqlWrmDVrFqmpqcyePZsVK1ZUGtf06dNp3LgxK1asYMWKFbz44ovs3LkTgOXLl/PYY4+xadMmJk+ezMyZMwHweDzMmjWrzES4ZcsWpkyZwrp162jUqBHPPvtsucdyppycHAYPHszatWu58MILefHFFzn//PNPHe+aNWvo2LEjU6dOJTU1lXXr1vH8889XeozGBKX9qZBzCLqMdjsSv7JEVEOLFi1iwoQJNGvWDIDExEQAli1bxrXXXgvADTfcwNKlSyvd16BBg2jTpg0RERH07duX9PT0CtffsmULKSkpdOnSBYBJkyaxZMmSCrf5/PPPGT9+PPXr16dRo0Zc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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# evaluate the surrogate function\n", + "#\n", + "surrogate_predictions = optimizer.surrogate_predict(line.reshape(-1, 1))\n", + "\n", + "# plot the observations\n", + "#\n", + "observations = optimizer.get_observations()\n", + "plt.scatter(observations.x, observations.score, label='observed points')\n", + "\n", + "# plot true function (usually unknown)\n", + "#\n", + "plt.plot(line, values, label='true function')\n", + "\n", + "# plot the surrogate\n", + "#\n", + "#ci_raduii = surrogate_predictions['prediction_ci']\n", + "plt.plot(line, surrogate_predictions, label='surrogate predictions')\n", + "#plt.fill_between(line, value - ci_radii, value + ci_radii, alpha=.1)\n", + "#plt.plot(line, -optimizer.utility_function(pd.DataFrame({'x': line})), ':', label='utility_function')\n", + "\n", + "ax = plt.gca()\n", + "ax.set_ylabel(\"Objective function f\")\n", + "ax.set_xlabel(\"Input variable\")\n", + "bins_axes = ax.twinx()\n", + "bins_axes.set_ylabel(\"Points sampled\")\n", + "pd.DataFrame(observations.x).hist(bins=20, ax=bins_axes, alpha=.3, color='k', label=\"count of query points\")\n", + "plt.legend()" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "dcb43bafd5ce954ead015d0d89e27da6852c23ac7a7b5a1213fc2fecbe919e66" + }, + "kernelspec": { + "display_name": "Python [conda env:mlos_core] *", + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/README.md b/README.md index b90dfd9e2f..ad7a63c44b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,47 @@ It is intended to provide a simplified, easier to consume (e.g. via `pip`), with For both design requires intend to reuse as much OSS libraries as possible. +## Getting Started + +0. Create the `mlos_core` Conda environment. + + ```sh + conda env create -f environment.yml + ``` + + or + + ```sh + # This will also ensure the environment is update to date using "conda env update -f environment.yml" + make conda-env + ``` + +1. Initialize the shell environment. + + ```sh + conda activate mlos_core + ``` + +2. Run the [`BayesianOptimization.ipynb`](./Notebooks/BayesianOptimization.ipynb) notebook. + +## Distributing + +1. Build the *wheel* file. + + ```sh + make dist + ``` + +2. Install it (e.g. after copying it somewhere else). + + ```sh + # this will install it with emukit support: + pip install dist/mlos_core-0.0.1-py3-none-any.whl[emukit] + + # this will install it with skopt support: + pip install dist/mlos_core-0.0.1-py3-none-any.whl[skopt] + ``` + ## See Also [MlosCoreApiDesign.docx](https://microsoft.sharepoint.com/:w:/t/CISLGSL/ESAS3G9q4P5Hoult9uqTfB4B3xh2v6yUfp3YNgIvoyR_IA?e=B6klWZ) diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..74e2c7421a --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,36 @@ +trigger: +#- main + branches: + include: + - '*' + +pr: + branches: + include: + - '*' + +pool: + vmImage: ubuntu-latest +# TODO: test multiple versions of python. +#strategy: +# matrix: +# Python39: +# python.version: '3.9' + +steps: +- bash: echo "##vso[task.prependpath]$CONDA/bin" + displayName: 'Add conda to PATH' +#- task: UsePythonVersion@0 +# inputs: +# versionSpec: '$(python.version)' +# displayName: 'Use Python $(python.version)' +- bash: make conda-env + displayName: 'Create mlos_core conda environment' +- bash: conda run -n mlos_core pip install pytest-azurepipelines + displayName: 'Install pytest-azurepipelines' +- bash: make check + displayName: 'Run lint checks' +- bash: make test + displayName: 'Run tests' +- bash: make dist + displayName: 'Generate binary distribution files' diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000000..73f3af64d7 --- /dev/null +++ b/environment.yml @@ -0,0 +1,29 @@ +name: mlos_core +channels: + - defaults +dependencies: + - scikit-learn + - scipy + - numpy + - pandas + - configspace + - pip + - pylint + - pytest + - setuptools + - jupyterlab + - jupyter + - ipykernel + - nb_conda_kernels + - matplotlib + - seaborn + - python + - gcc_linux-64 + - pip: + - scikit-optimize + - emukit + - "--editable ." + - pytest-cov + - pytest-forked + - pytest-xdist + - pytest-timeout diff --git a/mlos_core/__init__.py b/mlos_core/__init__.py new file mode 100644 index 0000000000..871f32bb54 --- /dev/null +++ b/mlos_core/__init__.py @@ -0,0 +1,5 @@ +""" +Basic initializer module for the mlos_core package. +""" + +from mlos_core import optimizers diff --git a/mlos_core/optimizers/__init__.py b/mlos_core/optimizers/__init__.py new file mode 100644 index 0000000000..d1e7f58c57 --- /dev/null +++ b/mlos_core/optimizers/__init__.py @@ -0,0 +1,18 @@ +""" +Basic initializer module for the mlos_core optimizers. +""" + +from enum import Enum + +import ConfigSpace + +from mlos_core.optimizers.optimizer import BaseOptimizer +from mlos_core.optimizers.random_optimizer import RandomOptimizer +from mlos_core.optimizers.bayesian_optimizers import EmukitOptimizer, SkoptOptimizer + +__all__ = [ + 'BaseOptimizer', + 'RandomOptimizer', + 'EmukitOptimizer', + 'SkoptOptimizer', +] diff --git a/mlos_core/optimizers/bayesian_optimizers.py b/mlos_core/optimizers/bayesian_optimizers.py new file mode 100644 index 0000000000..c7bd234bbb --- /dev/null +++ b/mlos_core/optimizers/bayesian_optimizers.py @@ -0,0 +1,191 @@ +""" +Contains the wrapper classes for different Bayesian optimizers. +""" + +from abc import ABCMeta, abstractmethod + +import ConfigSpace +import numpy as np +import pandas as pd + +from mlos_core.optimizers.optimizer import BaseOptimizer +from mlos_core.spaces import configspace_to_skopt_space, configspace_to_emukit_space + +# TODO: provide a default optimizer. + +class BaseBayesianOptimizer(BaseOptimizer, metaclass=ABCMeta): + """Abstract base class defining the interface for Bayesian optimization. """ + @abstractmethod + def surrogate_predict(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + """Obtain a prediction from this Bayesian optimizer's surrogate model for the given configuration(s). + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + pass # pylint: disable=unnecessary-pass + + @abstractmethod + def acquisition_function(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + """Invokes the acquisition function from this Bayesian optimizer for the given configuration. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + pass # pylint: disable=unnecessary-pass + +class EmukitOptimizer(BaseBayesianOptimizer): + """Wrapper class for Emukit based Bayesian optimization. + + Parameters + ---------- + parameter_space : ConfigSpace.ConfigurationSpace + The parameter space to optimize. + """ + def __init__(self, parameter_space: ConfigSpace.ConfigurationSpace): + super().__init__(parameter_space) + self.emukit_parameter_space = configspace_to_emukit_space(parameter_space) + self.gpbo = None + + def register(self, configurations: pd.DataFrame, scores: pd.Series, context: pd.DataFrame = None): + """Registers the given configurations and scores. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + scores : pd.Series + Scores from running the configurations. The index is the same as the index of the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + from emukit.core.loop.user_function_result import UserFunctionResult # pylint: disable=import-outside-toplevel + self._observations.append((configurations, scores, context)) + if context is not None: + # not sure how that works here? + raise NotImplementedError + if self.gpbo is None: + # we're in the random initialization phase + # just remembering the observation above is enough + return + results = [] + for (_, config), score in zip(configurations.iterrows(), scores): + results.append(UserFunctionResult(config, np.array([score]))) + self.gpbo.loop_state.update(results) + self.gpbo._update_models() # pylint: disable=protected-access + + def suggest(self, context: pd.DataFrame = None): + """Suggests a new configuration. + + Parameters + ---------- + context : pd.DataFrame + Not Yet Implemented. + + Returns + ------- + configuration : pd.DataFrame + Pandas dataframe with a single row. Column names are the parameter names. + """ + from emukit.examples.gp_bayesian_optimization.single_objective_bayesian_optimization import GPBayesianOptimization # pylint: disable=import-outside-toplevel + if context is not None: + raise NotImplementedError() + if len(self._observations) <= 10: + from emukit.core.initial_designs import RandomDesign # pylint: disable=import-outside-toplevel + config = RandomDesign(self.emukit_parameter_space).get_samples(1) + else: + if self.gpbo is None: + # this should happen exactly once, when calling the 11th time + observations = self.get_observations() + self.gpbo = GPBayesianOptimization( + variables_list=self.emukit_parameter_space.parameters, + X=np.array(observations.drop(columns='score')), + Y=np.array(observations[['score']])) + # this should happen any time after the initial model is created + config = self.gpbo.get_next_points(results=[]) + return pd.DataFrame(config, columns=self.parameter_space.get_hyperparameter_names()) + + def register_pending(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + raise NotImplementedError() + + def surrogate_predict(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + # TODO: return variance in some way + mean_predictions, variance_predictions = self.gpbo.model.predict(configurations) + return mean_predictions + + def acquisition_function(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + raise NotImplementedError() + + +class SkoptOptimizer(BaseBayesianOptimizer): + """Wrapper class for Skopt based Bayesian optimization. + + Parameters + ---------- + parameter_space : ConfigSpace.ConfigurationSpace + The parameter space to optimize. + """ + def __init__(self, parameter_space: ConfigSpace.ConfigurationSpace, base_estimator = 'gp'): + from skopt import Optimizer as Optimizer_Skopt # pylint: disable=import-outside-toplevel + self.base_optimizer = Optimizer_Skopt(configspace_to_skopt_space(parameter_space), base_estimator=base_estimator) + super().__init__(parameter_space) + + def register(self, configurations: pd.DataFrame, scores: pd.Series, context: pd.DataFrame = None): + """Registers the given configurations and scores. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + scores : pd.Series + Scores from running the configurations. The index is the same as the index of the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + self._observations.append((configurations, scores, context)) + + if context is not None: + raise NotImplementedError + self.base_optimizer.tell(np.array(configurations).tolist(), np.array(scores).tolist()) + + def suggest(self, context: pd.DataFrame = None): + """Suggests a new configuration. + + Parameters + ---------- + context : pd.DataFrame + Not Yet Implemented. + + Returns + ------- + configuration : pd.DataFrame + Pandas dataframe with a single row. Column names are the parameter names. + """ + if context is not None: + raise NotImplementedError + return pd.DataFrame([self.base_optimizer.ask()], columns=self.parameter_space.get_hyperparameter_names()) + + def register_pending(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + raise NotImplementedError() + + def surrogate_predict(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + if context is not None: + raise NotImplementedError + return self.base_optimizer.models[-1].predict(configurations) + + def acquisition_function(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + # This seems actually non-trivial to get out of skopt, so maybe we actually shouldn't implement this. + raise NotImplementedError() diff --git a/mlos_core/optimizers/optimizer.py b/mlos_core/optimizers/optimizer.py new file mode 100644 index 0000000000..0bfa9714f6 --- /dev/null +++ b/mlos_core/optimizers/optimizer.py @@ -0,0 +1,105 @@ +""" +Contains the BaseOptimizer abstract class. +""" + +from abc import ABCMeta, abstractmethod + +import ConfigSpace +import pandas as pd + +class BaseOptimizer(metaclass=ABCMeta): + """Optimizer abstract base class defining the basic interface. + + Parameters + ---------- + parameter_space : ConfigSpace.ConfigurationSpace + The parameter space to optimize. + """ + def __init__(self, parameter_space: ConfigSpace.ConfigurationSpace): + self.parameter_space: ConfigSpace.ConfigurationSpace = parameter_space + self._observations = [] + self._pending_observations = [] + + def __repr__(self): + return f"{self.__class__.__name__}(parameter_space={self.parameter_space})" + + @abstractmethod + def register(self, configurations: pd.DataFrame, scores: pd.Series, context: pd.DataFrame = None): + """Registers the given configurations and scores. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + scores : pd.Series + Scores from running the configurations. The index is the same as the index of the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + pass # pylint: disable=unnecessary-pass + + @abstractmethod + def suggest(self, context: pd.DataFrame = None): + """Suggests a new configuration. + + Parameters + ---------- + context : pd.DataFrame + Not Yet Implemented. + + Returns + ------- + configuration : pd.DataFrame + Pandas dataframe with a single row. Column names are the parameter names. + """ + pass # pylint: disable=unnecessary-pass + + @abstractmethod + def register_pending(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + """Registers the given configurations as "pending". + That is it say, it has been suggested by the optimizer, and an experiment trial has been started. + This can be useful for executing multiple trials in parallel, retry logic, etc. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + pass # pylint: disable=unnecessary-pass + + def get_observations(self): + """Returns the observations as a dataframe. + + Returns + ------- + observations : pd.DataFrame + Dataframe of observations. The columns are parameter names and "score" for the score, each row is an observation. + """ + configs = pd.concat([config for config, _, _ in self._observations]) + scores = pd.concat([score for _, score, _ in self._observations]) + try: + contexts = pd.concat([context for _, _, context in self._observations]) + except ValueError: + contexts = None + configs["score"] = scores + if contexts is not None: + configs = pd.concat([configs, contexts], axis=1) + return configs + + def get_best_observation(self): + """Returns the best observation so far as a dataframe. + + Returns + ------- + best_observation : pd.DataFrame + Dataframe with a single row containing the best observation. The columns are parameter names and "score" for the score. + """ + if len(self._observations) == 0: + raise ValueError("No observations registered yet.") + observations = self.get_observations() + return observations.nsmallest(1, columns='score') diff --git a/mlos_core/optimizers/random_optimizer.py b/mlos_core/optimizers/random_optimizer.py new file mode 100644 index 0000000000..c36e091435 --- /dev/null +++ b/mlos_core/optimizers/random_optimizer.py @@ -0,0 +1,55 @@ +""" +Contains the RandomOptimizer class. +""" + +import pandas as pd + +from mlos_core.optimizers.optimizer import BaseOptimizer + +class RandomOptimizer(BaseOptimizer): + """Optimizer class that produces random suggestions. + Useful for baseline comparison against Bayesian optimizers. + + Parameters + ---------- + parameter_space : ConfigSpace.ConfigurationSpace + The parameter space to optimize. + """ + def register(self, configurations: pd.DataFrame, scores: pd.Series, context: pd.DataFrame = None): + """Registers the given configurations and scores. + + Doesn't do anything on the RandomOptimizer except storing configurations for logging. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + scores : pd.Series + Scores from running the configurations. The index is the same as the index of the configurations. + + context : None + Not Yet Implemented. + """ + self._observations.append((configurations, scores, context)) + # should we pop them from self.pending_observations? + + def suggest(self, context: pd.DataFrame = None): + """Suggests a new configuration. + + Sampled at random using ConfigSpace. + + Parameters + ---------- + context : None + Not Yet Implemented. + + Returns + ------- + configuration : pd.DataFrame + Pandas dataframe with a single row. Column names are the parameter names. + """ + return self.parameter_space.sample_configuration().get_dictionary() + + def register_pending(self, configurations: pd.DataFrame, context: pd.DataFrame = None): + self._pending_observations.append((configurations, context)) diff --git a/mlos_core/optimizers/test/bayesian_optimizers_test.py b/mlos_core/optimizers/test/bayesian_optimizers_test.py new file mode 100644 index 0000000000..310c4eba02 --- /dev/null +++ b/mlos_core/optimizers/test/bayesian_optimizers_test.py @@ -0,0 +1,3 @@ +""" +Tests for Bayesian Optimizers. +""" diff --git a/mlos_core/optimizers/test/optimizer_test.py b/mlos_core/optimizers/test/optimizer_test.py new file mode 100644 index 0000000000..247c04192e --- /dev/null +++ b/mlos_core/optimizers/test/optimizer_test.py @@ -0,0 +1,35 @@ +""" +Tests for Bayesian Optimizers. +""" + +from typing import Type + +import pytest + +import ConfigSpace as CS + +from mlos_core.optimizers import BaseOptimizer, EmukitOptimizer, SkoptOptimizer, RandomOptimizer + +@pytest.mark.parametrize(('optimizer_class', 'kwargs'), [ + # FIXME: hangs on emukit import + #(EmukitOptimizer, {}), + (SkoptOptimizer, {'base_estimator': 'gp'}), + (RandomOptimizer, {}) +]) +def test_create_optimizer_and_suggest(optimizer_class: Type[BaseOptimizer], kwargs): + """ + Helper method for testing optimizers. + """ + # Start defining a ConfigurationSpace for the Optimizer to search. + input_space = CS.ConfigurationSpace(seed=1234) + + # Add a single continuous input dimension between 0 and 1. + input_space.add_hyperparameter(CS.UniformFloatHyperparameter(name='x', lower=0, upper=1)) + + optimizer = optimizer_class(input_space, **kwargs) + assert optimizer is not None + + assert optimizer.parameter_space is not None + + suggestion = optimizer.suggest() + assert suggestion is not None diff --git a/mlos_core/optimizers/test/random_optimizer_test.py b/mlos_core/optimizers/test/random_optimizer_test.py new file mode 100644 index 0000000000..76520e9984 --- /dev/null +++ b/mlos_core/optimizers/test/random_optimizer_test.py @@ -0,0 +1,3 @@ +""" +Tests for random optimizer. +""" diff --git a/mlos_core/runners.py b/mlos_core/runners.py new file mode 100644 index 0000000000..6b3bfbceae --- /dev/null +++ b/mlos_core/runners.py @@ -0,0 +1,46 @@ +""" +Contains classes related to experiment exectution runners. +These classes contain the policies for managing things like retries and failed +configs when interacting with the optimizer(s). +""" + +# TODO: Implement retry/failure handling logic. + +class ExperimentRunner: + """Manages pending observations for parallel & asynchronous optimization.""" + def __init__(self, optimizer): + self.optimizer = optimizer + + def register(self, configurations, scores, context=None): + """Registers the given configurations and scores with the optimizer associated with this ExperimentRunner. + + Parameters + ---------- + configurations : pd.DataFrame + Dataframe of configurations / parameters. The columns are parameter names and the rows are the configurations. + + scores : pd.Series + Scores from running the configurations. The index is the same as the index of the configurations. + + context : pd.DataFrame + Not Yet Implemented. + """ + self.optimizer.register(configurations, scores, context) + + def suggest(self, configurations, context=None): + """Gets a new configuration suggestion from the optimizer associated + with this ExperimentRunner and automatically registers it as "pending", + under the assumption that it will be executed as an experiment trial. + + Parameters + ---------- + context : pd.DataFrame + Not Yet Implemented. + + Returns + ------- + configuration : pd.DataFrame + Pandas dataframe with a single row. Column names are the parameter names. + """ + configurations = self.optimizer.suggest(context) + self.optimizer.register_pending(configurations, context) diff --git a/mlos_core/spaces/__init__.py b/mlos_core/spaces/__init__.py new file mode 100644 index 0000000000..943ee93146 --- /dev/null +++ b/mlos_core/spaces/__init__.py @@ -0,0 +1,67 @@ +""" +Contains some helper functions for converting config +""" + +import ConfigSpace +import numpy as np + + +def configspace_to_skopt_space(config_space: ConfigSpace.ConfigurationSpace): + """Converts a ConfigSpace.ConfigurationSpace to a list of skopt spaces. + + Parameters + ---------- + config_space : ConfigSpace.ConfigurationSpace + Input configuration space. + + Returns + ------- + list of skopt.space.Space + """ + import skopt.space # pylint: disable=import-outside-toplevel + def _one_parameter_convert(parameter): + if isinstance(parameter, ConfigSpace.UniformFloatHyperparameter): + return skopt.space.Real( + low=parameter.lower, + high=parameter.upper, + prior='uniform' if not parameter.log else 'log-uniform', + name=parameter.name) + elif isinstance(parameter, ConfigSpace.UniformIntegerHyperparameter): + return skopt.space.Integer( + low=parameter.lower, + high=parameter.upper, + prior='uniform' if not parameter.log else 'log-uniform', + name=parameter.name) + elif isinstance(parameter, ConfigSpace.CategoricalHyperparameter): + return skopt.space.Categorical(categories=parameter.choices, prior=parameter.weights, name=parameter.name) + raise ValueError(f"Type of parameter {parameter} ({type(parameter)}) not supported.") + + return [_one_parameter_convert(param) for param in config_space.get_hyperparameters()] + + +def configspace_to_emukit_space(config_space: ConfigSpace.ConfigurationSpace): + """Converts a ConfigSpace.ConfigurationSpace to emukit.core.ParameterSpace. + + Parameters + ---------- + config_space : ConfigSpace.ConfigurationSpace + Input configuration space. + + Returns + ------- + emukit.core.ParameterSpace + """ + import emukit.core # pylint: disable=import-outside-toplevel + def _one_parameter_convert(parameter): + if parameter.log: + raise ValueError("Emukit doesn't support log parameters.") + if isinstance(parameter, ConfigSpace.UniformFloatHyperparameter): + return emukit.core.ContinuousParameter(name=parameter.name, min_value=parameter.lower, max_value=parameter.upper) + elif isinstance(parameter,ConfigSpace.UniformIntegerHyperparameter): + return emukit.core.DiscreteParameter(name=parameter.name, domain=np.arange(parameter.lower, parameter.upper+1)) + elif isinstance(parameter, ConfigSpace.CategoricalHyperparameter): + encoding = emukit.core.OneHotEncoding(parameter.choices) + return emukit.core.CategoricalParameter(name=parameter.name, encoding=encoding) + raise ValueError(f"Type of parameter {parameter} ({type(parameter)}) not supported.") + + return emukit.core.ParameterSpace([_one_parameter_convert(param) for param in config_space.get_hyperparameters()]) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000000..27a74669b8 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,8 @@ +[pytest] + +# Note: --boxed is required for use with the pytest-timeout plugin and thread method. +addopts = -svxl +# --boxed +# Moved these to Makefile +#-n auto +#--cov=mlos_core --cov-report=xml diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..5dbdd88a50 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +""" +Setup instructions for the mlos_core package. +""" + +from setuptools import setup, find_packages + +setup( + name="mlos-core", + version="0.0.1", + packages=find_packages(), + install_requires=[ + 'scikit-learn>=0.22.1', + 'scipy>=1.3.2', + 'numpy>=1.18.1', + 'pandas>=1.0.3', + ], + extras_require={ + 'emukit': 'emukit', + 'skopt': 'scikit-optimize', + }, + author="Microsoft", + author_email="amueller@microsoft.com", + description=("MLOS Core Python interface for parameter optimization."), + license="", + keywords="", + #python_requires='>=3.7', +)