This is an automated email from the ASF dual-hosted git repository.
mboehm7 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/systemds.git
The following commit(s) were added to refs/heads/main by this push:
new 8f1a402645 [SYSTEMDS-3701] New Scuro data representation search
primitive
8f1a402645 is described below
commit 8f1a402645185baaf8c92bed42b03c89b9050312
Author: Christina Dionysio <[email protected]>
AuthorDate: Wed Aug 21 19:15:47 2024 +0200
[SYSTEMDS-3701] New Scuro data representation search primitive
Closes #2068.
---
.../python/systemds/scuro/aligner/dr_search.py | 124 +++++++++++++++++++++
src/main/python/systemds/scuro/aligner/task.py | 58 ++++++++++
src/main/python/systemds/scuro/main.py | 30 +++--
.../systemds/scuro/modality/video_modality.py | 2 +-
4 files changed, 204 insertions(+), 10 deletions(-)
diff --git a/src/main/python/systemds/scuro/aligner/dr_search.py
b/src/main/python/systemds/scuro/aligner/dr_search.py
new file mode 100644
index 0000000000..4bdc7da4a2
--- /dev/null
+++ b/src/main/python/systemds/scuro/aligner/dr_search.py
@@ -0,0 +1,124 @@
+# -------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# -------------------------------------------------------------
+import itertools
+from typing import List
+
+from aligner.task import Task
+from modality.aligned_modality import AlignedModality
+from modality.modality import Modality
+from representations.representation import Representation
+
+
+def get_modalities_by_name(modalities, name):
+ for modality in modalities:
+ if modality.name == name:
+ return modality
+
+ raise 'Modality ' + name + 'not in modalities'
+
+
+class DRSearch:
+ def __init__(self, modalities: List[Modality], task: Task,
representations: List[Representation]):
+ """
+ The DRSearch primitive finds the best uni- or multimodal data
representation for the given modalities for
+ a specific task
+ :param modalities: List of uni-modal modalities
+ :param task: custom task
+ :param representations: List of representations to be evaluated
+ """
+ self.modalities = modalities
+ self.task = task
+ self.representations = representations
+ self.scores = {}
+ self.best_modalities = None
+ self.best_representation = None
+ self.best_score = -1
+
+ def set_best_params(self, modality_name: str, representation:
Representation,
+ score: float, modality_names: List[str]):
+ """
+ Updates the best parameters for given modalities, representation, and
score
+ :param modality_name: The name of the aligned modality
+ :param representation: The representation used to retrieve the current
score
+ :param score: achieved score for the set of modalities and
representation
+ :param modality_names: List of modality names used in this setting
+ :return:
+ """
+
+ # check if modality name is already in dictionary
+ if modality_name not in self.scores.keys():
+ # if not add it to dictionary
+ self.scores[modality_name] = {}
+
+ # set score for representation
+ self.scores[modality_name][representation] = score
+
+ # compare current score with best score
+ if score > self.best_score:
+ self.best_score = score
+ self.best_representation = representation
+ self.best_modalities = modality_names
+
+ def fit(self):
+ """
+ This method finds the best representation out of a given List of
uni-modal modalities and
+ representations
+ :return: The best parameters found in the search procedure
+ """
+
+ for M in range(1, len(self.modalities) + 1):
+ for combination in itertools.combinations(self.modalities, M):
+ if len(combination) == 1:
+ modality = combination[0]
+ score =
self.task.run(modality.representation.scale_data(modality.data,
self.task.train_indices))
+ self.set_best_params(modality.name,
modality.representation.name, score, [modality.name])
+ self.scores[modality] = score
+ else:
+ for representation in self.representations:
+ modality = AlignedModality(representation,
list(combination)) # noqa
+ modality.combine(self.task.train_indices)
+
+ score = self.task.run(modality.data)
+ self.set_best_params(modality.name, representation,
score, modality.get_modality_names())
+
+ return self.best_representation, self.best_score, self.best_modalities
+
+ def transform(self, modalities: List[Modality]):
+ """
+ The transform method takes a list of uni-modal modalities and creates
an aligned representation
+ by using the best parameters found during the fitting step
+ :param modalities: List of uni-modal modalities
+ :return: aligned data
+ """
+
+ if self.best_score == -1:
+ raise 'Please fit representations first!'
+
+ used_modalities = []
+
+ for modality_name in self.best_modalities:
+ used_modalities.append(get_modalities_by_name(modalities,
modality_name))
+
+ modality = AlignedModality(self.best_representation, used_modalities)
# noqa
+ modality.combine(self.task.train_indices)
+
+ return modality.data
+
\ No newline at end of file
diff --git a/src/main/python/systemds/scuro/aligner/task.py
b/src/main/python/systemds/scuro/aligner/task.py
new file mode 100644
index 0000000000..79f9690e65
--- /dev/null
+++ b/src/main/python/systemds/scuro/aligner/task.py
@@ -0,0 +1,58 @@
+# -------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# -------------------------------------------------------------
+from typing import List
+
+from models.model import Model
+
+
+class Task:
+ def __init__(self, name: str, model: Model, labels, train_indices: List,
val_indices: List):
+ """
+ Parent class for the prediction task that is performed on top of the
aligned representation
+ :param name: Name of the task
+ :param model: ML-Model to run
+ :param labels: Labels used for prediction
+ :param train_indices: Indices to extract training data
+ :param val_indices: Indices to extract validation data
+ """
+ self.name = name
+ self.model = model
+ self.labels = labels
+ self.train_indices = train_indices
+ self.val_indices = val_indices
+
+ def get_train_test_split(self, data):
+ X_train = [data[i] for i in self.train_indices]
+ y_train = [self.labels[i] for i in self.train_indices]
+ X_test = [data[i] for i in self.val_indices]
+ y_test = [self.labels[i] for i in self.val_indices]
+
+ return X_train, y_train, X_test, y_test
+
+ def run(self, data):
+ """
+ The run method need to be implemented by every task class
+ It handles the training and validation procedures for the specific task
+ :param data: The aligned data used in the prediction process
+ :return: the validation accuracy
+ """
+ pass
+
\ No newline at end of file
diff --git a/src/main/python/systemds/scuro/main.py
b/src/main/python/systemds/scuro/main.py
index 398db9921c..22477eb549 100644
--- a/src/main/python/systemds/scuro/main.py
+++ b/src/main/python/systemds/scuro/main.py
@@ -23,16 +23,31 @@ import json
from datetime import datetime
from representations.average import Averaging
+from representations.concatenation import Concatenation
from modality.aligned_modality import AlignedModality
from modality.text_modality import TextModality
from modality.video_modality import VideoModality
from modality.audio_modality import AudioModality
from representations.unimodal import Pickle, JSON, HDF5, NPY
from models.discrete_model import DiscreteModel
+from aligner.task import Task
+from aligner.dr_search import DRSearch
+
+
+class CustomTask(Task):
+ def __init__(self, model, labels, train_indices, val_indices):
+ super().__init__('CustomTask', model, labels, train_indices,
val_indices)
+
+ def run(self, data):
+ X_train, y_train, X_test, y_test = self.get_train_test_split(data)
+ self.model.fit(X_train, y_train, X_test, y_test)
+ score = self.model.test(X_test, y_test)
+ return score
labels = []
train_indices = []
+val_indices = []
video_path = ''
audio_path = ''
@@ -47,15 +62,12 @@ video.read_all()
audio.read_all()
text.read_all()
-combined_modality = AlignedModality(Averaging(), [text, video, audio])
-combined_modality.combine()
-
-# create train-val split
-train_X, train_y = None, None
-val_X, val_y = None, None
+modalities = [text, audio, video]
model = DiscreteModel()
-model.fit(train_X, train_y)
-model.test(val_X, val_y)
-
+custom_task = CustomTask(model, labels, train_indices, val_indices)
+representations = [Concatenation(), Averaging()]
+dr_search = DRSearch(modalities, custom_task, representations)
+best_representation, best_score, best_modalities = dr_search.fit()
+aligned_representation = dr_search.transform(modalities)
diff --git a/src/main/python/systemds/scuro/modality/video_modality.py
b/src/main/python/systemds/scuro/modality/video_modality.py
index ff7eebc9cc..8062c26a89 100644
--- a/src/main/python/systemds/scuro/modality/video_modality.py
+++ b/src/main/python/systemds/scuro/modality/video_modality.py
@@ -49,5 +49,5 @@ class VideoModality(Modality):
def read_chunk(self):
pass
- def read_all(self, indices):
+ def read_all(self, indices=None):
self.data = self.representation.parse_all(self.file_path,
indices=indices) # noqa