I'm currently working with Optuna and I'm trying to suggest ratios that are bounded by multiple variables. Specifically, I'm dealing with ratios X_1, X_2, ..., X_k
that are bounded by ∑X_i = 1
and 0 <= X_i <= 1
for all i
.
Unfortunately, Optuna does not provide a Dirichlet distribution, which would be the ideal solution for this problem.
I've tried the following approach, where I iteratively suggest each ratio and subtract it from the remaining total. However, this does not work as expected:
def objective(trial):
k = 10
ratios = np.zeros(k)
residual = 1
for i in range(k - 1):
ratios[i] = trial.suggest_float(f'ratio_{i}', 0, residual)
residual -= ratios[i]
# ratios[k - 1] = trial.suggest_float(f'ratio_{k - 1}', residual, residual)
ratios[k - 1] = residual
return np.log(ratios).sum()
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=20)
I also tried another approach where I suggest all ratios independently and then normalize them. This approach does not produce any errors, but it suggests k
times, even though the degree of freedom is k - 1
, which is inconsistent:
def objective(trial):
k = 10
ratios = np.zeros(k)
for i in range(k):
ratios[i] = trial.suggest_float(f'ratio_{i}', 0, 1)
ratios /= ratios.sum()
return np.log(ratios).sum()
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=20)
I'm looking for a way to suggest a ratio that is bounded by multiple variables. While the above examples are simple and differentiable, I need to deal with a more complex objective function that requires variables.
The issue was resolved by creating an Objective
class that maintains a maximum value (self.max
) for the ratio suggestions. This maximum value is updated in each trial to be the average of the current maximum and the maximum ratio suggested in the current trial. This approach ensures that the suggested ratios are gradually adjusted towards the optimal values.
Here's the corrected code:
class Objective:
def __init__(self):
self.max = 1
def __call__(self, trial):
k = 10
ratios = np.zeros(k)
for i in range(k):
ratios[i] = trial.suggest_float(f'ratio_{i}', 0, self.max)
ratios /= ratios.sum()
self.max = (self.max + ratios.max()) / 2
return np.log(ratios).sum()
study = optuna.create_study(direction='maximize')
study.optimize(Objective(), n_trials=100)
In this code, the Objective
class is instantiated and passed to the study.optimize
method. The __call__
method of the Objective
class is used as the objective function for the optimization. This method suggests the ratios, normalizes them, updates the maximum value for the suggestions, and returns the sum of the logarithms of the ratios.