From 27ca26a417093e1f4d65ab4588485524baa07f3a Mon Sep 17 00:00:00 2001
From: Mathieu Beligon <mathieu@feedly.com>
Date: Thu, 17 Sep 2020 12:10:28 +0200
Subject: [PATCH] [robots@robots] (minor) make the evaluation objects generic

---
 .../dataset/image_dataset_generator.py        | 27 ---------------
 .../image_pipeline_evaluation_reporter.py     | 27 +++++++++------
 .../evaluation/image_pipeline_evaluator.py    | 34 ++++++++++---------
 3 files changed, 34 insertions(+), 54 deletions(-)
 delete mode 100644 robots-at-robots/research/robots_at_robots/dataset/image_dataset_generator.py

diff --git a/robots-at-robots/research/robots_at_robots/dataset/image_dataset_generator.py b/robots-at-robots/research/robots_at_robots/dataset/image_dataset_generator.py
deleted file mode 100644
index f9b321b..0000000
--- a/robots-at-robots/research/robots_at_robots/dataset/image_dataset_generator.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from abc import abstractmethod
-from pathlib import Path
-from typing import Generic, Iterable, List, Tuple, TypeVar
-
-from polystar.common.models.image import Image, load_image
-from research.common.dataset.directory_roco_dataset import DirectoryROCODataset
-
-T = TypeVar("T")
-
-
-class ImageDatasetGenerator(Generic[T]):
-    def from_roco_datasets(
-        self, datasets: Iterable[DirectoryROCODataset]
-    ) -> Tuple[List[Path], List[Image], List[T], List[int]]:
-        images_path, images, labels, dataset_sizes = [], [], [], []
-        for dataset in datasets:
-            prev_total_size = len(images)
-            for img_path, label in self.from_roco_dataset(dataset):
-                images_path.append(img_path)
-                images.append(load_image(img_path))
-                labels.append(label)
-            dataset_sizes.append(len(images) - prev_total_size)
-        return images_path, images, labels, dataset_sizes
-
-    @abstractmethod
-    def from_roco_dataset(self, dataset: DirectoryROCODataset) -> Iterable[Tuple[Path, T]]:
-        pass
diff --git a/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluation_reporter.py b/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluation_reporter.py
index 0e3cd25..dadcd94 100644
--- a/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluation_reporter.py
+++ b/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluation_reporter.py
@@ -2,24 +2,27 @@ from collections import Counter
 from dataclasses import dataclass
 from os.path import relpath
 from pathlib import Path
-from typing import Any, Dict, Iterable, List, Tuple
+from typing import Any, Dict, Iterable, List, Tuple, Generic
 
 import numpy as np
 from pandas import DataFrame
 from polystar.common.image_pipeline.image_pipeline import ImagePipeline
-from polystar.common.utils.dataframe import (format_df_column, format_df_row,
-                                             format_df_rows)
+from polystar.common.utils.dataframe import format_df_column, format_df_row, format_df_rows
 from polystar.common.utils.markdown import MarkdownFile
 from polystar.common.utils.time import create_time_id
 from research.common.constants import DSET_DIR, EVALUATION_DIR
 from research.common.datasets.roco.roco_dataset import ROCOFileDataset
+from research.robots_at_robots.dataset.armor_value_dataset import ValueT
 from research.robots_at_robots.evaluation.image_pipeline_evaluator import (
-    ClassificationResults, ImagePipelineEvaluator, SetClassificationResults)
+    ClassificationResults,
+    ImagePipelineEvaluator,
+    SetClassificationResults,
+)
 
 
 @dataclass
-class ImagePipelineEvaluationReporter:
-    evaluator: ImagePipelineEvaluator
+class ImagePipelineEvaluationReporter(Generic[ValueT]):
+    evaluator: ImagePipelineEvaluator[ValueT]
     evaluation_project: str
     main_metric: Tuple[str, str] = ("f1-score", "weighted avg")
 
@@ -67,17 +70,17 @@ class ImagePipelineEvaluationReporter:
         df["Repartition"] = (df["Total"] / total).map("{:.1%}".format)
         mf.table(df)
 
-    def _report_aggregated_results(self, mf: MarkdownFile, pipeline2results: Dict[str, ClassificationResults]):
+    def _report_aggregated_results(self, mf: MarkdownFile, pipeline2results: Dict[str, ClassificationResults[ValueT]]):
         aggregated_results = self._aggregate_results(pipeline2results)
         mf.title("Aggregated results", level=2)
         mf.paragraph("On test set:")
         mf.table(aggregated_results)
 
-    def _report_pipelines_results(self, mf: MarkdownFile, pipeline2results: Dict[str, ClassificationResults]):
+    def _report_pipelines_results(self, mf: MarkdownFile, pipeline2results: Dict[str, ClassificationResults[ValueT]]):
         for pipeline_name, results in pipeline2results.items():
             self._report_pipeline_results(mf, pipeline_name, results)
 
-    def _report_pipeline_results(self, mf: MarkdownFile, pipeline_name: str, results: ClassificationResults):
+    def _report_pipeline_results(self, mf: MarkdownFile, pipeline_name: str, results: ClassificationResults[ValueT]):
         mf.title(pipeline_name, level=2)
 
         mf.paragraph(results.full_pipeline_name)
@@ -93,7 +96,9 @@ class ImagePipelineEvaluationReporter:
         )
 
     @staticmethod
