Source code for vision_unlearning.evaluator.class_unlearning

import re
from typing import List, Dict, Union, Optional


[docs] def average_metrics(name_to_value_all: List[Dict[str, float | int | bool]]) -> Dict[str, float]: # TODO: this averages all metrics, even if they represent std, which is wrong common_keys = set(name_to_value_all[0].keys()) for d in name_to_value_all[1:]: common_keys.intersection_update(d.keys()) numeric_keys = {key for key in common_keys if isinstance(name_to_value_all[0][key], (int, float))} # Filter for numeric keys averages = {key: sum(d[key] for d in name_to_value_all) / len(name_to_value_all) for key in numeric_keys} return averages
[docs] def _convert_mean_to_std(name: str) -> str: name = re.sub(r'\(.*?\)', '(~↓)', name) name = name.replace(' mean ', ' std ') return name
[docs] def format_metrics_as_markdown( name_to_value: Dict[str, Union[float, int, bool]], name_to_value_all: Optional[Dict[str, Dict[str, Union[float, int, bool]]]] = None ) -> str: """ Formats metrics as a markdown table. Can display either just overall metrics or include per-class metrics as additional columns. Args: name_to_value: Dictionary of overall/average metrics name_to_value_all: Optional dictionary of per-class metrics Returns: str: Markdown formatted table """ # TODO: this function because way too ugly (see the original simple version bellow) # Maybe converting to pandas then `to_markdown` is better # Get all possible metric names from both dictionaries all_metric_names = set(name_to_value.keys()) if name_to_value_all is not None: for class_dict in name_to_value_all.values(): all_metric_names.update(class_dict.keys()) # Sort metric names alphabetically sorted_metric_names = sorted(all_metric_names) # Prepare header if name_to_value_all is None: table = "| Metric | Value |\n|--------|-------|\n" else: class_names = sorted(name_to_value_all.keys()) header = "| Metric | Overall | " + " | ".join(class_names) + " |\n" separator = "|--------|---------|" + "|".join(["---------"] * len(class_names)) + "|\n" table = header + separator # Process each metric for name in sorted_metric_names: if (' mean ' in name) and (_convert_mean_to_std(name) in name_to_value): # Handle mean ± std case for overall column overall_value = f"{name_to_value[name]:.2f} ± {name_to_value[_convert_mean_to_std(name)]:.1f}" elif (' std ' in name): continue # Skip std metrics as they're combined with means else: # Handle simple value case for overall column overall_value = f"{name_to_value.get(name, ''):.2f}" if name in name_to_value else "" if name_to_value_all is None: # Simple case - just overall metrics table += f"| {name} | {overall_value} |\n" else: # Complex case - include per-class columns row_parts = [f"| {name} | {overall_value} |"] for class_name in sorted(name_to_value_all.keys()): class_metrics = name_to_value_all[class_name] if (' mean ' in name) and (_convert_mean_to_std(name) in class_metrics): # Handle mean ± std for this class class_value = f"{class_metrics[name]:.2f} ± {class_metrics[_convert_mean_to_std(name)]:.1f}" elif (' std ' in name): class_value = "" else: # Handle simple value for this class class_value = f"{class_metrics.get(name, ''):.2f}" if name in class_metrics else "" row_parts.append(f" {class_value} |") table += "".join(row_parts) + "\n" return table
# This is the old simple version that does not handle multiple columns: # def format_metrics_as_markdown(name_to_value: Dict[str, float | int | bool]) -> str: # table = "| Metric | Value |\n|--------|-------|\n" # for name in sorted(name_to_value.keys()): # Sort keys alphabetically # if (' mean ' in name) and (_convert_mean_to_std(name) in name_to_value): # value = f"{name_to_value[name]:.2f} ± {name_to_value[_convert_mean_to_std(name)]:.1f}" # elif (' std ' in name): # continue # else: # value = f"{name_to_value[name]:.2f}" # table += f"| {name} | {value} |\n" # return table