Skip to content

Commit 608ddcd

Browse files
[SYTEMDS-3937] Access measures in optimizers correctly
This patch fixes some errors in how the optimizers access the performance meausures and correctly execute the ranking functionality.
1 parent d9f6c6d commit 608ddcd

File tree

14 files changed

+157
-126
lines changed

14 files changed

+157
-126
lines changed

src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ def extract_k_best_modalities_per_task(self):
103103
representations[task.model.name] = {}
104104
for modality in self.modalities:
105105
k_best_results, cached_data = (
106-
self.optimization_results.get_k_best_results(modality, self.k, task)
106+
self.optimization_results.get_k_best_results(
107+
modality, self.k, task, self.scoring_metric
108+
)
107109
)
108110
representations[task.model.name][modality.modality_id] = k_best_results
109111
self.k_best_representations[task.model.name].extend(k_best_results)

src/main/python/systemds/scuro/drsearch/multimodal_optimizer.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#
2020
# -------------------------------------------------------------
2121
import os
22+
import torch
2223
import multiprocessing as mp
2324
import itertools
2425
import threading
@@ -83,8 +84,9 @@ def _evaluate_dag_worker(dag_pickle, task_pickle, modalities_pickle, debug=False
8384

8485
return OptimizationResult(
8586
dag=dag_copy,
86-
train_score=scores[0],
87-
val_score=scores[1],
87+
train_score=scores[0].average_scores,
88+
val_score=scores[1].average_scores,
89+
test_score=scores[2].average_scores,
8890
runtime=total_time,
8991
task_name=task_copy.model.name,
9092
task_time=eval_time,
@@ -106,6 +108,7 @@ def __init__(
106108
debug: bool = True,
107109
min_modalities: int = 2,
108110
max_modalities: int = None,
111+
metric: str = "accuracy",
109112
):
110113
self.modalities = modalities
111114
self.tasks = tasks
@@ -116,6 +119,7 @@ def __init__(
116119

117120
self.operator_registry = Registry()
118121
self.fusion_operators = self.operator_registry.get_fusion_operators()
122+
self.metric_name = metric
119123

120124
self.k_best_representations = self._extract_k_best_representations(
121125
unimodal_optimization_results
@@ -242,7 +246,7 @@ def _extract_k_best_representations(
242246
for modality in self.modalities:
243247
k_best_results, cached_data = (
244248
unimodal_optimization_results.get_k_best_results(
245-
modality, self.k, task
249+
modality, self.k, task, self.metric_name
246250
)
247251
)
248252

@@ -367,6 +371,8 @@ def _evaluate_dag(self, dag: RepresentationDag, task: Task) -> "OptimizationResu
367371
task_copy,
368372
)
369373

374+
torch.cuda.empty_cache()
375+
370376
if fused_representation is None:
371377
return None
372378

@@ -388,8 +394,9 @@ def _evaluate_dag(self, dag: RepresentationDag, task: Task) -> "OptimizationResu
388394

389395
return OptimizationResult(
390396
dag=dag_copy,
391-
train_score=scores[0],
392-
val_score=scores[1],
397+
train_score=scores[0].average_scores,
398+
val_score=scores[1].average_scores,
399+
test_score=scores[2].average_scores,
393400
runtime=total_time,
394401
representation_time=total_time - eval_time,
395402
task_name=task_copy.model.name,
@@ -479,6 +486,7 @@ class OptimizationResult:
479486
dag: RepresentationDag
480487
train_score: PerformanceMeasure = None
481488
val_score: PerformanceMeasure = None
489+
test_score: PerformanceMeasure = None
482490
runtime: float = 0.0
483491
task_time: float = 0.0
484492
representation_time: float = 0.0

src/main/python/systemds/scuro/drsearch/ranking.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
#
2020
# -------------------------------------------------------------
2121

22-
from dataclasses import replace
23-
from typing import Callable, Iterable, List, Optional
22+
from typing import Callable, Iterable, Optional
2423

2524

2625
def rank_by_tradeoff(
@@ -31,14 +30,15 @@ def rank_by_tradeoff(
3130
runtime_accessor: Optional[Callable[[object], float]] = None,
3231
cache_scores: bool = True,
3332
score_attr: str = "tradeoff_score",
34-
) -> List:
33+
):
3534
entries = list(entries)
3635
if not entries:
3736
return []
3837

3938
performance_score_accessor = lambda entry: getattr(entry, "val_score")[
4039
performance_metric_name
4140
]
41+
4242
if runtime_accessor is None:
4343

4444
def runtime_accessor(entry):
@@ -77,14 +77,17 @@ def safe_normalize(values, vmin, vmax):
7777
if cache_scores:
7878
for entry, score in zip(entries, scores):
7979
if hasattr(entry, score_attr):
80-
try:
81-
new_entry = replace(entry, **{score_attr: score})
82-
entries[entries.index(entry)] = new_entry
83-
except TypeError:
84-
setattr(entry, score_attr, score)
80+
setattr(entry, score_attr, score)
8581
else:
8682
setattr(entry, score_attr, score)
8783

88-
return sorted(
89-
entries, key=lambda entry: getattr(entry, score_attr, 0.0), reverse=True
90-
)
84+
sorted_entries = sorted(entries, key=lambda e: e.tradeoff_score, reverse=True)
85+
86+
sorted_indices = [
87+
i
88+
for i, _ in sorted(
89+
enumerate(entries), key=lambda pair: pair[1].tradeoff_score, reverse=True
90+
)
91+
]
92+
93+
return sorted_entries, sorted_indices

src/main/python/systemds/scuro/drsearch/task.py

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@
2020
# -------------------------------------------------------------
2121
import copy
2222
import time
23-
from typing import List, Union
24-
from systemds.scuro.modality.modality import Modality
25-
from systemds.scuro.representations.representation import Representation
23+
from typing import List
2624
from systemds.scuro.models.model import Model
2725
import numpy as np
28-
from sklearn.model_selection import KFold
26+
from sklearn.model_selection import train_test_split
2927

3028

3129
class PerformanceMeasure:
@@ -69,7 +67,8 @@ def __init__(
6967
val_indices: List,
7068
kfold=5,
7169
measure_performance=True,
72-
performance_measures="accuracy",
70+
performance_measures=["accuracy"],
71+
fusion_train_split=0.8,
7372
):
7473
"""
7574
Parent class for the prediction task that is performed on top of the aligned representation
@@ -85,7 +84,7 @@ def __init__(
8584
self.model = model
8685
self.labels = labels
8786
self.train_indices = train_indices
88-
self.val_indices = val_indices
87+
self.test_indices = val_indices
8988
self.kfold = kfold
9089
self.measure_performance = measure_performance
9190
self.inference_time = []
@@ -94,6 +93,47 @@ def __init__(
9493
self.performance_measures = performance_measures
9594
self.train_scores = PerformanceMeasure("train", performance_measures)
9695
self.val_scores = PerformanceMeasure("val", performance_measures)
96+
self.test_scores = PerformanceMeasure("test", performance_measures)
97+
self.fusion_train_indices = None
98+
self._create_cv_splits()
99+
100+
def _create_cv_splits(self):
101+
train_labels = [self.labels[i] for i in self.train_indices]
102+
train_labels_array = np.array(train_labels)
103+
104+
train_indices_array = np.array(self.train_indices)
105+
106+
self.cv_train_indices = []
107+
self.cv_val_indices = []
108+
109+
for fold_idx in range(self.kfold):
110+
fold_train_indices_array, fold_val_indices_array, _, _ = train_test_split(
111+
train_indices_array,
112+
train_labels_array,
113+
test_size=0.2,
114+
shuffle=True,
115+
random_state=11 + fold_idx,
116+
)
117+
118+
fold_train_indices = fold_train_indices_array.tolist()
119+
fold_val_indices = fold_val_indices_array.tolist()
120+
121+
self.cv_train_indices.append(fold_train_indices)
122+
self.cv_val_indices.append(fold_val_indices)
123+
124+
overlap = set(fold_train_indices) & set(fold_val_indices)
125+
if overlap:
126+
raise ValueError(
127+
f"Fold {fold_idx}: Overlap detected between train and val indices: {overlap}"
128+
)
129+
130+
all_val_indices = set()
131+
for val_indices in self.cv_val_indices:
132+
all_val_indices.update(val_indices)
133+
134+
self.fusion_train_indices = [
135+
idx for idx in self.train_indices if idx not in all_val_indices
136+
]
97137

98138
def create_model(self):
99139
"""
@@ -107,12 +147,12 @@ def create_model(self):
107147
def get_train_test_split(self, data):
108148
X_train = [data[i] for i in self.train_indices]
109149
y_train = [self.labels[i] for i in self.train_indices]
110-
if self.val_indices is None:
150+
if self.test_indices is None:
111151
X_test = None
112152
y_test = None
113153
else:
114-
X_test = [data[i] for i in self.val_indices]
115-
y_test = [self.labels[i] for i in self.val_indices]
154+
X_test = [data[i] for i in self.test_indices]
155+
y_test = [self.labels[i] for i in self.test_indices]
116156

117157
return X_train, y_train, X_test, y_test
118158

@@ -125,71 +165,44 @@ def run(self, data):
125165
"""
126166
self._reset_params()
127167
model = self.create_model()
128-
skf = KFold(n_splits=self.kfold, shuffle=True, random_state=11)
129168

130-
fold = 0
131-
X, y, _, _ = self.get_train_test_split(data)
169+
test_X = np.array([data[i] for i in self.test_indices])
170+
test_y = np.array([self.labels[i] for i in self.test_indices])
171+
172+
for fold_idx in range(self.kfold):
173+
fold_train_indices = self.cv_train_indices[fold_idx]
174+
fold_val_indices = self.cv_val_indices[fold_idx]
132175

133-
for train, test in skf.split(X, y):
134-
train_X = np.array(X)[train]
135-
train_y = np.array(y)[train]
136-
test_X = np.array(X)[test]
137-
test_y = np.array(y)[test]
138-
self._run_fold(model, train_X, train_y, test_X, test_y)
139-
fold += 1
176+
train_X = np.array([data[i] for i in fold_train_indices])
177+
train_y = np.array([self.labels[i] for i in fold_train_indices])
178+
val_X = np.array([data[i] for i in fold_val_indices])
179+
val_y = np.array([self.labels[i] for i in fold_val_indices])
180+
181+
self._run_fold(model, train_X, train_y, val_X, val_y, test_X, test_y)
140182

141183
return [
142184
self.train_scores.compute_averages(),
143185
self.val_scores.compute_averages(),
186+
self.test_scores.compute_averages(),
144187
]
145188

146189
def _reset_params(self):
147190
self.inference_time = []
148191
self.training_time = []
149192
self.train_scores = PerformanceMeasure("train", self.performance_measures)
150193
self.val_scores = PerformanceMeasure("val", self.performance_measures)
194+
self.test_scores = PerformanceMeasure("test", self.performance_measures)
151195

152-
def _run_fold(self, model, train_X, train_y, test_X, test_y):
196+
def _run_fold(self, model, train_X, train_y, val_X, val_y, test_X, test_y):
153197
train_start = time.time()
154-
train_score = model.fit(train_X, train_y, test_X, test_y)
198+
train_score = model.fit(train_X, train_y, val_X, val_y)
155199
train_end = time.time()
156200
self.training_time.append(train_end - train_start)
157201
self.train_scores.add_scores(train_score[0])
202+
val_score = model.test(val_X, val_y)
158203
test_start = time.time()
159204
test_score = model.test(np.array(test_X), test_y)
160205
test_end = time.time()
161206
self.inference_time.append(test_end - test_start)
162-
self.val_scores.add_scores(test_score[0])
163-
164-
def create_representation_and_run(
165-
self,
166-
representation: Representation,
167-
modalities: Union[List[Modality], Modality],
168-
):
169-
self._reset_params()
170-
skf = KFold(n_splits=self.kfold, shuffle=True, random_state=11)
171-
172-
fold = 0
173-
X, y, _, _ = self.get_train_test_split(data)
174-
175-
for train, test in skf.split(X, y):
176-
train_X = np.array(X)[train]
177-
train_y = np.array(y)[train]
178-
test_X = s.transform(np.array(X)[test])
179-
test_y = np.array(y)[test]
180-
181-
if isinstance(modalities, Modality):
182-
rep = modality.apply_representation(representation())
183-
else:
184-
representation().transform(
185-
train_X, train_y
186-
) # TODO: think about a way how to handle masks
187-
188-
self._run_fold(train_X, train_y, test_X, test_y)
189-
fold += 1
190-
191-
if self.measure_performance:
192-
self.inference_time = np.mean(self.inference_time)
193-
self.training_time = np.mean(self.training_time)
194-
195-
return [np.mean(train_scores), np.mean(test_scores)]
207+
self.val_scores.add_scores(val_score[0])
208+
self.test_scores.add_scores(test_score[0])

0 commit comments

Comments
 (0)