Download app3 from leilaghomashchi/Benchmark-data-anonymization: direct link, hf CLI and curl.
- Browser
- Download file 20.5 kB
-
https://huggingface.co/spaces/leilaghomashchi/Benchmark-data-anonymization/resolve/main/app3
- Command line
-
hf download hf://spaces/leilaghomashchi/Benchmark-data-anonymization/app3
-
curl -L -o app3 https://huggingface.co/spaces/leilaghomashchi/Benchmark-data-anonymization/resolve/main/app3
20.5 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| import gradio as gr | |
| import pandas as pd | |
| import re | |
| class ImprovedAnonymizationEvaluator: | |
| def __init__(self): | |
| # فهرست بانکهای ایران | |
| self.iranian_banks = [ | |
| 'بانک ملی', 'بانک صادرات', 'بانک پاسارگاد', 'بانک کشاورزی', | |
| 'بانک ملت', 'بانک تجارت', 'بانک صنعت و معدن', 'بانک رسالت', | |
| 'بانک دی', 'بانک پارسیان', 'بانک کارآفرین', 'بانک سامان', | |
| 'بانک اقتصاد نوین', 'بانک مهر اقتصاد', 'بانک آینده' | |
| ] | |
| # فهرست سازمانهای دولتی | |
| self.government_orgs = [ | |
| 'بانک مرکزی جمهوری اسلامی ایران', | |
| 'دفتر اسناد رسمی', | |
| 'اداره کل مالیات', | |
| 'تامین اجتماعی', | |
| 'وزارت دادگستری' | |
| ] | |
| # کلمات عمومی که نباید entity محسوب شوند | |
| self.generic_words = { | |
| 'همین بانک', 'این بانک', 'آن بانک', 'بانک مذکور', | |
| 'همین شرکت', 'این شرکت', 'آن شرکت', 'شرکت مذکور', | |
| 'همین شعبه', 'این شعبه', 'آن شعبه', 'شعبه مذکور', | |
| 'شرکت متقاضی', 'دفتر حسابداری شرکت', | |
| 'متقاضی', 'ایشان', 'وی', 'مشتری' | |
| } | |
| # کلمات حذفی | |
| self.remove_words = [ | |
| 'در', 'که', 'با', 'به', 'از', 'را', 'و', 'یا', 'است', 'بوده', | |
| 'نموده', 'صادر', 'ارائه', 'معرفی', 'برگزار', 'واقع', 'مربوط', | |
| 'مطرح', 'شد', 'شده' | |
| ] | |
| self.patterns = { | |
| 'person_names': { | |
| 'pattern': re.compile(r'(?:آقای|خانم|مهندس|دکتر)\s+[\u0600-\u06FF\s]+?(?=\s+با\s+کد|\s+مدیر|$|،|\.)', re.UNICODE), | |
| 'replacement': re.compile(r'person_\d+'), | |
| 'name': 'اسامی اشخاص' | |
| }, | |
| 'national_ids': { | |
| 'pattern': re.compile(r'(?<!09)(?<!021-)(?<![0-9])\d{10,11}(?![0-9])'), | |
| 'replacement': re.compile(r'id_number_\d+'), | |
| 'name': 'کدهای ملی' | |
| }, | |
| 'phone_numbers': { | |
| 'pattern': re.compile(r'(?:09\d{9}|021-\d{8}|0\d{2,3}-?\d{7,8})'), | |
| 'replacement': re.compile(r'phone_\d+'), | |
| 'name': 'شماره تلفنها' | |
| }, | |
| 'account_numbers': { | |
| 'pattern': re.compile(r'\b\d{3}-\d{3}-\d{3}-\d{1}\b'), | |
| 'replacement': re.compile(r'account_\d+'), | |
| 'name': 'شماره حسابها' | |
| }, | |
| 'card_numbers': { | |
| 'pattern': re.compile(r'\b\d{4}-\d{4}-\d{4}-\d{4}\b'), | |
| 'replacement': re.compile(r'card_number_\d+'), | |
| 'name': 'شماره کارتها' | |
| }, | |
| 'amounts': { | |
| 'pattern': re.compile(r'\d{6,}\s*تومان'), | |
| 'replacement': re.compile(r'amount_\d+'), | |
| 'name': 'مبالغ مالی' | |
| }, | |
| 'dates': { | |
| 'pattern': re.compile(r'(?:\d{4}\/\d{2}\/\d{2}|۳۰\s*اسفند\s*۱۴۰۳|\b\d{4}\b(?=\s*سال))'), | |
| 'replacement': re.compile(r'date_\d+'), | |
| 'name': 'تاریخها' | |
| }, | |
| 'full_addresses': { | |
| 'pattern': re.compile(r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرمآباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجفآباد|شاهینشهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نیریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلامشهر|ملارد|قدس|بهارستان|چهاردانگه),\s*(?:میدان|خیابان|کوچه|شهرک|بلوار|کوی|محله)\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\s]+(?:،\s*(?:برج|ساختمان|مجتمع)\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\s]+)?(?:،\s*(?:طبقه|واحد)\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\d\s]+)?', re.UNICODE), | |
| 'replacement': re.compile(r'(?:full_address_\d+|location_\d+)'), | |
| 'name': 'آدرسهای کامل' | |
| }, | |
| 'iranian_banks': { | |
| 'pattern': re.compile(f"({'|'.join(re.escape(bank) for bank in self.iranian_banks)})", re.UNICODE), | |
| 'replacement': re.compile(r'company_\d+'), | |
| 'name': 'بانکهای ایران' | |
| }, | |
| 'government_orgs': { | |
| 'pattern': re.compile(f"({'|'.join(re.escape(org) for org in self.government_orgs)})", re.UNICODE), | |
| 'replacement': re.compile(r'company_\d+'), | |
| 'name': 'سازمانهای دولتی' | |
| }, | |
| 'other_companies': { | |
| 'pattern': re.compile(r'شرکت\s+[\u0600-\u06FF\u200C\u0621\u06F0-\u06F9\s]+?(?=\s|$|،|\.)', re.UNICODE), | |
| 'replacement': re.compile(r'company_\d+'), | |
| 'name': 'سایر شرکتها' | |
| }, | |
| 'invoice_numbers': { | |
| 'pattern': re.compile(r'(?:INV-\d{4}-\d{4}|RPT-\d{4}-\d{4})'), | |
| 'replacement': re.compile(r'(?:invoice_\d+|report_\d+)'), | |
| 'name': 'شماره فاکتور و گزارش' | |
| }, | |
| 'document_offices': { | |
| 'pattern': re.compile(r'دفتر\s+اسناد\s+رسمی\s+شماره\s+\d+'), | |
| 'replacement': re.compile(r'(?:contract_\d+|office_\d+)'), | |
| 'name': 'دفاتر اسناد رسمی' | |
| }, | |
| 'cheque_numbers': { | |
| 'pattern': re.compile(r'چک\s+شماره\s+\d+'), | |
| 'replacement': re.compile(r'cheque_\d+'), | |
| 'name': 'شماره چکها' | |
| } | |
| } | |
| def is_generic_word(self, text): | |
| """بررسی کلمات عمومی که نباید entity محسوب شوند""" | |
| text_clean = text.strip().lower() | |
| if text_clean in self.generic_words: | |
| return True | |
| if text_clean.startswith(('همین ', 'این ', 'آن ')): | |
| return True | |
| return len(text.strip()) < 3 | |
| def clean_entity(self, text): | |
| """تمیز کردن دقیق entity""" | |
| # ساخت الگو برای حذف کلمات اضافی | |
| pattern = r'\s*(' + '|'.join(re.escape(word) for word in self.remove_words) + r')\s*$' | |
| text = re.sub(pattern, '', text, flags=re.IGNORECASE) | |
| text = re.sub(r'\s+', ' ', text).strip() | |
| return text | |
| def is_valid_entity(self, text, category): | |
| """بررسی دقیقتر معتبر بودن entity""" | |
| if len(text) < 3 or len(text) > 100: | |
| return False | |
| if self.is_generic_word(text): | |
| return False | |
| forbidden_words = [ | |
| 'شد', 'کرد', 'است', 'بود', 'در', 'که', 'با', 'از', 'به', 'را', 'و', 'یا', | |
| 'شده', 'نموده', 'صادر', 'ارائه', 'معرفی', 'برگزار', 'مطرح', 'واقع' | |
| ] | |
| if text.lower().strip() in forbidden_words: | |
| return False | |
| if category in ['iranian_banks', 'other_companies']: | |
| if any(word in text.lower() for word in ['برگزار', 'مطرح', 'شد', 'است', 'نموده']): | |
| return False | |
| if text.strip() in ['شرکت', 'بانک', 'شرکت در', 'بانک در']: | |
| return False | |
| elif category == 'person_names': | |
| if text.strip() in ['آقای', 'خانم', 'مهندس', 'دکتر']: | |
| return False | |
| return True | |
| def analyze_entities(self, original_text, anonymized_text): | |
| """تحلیل موجودیتهای شناسایی شده و ناشناس شده""" | |
| results = {} | |
| for entity_type, config in self.patterns.items(): | |
| original_matches = config['pattern'].findall(original_text) | |
| replacement_matches = config['replacement'].findall(anonymized_text) | |
| # تمیز کردن و فیلتر کردن entities | |
| clean_original = [] | |
| for match in original_matches: | |
| cleaned = self.clean_entity(match) | |
| if self.is_valid_entity(cleaned, entity_type): | |
| clean_original.append(cleaned) | |
| # محاسبه تعداد موجودیتهای ناشناس شده | |
| anonymized_count = 0 | |
| for entity in clean_original: | |
| if not anonymized_text.count(entity.strip()): | |
| anonymized_count += 1 | |
| # اگر تعداد جایگزینها بیشتر باشد | |
| if len(replacement_matches) > anonymized_count: | |
| anonymized_count = min(len(replacement_matches), len(clean_original)) | |
| percentage = (anonymized_count / len(clean_original) * 100) if clean_original else 0 | |
| results[entity_type] = { | |
| 'name': config['name'], | |
| 'total': len(clean_original), | |
| 'anonymized': anonymized_count, | |
| 'percentage': round(percentage, 1), | |
| 'samples': clean_original[:3] if clean_original else [] | |
| } | |
| return results | |
| def evaluate_csv(self, csv_file): | |
| """ارزیابی فایل CSV و تولید گزارش""" | |
| try: | |
| if csv_file is None: | |
| return "لطفاً یک فایل CSV آپلود کنید." | |
| try: | |
| df = pd.read_csv(csv_file.name, encoding='utf-8') | |
| except: | |
| try: | |
| df = pd.read_csv(csv_file.name, encoding='utf-8', sep='\t') | |
| except: | |
| df = pd.read_csv(csv_file.name, encoding='utf-8-sig') | |
| if 'original_text' not in df.columns or 'anonymized_text' not in df.columns: | |
| return "فایل CSV باید شامل ستونهای 'original_text' و 'anonymized_text' باشد." | |
| overall_stats = {} | |
| total_entities = 0 | |
| total_anonymized = 0 | |
| for _, row in df.iterrows(): | |
| if pd.isna(row['original_text']) or pd.isna(row['anonymized_text']): | |
| continue | |
| row_analysis = self.analyze_entities(str(row['original_text']), str(row['anonymized_text'])) | |
| for entity_type, data in row_analysis.items(): | |
| if entity_type not in overall_stats: | |
| overall_stats[entity_type] = { | |
| 'name': data['name'], | |
| 'total': 0, | |
| 'anonymized': 0, | |
| 'samples': [] | |
| } | |
| overall_stats[entity_type]['total'] += data['total'] | |
| overall_stats[entity_type]['anonymized'] += data['anonymized'] | |
| overall_stats[entity_type]['samples'].extend(data['samples']) | |
| total_entities += data['total'] | |
| total_anonymized += data['anonymized'] | |
| for entity_type in overall_stats: | |
| stats = overall_stats[entity_type] | |
| stats['percentage'] = round((stats['anonymized'] / stats['total'] * 100) if stats['total'] > 0 else 0, 1) | |
| stats['samples'] = list(set(stats['samples']))[:3] | |
| return self.generate_report(overall_stats, total_entities, total_anonymized, len(df)) | |
| except Exception as e: | |
| return f"خطا در پردازش فایل: {str(e)}" | |
| def generate_report(self, stats, total_entities, total_anonymized, total_rows): | |
| """تولید گزارش کامل متریکها""" | |
| report = f"""# گزارش ارزیابی ناشناسسازی متن (نسخه بهبود یافته) | |
| ## خلاصه کلی | |
| - **تعداد ردیفهای پردازش شده**: {total_rows:,} ردیف | |
| - **تعداد موجودیتهای حساس شناسایی شده**: {total_entities:,} مورد | |
| - **تعداد موجودیتهای ناشناس شده**: {total_anonymized:,} مورد | |
| - **درصد پوشش کلی**: {(total_anonymized/total_entities*100) if total_entities > 0 else 0:.1f}% | |
| ## بهبودهای اعمال شده | |
| ✅ **آدرسهای کامل**: شناسایی دقیق آدرسها شامل میدان، برج، طبقه و واحد | |
| ✅ **اسامی کامل**: پشتیبانی از خانم، مهندس، دکتر | |
| ✅ **شرکتهای پیچیده**: شناسایی "شرکت پردازش دادههای ایرانیان" | |
| ✅ **جداسازی دقیق**: کد ملی از شماره تلفن جدا شده | |
| ✅ **فیلتر هوشمند**: حذف "همین بانک"، "شرکت متقاضی" | |
| ✅ **بانکهای ایران**: فهرست کامل 15 بانک اصلی | |
| ✅ **سازمانهای دولتی**: شناسایی نهادهای رسمی | |
| ✅ **اسناد و فاکتورها**: شماره فاکتور، چک، دفتر اسناد | |
| ## تحلیل تفصیلی دستهبندی موجودیتها | |
| """ | |
| excellent = [] | |
| good = [] | |
| poor = [] | |
| not_found = [] | |
| for entity_type, data in stats.items(): | |
| if data['total'] == 0: | |
| not_found.append((entity_type, data)) | |
| elif data['percentage'] == 100: | |
| excellent.append((entity_type, data)) | |
| elif data['percentage'] >= 80: | |
| good.append((entity_type, data)) | |
| else: | |
| poor.append((entity_type, data)) | |
| if excellent: | |
| report += "### ✅ عملکرد عالی (100% موفقیت)\n" | |
| for entity_type, data in excellent: | |
| report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} (100%)\n" | |
| report += "\n" | |
| if good: | |
| report += "### 🟡 عملکرد خوب (80-99% موفقیت)\n" | |
| for entity_type, data in good: | |
| report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} ({data['percentage']}%)\n" | |
| report += "\n" | |
| if poor: | |
| report += "### 🔴 عملکرد ضعیف (<80% موفقیت)\n" | |
| for entity_type, data in poor: | |
| missed = data['total'] - data['anonymized'] | |
| report += f"- **{data['name']}**: {data['anonymized']}/{data['total']} ({data['percentage']}%) - {missed} مورد جا مانده\n" | |
| if data['samples']: | |
| report += f" نمونههای شناسایی شده: {', '.join(data['samples'][:2])}\n" | |
| report += "\n" | |
| if not_found: | |
| report += "### ⚪ موجودیتهای یافت نشده\n" | |
| for entity_type, data in not_found: | |
| report += f"- **{data['name']}**: هیچ موجودیتی یافت نشد\n" | |
| report += "\n" | |
| report += "## جدول خلاصه متریکها\n\n" | |
| report += "| دسته موجودیت | یافته شده | ناشناس شده | درصد موفقیت | موارد جا مانده |\n" | |
| report += "|---------------|-----------|-------------|-------------|----------------|\n" | |
| for entity_type, data in stats.items(): | |
| if data['total'] > 0: | |
| missed = data['total'] - data['anonymized'] | |
| report += f"| {data['name']} | {data['total']} | {data['anonymized']} | {data['percentage']}% | {missed} |\n" | |
| major_issues = [(k, v) for k, v in stats.items() if v['total'] > 0 and v['percentage'] < 80] | |
| major_issues.sort(key=lambda x: x[1]['total'] - x[1]['anonymized'], reverse=True) | |
| if major_issues: | |
| report += "\n## 🚨 مشکلات اصلی شناسایی شده\n\n" | |
| for i, (entity_type, data) in enumerate(major_issues, 1): | |
| missed = data['total'] - data['anonymized'] | |
| impact = round(missed / total_entities * 100, 1) if total_entities > 0 else 0 | |
| report += f"### {i}. {data['name']}\n" | |
| report += f"- **وضعیت**: {data['percentage']}% موفقیت\n" | |
| report += f"- **موارد جا مانده**: {missed} مورد از {data['total']} مورد\n" | |
| report += f"- **تاثیر بر کل**: {impact}% از کل موجودیتها\n" | |
| if data['samples']: | |
| report += f"- **نمونهها**: {', '.join(data['samples'][:3])}\n" | |
| report += "\n" | |
| precision = round((total_anonymized / total_entities * 100) if total_entities > 0 else 0, 1) | |
| report += f"""## 📊 آمار نهایی | |
| - **کل موجودیتهای شناسایی شده**: {total_entities:,} | |
| - **کل موجودیتهای ناشناس شده**: {total_anonymized:,} | |
| - **موجودیتهای جا مانده**: {total_entities - total_anonymized:,} | |
| - **دقت (Precision)**: {precision}% | |
| - **پوشش (Recall)**: {precision}% | |
| - **امتیاز F1**: {precision}% | |
| ## 🆕 ویژگیهای جدید اعمال شده | |
| - الگوهای regex بهبود یافته و دقیقتر | |
| - فیلتر کلمات عمومی و غیرمعنادار | |
| - شناسایی آدرسهای کامل با جزئیات | |
| - تشخیص اسامی با عناوین مختلف | |
| - جداسازی دقیق انواع موجودیتها | |
| - پشتیبانی از شرکتهای پیچیده | |
| - شناسایی اسناد و فاکتورها | |
| """ | |
| return report | |
| def create_interface(): | |
| evaluator = ImprovedAnonymizationEvaluator() | |
| def process_file(csv_file): | |
| if csv_file is None: | |
| return "لطفاً یک فایل CSV آپلود کنید." | |
| return evaluator.evaluate_csv(csv_file) | |
| with gr.Blocks(title="ارزیاب ناشناسسازی بهبود یافته") as demo: | |
| gr.Markdown(""" | |
| # 📊 ارزیاب ناشناسسازی متن فارسی (نسخه بهبود یافته) | |
| ## 🆕 ویژگیهای جدید: | |
| - **آدرسهای کامل**: تهران، میدان ونک، برج میلاد، طبقه 15، واحد 1503 | |
| - **اسامی کامل**: خانم زهرا احمدی، مدیر مالی خانم احمدی | |
| - **شرکتهای پیچیده**: شرکت پردازش دادههای ایرانیان | |
| - **فیلتر هوشمند**: حذف "همین بانک"، "شرکت متقاضی" | |
| - **جداسازی دقیق**: کد ملی از شماره تلفن | |
| - **بانکهای ایران**: 15 بانک اصلی شناسایی میشود | |
| - **سازمانهای دولتی**: بانک مرکزی، دفتر اسناد رسمی | |
| - **اسناد**: شماره فاکتور، چک، دفتر اسناد | |
| """) | |
| file_input = gr.File(label="آپلود فایل CSV", file_types=[".csv"]) | |
| analyze_btn = gr.Button("محاسبه متریکها (نسخه بهبود یافته)", variant="primary") | |
| output = gr.Markdown(value="فایل CSV خود را آپلود کنید.") | |
| analyze_btn.click(fn=process_file, inputs=[file_input], outputs=[output]) | |
| return demo | |
| if __name__ == "__main__": | |
| app = create_interface() | |
| app.launch() |