Closed leigedove closed 1 month ago
Hi, thank you for opening the issue.
From the code snippet it looks like "mark_completed" is not being called prior to "fetch_data". In this section of the Building Blocks of Ax tutorial it says
"To fetch trial data, we need to run it and mark it completed. For most metrics in Ax, data is only available once the status of the trial is COMPLETED, since in real-worlds scenarios, metrics can typically only be fetched after the trial finished running."
Could you try calling "mark_completed", using the tutorial as a guide? If this doesn't work, please copy the full error trace as well as the printed statements so I can help debug further. Thanks!
Hi, thank you for your response. However, after modifying my code based on the tutorial, there is an error: "AttributeError: 'Data' object has no attribute 'is_ok'" when running data = experiment.fetch_data().
And when I ran the code from the tutorial, the same error was shown.
It might be a problem with the ax_platform version. However, when I changed the version from 0.4.0 to 0.1.9, there were many environment issues, such as incompatibilities between gpytorch and botorch versions.
This is my code:
import os
import pandas as pd
import torch
from ax import Data, Experiment, ParameterType, RangeParameter, SearchSpace, Models
from ax.core.metric import Metric
from ax.core.objective import Objective
from ax.core.optimization_config import OptimizationConfig
from ax.runners.synthetic import SyntheticRunner
# Set seed and device
torch.manual_seed(12345) # To always get the same Sobol points
tkwargs = {
"dtype": torch.double,
"device": torch.device("cuda" if torch.cuda.is_available() else "cpu"),
}
# Define BoothMetric
class BoothMetric(Metric):
def fetch_trial_data(self, trial):
records = []
for arm_name, arm in trial.arms_by_name.items():
params = arm.parameters
records.append(
{
"arm_name": arm_name,
"metric_name": self.name,
"trial_index": trial.index,
"mean": (params["x1"] + 2 * params["x2"] - 7) ** 2
+ (2 * params["x1"] + params["x2"] - 5) ** 2,
"sem": 0.0,
}
)
return Data(df=pd.DataFrame.from_records(records))
# Define the search space
search_space = SearchSpace(
parameters=[
RangeParameter(
name=f"x{i}", parameter_type=ParameterType.FLOAT, lower=-5.0, upper=10.0
)
for i in range(25)
]
+ [
RangeParameter(
name=f"x{i + 25}",
parameter_type=ParameterType.FLOAT,
lower=0.0,
upper=15.0,
)
for i in range(25)
]
)
# Define optimization configuration
optimization_config = OptimizationConfig(
objective=Objective(
metric=BoothMetric(name="objective"),
minimize=True,
)
)
# Initialize experiment parameters
N_INIT = 10
BATCH_SIZE = 3
N_BATCHES = 1 if os.environ.get("SMOKE_TEST") else 10
# Create experiment
experiment = Experiment(
name="saasbo_experiment",
search_space=search_space,
optimization_config=optimization_config,
runner=SyntheticRunner(),
)
# Generate initial Sobol points
sobol = Models.SOBOL(search_space=experiment.search_space)
for _ in range(N_INIT):
trial = experiment.new_trial(sobol.gen(1))
trial.run()
trial.mark_completed()
# Check initial data
data = experiment.fetch_data()
if data.df.empty:
print("Initial data from Sobol trials is empty. Check the objective function.")
else:
print(f"Initial data: {data.df}")
# Run SAASBO optimization
for i in range(N_BATCHES):
model = Models.SAASBO(experiment=experiment, data=data)
generator_run = model.gen(BATCH_SIZE)
trial = experiment.new_batch_trial(generator_run=generator_run)
trial.run()
trial.mark_completed()
# Fetch new data and merge with existing data
new_data = trial.fetch_data()
data = Data.from_multiple_data([data, new_data])
new_value = new_data.df["mean"].min()
print(
f"Iteration: {i}, Best in iteration {new_value:.3f}, Best so far: {data.df['mean'].min():.3f}"
)
The full error trace:
Traceback (most recent call last):
File "/home/building_blocks.py", line 232, in
Thanks for the detailed explanation.
What is happening is that BoothMetric::fetch_trial_data is returning a Data object, when the Metric class interface expects that a MetricFetchResult object like Ok or Err is returned, which has the is_ok attribute.
Because the tutorial is for an old version, its BoothMetric implementation returns Data instead of a MetricFetchResult.
For your purposes, you can try fixing this by updating BoothMetric's fetch_trial_data to the following:
def fetch_trial_data(self, trial: BaseTrial) -> MetricFetchResult:
records = []
for arm_name, arm in trial.arms_by_name.items():
params = arm.parameters
records.append(
{
"arm_name": arm_name,
"metric_name": self.name,
"trial_index": trial.index,
"mean": (params["x1"] + 2 * params["x2"] - 7) ** 2
+ (2 * params["x1"] + params["x2"] - 5) ** 2,
"sem": 0.0,
}
)
return Ok(value=Data(df=pd.DataFrame.from_records(records)))
You may also need to add error catching in case the data is not yet ready.
I'll make a note for us to update our tutorials and documentation with correct implementations of custom metrics. For now you can look off of current metric implementations like NoisyFunctionMetric.
Let me know if you encounter other blockers, thanks!
I'll close out this issue for now- we've added the work to fix the tutorials to our backlog. Please open a new issue if any additional support is needed. Thank you!
What happened?
When using custom metrics, trial data for each Sobol trial is generated correctly and attached to the experiment, but the fetched data is still empty.
Please provide a minimal, reproducible example of the unexpected behavior.
The error is:
Please paste any relevant traceback/logs produced by the example provided.
No response
Ax Version
0.4.0
Python Version
3.12.2
Operating System
linux
Code of Conduct