leilaghomashchi/Benchmark-data-anonymization
0
1import pandas as pd2import numpy as np3import re4from typing import Dict, List, Tuple, Set5import gradio as gr6from datetime import datetime7import io8import tempfile9import os10 11class AnonymizationEvaluator:12 """ابزار ارزیابی ناشناسسازی با استفاده از متن مرجع"""13 14 def __init__(self):15 self.results_df = None16 17 def extract_entity_types_from_text(self, text: str) -> Dict[str, int]:18 """استخراج انواع موجودیتها با پشتیبانی از فرمتهای مختلف"""19 if pd.isna(text) or not isinstance(text, str):20 return {'companies': 0, 'persons': 0, 'amounts': 0, 'percents': 0, 'groups': 0, 'stocks': 0}21 22 # تمیز کردن متن23 text = str(text).strip()24 25 # الگوهای برای فرمتهای مختلف26 patterns = {27 'companies': [28 r'company-\d+', r'Company-\d+', r'COMPANY-\d+', # فرمت استاندارد29 r'COMPANY_\d+(?:_[A-Z]+)?', r'company_\d+(?:_[a-z]+)?' # فرمت regex30 ],31 'persons': [32 r'person-\d+', r'Person-\d+', r'PERSON-\d+',33 r'PERSON_\d+(?:_[A-Z]+)?', r'person_\d+(?:_[a-z]+)?'34 ],35 'amounts': [36 r'amount-\d+', r'Amount-\d+', r'AMOUNT-\d+',37 r'AMOUNT_\d+(?:_[A-Z]+)?', r'amount_\d+(?:_[a-z]+)?'38 ],39 'percents': [40 r'percent-\d+', r'Percent-\d+', r'PERCENT-\d+',41 r'PERCENT_\d+(?:_[A-Z]+)?', r'percent_\d+(?:_[a-z]+)?'42 ],43 'groups': [44 r'group-\d+', r'Group-\d+', r'GROUP-\d+',45 r'GROUP_\d+(?:_[A-Z]+)?', r'group_\d+(?:_[a-z]+)?'46 ],47 'stocks': [48 r'stock-\d+', r'Stock-\d+', r'STOCK-\d+',49 r'STOCK_SYMBOL_\d+(?:_[A-Z]+)?', r'stock_symbol_\d+(?:_[a-z]+)?'50 ]51 }52 53 entity_counts = {}54 for entity_type, pattern_list in patterns.items():55 count = 056 for pattern in pattern_list:57 matches = re.findall(pattern, text)58 count += len(matches)59 entity_counts[entity_type] = count60 61 return entity_counts62 63 def normalize_entity_format(self, text: str) -> str:64 """نرمالسازی فرمتهای مختلف به یک فرمت استاندارد"""65 if pd.isna(text) or not isinstance(text, str):66 return ""67 68 # نگاشت انواع مختلف به نام استاندارد69 replacements = [70 # فرمت regex به استاندارد71 (r'COMPANY_(\d+)(?:_[A-Z]+)?', r'company-\1'),72 (r'PERSON_(\d+)(?:_[A-Z]+)?', r'person-\1'),73 (r'AMOUNT_(\d+)(?:_[A-Z]+)?', r'amount-\1'),74 (r'PERCENT_(\d+)(?:_[A-Z]+)?', r'percent-\1'),75 (r'GROUP_(\d+)(?:_[A-Z]+)?', r'group-\1'),76 (r'STOCK_SYMBOL_(\d+)(?:_[A-Z]+)?', r'stock-\1'),77 78 # تبدیل حروف بزرگ به کوچک79 (r'Company-(\d+)', r'company-\1'),80 (r'Person-(\d+)', r'person-\1'),81 (r'Amount-(\d+)', r'amount-\1'),82 (r'Percent-(\d+)', r'percent-\1'),83 (r'Group-(\d+)', r'group-\1'),84 ]85 86 normalized_text = text87 for pattern, replacement in replacements:88 normalized_text = re.sub(pattern, replacement, normalized_text)89 90 return normalized_text91 92 def calculate_precision_recall_f1_by_type(self, reference_counts: Dict[str, int], 93 predicted_counts: Dict[str, int]) -> Tuple[float, float, float]:94 """محاسبه Precision, Recall و F1-Score بر اساس نوع موجودیتها"""95 96 # مجموع کل موجودیتها97 total_reference = sum(reference_counts.values())98 total_predicted = sum(predicted_counts.values())99 100 if total_predicted == 0 and total_reference == 0:101 return 1.0, 1.0, 1.0 # هر دو خالی هستند - تطبیق کامل102 elif total_predicted == 0:103 return 0.0, 0.0, 0.0 # predicted خالی ولی reference دارد104 elif total_reference == 0:105 return 0.0, 1.0 if total_predicted > 0 else 1.0, 0.0 # reference خالی106 107 # محاسبه True Positive برای هر نوع موجودیت108 true_positives = 0109 for entity_type in reference_counts.keys():110 ref_count = reference_counts[entity_type]111 pred_count = predicted_counts[entity_type]112 # True Positive = کمترین تعداد بین reference و predicted برای هر نوع113 true_positives += min(ref_count, pred_count)114 115 # محاسبه Precision و Recall116 precision = true_positives / total_predicted if total_predicted > 0 else 0.0117 recall = true_positives / total_reference if total_reference > 0 else 0.0118 119 # محاسبه F1-Score120 if precision + recall == 0:121 f1 = 0.0122 else:123 f1 = 2 * (precision * recall) / (precision + recall)124 125 return precision, recall, f1126 127 def evaluate_single_row(self, reference_text: str, predicted_text: str) -> Tuple[float, float, float]:128 """ارزیابی یک سطر بر اساس نوع موجودیتها"""129 try:130 # استخراج تعداد موجودیتها بر اساس نوع131 ref_counts = self.extract_entity_types_from_text(reference_text)132 pred_counts = self.extract_entity_types_from_text(predicted_text)133 134 # محاسبه متریکها135 precision, recall, f1 = self.calculate_precision_recall_f1_by_type(ref_counts, pred_counts)136 137 return precision, recall, f1138 139 except Exception as e:140 print(f"خطا در ارزیابی: {str(e)}")141 return 0.0, 0.0, 0.0142 143 def debug_text_analysis(self, reference_text: str, predicted_text: str, row_num: int = 0) -> str:144 """تابع debugging برای تحلیل متنها بر اساس نوع موجودیتها"""145 debug_info = f"\n--- Debug Row {row_num + 1} ---\n"146 debug_info += f"Reference: '{reference_text[:100]}...'\n"147 debug_info += f"Predicted: '{predicted_text[:100]}...'\n"148 149 ref_counts = self.extract_entity_types_from_text(reference_text)150 pred_counts = self.extract_entity_types_from_text(predicted_text)151 152 debug_info += f"Reference entity counts: {ref_counts}\n"153 debug_info += f"Predicted entity counts: {pred_counts}\n"154 155 # محاسبه متریکها برای این سطر156 precision, recall, f1 = self.calculate_precision_recall_f1_by_type(ref_counts, pred_counts)157 debug_info += f"Metrics: P={precision:.3f}, R={recall:.3f}, F1={f1:.3f}\n"158 159 return debug_info160 161 def evaluate_dataset(self, file_path: str) -> Tuple[bool, str, pd.DataFrame]:162 """ارزیابی کل دیتاست با پشتیبانی از فرمتهای مختلف"""163 try:164 # بارگذاری فایل165 df = pd.read_csv(file_path)166 167 # بررسی ستونها - پشتیبانی از دو حالت168 if 'Reference_text' in df.columns and 'anonymized_text' in df.columns:169 # حالت سه ستونه (فرمت قبلی)170 required_columns = ['original_text', 'Reference_text', 'anonymized_text']171 missing_columns = [col for col in required_columns if col not in df.columns]172 173 if missing_columns:174 return False, f"ستونهای مفقود: {', '.join(missing_columns)}", pd.DataFrame()175 176 reference_col = 'Reference_text'177 predicted_col = 'anonymized_text'178 179 elif 'original_text' in df.columns and 'anonymized_text' in df.columns:180 # حالت دو ستونه جدید - فرض: original_text مرجع است181 reference_col = 'original_text' # متن اصلی182 predicted_col = 'anonymized_text' # متن ناشناسشده با regex183 184 else:185 return False, "فایل باید شامل ستونهای 'original_text' و 'anonymized_text' باشد", pd.DataFrame()186 187 # تشخیص مشکل - بررسی نمونهای از دادهها188 debug_info = "\n=== ارزیابی با پشتیبانی فرمتهای مختلف ===\n"189 debug_info += f"تعداد سطرها: {len(df)}\n"190 debug_info += f"ستونها: {list(df.columns)}\n"191 debug_info += f"مرجع: {reference_col}, پیشبینی: {predicted_col}\n\n"192 193 # بررسی چند سطر اول194 for i in range(min(3, len(df))):195 ref_text = str(df.iloc[i][reference_col])196 pred_text = str(df.iloc[i][predicted_col])197 198 debug_info += self.debug_text_analysis(ref_text, pred_text, i)199 200 # نمایش نرمالسازی201 normalized_ref = self.normalize_entity_format(ref_text)202 normalized_pred = self.normalize_entity_format(pred_text)203 204 if normalized_ref != ref_text or normalized_pred != pred_text:205 debug_info += f" نرمالسازی مرجع: '{normalized_ref[:50]}...'\n"206 debug_info += f" نرمالسازی پیشبینی: '{normalized_pred[:50]}...'\n"207 208 print(debug_info) # نمایش در console209 210 # محاسبه متریکها برای هر سطر211 precisions = []212 recalls = []213 f1_scores = []214 215 total_entity_types_found = 0216 217 for index, row in df.iterrows():218 precision, recall, f1 = self.evaluate_single_row(219 row[reference_col], 220 row[predicted_col]221 )222 223 precisions.append(round(precision, 4))224 recalls.append(round(recall, 4))225 f1_scores.append(round(f1, 4))226 227 # شمارش انواع موجودیتها برای debugging228 ref_counts = self.extract_entity_types_from_text(str(row[reference_col]))229 pred_counts = self.extract_entity_types_from_text(str(row[predicted_col]))230 total_entity_types_found += sum(ref_counts.values()) + sum(pred_counts.values())231 232 # اضافه کردن ستونهای جدید233 df['Precision'] = precisions234 df['Recall'] = recalls235 df['F1_Score'] = f1_scores236 237 # ذخیره نتایج238 self.results_df = df239 240 # پیام وضعیت شامل اطلاعات debugging241 avg_precision = np.mean(precisions)242 avg_recall = np.mean(recalls)243 avg_f1 = np.mean(f1_scores)244 245 status_message = f"""ارزیابی انجام شد (مرجع: {reference_col}):246• میانگین Precision: {avg_precision:.3f}247• میانگین Recall: {avg_recall:.3f} 248• میانگین F1-Score: {avg_f1:.3f}249• کل موجودیتهای یافت شده: {total_entity_types_found}250• پشتیبانی فرمت REGEX اضافه شد"""251 252 if total_entity_types_found == 0:253 status_message += "\n⚠️ هیچ موجودیتی تشخیص داده نشد!"254 255 return True, status_message, df256 257 except Exception as e:258 return False, f"خطا در پردازش فایل: {str(e)}", pd.DataFrame()259 260 def generate_summary_report(self, df: pd.DataFrame) -> str:261 """تولید گزارش خلاصه"""262 if df.empty:263 return "هیچ دادهای برای گزارش یافت نشد"264 265 # محاسبه آمار کلی266 avg_precision = df['Precision'].mean()267 avg_recall = df['Recall'].mean() 268 avg_f1 = df['F1_Score'].mean()269 270 # محاسبه آمار تفصیلی271 total_rows = len(df)272 high_precision_count = len(df[df['Precision'] >= 0.8])273 high_recall_count = len(df[df['Recall'] >= 0.8])274 high_f1_count = len(df[df['F1_Score'] >= 0.8])275 276 # بهترین و بدترین نتایج277 best_f1_idx = df['F1_Score'].idxmax()278 worst_f1_idx = df['F1_Score'].idxmin()279 280 report = f"""281 ## 📊 گزارش جامع ارزیابی282 283 ### آمار کلی:284 - **تعداد کل سطرها:** {total_rows}285 - **میانگین Precision:** {avg_precision:.4f}286 - **میانگین Recall:** {avg_recall:.4f} 287 - **میانگین F1-Score:** {avg_f1:.4f}288 289 ### توزیع عملکرد (امتیاز ≥ 0.8):290 - **Precision بالا:** {high_precision_count} سطر ({high_precision_count/total_rows*100:.1f}%)291 - **Recall بالا:** {high_recall_count} سطر ({high_recall_count/total_rows*100:.1f}%)292 - **F1-Score بالا:** {high_f1_count} سطر ({high_f1_count/total_rows*100:.1f}%)293 294 ### نمونههای برتر و ضعیف:295 - **بهترین F1-Score:** {df.loc[best_f1_idx, 'F1_Score']:.4f} (سطر {best_f1_idx + 1})296 - **ضعیفترین F1-Score:** {df.loc[worst_f1_idx, 'F1_Score']:.4f} (سطر {worst_f1_idx + 1})297 """298 299 return report300 301 def create_downloadable_csv(self) -> bytes:302 """ایجاد محتوای CSV برای دانلود مستقیم"""303 if self.results_df is None or self.results_df.empty:304 return None305 306 try:307 # تولید محتوای CSV در حافظه308 csv_buffer = io.StringIO()309 self.results_df.to_csv(csv_buffer, index=False, encoding='utf-8')310 csv_content = csv_buffer.getvalue()311 csv_buffer.close()312 313 # تبدیل به bytes برای دانلود314 return csv_content.encode('utf-8-sig')315 316 except Exception as e:317 print(f"خطا در ایجاد محتوای CSV: {str(e)}")318 return None319 320def create_evaluation_interface():321 """ایجاد رابط کاربری ارزیابی"""322 evaluator = AnonymizationEvaluator()323 324 with gr.Blocks(325 title="ارزیابی ناشناسسازی",326 theme=gr.themes.Soft(),327 css="""328 .gradio-container {329 font-family: 'Tahoma', 'Arial', sans-serif !important;330 direction: rtl;331 max-width: 1200px;332 margin: 0 auto;333 }334 .upload-area {335 border: 2px dashed #4CAF50;336 border-radius: 15px;337 padding: 30px;338 text-align: center;339 background: linear-gradient(145deg, #f8f9fa, #e9ecef);340 margin: 20px 0;341 }342 .results-table {343 direction: ltr;344 font-family: monospace;345 font-size: 12px;346 }347 .summary-box {348 background-color: #e3f2fd;349 border: 1px solid #2196F3;350 border-radius: 10px;351 padding: 20px;352 margin: 15px 0;353 }354 """355 ) as interface:356 357 gr.Markdown("""358 # 📊 ابزار ارزیابی ناشناسسازی با پشتیبانی فرمتهای مختلف359 ### پشتیبانی از فرمتهای استاندارد و REGEX360 """)361 362 with gr.Row():363 with gr.Column(scale=1):364 gr.Markdown("### 📁 بارگذاری فایل")365 366 file_input = gr.File(367 label="انتخاب فایل CSV (2 یا 3 ستون)",368 file_types=[".csv"],369 elem_classes=["upload-area"]370 )371 372 evaluate_btn = gr.Button(373 "🚀 شروع ارزیابی",374 variant="primary",375 size="lg",376 interactive=False377 )378 379 download_btn = gr.Button(380 "💾 دانلود نتایج CSV",381 variant="secondary",382 visible=False383 )384 385 with gr.Column(scale=2):386 status_output = gr.Markdown("وضعیت: آماده بارگذاری فایل...")387 388 summary_output = gr.Markdown(389 visible=False,390 elem_classes=["summary-box"]391 )392 393 # جدول نتایج394 results_table = gr.Dataframe(395 label="نتایج تفصیلی (نمایش 10 سطر اول)",396 visible=False,397 elem_classes=["results-table"],398 wrap=True399 )400 401 # فایل دانلود402 download_file = gr.File(403 visible=False,404 label="فایل نتایج"405 )406 407 def on_file_upload(file):408 if file is None:409 return "❌ لطفاً فایل را انتخاب کنید", gr.Button(interactive=False)410 411 return "✅ فایل بارگذاری شد، آماده ارزیابی", gr.Button(interactive=True)412 413 def evaluate_file(file):414 if file is None:415 return (416 "❌ هیچ فایلی انتخاب نشده",417 gr.Markdown(visible=False),418 gr.Dataframe(visible=False),419 gr.Button(visible=False),420 gr.File(visible=False)421 )422 423 try:424 success, message, df = evaluator.evaluate_dataset(file.name)425 426 if not success:427 return (428 f"❌ {message}",429 gr.Markdown(visible=False),430 gr.Dataframe(visible=False),431 gr.Button(visible=False),432 gr.File(visible=False)433 )434 435 # تولید گزارش خلاصه436 summary = evaluator.generate_summary_report(df)437 438 # نمایش 10 سطر اول برای نمونه در رابط439 display_df = df.head(10)440 441 # پیام اطلاعرسانی442 status_message = f"✅ {message} - {len(df)} سطر پردازش شد. نمایش: 10 سطر اول، دانلود: همه سطرها"443 444 return (445 status_message,446 gr.Markdown(value=summary, visible=True),447 gr.Dataframe(value=display_df, visible=True),448 gr.Button(visible=True),449 gr.File(visible=False)450 )451 452 except Exception as e:453 return (454 f"❌ خطای غیرمنتظره: {str(e)}",455 gr.Markdown(visible=False),456 gr.Dataframe(visible=False),457 gr.Button(visible=False),458 gr.File(visible=False)459 )460 461 def download_results():462 try:463 if evaluator.results_df is None or evaluator.results_df.empty:464 return (465 "❌ هیچ دادهای برای دانلود وجود ندارد. ابتدا ارزیابی را انجام دهید.",466 gr.File(visible=False)467 )468 469 # ایجاد محتوای CSV470 csv_content = evaluator.create_downloadable_csv()471 if csv_content:472 # تولید نام فایل473 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")474 filename = f"evaluation_results_{timestamp}.csv"475 476 # ذخیره در فایل موقت برای دانلود477 with tempfile.NamedTemporaryFile(mode='wb', delete=False, 478 suffix='.csv', prefix='eval_') as temp_file:479 temp_file.write(csv_content)480 temp_filename = temp_file.name481 482 return (483 f"✅ فایل نتایج آماده شد: {filename} ({len(evaluator.results_df)} سطر)",484 gr.File(value=temp_filename, visible=True)485 )486 else:487 return (488 "❌ خطا در ایجاد محتوای CSV",489 gr.File(visible=False)490 )491 except Exception as e:492 return (493 f"❌ خطا در دانلود: {str(e)}",494 gr.File(visible=False)495 )496 497 # اتصال رویدادها498 file_input.change(499 fn=on_file_upload,500 inputs=[file_input],501 outputs=[status_output, evaluate_btn]502 )503 504 evaluate_btn.click(505 fn=evaluate_file,506 inputs=[file_input],507 outputs=[status_output, summary_output, results_table, download_btn, download_file]508 )509 510 download_btn.click(511 fn=download_results,512 outputs=[status_output, download_file]513 )514 515 # راهنمای استفاده516 with gr.Accordion("📖 راهنمای استفاده", open=False):517 gr.Markdown("""518 ### فرمتهای پشتیبانی شده:519 520 **حالت 1: فایل 3 ستونه (مرجع و پیشبینی جداگانه)**521 - original_text: متن اصلی522 - Reference_text: متن مرجع ناشناسشده 523 - anonymized_text: متن پیشبینی شده524 525 **حالت 2: فایل 2 ستونه (مقایسه با اصل)**526 - original_text: متن اصلی (مرجع)527 - anonymized_text: متن ناشناسشده (پیشبینی)528 529 ### انواع فرمتهای موجودیت پشتیبانی شده:530 531 **فرمت استاندارد:**532 - company-01, person-02, amount-03533 - Company-01, Person-02, Amount-03534 - COMPANY-01, PERSON-02, AMOUNT-03535 536 **فرمت REGEX:**537 - COMPANY_001_REGEX, PERSON_002_REGEX538 - AMOUNT_012, PERCENT_025539 - STOCK_SYMBOL_001_REGEX540 541 ### نرمالسازی خودکار:542 543 برنامه خودکار انواع مختلف را تشخیص داده و نرمالسازی میکند:544 - COMPANY_001_REGEX → company-001545 - AMOUNT_012 → amount-012546 - STOCK_SYMBOL_005_REGEX → stock-005547 548 ### متریکهای محاسبه شده:549 550 - **Precision**: موجودیتهای درست / کل شناساییشده551 - **Recall**: موجودیتهای درست / کل مرجع552 - **F1-Score**: میانگین هارمونیک Precision و Recall553 554 ### مراحل کار:555 556 1. فایل CSV را آپلود کنید (2 یا 3 ستون)557 2. روی "شروع ارزیابی" کلیک کنید558 3. گزارش و نتایج را مشاهده کنید559 4. فایل نتایج کامل را دانلود کنید560 561 ### نکات:562 563 - برنامه خودکار تشخیص میدهد فایل 2 ستونه است یا 3 ستونه564 - فرمتهای مختلف خودکار نرمالسازی میشوند565 - فقط نوع و تعداد موجودیتها مهم است، شناسه عددی نه566 """)567 568 # نمایش مثال فرمتهای مختلف569 with gr.Accordion("💡 مثال فرمتهای مختلف", open=False):570 gr.Markdown("""571 ### مثال 1: فرمت استاندارد vs فرمت REGEX572 573 **متن اصلی**: `شرکت فولاد مبارکه با درآمد 127 میلیارد تومان`574 575 **فرمت استاندارد**: `شرکت company-01 با درآمد amount-02 میلیارد تومان`576 577 **فرمت REGEX**: `شرکت COMPANY_001_REGEX با درآمد AMOUNT_012 میلیارد تومان`578 579 **نتیجه ارزیابی**: 580 - هر دو: 1 company + 1 amount581 - Precision = 1.0, Recall = 1.0, F1 = 1.0582 583 ### مثال 2: تعداد موجودیتهای متفاوت584 585 **مرجع**: `company-01 amount-02 person-03` (1+1+1=3)586 587 **پیشبینی**: `COMPANY_001_REGEX AMOUNT_012` (1+1=2)588 589 **نتیجه**:590 - True Positive = min(1,1) + min(1,1) + min(1,0) = 2591 - Precision = 2/2 = 1.0592 - Recall = 2/3 = 0.67593 - F1 = 0.80594 """)595 596 597 return interface598 599# اجرای برنامه600if __name__ == "__main__":601 interface = create_evaluation_interface()602 interface.launch()