Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor convergence rules #331

Merged
merged 7 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions alphadia/constants/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -250,48 +250,55 @@ optimization:
# Example: [['ms1_error', 'ms2_error', 'rt_error', 'mobility_error']] means that all parameters are optimized simultaneously.
# Example: [["ms2_error"], ["rt_error"], ["ms1_error"], ["mobility_error"]] means that the parameters are optimized sequentially in the order given.
# Example: [["rt_error"], ["ms1_error", "ms2_error"]] means that first rt_error is optimized, then ms1_error and ms2_error are optimized simultaneously, and mobility_error is not optimized at all.
# If order_of_optimizatoin is null, first all targeted optimizers run simultaneously, then any remaining automatic optimizers run sequentially in the order [["ms2_error"], ["rt_error"], ["ms1_error"], ["mobility_error"]]
# If order_of_optimization is null, first all targeted optimizers run simultaneously, then any remaining automatic optimizers run sequentially in the order [["ms2_error"], ["rt_error"], ["ms1_error"], ["mobility_error"]]
order_of_optimization: null

# Parameters for the update rule for each parameter:
# - update_percentile_range: the percentile interval to use (as a decimal)
# - update_factor: the factor by which to multiply the result from the percentile interval to get the new parameter value for the next round of search
# - favour_narrower_parameter: if True, the optimization will not take the value that maximizes the feature used for optimization, but instead the smallest value compatible with the minimum_proportion_of_maximum value.
# This setting can be useful for optimizing parameters for which many parameter values have similar feature values and therefore favouring narrower parameters helps to overcome noise.
# - maximal_decrease: the maximal decrease of the parameter value before stopping optimization (only relevant if favour_narrower_parameter is True)
# - minimum_proportion_of_maximum: the minimum proportion of the maximum value of the parameter that the designated optimum should have (only relevant if favour_narrower_parameter is True)
# - try_narrower_parameters: if True, the optimization will try narrower parameters until a substantial (as determined by maximal_decrease) decrease in the feature used for optimization is observed.
# - maximal_decrease: the maximal decrease of the parameter value before stopping optimization (only relevant if favour_narrower_parameter is True).
# For example, a value of 0.2 indicates up to 20% decrease from the previous parameter is permissible.
# - favour_narrower_optimum: if True, the optimization will not take the value that maximizes the feature used for optimization, but instead the smallest value compatible with the maximum_decrease_from_maximum value.
# This setting can be useful for optimizing parameters for which many parameter values have similar feature values and therefore favouring narrower parameters helps to overcome noise.
# - maximum_decrease_from_maximum: the maximum proportional decrease from the maximum value of the parameter that the designated optimum should have (only relevant if favour_narrower_optimum is True).
# For example, a value of 0.1 indicates that the optimum should no more than 10% less than the maximum value.
ms2_error:
targeted_update_percentile_range: 0.95
targeted_update_factor: 1.0
automatic_update_percentile_range: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: False
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.9
try_narrower_values: False
maximal_decrease: 0.2
favour_narrower_optimum: False
maximum_decrease_from_maximum: 0.1
ms1_error:
targeted_update_percentile_range: 0.95
targeted_update_factor: 1.0
automatic_update_percentile_range: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: False
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.9
try_narrower_values: False
maximal_decrease: 0.2
favour_narrower_optimum: False
maximum_decrease_from_maximum: 0.1
mobility_error:
targeted_update_percentile_range: 0.95
targeted_update_factor: 1.0
automatic_update_percentile_range: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: False
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.9
try_narrower_values: False
maximal_decrease: 0.2
favour_narrower_optimum: False
maximum_decrease_from_maximum: 0.1
rt_error:
targeted_update_percentile_range: 0.95
targeted_update_factor: 1.0
automatic_update_percentile_range: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: True
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.9
try_narrower_values: True
maximal_decrease: 0.2
favour_narrower_optimum: True
maximum_decrease_from_maximum: 0.1

# configuration for the optimization manager
# initial parameters, will be optimized
Expand Down
86 changes: 55 additions & 31 deletions alphadia/workflow/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,27 @@ def __init__(
self.parameter_name
]["automatic_update_percentile_range"]

self.favour_narrower_parameter = workflow.config["optimization"][
self._try_narrower_values = workflow.config["optimization"][
self.parameter_name
]["favour_narrower_parameter"]
]["try_narrower_values"]

if self.favour_narrower_parameter:
self.maximal_decrease = workflow.config["optimization"][
self.parameter_name
]["maximal_decrease"]
self.minimum_proportion_of_maximum = workflow.config["optimization"][
self.parameter_name
]["minimum_proportion_of_maximum"]
self._maximal_decrease = (
workflow.config["optimization"][self.parameter_name]["maximal_decrease"]
if self._try_narrower_values
else None
)

self._favour_narrower_optimum = workflow.config["optimization"][
self.parameter_name
]["favour_narrower_optimum"]

self._maximum_decrease_from_maximum = (
workflow.config["optimization"][self.parameter_name][
"maximum_decrease_from_maximum"
]
if self._favour_narrower_optimum
else None
)

