Skip to content

Commit

Permalink
Allows user to set metric id for visualisations
Browse files Browse the repository at this point in the history
  • Loading branch information
YamLyubov committed Feb 16, 2024
1 parent 12248f0 commit fff90ac
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 27 deletions.
6 changes: 3 additions & 3 deletions golem/visualisation/opt_history/fitness_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@


class FitnessBox(HistoryVisualization):
def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None,
def visualize(self, metric_id: int = 0, save_path: Optional[Union[os.PathLike, str]] = None,
dpi: Optional[int] = None, best_fraction: Optional[float] = None):
""" Visualizes fitness values across generations in the form of boxplot.
:params metric_id: numeric index of the metric to visualize (for multi-objective opt-n)
:param save_path: path to save the visualization. If set, then the image will be saved, and if not,
it will be displayed.
:param dpi: DPI of the output figure.
Expand All @@ -23,7 +23,7 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None,
dpi = dpi or self.get_predefined_value('dpi')
best_fraction = best_fraction or self.get_predefined_value('best_fraction')

df_history = get_history_dataframe(self.history, best_fraction=best_fraction)
df_history = get_history_dataframe(self.history, metric_id, best_fraction=best_fraction)
columns_needed = ['generation', 'individual', 'fitness']
df_history = df_history[columns_needed].drop_duplicates(ignore_index=True)
# Get color palette by mean fitness per generation
Expand Down
33 changes: 18 additions & 15 deletions golem/visualisation/opt_history/fitness_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from matplotlib.widgets import Button

from golem.core.log import default_log
from golem.core.optimisers.fitness import null_fitness
from golem.core.optimisers.opt_history_objects.individual import Individual
from golem.core.paths import default_data_dir
from golem.visualisation.opt_history.history_visualization import HistoryVisualization
Expand Down Expand Up @@ -44,10 +43,10 @@ def setup_fitness_plot(axis: plt.Axes, xlabel: str, title: Optional[str] = None,
axis.grid(axis='y')


def plot_fitness_line_per_time(axis: plt.Axes, generations, label: Optional[str] = None,
def plot_fitness_line_per_time(axis: plt.Axes, generations, metric_id: int, label: Optional[str] = None,
with_generation_limits: bool = True) \
-> Dict[int, Individual]:
best_fitness = null_fitness()
best_metric = np.inf # Assuming metric minimization
gen_start_times = []
best_individuals = {}

Expand All @@ -65,21 +64,22 @@ def plot_fitness_line_per_time(axis: plt.Axes, generations, label: Optional[str]
if ind.native_generation != gen_num:
continue
evaluation_time = (datetime.fromisoformat(ind.metadata['evaluation_time_iso']) - start_time).seconds
target_metric = ind.fitness.values[metric_id]
if evaluation_time < gen_start_times[gen_num]:
gen_start_times[gen_num] = evaluation_time
if ind.fitness > best_fitness:
if target_metric <= best_metric:
best_individuals[evaluation_time] = ind
best_fitness = ind.fitness
best_metric = target_metric

best_eval_times, best_fitnesses = np.transpose(
[(evaluation_time, abs(individual.fitness.value))
[(evaluation_time, individual.fitness.value)
for evaluation_time, individual in best_individuals.items()])

best_eval_times = list(best_eval_times)
best_fitnesses = list(best_fitnesses)

if best_eval_times[-1] != end_time_seconds:
best_fitnesses.append(abs(best_fitness.value))
best_fitnesses.append(best_metric)
best_eval_times.append(end_time_seconds)
gen_start_times.append(end_time_seconds)

Expand All @@ -104,19 +104,21 @@ def plot_fitness_line_per_time(axis: plt.Axes, generations, label: Optional[str]
return best_individuals


def plot_fitness_line_per_generations(axis: plt.Axes, generations, label: Optional[str] = None) \
def plot_fitness_line_per_generations(axis: plt.Axes, generations, metric_id: int, label: Optional[str] = None) \
-> Dict[int, Individual]:
best_fitnesses, best_generations, best_individuals = find_best_running_fitness(generations, metric_id=0)
best_fitnesses, best_generations, best_individuals = find_best_running_fitness(generations, metric_id=metric_id)
axis.step(best_generations, best_fitnesses, where='post', label=label)
axis.set_xticks(range(len(generations)))
axis.locator_params(nbins=10)
return best_individuals


class FitnessLine(HistoryVisualization):
def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None,
per_time: Optional[bool] = None):
def visualize(self, metric_id: int = 0, save_path: Optional[Union[os.PathLike, str]] = None,
dpi: Optional[int] = None, per_time: Optional[bool] = None):
""" Visualizes the best fitness values during the evolution in the form of line.
:params metric_id: numeric index of the metric to visualize (for multi-objective opt-n).
:param save_path: path to save the visualization. If set, then the image will be saved,
and if not, it will be displayed.
:param dpi: DPI of the output figure.
Expand All @@ -129,21 +131,22 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op
fig, ax = plt.subplots(figsize=(6.4, 4.8), facecolor='w')
if per_time:
xlabel = 'Time, s'
plot_fitness_line_per_time(ax, self.history.generations)
plot_fitness_line_per_time(ax, self.history.generations, metric_id)
else:
xlabel = 'Generation'
plot_fitness_line_per_generations(ax, self.history.generations)
plot_fitness_line_per_generations(ax, self.history.generations, metric_id)
setup_fitness_plot(ax, xlabel)
show_or_save_figure(fig, save_path, dpi)


