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 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 308 |
-
result.append(AraSpellPostProcessor.HAMZA_WHITELIST[
|
| 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
|
| 315 |
-
remainder =
|
| 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
|