Source code for tabeval.metrics.core.metric

# stdlib
import platform
from abc import ABCMeta, abstractmethod
from pathlib import Path
from typing import Callable, Dict, Optional

# third party
import numpy as np
import pandas as pd
import torch
from pydantic import validate_arguments

# tabeval absolute
from tabeval.metrics.representations.OneClass import OneClassLayer
from tabeval.plugins.core.dataloader import DataLoader
from tabeval.utils.constants import DEVICE
from tabeval.utils.serialization import dataframe_hash, load_from_file, save_to_file


[docs] class MetricEvaluator(metaclass=ABCMeta): """Base class for all metrics. Each derived class must implement the following methods: evaluate() - compare two datasets and return a dictionary of metrics. direction() - direction of metric (bigger better or smaller better). type() - type of the metric. name() - name of the metric. If any method implementation is missing, the class constructor will fail. Constructor Args: reduction: str The way to aggregate metrics across folds. Default: 'mean'. n_histogram_bins: int The number of bins used in histogram calculation. Default: 10. n_folds: int The number of folds in cross validation. Default: 3. task_type: str The type of downstream task. Default: 'classification'. workspace: Path The directory to save intermediate models or results. Default: Path("logs/tabeval_workspace"). use_cache: bool Whether to use cache. If True, it will try to load saved results in workspace directory where possible. """ @validate_arguments(config=dict(arbitrary_types_allowed=True)) def __init__( self, reduction: str = "mean", n_histogram_bins: int = 10, n_folds: int = 3, task_type: str = "classification", random_state: int = 0, workspace: Path = Path("logs/tabeval_workspace"), use_cache: bool = True, default_metric: Optional[str] = None, ) -> None: self._reduction = reduction self._n_histogram_bins = n_histogram_bins self._n_folds = n_folds self._task_type = task_type self._random_state = random_state self._workspace = workspace self._use_cache = use_cache if default_metric is None: default_metric = reduction self._default_metric = default_metric workspace.mkdir(parents=True, exist_ok=True)
[docs] @validate_arguments(config=dict(arbitrary_types_allowed=True)) @abstractmethod def evaluate(self, X_gt: DataLoader, X_syn: DataLoader) -> Dict: ...
[docs] @validate_arguments(config=dict(arbitrary_types_allowed=True)) @abstractmethod def evaluate_default(self, X_gt: DataLoader, X_syn: DataLoader) -> float: ...
[docs] @staticmethod @abstractmethod def direction() -> str: ...
[docs] @staticmethod @abstractmethod def type() -> str: ...
[docs] @staticmethod @abstractmethod def name() -> str: ...
[docs] @classmethod def fqdn(cls) -> str: return f"{cls.type()}.{cls.name()}"
[docs] def reduction(self) -> Callable: if self._reduction == "mean": return np.mean elif self._reduction == "max": return np.max elif self._reduction == "min": return np.min elif self._reduction == "median": return np.median else: raise ValueError(f"Unknown reduction {self._reduction}")
def _get_oneclass_model(self, X_gt: np.ndarray) -> OneClassLayer: X_hash = dataframe_hash(pd.DataFrame(X_gt)) cache_file = self._workspace / f"sc_metric_cache_model_oneclass_{X_hash}_{platform.python_version()}.bkp" if cache_file.exists() and self._use_cache: return load_from_file(cache_file) model = OneClassLayer( input_dim=X_gt.shape[1], rep_dim=X_gt.shape[1], center=torch.ones(X_gt.shape[1]) * 10, ) model.fit(X_gt) save_to_file(cache_file, model) return model.to(DEVICE) def _oneclass_predict(self, model: OneClassLayer, X: np.ndarray) -> np.ndarray: with torch.no_grad(): return model(torch.from_numpy(X).float().to(DEVICE)).cpu().detach().numpy()
[docs] def use_cache(self, path: Path) -> bool: return path.exists() and self._use_cache