{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n\n# Ask-and-Tell Interface\n\nOptuna has an `Ask-and-Tell` interface, which provides a more flexible interface for hyperparameter optimization.\nThis tutorial explains three use-cases when the ask-and-tell interface is beneficial:\n\n- `Apply-optuna-to-an-existing-optimization-problem-with-minimum-modifications`\n- `Define-and-Run`\n- `Batch-Optimization`\n\n\n## Apply Optuna to an existing optimization problem with minimum modifications\n\nLet's consider the traditional supervised classification problem; you aim to maximize the validation accuracy.\nTo do so, you train `LogisticRegression` as a simple model.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import numpy as np\nfrom sklearn.datasets import make_classification\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import train_test_split\n\nimport optuna\n\n\nX, y = make_classification(n_features=10)\nX_train, X_test, y_train, y_test = train_test_split(X, y)\n\nC = 0.01\nclf = LogisticRegression(C=C)\nclf.fit(X_train, y_train)\nval_accuracy = clf.score(X_test, y_test)  # the objective"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Then you try to optimize hyperparameters ``C`` and ``solver`` of the classifier by using optuna.\nWhen you introduce optuna naively, you define an ``objective`` function\nsuch that it takes ``trial`` and calls ``suggest_*`` methods of ``trial`` to sample the hyperparameters:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def objective(trial):\n    X, y = make_classification(n_features=10)\n    X_train, X_test, y_train, y_test = train_test_split(X, y)\n\n    C = trial.suggest_loguniform(\"C\", 1e-7, 10.0)\n    solver = trial.suggest_categorical(\"solver\", (\"lbfgs\", \"saga\"))\n\n    clf = LogisticRegression(C=C, solver=solver)\n    clf.fit(X_train, y_train)\n    val_accuracy = clf.score(X_test, y_test)\n\n    return val_accuracy\n\n\nstudy = optuna.create_study(direction=\"maximize\")\nstudy.optimize(objective, n_trials=10)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "This interface is not flexible enough.\nFor example, if ``objective`` requires additional arguments other than ``trial``,\nyou need to define a class as in\n[How to define objective functions that have own arguments?](../../faq.html#how-to-define-objective-functions-that-have-own-arguments).\nThe ask-and-tell interface provides a more flexible syntax to optimize hyperparameters.\nThe following example is equivalent to the previous code block.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "study = optuna.create_study(direction=\"maximize\")\n\nn_trials = 10\nfor _ in range(n_trials):\n    trial = study.ask()  # `trial` is a `Trial` and not a `FrozenTrial`.\n\n    C = trial.suggest_loguniform(\"C\", 1e-7, 10.0)\n    solver = trial.suggest_categorical(\"solver\", (\"lbfgs\", \"saga\"))\n\n    clf = LogisticRegression(C=C, solver=solver)\n    clf.fit(X_train, y_train)\n    val_accuracy = clf.score(X_test, y_test)\n\n    study.tell(trial, val_accuracy)  # tell the pair of trial and objective value"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The main difference is to use two methods: :func:`optuna.study.Study.ask`\nand :func:`optuna.study.Study.tell`.\n:func:`optuna.study.Study.ask` creates a trial that can sample hyperparameters, and\n:func:`optuna.study.Study.tell` finishes the trial by passing ``trial`` and an objective value.\nYou can apply Optuna's hyperparameter optimization to your original code\nwithout an ``objective`` function.\n\nIf you want to make your optimization faster with a pruner, you need to explicitly pass the state of trial\nto the argument of :func:`optuna.study.Study.tell` method as follows:\n\n```python\nimport numpy as np\nfrom sklearn.datasets import load_iris\nfrom sklearn.linear_model import SGDClassifier\nfrom sklearn.model_selection import train_test_split\n\nimport optuna\n\n\nX, y = load_iris(return_X_y=True)\nX_train, X_valid, y_train, y_valid = train_test_split(X, y)\nclasses = np.unique(y)\nn_train_iter = 100\n\n# define study with hyperband pruner.\nstudy = optuna.create_study(\n    direction=\"maximize\",\n    pruner=optuna.pruners.HyperbandPruner(\n        min_resource=1, max_resource=n_train_iter, reduction_factor=3\n    ),\n)\n\nfor _ in range(20):\n    trial = study.ask()\n\n    alpha = trial.suggest_uniform(\"alpha\", 0.0, 1.0)\n\n    clf = SGDClassifier(alpha=alpha)\n    pruned_trial = False\n\n    for step in range(n_train_iter):\n        clf.partial_fit(X_train, y_train, classes=classes)\n\n        intermediate_value = clf.score(X_valid, y_valid)\n        trial.report(intermediate_value, step)\n\n        if trial.should_prune():\n            pruned_trial = True\n            break\n\nif pruned_trial:\n    study.tell(trial, state=optuna.trial.TrialState.PRUNED)  # tell the pruned state\nelse:\n    score = clf.score(X_valid, y_valid)\n    study.tell(trial, score)  # tell objective value\n```\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\"><h4>Note</h4><p>:func:`optuna.study.Study.tell` method can take a trial number rather than the trial object.\n    ``study.tell(trial.number, y)`` is equivalent to ``study.tell(trial, y)``.</p></div>\n\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n## Define-and-Run\nThe ask-and-tell interface supports both `define-by-run` and `define-and-run` APIs.\nThis section shows the example of the `define-and-run` API\nin addition to the define-by-run example above.\n\nDefine distributions for the hyperparameters before calling the\n:func:`optuna.study.Study.ask` method for define-and-run API.\nFor example,\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "distributions = {\n    \"C\": optuna.distributions.LogUniformDistribution(1e-7, 10.0),\n    \"solver\": optuna.distributions.CategoricalDistribution((\"lbfgs\", \"saga\")),\n}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Pass ``distributions`` to :func:`optuna.study.Study.ask` method at each call.\nThe retuned ``trial`` contains the suggested hyperparameters.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "study = optuna.create_study(direction=\"maximize\")\nn_trials = 10\nfor _ in range(n_trials):\n    trial = study.ask(distributions)  # pass the pre-defined distributions.\n\n    # two hyperparameters are already sampled from the pre-defined distributions\n    C = trial.params[\"C\"]\n    solver = trial.params[\"solver\"]\n\n    clf = LogisticRegression(C=C, solver=solver)\n    clf.fit(X_train, y_train)\n    val_accuracy = clf.score(X_test, y_test)\n\n    study.tell(trial, val_accuracy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n## Batch Optimization\nThe ask-and-tell interface enables us to optimize a batched objective for faster optimization.\nFor example, parallelizable evaluation, operation over vectors, etc.\n\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The following objective takes batched hyperparameters ``xs`` instead of a single\nhyperparameter and calculates the objective over the full vector.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def batched_objective(xs: np.ndarray):\n    return xs ** 2 + 1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In the following example, the number of hyperparameters in a batch is $10$,\nand ``batched_objective`` is evaluated three times.\nThus, the number of trials is $30$.\nNote that you need to store either ``trial_ids`` or ``trial`` to call\n:func:`optuna.study.Study.tell` method after the batched evaluations.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "batch_size = 10\nstudy = optuna.create_study()\n\nfor _ in range(3):\n\n    # create batch\n    trial_ids = []\n    samples = []\n    for _ in range(batch_size):\n        trial = study.ask()\n        trial_ids.append(trial.number)\n        x = trial.suggest_int(\"x\", -10, 10)\n        samples.append(x)\n\n    # evaluate batched objective\n    samples = np.array(samples)\n    objectives = batched_objective(samples)\n\n    # finish all trials in the batch\n    for trial_id, objective in zip(trial_ids, objectives):\n        study.tell(trial_id, objective)"
      ]
    }
  ],
  "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.8.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}