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()