def step(
self,
Expand Down Expand Up @@ -287,10 +297,10 @@ def _batch_substantially_bigger(self):
@property
def _just_converged(self):
"""Optimization should stop if continued narrowing of the parameter is not improving the feature value.
If self.favour_narrower_parameter is False:
If self._try_narrower_values is False:
1) This function checks if the previous rounds of optimization have led to a meaningful improvement in the feature value.
2) If so, it continues optimization and appends the proposed new parameter to the list of parameters. If not, it stops optimization and sets the optimal parameter attribute.
If self.favour_narrower_parameter is True:
If self._try_narrower_values is True:
1) This function checks if the previous rounds of optimization have led to a meaningful disimprovement in the feature value or if the parameter has not changed substantially.
2) If not, it continues optimization and appends the proposed new parameter to the list of parameters. If so, it stops optimization and sets the optimal parameter attribute.

Expand All @@ -303,23 +313,33 @@ def _just_converged(self):
if len(self.history_df) < 3:
return False

if self.favour_narrower_parameter: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
feature_history = self.history_df[self.feature_name]
last_feature_value = feature_history.iloc[-1]
second_last_feature_value = feature_history.iloc[-2]
third_last_feature_value = feature_history.iloc[-3]

if self._try_narrower_values: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
min_steps_reached = (
self._num_prev_optimizations
>= self.workflow.config["calibration"]["min_steps"]
)

feature_history = self.history_df[self.feature_name]
feature_substantially_decreased = (
feature_history.iloc[-1]
< self.maximal_decrease * feature_history.iloc[-2]
and feature_history.iloc[-1]
< self.maximal_decrease * feature_history.iloc[-3]
)
last_feature_value - second_last_feature_value
) / np.abs(second_last_feature_value) < -self._maximal_decrease and (
last_feature_value - third_last_feature_value
) / np.abs(third_last_feature_value) < -self._maximal_decrease

parameter_history = self.history_df["parameter"]

last_parameter_value = parameter_history.iloc[-1]
second_last_parameter_value = parameter_history.iloc[-2]
parameter_not_substantially_changed = (
parameter_history.iloc[-1] / parameter_history.iloc[-2] > 0.95
np.abs(
(last_parameter_value - second_last_parameter_value)
/ second_last_parameter_value
)
< 0.05
)

return min_steps_reached and (
Expand All @@ -332,20 +352,20 @@ def _just_converged(self):
>= self.workflow.config["calibration"]["min_steps"]
)

feature_history = self.history_df[self.feature_name]
feature_not_substantially_increased = (
last_feature_value - second_last_feature_value
) / np.abs(second_last_feature_value) < 0.1 and (
last_feature_value - third_last_feature_value
) / np.abs(third_last_feature_value) < 0.1

return (
min_steps_reached
and feature_history.iloc[-1] < 1.1 * feature_history.iloc[-2]
and feature_history.iloc[-1] < 1.1 * feature_history.iloc[-3]
)
return min_steps_reached and feature_not_substantially_increased

def _find_index_of_optimum(self):
"""Finds the index of the row in the history dataframe with the optimal value of the feature used for optimization.
if self.favour_narrower_parameter is False:
if self._favour_narrower_parameter is False:
The index at optimum is the index of the parameter value that maximizes the feature.
if self.favour_narrower_parameter is True:
The index at optimum is the index of the minimal parameter value whose feature value is at least self.minimum_proportion_of_maximum of the maximum value of the feature.
if self._favour_narrower_parameter is True:
The index at optimum is the index of the minimal parameter value whose feature value is at least self._maximum_decrease_from_maximum of the maximum value of the feature.

Returns
-------
Expand All @@ -358,11 +378,15 @@ def _find_index_of_optimum(self):

"""

if self.favour_narrower_parameter: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
if self._favour_narrower_optimum: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
maximum_feature_value = self.history_df[self.feature_name].max()
rows_within_thresh_of_max = self.history_df.loc[
self.history_df[self.feature_name]
> self.history_df[self.feature_name].max()
* self.minimum_proportion_of_maximum
> (
maximum_feature_value
- self._maximum_decrease_from_maximum
* np.abs(maximum_feature_value)
)
]
index_of_optimum = rows_within_thresh_of_max["parameter"].idxmin()
return index_of_optimum
Expand Down
12 changes: 5 additions & 7 deletions tests/unit_tests/test_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,15 +960,16 @@ def test_configurability():
"rt_error": {
"automatic_update_percentile_range": 0.99,
"automatic_update_factor": 1.3,
"favour_narrower_parameter": True,
"maximal_decrease": 0.8,
"minimum_proportion_of_maximum": 0.95,
"try_narrower_values": True,
"maximal_decrease": 0.4,
"favour_narrower_optimum": True,
"maximum_decrease_from_maximum": 0.3,
},
"ms2_error": {
"automatic_update_percentile_range": 0.80,
"targeted_update_percentile_range": 0.995,
"targeted_update_factor": 1.2,
"favour_narrower_parameter": False,
"favour_narrower_optimum": False,
},
}
)
Expand Down Expand Up @@ -1049,6 +1050,3 @@ def test_optimizer_skipping():
rt_optimizer.step(calibration_test_df1, calibration_test_df2)

assert rt_optimizer.has_converged is True


test_optimizer_skipping()
Loading