class FitnessLineInteractive(HistoryVisualization):

@with_alternate_matplotlib_backend
def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None,
def visualize(self, metric_id: int = 0, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Optional[int] = None,
per_time: Optional[bool] = None, graph_show_kwargs: Optional[Dict[str, Any]] = None):
""" Visualizes the best fitness values during the evolution in the form of line.
Additionally, shows the structure of the best individuals and the moment of their discovering.
:params metric_id: numeric index of the metric to visualize (for multi-objective opt-n)
:param save_path: path to save the visualization. If set, then the image will be saved, and if not,
it will be displayed.
:param dpi: DPI of the output figure.
Expand All @@ -170,7 +173,7 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op
x_template = 'generation {}'
plot_func = plot_fitness_line_per_generations

best_individuals = plot_func(ax_fitness, self.history.generations)
best_individuals = plot_func(ax_fitness, self.history.generations, metric_id)
setup_fitness_plot(ax_fitness, x_label)

ax_graph.axis('off')
Expand Down
2 changes: 2 additions & 0 deletions golem/visualisation/opt_history/multiple_fitness_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def visualize(self,

def plot_multiple_fitness_lines(self, ax: plt.axis, metric_id: int = 0, with_confidence: bool = True):
for histories, label in zip(list(self.historical_fitnesses.values()), list(self.historical_fitnesses.keys())):
if len(self.metric_names) == 1:
histories = [[gen_fit] for gen_fit in histories]
plot_average_fitness_line_per_generations(ax, histories, label,
with_confidence=with_confidence,
metric_id=metric_id)
Expand Down
2 changes: 1 addition & 1 deletion golem/visualisation/opt_history/operations_animated_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def animate(frame_num):
operation_column_name = 'Operation'
column_for_operation = 'tag' if tags_map else 'node'

df_history = get_history_dataframe(self.history, best_fraction, tags_map)
df_history = get_history_dataframe(self.history, best_fraction=best_fraction, tags_map=tags_map)
df_history = df_history.rename({
'generation': generation_column_name,
'fitness': fitness_column_name,
Expand Down
2 changes: 1 addition & 1 deletion golem/visualisation/opt_history/operations_kde.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def visualize(self, save_path: Optional[Union[os.PathLike, str]] = None, dpi: Op
operation_column_name = 'Operation'
column_for_operation = 'tag' if tags_map else 'node'

df_history = get_history_dataframe(self.history, best_fraction, tags_map)
df_history = get_history_dataframe(self.history, best_fraction=best_fraction, tags_map=tags_map)
df_history = df_history.rename({'generation': generation_column_name,
column_for_operation: operation_column_name}, axis='columns')
operations_found = df_history[operation_column_name].unique()
Expand Down
4 changes: 2 additions & 2 deletions golem/visualisation/opt_history/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
TagOperationsMap = Dict[str, List[str]]


def get_history_dataframe(history: OptHistory, best_fraction: Optional[float] = None,
def get_history_dataframe(history: OptHistory, metric_id: int = 0, best_fraction: Optional[float] = None,
tags_map: Optional[TagOperationsMap] = None):
history_data = {
'generation': [],
Expand All @@ -41,7 +41,7 @@ def get_history_dataframe(history: OptHistory, best_fraction: Optional[float] =
for node in ind.graph.nodes:
history_data['generation'].append(gen_num)
history_data['individual'].append('_'.join([ind.uid, str(uid_counts[ind.uid])]))
fitness = abs(ind.fitness.value)
fitness = abs(ind.fitness.values[metric_id])
history_data['fitness'].append(fitness)
history_data['node'].append(str(node))
if not tags_map:
Expand Down
14 changes: 9 additions & 5 deletions test/unit/optimizers/test_composing_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,19 @@ def test_newly_generated_history(n_jobs: int):
indirect=True)
@pytest.mark.parametrize('plot_type', PlotTypesEnum)
def test_history_show_saving_plots(tmp_path, plot_type: PlotTypesEnum, generate_history):
save_path = Path(tmp_path, plot_type.name)
gif_plots = [PlotTypesEnum.operations_animated_bar,
PlotTypesEnum.diversity_population]
save_path = save_path.with_suffix('.gif') if plot_type in gif_plots \
else save_path.with_suffix('.png')
differ_by_metric_id_plots = [PlotTypesEnum.fitness_box, PlotTypesEnum.fitness_line,
PlotTypesEnum.fitness_line_interactive]
history: OptHistory = generate_history
visualizer = OptHistoryVisualizer(history)
visualization = plot_type.value(visualizer.history, visualizer.visuals_params)
visualization.visualize(save_path=str(save_path), best_fraction=0.1, dpi=100)
num_metrics_to_check = 2 if plot_type in differ_by_metric_id_plots else 1
for metric_id in range(num_metrics_to_check): # check for the first two metrics
save_path = Path(tmp_path, f'{plot_type.name}_{metric_id}')
save_path = save_path.with_suffix('.gif') if plot_type in gif_plots \
else save_path.with_suffix('.png')
visualization = plot_type.value(visualizer.history, visualizer.visuals_params)
visualization.visualize( metric_id= metric_id, save_path=str(save_path), best_fraction=0.1, dpi=100)
if plot_type is not PlotTypesEnum.fitness_line_interactive:
assert save_path.exists()

Expand Down

0 comments on commit fff90ac

Please sign in to comment.