epsilon-compositionality

Code for our paper Measuring Idiomaticity in Text Embedding Models with 𝜀-compositionality
git clone https://esimon.eu/repos/epsilon-compositionality.git
Log | Files | Refs | Submodules | README

commit 63a153636d64e89c73b03ed3919275b5af51913b
parent 686525fcec3aacf31d60ec9a1c312d14cf3a7bbd
Author: Sondre Wold <sondrewold1@gmail.com>
Date:   Sat, 12 Jul 2025 20:13:59 +0200

linting

Diffstat:
Mexperiment.py | 111++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
1 file changed, 65 insertions(+), 46 deletions(-)

diff --git a/experiment.py b/experiment.py @@ -1,7 +1,6 @@ from __future__ import annotations -from typing import Callable, NamedTuple, Any +from typing import NamedTuple, Any -import sys import pandas import pathlib import argparse @@ -16,9 +15,7 @@ from sentence_transformers import SentenceTransformer def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Experiment" - ) + parser = argparse.ArgumentParser(description="Experiment") parser.add_argument("-data_path", type=pathlib.Path) parser.add_argument("-result_path", type=pathlib.Path) parser.add_argument("-normalization_value", type=float) @@ -46,7 +43,12 @@ class Model(ABC): def embed(self, string: str) -> torch.Tensor: pass - def normalize(self, composed: torch.Tensor, original_phrase_removed: torch.Tensor, lambda_value: float): + def normalize( + self, + composed: torch.Tensor, + original_phrase_removed: torch.Tensor, + lambda_value: float, + ): return composed - (original_phrase_removed * lambda_value) @abstractmethod @@ -61,7 +63,6 @@ class ModelFactory: def __init__(self, name: str): self._name = name - def __call__(self, model: type[Model]) -> type[Model]: self._factory[self._name] = model return model @@ -81,12 +82,14 @@ class BERT_cls(Model): def embed(self, string: str) -> torch.Tensor: return self.encoder( - self.tokenizer(string, return_tensors="pt")["input_ids"] - ).pooler_output # the phrase in a that is not in b + self.tokenizer(string, return_tensors="pt")["input_ids"] + ).pooler_output # the phrase in a that is not in b - def calculate_similarity(self, a: torch.Tensor, b: torch.Tensor) -> float: - return torch.nn.functional.cosine_similarity(a, b).item() # dist(\mu(a), \mu(b)) + return torch.nn.functional.cosine_similarity( + a, b + ).item() # dist(\mu(a), \mu(b)) + @ModelFactory("S_BERT") class S_BERT(Model): @@ -104,8 +107,9 @@ class S_BERT(Model): def find_diff(a: str, b: str) -> list[str] | None: """ Finds the longest continuous substring where A differs from B and B differs from A. + Subtracts the longest common prefix and suffix and takes whats left. Not the smartest algorithm, perhaps. """ - if a == b: # Should not happen for SICK, but you never know. + if a == b: # Should not happen for SICK or NCIP, but you never know. return None words_a = a.split() @@ -127,10 +131,10 @@ def find_diff(a: str, b: str) -> list[str] | None: break diff_a = words_a[ - prefix_len : len(words_a) - suffix_len if suffix_len > 0 else len(words_a) + prefix_len:len(words_a) - suffix_len if suffix_len > 0 else len(words_a) ] diff_b = words_b[ - prefix_len : len(words_b) - suffix_len if suffix_len > 0 else len(words_b) + prefix_len:len(words_b) - suffix_len if suffix_len > 0 else len(words_b) ] if not diff_a or not diff_b: @@ -144,7 +148,13 @@ def find_diff(a: str, b: str) -> list[str] | None: class Experiment: - def __init__(self, sentence_pairs: SentencePairs, model: Model, result_path: pathlib.Path, normalization_value: float): + def __init__( + self, + sentence_pairs: SentencePairs, + model: Model, + result_path: pathlib.Path, + normalization_value: float, + ): self.model: Model = model self.sentence_pairs = sentence_pairs self.normalization_value: float = normalization_value @@ -152,8 +162,7 @@ class Experiment: self.points: list[Point] = [] self.result_path: pathlib.Path = result_path - - def run_experiment(self): + def run_experiment(self) -> None: for sentence_a, sentence_b in tqdm.tqdm(zip(*self.sentence_pairs)): diffs: list[str] | None = find_diff(sentence_a, sentence_b) if not diffs: @@ -166,37 +175,44 @@ class Experiment: b_in_a: str = sentence_a.replace(phrase_a, phrase_b) phrase_a_embedded: torch.Tensor = self.model.embed(phrase_a) - phrase_b_embedded: torch.Tensor = self.model.embed(phrase_b) + phrase_b_embedded: torch.Tensor = self.model.embed(phrase_b) - phrase_similarity: float = self.model.calculate_similarity(phrase_a_embedded, phrase_b_embedded) + phrase_similarity: float = self.model.calculate_similarity( + phrase_a_embedded, phrase_b_embedded + ) a_in_context_embedded = self.model.embed(sentence_a) b_in_context_embedded = self.model.embed(b_in_a) if self.normalization_value > 0.0: a_removed: str = sentence_a.replace(phrase_a, "") - b_removed: str = sentence_b.replace(phrase_b, "") + b_removed: str = sentence_b.replace(phrase_b, "") a_removed_embedded = self.model.embed(a_removed) b_removed_embedded = self.model.embed(b_removed) - a_in_context_embedded = self.model.normalize(a_in_context_embedded, a_removed_embedded, self.normalization_value) - b_in_context_embedded = self.model.normalize(b_in_context_embedded, b_removed_embedded, self.normalization_value) + a_in_context_embedded = self.model.normalize( + a_in_context_embedded, a_removed_embedded, self.normalization_value + ) + b_in_context_embedded = self.model.normalize( + b_in_context_embedded, b_removed_embedded, self.normalization_value + ) - context_similarity: float = self.model.calculate_similarity(a_in_context_embedded, b_in_context_embedded) + context_similarity: float = self.model.calculate_similarity( + a_in_context_embedded, b_in_context_embedded + ) self.points.append( - Point( - phrase_1=phrase_a, - phrase_2=phrase_b, - context_1=sentence_a, - context_2=b_in_a, - phrase_similarity=phrase_similarity, - contextualized_similarity=context_similarity - ) + Point( + phrase_1=phrase_a, + phrase_2=phrase_b, + context_1=sentence_a, + context_2=b_in_a, + phrase_similarity=phrase_similarity, + contextualized_similarity=context_similarity, + ) ) def save_results(self): - pathlib.Path(self.result_path).mkdir(parents=True, exist_ok=True) with open(self.result_path.with_suffix(".pkl"), "wb") as file: pickle.dump(self.points, file) @@ -206,7 +222,7 @@ def main() -> None: sep = "\t" if args.experiment_type == "ncip": sep = "," - + df: pandas.DataFrame = pandas.read_csv(args.data_path, sep=sep) if args.experiment_type == "ncip": @@ -216,29 +232,33 @@ def main() -> None: synonym_sentence3: list[str] = df["both synonym alt3_1"].values.tolist() synonym_sentence4: list[str] = df["both synonym alt4_1"].values.tolist() - sentence_A = original_sentence * 4 - sentence_B = synonym_sentence + synonym_sentence2 + synonym_sentence3 + synonym_sentence4 - sentence_pairs = SentencePairs(sentence_A=sentence_A, sentence_B=sentence_B) + sentence_A: list[str] = original_sentence * 4 + sentence_B: list[str] = ( + synonym_sentence + synonym_sentence2 + synonym_sentence3 + synonym_sentence4 + ) elif args.experiment_type == "sick": lexA = df.loc[df["sentence_A_expRule"] == "S2_lex"] lexB = df.loc[df["sentence_B_expRule"] == "S2_lex"] lex = pandas.concat([lexA, lexB]) - sentence_A: list[str] = lex["sentence_A"].values.tolist() - sentence_B: list[str] = lex["sentence_B"].values.tolist() - sentence_pairs = SentencePairs(sentence_A=sentence_A, sentence_B=sentence_B) + sentence_A = lex["sentence_A"].values.tolist() + sentence_B = lex["sentence_B"].values.tolist() else: - assert ValueError("Undefined experiment type...") + raise ValueError("Undefined experiment type...") + sentence_pairs = SentencePairs(sentence_A=sentence_A, sentence_B=sentence_B) for model_type in ModelFactory._factory.keys(): model = ModelFactory.get(model_type) - result_path = args.result_path / f"{args.experiment_type}_{model_type}_lambda{int(args.normalization_value * 10)}" # shitty logging of lambda parameter, need to fix + result_path = ( + args.result_path + / f"{args.experiment_type}-{model_type}-lambda{int(args.normalization_value * 10)}" + ) # shitty logging of lambda parameter, need to fix experiment = Experiment( - sentence_pairs=sentence_pairs, - model=model, - result_path = result_path, - normalization_value=args.normalization_value, + sentence_pairs=sentence_pairs, + model=model, + result_path=result_path, + normalization_value=args.normalization_value, ) print(f"Running experiment for model: {model_type}.") experiment.run_experiment() @@ -248,4 +268,3 @@ def main() -> None: if __name__ == "__main__": main() -