From 7842933006aa32078c19318a2fb18f2e6a17b066 Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Mon, 26 Jul 2021 22:54:53 +0200 Subject: [PATCH 1/8] new package design --- optunapi/config/optunapi-test.yaml | 17 --- optunapi/server.py | 187 -------------------------- optunapi/{db => study}/.gitkeep | 0 optunapi/{log => trial}/.gitkeep | 0 optunapi/utils/__init__.py | 2 - optunapi/utils/create_log_file.py | 25 ---- optunapi/utils/suggest_from_config.py | 51 ------- optunapi/version.py | 1 + tests/.gitkeep | 0 tests/secured_client.py | 14 -- tests/simple_client.py | 45 ------- 11 files changed, 1 insertion(+), 341 deletions(-) delete mode 100644 optunapi/config/optunapi-test.yaml delete mode 100644 optunapi/server.py rename optunapi/{db => study}/.gitkeep (100%) rename optunapi/{log => trial}/.gitkeep (100%) delete mode 100644 optunapi/utils/__init__.py delete mode 100644 optunapi/utils/create_log_file.py delete mode 100644 optunapi/utils/suggest_from_config.py create mode 100644 optunapi/version.py create mode 100644 tests/.gitkeep delete mode 100644 tests/secured_client.py delete mode 100644 tests/simple_client.py diff --git a/optunapi/config/optunapi-test.yaml b/optunapi/config/optunapi-test.yaml deleted file mode 100644 index e415ecc..0000000 --- a/optunapi/config/optunapi-test.yaml +++ /dev/null @@ -1,17 +0,0 @@ -x : - name : x - type : float - choices : - low : -10 - high : 10 - step : - log : False - -y : - name : y - type : float - choices : - low : -10 - high : 10 - step : - log : False diff --git a/optunapi/server.py b/optunapi/server.py deleted file mode 100644 index 66af904..0000000 --- a/optunapi/server.py +++ /dev/null @@ -1,187 +0,0 @@ -import os -PATH = os.path.abspath (os.path.dirname (__file__)) - -from fastapi import FastAPI -optunapi = FastAPI() - -import optuna -from optuna.trial._state import TrialState - -from typing import Optional -from utils import suggest_from_config, create_log_file - - -@optunapi.get ('/optunapi/ping') -async def ping_server(): - """ - Ping Server - =========== - - Returns the message "The Optuna-server is alive!" if the server is running. - - Parameters - ---------- - None - - Returns - ------- - msg : str - A message witnessing that the server is running. - """ - msg = 'The Optuna-server is alive!' - return msg - - -@optunapi.get ('/optunapi/hparams/{model_name}') -async def read_hparams (model_name: str): - """ - Read Hyperparameters - ==================== - - When a machine submits a GET request with path `/optunapi/hparams/{model_name}`, - an Optuna study is created (if it's the first request) or loaded (for any other - requests) and an ask instance is called. The resulting trial is equipped with a - set of hyperparameters and encoded to the HTTP response together with the name - of the optimization session, the trial identifier, the number of running trials - and the number of completed trials. - - Parameters - ---------- - model_name : str (path parameter) - Name of the optimization session for which one asks for hyperparameters. - - Returns - ------- - response : dict (HTTP response) - Dictionary with the following items: - - `model_name` > Name of the optimization session - - `trial_id` > Number identifying the created trial - - `params` > Current set of values for the hyperparameters - - `running_trials` > Number of running trials - - `completed_trials` > Number of completed trials - """ - storage_dir = os.path.join (PATH, 'db') - storage_name = 'sqlite:///{}/{}.db' . format (storage_dir, model_name) - study = optuna.create_study ( - study_name = model_name , - storage = storage_name , - load_if_exists = True , - ) - - trial = study.ask() - - config_file = '{}/config/{}.yaml' . format (PATH, model_name) - suggest_from_config (trial, configuration = config_file) - - log_file = '{}/log/{}.log' . format (PATH, model_name) - create_log_file (study, log_file = log_file) - - running_trials = study.get_trials ( - deepcopy = False, - states = (TrialState.RUNNING,) - ) - completed_trials = study.get_trials ( - deepcopy = False, - states = (TrialState.COMPLETE,) - ) - - trial_id = study.trials[-1].number - params = study.trials[-1].params - num_running_trials = len (running_trials) - num_completed_trials = len (completed_trials) - - response = { - 'model_name' : model_name , - 'trial_id' : trial_id , - 'params' : params , - 'running_trials' : num_running_trials , - 'completed_trials' : num_completed_trials , - } - - return response - - -@optunapi.get ('/optunapi/score/{model_name}') -async def send_score ( - model_name : str , - trial_id : int , - score : float , - ): - """ - Send Score - ========== - - When a machine submits a GET request with path `/optunapi/score/{model_name}?trial_id=TRIAL_ID&score=SCORE`, - an Optuna study is loaded and its trial `TRIAL_ID` is finished with score `SCORE` calling a tell instance. - The corresponding HTTP response encodes the name of the optimization session, `TRIAL_ID`, the tested set of - hyperparameters, `SCORE`, the number identifying the best trial, the best set of hyperparameters, the best - score, the number of running trials and the number of completed trials. - - Parameters - ---------- - model_name : str (path parameter) - Name of the optimization session for which one asks for hyperparameters. - - trial_id : int (query parameter) - Number identifying the tested trial. - - score : float (query parameter) - Score obtained with the set of hyperparameters tested. - - Returns - ------- - response : dict (HTTP response) - Dictionary with the following items: - - `model_name` > Name of the optimization session - - `trial_id` > Number identifying the tested trial - - `params` > Tested set of values for the hyperparameters - - `score` > Score obtained with the tested set of hyperparameters - - `best_trial_id` > Number identifying the best trial - - `best_params` > Best set of values for the hyperparameters - - `best_score` > Score obtained with the best set of hyperparameters - - `running_trials` > Number of running trials - - `completed_trials` > Number of completed trials - """ - storage_dir = os.path.join (PATH, 'db') - storage_name = 'sqlite:///{}/{}.db' . format (storage_dir, model_name) - study = optuna.create_study ( - study_name = model_name , - storage = storage_name , - load_if_exists = True , - ) - - study.tell (trial_id, score) - - log_file = '{}/log/{}.log' . format (PATH, model_name) - create_log_file (study, log_file = log_file) - - running_trials = study.get_trials ( - deepcopy = False, - states = (TrialState.RUNNING,) - ) - completed_trials = study.get_trials ( - deepcopy = False, - states = (TrialState.COMPLETE,) - ) - - params = study.trials[trial_id].params - best_trial = study.best_trial.number - best_params = study.best_params - best_score = study.best_value - num_running_trials = len (running_trials) - num_completed_trials = len (completed_trials) - - response = { - 'model_name' : model_name , - 'trial_id' : trial_id , - 'params' : params , - 'score' : score , - 'best_trial' : best_trial , - 'best_params' : best_params , - 'best_score' : best_score , - 'running_trials' : num_running_trials , - 'completed_trials' : num_completed_trials , - } - - return response - \ No newline at end of file diff --git a/optunapi/db/.gitkeep b/optunapi/study/.gitkeep similarity index 100% rename from optunapi/db/.gitkeep rename to optunapi/study/.gitkeep diff --git a/optunapi/log/.gitkeep b/optunapi/trial/.gitkeep similarity index 100% rename from optunapi/log/.gitkeep rename to optunapi/trial/.gitkeep diff --git a/optunapi/utils/__init__.py b/optunapi/utils/__init__.py deleted file mode 100644 index ca7f3ff..0000000 --- a/optunapi/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .suggest_from_config import suggest_from_config -from .create_log_file import create_log_file diff --git a/optunapi/utils/create_log_file.py b/optunapi/utils/create_log_file.py deleted file mode 100644 index 51b96a9..0000000 --- a/optunapi/utils/create_log_file.py +++ /dev/null @@ -1,25 +0,0 @@ -from optuna.study import Study - - -def create_log_file (study: Study, log_file: str): - """ - Create Log File - =============== - - Allows to create a log file containing a pandas `DataFrame` of trials in the `Study`. - - Parameters - ---------- - study : optuna.study.Study - Set of `Trial` objects deriving from an Ask-and-Tell interface. - - log_file : str - Name of file reporting the study results. - - Returns - ------- - None - """ - df = study.trials_dataframe ( attrs = ('number', 'params', 'value', 'state') ) - with open (log_file, 'w') as file: - print (df.to_string(), file = file) diff --git a/optunapi/utils/suggest_from_config.py b/optunapi/utils/suggest_from_config.py deleted file mode 100644 index 2b2b3e3..0000000 --- a/optunapi/utils/suggest_from_config.py +++ /dev/null @@ -1,51 +0,0 @@ -import yaml -from optuna.trial import Trial - - -def suggest_from_config (trial: Trial, configuration: str): - """ - Suggest From Config - =================== - - Allows to generalize the `suggest_*` functions taking - information from a YAML configuration file. - - Parameters - ---------- - trial : optuna.trial.Trial - `Trial` object deriving from an Ask-and-Tell interface. - - configuration : str - YAML file containing hyperparameters configuration. - - Returns - ------- - None - """ - with open (configuration) as file: - params = yaml.full_load (file) - - for par in params.values(): - if par['type'] == 'categorical': - trial.suggest_categorical ( - name = str ( par [ 'name' ] ) , - choices = list ( par [ 'choices' ] ) , - ) - elif par['type'] == 'float': - trial.suggest_float ( - name = str ( par [ 'name' ] ) , - low = float ( par [ 'low' ] ) , - high = float ( par [ 'high' ] ) , - step = float ( par [ 'step' ] ) if par [ 'step' ] else None , - log = bool ( par [ 'log' ] ) if par [ 'log' ] else False , - ) - elif par['type'] == 'int': - trial.suggest_int ( - name = str ( par [ 'name' ] ) , - low = float ( par [ 'low' ] ) , - high = float ( par [ 'high' ] ) , - step = float ( par [ 'step' ] ) if par [ 'step' ] else 1 , - log = bool ( par [ 'log' ] ) if par [ 'log' ] else False , - ) - else: - raise ValueError ('Trial suggestion not implemented.') diff --git a/optunapi/version.py b/optunapi/version.py new file mode 100644 index 0000000..49f34f4 --- /dev/null +++ b/optunapi/version.py @@ -0,0 +1 @@ +__version__ = "0.2.0" \ No newline at end of file diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/secured_client.py b/tests/secured_client.py deleted file mode 100644 index 8daa172..0000000 --- a/tests/secured_client.py +++ /dev/null @@ -1,14 +0,0 @@ -import sshtunnel -import requests - - -with sshtunnel.open_tunnel ( - (REMOTE_SERVER_IP, 22), - ssh_username = 'mbarbetti', - ssh_pkey = '/home/mbarbetti/.ssh/id_rsa', - remote_bind_address = (PRIVATE_SERVER_IP, PRIVATE_SERVER_PORT), - local_bind_address = ('127.0.0.1', 10022) -) as tunnel: - ping_server = requests.get ('http://localhost:10022/optunapi/ping') - ping_msg = ping_server.json() - print (ping_msg) \ No newline at end of file diff --git a/tests/simple_client.py b/tests/simple_client.py deleted file mode 100644 index 55385d4..0000000 --- a/tests/simple_client.py +++ /dev/null @@ -1,45 +0,0 @@ -import requests - - -HOST = 'http://127.0.0.1:8000' -model_name = 'optunapi-test' - -num_trials = 10 -for _ in range (num_trials): - read_hparams = requests.get (HOST + '/optunapi/hparams/{}' . format (model_name)) - hp_req = read_hparams.json() - - trial_id = hp_req [ 'trial_id' ] - params = hp_req [ 'params' ] - running_trials = hp_req [ 'running_trials' ] - - print ( - 'Trial {} started with parameters: {}. Total number of running trials: {}.' \ - . format ( trial_id, params, running_trials ) - ) - - x = params['x'] - y = params['y'] - func = (x - 2) ** 2 + (y - 3) ** 2 - - send_score = requests.get (HOST + '/optunapi/score/{}?trial_id={}&score={}' . format (model_name, trial_id, func)) - score_req = send_score.json() - - trial_id = score_req [ 'trial_id' ] - score = score_req [ 'score' ] - best_trial = score_req [ 'best_trial' ] - best_score = score_req [ 'best_score' ] - best_params = score_req [ 'best_params' ] - completed_trials = score_req [ 'completed_trials' ] - - print ( - 'Trial {} finished with value: {}. Best is trial {} with value: {} and parameters: {}. Total number of completed trials: {}.\n' \ - . format ( trial_id, score, best_trial, best_score, best_params, completed_trials ) - ) - -print ( '\nRESULT AFTER {} TRIALS' . format (completed_trials) ) -print ( '+--------------------------------+' ) -print ( '| x : {:.4f} |' . format (best_params['x']) ) -print ( '| y : {:.4f} |' . format (best_params['y']) ) -print ( '| (x - 2)^2 + (y - 3)^2 : {:.4f} |' . format (best_score) ) -print ( '+--------------------------------+\n' ) From 50c784be108ac643e53a49b76295f85438b173da Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Mon, 30 Aug 2021 15:05:01 +0200 Subject: [PATCH 2/8] new logo for optunapi --- README.md | 10 +++++++++- docs/img/optunapi-logo.png | Bin 0 -> 26737 bytes 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 docs/img/optunapi-logo.png diff --git a/README.md b/README.md index a660932..6fbc1ab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +
+ +
+ +

