CoolFace
Apppublic

leilaghomashchi/Benchmark-data-anonymization

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
last-correctapp1.py602 linesDownload Raw Back to root
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()