From 1c2f04ac751698d5db076d7dd572b585d81608a5 Mon Sep 17 00:00:00 2001 From: Anssi Moisio Date: Sat, 2 Sep 2023 10:47:25 +0300 Subject: [PATCH 1/6] Add europarl_dbca_splits task with subtasks --- .../tasks/europarl_dbca_splits/__init__.py | 5 + .../comdiv0_de/__init__.py | 0 .../comdiv0_de/config.jsonnet | 43 +++++++ .../europarl_dbca_splits/comdiv0_de/doc.md | 3 + .../europarl_dbca_splits/comdiv0_de/task.py | 116 ++++++++++++++++++ .../comdiv0_el/__init__.py | 0 .../comdiv0_el/config.jsonnet | 43 +++++++ .../europarl_dbca_splits/comdiv0_el/doc.md | 3 + .../europarl_dbca_splits/comdiv0_el/task.py | 116 ++++++++++++++++++ .../comdiv0_fi/__init__.py | 0 .../comdiv0_fi/config.jsonnet | 43 +++++++ .../europarl_dbca_splits/comdiv0_fi/doc.md | 3 + .../europarl_dbca_splits/comdiv0_fi/task.py | 116 ++++++++++++++++++ .../comdiv0_fr/__init__.py | 0 .../comdiv0_fr/config.jsonnet | 43 +++++++ .../europarl_dbca_splits/comdiv0_fr/doc.md | 3 + .../europarl_dbca_splits/comdiv0_fr/task.py | 116 ++++++++++++++++++ .../comdiv1_de/__init__.py | 0 .../comdiv1_de/config.jsonnet | 44 +++++++ .../europarl_dbca_splits/comdiv1_de/doc.md | 3 + .../europarl_dbca_splits/comdiv1_de/task.py | 116 ++++++++++++++++++ .../comdiv1_el/__init__.py | 0 .../comdiv1_el/config.jsonnet | 44 +++++++ .../europarl_dbca_splits/comdiv1_el/doc.md | 3 + .../europarl_dbca_splits/comdiv1_el/task.py | 116 ++++++++++++++++++ .../comdiv1_fi/__init__.py | 0 .../comdiv1_fi/config.jsonnet | 43 +++++++ .../europarl_dbca_splits/comdiv1_fi/doc.md | 3 + .../europarl_dbca_splits/comdiv1_fi/task.py | 116 ++++++++++++++++++ .../comdiv1_fr/__init__.py | 0 .../comdiv1_fr/config.jsonnet | 43 +++++++ .../europarl_dbca_splits/comdiv1_fr/doc.md | 19 +++ .../europarl_dbca_splits/comdiv1_fr/task.py | 116 ++++++++++++++++++ .../tasks/europarl_dbca_splits/config.jsonnet | 28 +++++ .../tasks/europarl_dbca_splits/doc.md | 62 ++++++++++ .../tasks/europarl_dbca_splits/eval_card.png | Bin 0 -> 163572 bytes 36 files changed, 1409 insertions(+) create mode 100644 src/genbench/tasks/europarl_dbca_splits/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_de/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_de/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_de/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_el/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_el/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_el/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_de/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_de/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_de/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_el/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_el/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_el/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/__init__.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py create mode 100644 src/genbench/tasks/europarl_dbca_splits/config.jsonnet create mode 100644 src/genbench/tasks/europarl_dbca_splits/doc.md create mode 100644 src/genbench/tasks/europarl_dbca_splits/eval_card.png diff --git a/src/genbench/tasks/europarl_dbca_splits/__init__.py b/src/genbench/tasks/europarl_dbca_splits/__init__.py new file mode 100644 index 0000000..eecdf60 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/__init__.py @@ -0,0 +1,5 @@ +from genbench import TaskDict + + +class EuroparlDbcaSplits(TaskDict): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/config.jsonnet new file mode 100644 index 0000000..4c9d9bd --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/config.jsonnet @@ -0,0 +1,43 @@ +{ + name: 'Europarl DBCA splits (comdiv0_de)', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv0.0_en_de'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/doc.md new file mode 100644 index 0000000..c6e1e28 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv0_de) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py new file mode 100644 index 0000000..ed51ebb --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv0De(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/config.jsonnet new file mode 100644 index 0000000..c8b975b --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/config.jsonnet @@ -0,0 +1,43 @@ +{ + name: 'Europarl DBCA splits (comdiv0_el)', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv0.0_en_el'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/doc.md new file mode 100644 index 0000000..f880163 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv0_el) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py new file mode 100644 index 0000000..1197055 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv0El(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/config.jsonnet new file mode 100644 index 0000000..e97f2bd --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/config.jsonnet @@ -0,0 +1,43 @@ +{ + name: 'Europarl DBCA splits (comdiv0_fi)', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv0.0_en_fi'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/doc.md new file mode 100644 index 0000000..31a0e0d --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv0_fi) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py new file mode 100644 index 0000000..7e82a88 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv0Fi(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/config.jsonnet new file mode 100644 index 0000000..0cf8db9 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/config.jsonnet @@ -0,0 +1,43 @@ +{ + name: 'Europarl DBCA splits (comdiv0_fr)', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv0.0_en_fr'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/doc.md new file mode 100644 index 0000000..79e7f71 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv0_fr) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py new file mode 100644 index 0000000..bfff4f1 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv0Fr(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/config.jsonnet new file mode 100644 index 0000000..837e681 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/config.jsonnet @@ -0,0 +1,44 @@ +{ + name: 'Europarl DBCA splits (comdiv1_de)', + + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv1.0_en_de'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/doc.md new file mode 100644 index 0000000..58415ce --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv1_de) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py new file mode 100644 index 0000000..b89d8aa --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv1De(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/config.jsonnet new file mode 100644 index 0000000..f6be560 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/config.jsonnet @@ -0,0 +1,44 @@ +{ + name: 'Europarl DBCA splits (comdiv1_el)', + + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv1.0_en_el'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/doc.md new file mode 100644 index 0000000..90b6a6b --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv1_el) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py new file mode 100644 index 0000000..1db49a0 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv1El(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/config.jsonnet new file mode 100644 index 0000000..76976df --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/config.jsonnet @@ -0,0 +1,43 @@ +{ + name: 'Europarl DBCA splits (comdiv1_fi)', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv1.0_en_fi'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/doc.md new file mode 100644 index 0000000..0c5f258 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/doc.md @@ -0,0 +1,3 @@ +# Europarl DBCA splits (comdiv1_fi) + +see ../doc.md diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py new file mode 100644 index 0000000..3e7b7f0 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv1Fi(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/__init__.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/config.jsonnet new file mode 100644 index 0000000..6d095f4 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/config.jsonnet @@ -0,0 +1,43 @@ +{ + name: 'Europarl DBCA splits (comdiv1_fr)', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + 'dependency relations', + ], + + authors: [ + 'Anssi Moisio', + ], + + task_type: 'free_form', + + data_source: { + type: 'hf', + hf_id: ['Anssi/europarl_dbca_splits', 'comdiv1.0_en_fr'], + git_commit_sha: '0dcb7abe8e18aa520cbfcbe9141b916c684912fc' + }, + + evaluation_metrics: [ + { + hf_id: 'chrf', + git_commit_sha: '4b119256e85de9130aa84d87247381c5acb29bc1', + best_score: 100.0, + } + ], + + has_validation_set: false, + has_train_set: true, + + preparation_strategies: { + finetuning: { + objective: 'maximum_likelihood', + }, + }, +} \ No newline at end of file diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md new file mode 100644 index 0000000..50a2694 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md @@ -0,0 +1,19 @@ +# Europarl DBCA splits (comdiv1_fr) + +## Abstract +*Copy the abstract of your accompanying paper for this task here Europarl DBCA splits (comdiv1_fr).* + +## Examples +*Give some examples of the Europarl DBCA splits (comdiv1_fr).* + +## Usage +*Describe how to load your task and what is required for evaluation, if anything.* + +## Data Source +*Describe the data source for this Europarl DBCA splits (comdiv1_fr).* + +## Limitations and Bias +*Note any known limitations or biases that the Europarl DBCA splits (comdiv1_fr) has, with links and references if possible.* + +## GenBench Eval card +*Describe what kind of generalisation your task is evaluating, and include a [genbench eval card](https://genbench.org/eval_cards/) for your task*. diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py new file mode 100644 index 0000000..9358e5f --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class EuroparlDbcaSplitsComdiv1Fr(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/config.jsonnet b/src/genbench/tasks/europarl_dbca_splits/config.jsonnet new file mode 100644 index 0000000..9b01c57 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/config.jsonnet @@ -0,0 +1,28 @@ +{ + name: 'Divergent DepRel Distributions', + + description: 'This task aims to measure how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms).', + + keywords: [ + 'translation', + ], + + authors: [ + 'Anssi Moisio', + ], + + subtasks_order: [ + 'comdiv0_de', + 'comdiv1_de', + 'comdiv0_fr', + 'comdiv1_fr', + 'comdiv0_el', + 'comdiv1_el', + 'comdiv0_fi', + 'comdiv1_fi', + ], +} diff --git a/src/genbench/tasks/europarl_dbca_splits/doc.md b/src/genbench/tasks/europarl_dbca_splits/doc.md new file mode 100644 index 0000000..879242a --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/doc.md @@ -0,0 +1,62 @@ +# Train-test data splits of the Europarl NMT corpus with divergent distributions of dependency relations +## Abstract +Compositional generalisation (CG), in NLP and in machine learning generally, has been assessed mostly using artificial datasets. It is important to develop benchmarks to assess CG also in real-world natural language tasks in order to understand the abilities and limitations of systems that are deployed in the wild. In our GenBench Collaborative Benchmarking Task submission, we utilise the distribution-based compositionality assessment (DBCA) framework to split the Europarl translation corpus into a training and test set in a way that translating the test set requires compositional generalisation capacity. Specifically, the training and test sets have divergent distributions of dependency relations, testing the NMT system's capacity to translate dependencies that they have not been trained on. + + +## Examples +The task is simply sentence-level translation, e.g.: +``` +"input": "If the House agrees, I shall do as Mr Evans has suggested.", "target": "Jos parlamentin jäsenet kannattavat sitä, teen niin kuin jäsen Evans ehdotti." +``` + + +## Usage +To use the provided maximum-compound-divergence train-test split for a target language (German=de, French=fr, Greek=el, Finnish=fi), load the data, train a model on the training subset, and evaluate the model's predictions on the test subset +``` +from genbench import load_task +from genbench.api import PreparationStrategy + +# Load the task +task = load_task("europarl_dbca_splits") +ds = task.comdiv1_de.get_prepared_datasets(PreparationStrategy.FINETUNING) + +# Evaluate predictions +preds = ... +print(task.comdiv1_de.evaluate_predictions( + predictions=preds, + gold=ds['test'], + ) + ) +``` +To compare a model's capacity to generalise, we assess how much the translation accuracy decreases when the compound divergence between train and test sets increases. We keep atom distributions the same between train and test sets to make generalisation possible in principle. This means we should evaluate each model on both low- and high-compound-divergence data splits. To compute the generalisation score as described in the accompanying paper, train two systems on the splits with compound divergence values 0 and 1 (e.g. subtasks "comdiv0_de" and "comdiv1_de"), and take the ratio of the chrF2++ scores. + +#### Using your other data sets: +To compute the atom and compound divergences for any pair of training (pre-training, training and/or fine-tuning) and test data sets, use method `EuroparlDbcaSplitsComdiv0De.divergence`. To create the atom and compound distributions of the training and test sets, the frequencies of each atom and compound in each set need to be first counted. The vectors that represent the atom and compound distributions of the train/test sets are inputted to the method to calculate the divergences: +``` +import numpy as np +# alpha is 0.5 for atom divergence and 0.1 for compound divergence +train_set_atom_distribution = np.array([[2,4,10]]) +test_set_atom_distribution = np.array([[1,2,5]]) +atom_divergence = task.comdiv1_de.divergence(train_set_atom_distribution, + test_set_atom_distribution, + 0.5) +# atom_divergence = 0.0 + +train_set_compound_distribution = np.array([[2,0,6]]) +test_set_compound_distribution = np.array([[0,5,5]]) +compound_divergence = task.comdiv1_de.divergence(train_set_compound_distribution, + test_set_compound_distribution, + 0.1) +# compound_divergence = 0.4793101280037947 +``` +Each element in the distribution vectors represents the frequency of one type of atom/compound. + + +## Data Source +The original data source is `https://opus.nlpl.eu/Europarl.php` + +## Limitations and Bias +Our goal was to create a benchmark that tests generalisation to novel dependency relations in a comprehensive way, not selecting some specific types of dependency relations and leaving out other types. However, memory requirements of the data splitting algorithm did not permit us to use all of the atoms and compounds in the distribution divergence calculations, so we opted to leave out the most frequent and the most infrequent lemmas, and the dependency relations that include them, which probably affects the results. + +## GenBench Eval card +![GenBench Eval Card](eval_card.png) diff --git a/src/genbench/tasks/europarl_dbca_splits/eval_card.png b/src/genbench/tasks/europarl_dbca_splits/eval_card.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7cd9588f976583c740e9d07ef98d1a9f3590c5 GIT binary patch literal 163572 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

;+I6}qD(?OW>sdikI{6jmhsR6 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�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 Date: Sat, 2 Sep 2023 11:40:12 +0300 Subject: [PATCH 2/6] Update doc.md --- src/genbench/tasks/europarl_dbca_splits/doc.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/genbench/tasks/europarl_dbca_splits/doc.md b/src/genbench/tasks/europarl_dbca_splits/doc.md index 879242a..a32d0bf 100644 --- a/src/genbench/tasks/europarl_dbca_splits/doc.md +++ b/src/genbench/tasks/europarl_dbca_splits/doc.md @@ -28,7 +28,7 @@ print(task.comdiv1_de.evaluate_predictions( ) ) ``` -To compare a model's capacity to generalise, we assess how much the translation accuracy decreases when the compound divergence between train and test sets increases. We keep atom distributions the same between train and test sets to make generalisation possible in principle. This means we should evaluate each model on both low- and high-compound-divergence data splits. To compute the generalisation score as described in the accompanying paper, train two systems on the splits with compound divergence values 0 and 1 (e.g. subtasks "comdiv0_de" and "comdiv1_de"), and take the ratio of the chrF2++ scores. +To compare a model's capacity to generalise, we assess how much the translation accuracy decreases when the compound divergence between train and test sets increases. We keep atom distributions the same between train and test sets to make generalisation possible in principle. This means we should evaluate each model on both low- and high-compound-divergence data splits. To compute the generalisation score as described in the accompanying paper, train two systems on the splits with compound divergence values 0 and 1 (e.g. subtasks "comdiv0_de" and "comdiv1_de"), and take the ratio of the chrF2++ scores: `task.comdiv1_de.evaluate_predictions(predictions_comdiv1_de, gold_comdiv1_de) / task.comdiv0_de.evaluate_predictions(predictions_comdiv0_de, gold_comdiv0_de)` #### Using your other data sets: To compute the atom and compound divergences for any pair of training (pre-training, training and/or fine-tuning) and test data sets, use method `EuroparlDbcaSplitsComdiv0De.divergence`. To create the atom and compound distributions of the training and test sets, the frequencies of each atom and compound in each set need to be first counted. The vectors that represent the atom and compound distributions of the train/test sets are inputted to the method to calculate the divergences: @@ -59,4 +59,6 @@ The original data source is `https://opus.nlpl.eu/Europarl.php` Our goal was to create a benchmark that tests generalisation to novel dependency relations in a comprehensive way, not selecting some specific types of dependency relations and leaving out other types. However, memory requirements of the data splitting algorithm did not permit us to use all of the atoms and compounds in the distribution divergence calculations, so we opted to leave out the most frequent and the most infrequent lemmas, and the dependency relations that include them, which probably affects the results. ## GenBench Eval card +The motivation is primarily intrinsic: it is important to assess if translation models learn the systematic rules that characterise natural language, in order to get some understanding how the models work. Another motivation is practical; compositional generalisation is important for the practical reason that it would make the models robust. The type of the generalisation is compositional, and the shift type is covariate, since the input data distribution changes but the task remains otherwise the same. Shift source is partitioned natural data, since we do not use any artificial data, but the train-test split is artificial. Lastly, the shift locus in our experiments is train-test, but the method and benchmark could also possibly be used as a finetune train-test benchmark, by finetuning a pretrained model on the training set. + ![GenBench Eval Card](eval_card.png) From 42e2ca38dd9728854af8789267d791cbae86665a Mon Sep 17 00:00:00 2001 From: Anssi Moisio Date: Sat, 2 Sep 2023 15:08:01 +0300 Subject: [PATCH 3/6] updated europarl_dbca_splits/comdiv1_fr/doc.md --- .../europarl_dbca_splits/comdiv1_fr/doc.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md index 50a2694..eda471f 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/doc.md @@ -1,19 +1,3 @@ # Europarl DBCA splits (comdiv1_fr) -## Abstract -*Copy the abstract of your accompanying paper for this task here Europarl DBCA splits (comdiv1_fr).* - -## Examples -*Give some examples of the Europarl DBCA splits (comdiv1_fr).* - -## Usage -*Describe how to load your task and what is required for evaluation, if anything.* - -## Data Source -*Describe the data source for this Europarl DBCA splits (comdiv1_fr).* - -## Limitations and Bias -*Note any known limitations or biases that the Europarl DBCA splits (comdiv1_fr) has, with links and references if possible.* - -## GenBench Eval card -*Describe what kind of generalisation your task is evaluating, and include a [genbench eval card](https://genbench.org/eval_cards/) for your task*. +see ../doc.md From a6b6019f94dac6e7f46cb33a11cf566bcf2baa48 Mon Sep 17 00:00:00 2001 From: Anssi Moisio Date: Mon, 20 Nov 2023 07:53:42 +0200 Subject: [PATCH 4/6] Add usage_example.py and requirements-usage-example.txt for europarl_dbca_splits task --- .../requirements-usage-example.txt | 1 + .../europarl_dbca_splits/usage_example.py | 188 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/genbench/tasks/europarl_dbca_splits/requirements-usage-example.txt create mode 100644 src/genbench/tasks/europarl_dbca_splits/usage_example.py diff --git a/src/genbench/tasks/europarl_dbca_splits/requirements-usage-example.txt b/src/genbench/tasks/europarl_dbca_splits/requirements-usage-example.txt new file mode 100644 index 0000000..765824a --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/requirements-usage-example.txt @@ -0,0 +1 @@ +transformers==4.35.2 diff --git a/src/genbench/tasks/europarl_dbca_splits/usage_example.py b/src/genbench/tasks/europarl_dbca_splits/usage_example.py new file mode 100644 index 0000000..efd52f9 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/usage_example.py @@ -0,0 +1,188 @@ +""" +Usage example for the Europarl DBCA splits task. + +Training of the NMT model is mostly based on the HuggingFace NLP course chapter on translation: +https://huggingface.co/learn/nlp-course/chapter7/4?fw=pt +""" +import argparse +from genbench import load_task +from genbench.api import PreparationStrategy +from datasets import DatasetDict +from transformers import FSMTConfig, FSMTTokenizer, FSMTForConditionalGeneration, pipeline +from transformers import DataCollatorForSeq2Seq, Seq2SeqTrainingArguments, Seq2SeqTrainer + + +def tokenize_corpus(dataset, save_to_file): + """ + Tokenizes the dataset and saves it to disk. + """ + def preprocess_function(examples): + inputs = examples["input"] + targets = examples["target"] + model_inputs = tokenizer( + inputs, text_target=targets, max_length=MAX_LENGTH, truncation=True + ) + return model_inputs + + dataset = DatasetDict(dataset) + tokenized = dataset.map( + preprocess_function, + batched=True, + ) + tokenized.save_to_disk(save_to_file) + return tokenized + + +def translate_sentences(model_name_or_path, eval_dataset): + """ + Translates the sentences in eval_dataset using the given model. + """ + translator = pipeline( + "translation", + model=model_name_or_path, + device="cuda", + batch_size=BATCH_SIZE, + ) + return translator(eval_dataset, max_length=MAX_LENGTH) + + +def train_from_scratch(tokenized_corpus, output_dir_name): + """ + Trains an FSMT model from scratch. + Model architecture is similar to that in Vaswani et al. (2017). + """ + config = FSMTConfig( + activation_dropout=0.0, + activation_function="relu", + architectures=["FSMTForConditionalGeneration"], + attention_dropout=0.1, + bos_token_id=0, + d_model=512, + decoder={ + "bos_token_id": 2, + "model_type": "fsmt_decoder", + "vocab_size": 42024 + }, + decoder_attention_heads=8, + decoder_ffn_dim=2048, + decoder_layerdrop=0, + decoder_layers=6, + decoder_start_token_id=2, + dropout=0.1, + encoder_attention_heads=8, + encoder_ffn_dim=2048, + encoder_layerdrop=0, + encoder_layers=6, + eos_token_id=2, + forced_eos_token_id=2, + init_std=0.02, + is_encoder_decoder=True, + langs=["en", "de"], + length_penalty=1.15, + max_length=MAX_LENGTH, + max_position_embeddings=1024, + model_type="fsmt", + num_beams=5, + num_hidden_layers=6, + pad_token_id=1, + scale_embedding=True, + src_vocab_size=42024, + tgt_vocab_size=42024, + tie_word_embeddings=False, + transformers_version="4.35.2", + use_cache=True, + ) + model = FSMTForConditionalGeneration(config=config) + + data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model) + + training_args = Seq2SeqTrainingArguments( + output_dir=output_dir_name, + evaluation_strategy="steps", + eval_steps=5000, + save_strategy="steps", + save_steps=10000, + learning_rate=2e-5, + per_device_train_batch_size=BATCH_SIZE, + per_device_eval_batch_size=BATCH_SIZE, + weight_decay=0.01, + save_total_limit=10, + max_steps=100000, + fp16=True, + ) + + trainer = Seq2SeqTrainer( + model, + training_args, + train_dataset=tokenized_corpus["train"], + eval_dataset=tokenized_corpus["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + ) + trainer.train() + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser() + argparser.add_argument("--tokenize", action="store_true") + argparser.add_argument("--train", action="store_true") + argparser.add_argument("--eval", action="store_true") + args = argparser.parse_args() + + # Load the task + task = load_task('europarl_dbca_splits') + + # A pretrained multilingual tokenizer, used for both models and both languages + tokenizer = FSMTTokenizer.from_pretrained('stas/tiny-wmt19-en-de') + + MAX_LENGTH = 128 + BATCH_SIZE = 128 + + results = [] + # "comdiv0" is the easy non-compositional data split, with minimal compound divergence + # "comdiv1" is the difficult, compositional data split, with maximal compound divergence + # English-German corpus is used for this example. + # For other target languages, replace "de" with "fr", "el", or "fi" in the subtask name. + for comdiv in ["0", "1"]: + if comdiv == "0": + subtask = task.comdiv0_de + else: + subtask = task.comdiv1_de + + subtask_dataset = subtask.get_prepared_datasets(PreparationStrategy.FINETUNING) + + tokenized_dataset_dir = f'ds_de_comdiv{comdiv}_tokenized' + if args.tokenize: + tokenized_datasets = tokenize_corpus(subtask_dataset, tokenized_dataset_dir) + else: + tokenized_datasets = DatasetDict.load_from_disk(tokenized_dataset_dir) + + # Extract a validation set from training set + train_val_split = tokenized_datasets["train"].train_test_split(test_size=0.01) + tokenized_datasets["train"] = train_val_split["train"] + tokenized_datasets["validation"] = train_val_split["test"] + + nmt_model_dir = f'FSMT_en-de_comdiv{comdiv}' + if args.train: + train_from_scratch(tokenized_datasets, nmt_model_dir) + + if args.eval: + cp = 'checkpoint-100000' + print(f"Results for comdiv{comdiv}, checkpoint {cp}") + preds = translate_sentences(nmt_model_dir + '/' + cp, + tokenized_datasets["test"]["input"]) + + # re-map the keys to match the evaluation script + preds = [{'target': pred['translation_text']} for pred in preds] + + score = subtask.evaluate_predictions( + predictions=preds, + gold=tokenized_datasets["test"], + ) + print(score) + results.append(score) + + if args.eval: + print('Generalisation score (maximum compound divergence score divided by ' \ + + 'minimum compound divergence score):') + print(results[1]['hf_chrf__score'] / results[0]['hf_chrf__score']) From 94f8119b6a2098aca61a6b969b5c89073822e105 Mon Sep 17 00:00:00 2001 From: Anssi Moisio Date: Mon, 20 Nov 2023 07:56:09 +0200 Subject: [PATCH 5/6] Fix style --- .../europarl_dbca_splits/usage_example.py | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/genbench/tasks/europarl_dbca_splits/usage_example.py b/src/genbench/tasks/europarl_dbca_splits/usage_example.py index efd52f9..c3c9b12 100644 --- a/src/genbench/tasks/europarl_dbca_splits/usage_example.py +++ b/src/genbench/tasks/europarl_dbca_splits/usage_example.py @@ -5,23 +5,31 @@ https://huggingface.co/learn/nlp-course/chapter7/4?fw=pt """ import argparse + +from datasets import DatasetDict +from transformers import ( + DataCollatorForSeq2Seq, + FSMTConfig, + FSMTForConditionalGeneration, + FSMTTokenizer, + Seq2SeqTrainer, + Seq2SeqTrainingArguments, + pipeline, +) + from genbench import load_task from genbench.api import PreparationStrategy -from datasets import DatasetDict -from transformers import FSMTConfig, FSMTTokenizer, FSMTForConditionalGeneration, pipeline -from transformers import DataCollatorForSeq2Seq, Seq2SeqTrainingArguments, Seq2SeqTrainer def tokenize_corpus(dataset, save_to_file): """ Tokenizes the dataset and saves it to disk. """ + def preprocess_function(examples): inputs = examples["input"] targets = examples["target"] - model_inputs = tokenizer( - inputs, text_target=targets, max_length=MAX_LENGTH, truncation=True - ) + model_inputs = tokenizer(inputs, text_target=targets, max_length=MAX_LENGTH, truncation=True) return model_inputs dataset = DatasetDict(dataset) @@ -58,11 +66,7 @@ def train_from_scratch(tokenized_corpus, output_dir_name): attention_dropout=0.1, bos_token_id=0, d_model=512, - decoder={ - "bos_token_id": 2, - "model_type": "fsmt_decoder", - "vocab_size": 42024 - }, + decoder={"bos_token_id": 2, "model_type": "fsmt_decoder", "vocab_size": 42024}, decoder_attention_heads=8, decoder_ffn_dim=2048, decoder_layerdrop=0, @@ -130,10 +134,10 @@ def train_from_scratch(tokenized_corpus, output_dir_name): args = argparser.parse_args() # Load the task - task = load_task('europarl_dbca_splits') + task = load_task("europarl_dbca_splits") # A pretrained multilingual tokenizer, used for both models and both languages - tokenizer = FSMTTokenizer.from_pretrained('stas/tiny-wmt19-en-de') + tokenizer = FSMTTokenizer.from_pretrained("stas/tiny-wmt19-en-de") MAX_LENGTH = 128 BATCH_SIZE = 128 @@ -151,7 +155,7 @@ def train_from_scratch(tokenized_corpus, output_dir_name): subtask_dataset = subtask.get_prepared_datasets(PreparationStrategy.FINETUNING) - tokenized_dataset_dir = f'ds_de_comdiv{comdiv}_tokenized' + tokenized_dataset_dir = f"ds_de_comdiv{comdiv}_tokenized" if args.tokenize: tokenized_datasets = tokenize_corpus(subtask_dataset, tokenized_dataset_dir) else: @@ -162,27 +166,28 @@ def train_from_scratch(tokenized_corpus, output_dir_name): tokenized_datasets["train"] = train_val_split["train"] tokenized_datasets["validation"] = train_val_split["test"] - nmt_model_dir = f'FSMT_en-de_comdiv{comdiv}' + nmt_model_dir = f"FSMT_en-de_comdiv{comdiv}" if args.train: train_from_scratch(tokenized_datasets, nmt_model_dir) if args.eval: - cp = 'checkpoint-100000' + cp = "checkpoint-100000" print(f"Results for comdiv{comdiv}, checkpoint {cp}") - preds = translate_sentences(nmt_model_dir + '/' + cp, - tokenized_datasets["test"]["input"]) + preds = translate_sentences(nmt_model_dir + "/" + cp, tokenized_datasets["test"]["input"]) # re-map the keys to match the evaluation script - preds = [{'target': pred['translation_text']} for pred in preds] + preds = [{"target": pred["translation_text"]} for pred in preds] score = subtask.evaluate_predictions( - predictions=preds, - gold=tokenized_datasets["test"], - ) + predictions=preds, + gold=tokenized_datasets["test"], + ) print(score) results.append(score) if args.eval: - print('Generalisation score (maximum compound divergence score divided by ' \ - + 'minimum compound divergence score):') - print(results[1]['hf_chrf__score'] / results[0]['hf_chrf__score']) + print( + "Generalisation score (maximum compound divergence score divided by " + + "minimum compound divergence score):" + ) + print(results[1]["hf_chrf__score"] / results[0]["hf_chrf__score"]) From ee8608976e994b3a9ca24807c730a1dd4fd61bcf Mon Sep 17 00:00:00 2001 From: Anssi Moisio Date: Fri, 15 Dec 2023 15:04:52 +0200 Subject: [PATCH 6/6] Remove repetitive Task classes from subtasks, inherit from _base_task.py instead. --- .../tasks/europarl_dbca_splits/_base_task.py | 116 +++++++++++++++++ .../europarl_dbca_splits/comdiv0_de/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv0_el/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv0_fi/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv0_fr/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv1_de/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv1_el/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv1_fi/task.py | 117 +----------------- .../europarl_dbca_splits/comdiv1_fr/task.py | 117 +----------------- 9 files changed, 140 insertions(+), 912 deletions(-) create mode 100644 src/genbench/tasks/europarl_dbca_splits/_base_task.py diff --git a/src/genbench/tasks/europarl_dbca_splits/_base_task.py b/src/genbench/tasks/europarl_dbca_splits/_base_task.py new file mode 100644 index 0000000..3e4be76 --- /dev/null +++ b/src/genbench/tasks/europarl_dbca_splits/_base_task.py @@ -0,0 +1,116 @@ +from collections import OrderedDict +from typing import Any, List, Mapping + +import evaluate +import numpy as np +from datasets import Dataset + +from genbench import Task +from genbench.api import EvaluationResult, TaskType +from genbench.utils.logging import get_logger + + +logger = get_logger(__name__) + + +class BaseDbcaTask(Task): + """This task evaluates how well an NMT model generalises to a shifted distribution of + dependency relations. In practice, this means that the test set includes novel + (, , ) tuples (=compounds) that were not seen in + the training set, while having similar relative frequencies of the lemmas and dependency + relation tags (= elements of the compound tuples = atoms). + """ + + def evaluate_predictions( + self, + *, + predictions: List[Mapping[str, Any]] = None, + gold: Dataset = None, + ) -> EvaluationResult: + result = OrderedDict() + for metric_config in self.config.evaluation_metrics: + hf_id = metric_config.hf_id + if isinstance(hf_id, str): + hf_id = [hf_id] + + metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) + + refs_lst = [g["target"] for g in gold] + preds_lst = [pred["target"] for pred in predictions] + + ref_type = type(refs_lst[0]) + pred_type = type(preds_lst[0]) + if pred_type != ref_type: + if self.config.task_type != TaskType.MULTIPLE_CHOICE: + raise ValueError( + f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " + ) + # Convert predictions to the same type as the references + if pred_type == str and ref_type == int: + logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") + converted_preds = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + preds_lst = converted_preds + elif pred_type == int and ref_type == str: + logger.warning("Predictions are ints, but references are strings. Converting references to ints.") + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_refs.append(ref["target_options"].index(ref["target"])) + refs_lst = converted_refs + else: + if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: + # Convert both predictions and references to int + logger.warning( + "Predictions and references have the same type, but it is not int. Converting both to int." + ) + converted_preds = [] + converted_refs = [] + for pred, ref in zip(preds_lst, gold): + assert "target_options" in ref + converted_preds.append(ref["target_options"].index(pred)) + converted_refs.append(ref["target_options"].index(ref["target"])) + preds_lst = converted_preds + refs_lst = converted_refs + + extra_kwargs = metric_config.compute_extra_kwargs or {} + output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) + + if output is None: + raise ValueError( + f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." + ) + + # Update output keys to include the metric id + metric_id = "_".join(hf_id) + output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} + + result.update(output) + + return result + + def chernoff_coef(self, vec1, vec2, alpha): + """ + The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) + = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) + distributions P and Q. The alpha parameter determines if we want to + measure whether Q includes elements that are not in P. + """ + if alpha < 0 or alpha > 1: + raise ValueError("alpha must be in [0,1]") + # use log to avoid underflow + return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) + + def normalize_vector(self, vector): + """Normalize a vector to have sum 1.""" + return np.nan_to_num(np.divide(vector, np.sum(vector))) + + def divergence(self, vec1, vec2, alpha): + """ + Calculate divergence between two vectors. + Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. + Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. + """ + return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py index ed51ebb..898b036 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_de/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv0De(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv0De(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py index 1197055..1124f49 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_el/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv0El(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv0El(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py index 7e82a88..7bf9f32 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fi/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv0Fi(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv0Fi(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py index bfff4f1..943fe65 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv0_fr/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv0Fr(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv0Fr(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py index b89d8aa..3b9ec0a 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_de/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv1De(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv1De(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py index 1db49a0..7fcf724 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_el/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv1El(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv1El(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py index 3e7b7f0..8fc677b 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fi/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv1Fi(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv1Fi(BaseDbcaTask): + pass diff --git a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py index 9358e5f..8e27ac1 100644 --- a/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py +++ b/src/genbench/tasks/europarl_dbca_splits/comdiv1_fr/task.py @@ -1,116 +1,5 @@ -from collections import OrderedDict -from typing import Any, List, Mapping +from genbench.tasks.europarl_dbca_splits._base_task import BaseDbcaTask -import evaluate -import numpy as np -from datasets import Dataset -from genbench import Task -from genbench.api import EvaluationResult, TaskType -from genbench.utils.logging import get_logger - - -logger = get_logger(__name__) - - -class EuroparlDbcaSplitsComdiv1Fr(Task): - """This task evaluates how well an NMT model generalises to a shifted distribution of - dependency relations. In practice, this means that the test set includes novel - (, , ) tuples (=compounds) that were not seen in - the training set, while having similar relative frequencies of the lemmas and dependency - relation tags (= elements of the compound tuples = atoms). - """ - - def evaluate_predictions( - self, - *, - predictions: List[Mapping[str, Any]] = None, - gold: Dataset = None, - ) -> EvaluationResult: - result = OrderedDict() - for metric_config in self.config.evaluation_metrics: - hf_id = metric_config.hf_id - if isinstance(hf_id, str): - hf_id = [hf_id] - - metric = evaluate.load(*hf_id, revision=metric_config.git_commit_sha) - - refs_lst = [g["target"] for g in gold] - preds_lst = [pred["target"] for pred in predictions] - - ref_type = type(refs_lst[0]) - pred_type = type(preds_lst[0]) - if pred_type != ref_type: - if self.config.task_type != TaskType.MULTIPLE_CHOICE: - raise ValueError( - f"Predictions and references have different types: preds: {pred_type} and refs: {ref_type}. " - ) - # Convert predictions to the same type as the references - if pred_type == str and ref_type == int: - logger.warning("Predictions are strings, but references are ints. Converting predictions to ints.") - converted_preds = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - preds_lst = converted_preds - elif pred_type == int and ref_type == str: - logger.warning("Predictions are ints, but references are strings. Converting references to ints.") - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_refs.append(ref["target_options"].index(ref["target"])) - refs_lst = converted_refs - else: - if self.config.task_type == TaskType.MULTIPLE_CHOICE and pred_type != int: - # Convert both predictions and references to int - logger.warning( - "Predictions and references have the same type, but it is not int. Converting both to int." - ) - converted_preds = [] - converted_refs = [] - for pred, ref in zip(preds_lst, gold): - assert "target_options" in ref - converted_preds.append(ref["target_options"].index(pred)) - converted_refs.append(ref["target_options"].index(ref["target"])) - preds_lst = converted_preds - refs_lst = converted_refs - - extra_kwargs = metric_config.compute_extra_kwargs or {} - output: dict = metric.compute(predictions=preds_lst, references=refs_lst, **extra_kwargs) - - if output is None: - raise ValueError( - f"Metric {metric_config.hf_id} returned None. " f"Please check the metric implementation." - ) - - # Update output keys to include the metric id - metric_id = "_".join(hf_id) - output = {f"hf_{metric_id}__{k}": v for k, v in output.items() if k == "score"} - - result.update(output) - - return result - - def chernoff_coef(self, vec1, vec2, alpha): - """ - The Chernoff coefficient c is a similarity measure C_{alpha}(P||Q) - = sum_k[p_k^alpha * q_k^(1-alpha)] e[0,1] between two (probability) - distributions P and Q. The alpha parameter determines if we want to - measure whether Q includes elements that are not in P. - """ - if alpha < 0 or alpha > 1: - raise ValueError("alpha must be in [0,1]") - # use log to avoid underflow - return np.sum(np.exp((np.log(vec1) * alpha) + (np.log(vec2) * (1 - alpha))), axis=1) - - def normalize_vector(self, vector): - """Normalize a vector to have sum 1.""" - return np.nan_to_num(np.divide(vector, np.sum(vector))) - - def divergence(self, vec1, vec2, alpha): - """ - Calculate divergence between two vectors. - Atom divergence is 1 - Chernoff coefficient, with alpha=0.5. - Compound divergence is 1 - Chernoff coefficient, with alpha=0.1. - """ - return float(1 - self.chernoff_coef(self.normalize_vector(vec1), self.normalize_vector(vec2), alpha)) +class EuroparlDbcaSplitsComdiv1Fr(BaseDbcaTask): + pass