{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "w1RyZbo_F8IT" }, "source": [ "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/google/vizier/blob/main/docs/guides/user/search_spaces.ipynb)\n", "\n", "# Search Spaces\n", "Below, we provide examples of how to:\n", "* Setup a flat search space consisting of all four parameter types and additional auxiliary parameter types.\n", "* Setup a conditional search space correctly.\n", "* Reparameterize search spaces, which is useful for combinatorial search spaces.\n", "* Use infeasibility to define shaped search spaces." ] }, { "cell_type": "markdown", "metadata": { "id": "Fy5PtvbJGBfz" }, "source": [ "## Installation and reference imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "AOgGrhJ4vNoz" }, "outputs": [], "source": [ "!pip install google-vizier" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Sj6LHlz-GGW_" }, "outputs": [], "source": [ "import math\n", "from typing import List\n", "\n", "from vizier import pyvizier as vz" ] }, { "cell_type": "markdown", "metadata": { "id": "hFR0XKDjGIGu" }, "source": [ "## Flat search spaces\n", "Below are the core primitive parameter types and their specifications:\n", "\n", "* `DOUBLE`: Continuous range of possible values in the closed interval $[a,b]$ for some real numbers $a \\le b$.\n", "* `INTEGER`: Integer range of possible values in $[a,b] \\subset \\mathbb{Z}$ for some integers $a \\le b$.\n", "* `DISCRETE`: Finite, ordered set of values from $\\mathbb{R}$.\n", "* `CATEGORICAL`: Unordered list of strings." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "k2nwB6bFGKUL" }, "outputs": [], "source": [ "flat_problem = vz.ProblemStatement()\n", "flat_problem_root = flat_problem.search_space.root\n", "flat_problem_root.add_float_param(name='double', min_value=0.0, max_value=1.0)\n", "flat_problem_root.add_int_param(name='int', min_value=1, max_value=10)\n", "flat_problem_root.add_discrete_param(\n", " name='discrete', feasible_values=[0.1, 0.3, 0.5])\n", "flat_problem_root.add_categorical_param(\n", " name='categorical', feasible_values=['a', 'b', 'c'])" ] }, { "cell_type": "markdown", "metadata": { "id": "B_oGWIsRIyWl" }, "source": [ "PyVizier also has a `BOOLEAN` parameter which under-the-hood, is a binary `CATEGORICAL` parameter with values `'True'` and `'False'`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "9SuRGThBJT9Q" }, "outputs": [], "source": [ "flat_problem_root.add_bool_param(name='bool')" ] }, { "cell_type": "markdown", "metadata": { "id": "6ppz7TC3JsUZ" }, "source": [ "A default value for seeding the study may be used when constructing a parameter." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "M8bFDwCmJeG3" }, "outputs": [], "source": [ "flat_problem_root.add_float_param(\n", " name='double_with_default', min_value=0.0, max_value=1.0, default_value=0.5)" ] }, { "cell_type": "markdown", "metadata": { "id": "Rzo15A08KPrG" }, "source": [ "## Scaling\n", "Each of the numerical parameter types (`DOUBLE`, `INTEGER`, `DISCRETE`) may also have a **scaling type**, which toggles whether optimization occurs over a transformed space." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Z4gnOcngK0rV" }, "outputs": [], "source": [ "# Default scaling used.\n", "flat_problem_root.add_float_param(\n", " name='double_uniform',\n", " min_value=0.0,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.LINEAR)\n", "\n", "# Points near min_value are more important.\n", "flat_problem_root.add_float_param(\n", " name='double_log',\n", " min_value=0.0,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.LOG)\n", "\n", "# Points near the max_value are more important.\n", "flat_problem_root.add_float_param(\n", " name='double_reverse_log',\n", " min_value=0.0,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.REVERSE_LOG)\n", "\n", "# Default scaling used for DISCRETE parameters.\n", "flat_problem_root.add_discrete_param(\n", " name='discrete_uniform',\n", " feasible_values=[0.1, 0.3, 0.5],\n", " scale_type=vz.ScaleType.UNIFORM_DISCRETE)" ] }, { "cell_type": "markdown", "metadata": { "id": "hRb2MnkMGLRK" }, "source": [ "## Conditional search spaces\n", "Sometimes, **child parameters** only exist in specific scenarios or *conditions* when a **parent parameter** is equal to one or more specific\n", "values.\n", "\n", "Example: Momentum hyperparameters are used by the [Adam optimizer](https://arxiv.org/abs/1412.6980), but not stochastic gradient descent (SGD).\n", "\n", "**Caveat:** Since the value of a \"learning rate\" depends strongly on the\n", "optimizer being used (e.g. a learning rate of 0.1 to SGD means completely\n", "differently to Adam), we must create two separate child parameters, rather than\n", "sharing a single one." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "3VHSGrguGO08" }, "outputs": [], "source": [ "conditional_problem = vz.ProblemStatement()\n", "conditional_problem_root = conditional_problem.search_space.root\n", "optimizer = conditional_problem_root.add_categorical_param(\n", " name='optimizer', feasible_values=['sgd', 'adam'])\n", "\n", "# SGD child parameters\n", "optimizer.select_values(['sgd']).add_float_param(\n", " 'sgd_learning_rate',\n", " min_value=0.0001,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.LOG)\n", "\n", "# Adam child parameters\n", "optimizer.select_values(['adam']).add_float_param(\n", " 'adam_learning_rate',\n", " min_value=0.0001,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.LOG)\n", "optimizer.select_values(['adam']).add_float_param(\n", " 'adam_beta1',\n", " min_value=0.0,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.REVERSE_LOG)\n", "optimizer.select_values(['adam']).add_float_param(\n", " 'adam_beta2',\n", " min_value=0.0,\n", " max_value=1.0,\n", " scale_type=vz.ScaleType.REVERSE_LOG)" ] }, { "cell_type": "markdown", "metadata": { "id": "ZKAEwmapGOGT" }, "source": [ "## Combinatorial Reparamterization\n", "When dealing with a combinatorial search space $X$, one way to easily deal with such cases is to construct a reparameterization. Mathematically, this means finding a practical search space $Z$ and surjective mapping $\\Phi: Z \\rightarrow X$.\n", "\n", "Below is an example over the space of permutations of size $N$, where our mapping utilizes the [Lehmer code](https://en.wikipedia.org/wiki/Lehmer_code)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "tj10-pqxGcxy" }, "outputs": [], "source": [ "N = 10\n", "\n", "# Setup search space.\n", "permutation_problem = vz.ProblemStatement()\n", "for n in range(N):\n", " permutation_problem.search_space.root.add_int_param(\n", " name=str(n), min_value=0, max_value=n)\n", "\n", "\n", "def compute_index(trial: vz.Trial) -\u003e int:\n", " \"\"\"Computes index from Lehmer code.\"\"\"\n", " index = 0\n", " for n in range(N):\n", " index += trial.parameters.get_value(str(n)) * math.factorial(n)\n", " return index\n", "\n", "\n", "def compute_permutation(index: int) -\u003e List[int]:\n", " \"\"\"Outputs a N-permutation as a list of indices.\"\"\"\n", " all_indices = list(range(N))\n", " temp_index = index\n", " output = []\n", " for k in range(1, N + 1):\n", " factorial_value = math.factorial(N - k)\n", " value = all_indices[temp_index // factorial_value]\n", " output.append(value)\n", " all_indices.remove(value)\n", " temp_index = temp_index % factorial_value\n", " return output\n", "\n", "\n", "def phi(trial: vz.Trial) -\u003e List[int]:\n", " \"\"\"Maps a suggestion to a permutation.\"\"\"\n", " return compute_permutation(compute_index(trial))" ] }, { "cell_type": "markdown", "metadata": { "id": "0_i8ezfuGdG1" }, "source": [ "## Infeasibility\n", "Consider an optimization problem where we only consider float parameters $(x,y)$ from the unit disk $x^{2} + y^{2} \\le 1$. For such a scenario, we may denote any parameters outside of this area to be **infeasible**." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "O6aHmIF1Gfd8" }, "outputs": [], "source": [ "disk_problem = vz.ProblemStatement()\n", "disk_problem_root = disk_problem.search_space.root\n", "disk_problem_root.add_float_param(name='x', min_value=-1.0, max_value=1.0)\n", "disk_problem_root.add_float_param(name='y', min_value=-1.0, max_value=1.0)\n", "\n", "\n", "def evaluate(trial: vz.Trial) -\u003e vz.Trial:\n", " x = trial.parameters['x']\n", " y = trial.parameters['y']\n", " if x**2 + y**2 \u003c= 1:\n", " trial.complete(vz.Measurement(metrics={'sum': x + y}))\n", " else:\n", " trial.complete(vz.Measurement(), infeasibility_reason='Outside of range.')\n", " return trial" ] } ], "metadata": { "colab": { "name": "Search Spaces.ipynb", "private_outputs": true, "provenance": [] }, "gpuClass": "standard", "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }