youssefreda9 commited on
Commit
5ed1cab
·
1 Parent(s): 9786f39

Fix collision benchmark: non-overlap, destructive tokenization, colon guard, hamza punctuation

Browse files
src/nlp/correction_patch.py CHANGED
@@ -178,12 +178,10 @@ class PatchSet:
178
  overlap_start = max(patch.start_original, cs)
179
  overlap_end = min(patch.end_original, ce)
180
  overlap_width = overlap_end - overlap_start
181
- # Compare to the smaller patch's width
182
- patch_width = max(1, patch.end_original - patch.start_original)
183
- claimed_width = max(1, ce - cs)
184
- smaller_width = min(patch_width, claimed_width)
185
- overlap_ratio = overlap_width / smaller_width
186
- if overlap_ratio > 0.5:
187
  has_substantial_overlap = True
188
  overlapping_resolved_idx = res_idx
189
  break
 
178
  overlap_start = max(patch.start_original, cs)
179
  overlap_end = min(patch.end_original, ce)
180
  overlap_width = overlap_end - overlap_start
181
+
182
+ if overlap_width > 0:
183
+ # STRICT NON-OVERLAP RULE: ANY overlap causes the lower priority patch to be dropped.
184
+ # Overlapping patches cannot be safely applied sequentially by standard frontend/benchmark clients.
 
 
185
  has_substantial_overlap = True
186
  overlapping_resolved_idx = res_idx
187
  break
src/nlp/punctuation/punctuation_rules.py CHANGED
@@ -59,7 +59,22 @@ def arabic_postprocessing(text: str) -> str:
59
  # Fix misplaced colons for saying verbs (e.g. قال: المعلم -> قال المعلم:)
60
  text = re.sub(r'\b(قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت):?\s+(ال[أ-ي]+|أحمد|محمد|محمود|علي|عمر|خالد|فاطمة|مريم|عائشة|خديجة)\b:?', r'\1 \2:', text)
61
 
62
- # Remove colons after verbs that do not introduce speech/lists
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  text = re.sub(r'\b(يقدر|يستطيع|يمكن|يجب|ينبغي|يعتبر|يعد|يرى|يعتقد)\s*:', r'\1 ', text)
64
  # Replace semicolon with comma if followed by "و" (and) or similar conjunctions, as semicolon is for separate clauses
65
  text = re.sub(r'؛\s*(و|ف|ثم|أو|أم|بل)\b', r'، \1', text)
 
59
  # Fix misplaced colons for saying verbs (e.g. قال: المعلم -> قال المعلم:)
60
  text = re.sub(r'\b(قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت):?\s+(ال[أ-ي]+|أحمد|محمد|محمود|علي|عمر|خالد|فاطمة|مريم|عائشة|خديجة)\b:?', r'\1 \2:', text)
61
 
62
+ # NEW: Strict Colon Guard
63
+ _ALLOWED_COLON_CUES = r'(قال|يقول|قالت|تقول|أجاب|أجابت|سأل|سألت|أخبر|أخبرت|صرح|صرحت|أضاف|أضافت|أردف|أردفت|وضح|وضحت|أوضح|أوضحت|رد|ردت|التالي|الآتي|مثال|ملاحظة|تنبيه|تحذير|قائلا|قائلة|اسم|العمر|تاريخ|رقم|عاجل|الآتية|التالية)'
64
+
65
+ def _colon_guard(match):
66
+ prev_word = match.group(1)
67
+ if re.fullmatch(_ALLOWED_COLON_CUES, prev_word):
68
+ return match.group(0)
69
+ # If it's a definite noun (starts with ال) and not in allowed list, it's hallucinated.
70
+ # e.g., "الشمس:" -> "الشمس،"
71
+ if prev_word.startswith('ال'):
72
+ return f'{prev_word}،'
73
+ return match.group(0)
74
+
75
+ text = re.sub(r'([\u0600-\u06FF]+)(\s*:)', _colon_guard, text)
76
+
77
+ # Remove colons after specific non-speech verbs (fallback for verbs without ال)
78
  text = re.sub(r'\b(يقدر|يستطيع|يمكن|يجب|ينبغي|يعتبر|يعد|يرى|يعتقد)\s*:', r'\1 ', text)
79
  # Replace semicolon with comma if followed by "و" (and) or similar conjunctions, as semicolon is for separate clauses
