33import pandas as pd
44import json
55import math
6+ import string
67from nltk .metrics import edit_distance
78from tqdm import tqdm
89from plotly import graph_objects as go
@@ -63,7 +64,19 @@ def normalize_noise_level(noise_level):
6364 return min (noise_level , 1.0 )
6465
6566
66- def apply_text_noise (text , noise_level = 0.1 , character_mask = '_' ):
67+ def get_random_replacement_char (original_char ):
68+ if str (original_char ).isupper ():
69+ alphabet = string .ascii_uppercase
70+ original = str (original_char ).upper ()
71+ else :
72+ alphabet = string .ascii_lowercase
73+ original = str (original_char ).lower ()
74+
75+ candidates = [char for char in alphabet if char != original ]
76+ return str (np .random .choice (candidates ))
77+
78+
79+ def apply_text_noise (text , noise_level = 0.1 , character_mask = None ):
6780 text = str (text ).strip ()
6881 if not text :
6982 return text
@@ -95,21 +108,35 @@ def apply_text_noise(text, noise_level=0.1, character_mask='_'):
95108
96109 masked_text = list (text )
97110 for position in selected_positions :
98- masked_text [position ] = character_mask
111+ masked_text [position ] = (
112+ get_random_replacement_char (masked_text [position ])
113+ if character_mask is None
114+ else character_mask
115+ )
99116
100117 return '' .join (masked_text )
101118
102119
103- def test_recall (cfg , device , experiments_root ):
120+ def test_recall (cfg , device , experiments_root , use_noise = False ):
104121
105122 latent = cfg .neural .latent_dim [0 ] if isinstance (cfg .neural .latent_dim , ListConfig ) else int (cfg .neural .latent_dim )
106123
107124 path = get_experiment_path (cfg , experiments_root , latent )
108125 embeddings_dataset = load_embeddings_dataset (path , device = device )
126+ test_embeddings_dataset = embeddings_dataset
127+ recognition_dirname = "recognition"
128+
129+ if use_noise :
130+ test_embeddings_dataset = load_embeddings_dataset (
131+ path ,
132+ device = device ,
133+ noised = True ,
134+ )
135+ recognition_dirname = f"recognition_noise_{ cfg .app .noise } "
109136
110137 # First Experiment: Test Recognition Rates
111138
112- recognition_text_path = ensure_directory (path / "recognition" )
139+ recognition_text_path = ensure_directory (path / recognition_dirname )
113140
114141 quantizer = Quant (
115142 torch .cat ([embeddings_dataset .train .data , embeddings_dataset .test .data ], dim = 0 )
@@ -141,7 +168,10 @@ def test_recall(cfg, device, experiments_root):
141168 else [float (cfg .memory .sigma )]
142169 )
143170
144- print (f"[INFO] Testing recall with latent={ latent } , domains={ domains } , sigmas={ sigmas } , xis={ xis } , iotas={ iotas } , kappas={ kappas } " )
171+ print (
172+ f"[INFO] Testing recall with latent={ latent } , domains={ domains } , sigmas={ sigmas } , "
173+ f"xis={ xis } , iotas={ iotas } , kappas={ kappas } , use_noise={ use_noise } "
174+ )
145175
146176 train_embeddings = embeddings_dataset .train .data
147177 split_index = max (1 , len (train_embeddings ) // 2 ) # Split train into two halves for seen/unseen evaluation, ensuring at least one sample in the seen half
@@ -152,7 +182,7 @@ def test_recall(cfg, device, experiments_root):
152182
153183 confusion_summaries = [] # Prepare to collect confusion summaries for all domains
154184 confusion_summary_path = (
155- recognition_text_path / "recognition_confusion_summary .json"
185+ recognition_text_path / f"recognition_confusion_summary_ { use_noise } .json"
156186 )
157187
158188 from rich .progress import (
@@ -223,7 +253,7 @@ def test_recall(cfg, device, experiments_root):
223253 eam ,
224254 seen_dataset = memory_wrapper .train ,
225255 unseen_dataset = memory_wrapper .test ,
226- test_dataset = embeddings_dataset .test ,
256+ test_dataset = test_embeddings_dataset .test ,
227257 quantizer = quantizer ,
228258 batch_size = batch_size ,
229259 )
@@ -238,15 +268,14 @@ def test_recall(cfg, device, experiments_root):
238268 "xi" : xi ,
239269 "seen_source" : "first_half_of_train" ,
240270 "unseen_source" : "second_half_of_train" ,
241- "test_source" : "dataset_test_split" ,
242271 "labels" : confusion ["labels" ],
243272 "matrix" : confusion ["matrix" ].tolist (),
244273 "counts" : confusion ["counts" ],
245274 "rates" : confusion ["rates" ],
246275 }
247276
248277 confusion_path = (
249- recognition_text_path / f"recognition_confusion_domain_{ domain } _sigma_{ sigma } _xi_{ xi } _iota_{ iota } _kappa_{ kappa } .json"
278+ recognition_text_path / f"recognition_confusion_domain_{ domain } _sigma_{ sigma } _xi_{ xi } _iota_{ iota } _kappa_{ kappa } _ { test_source } .json"
250279 )
251280
252281 with open (confusion_path , "w" , encoding = "utf-8" ) as f_out :
@@ -633,17 +662,18 @@ def text_reconstruction_metrics(
633662 reconstructed_embeddings = torch .cat (reconstructed_embeddings , dim = 0 )
634663 source_embeddings = source_embeddings .detach ().cpu ()
635664
636- source_norm = torch .linalg .norm (source_embeddings , dim = 1 )
637- reconstructed_norm = torch .linalg .norm (reconstructed_embeddings , dim = 1 )
638- denom = torch .clamp (source_norm * reconstructed_norm , min = 1e-12 )
665+ source_norm = torch .linalg .norm (source_embeddings , dim = 1 ) # Compute norms for cosine similarity
666+ reconstructed_norm = torch .linalg .norm (reconstructed_embeddings , dim = 1 ) # Compute norms for cosine similarity
667+ denom = torch .clamp (source_norm * reconstructed_norm , min = 1e-12 ) # Avoid division by zero in cosine similarity calculation
639668 cosine_scores = (
640669 torch .sum (source_embeddings * reconstructed_embeddings , dim = 1 ) / denom
641- )
642- l2_scores = torch .linalg .norm (source_embeddings - reconstructed_embeddings , dim = 1 )
670+ ) # Get cosine similarity scores between source and reconstructed embeddings
671+ l2_scores = torch .linalg .norm (source_embeddings - reconstructed_embeddings , dim = 1 ) # Get L2 distance scores between source and reconstructed embeddings
672+ euclidean_scores = torch .sqrt (torch .sum ((source_embeddings - reconstructed_embeddings ) ** 2 , dim = 1 )) # Get Euclidean distance scores between source and reconstructed embeddings
643673
644674 samples = []
645- for index , (original , cue , reconstructed , cosine , l2 ) in enumerate (
646- zip (original_texts , cue_texts , reconstructed_texts , cosine_scores , l2_scores )
675+ for index , (original , cue , reconstructed , cosine , l2 , euclidean ) in enumerate (
676+ zip (original_texts , cue_texts , reconstructed_texts , cosine_scores , l2_scores , euclidean_scores )
647677 ):
648678 samples .append (
649679 {
@@ -652,6 +682,7 @@ def text_reconstruction_metrics(
652682 "cue" : cue ,
653683 "reconstructed" : reconstructed ,
654684 "cosine" : float (cosine .item ()),
685+ "euclidean" : float (euclidean .item ()),
655686 "l2" : float (l2 .item ()),
656687 "edit_distance" : int (
657688 edit_distance (original .lower (), reconstructed .lower ())
@@ -662,6 +693,7 @@ def text_reconstruction_metrics(
662693 summary = {
663694 "samples" : len (samples ),
664695 "mean_cosine" : float (np .mean ([item ["cosine" ] for item in samples ])),
696+ "mean_euclidean" : float (np .mean ([item ["euclidean" ] for item in samples ])),
665697 "mean_l2" : float (np .mean ([item ["l2" ] for item in samples ])),
666698 "mean_edit_distance" : float (
667699 np .mean ([item ["edit_distance" ] for item in samples ])
@@ -851,6 +883,7 @@ def get_besttext_params(cfg, config, device, EXPERIMENTS_ROOT, use_noise=False):
851883 "recognized" : recognized ,
852884 "unrecognized" : unrecognized ,
853885 "mean_cosine" : summary ["mean_cosine" ],
886+ "mean_euclidean" : summary ["mean_euclidean" ],
854887 "mean_l2" : summary ["mean_l2" ],
855888 "mean_edit_distance" : summary ["mean_edit_distance" ],
856889 }
0 commit comments