leilaghomashchi commited on
Commit
19c1f41
·
verified ·
1 Parent(s): 8ba211d

Upload complete_hf_app.py

Browse files
Files changed (1) hide show
  1. complete_hf_app.py +772 -0
complete_hf_app.py ADDED
@@ -0,0 +1,772 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NER Anonymization Evaluator for Hugging Face Spaces
3
+ ابزار ارزیابی استاندارد سیستم‌های ناشناس‌سازی با NER
4
+
5
+ Author: Your Name
6
+ Version: 1.0.0
7
+ License: MIT
8
+ """
9
+
10
+ import pandas as pd
11
+ import numpy as np
12
+ import re
13
+ from typing import Dict, List, Tuple
14
+ import gradio as gr
15
+ from datetime import datetime
16
+ import io
17
+
18
+ # ==================== Import seqeval ====================
19
+ try:
20
+ from seqeval.metrics import (
21
+ classification_report,
22
+ f1_score,
23
+ precision_score,
24
+ recall_score,
25
+ accuracy_score
26
+ )
27
+ from seqeval.scheme import IOB2
28
+ SEQEVAL_AVAILABLE = True
29
+ except ImportError:
30
+ SEQEVAL_AVAILABLE = False
31
+ print("⚠️ Warning: seqeval not installed. Only Exact Match will be available.")
32
+
33
+
34
+ # ==================== Main Evaluator Class ====================
35
+ class StandardNEREvaluator:
36
+ """
37
+ ارزیابی استاندارد Named Entity Recognition
38
+
39
+ این کلاس دو روش ارزیابی ارائه می‌دهد:
40
+ 1. seqeval: استاندارد علمی با IOB2 tagging
41
+ 2. Exact Match: مقایسه مستقیم شناسه‌ها
42
+ """
43
+
44
+ def __init__(self):
45
+ """مقداردهی اولیه"""
46
+ self.results_df = None
47
+
48
+ # انواع entity های پشتیبانی شده
49
+ self.entity_types = ['COMPANY', 'PERSON', 'AMOUNT', 'PERCENT', 'GROUP', 'STOCK']
50
+
51
+ # الگوهای regex برای تشخیص entities
52
+ self.patterns = {
53
+ 'COMPANY': [
54
+ r'company-(\d+)', r'Company-(\d+)', r'COMPANY-(\d+)',
55
+ r'COMPANY_(\d+)(?:_[A-Z]+)?', r'company_(\d+)(?:_[a-z]+)?'
56
+ ],
57
+ 'PERSON': [
58
+ r'person-(\d+)', r'Person-(\d+)', r'PERSON-(\d+)',
59
+ r'PERSON_(\d+)(?:_[A-Z]+)?', r'person_(\d+)(?:_[a-z]+)?'
60
+ ],
61
+ 'AMOUNT': [
62
+ r'amount-(\d+)', r'Amount-(\d+)', r'AMOUNT-(\d+)',
63
+ r'AMOUNT_(\d+)(?:_[A-Z]+)?', r'amount_(\d+)(?:_[a-z]+)?'
64
+ ],
65
+ 'PERCENT': [
66
+ r'percent-(\d+)', r'Percent-(\d+)', r'PERCENT-(\d+)',
67
+ r'PERCENT_(\d+)(?:_[A-Z]+)?', r'percent_(\d+)(?:_[a-z]+)?'
68
+ ],
69
+ 'GROUP': [
70
+ r'group-(\d+)', r'Group-(\d+)', r'GROUP-(\d+)',
71
+ r'GROUP_(\d+)(?:_[A-Z]+)?', r'group_(\d+)(?:_[a-z]+)?'
72
+ ],
73
+ 'STOCK': [
74
+ r'stock-(\d+)', r'Stock-(\d+)', r'STOCK-(\d+)',
75
+ r'STOCK_SYMBOL_(\d+)(?:_[A-Z]+)?', r'stock_symbol_(\d+)(?:_[a-z]+)?'
76
+ ]
77
+ }
78
+
79
+ def tokenize_text(self, text: str) -> List[str]:
80
+ """
81
+ تبدیل متن به توکن‌ها (کلمات)
82
+
83
+ Args:
84
+ text: متن ورودی
85
+
86
+ Returns:
87
+ لیست توکن‌ها
88
+ """
89
+ if pd.isna(text) or not isinstance(text, str):
90
+ return []
91
+ return text.split()
92
+
93
+ def text_to_iob2_tags(self, text: str) -> List[str]:
94
+ """
95
+ تبدیل متن به فرمت IOB2 Tagging
96
+
97
+ IOB2 Format:
98
+ - B-TYPE: Beginning of entity
99
+ - I-TYPE: Inside entity (continuation)
100
+ - O: Outside (not an entity)
101
+
102
+ Args:
103
+ text: متن ورودی
104
+
105
+ Returns:
106
+ لیست تگ‌های IOB2
107
+ """
108
+ if pd.isna(text) or not isinstance(text, str):
109
+ return []
110
+
111
+ tokens = self.tokenize_text(text)
112
+ tags = ['O'] * len(tokens)
113
+
114
+ # پیدا کردن entities در متن
115
+ for entity_type, pattern_list in self.patterns.items():
116
+ for pattern in pattern_list:
117
+ for match in re.finditer(pattern, text):
118
+ start_pos = match.start()
119
+ end_pos = match.end()
120
+
121
+ # پیدا کردن توکن‌هایی که entity در آنها است
122
+ current_pos = 0
123
+ for i, token in enumerate(tokens):
124
+ token_start = text.find(token, current_pos)
125
+ token_end = token_start + len(token)
126
+
127
+ if token_start >= start_pos and token_end <= end_pos:
128
+ if tags[i] == 'O':
129
+ # اولین توکن: B-TYPE
130
+ if token_start == start_pos or i == 0 or tags[i-1].split('-')[-1] != entity_type:
131
+ tags[i] = f'B-{entity_type}'
132
+ # توکن‌های بعدی: I-TYPE
133
+ else:
134
+ tags[i] = f'I-{entity_type}'
135
+
136
+ current_pos = token_end
137
+
138
+ return tags
139
+
140
+ def evaluate_with_seqeval(self, reference_text: str, predicted_text: str) -> Dict:
141
+ """
142
+ ارزیابی با seqeval (روش استاندارد)
143
+
144
+ Args:
145
+ reference_text: متن مرجع
146
+ predicted_text: متن پیش‌بینی شده
147
+
148
+ Returns:
149
+ دیکشنری شامل metrics
150
+ """
151
+ if not SEQEVAL_AVAILABLE:
152
+ return {
153
+ 'precision': 0.0,
154
+ 'recall': 0.0,
155
+ 'f1': 0.0,
156
+ 'accuracy': 0.0,
157
+ 'error': 'seqeval not available'
158
+ }
159
+
160
+ try:
161
+ # تبدیل به IOB2 tags
162
+ y_true = [self.text_to_iob2_tags(reference_text)]
163
+ y_pred = [self.text_to_iob2_tags(predicted_text)]
164
+
165
+ # اگر هر دو خالی باشند
166
+ if not y_true[0] and not y_pred[0]:
167
+ return {
168
+ 'precision': 1.0,
169
+ 'recall': 1.0,
170
+ 'f1': 1.0,
171
+ 'accuracy': 1.0
172
+ }
173
+
174
+ # محاسبه metrics
175
+ precision = precision_score(y_true, y_pred, scheme=IOB2, mode='strict')
176
+ recall = recall_score(y_true, y_pred, scheme=IOB2, mode='strict')
177
+ f1 = f1_score(y_true, y_pred, scheme=IOB2, mode='strict')
178
+ accuracy = accuracy_score(y_true, y_pred)
179
+
180
+ return {
181
+ 'precision': round(precision, 4),
182
+ 'recall': round(recall, 4),
183
+ 'f1': round(f1, 4),
184
+ 'accuracy': round(accuracy, 4)
185
+ }
186
+
187
+ except Exception as e:
188
+ print(f"خطا در seqeval: {str(e)}")
189
+ return {
190
+ 'precision': 0.0,
191
+ 'recall': 0.0,
192
+ 'f1': 0.0,
193
+ 'accuracy': 0.0,
194
+ 'error': str(e)
195
+ }
196
+
197
+ def evaluate_with_exact_match(self, reference_text: str, predicted_text: str) -> Dict:
198
+ """
199
+ ارزیابی با Exact Match (روش ساده)
200
+
201
+ Args:
202
+ reference_text: متن مرجع
203
+ predicted_text: متن پیش‌بینی شده
204
+
205
+ Returns:
206
+ دیکشنری شامل metrics
207
+ """
208
+ def extract_entities(text):
209
+ """استخراج entities از متن"""
210
+ entities = set()
211
+ for entity_type, pattern_list in self.patterns.items():
212
+ for pattern in pattern_list:
213
+ for match in re.finditer(pattern, text):
214
+ entity_id = match.group(1)
215
+ entities.add(f"{entity_type}-{entity_id}")
216
+ return entities
217
+
218
+ ref_entities = extract_entities(reference_text)
219
+ pred_entities = extract_entities(predicted_text)
220
+
221
+ # محاسبه TP, FP, FN
222
+ tp = len(ref_entities & pred_entities)
223
+ fp = len(pred_entities - ref_entities)
224
+ fn = len(ref_entities - pred_entities)
225
+
226
+ # محاسبه metrics
227
+ precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
228
+ recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
229
+ f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
230
+
231
+ return {
232
+ 'precision': round(precision, 4),
233
+ 'recall': round(recall, 4),
234
+ 'f1': round(f1, 4),
235
+ 'tp': tp,
236
+ 'fp': fp,
237
+ 'fn': fn
238
+ }
239
+
240
+ def evaluate_single_row(self, reference_text: str, predicted_text: str) -> Dict:
241
+ """
242
+ ارزیابی یک سطر با هر دو روش
243
+
244
+ Args:
245
+ reference_text: متن مرجع
246
+ predicted_text: متن پیش‌بینی شده
247
+
248
+ Returns:
249
+ دیکشنری شامل همه metrics
250
+ """
251
+ # روش 1: seqeval
252
+ seqeval_metrics = self.evaluate_with_seqeval(reference_text, predicted_text)
253
+
254
+ # روش 2: Exact Match
255
+ exact_metrics = self.evaluate_with_exact_match(reference_text, predicted_text)
256
+
257
+ return {
258
+ 'seqeval_precision': seqeval_metrics['precision'],
259
+ 'seqeval_recall': seqeval_metrics['recall'],
260
+ 'seqeval_f1': seqeval_metrics['f1'],
261
+ 'seqeval_accuracy': seqeval_metrics['accuracy'],
262
+ 'exact_precision': exact_metrics['precision'],
263
+ 'exact_recall': exact_metrics['recall'],
264
+ 'exact_f1': exact_metrics['f1'],
265
+ 'tp_count': exact_metrics['tp'],
266
+ 'fp_count': exact_metrics['fp'],
267
+ 'fn_count': exact_metrics['fn']
268
+ }
269
+
270
+ def evaluate_dataset(self, file_path: str) -> Tuple[bool, str, pd.DataFrame]:
271
+ """
272
+ ارزیابی کل دیتاست
273
+
274
+ Args:
275
+ file_path: مسیر فایل CSV
276
+
277
+ Returns:
278
+ (موفقیت, پیام وضعیت, DataFrame نتایج)
279
+ """
280
+ if not SEQEVAL_AVAILABLE:
281
+ return (
282
+ False,
283
+ "⚠️ seqeval نصب نیست. لطفاً requirements.txt را چک کنید.",
284
+ pd.DataFrame()
285
+ )
286
+
287
+ try:
288
+ # بارگذاری فایل
289
+ df = pd.read_csv(file_path)
290
+
291
+ # تشخیص ستون‌ها
292
+ if 'Reference_text' in df.columns and 'anonymized_text' in df.columns:
293
+ reference_col = 'Reference_text'
294
+ predicted_col = 'anonymized_text'
295
+ elif 'original_text' in df.columns and 'anonymized_text' in df.columns:
296
+ reference_col = 'original_text'
297
+ predicted_col = 'anonymized_text'
298
+ else:
299
+ return (
300
+ False,
301
+ "❌ فایل باید شامل ستون‌های 'original_text' و 'anonymized_text' باشد",
302
+ pd.DataFrame()
303
+ )
304
+
305
+ # ارزیابی هر سطر
306
+ results = []
307
+ for index, row in df.iterrows():
308
+ metrics = self.evaluate_single_row(
309
+ str(row[reference_col]),
310
+ str(row[predicted_col])
311
+ )
312
+ results.append(metrics)
313
+
314
+ # ایجاد DataFrame نتایج
315
+ results_df = pd.DataFrame(results)
316
+
317
+ # اضافه کردن ستون‌های اصلی
318
+ for col in df.columns:
319
+ results_df[col] = df[col].values
320
+
321
+ # ترتیب ستون‌ها
322
+ metric_cols = [
323
+ 'seqeval_precision', 'seqeval_recall', 'seqeval_f1', 'seqeval_accuracy',
324
+ 'exact_precision', 'exact_recall', 'exact_f1',
325
+ 'tp_count', 'fp_count', 'fn_count'
326
+ ]
327
+ other_cols = [col for col in results_df.columns if col not in metric_cols]
328
+ results_df = results_df[metric_cols + other_cols]
329
+
330
+ self.results_df = results_df
331
+
332
+ # محاسبه آمار کلی
333
+ avg_seqeval_p = results_df['seqeval_precision'].mean()
334
+ avg_seqeval_r = results_df['seqeval_recall'].mean()
335
+ avg_seqeval_f1 = results_df['seqeval_f1'].mean()
336
+ avg_seqeval_acc = results_df['seqeval_accuracy'].mean()
337
+ avg_exact_f1 = results_df['exact_f1'].mean()
338
+
339
+ total_tp = results_df['tp_count'].sum()
340
+ total_fp = results_df['fp_count'].sum()
341
+ total_fn = results_df['fn_count'].sum()
342
+
343
+ # ایجاد پیام وضعیت
344
+ status = f"""✅ ارزیابی با موفقیت انجام شد!
345
+
346
+ 📊 **نتایج seqeval (استاندارد NER - IOB2 Tagging):**
347
+ • Precision: {avg_seqeval_p:.4f}
348
+ • Recall: {avg_seqeval_r:.4f}
349
+ • F1-Score: {avg_seqeval_f1:.4f}
350
+ • Accuracy: {avg_seqeval_acc:.4f}
351
+
352
+ 📈 **آمار کلی:**
353
+ • کل True Positives: {total_tp}
354
+ • کل False Positives: {total_fp}
355
+ • کل False Negatives: {total_fn}
356
+ • تعداد سطرها: {len(df)}
357
+
358
+ 🔬 **مقایسه با Exact Match:**
359
+ • F1 (seqeval): {avg_seqeval_f1:.4f}
360
+ • F1 (Exact): {avg_exact_f1:.4f}
361
+ • اختلاف: {abs(avg_seqeval_f1 - avg_exact_f1):.4f}
362
+
363
+ ✅ این ارزیابی مطابق با استانداردهای CoNLL-2003 است"""
364
+
365
+ return True, status, results_df
366
+
367
+ except Exception as e:
368
+ return False, f"❌ خطا در پردازش: {str(e)}", pd.DataFrame()
369
+
370
+ def generate_report(self, df: pd.DataFrame) -> str:
371
+ """
372
+ تولید گزارش جامع
373
+
374
+ Args:
375
+ df: DataFrame نتایج
376
+
377
+ Returns:
378
+ متن گزارش
379
+ """
380
+ if df.empty:
381
+ return "هیچ داده‌ای برای گزارش یافت نشد"
382
+
383
+ # محاسبه آمار
384
+ total_rows = len(df)
385
+
386
+ avg_seqeval_p = df['seqeval_precision'].mean()
387
+ avg_seqeval_r = df['seqeval_recall'].mean()
388
+ avg_seqeval_f1 = df['seqeval_f1'].mean()
389
+ avg_seqeval_acc = df['seqeval_accuracy'].mean()
390
+
391
+ high_f1_count = len(df[df['seqeval_f1'] >= 0.9])
392
+ mid_f1_count = len(df[df['seqeval_f1'] >= 0.7])
393
+ low_f1_count = len(df[df['seqeval_f1'] < 0.5])
394
+
395
+ best_idx = df['seqeval_f1'].idxmax()
396
+ worst_idx = df['seqeval_f1'].idxmin()
397
+
398
+ # تفسیر نتایج
399
+ if avg_seqeval_f1 >= 0.9:
400
+ interpretation = "✅ عملکرد عالی - سیستم شما بسیار دقیق است"
401
+ elif avg_seqeval_f1 >= 0.7:
402
+ interpretation = "⚠️ عملکرد خوب - اما قابل بهبود"
403
+ else:
404
+ interpretation = "❌ عملکرد ضعیف - نیاز به بهبود اساسی"
405
+
406
+ report = f"""
407
+ ## 📊 گزارش جامع ارزیابی NER
408
+
409
+ ### 🎯 خلاصه نتا��ج:
410
+ {interpretation}
411
+
412
+ ### 📈 آمار کلی:
413
+ - **تعداد کل سطرها:** {total_rows}
414
+ - **روش ارزیابی:** IOB2 Tagging (استاندارد CoNLL-2003)
415
+
416
+ ### ✅ نتایج seqeval (استاندارد):
417
+ - **میانگین Precision:** {avg_seqeval_p:.4f}
418
+ - **میانگین Recall:** {avg_seqeval_r:.4f}
419
+ - **میانگین F1-Score:** {avg_seqeval_f1:.4f}
420
+ - **میانگین Accuracy:** {avg_seqeval_acc:.4f}
421
+
422
+ ### 📊 توزیع عملکرد:
423
+ - **F1 ≥ 0.9 (عالی):** {high_f1_count} سطر ({high_f1_count/total_rows*100:.1f}%)
424
+ - **F1 ≥ 0.7 (خوب):** {mid_f1_count} سطر ({mid_f1_count/total_rows*100:.1f}%)
425
+ - **F1 < 0.5 (ضعیف):** {low_f1_count} سطر ({low_f1_count/total_rows*100:.1f}%)
426
+
427
+ ### 🏆 بهترین و بدترین:
428
+ - **بهترین F1:** {df.loc[best_idx, 'seqeval_f1']:.4f} (سطر {best_idx + 1})
429
+ - **بدترین F1:** {df.loc[worst_idx, 'seqeval_f1']:.4f} (سطر {worst_idx + 1})
430
+
431
+ ### 💡 توصیه‌ها:
432
+ {"- سیستم شما عملکرد بسیار خوبی دارد" if avg_seqeval_f1 >= 0.9 else ""}
433
+ {"- روی بهبود Precision تمرکز کنید" if avg_seqeval_p < avg_seqeval_r else ""}
434
+ {"- روی بهبود Recall تمرکز کنید" if avg_seqeval_r < avg_seqeval_p else ""}
435
+ {"- نیاز به بازنگری اساسی در مدل دارید" if avg_seqeval_f1 < 0.5 else ""}
436
+ """
437
+
438
+ return report
439
+
440
+ def create_csv(self) -> bytes:
441
+ """
442
+ ایجاد فایل CSV برای دانلود
443
+
444
+ Returns:
445
+ محتوای CSV به صورت bytes
446
+ """
447
+ if self.results_df is None or self.results_df.empty:
448
+ return None
449
+
450
+ try:
451
+ csv_buffer = io.StringIO()
452
+ self.results_df.to_csv(csv_buffer, index=False, encoding='utf-8')
453
+ return csv_buffer.getvalue().encode('utf-8-sig')
454
+ except Exception as e:
455
+ print(f"خطا در ایجاد CSV: {str(e)}")
456
+ return None
457
+
458
+
459
+ # ==================== Gradio Interface ====================
460
+ def create_interface():
461
+ """ایجاد رابط کاربری Gradio"""
462
+
463
+ evaluator = StandardNEREvaluator()
464
+
465
+ # بررسی وضعیت seqeval
466
+ seqeval_status = "✅ فعال و آماده" if SEQEVAL_AVAILABLE else "❌ نصب نشده"
467
+ seqeval_emoji = "🟢" if SEQEVAL_AVAILABLE else "🔴"
468
+
469
+ # تعریف CSS سفارشی
470
+ custom_css = """
471
+ .rtl {
472
+ direction: rtl;
473
+ text-align: right;
474
+ font-family: Tahoma, Arial, sans-serif;
475
+ }
476
+ .ltr {
477
+ direction: ltr;
478
+ text-align: left;
479
+ }
480
+ .center {
481
+ text-align: center;
482
+ }
483
+ .header-box {
484
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
485
+ color: white;
486
+ padding: 20px;
487
+ border-radius: 10px;
488
+ margin-bottom: 20px;
489
+ }
490
+ .status-box {
491
+ background: #f0f9ff;
492
+ border-left: 4px solid #0284c7;
493
+ padding: 15px;
494
+ border-radius: 5px;
495
+ margin: 10px 0;
496
+ }
497
+ .metric-good {
498
+ color: #059669;
499
+ font-weight: bold;
500
+ }
501
+ .metric-bad {
502
+ color: #dc2626;
503
+ font-weight: bold;
504
+ }
505
+ """
506
+
507
+ # ساخت Interface
508
+ with gr.Blocks(
509
+ title="NER Anonymization Evaluator",
510
+ theme=gr.themes.Soft(
511
+ primary_hue="blue",
512
+ secondary_hue="purple",
513
+ ),
514
+ css=custom_css
515
+ ) as demo:
516
+
517
+ # هدر
518
+ with gr.Row():
519
+ gr.Markdown(f"""
520
+ <div class="header-box">
521
+ <h1 style="margin:0; text-align:center;">🎯 ابزار ارزیابی استاندارد NER</h1>
522
+ <p style="margin:5px 0 0 0; text-align:center;">
523
+ Named Entity Recognition Evaluation Tool
524
+ </p>
525
+ </div>
526
+ """)
527
+
528
+ # وضعیت seqeval
529
+ with gr.Row():
530
+ gr.Markdown(f"""
531
+ <div class="status-box rtl">
532
+ <strong>وضعیت seqeval:</strong> {seqeval_emoji} {seqeval_status}
533
+ <br>
534
+ <small>این ابزار برای ارزیابی سیستم‌های ناشناس‌سازی متن با استفاده از الگوریتم‌های استاندارد NER طراحی شده است.</small>
535
+ </div>
536
+ """)
537
+
538
+ # بخش اصلی
539
+ with gr.Row():
540
+ # ستون چپ: آپلود
541
+ with gr.Column(scale=1):
542
+ gr.Markdown("### 📁 بارگذاری فایل", elem_classes=["rtl"])
543
+
544
+ file_input = gr.File(
545
+ label="فایل CSV",
546
+ file_types=[".csv"],
547
+ type="filepath"
548
+ )
549
+
550
+ gr.Markdown("""
551
+ <div class="rtl" style="font-size:0.9em; color:#666;">
552
+ فایل باید شامل دو ستون باشد:<br>
553
+ • <code>original_text</code> یا <code>Reference_text</code><br>
554
+ • <code>anonymized_text</code>
555
+ </div>
556
+ """)
557
+
558
+ evaluate_btn = gr.Button(
559
+ "🚀 شروع ارزیابی",
560
+ variant="primary",
561
+ size="lg"
562
+ )
563
+
564
+ download_btn = gr.DownloadButton(
565
+ label="💾 دانلود نتایج CSV",
566
+ visible=False,
567
+ variant="secondary"
568
+ )
569
+
570
+ # ستون راست: وضعیت
571
+ with gr.Column(scale=2):
572
+ status_output = gr.Markdown(
573
+ "آماده دریافت فایل CSV...",
574
+ elem_classes=["rtl"]
575
+ )
576
+
577
+ # گزارش خلاصه
578
+ summary_output = gr.Markdown(
579
+ visible=False,
580
+ elem_classes=["rtl"]
581
+ )
582
+
583
+ # جدول نتایج
584
+ results_table = gr.Dataframe(
585
+ label="نتایج تفصیلی (10 سطر اول)",
586
+ visible=False,
587
+ wrap=True
588
+ )
589
+
590
+ # راهنما
591
+ with gr.Accordion("📖 راهنمای استفاده", open=False):
592
+ gr.Markdown("""
593
+ <div class="rtl">
594
+
595
+ ## 🎯 نحوه استفاده:
596
+
597
+ ### 1. آماده‌سازی فایل CSV:
598
+
599
+ فایل شما باید شامل دو ستون باشد:
600
+
601
+ ```csv
602
+ original_text,anonymized_text
603
+ "شرکت فولاد مبارکه","شرکت company-01"
604
+ "آقای احمد رضایی","person-02"
605
+ "سود 15 درصد","سود percent-03"
606
+ ```
607
+
608
+ ### 2. فرمت‌های پشتیبانی شده:
609
+
610
+ #### شرکت‌ها (Company):
611
+ - `company-01`, `Company-01`, `COMPANY-01`
612
+ - `COMPANY_001`, `COMPANY_001_REGEX`
613
+
614
+ #### افراد (Person):
615
+ - `person-02`, `Person-02`, `PERSON-02`
616
+ - `PERSON_002`, `PERSON_002_REGEX`
617
+
618
+ #### مبالغ (Amount):
619
+ - `amount-03`, `AMOUNT-03`
620
+ - `AMOUNT_003`, `AMOUNT_003_REGEX`
621
+
622
+ #### درصدها (Percent):
623
+ - `percent-04`, `PERCENT-04`
624
+
625
+ #### گروه‌ها (Group):
626
+ - `group-05`, `GROUP-05`
627
+
628
+ #### سهام (Stock):
629
+ - `stock-06`, `STOCK-06`
630
+ - `STOCK_SYMBOL_006`
631
+
632
+ ### 3. معیارهای ارزیابی:
633
+
634
+ - **Precision**: از entities شناسایی شده، چند درصد درست بودند؟
635
+ - **Recall**: از entities واقعی، چند درصد پیدا شدند؟
636
+ - **F1-Score**: میانگین هماهنگ Precision و Recall
637
+ - **Accuracy**: دقت کلی
638
+
639
+ ### 4. روش‌های ارزیابی:
640
+
641
+ 1. **seqeval (پیشنهادی)**: استفاده از IOB2 tagging - استاندارد CoNLL-2003
642
+ 2. **Exact Match**: مقایسه مستقیم شناسه‌ها
643
+
644
+ ### 5. تفسیر نتایج:
645
+
646
+ - **F1 ≥ 0.9**: عملکرد عالی ✅
647
+ - **F1 ≥ 0.7**: عملکرد خوب ⚠️
648
+ - **F1 < 0.7**: نیاز به بهبود ❌
649
+
650
+ </div>
651
+ """)
652
+
653
+ # مثال
654
+ with gr.Accordion("💡 مثال عملی", open=False):
655
+ gr.Markdown("""
656
+ <div class="rtl">
657
+
658
+ ## مثال:
659
+
660
+ ### ورودی:
661
+ ```
662
+ متن مرجع: "شرکت company-01 با person-02 کار می‌کند"
663
+ متن پیش‌بینی: "شرکت company-01 با person-99 کار می‌کند"
664
+ ```
665
+
666
+ ### تحلیل:
667
+ - ✅ `company-01` درست شناسایی شد
668
+ - ❌ `person-02` باید بود اما `person-99` شد
669
+
670
+ ### نتایج:
671
+ - **True Positive**: 1 (company-01)
672
+ - **False Positive**: 1 (person-99)
673
+ - **False Negative**: 1 (person-02)
674
+ - **Precision**: 0.50
675
+ - **Recall**: 0.50
676
+ - **F1-Score**: 0.50
677
+
678
+ ### تفسیر:
679
+ سیستم 50% دقت دارد - نیمی از entities را درست تشخیص داده است.
680
+
681
+ </div>
682
+ """)
683
+
684
+ # فوتر
685
+ gr.Markdown("""
686
+ ---
687
+
688
+ <div class="center">
689
+
690
+ ### 📚 منابع:
691
+ [seqeval](https://github.com/chakki-works/seqeval) •
692
+ [CoNLL-2003](https://www.clips.uantwerpen.be/conll2003/ner/) •
693
+ [Gradio](https://gradio.app)
694
+
695
+ ---
696
+
697
+ Made with ❤️ for Persian NLP Community
698
+
699
+ <small>Version 1.0.0 • MIT License</small>
700
+
701
+ </div>
702
+ """)
703
+
704
+ # ==================== Event Handlers ====================
705
+
706
+ def evaluate_file(file):
707
+ """تابع ارزیابی فایل"""
708
+ if file is None:
709
+ return (
710
+ "❌ لطفاً فایل CSV را بارگذاری کنید",
711
+ gr.Markdown(visible=False),
712
+ gr.Dataframe(visible=False),
713
+ gr.DownloadButton(visible=False)
714
+ )
715
+
716
+ try:
717
+ # ارزیابی
718
+ success, message, df = evaluator.evaluate_dataset(file)
719
+
720
+ if not success:
721
+ return (
722
+ f"❌ {message}",
723
+ gr.Markdown(visible=False),
724
+ gr.Dataframe(visible=False),
725
+ gr.DownloadButton(visible=False)
726
+ )
727
+
728
+ # تولید گزارش
729
+ summary = evaluator.generate_report(df)
730
+
731
+ # ایجاد CSV
732
+ csv_content = evaluator.create_csv()
733
+
734
+ # نمایش نتایج
735
+ return (
736
+ message,
737
+ gr.Markdown(value=summary, visible=True),
738
+ gr.Dataframe(value=df.head(10), visible=True),
739
+ gr.DownloadButton(
740
+ label="💾 دانلود نتایج کامل CSV",
741
+ value=csv_content,
742
+ visible=True
743
+ )
744
+ )
745
+
746
+ except Exception as e:
747
+ return (
748
+ f"❌ خطای غیرمنتظره: {str(e)}",
749
+ gr.Markdown(visible=False),
750
+ gr.Dataframe(visible=False),
751
+ gr.DownloadButton(visible=False)
752
+ )
753
+
754
+ # اتصال event
755
+ evaluate_btn.click(
756
+ fn=evaluate_file,
757
+ inputs=[file_input],
758
+ outputs=[status_output, summary_output, results_table, download_btn]
759
+ )
760
+
761
+ return demo
762
+
763
+
764
+ # ==================== Main ====================
765
+ if __name__ == "__main__":
766
+ # ایجاد و اجرای interface
767
+ demo = create_interface()
768
+ demo.launch(
769
+ server_name="0.0.0.0",
770
+ server_port=7860,
771
+ share=False
772
+ )