80
  text = re.sub(r'؛\s*(و|ف|ثم|أو|أم|بل)\b', r'، \1', text)
src/nlp/spelling/araspell_rules.py CHANGED
@@ -298,23 +298,33 @@ class AraSpellPostProcessor:
298
  """
299
  Fix common hamza placement errors using a whitelist.
300
  Also handles prefixed words: و/ف/ب/ك/ل + whitelist word.
301
- e.g. واصدقائي → وأصدقائي, بالاسعار → بالأسعار
302
  """
303
  words = text.split()
304
  result = []
305
  for word in words:
 
 
 
 
 
 
 
 
 
 
306
  # Check exact match first
307
- if word in AraSpellPostProcessor.HAMZA_WHITELIST:
308
- result.append(AraSpellPostProcessor.HAMZA_WHITELIST[word])
309
  continue
310
 
311
  # Try stripping common prefixes and looking up the remainder
312
  fixed = False
313
  for prefix in AraSpellPostProcessor.HAMZA_PREFIXES:
314
- if word.startswith(prefix) and len(word) > len(prefix) + 1:
315
- remainder = word[len(prefix):]
316
  if remainder in AraSpellPostProcessor.HAMZA_WHITELIST:
317
- result.append(prefix + AraSpellPostProcessor.HAMZA_WHITELIST[remainder])
318
  fixed = True
319
  break
320
  if not fixed:
@@ -1607,4 +1617,16 @@ class ArabicSpellChecker:
1607
  result = AraSpellPostProcessor.fix_common_hamza(result)
1608
  result = AraSpellPostProcessor.fix_ha_ta_marbuta(result, vocab_manager=self.vocab_manager)
1609
 
 
 
 
 
 
 
 
 
 
 
 
 
1610
  return result
 
298
  """
299
  Fix common hamza placement errors using a whitelist.
300
  Also handles prefixed words: و/ف/ب/ك/ل + whitelist word.
301
+ Handles adjacent punctuation (e.g. واصدقائي، → وأصدقائي،)
302
  """
303
  words = text.split()
304
  result = []
305
  for word in words:
306
+ # Separate leading/trailing punctuation from the core word
307
+ match = re.match(r'^([\.,،؛؟!:;?\(\)\[\]«»"\'\s]*)(.*?)([\.,،؛؟!:;?\(\)\[\]«»"\'\s]*)$', word)
308
+ if not match or not match.group(2):
309
+ result.append(word)
310
+ continue
311
+
312
+ lead_punct = match.group(1)
313
+ core_word = match.group(2)
314
+ trail_punct = match.group(3)
315
+
316
  # Check exact match first
317
+ if core_word in AraSpellPostProcessor.HAMZA_WHITELIST:
318
+ result.append(lead_punct + AraSpellPostProcessor.HAMZA_WHITELIST[core_word] + trail_punct)
319
  continue
320
 
321
  # Try stripping common prefixes and looking up the remainder
322
  fixed = False
323
  for prefix in AraSpellPostProcessor.HAMZA_PREFIXES:
324
+ if core_word.startswith(prefix) and len(core_word) > len(prefix) + 1:
325
+ remainder = core_word[len(prefix):]
326
  if remainder in AraSpellPostProcessor.HAMZA_WHITELIST:
327
+ result.append(lead_punct + prefix + AraSpellPostProcessor.HAMZA_WHITELIST[remainder] + trail_punct)
328
  fixed = True
329
  break
330
  if not fixed:
 
1617
  result = AraSpellPostProcessor.fix_common_hamza(result)
1618
  result = AraSpellPostProcessor.fix_ha_ta_marbuta(result, vocab_manager=self.vocab_manager)
1619
 
1620
+ # 11. DESTRUCTIVE TOKENIZATION GUARD
1621
+ # Arabic orthography does not use standalone 1-letter words.
1622
+ # If the model creates a standalone 1-letter word that was not in the original, it's a tokenization hallucination.
1623
+ orig_standalone = set(w for w in original.split() if len(w) == 1)
1624
+ res_words_list = result.split()
1625
+ for w in res_words_list:
1626
+ if len(w) == 1 and w not in orig_standalone:
1627
+ if w in 'واتيبلفك':
1628
+ logger.info(f"[SPELLING] Blocked destructive tokenization (hallucinated standalone '{w}'): '{original}' -> '{result}'")
1629
+ result = original
1630
+ break
1631
+
1632
  return result