Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| اختبار مبسط لمعالج النصوص بدون تحميل نموذج Whisper | |
| """ | |
| import sys | |
| import os | |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) | |
| import logging | |
| from collections import Counter | |
| import nltk | |
| from nltk.tokenize import word_tokenize | |
| import textdistance | |
| from diff_match_patch import diff_match_patch | |
| import Levenshtein as lev | |
| import re | |
| # إعداد التسجيل | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # تحميل بيانات NLTK المطلوبة | |
| try: | |
| nltk.data.find('tokenizers/punkt') | |
| except LookupError: | |
| nltk.download('punkt') | |
| try: | |
| nltk.data.find('tokenizers/punkt_tab') | |
| except LookupError: | |
| nltk.download('punkt_tab') | |
| class SimpleTextProcessor: | |
| """ | |
| معالج نصوص مبسط للاختبار بدون نموذج Whisper | |
| """ | |
| def __init__(self): | |
| self.dictionary = {} | |
| def create_dictionary(self, texts): | |
| """إنشاء قاموس من النصوص المرجعية""" | |
| words = [] | |
| for text in texts: | |
| words.extend(word_tokenize(text)) | |
| word_counts = Counter(words) | |
| self.dictionary = word_counts | |
| logger.info(f"تم إنشاء قاموس يحتوي على {len(word_counts)} كلمة") | |
| return word_counts | |
| def dl_distance(self, word1, word2): | |
| """حساب مسافة Damerau-Levenshtein بين كلمتين""" | |
| return textdistance.damerau_levenshtein(word1, word2) | |
| def find_closest_words(self, misspelled_word, max_distance=2): | |
| """البحث عن أقرب الكلمات في القاموس""" | |
| candidates = [] | |
| for word, freq in self.dictionary.items(): | |
| if word in ['.', '،', '(', ')', ':', '؟', '!']: # تجاهل علامات الترقيم | |
| continue | |
| distance = self.dl_distance(misspelled_word, word) | |
| if distance <= max_distance: | |
| candidates.append((word, freq, distance)) | |
| # ترتيب حسب: المسافة، ثم التكرار، ثم التقارب في الطول، ثم نفس الحرف الأول | |
| candidates.sort(key=lambda x: ( | |
| x[2], # أقل مسافة أولاً | |
| -x[1], # أكثر تكراراً | |
| abs(len(x[0]) - len(misspelled_word)), # تقارب الطول | |
| x[0][0] != misspelled_word[0] if x[0] and misspelled_word else False # يبدأ بنفس الحرف | |
| )) | |
| return candidates | |
| def correct_text(self, text, max_distance=3, threshold_freq=1): | |
| """تصحيح النص باستخدام القاموس المرجعي""" | |
| if not self.dictionary: | |
| logger.warning("لم يتم إنشاء قاموس مرجعي بعد") | |
| return text, [] | |
| words = word_tokenize(text) | |
| corrected_words = [] | |
| corrections = [] | |
| for word in words: | |
| suggestions = self.find_closest_words(word, max_distance) | |
| if suggestions: | |
| best_suggestion, freq, dist = suggestions[0] | |
| if best_suggestion != word and freq > threshold_freq: | |
| corrected_words.append(best_suggestion) | |
| corrections.append({ | |
| "original": word, | |
| "corrected": best_suggestion, | |
| "frequency": freq, | |
| "distance": dist | |
| }) | |
| else: | |
| corrected_words.append(word) | |
| else: | |
| corrected_words.append(word) | |
| corrected_text = " ".join(corrected_words) | |
| # تنظيف المسافات الزائدة حول علامات الترقيم | |
| corrected_text = re.sub(r'\s+([.،؟!])', r'\1', corrected_text) | |
| logger.info(f"تم تصحيح {len(corrections)} كلمة") | |
| return corrected_text, corrections | |
| def compare_texts(self, reference_text, transcribed_text): | |
| """مقارنة النصوص وإظهار الاختلافات""" | |
| # معالجة أولية للنصوص | |
| ref_processed = re.sub(r'[.،؟!]', '', reference_text).lower().strip() | |
| trans_processed = re.sub(r'[.،؟!]', '', transcribed_text).lower().strip() | |
| # حساب مسافة Levenshtein للكلمات (WER) | |
| ref_words = ref_processed.split() | |
| trans_words = trans_processed.split() | |
| wer = lev.distance(ref_words, trans_words) / len(ref_words) if ref_words else 0 | |
| # حساب مسافة Levenshtein للأحرف (CER) | |
| cer = lev.distance(ref_processed, trans_processed) / len(ref_processed) if ref_processed else 0 | |
| # إنشاء HTML للاختلافات | |
| dmp = diff_match_patch() | |
| diffs = dmp.diff_main(ref_processed, trans_processed) | |
| dmp.diff_cleanupSemantic(diffs) | |
| html_diff = dmp.diff_prettyHtml(diffs) | |
| return { | |
| "wer": wer, | |
| "cer": cer, | |
| "wer_percentage": f"{wer:.2%}", | |
| "cer_percentage": f"{cer:.2%}", | |
| "html_diff": html_diff, | |
| "reference_words": len(ref_words), | |
| "transcribed_words": len(trans_words), | |
| "word_differences": abs(len(ref_words) - len(trans_words)) | |
| } | |
| def test_text_correction(): | |
| """اختبار تصحيح النصوص""" | |
| logger.info("🧪 بدء اختبار تصحيح النصوص") | |
| # إنشاء معالج النصوص | |
| processor = SimpleTextProcessor() | |
| # نصوص مرجعية لبناء القاموس | |
| reference_texts = [ | |
| "يهتز غشاء الطبل تنتقل عظيمات السمع الاهتزازات إلى النافذة البيضية", | |
| "يهتز غشاء النافذة البيضية يهتز اللمف الخارجي في القناة الدهليزية", | |
| "يهتز غشاء رايسنر تنتقل الاهتزازات إلى اللمف الداخلي في القناة القوقعية", | |
| "اهتزاز الغشاء القاعدي بشكل موجي" | |
| ] | |
| # إنشاء القاموس | |
| dictionary = processor.create_dictionary(reference_texts) | |
| logger.info(f"تم إنشاء قاموس يحتوي على {len(dictionary)} كلمة") | |
| # نص للاختبار (يحتوي على أخطاء) | |
| test_text = "يحتاز غشاء الطبل تنتقل عظيمات السماء الاحتزازات إلى النافذة البيضية يحتاز الملف الخارجي" | |
| logger.info(f"النص الأصلي: {test_text}") | |
| # تصحيح النص | |
| corrected_text, corrections = processor.correct_text(test_text, max_distance=3, threshold_freq=1) | |
| logger.info(f"النص المصحح: {corrected_text}") | |
| logger.info(f"عدد التصحيحات: {len(corrections)}") | |
| for correction in corrections: | |
| logger.info(f" تصحيح: '{correction['original']}' → '{correction['corrected']}' " | |
| f"(تكرار: {correction['frequency']}, مسافة: {correction['distance']})") | |
| return corrected_text, corrections | |
| def test_text_comparison(): | |
| """اختبار مقارنة النصوص""" | |
| logger.info("🧪 بدء اختبار مقارنة النصوص") | |
| processor = SimpleTextProcessor() | |
| # نصوص للمقارنة | |
| reference_text = "يهتز غشاء الطبل تنتقل عظيمات السمع الاهتزازات إلى النافذة البيضية" | |
| transcribed_text = "يحتاز غشاء الطبل تنتقل عظيمات السماء الاحتزازات إلى النافذة البيضية" | |
| # مقارنة النصوص | |
| comparison_result = processor.compare_texts(reference_text, transcribed_text) | |
| logger.info(f"معدل خطأ الكلمات (WER): {comparison_result['wer_percentage']}") | |
| logger.info(f"معدل خطأ الأحرف (CER): {comparison_result['cer_percentage']}") | |
| logger.info(f"عدد كلمات النص المرجعي: {comparison_result['reference_words']}") | |
| logger.info(f"عدد كلمات النص المستخرج: {comparison_result['transcribed_words']}") | |
| logger.info(f"فرق عدد الكلمات: {comparison_result['word_differences']}") | |
| return comparison_result | |
| def main(): | |
| """الدالة الرئيسية للاختبار""" | |
| logger.info("🚀 بدء اختبارات معالج النصوص المبسط") | |
| try: | |
| # اختبار تصحيح النصوص | |
| corrected_text, corrections = test_text_correction() | |
| # اختبار مقارنة النصوص | |
| comparison_result = test_text_comparison() | |
| logger.info("✅ تم إنجاز جميع الاختبارات بنجاح!") | |
| # عرض ملخص النتائج | |
| print("\n" + "="*60) | |
| print("📊 ملخص نتائج الاختبارات") | |
| print("="*60) | |
| print(f"عدد التصحيحات المطبقة: {len(corrections)}") | |
| print(f"معدل خطأ الكلمات: {comparison_result['wer_percentage']}") | |
| print(f"معدل خطأ الأحرف: {comparison_result['cer_percentage']}") | |
| print("="*60) | |
| return True | |
| except Exception as e: | |
| logger.error(f"❌ فشل في الاختبارات: {str(e)}") | |
| return False | |
| if __name__ == "__main__": | |
| success = main() | |
| if success: | |
| print("✅ جميع الاختبارات نجحت!") | |
| else: | |
| print("❌ فشلت بعض الاختبارات!") | |
| sys.exit(1) | |