PageSourceSearch

https://ax.dev/assets/js/ea7e0035.befebec8.js

js ax.dev collected 2026-09-24 10:34:08 UTC 17,223 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[3645],{13119(e,a,r){r.r(a),r.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"core","title":"Core","description":"Overview","source":"@site/versioned_docs/version-0.5.0/core.md","sourceDirName":".","slug":"/core","permalink":"/docs/0.5.0/core","draft":false,"unlisted":false,"tags":[],"version":"0.5.0","lastUpdatedBy":"Cristian Lara","lastUpdatedAt":1744730923000,"frontMatter":{"id":"core","title":"Core"},"sidebar":"docs","previous":{"title":"Bandit Optimization","permalink":"/docs/0.5.0/banditopt"},"next":{"title":"Trial Evaluation","permalink":"/docs/0.5.0/trial-evaluation"}}');var n=r(74848),i=r(28453);const s={id:"core",title:"Core"},o=void 0,l={},c=[{value:"Overview",id:"overview",level:3},{value:"Trial VS. Batch Trial",id:"trial-vs-batch-trial",level:3},{value:"Search Space and Parameters",id:"search-space-and-parameters",level:3},{value:"Optimization Config",id:"optimization-config",level:3},{value:"Arm",id:"arm",level:3},{value:"Status Quo",id:"status-quo",level:3},{value:"Experiment Lifecycle",id:"experiment-lifecycle",level:2}];function d(e){const a={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(a.h3,{id:"overview",children:"Overview"}),"\n",(0,n.jsxs)(a.p,{children:["In Ax, an ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#experiment",children:"experiment"})," keeps track of the whole optimization process. It contains a search space, optimization config, metadata, information on what metrics to track and how to run iterations, etc. An ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#experiment",children:"experiment"})," is composed of a sequence of ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trials"})," each of which has a set of parameterizations (or ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#arm",children:"arms"}),") to be evaluated. A ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trial"})," is added to the experiment when a new set of arms is proposed by the optimization algorithm. The trial is then evaluated to compute the values of each ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#metric",children:"metric"})," for each arm, which are fed into the algorithms to create a new trial. Most applications have one arm per trial, which is the default implementation."]}),"\n",(0,n.jsx)(a.p,{children:"The core constructs that define the experiment are detailed below."}),"\n",(0,n.jsx)(a.h3,{id:"trial-vs-batch-trial",children:"Trial VS. Batch Trial"}),"\n",(0,n.jsxs)(a.p,{children:["An ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#experiment",children:"experiment"})," consists of ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trials"}),", which can be one of two types: regular ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trial"})," or ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#batch-trial",children:"batch trial"}),". A regular ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trial"})," contains a single ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#arm",children:"arm"})," and relevant metadata. A ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#batch-trial",children:"batch trial"})," contains multiple ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#arm",children:"arms"}),", relevant metadata, and optionally a set of arm weights, which are a measure of how much of the total resources allocated to evaluating a batch should go towards evaluating the specific arm."]}),"\n",(0,n.jsxs)(a.p,{children:[(0,n.jsxs)(a.strong,{children:["A ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#batch-trial",children:"batch trial"})," is not just a ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trial"})," with many arms!"]})," It is a trial for which it is important that the arms are evaluated ",(0,n.jsx)(a.strong,{children:"simultaneously and together"}),". For instance, a batch trial would be appropriate in an A/B test where the evaluation results are subject to nonstationarity and require multiple arms to be deployed (and gathered data for) at the same time. For cases where multiple arms are evaluated separately and independently of each other, use multiple ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#trial",children:"trials"})," with a single arm each, which will allow Ax to keep track of their deployment and results appropriately."]}),"\n",(0,n.jsx)(a.h3,{id:"search-space-and-parameters",children:"Search Space and Parameters"}),"\n",(0,n.jsxs)(a.p,{children:["A ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#search-space",children:"search space"})," is composed of a set of ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#parameter",children:"parameters"})," to be tuned in the experiment, and optionally a set of ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#parameter-constraint",children:"parameter constraints"})," that define restrictions across these parameters (e.g. ",(0,n.jsx)(a.code,{children:"p_a <= p_b"}),"). Each parameter has a name, a type (",(0,n.jsx)(a.code,{children:"int"}),", ",(0,n.jsx)(a.code,{children:"float"}),", ",(0,n.jsx)(a.code,{children:"bool"}),", or ",(0,n.jsx)(a.code,{children:"string"}),"), and a domain, which is a representation of the possible values the parameter can take. The search space is used by the optimization algorithms to know which arms are valid to suggest."]}),"\n",(0,n.jsx)(a.p,{children:"Ax supports three types of parameters:"}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.strong,{children:"Range parameters"}),": must be of type ",(0,n.jsx)(a.code,{children:"int"})," or ",(0,n.jsx)(a.code,{children:"float"}),", and the domain is represented by a lower and upper bound. If the parameter is specified as an ",(0,n.jsx)(a.code,{children:"int"}),", newly generated points are rounded to the nearest integer by default."]}),"\n"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import RangeParameter, ParameterType\nfloat_range_param = RangeParameter(name="x1", parameter_type=ParameterType.FLOAT, lower=0.0, upper=1.0)\nint_range_param = RangeParameter(name="x2", parameter_type=ParameterType.INT, lower=0, upper=10)\n'})}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.strong,{children:"Choice parameters"}),": domain is a set of values"]}),"\n"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import ChoiceParameter, ParameterType\nchoice_param = ChoiceParameter(name="y", parameter_type=ParameterType.STRING, values=["foo", "bar"])\n'})}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.strong,{children:"Fixed parameters"}),": domain is a single value"]}),"\n"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import FixedParameter, ParameterType\nfixed_param = FixedParameter(name="z", parameter_type=ParameterType.BOOL, value=True)\n'})}),"\n",(0,n.jsxs)(a.p,{children:["Ax supports three types of parameter constraints, each of which can only be used on ",(0,n.jsx)(a.code,{children:"int"})," or ",(0,n.jsx)(a.code,{children:"float"})," parameters:"]}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.strong,{children:"Linear c
1onstraints"}),": ",(0,n.jsx)(a.code,{children:"w * v"})," <= b where w is the vector of parameter weights, v is a vector of parameter values, * is the dot product, and b is the specified bound. Linear constraints are specified with the bound and a dictionary that maps parameter name to the weight"]}),"\n"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import ParameterConstraint\n\nparam_a = RangeParameter(name="a", parameter_type=ParameterType.FLOAT, lower=0.0, upper=1.0)\nparam_b = RangeParameter(name="b", parameter_type=ParameterType.FLOAT, lower=0.0, upper=1.0)\n\n# 1.0*a + 0.5*b <= 1.0\ncon_1 = ParameterConstraint(constraint_dict={"a": 1.0, "b": 0.5}, bound=1.0)\n'})}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.strong,{children:"Order constraints"}),": specifies that one parameter must be smaller than the other"]}),"\n"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:"from ax import OrderConstraint\n\n# a <= b\ncon_2 = OrderConstraint(lower_parameter=param_a, upper_parameter=param_b)\n"})}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.strong,{children:"Sum constraints"}),": specifies that the sum of the parameters must be greater or less than a bound"]}),"\n"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:"from ax import SumConstraint\n\n# a + b >= 0.5\ncon_3 = SumConstraint(parameters=[param_a, param_b], is_upper_bound=False, bound=0.5)\n"})}),"\n",(0,n.jsx)(a.p,{children:"Given parameters and (optionally) parameter constraints, you can construct a search space:"}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:"from ax import SearchSpace\n\nSearchSpace(parameters=[param_a, param_b], parameter_constraints=[con_1, con_2, con_3])\n"})}),"\n",(0,n.jsx)(a.h3,{id:"optimization-config",children:"Optimization Config"}),"\n",(0,n.jsxs)(a.p,{children:["An ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#optimization-config",children:"optimization config"})," is composed of an ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#objective",children:"objective metric"})," to be minimized or maximized, and optionally a set of ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#outcome-constraint",children:"outcome constraints"})," that place restrictions on how other metrics can be moved by the experiment. Note that you cannot constrain the objective metric."]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import Metric\nfrom ax import Objective\n\nobjective = Objective(metric=Metric(name="m1"), minimize=True)\n'})}),"\n",(0,n.jsx)(a.p,{children:"There is no minimum or maximum number of outcome constraints, but an individual metric can have at most two constraints \u2014 which is how we represent metrics with both upper and lower bounds."}),"\n",(0,n.jsxs)(a.p,{children:["Outcome constraints may be of the form ",(0,n.jsx)(a.code,{children:"metric >= bound"})," or ",(0,n.jsx)(a.code,{children:"metric <= bound"}),". The bound can be expressed as an absolute measurement, or relative to the status quo (if applicable), in which case the bound is the acceptable percent change from the status quo's value."]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import Metric\nfrom ax import OutcomeConstraint\nfrom ax import ComparisonOp\n\n# m2 cannot regress the status quo by more than 5%\noc = OutcomeConstraint(metric=Metric(name="m2"), op = ComparisonOp.GEQ, bound=-5.0, relative=True)\n'})}),"\n",(0,n.jsx)(a.p,{children:"Finally, create the optimization config to attach to the experiment."}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:"from ax import OptimizationConfig\n\nopt_config = OptimizationConfig(objective=objective, outcome_constraints=[oc])\n"})}),"\n",(0,n.jsx)(a.h3,{id:"arm",children:"Arm"}),"\n",(0,n.jsxs)(a.p,{children:["An ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#arm",children:"arm"})," in Ax is a set of ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#parameter",children:"parameters"})," and their values with a name attached to it. In the case of ",(0,n.jsx)(a.strong,{children:"hyperparameter optimization"}),", an ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#arm",children:"arm"})," corresponds to a hyperparameter configuration explored in the course of a given optimization."]}),"\n",(0,n.jsx)(a.p,{children:"An arm is defined by specifying the value for each parameter, and optionally giving it a name:"}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:'from ax import Arm\n\nArm(parameters={"x": 0, "
1y": "Foo", z: True})\n\n# Names are automatically assigned by the experiment\n# but can also be specified by the user\nArm(parameters={"x": 0, "y": "Foo", z: True}, name="arm1")\n'})}),"\n",(0,n.jsxs)(a.p,{children:["Arms are typically attached to trials, as discussed in the ",(0,n.jsx)(a.a,{href:"#experiment-lifecycle",children:"Experiment Lifecycle"})," section below."]}),"\n",(0,n.jsx)(a.h3,{id:"status-quo",children:"Status Quo"}),"\n",(0,n.jsxs)(a.p,{children:["An experiment can optionally contain a ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#status-quo",children:"status quo"})," arm, which represents the \u201ccontrol\u201d parameterization. This allows viewing results and doing optimization using ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#relative-outcome-constraint",children:"relativized"})," outcomes, meaning all metrics will be presented as percentage deltas against the status quo."]}),"\n",(0,n.jsx)(a.p,{children:"If the status quo is specified on the experiment, it will be automatically added to every trial that is created."}),"\n",(0,n.jsx)(a.h2,{id:"experiment-lifecycle",children:"Experiment Lifecycle"}),"\n",(0,n.jsxs)(a.p,{children:["An experiment consists of a sequence of trials, each of which evaluates one or more arms. For more details on the implementing the evaluation, see the ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/trial-evaluation",children:"trial evaluation"})," and ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/data",children:"metric"})," references."]}),"\n",(0,n.jsx)(a.p,{children:"Based on the evaluation results, the optimization algorithm suggest one or more arms to evaluate. You then create a new trial containing these suggested arms, evaluate this trial, and repeat."}),"\n",(0,n.jsxs)(a.p,{children:["You can directly add arm(s) to a new trial, or you can add a ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/glossary#generator-run",children:"generator run"})," \u2013\u2013 output of the optimization algorithm:"]}),"\n",(0,n.jsx)(a.pre,{children:(0,n.jsx)(a.code,{className:"language-python",children:"# If only one arm should be evaluated\nexperiment.new_trial().add_arm(Arm(...))\n\n# If multiple arms should be evaluated\nexperiment.new_batch_trial().add_arms_and_weights(arms=[Arm(...), Arm(...)])\n\n# To evaluate the arms suggested by a GeneratorRun\nexperiment.new_batch_trial().add_generator_run(generator_run=GeneratorRun(...))\n"})}),"\n",(0,n.jsxs)(a.p,{children:["A trial goes through multiple phases during the experimentation cycle, tracked by its ",(0,n.jsx)(a.a,{href:"https://ax.readthedocs.io/en/0.5.0/core.html#ax.core.base_trial.TrialStatus",children:(0,n.jsx)(a.code,{children:"TrialStatus"})})," field. These stages are:"]}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"CANDIDATE"})," - Trial has just been created and can still be modified before deployment."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"STAGED"})," - Relevant for external systems, where the trial configuration has been deployed but not begun the evaluation stage."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"RUNNING"})," - Trial is in the process of being evaluated."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"COMPLETED"})," - Trial completed evaluation successfully."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"FAILED"})," - Trial incurred a failure while being evaluated."]}),"\n",(0,n.jsxs)(a.li,{children:[(0,n.jsx)(a.code,{children:"ABANDONED"})," - User manually stopped the trial for some specified reason."]}),"\n"]}),"\n",(0,n.jsxs)(a.p,{children:['When a trial is first created, its status is "candidate". If applicable, we can call ',(0,n.jsx)(a.code,{children:"trial.mark_staged"}),' to move the trial into "staged" mode. We then call ',(0,n.jsx)(a.code,{children:"trial.run"}),'\nto run the trial, which moves it into the "running" stage. We can then call\n',(0,n.jsx)(a.code,{children:"trial.mark_completed"}),", ",(0,n.jsx)(a.code,{children:"trial.mark_failed"}),", or ",(0,n.jsx)(a.code,{children:"trial.mark_abandoned"})," to end the trial."]}),"\n",(0,n.jsxs)(a.p,{children:["If the trial's ",(0,n.jsx)(a.a,{href:"/docs/0.5.0/trial-evaluation#adding-your-own-runner",children:"runner"}),' has "staging_required" = True,\nthen ',(0,n.jsx)(a.code,{children:"trial.run"}),' will first mark the trial as "staged", and we can later call\n',(0,n.jsx)(a.code,{children:"trial.mark_running"}),' explicitly to move the trial to "running".']})]})}function h(e={}){const{wrapper:a}={...(0,i.R)(),...e.components};return a?(0,n.jsx)(a,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},28453(e,a,r){r.d(a,{R:()=>s,x:()=>o});var t=r(96540);const n={},i=t.createContext(n);function s(e){const a=t.useContext(i);return t.useMemo(function(){return"function"==typeof e?e(a):{...a,...e}},[a,e])}function o(e){let a;return a=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:s(e.components),t.createElement(i.Provider,{value:a},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.