-    def _report_pipeline_set_results(mf: MarkdownFile, results: SetClassificationResults, image_paths: List[Path]):
+    def _report_pipeline_set_results(
+        mf: MarkdownFile, results: SetClassificationResults[ValueT], image_paths: List[Path]
+    ):
         mf.title("Metrics", level=4)
         mf.paragraph(f"Inference time: {results.mean_inference_time: .2e} s/img")
         df = DataFrame(results.report)
@@ -119,7 +124,7 @@ class ImagePipelineEvaluationReporter:
             ).set_index("images")
         )
 
-    def _aggregate_results(self, pipeline2results: Dict[str, ClassificationResults]) -> DataFrame:
+    def _aggregate_results(self, pipeline2results: Dict[str, ClassificationResults[ValueT]]) -> DataFrame:
         main_metric_name = f"{self.main_metric[0]} {self.main_metric[1]}"
         df = DataFrame(columns=["pipeline", main_metric_name, "inf time"]).set_index("pipeline")
 
diff --git a/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluator.py b/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluator.py
index cb4797a..27afedb 100644
--- a/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluator.py
+++ b/robots-at-robots/research/robots_at_robots/evaluation/image_pipeline_evaluator.py
@@ -2,7 +2,7 @@ import logging
 from dataclasses import dataclass
 from pathlib import Path
 from time import time
-from typing import Any, Dict, Iterable, List, Sequence, Tuple
+from typing import Dict, Generic, Iterable, List, Sequence, Tuple
 
 import numpy as np
 from memoized_property import memoized_property
@@ -10,13 +10,13 @@ from polystar.common.image_pipeline.image_pipeline import ImagePipeline
 from polystar.common.models.image import Image, load_images
 from research.common.datasets.roco.directory_roco_dataset import \
     DirectoryROCODataset
-from research.robots_at_robots.dataset.armor_value_dataset import \
-    ArmorValueDatasetCache
+from research.robots_at_robots.dataset.armor_value_dataset import (
+    ArmorValueDatasetCache, ValueT)
 from sklearn.metrics import classification_report, confusion_matrix
 
 
 @dataclass
-class SetClassificationResults:
+class SetClassificationResults(Generic[ValueT]):
     labels: np.ndarray
     predictions: np.ndarray
     mean_inference_time: float
@@ -34,23 +34,23 @@ class SetClassificationResults:
         return np.where(self.labels != self.predictions)[0]
 
     @memoized_property
-    def unique_labels(self) -> List[Any]:
+    def unique_labels(self) -> List[ValueT]:
         return sorted(set(self.labels) | set(self.predictions))
 
 
 @dataclass
-class ClassificationResults:
-    train_results: SetClassificationResults
-    test_results: SetClassificationResults
+class ClassificationResults(Generic[ValueT]):
+    train_results: SetClassificationResults[ValueT]
+    test_results: SetClassificationResults[ValueT]
     full_pipeline_name: str
 
 
-class ImagePipelineEvaluator:
+class ImagePipelineEvaluator(Generic[ValueT]):
     def __init__(
         self,
         train_roco_datasets: List[DirectoryROCODataset],
         test_roco_datasets: List[DirectoryROCODataset],
-        image_dataset_cache: ArmorValueDatasetCache,
+        image_dataset_cache: ArmorValueDatasetCache[ValueT],
     ):
         logging.info("Loading data")
         self.train_roco_datasets = train_roco_datasets
@@ -63,22 +63,24 @@ class ImagePipelineEvaluator:
         )
 
     def evaluate_pipelines(self, pipelines: Iterable[ImagePipeline]) -> Dict[str, ClassificationResults]:
-        return {str(pipeline): self.evaluate(pipeline) for pipeline in pipelines}
+        return {str(pipeline): self.evaluate_pipeline(pipeline) for pipeline in pipelines}
 
-    def evaluate(self, pipeline: ImagePipeline) -> ClassificationResults:
+    def evaluate_pipeline(self, pipeline: ImagePipeline) -> ClassificationResults:
         logging.info(f"Training pipeline {pipeline}")
         pipeline.fit(self.train_images, self.train_labels)
 
         logging.info(f"Infering")
-        train_results = self._evaluate_on_set(pipeline, self.train_images, self.train_labels)
-        test_results = self._evaluate_on_set(pipeline, self.test_images, self.test_labels)
+        train_results = self._evaluate_pipeline_on_set(pipeline, self.train_images, self.train_labels)
+        test_results = self._evaluate_pipeline_on_set(pipeline, self.test_images, self.test_labels)
 
         return ClassificationResults(
             train_results=train_results, test_results=test_results, full_pipeline_name=repr(pipeline),
         )
 
     @staticmethod
-    def _evaluate_on_set(pipeline: ImagePipeline, images: List[Image], labels: List[Any]) -> SetClassificationResults:
+    def _evaluate_pipeline_on_set(
+        pipeline: ImagePipeline, images: List[Image], labels: List[ValueT]
+    ) -> SetClassificationResults:
         t = time()
         preds = pipeline.predict(images)
         mean_time = (time() - t) / len(images)
@@ -87,7 +89,7 @@ class ImagePipelineEvaluator:
 
 def load_datasets(
     roco_datasets: List[DirectoryROCODataset], image_dataset_cache: ArmorValueDatasetCache,
-) -> Tuple[List[Path], List[Image], List[Any], List[int]]:
+) -> Tuple[List[Path], List[Image], List[ValueT], List[int]]:
     dataset = image_dataset_cache.from_roco_datasets(roco_datasets)
     dataset_sizes = [len(d) for d in dataset.datasets]
 
-- 
GitLab