From c6d0d2cf877110f083c7412af5afc0701e79a36a Mon Sep 17 00:00:00 2001 From: Christina Dionysio Date: Tue, 23 Jun 2026 11:30:26 +0200 Subject: [PATCH 1/4] reduce resources used in scuro tests --- src/main/python/tests/scuro/test_hp_tuner.py | 9 ++- .../tests/scuro/test_multimodal_join.py | 8 +-- .../tests/scuro/test_unimodal_optimizer.py | 36 ++++------ .../scuro/test_unimodal_representations.py | 70 +++++-------------- .../tests/scuro/test_window_operations.py | 22 +++--- 5 files changed, 53 insertions(+), 92 deletions(-) diff --git a/src/main/python/tests/scuro/test_hp_tuner.py b/src/main/python/tests/scuro/test_hp_tuner.py index c418cefcae8..03ffc1c2dad 100644 --- a/src/main/python/tests/scuro/test_hp_tuner.py +++ b/src/main/python/tests/scuro/test_hp_tuner.py @@ -24,6 +24,7 @@ import numpy as np +from systemds.scuro import Mean from systemds.scuro.drsearch.multimodal_optimizer import MultimodalOptimizer from systemds.scuro.representations.average import Average from systemds.scuro.representations.color_histogram import ColorHistogram @@ -128,7 +129,7 @@ def run_hp_for_modality( { ModalityType.TEXT: [BoW, W2V], ModalityType.AUDIO: [Spectrogram, ZeroCrossing, Spectral, Pitch], - ModalityType.TIMESERIES: [ResNet], + ModalityType.TIMESERIES: [Mean], ModalityType.VIDEO: [ResNet], ModalityType.IMAGE: [ResNet, ColorHistogram], ModalityType.EMBEDDING: [], @@ -136,7 +137,9 @@ def run_hp_for_modality( ): registry = Registry() registry._fusion_operators = [LSTM] - unimodal_optimizer = UnimodalOptimizer(modalities, self.tasks, False) + unimodal_optimizer = UnimodalOptimizer( + modalities, self.tasks, False, k=2, max_num_workers=1 + ) unimodal_optimizer.optimize() hp = HyperparameterTuner( @@ -165,7 +168,7 @@ def run_hp_for_modality( ) else: - hp.tune_unimodal_representations(max_eval_per_rep=10) + hp.tune_unimodal_representations(max_eval_per_rep=2) assert len(hp.optimization_results.results) == len(self.tasks) if multimodal: diff --git a/src/main/python/tests/scuro/test_multimodal_join.py b/src/main/python/tests/scuro/test_multimodal_join.py index 14ce9376be1..4a53129db33 100644 --- a/src/main/python/tests/scuro/test_multimodal_join.py +++ b/src/main/python/tests/scuro/test_multimodal_join.py @@ -47,7 +47,7 @@ def setUpClass(cls): cls.num_instances = 4 cls.indices = np.array(range(cls.num_instances)) cls.audio_data, cls.audio_md = ModalityRandomDataGenerator().create_audio_data( - cls.num_instances, 32000 + cls.num_instances, 500 ) cls.video_data, cls.video_md = ( @@ -104,7 +104,7 @@ def _prepare_data(self, l_chunk_size=None, r_chunk_size=None): l_chunk_size, ModalityType.VIDEO, copy.deepcopy(self.video_data), - np.float32, + np.uint8, copy.deepcopy(self.video_md), ) ) @@ -118,9 +118,7 @@ def _join(self, left_modality, right_modality, window_size): left_modality.join( right_modality, JoinCondition("timestamp", "timestamp", "<") ) - .apply_representation( - ResNet(layer_name="layer1.0.conv2", model_name="ResNet18") - ) + .apply_representation(ResNet()) .window_aggregation(window_size, "mean") .combine("concat") ) diff --git a/src/main/python/tests/scuro/test_unimodal_optimizer.py b/src/main/python/tests/scuro/test_unimodal_optimizer.py index ad824b0335f..11c3aa29ea6 100644 --- a/src/main/python/tests/scuro/test_unimodal_optimizer.py +++ b/src/main/python/tests/scuro/test_unimodal_optimizer.py @@ -23,17 +23,17 @@ import unittest import numpy as np -from systemds.scuro.representations.clip import CLIPText, CLIPVisual from systemds.scuro.representations.color_histogram import ColorHistogram from systemds.scuro.drsearch.operator_registry import Registry from systemds.scuro.drsearch.unimodal_optimizer import UnimodalOptimizer -from systemds.scuro.representations.mfcc import MFCC +from systemds.scuro.representations.covarep_audio_features import ZeroCrossing + +from systemds.scuro.representations.resnet import ResNet from systemds.scuro.representations.mel_spectrogram import MelSpectrogram -from systemds.scuro.representations.word2vec import W2V +from systemds.scuro.representations.tfidf import TfIdf from systemds.scuro.representations.bow import BoW from systemds.scuro.representations.bert import Bert from systemds.scuro.modality.unimodal_modality import UnimodalModality -from systemds.scuro.representations.resnet import ResNet from tests.scuro.data_generator import ( ModalityRandomDataGenerator, TestDataLoader, @@ -53,6 +53,15 @@ from unittest.mock import patch +LIGHTWEIGHT_REGISTRY = { + ModalityType.TEXT: [BoW, TfIdf], + ModalityType.AUDIO: [MelSpectrogram, ZeroCrossing], + ModalityType.VIDEO: [ResNet], + ModalityType.IMAGE: [ColorHistogram], + ModalityType.TIMESERIES: [], + ModalityType.EMBEDDING: [], +} + class TestUnimodalRepresentationOptimizer(unittest.TestCase): data_generator = None @@ -198,24 +207,7 @@ def optimize_unimodal_representation_for_modality(self, modalities): with patch.object( Registry, "_representations", - { - ModalityType.TEXT: [ - W2V, - BoW, - Bert, - CLIPText, - ], - ModalityType.AUDIO: [ - MFCC, - MelSpectrogram, - ], - ModalityType.VIDEO: [ - ResNet, - CLIPVisual, - ], - ModalityType.IMAGE: [ColorHistogram, CLIPVisual], - ModalityType.EMBEDDING: [], - }, + LIGHTWEIGHT_REGISTRY, ): registry = Registry() diff --git a/src/main/python/tests/scuro/test_unimodal_representations.py b/src/main/python/tests/scuro/test_unimodal_representations.py index 2f474be7fd9..59bef40ef64 100644 --- a/src/main/python/tests/scuro/test_unimodal_representations.py +++ b/src/main/python/tests/scuro/test_unimodal_representations.py @@ -19,18 +19,10 @@ # # ------------------------------------------------------------- -import time import unittest import copy import numpy as np -from systemds.scuro.representations.bert import ( - Bert, - ALBERT, - ELECTRA, - RoBERTa, - DistillBERT, -) -from systemds.scuro.representations.clip import CLIPVisual, CLIPText + from systemds.scuro.representations.bow import BoW from systemds.scuro.representations.covarep_audio_features import ( Spectral, @@ -38,20 +30,13 @@ Pitch, ZeroCrossing, ) -from systemds.scuro.representations.glove import GloVe -from systemds.scuro.representations.wav2vec import Wav2Vec +from systemds.scuro.representations.color_histogram import ColorHistogram from systemds.scuro.representations.spectrogram import Spectrogram -from systemds.scuro.representations.window_aggregation import WindowAggregation -from systemds.scuro.representations.word2vec import W2V from systemds.scuro.representations.tfidf import TfIdf -from systemds.scuro.representations.x3d import X3D -from systemds.scuro.representations.x3d import I3D -from systemds.scuro.representations.color_histogram import ColorHistogram +from systemds.scuro.representations.resnet import ResNet from systemds.scuro.modality.unimodal_modality import UnimodalModality from systemds.scuro.representations.mel_spectrogram import MelSpectrogram from systemds.scuro.representations.mfcc import MFCC -from systemds.scuro.representations.resnet import ResNet -from systemds.scuro.representations.swin_video_transformer import SwinVideoTransformer from tests.scuro.data_generator import ( TestDataLoader, ModalityRandomDataGenerator, @@ -72,7 +57,6 @@ ZeroCrossingRate, BandpowerFFT, ) -from systemds.scuro.representations.vgg import VGG19 class TestUnimodalRepresentations(unittest.TestCase): @@ -103,12 +87,11 @@ def _create_audio_modality(self, signal_length=1000): return audio def test_audio_representation_transform_output_shapes(self): - audio = self._create_audio_modality() + audio = self._create_audio_modality(signal_length=200) audio_representations = [ (MFCC(), (2, 12)), (MelSpectrogram(), (2, 128)), (Spectrogram(), (2, 1025)), - (Wav2Vec(), (1, None)), (Spectral(), (2, 4)), (ZeroCrossing(), (2, None)), (RMSE(), (2, None)), @@ -138,14 +121,13 @@ def test_audio_representations(self): MFCC(), MelSpectrogram(), Spectrogram(), - Wav2Vec(), Spectral(), ZeroCrossing(), RMSE(), Pitch(), ] audio_data, audio_md = ModalityRandomDataGenerator().create_audio_data( - self.num_instances, 1000 + self.num_instances, 200 ) audio = UnimodalModality( @@ -181,7 +163,7 @@ def test_timeseries_representations(self): BandpowerFFT(), ] ts_data, ts_md = ModalityRandomDataGenerator().create_timeseries_data( - self.num_instances, 1000 + self.num_instances, 100 ) ts = UnimodalModality( @@ -201,10 +183,8 @@ def test_timeseries_representations(self): assert (ts.data[i] == original_data[i]).all() def test_image_representations(self): - image_representations = [ColorHistogram(), CLIPVisual(), ResNet()] - image_data, image_md = ModalityRandomDataGenerator().create_visual_modality( - self.num_instances, 1 + self.num_instances, 1, height=8, width=8 ) image = UnimodalModality( @@ -213,10 +193,9 @@ def test_image_representations(self): ) ) - for representation in image_representations: - r = image.apply_representation(representation) - assert r.data is not None - assert len(r.data) == self.num_instances + r = image.apply_representation(ColorHistogram()) + assert r.data is not None + assert len(r.data) == self.num_instances # def test_video_representations(self): # video_representations = [ @@ -241,47 +220,34 @@ def test_image_representations(self): # assert len(r.data) == self.num_instances def test_text_representations(self): - test_representations = [ - CLIPText(), - Bert(), - BoW(2, 2), - TfIdf(), - W2V(), - GloVe(), - ALBERT(), - ELECTRA(), - RoBERTa(), - DistillBERT(), - ] text_data, text_md = ModalityRandomDataGenerator().create_text_data( - self.num_instances, 100 + self.num_instances, 3 ) text = UnimodalModality( TestDataLoader( self.indices, None, ModalityType.TEXT, text_data, str, text_md ) ) - for representation in test_representations: + for representation in [BoW(2, 2), TfIdf()]: r = text.apply_representation(representation) assert r.data is not None assert len(r.data) == self.num_instances def test_chunked_video_representations(self): - video_representations = [ResNet()] video_data, video_md = ModalityRandomDataGenerator().create_visual_modality( - self.num_instances, 25 + self.num_instances, 30 ) video = UnimodalModality( TestDataLoader( self.indices, None, ModalityType.VIDEO, video_data, np.float32, video_md ) ) - for representation in video_representations: - r = video.apply_representation(representation) - assert r.data is not None - assert len(r.data) == self.num_instances - assert len(r.metadata) == self.num_instances + r = video.apply_representation(ResNet(model_name="ResNet18")) + assert r.data is not None + assert len(r.data) == self.num_instances + assert len(r.metadata) == self.num_instances +# TODO: add unit tests for the other representations if __name__ == "__main__": unittest.main() diff --git a/src/main/python/tests/scuro/test_window_operations.py b/src/main/python/tests/scuro/test_window_operations.py index 2eaf5985db1..a8c86374801 100644 --- a/src/main/python/tests/scuro/test_window_operations.py +++ b/src/main/python/tests/scuro/test_window_operations.py @@ -39,13 +39,13 @@ class TestWindowOperations(unittest.TestCase): @classmethod def setUpClass(cls): - cls.num_instances = 40 + cls.num_instances = 4 cls.data_generator = ModalityRandomDataGenerator() cls.aggregations = ["mean", "sum", "max", "min"] def test_static_window(self): num_windows = 5 - data, md = self.data_generator.create_visual_modality(self.num_instances, 50) + data, md = self.data_generator.create_visual_modality(self.num_instances, 10) modality = UnimodalModality( TestDataLoader( [i for i in range(0, self.num_instances)], @@ -63,7 +63,7 @@ def test_static_window(self): def test_dynamic_window(self): num_windows = 5 - data, md = self.data_generator.create_visual_modality(self.num_instances, 50) + data, md = self.data_generator.create_visual_modality(self.num_instances, 10) modality = UnimodalModality( TestDataLoader( [i for i in range(0, self.num_instances)], @@ -93,19 +93,21 @@ def test_window_operations_on_text_representations(self): self.run_window_aggregation_for_modality(ModalityType.TEXT, window_size) def run_window_aggregation_for_modality(self, modality_type, window_size): - r = self.data_generator.create1DModality(40, 5000, modality_type) + r = self.data_generator.create1DModality(self.num_instances, 200, modality_type) for aggregation in self.aggregations: windowed_modality = r.window_aggregation(window_size, aggregation) self.verify_window_operation(aggregation, r, windowed_modality, window_size) def test_window_aggregation_on_3d_modality(self): - data, _ = self.data_generator.create_3d_modality(40, (100, 28, 28)) + data, _ = self.data_generator.create_3d_modality( + self.num_instances, (100, 8, 8) + ) embedding_modality = TransformedModality( self.data_generator, "test_transformation" ) embedding_modality.data = data - embedding_modality.stats = RepresentationStats(40, (100, 28, 28)) + embedding_modality.stats = RepresentationStats(self.num_instances, (100, 8, 8)) num_windows = 10 for window_operator in [ @@ -115,17 +117,17 @@ def test_window_aggregation_on_3d_modality(self): ]: stats = window_operator.get_output_stats(embedding_modality.stats) assert stats.num_instances == self.num_instances - assert stats.output_shape == (num_windows, 28, 28) + assert stats.output_shape == (num_windows, 8, 8) windowed_modality = embedding_modality.context(window_operator) def test_window_aggregation_on_2d_modality(self): - data, _ = self.data_generator.create_2d_modality(40, (100, 28)) + data, _ = self.data_generator.create_2d_modality(self.num_instances, (100, 8)) embedding_modality = TransformedModality( self.data_generator, "test_transformation" ) embedding_modality.data = data - embedding_modality.stats = RepresentationStats(40, (100, 28)) + embedding_modality.stats = RepresentationStats(self.num_instances, (100, 8)) num_windows = 10 for window_operator in [ @@ -135,7 +137,7 @@ def test_window_aggregation_on_2d_modality(self): ]: stats = window_operator.get_output_stats(embedding_modality.stats) assert stats.num_instances == self.num_instances - assert stats.output_shape == (num_windows, 28) + assert stats.output_shape == (num_windows, 8) windowed_modality = embedding_modality.context(window_operator) From cd738ceb9d86f90a808dd73f064b42442b9c0d49 Mon Sep 17 00:00:00 2001 From: Christina Dionysio Date: Thu, 6 Aug 2026 16:40:09 +0200 Subject: [PATCH 2/4] add min/max robustness check to hp tuner --- .../scuro/drsearch/hyperparameter_tuner.py | 98 +++++++++++++++---- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py b/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py index 4f04bffcbe5..518809e9ac9 100644 --- a/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py +++ b/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py @@ -40,6 +40,10 @@ from systemds.scuro.utils.checkpointing import CheckpointManager +def _wandb_safe_tag(s: str, max_len: int = 64) -> str: + return s if len(s) <= max_len else s[: max_len - 3] + "..." + + def _get_params_for_node(node_id, params): return { k.split("-")[-1]: v for k, v in params.items() if k.startswith(node_id + "-") @@ -53,6 +57,7 @@ def _param_values_to_spec( return {"name": full_name, "type": "categorical", "domain": list(param_values)} if isinstance(param_values, tuple) and len(param_values) == 2: lo, hi = param_values + lo, hi = min(lo, hi), max(lo, hi) if isinstance(lo, int) and isinstance(hi, int): return {"name": full_name, "type": "integer", "domain": (lo, hi)} return {"name": full_name, "type": "real", "domain": (float(lo), float(hi))} @@ -456,16 +461,25 @@ def visit_node(node_id): modalities_override = ( self._get_cached_modalities_for_task(task, modality_ids) if mm_opt else None ) + + baseline_params, baseline_raw_scores = self.evaluate_dag_config( + dag, + {}, + node_order, + modality_ids, + task, + modalities_override=modalities_override, + ) + baseline = ( + baseline_params, + [ + self._score_value(baseline_raw_scores[0]), + self._score_value_list(baseline_raw_scores[1]), + self._score_value(baseline_raw_scores[2]), + ], + ) + if not hyperparams: - # TODO: extract the information from the unimodal optimization results - baseline = self.evaluate_dag_config( - dag, - {}, - node_order, - modality_ids, - task, - modalities_override=modalities_override, - ) all_results = [baseline] else: param_specs = self._build_param_specs(hyperparams) @@ -482,6 +496,7 @@ def visit_node(node_id): initial_config=None, rep_name=rep_name, ) + all_results.append(baseline) if not all_results: return None @@ -491,14 +506,37 @@ def get_score(result): if isinstance(score, PerformanceMeasure): return score.average_scores[self.scoring_metric] elif isinstance(score, list): - return score[1] + score = score[1] + + if isinstance(score, list): + score = np.mean(score) return score - if self.maximize_metric: - best_params, best_score = max(all_results, key=get_score) - else: - best_params, best_score = min(all_results, key=get_score) + best_params, best_score = all_results[0][0], get_score(all_results[0]) + for params, score in all_results[1:]: + candidate_score = get_score((params, score)) + if self._is_better(candidate_score, best_score): + best_params, best_score = params, candidate_score + + if hyperparams and best_params != baseline_params: + baseline_folds = self._score_value_list(baseline_raw_scores[1]) + candidate_folds = next(s for p, s in all_results if p == best_params)[1] + if baseline_folds is not None and candidate_folds is not None: + accept, candidate_range, baseline_range = self._should_accept_optimized( + baseline_folds, candidate_folds + ) + if not accept: + self.logger.info( + f"{rep_name}: optimized config too variable across folds " + f"(range={candidate_range:.4f} vs baseline={baseline_range:.4f}) " + "— keeping baseline" + ) + best_params, best_score = baseline_params, get_score(baseline) + else: + self.logger.warning( + f"{rep_name}: fold-level scores unavailable, skipping variance gate" + ) tuning_time = time.time() - start_time best_result = HyperparamResult( @@ -572,6 +610,23 @@ def _score_value(self, score: Any) -> float: return score.average_scores.get(self.scoring_metric, np.nan) return score + def _score_value_list(self, score: Any) -> List[float]: + if isinstance(score, PerformanceMeasure): + return score.scores.get(self.scoring_metric, []) + return [score] + + def _should_accept_optimized( + self, + baseline_folds: List[float], + candidate_folds: List[float], + range_threshold: float = 0.03, + ) -> Tuple[bool, float, float]: + baseline_range = max(baseline_folds) - min(baseline_folds) + candidate_range = max(candidate_folds) - min(candidate_folds) + if candidate_range > baseline_range * (1 + range_threshold): + return False, candidate_range, baseline_range + return True, candidate_range, baseline_range + def _is_better(self, candidate_score: float, best_score: float) -> bool: if np.isnan(candidate_score): return False @@ -789,7 +844,10 @@ def _search_best_configs( "project": self.wandb_project, "entity": self.wandb_entity, "group": self.wandb_group or task.model.name, - "tags": self.wandb_tags + [rep_name, task.model.name], + "tags": [ + _wandb_safe_tag(t) + for t in (self.wandb_tags + [rep_name, task.model.name]) + ], "name": f"{task.model.name}-{rep_name}-{int(time.time())}", "config": { "task": task.model.name, @@ -835,10 +893,16 @@ def objective(trial: optuna.Trial) -> float: seen[self._config_key(params)] = ( params, - [train_score, val_score, test_score], + [train_score, self._score_value_list(scores[1]), test_score], ) - trial_results.append((params, [train_score, val_score, test_score])) + trial_results.append((params, [train_score, self._score_value_list(scores[1]), test_score])) + val_folds = self._score_value_list(scores[1]) + if val_folds is not None and len(val_folds) > 1: + lam = 0.5 + robust_val = np.mean(val_folds) - lam * np.std(val_folds, ddof=1) + return robust_val + return val_score callbacks = [c for c in [wandb_cb] if c is not None] From 361847ddbda6ca801974bf9b3e29986156ed0921 Mon Sep 17 00:00:00 2001 From: Christina Dionysio Date: Thu, 6 Aug 2026 16:43:59 +0200 Subject: [PATCH 3/4] add missing params to reps --- .../scuro/representations/color_histogram.py | 5 +++++ .../python/systemds/scuro/representations/lstm.py | 14 ++++++++++---- .../scuro/representations/mlp_averaging.py | 3 +++ .../systemds/scuro/representations/resnet.py | 1 + .../representations/swin_video_transformer.py | 2 ++ .../representations/text_context_with_indices.py | 5 +++++ .../python/systemds/scuro/representations/tfidf.py | 2 ++ .../representations/timeseries_representations.py | 8 ++++++++ .../python/systemds/scuro/representations/vgg.py | 2 ++ .../systemds/scuro/representations/word2vec.py | 3 +++ .../python/systemds/scuro/representations/x3d.py | 5 ++++- 11 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/python/systemds/scuro/representations/color_histogram.py b/src/main/python/systemds/scuro/representations/color_histogram.py index d1c7175b166..993b179fb8b 100644 --- a/src/main/python/systemds/scuro/representations/color_histogram.py +++ b/src/main/python/systemds/scuro/representations/color_histogram.py @@ -49,6 +49,11 @@ def __init__( super().__init__( "ColorHistogram", ModalityType.EMBEDDING, self._get_parameters() ) + if params is not None: + color_space = params.get("color_space", color_space) + bins = params.get("bins", bins) + normalize = params.get("normalize", normalize) + aggregation = params.get("aggregation", aggregation) self.color_space = color_space self.bins = bins self.normalize = normalize diff --git a/src/main/python/systemds/scuro/representations/lstm.py b/src/main/python/systemds/scuro/representations/lstm.py index c15776284ce..08d5bc8af78 100644 --- a/src/main/python/systemds/scuro/representations/lstm.py +++ b/src/main/python/systemds/scuro/representations/lstm.py @@ -51,19 +51,27 @@ def __init__( "depth": [1, 2, 3], "dropout_rate": [0.1, 0.2, 0.3, 0.4, 0.5], "learning_rate": [0.001, 0.0001, 0.01, 0.1], - "epochs": [10, 2050, 100, 200], + "epochs": [10, 20, 50, 100, 200], "batch_size": [8, 16, 32, 64, 128], } super().__init__("LSTM", parameters) + if params is not None: + width = params.get("width", width) + depth = params.get("depth", depth) + dropout_rate = params.get("dropout_rate", dropout_rate) + learning_rate = params.get("learning_rate", learning_rate) + epochs = params.get("epochs", epochs) + batch_size = params.get("batch_size", batch_size) + self.width = int(width) self.depth = int(depth) self.dropout_rate = float(dropout_rate) self.learning_rate = float(learning_rate) self.epochs = int(epochs) self.batch_size = int(batch_size) - + self.device = get_device() self.needs_training = True self.needs_alignment = True self.model = None @@ -180,7 +188,6 @@ def execute(self, modalities: List[Modality], labels: np.ndarray = None): self.input_dim = X.shape[2] self.model = self._build_model(self.input_dim, self.num_classes) - self.device = get_device_for_model(self.model, memory_factor=1.5) self.model = self.model.to(self.device) if self.is_multilabel: @@ -245,7 +252,6 @@ def apply_representation(self, modalities: List[Modality]) -> np.ndarray: X = self._prepare_data(modalities) - self.device = get_device_for_model(self.model, memory_factor=1.5) self.model = self.model.to(self.device) X_tensor = torch.FloatTensor(X) diff --git a/src/main/python/systemds/scuro/representations/mlp_averaging.py b/src/main/python/systemds/scuro/representations/mlp_averaging.py index 8c8d67a06ec..fb71424d738 100644 --- a/src/main/python/systemds/scuro/representations/mlp_averaging.py +++ b/src/main/python/systemds/scuro/representations/mlp_averaging.py @@ -54,6 +54,9 @@ def __init__(self, output_dim=512, batch_size=32, params=None): "batch_size": [8, 16, 32, 64, 128], } super().__init__("MLPAveraging", parameters) + if params is not None: + output_dim = params.get("output_dim", output_dim) + batch_size = params.get("batch_size", batch_size) self.output_dim = output_dim self.batch_size = batch_size self.device = None diff --git a/src/main/python/systemds/scuro/representations/resnet.py b/src/main/python/systemds/scuro/representations/resnet.py index 1202748aa63..26a3350e9c8 100644 --- a/src/main/python/systemds/scuro/representations/resnet.py +++ b/src/main/python/systemds/scuro/representations/resnet.py @@ -56,6 +56,7 @@ def __init__( if params is not None: self.batch_size = int(params.get("batch_size", batch_size)) self.layer_name = params.get("layer_name", layer_name) + model_name = params.get("model_name", model_name) else: self.batch_size = batch_size self.layer_name = layer_name diff --git a/src/main/python/systemds/scuro/representations/swin_video_transformer.py b/src/main/python/systemds/scuro/representations/swin_video_transformer.py index 39191f2f252..7bb40e278f3 100644 --- a/src/main/python/systemds/scuro/representations/swin_video_transformer.py +++ b/src/main/python/systemds/scuro/representations/swin_video_transformer.py @@ -59,6 +59,8 @@ def __init__(self, layer_name="avgpool", params=None): } self.data_type = torch.float32 super().__init__("SwinVideoTransformer", ModalityType.EMBEDDING, parameters) + if params is not None: + layer_name = params.get("layer_name", layer_name) self.layer_name = layer_name self.model = swin3d_t(weights=models.video.Swin3D_T_Weights.KINETICS400_V1) self.device = get_device_for_model(self.model, memory_factor=1.5) diff --git a/src/main/python/systemds/scuro/representations/text_context_with_indices.py b/src/main/python/systemds/scuro/representations/text_context_with_indices.py index 1a341af1e3a..4de53698d7a 100644 --- a/src/main/python/systemds/scuro/representations/text_context_with_indices.py +++ b/src/main/python/systemds/scuro/representations/text_context_with_indices.py @@ -296,6 +296,11 @@ def __init__(self, max_words=55, overlap=0.5, stride=None, params=None): "stride": [10, 15, 20, 30], } super().__init__("OverlappingSplit", parameters) + if params is not None: + max_words = params.get("max_words", max_words) + overlap = params.get("overlap", overlap) + overlap_words = int(max_words * overlap) + stride = params.get("stride", max_words - overlap_words) self.max_words = max_words self.overlap = overlap self.stride = stride diff --git a/src/main/python/systemds/scuro/representations/tfidf.py b/src/main/python/systemds/scuro/representations/tfidf.py index 0b603f247e3..3c3d894c173 100644 --- a/src/main/python/systemds/scuro/representations/tfidf.py +++ b/src/main/python/systemds/scuro/representations/tfidf.py @@ -36,6 +36,8 @@ class TfIdf(UnimodalRepresentation): def __init__(self, min_df=2, output_file=None, params=None): parameters = {"min_df": [min_df, 4, 8]} super().__init__("TF-IDF", ModalityType.EMBEDDING, parameters) + if params is not None: + min_df = params.get("min_df", min_df) self.min_df = int(min_df) self.output_file = output_file self.data_type = np.float32 diff --git a/src/main/python/systemds/scuro/representations/timeseries_representations.py b/src/main/python/systemds/scuro/representations/timeseries_representations.py index 14fcacf724f..6bf7f38d132 100644 --- a/src/main/python/systemds/scuro/representations/timeseries_representations.py +++ b/src/main/python/systemds/scuro/representations/timeseries_representations.py @@ -199,6 +199,8 @@ def compute_feature(self, signal, axis=-1): class ACF(TimeSeriesRepresentation): def __init__(self, k=1, params=None): super().__init__("ACF", {"k": [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]}) + if params is not None: + k = params.get("k", k) self.k = k def compute_feature(self, signal, axis=-1): @@ -246,6 +248,8 @@ def compute_feature(self, signal, axis=-1): class SpectralCentroid(TimeSeriesRepresentation): def __init__(self, fs=1.0, params=None): super().__init__("SpectralCentroid", parameters={"fs": [0.5, 1.0, 2.0]}) + if params is not None: + fs = params.get("fs", fs) self.fs = fs def compute_feature(self, signal, axis=-1): @@ -271,6 +275,10 @@ def __init__(self, fs=1.0, f1=0.0, f2=0.5, params=None): "BandpowerFFT", parameters={"fs": [0.5, 1.0], "f1": [0.0, 1.0], "f2": [0.5, 1.0]}, ) + if params is not None: + fs = params.get("fs", fs) + f1 = params.get("f1", f1) + f2 = params.get("f2", f2) self.fs = fs self.f1 = f1 self.f2 = f2 diff --git a/src/main/python/systemds/scuro/representations/vgg.py b/src/main/python/systemds/scuro/representations/vgg.py index 35bc07d8a29..c2b56e8d6bd 100644 --- a/src/main/python/systemds/scuro/representations/vgg.py +++ b/src/main/python/systemds/scuro/representations/vgg.py @@ -56,6 +56,8 @@ def __init__( self.model = self.model.to(self.device) parameters = self._get_parameters() super().__init__("VGG19", ModalityType.EMBEDDING, parameters) + if params is not None: + layer = params.get("layer_name", layer) self.output_file = output_file self.layer_name = layer self.model.eval() diff --git a/src/main/python/systemds/scuro/representations/word2vec.py b/src/main/python/systemds/scuro/representations/word2vec.py index a744bc8db37..bc1c8791f20 100644 --- a/src/main/python/systemds/scuro/representations/word2vec.py +++ b/src/main/python/systemds/scuro/representations/word2vec.py @@ -49,6 +49,9 @@ def __init__(self, vector_size=150, min_count=1, output_file=None, params=None): "min_count": [1, 2, 4, 8], } super().__init__("Word2Vec", ModalityType.EMBEDDING, parameters) + if params is not None: + vector_size = params.get("vector_size", vector_size) + min_count = params.get("min_count", min_count) self.vector_size = vector_size self.min_count = min_count self.output_file = output_file diff --git a/src/main/python/systemds/scuro/representations/x3d.py b/src/main/python/systemds/scuro/representations/x3d.py index ace4cf4b8ca..bba22434fc4 100644 --- a/src/main/python/systemds/scuro/representations/x3d.py +++ b/src/main/python/systemds/scuro/representations/x3d.py @@ -50,6 +50,9 @@ def __init__( self, layer="classifier.1", model_name="s3d", output_file=None, params=None ): self.data_type = torch.float32 + if params is not None: + model_name = params.get("model_name", model_name) + layer = params.get("layer_name", layer) self.model_name = model_name parameters = self._get_parameters() super().__init__("X3D", ModalityType.EMBEDDING, parameters) @@ -127,7 +130,7 @@ def model_name(self, model_name): def _get_parameters(self, high_level=True): parameters = {"model_name": [], "layer_name": []} - for m in ["c3d", "s3d"]: + for m in ["r3d", "s3d"]: parameters["model_name"].append(m) # TODO: add embedding dimensions for each layer From 79d1512e1696c118d2c16690d33b79b8c9926c39 Mon Sep 17 00:00:00 2001 From: Christina Dionysio Date: Thu, 6 Aug 2026 17:28:44 +0200 Subject: [PATCH 4/4] formatting --- .../systemds/scuro/drsearch/hyperparameter_tuner.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py b/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py index 518809e9ac9..9407bdf1250 100644 --- a/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py +++ b/src/main/python/systemds/scuro/drsearch/hyperparameter_tuner.py @@ -507,7 +507,7 @@ def get_score(result): return score.average_scores[self.scoring_metric] elif isinstance(score, list): score = score[1] - + if isinstance(score, list): score = np.mean(score) return score @@ -519,7 +519,7 @@ def get_score(result): best_params, best_score = params, candidate_score if hyperparams and best_params != baseline_params: - baseline_folds = self._score_value_list(baseline_raw_scores[1]) + baseline_folds = self._score_value_list(baseline_raw_scores[1]) candidate_folds = next(s for p, s in all_results if p == best_params)[1] if baseline_folds is not None and candidate_folds is not None: @@ -896,7 +896,9 @@ def objective(trial: optuna.Trial) -> float: [train_score, self._score_value_list(scores[1]), test_score], ) - trial_results.append((params, [train_score, self._score_value_list(scores[1]), test_score])) + trial_results.append( + (params, [train_score, self._score_value_list(scores[1]), test_score]) + ) val_folds = self._score_value_list(scores[1]) if val_folds is not None and len(val_folds) > 1: lam = 0.5