+ API to distribute hyperparameters optimization through HTTP requests +

+ [![PyPI license](https://img.shields.io/pypi/l/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI version shields.io](https://img.shields.io/pypi/v/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI status](https://img.shields.io/pypi/status/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) @@ -7,7 +15,7 @@ [![Downloads](https://pepy.tech/badge/optunapi)](https://pepy.tech/project/optunapi) [![GitHub stars](https://img.shields.io/github/stars/mbarbetti/optunapi?style=social)](https://github.com/mbarbetti/optunapi) -# OptunAPI +--- OptunAPI is a simple API designed for Machine Learning applications that allows to distribute an automatic hyperparameters optimization over different machines through _HTTP requests_. Each set of hyperparameters diff --git a/docs/img/optunapi-logo.png b/docs/img/optunapi-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..235f4df4aebdbe1d828bc52620dd0cddfeaa204e GIT binary patch literal 26737 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 Date: Mon, 30 Aug 2021 15:05:51 +0200 Subject: [PATCH 3/8] fixed bug --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6fbc1ab..66bc9bb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- +

From bd008f929026c924e97414b90698b881195b5b8d Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Mon, 30 Aug 2021 15:07:18 +0200 Subject: [PATCH 4/8] afixed image size --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66bc9bb..c2c86cf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

From 52bc14e719052d33cbfa56cda2d417037381f2cc Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Mon, 30 Aug 2021 15:08:52 +0200 Subject: [PATCH 5/8] afixed image size --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c2c86cf..dcce8d8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

@@ -15,8 +15,6 @@ [![Downloads](https://pepy.tech/badge/optunapi)](https://pepy.tech/project/optunapi) [![GitHub stars](https://img.shields.io/github/stars/mbarbetti/optunapi?style=social)](https://github.com/mbarbetti/optunapi) ---- - OptunAPI is a simple API designed for Machine Learning applications that allows to distribute an automatic hyperparameters optimization over different machines through _HTTP requests_. Each set of hyperparameters can be studied independently since the minima research does't require any gradients computation, but instead From ff4ce1271058995d18985e6a6a25f3f4b85cf6bd Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Mon, 30 Aug 2021 15:16:17 +0200 Subject: [PATCH 6/8] mmiinor change --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dcce8d8..7a7f4b2 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ [![Downloads](https://pepy.tech/badge/optunapi)](https://pepy.tech/project/optunapi) [![GitHub stars](https://img.shields.io/github/stars/mbarbetti/optunapi?style=social)](https://github.com/mbarbetti/optunapi) -OptunAPI is a simple API designed for Machine Learning applications that allows to distribute an automatic -hyperparameters optimization over different machines through _HTTP requests_. Each set of hyperparameters +_OptunAPI_ is a simple API designed for Machine Learning applications that allows to distribute an automatic +hyperparameters optimization over different machines through **HTTP requests**. Each set of hyperparameters can be studied independently since the minima research does't require any gradients computation, but instead -is performed through a _Bayesian optimization_ based on [Optuna](https://optuna.org/). The machine running +is performed through a **Bayesian optimization** based on [Optuna](https://optuna.org/). The machine running Optuna manages centrally the optimization studies -- the so-called "Optuna-server" -- providing sets of hyperparameters and assessing them by the scores evaluated and sent back by the single computing instance, named "Trainer-client". The HTTP requests underlying such client-server system are powered by [FastAPI](https://fastapi.tiangolo.com). From 3fe27e78455d6c30325b6aa340a7227d4b9f4ff3 Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Wed, 29 Sep 2021 19:13:40 +0200 Subject: [PATCH 7/8] v0.1.1 --- README.md | 3 +-- setup.py | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7a7f4b2..36ad008 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ [![PyPI version shields.io](https://img.shields.io/pypi/v/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI status](https://img.shields.io/pypi/status/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) -[![GitHub issues](https://img.shields.io/github/issues/mbarbetti/optunapi.svg)](https://github.com/mbarbetti/optunapi/issues/) -[![GitHub pull-requests](https://img.shields.io/github/issues-pr/mbarbetti/optunapi.svg)](https://github.com/mbarbetti/optunapi/pulls/) +[![DOI](https://zenodo.org/badge/357996871.svg)](https://zenodo.org/badge/latestdoi/357996871) [![Downloads](https://pepy.tech/badge/optunapi)](https://pepy.tech/project/optunapi) [![GitHub stars](https://img.shields.io/github/stars/mbarbetti/optunapi?style=social)](https://github.com/mbarbetti/optunapi) diff --git a/setup.py b/setup.py index 5336b76..beab9e6 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup ( name = 'optunapi', - version = '0.1.0', + version = '0.1.1', author = 'Matteo Barbetti', author_email = 'matteo.barbetti@fi.infn.it', description = 'API to distribute hyperparameters optimization through HTTP requests', @@ -34,6 +34,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Mathematics', @@ -42,4 +43,5 @@ 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: HTTP Servers', ], - ) \ No newline at end of file + ) + \ No newline at end of file From 11ade48a4479594e67dd5fe692636d23b094e3eb Mon Sep 17 00:00:00 2001 From: Matteo Barbetti Date: Wed, 29 Sep 2021 20:47:38 +0200 Subject: [PATCH 8/8] aligned with main --- {docs/img => .github/images}/optunapi-logo.png | Bin README.md | 10 ++++++---- setup.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) rename {docs/img => .github/images}/optunapi-logo.png (100%) diff --git a/docs/img/optunapi-logo.png b/.github/images/optunapi-logo.png similarity index 100% rename from docs/img/optunapi-logo.png rename to .github/images/optunapi-logo.png diff --git a/README.md b/README.md index 36ad008..267a32c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@

- +
-

+

API to distribute hyperparameters optimization through HTTP requests -

+

[![PyPI license](https://img.shields.io/pypi/l/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI version shields.io](https://img.shields.io/pypi/v/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI status](https://img.shields.io/pypi/status/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/optunapi.svg)](https://pypi.python.org/pypi/optunapi/) -[![DOI](https://zenodo.org/badge/357996871.svg)](https://zenodo.org/badge/latestdoi/357996871) +[![GitHub issues](https://img.shields.io/github/issues/mbarbetti/optunapi.svg)](https://github.com/mbarbetti/optunapi/issues/) +[![GitHub pull-requests](https://img.shields.io/github/issues-pr/mbarbetti/optunapi.svg)](https://github.com/mbarbetti/optunapi/pulls/) [![Downloads](https://pepy.tech/badge/optunapi)](https://pepy.tech/project/optunapi) +[![DOI](https://zenodo.org/badge/357996871.svg)](https://zenodo.org/badge/latestdoi/357996871) [![GitHub stars](https://img.shields.io/github/stars/mbarbetti/optunapi?style=social)](https://github.com/mbarbetti/optunapi) _OptunAPI_ is a simple API designed for Machine Learning applications that allows to distribute an automatic diff --git a/setup.py b/setup.py index beab9e6..ef6adee 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup ( name = 'optunapi', - version = '0.1.1', + version = '0.1.4', author = 'Matteo Barbetti', author_email = 'matteo.barbetti@fi.infn.it', description = 'API to distribute hyperparameters optimization through HTTP requests',