Bayan AI commited on
Commit
19a801d
·
1 Parent(s): 44c30d6

Feat: Add demonstrative and noun-adjective dual/plural agreement rules

Browse files
src/nlp/grammar/grammar_rules.py CHANGED
@@ -407,6 +407,103 @@ class ArabicGrammarGuard:
407
 
408
  return " ".join(corrected_tokens)
409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
  def process(self, original_text, generated_text):
412
  """Apply all grammar rules to model output."""
@@ -415,10 +512,12 @@ class ArabicGrammarGuard:
415
  # Each rule is wrapped in try/except so that if camel-tools
416
  # functions fail, the regex-based rules still execute.
417
  for rule_name, rule_fn in [
 
418
  ('fix_number_and_gender_agreement', self.fix_number_and_gender_agreement),
419
  ('smart_asmaa_khamsa_fix', self.smart_asmaa_khamsa_fix),
420
  ('fix_verbs_nasb_and_jazm', self.fix_verbs_nasb_and_jazm),
421
  ('fix_gender_agreement', self.fix_gender_agreement),
 
422
  ('fix_prepositions_advanced', self.fix_prepositions_advanced),
423
  ('fix_subject_verb_agreement', self.fix_subject_verb_agreement),
424
  ('fix_conditional_sentences', self.fix_conditional_sentences),
 
407
 
408
  return " ".join(corrected_tokens)
409
 
410
+ def fix_demonstrative_agreement(self, text):
411
+ tokens = simple_word_tokenize(text)
412
+ disambig_tokens = self.mle.disambiguate(tokens)
413
+ corrected_tokens = list(tokens)
414
+
415
+ for i in range(len(disambig_tokens) - 1):
416
+ w1 = corrected_tokens[i]
417
+ w2 = corrected_tokens[i+1]
418
+
419
+ if w1 not in ['هذا', 'هذه', 'هذان', 'هاتان', 'هذين', 'هاتين', 'هؤلاء']:
420
+ continue
421
+
422
+ w2_info = disambig_tokens[i+1].analyses[0].analysis if disambig_tokens[i+1].analyses else {}
423
+ w2_num = w2_info.get('num', 's')
424
+ w2_gen = w2_info.get('gen', 'm')
425
+
426
+ # Use heuristics to override or reinforce POS tags for duals
427
+ if w2.endswith('تان') or w2.endswith('تين'):
428
+ if w2_num == 'd':
429
+ w2_gen = 'f'
430
+
431
+ if w2_num == 'd':
432
+ is_nom = w2.endswith('ان') or w2.endswith('تان')
433
+ if w2_gen == 'f':
434
+ corrected_tokens[i] = 'هاتان' if is_nom else 'هاتين'
435
+ elif w2_gen == 'm':
436
+ corrected_tokens[i] = 'هذان' if is_nom else 'هذين'
437
+
438
+ return " ".join(corrected_tokens)
439
+
440
+ def fix_noun_adjective_agreement_advanced(self, text):
441
+ tokens = simple_word_tokenize(text)
442
+ disambig_tokens = self.mle.disambiguate(tokens)
443
+ corrected_tokens = list(tokens)
444
+
445
+ MASC_TO_FEM_ADJ = {
446
+ 'جميل': 'جميلة', 'كبير': 'كبيرة', 'صغير': 'صغيرة',
447
+ 'طويل': 'طويلة', 'قصير': 'قصيرة', 'جديد': 'جديدة',
448
+ 'قديم': 'قديمة', 'بعيد': 'بعيدة', 'قريب': 'قريبة',
449
+ 'سريع': 'سريعة', 'بطيء': 'بطيئة', 'واسع': 'واسعة',
450
+ 'ضيق': 'ضيقة', 'عميق': 'عميقة', 'خفيف': 'خفيفة',
451
+ 'ثقيل': 'ثقيلة', 'نظيف': 'نظيفة', 'مشرق': 'مشرقة',
452
+ 'ذكي': 'ذكية', 'غني': 'غنية', 'فقير': 'فقيرة',
453
+ 'متفوق': 'متفوقة', 'مجتهد': 'مجتهدة', 'ممتاز': 'ممتازة',
454
+ }
455
+
456
+ for i in range(len(disambig_tokens) - 1):
457
+ w1 = corrected_tokens[i]
458
+ w2 = corrected_tokens[i+1]
459
+
460
+ w1_info = disambig_tokens[i].analyses[0].analysis if disambig_tokens[i].analyses else {}
461
+ w1_pos = w1_info.get('pos', 'unknown')
462
+ w1_num = w1_info.get('num', 's')
463
+ w1_gen = w1_info.get('gen', 'm')
464
+
465
+ # Heuristic override for duals (camel_tools sometimes gets them wrong)
466
+ if w1.endswith('تان') or w1.endswith('تين'):
467
+ w1_gen = 'f'
468
+ w1_num = 'd'
469
+ elif w1.endswith('ان') or w1.endswith('ين'):
470
+ if len(w1) > 4:
471
+ w1_num = 'd'
472
+
473
+ # Dual Adjective Agreement
474
+ if w1_num == 'd' and w1_pos in ['noun', 'unknown', 'noun_prop']:
475
+ base_adj = None
476
+ for suffix in ['ان', 'ين', 'تان', 'تين', 'ة', 'ون', 'ات', '']:
477
+ stem = w2[:-len(suffix)] if suffix else w2
478
+ if stem in MASC_TO_FEM_ADJ:
479
+ base_adj = stem
480
+ break
481
+
482
+ if base_adj:
483
+ is_nom = w1.endswith('ان') or w1.endswith('تان')
484
+ if w1_gen == 'f':
485
+ corrected_tokens[i+1] = base_adj + ('تان' if is_nom else 'تين')
486
+ else:
487
+ corrected_tokens[i+1] = base_adj + ('ان' if is_nom else 'ين')
488
+
489
+ # Plural Human Adjective Agreement
490
+ elif w1_num == 'p' and w1_pos in ['noun', 'unknown']:
491
+ base_adj = None
492
+ for suffix in ['ان', 'ين', 'تان', 'تين', 'ة', 'ون', 'ات', 'ين', '']:
493
+ stem = w2[:-len(suffix)] if suffix else w2
494
+ if stem in MASC_TO_FEM_ADJ:
495
+ base_adj = stem
496
+ break
497
+
498
+ if base_adj:
499
+ if w1.endswith('ون') or w1.endswith('ين') or w1_gen == 'm':
500
+ is_nom = w1.endswith('ون')
501
+ corrected_tokens[i+1] = base_adj + ('ون' if is_nom else 'ين')
502
+ elif w1.endswith('ات') or w1_gen == 'f':
503
+ corrected_tokens[i+1] = base_adj + 'ات'
504
+
505
+ return " ".join(corrected_tokens)
506
+
507
 
508
  def process(self, original_text, generated_text):
509
  """Apply all grammar rules to model output."""
 
512
  # Each rule is wrapped in try/except so that if camel-tools
513
  # functions fail, the regex-based rules still execute.
514
  for rule_name, rule_fn in [
515
+ ('fix_demonstrative_agreement', self.fix_demonstrative_agreement),
516
  ('fix_number_and_gender_agreement', self.fix_number_and_gender_agreement),
517
  ('smart_asmaa_khamsa_fix', self.smart_asmaa_khamsa_fix),
518
  ('fix_verbs_nasb_and_jazm', self.fix_verbs_nasb_and_jazm),
519
  ('fix_gender_agreement', self.fix_gender_agreement),
520
+ ('fix_noun_adjective_agreement_advanced', self.fix_noun_adjective_agreement_advanced),
521
  ('fix_prepositions_advanced', self.fix_prepositions_advanced),
522
  ('fix_subject_verb_agreement', self.fix_subject_verb_agreement),
523
  ('fix_conditional_sentences', self.fix_conditional_sentences),
test_grammar_only.py CHANGED
@@ -18,7 +18,8 @@ def test_grammar():
18
  "يذهبون المهندسون الى الشركة",
19
  "يذهبون المهندسون الى الشركة، أليس كذلك؟",
20
  "مرحبا، يا عالم الطيور!",
21
- "متى يسعون في تطوير مهاراتكم بجد واخلاص تجدون ثمرة ذلك قريبا"
 
22
  ]
23
 
24
  for text in test_cases:
 
18
  "يذهبون المهندسون الى الشركة",
19
  "يذهبون المهندسون الى الشركة، أليس كذلك؟",
20
  "مرحبا، يا عالم الطيور!",
21
+ "متى يسعون في تطوير مهاراتكم بجد واخلاص تجدون ثمرة ذلك قريبا",
22
+ "هذان فتاتان جميل."
23
  ]
24
 
25
  for text in test_cases:
test_pos.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ sys.stdout.reconfigure(encoding='utf-8')
3
+ from camel_tools.tokenizers.word import simple_word_tokenize
4
+ from camel_tools.disambig.mle import MLEDisambiguator
5
+
6
+ mle = MLEDisambiguator.pretrained()
7
+ t = simple_word_tokenize('فتاتان جميل')
8
+ print([a.analyses[0].analysis for a in mle.disambiguate(t)])