leilaghomashchi's picture
Rename fixed_ner_evaluator.py to app.py
3090be6 verified
"""
Fixed NER Anonymization Evaluator
ارزیاب درست و دقیق - بدون مشکلات tokenization
این نسخه مستقیماً entities را مقایسه می‌کند بدون IOB2
"""
import pandas as pd
import re
from typing import Dict, List, Set, Tuple
import gradio as gr
from datetime import datetime
import tempfile
import os
class FixedNEREvaluator:
"""ارزیاب درست - مقایسه مستقیم entities"""
def __init__(self):
self.results_df = None
# الگوهای regex برای تشخیص entities
# توجه: این الگوها باید با فرمت واقعی شما match کنند
self.entity_patterns = [
# فرمت استاندارد: type-number
r'\b(COMPANY|company|PERSON|person|AMOUNT|amount|PERCENT|percent|GROUP|group|STOCK|stock)-(\d+)\b',
# فرمت با underscore: TYPE_NUMBER
r'\b(COMPANY|PERSON|AMOUNT|PERCENT|GROUP|STOCK)_(\d+)\b',
# فرمت کامل: TYPE_NUMBER_SUFFIX
r'\b(COMPANY|PERSON|AMOUNT|PERCENT|GROUP|STOCK)_(\d+)_[A-Z]+\b',
# فرمت STOCK خاص
r'\bSTOCK_SYMBOL_(\d+)(?:_[A-Z]+)?\b',
]
def extract_entities(self, text: str) -> Set[Tuple[str, str]]:
"""
استخراج entities از متن
Returns:
Set of (entity_type, entity_id) tuples
مثال: {('COMPANY', '01'), ('PERSON', '02')}
"""
if pd.isna(text) or not isinstance(text, str):
return set()
entities = set()
for pattern in self.entity_patterns:
matches = re.finditer(pattern, text, re.IGNORECASE)
for match in matches:
groups = match.groups()
if len(groups) >= 2:
entity_type = groups[0].upper()
entity_id = groups[1]
# نرمال‌سازی: همه به فرمت TYPE-ID
entities.add((entity_type, entity_id))
return entities
def calculate_metrics(self, reference_entities: Set, predicted_entities: Set) -> Dict:
"""
محاسبه metrics بر اساس مجموعه entities
Args:
reference_entities: مجموعه entities مرجع
predicted_entities: مجموعه entities پیش‌بینی شده
Returns:
دیکشنری شامل TP, FP, FN, Precision, Recall, F1
"""
# محاسبه TP, FP, FN
tp = len(reference_entities & predicted_entities) # اشتراک
fp = len(predicted_entities - reference_entities) # پیش‌بینی اضافی
fn = len(reference_entities - predicted_entities) # فراموش شده
# محاسبه Precision, Recall, F1
precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
# اگر هر دو خالی باشند = تطابق کامل
if len(reference_entities) == 0 and len(predicted_entities) == 0:
precision = recall = f1 = 1.0
return {
'tp': tp,
'fp': fp,
'fn': fn,
'precision': round(precision, 4),
'recall': round(recall, 4),
'f1': round(f1, 4)
}
def evaluate_single_row(self, reference_text: str, predicted_text: str) -> Dict:
"""
ارزیابی یک سطر
Returns:
دیکشنری شامل metrics + entities برای debug
"""
ref_entities = self.extract_entities(reference_text)
pred_entities = self.extract_entities(predicted_text)
metrics = self.calculate_metrics(ref_entities, pred_entities)
# اضافه کردن entities برای debug
metrics['ref_entities'] = sorted(list(ref_entities))
metrics['pred_entities'] = sorted(list(pred_entities))
metrics['matched'] = sorted(list(ref_entities & pred_entities))
metrics['missed'] = sorted(list(ref_entities - pred_entities))
metrics['extra'] = sorted(list(pred_entities - ref_entities))
return metrics
def evaluate_dataset(self, file_path: str) -> Tuple[bool, str, pd.DataFrame]:
"""ارزیابی کل دیتاست"""
try:
print(f"📂 در حال خواندن فایل: {file_path}")
df = pd.read_csv(file_path, encoding='utf-8-sig')
print(f"✅ فایل خوانده شد: {len(df)} سطر")
print(f"📋 ستون‌ها: {list(df.columns)}")
# تشخیص ستون‌ها
if 'Reference_text' in df.columns and 'anonymized_text' in df.columns:
reference_col = 'Reference_text'
predicted_col = 'anonymized_text'
elif 'original_text' in df.columns and 'anonymized_text' in df.columns:
reference_col = 'original_text'
predicted_col = 'anonymized_text'
else:
return (
False,
f"❌ ستون‌های مورد نیاز یافت نشد!\n\nستون‌های موجود: {list(df.columns)}",
pd.DataFrame()
)
print(f"🔍 شروع ارزیابی...")
# ارزیابی هر سطر
results = []
for index, row in df.iterrows():
if (index + 1) % 10 == 0:
print(f" پردازش سطر {index + 1}/{len(df)}...")
metrics = self.evaluate_single_row(
str(row[reference_col]),
str(row[predicted_col])
)
results.append(metrics)
print(f"✅ ارزیابی کامل شد!")
# ایجاد DataFrame
results_df = pd.DataFrame(results)
# اضافه کردن ستون‌های اصلی
for col in df.columns:
results_df[col] = df[col].values
# ترتیب ستون‌ها
metric_cols = ['precision', 'recall', 'f1', 'tp', 'fp', 'fn']
debug_cols = ['ref_entities', 'pred_entities', 'matched', 'missed', 'extra']
main_cols = [col for col in df.columns if col in results_df.columns]
results_df = results_df[metric_cols + debug_cols + main_cols]
self.results_df = results_df
# محاسبه آمار کلی
avg_precision = results_df['precision'].mean()
avg_recall = results_df['recall'].mean()
avg_f1 = results_df['f1'].mean()
total_tp = results_df['tp'].sum()
total_fp = results_df['fp'].sum()
total_fn = results_df['fn'].sum()
# F1 کلی (macro-average)
macro_f1 = avg_f1
# F1 کلی (micro-average) - بر اساس مجموع TP/FP/FN
micro_precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0
micro_recall = total_tp / (total_tp + total_fn) if (total_tp + total_fn) > 0 else 0
micro_f1 = 2 * micro_precision * micro_recall / (micro_precision + micro_recall) if (micro_precision + micro_recall) > 0 else 0
high_f1 = len(results_df[results_df['f1'] >= 0.9])
mid_f1 = len(results_df[results_df['f1'] >= 0.7])
low_f1 = len(results_df[results_df['f1'] < 0.5])
status = f"""✅ ارزیابی با موفقیت انجام شد!
📊 **نتایج کلی (Direct Entity Matching):**
• Macro-Average F1: {macro_f1:.4f}
• Micro-Average F1: {micro_f1:.4f}
• میانگین Precision: {avg_precision:.4f}
• میانگین Recall: {avg_recall:.4f}
📈 **آمار کلی:**
• کل True Positives: {total_tp}
• کل False Positives: {total_fp}
• کل False Negatives: {total_fn}
• تعداد سطرها: {len(df)}
📊 **توزیع عملکرد:**
• F1 ≥ 0.9 (عالی): {high_f1} سطر ({high_f1/len(df)*100:.1f}%)
• F1 ≥ 0.7 (خوب): {mid_f1} سطر ({mid_f1/len(df)*100:.1f}%)
• F1 < 0.5 (ضعیف): {low_f1} سطر ({low_f1/len(df)*100:.1f}%)
🔬 **مقایسه:**
• مرجع (انسانی): {reference_col}
• پیش‌بینی (LLM): {predicted_col}
💡 **تفاوت با seqeval:**
این نسخه مستقیماً entities را مقایسه می‌کند بدون مشکلات tokenization
"""
return True, status, results_df
except Exception as e:
import traceback
error_details = traceback.format_exc()
return False, f"❌ خطا در پردازش:\n\n{str(e)}\n\n{error_details[:500]}", pd.DataFrame()
def create_downloadable_csv(self) -> str:
"""ایجاد فایل CSV برای دانلود"""
if self.results_df is None or self.results_df.empty:
return None
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
temp_filename = f"fixed_evaluation_results_{timestamp}.csv"
temp_path = os.path.join(tempfile.gettempdir(), temp_filename)
# تبدیل لیست‌ها به string برای CSV
df_to_save = self.results_df.copy()
for col in ['ref_entities', 'pred_entities', 'matched', 'missed', 'extra']:
if col in df_to_save.columns:
df_to_save[col] = df_to_save[col].apply(str)
df_to_save.to_csv(temp_path, index=False, encoding='utf-8-sig')
return temp_path
except Exception as e:
print(f"❌ خطا در ایجاد CSV: {str(e)}")
return None
def create_interface():
"""ایجاد رابط کاربری Gradio"""
evaluator = FixedNEREvaluator()
with gr.Blocks(title="Fixed NER Evaluator", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🎯 ارزیاب درست و دقیق NER
## Fixed NER Anonymization Evaluator
### ✅ این نسخه بدون مشکلات tokenization کار می‌کند
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 📂 بارگذاری فایل")
file_input = gr.File(
label="فایل CSV (با ستون‌های Reference_text و anonymized_text)",
file_types=[".csv"]
)
evaluate_btn = gr.Button("🚀 شروع ارزیابی", variant="primary", size="lg")
download_btn = gr.Button("💾 دانلود نتایج CSV", visible=False, variant="secondary")
with gr.Column(scale=2):
status_output = gr.Markdown("آماده دریافت فایل...")
results_table = gr.Dataframe(
label="نتایج تفصیلی (10 سطر اول)",
visible=False,
wrap=True
)
download_file = gr.File(visible=False)
with gr.Accordion("📖 راهنمای استفاده", open=False):
gr.Markdown("""
## نحوه استفاده:
1. فایل CSV خود را آپلود کنید
2. فایل باید شامل این ستون‌ها باشد:
- `Reference_text` (مرجع انسانی)
- `anonymized_text` (پیش‌بینی LLM)
3. روی دکمه "شروع ارزیابی" کلیک کنید
4. نتایج را مشاهده و دانلود کنید
## تفاوت با نسخه قبلی:
- ✅ مستقیماً entities را مقایسه می‌کند
- ✅ بدون مشکلات tokenization
- ✅ برای فارسی کاملاً دقیق
- ✅ شامل اطلاعات debug (matched, missed, extra entities)
""")
def evaluate_file(file):
if file is None:
return (
"❌ لطفاً فایل CSV را بارگذاری کنید",
gr.Dataframe(visible=False),
gr.Button(visible=False),
gr.File(visible=False)
)
success, message, df = evaluator.evaluate_dataset(file)
if not success:
return (
f"❌ {message}",
gr.Dataframe(visible=False),
gr.Button(visible=False),
gr.File(visible=False)
)
return (
message,
gr.Dataframe(value=df.head(10), visible=True),
gr.Button(visible=True),
gr.File(visible=False)
)
def download_results():
csv_path = evaluator.create_downloadable_csv()
if csv_path and os.path.exists(csv_path):
return "✅ فایل نتایج آماده دانلود است", gr.File(value=csv_path, visible=True)
return "❌ خطا در ایجاد فایل", gr.File(visible=False)
evaluate_btn.click(
fn=evaluate_file,
inputs=[file_input],
outputs=[status_output, results_table, download_btn, download_file]
)
download_btn.click(
fn=download_results,
outputs=[status_output, download_file]
)
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)