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

extract_similarities.py (5691B)


      1 from typing import Any, NamedTuple, Iterator
      2 import copy
      3 import json
      4 import pathlib
      5 
      6 import torch
      7 import tqdm
      8 
      9 from epsilon_compositionality.models import ModelFactory, Model
     10 
     11 
     12 INPUT_PATH = pathlib.Path(__file__).parent / "data" / "ncimp.processed.jsonl"
     13 OUTPUT_PATH = pathlib.Path(__file__).parent / "data" / "similarities"
     14 
     15 
     16 def process_sample(input_sample: dict[str, Any]) -> dict[str, Any] | None:
     17     """Process a NCIMP sample into set of (a, b, ac, bc).
     18 
     19     A processed sample has the following shape:
     20     {
     21         "compound": "acid test",
     22         "comp_class": "NC",
     23         "head": [
     24             { # sentence 1
     25                 "positives": [ # compare "acid test" to alternatives
     26                     {
     27                         "phrase_a": "acid test",
     28                         "phrase_b": "acid trial",
     29                         "context_ac": "sentence1 with acid test…",
     30                         "context_bc": "sentence1 with acid trial…"
     31                     },
     32                     {
     33                         "phrase_a": "acid test",
     34                         "phrase_b": "acid analysis",
     35                         "context_ac": "sentence1 with acid test…",
     36                         "context_bc": "sentence1 with acid analysis…"
     37                     },
     38                     # …
     39                 ],
     40                 "negatives": [ # compare alternatives between themselves
     41                     {
     42                         "phrase_a": "acid analysis",
     43                         "phrase_b": "acid trial",
     44                         "context_ac": "sentence1 with acid analysis…",
     45                         "context_bc": "sentence1 with acid trial…"
     46                     },
     47                     {
     48                         "phrase_a": "acid tryout",
     49                         "phrase_b": "acid analysis",
     50                         "context_ac": "sentence1 with acid tryout…",
     51                         "context_bc": "sentence1 with acid analysis…"
     52                     },
     53                 ]
     54             },
     55             # … more sentences
     56         ],
     57         "modifier": [
     58             # similarly set of sentences where acid is replaced instead of test (e.g. sour test)
     59         ]
     60     }
     61     """
     62     output_sample: dict[str, Any] = {
     63             "compound": input_sample["compound"],
     64             "comp_class": input_sample["general class"],
     65         }
     66     for side_index, side in enumerate(["modifier", "head"]):
     67         output_sample[side] = []
     68         for sentence in input_sample["sentences"]:
     69             if len(sentence[f"{side} synonym"]) <= 1:
     70                 continue
     71 
     72             alternatives = list(sentence[f"{side} synonym"].items())
     73             second_alternatives = alternatives[1:] + [alternatives[0]]
     74             if len(alternatives) == 2:
     75                 alternatives, second_alternatives = [alternatives[0]], [alternatives[1]]
     76 
     77             positives: list[Any] = []
     78             negatives: list[Any] = []
     79             for (alt_phrase, alt_sentence), (second_alt_phrase, second_alt_sentence) in zip(alternatives, second_alternatives):
     80                 positives.append({
     81                         "phrase_a": input_sample["compound"].split()[side_index],
     82                         "phrase_b": alt_phrase.split()[side_index],
     83                         "context_ac": sentence["original"],
     84                         "context_bc": alt_sentence,
     85                     })
     86                 negatives.append({
     87                         "phrase_a": second_alt_phrase.split()[side_index],
     88                         "phrase_b": alt_phrase.split()[side_index],
     89                         "context_ac": second_alt_sentence,
     90                         "context_bc": alt_sentence,
     91                     })
     92             output_sample[side].append({"positives": positives, "negatives": negatives})
     93         if not output_sample[side]:
     94             return None
     95     return output_sample
     96 
     97 
     98 def load_dataset() -> list[dict[str, Any]]:
     99     dataset: list[dict[str, Any]] = []
    100     with INPUT_PATH.open("r") as input_file:
    101         for line in input_file:
    102             sample: dict[str, Any] | None = process_sample(json.loads(line))
    103             if sample:
    104                 dataset.append(sample)
    105     return dataset
    106 
    107 
    108 def main() -> None:
    109     dataset: list[dict[str, Any]] = load_dataset()
    110     OUTPUT_PATH.mkdir(parents=True, exist_ok=True)
    111     for model_name in ModelFactory._factory:
    112         model_path: pathlib.Path = OUTPUT_PATH / f"{model_name}.jsonl"
    113 
    114         model: Model = ModelFactory.get(model_name)
    115         output: list[dict[str, Any]] = []
    116 
    117         with model_path.open("w") as output_file:
    118             for input_sample in tqdm.tqdm(dataset, f"Embedding dataset with {model_name}"):
    119                 output_sample: dict[str, Any] = copy.deepcopy(input_sample)
    120                 for side_index, side in enumerate(["modifier", "head"]):
    121                     for sentence in output_sample[side]:
    122                         for polarity in ["positives", "negatives"]:
    123                             for subsample in sentence[polarity]:
    124                                 phrase_a: torch.Tensor = model.embed(subsample["phrase_a"])
    125                                 phrase_b: torch.Tensor = model.embed(subsample["phrase_b"])
    126                                 context_ac: torch.Tensor = model.embed(subsample["context_ac"])
    127                                 context_bc: torch.Tensor = model.embed(subsample["context_bc"])
    128                                 subsample["phrase_similarity"] = model.calculate_similarity(phrase_a, phrase_b)
    129                                 subsample["contextual_similarity"] = model.calculate_similarity(context_ac, context_bc)
    130                 print(json.dumps(output_sample), file=output_file)
    131 
    132 
    133 if __name__ == "__main__":
    134     main()