leilaghomashchi commited on
Commit
83f0287
·
verified ·
1 Parent(s): 2efd674

Delete error analysis.py

Browse files
Files changed (1) hide show
  1. error analysis.py +0 -899
error analysis.py DELETED
@@ -1,899 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- سیستم benchmark ناشناس‌سازی - Error Analysis برای بهبود سیستم اصلی
5
- """
6
-
7
- import pandas as pd
8
- import re
9
- import json
10
- import logging
11
- import os
12
- import gradio as gr
13
- from typing import Dict, List, Tuple, Set
14
- from collections import defaultdict
15
- import numpy as np
16
-
17
- # تنظیم logging
18
- logging.basicConfig(level=logging.INFO)
19
- logger = logging.getLogger(__name__)
20
-
21
- # ===== تابع کمکی برای تبدیل numpy/pandas types =====
22
- def convert_to_serializable(obj):
23
- if isinstance(obj, (np.integer, np.int64, np.int32)):
24
- return int(obj)
25
- elif isinstance(obj, (np.floating, np.float64, np.float32)):
26
- return float(obj)
27
- elif isinstance(obj, np.ndarray):
28
- return obj.tolist()
29
- elif isinstance(obj, dict):
30
- return {key: convert_to_serializable(value) for key, value in obj.items()}
31
- elif isinstance(obj, list):
32
- return [convert_to_serializable(item) for item in obj]
33
- else:
34
- return obj
35
-
36
- # ===== کلاس پردازش entities با الگوهای آپدیت شده =====
37
- class UpdatedEntityExtractor:
38
- def __init__(self):
39
- # الگوهای آپدیت شده براساس سیستم ناشناس‌سازی بهبود یافته
40
- self.patterns = {
41
- # آدرس‌های کامل - اولویت بالا با پوشش میدان و برج
42
- 'FULL_ADDRESS': [
43
- # الگوی آدرس کامل: شهر + میدان + برج + طبقه + واحد
44
- r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)،\s*(?:میدان|خیابان|کوچه|شهرک|بلوار|کوی|محله)\s+[آ-ی‌ّٰ-ٹ\s]+(?:،\s*(?:برج|ساختمان|مجتمع)\s+[آ-ی‌ّٰ-ٹ\s]+)?(?:،\s*(?:طبقه|واحد)\s+[آ-ی‌ّٰ-ٹ\d\s]+)?(?:،\s*واحد\s+[آ-ی‌ّٰ-ٹ\d\s]+)?',
45
-
46
- # الگوی آدرس کامل: شهر + خیابان + کوچه + پلاک + طبقه
47
- r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)،\s*(?:خیابان|کوچه|شهرک|بلوار|میدان|کوی|محله)\s+[آ-ی‌ّٰ-ٹ\s]+(?:،\s*(?:خیابان|کوچه|بلوار|کوی)\s+[آ-ی‌ّٰ-ٹ\s]+)?(?:،\s*پلاک\s+\d+)?(?:،\s*(?:طبقه|واحد)\s+[آ-ی‌ّٰ-ٹ\d\s]+)?',
48
-
49
- # الگوی آدرس با شهرک
50
- r'(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|بهارستان|چهاردانگه)،\s*شهرک\s+[آ-ی‌ّٰ-ٹ\s]+،\s*(?:خیابان|کوچه|بلوار)\s+[آ-ی‌ّٰ-ٹ\s]+(?:،\s*پلاک\s+\d+)?',
51
-
52
- # الگوی ساده‌تر برای آدرس‌های کوتاه‌تر
53
- r'خیابان\s+[آ-ی‌ّٰ-ٹ\s]+،\s*کوچه\s+[آ-ی‌ّٰ-ٹ\s]+،\s*پلاک\s+\d+(?:،\s*(?:طبقه|واحد)\s+[آ-ی‌ّٰ-ٹ\d\s]+)?',
54
- ],
55
-
56
- # اسامی اشخاص - الگوهای دقیق‌تر شامل خانم
57
- 'PERSON': [
58
- r'آقای\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|\s+مدیر|$|،|\.)',
59
- r'خانم\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|\s+با\s+موبایل|$|،|\.)',
60
- r'مهندس\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|$|،|\.)',
61
- r'دکتر\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+(?=\s+با\s+کد|$|،|\.)',
62
- r'مدیر\s+مالی\s+خانم\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+',
63
- r'مدیرعامل\s+[آ-ی‌ّٰ-ٹ]+\s+[آ-ی‌ّٰ-ٹ]+',
64
- r'Mr\.\s+[A-Z][a-z]+\s+[A-Z][a-z]+(?=\s|,|\.|$)',
65
- r'Ms\.\s+[A-Z][a-z]+\s+[A-Z][a-z]+(?=\s|,|\.|$)',
66
- r'Dr\.\s+[A-Z][a-z]+\s+[A-Z][a-z]+(?=\s|,|\.|$)',
67
- ],
68
-
69
- # کدهای ملی و شناسه‌ها - جداسازی از شماره تلفن
70
- 'ID_NUMBER': [
71
- r'کد\s+ملی\s+\d{10}',
72
- r'شناسه\s+ملی\s+\d{11}',
73
- r'(?<!09)(?<!021-)(?<![0-9])\d{10}(?![0-9])', # کد ملی 10 رقمی مستقل
74
- r'(?<!09)(?<!021-)(?<![0-9])\d{11}(?![0-9])', # شناسه 11 رقمی مستقل
75
- ],
76
-
77
- # مبالغ مالی - جداسازی از شماره تلفن
78
- 'AMOUNT': [
79
- r'\d{6,}\s*تومان', # مبالغ 6 رقمی یا بیشتر با کلمه تومان
80
- r'مبلغ\s+\d{6,}(?:\s*تومان)?',
81
- r'موجودی\s+حساب\s+[^\s]+\s+حدود\s+\d{6,}\s*تومان',
82
- r'ارزش\s+روز\s+آن\s+\d{6,}\s*تومان',
83
- r'میانگین\s+موجودی\s+حساب\s+وی\s+حدود\s+\d{6,}\s*تومان',
84
- r'\d+\s*درصد\s+مبلغ\s+معادل\s+\d{6,}\s*تومان',
85
- r'\$\d+(?:,\d{3})*(?:\.\d+)?',
86
- ],
87
-
88
- # شماره حساب و کارت بانکی - جداسازی دقیق
89
- 'ACCOUNT': [
90
- r'حساب\s+جاری\s+شماره\s+[\d-]+',
91
- r'شماره\s+[\d-]{8,}(?=\s+در|\s+بانک)', # شماره حساب
92
- ],
93
-
94
- 'CARD_NUMBER': [
95
- r'شماره\s+کارت\s+مربوطه\s+\d{4}-\d{4}-\d{4}-\d{4}',
96
- r'\d{4}-\d{4}-\d{4}-\d{4}(?=\s+می‌باشد|\s+نیز)',
97
- ],
98
-
99
- # شماره تلفن - فقط شماره، نه کل عبارت
100
- 'PHONE': [
101
- r'09\d{9}(?=\s+را)', # فقط شماره موبایل قبل از "را"
102
- r'(?<![0-9])09\d{9}(?![0-9])(?!\s+را\s+اعلام)', # شماره موبایل مستقل
103
- r'موبایل\s+09\d{9}', # با کلمه موبایل
104
- r'021-\d{8}', # تلفن تهران
105
- r'0\d{2,3}-?\d{7,8}', # تلفن‌های شهری
106
- ],
107
-
108
- # تاریخ
109
- 'DATE': [
110
- r'\d{4}/\d{1,2}/\d{1,2}',
111
- r'[۰-۹]{1,2}\s+(?:فروردین|اردیبهشت|خرداد|تیر|مرداد|شهریور|مهر|آبان|آذر|دی|بهمن|اسفند)\s+[۰-۹]{4}',
112
- ],
113
-
114
- # شرکت‌ها - الگوهای گسترده‌تر اصلاح شده
115
- 'COMPANY': [
116
- r'شرکت\s+پردازش\s+داده‌های\s+[آ-ی‌ّٰ-ٹ\s]+',
117
- r'شرکت\s+تولیدی\s+[آ-ی‌ّٰ-ٹ\s]+(?=\s|$|،|\.)',
118
- r'شرکت\s+[آ-ی‌ّٰ-ٹ\s]+(?=\s|$|،|\.)',
119
- r'(?<!همین\s)(?<!این\s)(?<!آن\s)بانک\s+[آ-ی‌ّٰ-ٹ\s]+(?=\s|$|،|\.)', # بانک فقط اگر قبلش همین/این/آن نباشد
120
- r'دفتر\s+حسابداری\s+شرکت',
121
- r'[A-Z][a-zA-Z\s]+(?:Inc|Corp|Corporation|Company|Ltd|Limited|LLC)',
122
- ],
123
-
124
- # شعب و واحدهای تجاری - دقیق‌تر شده
125
- 'BRANCH': [
126
- r'شعبه\s+[آ-ی‌ّٰ-ٹ\s]+\s+بانک\s+[آ-ی‌ّٰ-ٹ\s]+',
127
- r'شعبه\s+مرکزی\s+بانک\s+[آ-ی‌ّٰ-ٹ\s]+',
128
- ],
129
-
130
- # مکان‌ها - شهرها (فقط نام شهرها تنها)
131
- 'LOCATION': [
132
- r'\b(?:تهران|اصفهان|مشهد|شیراز|کرج|اهواز|قم|رشت|کرمان|یزد|بوشهر|ارومیه|همدان|بندر عباس|ساری|اردبیل|خرم‌آباد|ایلام|بیرجند|گرگان|زنجان|سنندج|شهرکرد|سبزوار|قزوین|زاهدان|خوی|مراغه|کاشان|نجف‌آباد|شاهین‌شهر|ملایر|آبادان|دزفول|بابل|آمل|شاهرود|گنبد کاووس|خرمشهر|جهرم|فسا|مرودشت|لار|داراب|فیروزآباد|کازرون|سپیدان|نی‌ریز|استهبان|فارسان|میانه|ورامین|قرچک|ری|پاکدشت|دماوند|فیروزکوه|شهریار|اسلام‌شهر|ملارد|قدس|��هارستان|چهاردانگه)\b(?!\s*،)(?!\s+میدان)(?!\s+خیابان)', # فقط نام شهر تنها
133
- r'استان\s+[آ-ی‌ّٰ-ٹ\s]+',
134
- r'شهر\s+[آ-ی‌ّٰ-ٹ\s]+',
135
- ],
136
-
137
- # شماره فاکتور و اسناد - الگوهای جدید
138
- 'INVOICE_NUMBER': [
139
- r'فاکتور\s+شماره\s+[A-Z]+-\d{4}-\d{4}',
140
- r'[A-Z]+-\d{4}-\d{4}(?=\s+را\s+برای)',
141
- ],
142
-
143
- # دفاتر اسناد رسمی
144
- 'DOCUMENT_OFFICE': [
145
- r'دفتر\s+اسناد\s+رسمی\s+شماره\s+\d+',
146
- r'شماره\s+\d+\s+(?:تهران|اصفهان|مشهد|شیراز|کرج)',
147
- ],
148
-
149
- # ایمیل
150
- 'EMAIL': [
151
- r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
152
- ],
153
- }
154
-
155
- # ترتیب اولویت براساس سیستم ناشناس‌سازی
156
- self.priority_order = [
157
- 'FULL_ADDRESS', # بالاترین اولویت - آدرس کامل قبل از قطعات
158
- 'INVOICE_NUMBER', # شماره فاکتور
159
- 'DOCUMENT_OFFICE', # دفاتر اسناد رسمی
160
- 'EMAIL',
161
- 'CARD_NUMBER', # کارت بانکی
162
- 'ACCOUNT', # حساب بانکی
163
- 'PHONE', # شماره تلفن - اولویت بالا
164
- 'ID_NUMBER', # کد ملی
165
- 'AMOUNT', # مبالغ مالی
166
- 'BRANCH', # شعبه
167
- 'COMPANY', # شرکت
168
- 'LOCATION', # مکان
169
- 'DATE', # تاریخ
170
- 'PERSON', # نام اشخاص
171
- ]
172
-
173
- def clean_entity(self, text):
174
- """تمیز کردن دقیق entity"""
175
- # حذف کلمات اضافی در انتها
176
- text = re.sub(r'\s*(در|که|با|به|از|را|و|یا|است|بوده|نموده|صادر|ارائه|معرفی|برگزار|واقع|مربوط|مطرح|شد|شده)\s*$', '', text, flags=re.IGNORECASE)
177
- # حذف فاصله‌های اضافی
178
- text = re.sub(r'\s+', ' ', text).strip()
179
- return text
180
-
181
- def is_generic_word(self, text):
182
- """بررسی کلمات عمومی که نباید entity محسوب شوند"""
183
- text_clean = text.strip().lower()
184
-
185
- generic_words = {
186
- 'همین بانک', 'این بانک', 'آن بانک', 'بانک مذکور',
187
- 'همین شرکت', 'این شرکت', 'آن شرکت', 'شرکت مذکور',
188
- 'همین شعبه', 'این شعبه', 'آن شعبه', 'شعبه مذکور',
189
- 'همین شهر', 'این شهر', 'آن شهر',
190
- 'شرکت متقاضی', 'دفتر حسابداری شرکت',
191
- 'متقاضی', 'ایشان', 'وی', 'مشتری',
192
- 'بانک', 'شرکت', 'شعبه' # کلمات تنها
193
- }
194
-
195
- # بررسی exact match
196
- if text_clean in generic_words:
197
- return True
198
-
199
- # بررسی کلمات که با همین/این/آن شروع می‌شوند
200
- if text_clean.startswith(('همین ', 'این ', 'آن ')):
201
- return True
202
-
203
- return len(text.strip()) < 3
204
-
205
- def is_valid_entity(self, text, category):
206
- """بررسی دقیق‌تر معتبر بودن entity"""
207
- if len(text) < 3 or len(text) > 100:
208
- return False
209
-
210
- # بررسی کلمات عمومی
211
- if self.is_generic_word(text):
212
- return False
213
-
214
- # کلمات ممنوع که نباید entity باشند
215
- forbidden_words = [
216
- 'شد', 'کرد', 'است', 'بود', 'در', 'که', 'با', 'از', 'به', 'را', 'و', 'یا',
217
- 'شده', 'نموده', 'صادر', 'ارائه', 'معرفی', 'برگزار', 'مطرح', 'واقع'
218
- ]
219
-
220
- if text.lower().strip() in forbidden_words:
221
- return False
222
-
223
- # بررسی‌های خاص برای هر category
224
- if category == 'COMPANY':
225
- # نباید شامل فعل یا کلمات اضافی باشد
226
- if any(word in text.lower() for word in ['برگزار', 'مطرح', 'شد', 'است', 'نموده']):
227
- return False
228
- # باید حداقل یک اسم خاص داشته باشد
229
- if text.strip() in ['شرکت', 'بانک', 'شرکت در', 'بانک در']:
230
- return False
231
-
232
- elif category == 'LOCATION':
233
- # نباید شامل فعل باشد
234
- if any(word in text.lower() for word in ['برگزار', 'شد', 'است', 'واقع']):
235
- return False
236
- # باید نام مکان واقعی باشد
237
- if text.strip() in ['شهر', 'بندر', 'استان']:
238
- return False
239
-
240
- elif category == 'DATE':
241
- # نباید عبارات طولانی باشد
242
- if 'سال مالی' in text:
243
- return False
244
-
245
- elif category == 'PERSON':
246
- # نباید فقط عنوان باشد
247
- if text.strip() in ['آقای', 'خانم', 'مهندس', 'دکتر']:
248
- return False
249
-
250
- return True
251
-
252
- def extract_entities(self, text):
253
- """استخراج entities با دقت بالا و اولویت‌بندی"""
254
- if not text or text.strip() == '':
255
- return {}
256
-
257
- entities = {}
258
- processed_positions = set()
259
-
260
- # پردازش براساس اولویت
261
- for category in self.priority_order:
262
- if category not in self.patterns:
263
- continue
264
-
265
- pattern_list = self.patterns[category]
266
- found_entities = []
267
-
268
- for pattern_str in pattern_list:
269
- try:
270
- pattern = re.compile(pattern_str, re.IGNORECASE | re.MULTILINE)
271
- matches = pattern.finditer(text)
272
-
273
- for match in matches:
274
- # بررسی تداخل با entities قبلی
275
- match_start, match_end = match.span()
276
- overlaps = False
277
-
278
- for proc_start, proc_end in processed_positions:
279
- if not (match_end <= proc_start or match_start >= proc_end):
280
- overlaps = True
281
- break
282
-
283
- if not overlaps:
284
- entity = self.clean_entity(match.group(0))
285
- if self.is_valid_entity(entity, category):
286
- found_entities.append(entity)
287
- processed_positions.add((match_start, match_end))
288
-
289
- except re.error as e:
290
- logger.error(f"Regex error in pattern {pattern_str}: {e}")
291
- continue
292
-
293
- # حذف تکراری‌ها و مرتب‌سازی
294
- if found_entities:
295
- # حذف entities که زیرمجموعه entities دیگر هستند
296
- unique_entities = []
297
- for entity in found_entities:
298
- is_subset = False
299
- for other in found_entities:
300
- if entity != other and entity in other:
301
- is_subset = True
302
- break
303
- if not is_subset:
304
- unique_entities.append(entity)
305
-
306
- entities[category] = sorted(list(set(unique_entities)))
307
-
308
- return entities
309
-
310
- def extract_anonymized_codes(self, text):
311
- """استخراج کدهای ناشناس‌سازی"""
312
- if not text or text.strip() == '':
313
- return {}
314
-
315
- codes = {}
316
- # الگو براساس سیستم ناشناس‌سازی: category_number
317
- pattern = r'([a-zA-Z_]+)_(\d{3})'
318
-
319
- try:
320
- matches = re.finditer(pattern, text, re.IGNORECASE)
321
- for match in matches:
322
- category = match.group(1).upper()
323
- code = match.group(0)
324
-
325
- if category not in codes:
326
- codes[category] = []
327
- codes[category].append(code)
328
-
329
- except Exception as e:
330
- logger.error(f"Error extracting codes: {e}")
331
-
332
- # حذف تکراری‌ها
333
- for category in codes:
334
- codes[category] = sorted(list(set(codes[category])))
335
-
336
- return codes
337
-
338
- # ===== کلاس Benchmark آپدیت شده =====
339
- class UpdatedAnonymizationBenchmark:
340
- def __init__(self):
341
- self.extractor = UpdatedEntityExtractor()
342
-
343
- def _analyze_detailed_issues(self, original_entities, anonymized_codes):
344
- """تحلیل جزئیات مشکلات برای Error Analysis"""
345
- all_categories = set(original_entities.keys()) | set(anonymized_codes.keys())
346
-
347
- detailed_issues = {
348
- 'false_negatives': {}, # entities که از دست رفته‌اند
349
- 'false_positives': {}, # کدهای اضافی
350
- 'perfect_matches': [], # categories بدون مشکل
351
- 'error_analysis': {} # تحلیل خطا برای بهبود الگوها
352
- }
353
-
354
- for category in sorted(all_categories):
355
- orig_entities = original_entities.get(category, [])
356
- anon_codes = anonymized_codes.get(category, [])
357
- orig_count = len(orig_entities)
358
- anon_count = len(anon_codes)
359
-
360
- if orig_count == anon_count:
361
- detailed_issues['perfect_matches'].append(category)
362
-
363
- elif orig_count > anon_count:
364
- # False Negatives - entities از دست رفته
365
- missing_count = orig_count - anon_count
366
- detailed_issues['false_negatives'][category] = {
367
- 'missing_entities': orig_entities, # همه entities که شناسایی نشدند
368
- 'missing_count': missing_count,
369
- 'found_codes': anon_codes,
370
- 'success_rate': (anon_count / orig_count * 100) if orig_count > 0 else 0
371
- }
372
-
373
- else:
374
- # False Positives - کدهای اضافی
375
- extra_count = anon_count - orig_count
376
- detailed_issues['false_positives'][category] = {
377
- 'expected_entities': orig_entities,
378
- 'all_generated_codes': anon_codes,
379
- 'extra_codes_count': extra_count,
380
- 'overdetection_rate': ((extra_count / orig_count) * 100) if orig_count > 0 else 0
381
- }
382
-
383
- return detailed_issues
384
-
385
- def analyze_single_row(self, original_text, anonymized_text, row_number):
386
- """تحلیل دقیق یک ردیف"""
387
- print(f"\n{'='*80}")
388
- print(f"تحلیل دقیق ردیف {row_number} (Error Analysis)")
389
- print(f"{'='*80}")
390
-
391
- print(f"\n📝 متن اصلی ({len(original_text)} کاراکتر):")
392
- print(f"'{original_text[:150]}{'...' if len(original_text) > 150 else ''}'")
393
-
394
- print(f"\n🔒 متن ناشناس‌سازی شده ({len(anonymized_text)} کاراکتر):")
395
- print(f"'{anonymized_text[:150]}{'...' if len(anonymized_text) > 150 else ''}'")
396
-
397
- # استخراج entities از متن اصلی
398
- print(f"\n🔍 Entities استخراج شده از متن اصلی:")
399
- original_entities = self.extractor.extract_entities(original_text)
400
-
401
- total_original_entities = 0
402
- for category, entities in original_entities.items():
403
- print(f"\n 📊 {category} ({len(entities)} عدد):")
404
- for i, entity in enumerate(entities, 1):
405
- print(f" {i}. '{entity}'")
406
- total_original_entities += len(entities)
407
-
408
- if not original_entities:
409
- print(" ❌ هیچ entity ای یافت نشد!")
410
- else:
411
- print(f"\n✅ مجموع entities یافت شده: {total_original_entities}")
412
-
413
- # استخراج کدهای ناشناس‌سازی
414
- print(f"\n🔒 کدهای ناشناس‌سازی:")
415
- anonymized_codes = self.extractor.extract_anonymized_codes(anonymized_text)
416
-
417
- total_anonymized_codes = 0
418
- for category, codes in anonymized_codes.items():
419
- print(f"\n 🔑 {category} ({len(codes)} عدد):")
420
- for i, code in enumerate(codes, 1):
421
- print(f" {i}. '{code}'")
422
- total_anonymized_codes += len(codes)
423
-
424
- if not anonymized_codes:
425
- print(" ❌ هیچ کد ناشناس‌سازی یافت نشد!")
426
- else:
427
- print(f"\n✅ مجموع کدهای ناشناس‌سازی: {total_anonymized_codes}")
428
-
429
- # محاسبه متریک‌ها با جزئیات مشکلات
430
- detailed_issues = self._analyze_detailed_issues(original_entities, anonymized_codes)
431
-
432
- category_metrics = {}
433
- total_tp, total_fp, total_fn = 0, 0, 0
434
-
435
- all_categories = set(original_entities.keys()) | set(anonymized_codes.keys())
436
- for category in all_categories:
437
- original_count = len(original_entities.get(category, []))
438
- anonymized_count = len(anonymized_codes.get(category, []))
439
-
440
- tp = min(original_count, anonymized_count)
441
- fp = max(0, anonymized_count - original_count)
442
- fn = max(0, original_count - anonymized_count)
443
-
444
- precision = tp / (tp + fp) if (tp + fp) > 0 else 0
445
- recall = tp / (tp + fn) if (tp + fn) > 0 else 0
446
- f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
447
-
448
- category_metrics[category] = {
449
- 'original_count': original_count, 'anonymized_count': anonymized_count,
450
- 'tp': tp, 'fp': fp, 'fn': fn,
451
- 'precision': precision, 'recall': recall, 'f1_score': f1_score
452
- }
453
-
454
- total_tp += tp
455
- total_fp += fp
456
- total_fn += fn
457
-
458
- # متریک‌های کلی
459
- overall_precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0
460
- overall_recall = total_tp / (total_tp + total_fn) if (total_tp + total_fn) > 0 else 0
461
- overall_f1 = 2 * (overall_precision * overall_recall) / (overall_precision + overall_recall) if (overall_precision + overall_recall) > 0 else 0
462
- accuracy = total_tp / total_original_entities if total_original_entities > 0 else 0
463
-
464
- print(f"\n🎯 متریک‌های نهایی ردیف {row_number}:")
465
- print(f" TP: {total_tp}, FP: {total_fp}, FN: {total_fn}")
466
- print(f" Precision: {overall_precision:.4f}")
467
- print(f" Recall: {overall_recall:.4f}")
468
- print(f" F1-Score: {overall_f1:.4f}")
469
- print(f" Accuracy: {accuracy:.4f}")
470
-
471
- return {
472
- 'original_entities': original_entities,
473
- 'anonymized_codes': anonymized_codes,
474
- 'category_metrics': category_metrics,
475
- 'detailed_issues': detailed_issues, # اضافه کردن جزئیات مشکلات
476
- 'overall_metrics': {
477
- 'total_original_entities': total_original_entities,
478
- 'total_anonymized_entities': total_anonymized_codes,
479
- 'total_tp': total_tp, 'total_fp': total_fp, 'total_fn': total_fn,
480
- 'precision': overall_precision, 'recall': overall_recall,
481
- 'f1_score': overall_f1, 'accuracy': accuracy
482
- }
483
- }
484
-
485
- def generate_error_analysis_report(self, all_analysis):
486
- """تولید گزارش کامل Error Analysis"""
487
- report = "\n" + "="*80 + "\n"
488
- report += "🔍 ERROR ANALYSIS REPORT - برای بهبود الگوهای regex\n"
489
- report += "="*80 + "\n"
490
-
491
- # جمع‌آوری همه خطاها
492
- all_errors = {
493
- 'false_negatives': {},
494
- 'false_positives': {},
495
- }
496
-
497
- for row_idx, analysis in enumerate(all_analysis, 1):
498
- if 'detailed_issues' in analysis:
499
- issues = analysis['detailed_issues']
500
-
501
- # False Negatives
502
- for category, details in issues.get('false_negatives', {}).items():
503
- if category not in all_errors['false_negatives']:
504
- all_errors['false_negatives'][category] = []
505
- all_errors['false_negatives'][category].extend(details['missing_entities'])
506
-
507
- # False Positives
508
- for category, details in issues.get('false_positives', {}).items():
509
- if category not in all_errors['false_positives']:
510
- all_errors['false_positives'][category] = []
511
- all_errors['false_positives'][category].extend(details['all_generated_codes'])
512
-
513
- # گزارش False Negatives
514
- if all_errors['false_negatives']:
515
- report += "\n❌ CRITICAL: FALSE NEGATIVES (Entities از دست رفته)\n"
516
- report += "این entities در متن اصلی هست اما ناشناس‌سازی نشده:\n\n"
517
-
518
- for category, entities in all_errors['false_negatives'].items():
519
- unique_entities = list(set(entities))
520
- report += f"🔸 {category} ({len(unique_entities)} مورد):\n"
521
- for entity in unique_entities[:10]: # نمایش 10 مورد اول
522
- report += f" '{entity}'\n"
523
- if len(unique_entities) > 10:
524
- report += f" ... و {len(unique_entities)-10} مورد دیگر\n"
525
- report += "\n"
526
-
527
- # گزارش False Positives
528
- if all_errors['false_positives']:
529
- report += "\n⚠️ WARNING: FALSE POSITIVES (کدهای اضافی)\n"
530
- report += "این کدها تولید شده اما entity متناظر در متن اصلی نیست:\n\n"
531
-
532
- for category, codes in all_errors['false_positives'].items():
533
- unique_codes = list(set(codes))
534
- report += f"🔸 {category} ({len(unique_codes)} کد اضافی):\n"
535
- for code in unique_codes[:10]:
536
- report += f" '{code}'\n"
537
- if len(unique_codes) > 10:
538
- report += f" ... و {len(unique_codes)-10} کد دیگر\n"
539
- report += "\n"
540
-
541
- return report
542
-
543
- def generate_error_entities_list(self, all_analysis):
544
- """تولید لیست کامل انتیتی‌های مشکل‌دار"""
545
- error_entities = {
546
- 'false_negatives': {}, # انتیتی‌هایی که از دست رفته‌اند
547
- 'false_positives': {} # کدهای اضافی که تولید شده‌اند
548
- }
549
-
550
- # جمع‌آوری تمام مشکلات از همه ردیف‌ها
551
- for row_idx, analysis in enumerate(all_analysis, 1):
552
- if 'detailed_issues' in analysis:
553
- issues = analysis['detailed_issues']
554
-
555
- # جمع‌آوری False Negatives
556
- for category, details in issues.get('false_negatives', {}).items():
557
- if category not in error_entities['false_negatives']:
558
- error_entities['false_negatives'][category] = {}
559
-
560
- # ��روه‌بندی بر اساس ردیف
561
- if f"ردیف {row_idx}" not in error_entities['false_negatives'][category]:
562
- error_entities['false_negatives'][category][f"ردیف {row_idx}"] = []
563
-
564
- error_entities['false_negatives'][category][f"ردیف {row_idx}"].extend(details['missing_entities'])
565
-
566
- # جمع‌آوری False Positives
567
- for category, details in issues.get('false_positives', {}).items():
568
- if category not in error_entities['false_positives']:
569
- error_entities['false_positives'][category] = {}
570
-
571
- # گروه‌بندی بر اساس ردیف
572
- if f"ردیف {row_idx}" not in error_entities['false_positives'][category]:
573
- error_entities['false_positives'][category][f"ردیف {row_idx}"] = []
574
-
575
- error_entities['false_positives'][category][f"ردیف {row_idx}"].extend(details['all_generated_codes'])
576
-
577
- # تولید گزارش نهایی
578
- report = "\n" + "="*80 + "\n"
579
- report += "🔍 ERROR ANALYSIS - لیست کامل انتیتی‌های مشکل‌دار\n"
580
- report += "="*80 + "\n"
581
-
582
- # FALSE NEGATIVES - انتیتی‌های از دست رفته
583
- if error_entities['false_negatives']:
584
- report += "\n❌ FALSE NEGATIVES (انتیتی‌هایی که باید ناشناس می‌شدند اما نشدند):\n"
585
- report += "="*60 + "\n"
586
-
587
- for category, rows_data in error_entities['false_negatives'].items():
588
- report += f"\n📌 {category}:\n"
589
- all_entities_in_category = []
590
-
591
- for row_name, entities in rows_data.items():
592
- if entities:
593
- report += f" {row_name}:\n"
594
- for entity in entities:
595
- report += f" ❌ '{entity}'\n"
596
- all_entities_in_category.append(entity)
597
-
598
- # خلاصه کلی هر category
599
- unique_entities = list(set(all_entities_in_category))
600
- report += f" 📊 خلاصه {category}: {len(unique_entities)} انتیتی منحصربفرد از دست رفته\n"
601
- report += f" 📋 لیست کامل: {unique_entities[:10]}{'...' if len(unique_entities) > 10 else ''}\n"
602
-
603
- # FALSE POSITIVES - کدهای اضافی
604
- if error_entities['false_positives']:
605
- report += f"\n⚠️ FALSE POSITIVES (کدهایی که اضافه تولید شده‌اند):\n"
606
- report += "="*60 + "\n"
607
-
608
- for category, rows_data in error_entities['false_positives'].items():
609
- report += f"\n📌 {category}:\n"
610
- all_codes_in_category = []
611
-
612
- for row_name, codes in rows_data.items():
613
- if codes:
614
- report += f" {row_name}:\n"
615
- for code in codes:
616
- report += f" ⚠️ '{code}' (کد اضافی)\n"
617
- all_codes_in_category.append(code)
618
-
619
- # خلاصه کلی هر category
620
- unique_codes = list(set(all_codes_in_category))
621
- report += f" 📊 خلاصه {category}: {len(unique_codes)} کد اضافی تولید شده\n"
622
- report += f" 📋 لیست کامل: {unique_codes[:10]}{'...' if len(unique_codes) > 10 else ''}\n"
623
-
624
- # خلاصه کلی
625
- total_fn_categories = len(error_entities['false_negatives'])
626
- total_fp_categories = len(error_entities['false_positives'])
627
-
628
- report += f"\n{'='*60}\n"
629
- report += f"📊 خلاصه کلی ERROR ANALYSIS:\n"
630
- report += f" ❌ Categories با False Negatives: {total_fn_categories}\n"
631
- report += f" ⚠️ Categories با False Positives: {total_fp_categories}\n"
632
-
633
- if total_fn_categories == 0 and total_fp_categories == 0:
634
- report += f"\n✅ هیچ مشکل Error شناسایی نشد - سیستم کاملاً درست کار می‌کند!\n"
635
- else:
636
- report += f"\n🛠️ اقدامات لازم:\n"
637
- if total_fn_categories > 0:
638
- report += f" 1. الگوهای regex را گسترده‌تر کنید تا False Negatives کاهش یابد\n"
639
- if total_fp_categories > 0:
640
- report += f" 2. الگوهای regex را محدودتر کنید تا False Positives کاهش یابد\n"
641
-
642
- report += f"\n{'='*80}\n"
643
-
644
- return report
645
-
646
- def process_csv(self, csv_file_path):
647
- """پردازش فایل CSV"""
648
- try:
649
- # خواندن فایل
650
- df = None
651
- for encoding in ['utf-8', 'utf-8-sig', 'cp1256', 'windows-1256']:
652
- try:
653
- df = pd.read_csv(csv_file_path, encoding=encoding)
654
- print(f"✅ فایل با encoding {encoding} خوانده شد")
655
- break
656
- except UnicodeDecodeError:
657
- continue
658
-
659
- if df is None:
660
- return "❌ خطا: نمی‌توان فایل را خواند"
661
-
662
- print(f"\n📋 اطلاعات فایل CSV:")
663
- print(f" تعداد ردیف‌ها: {len(df)}")
664
- print(f" ستون‌ها: {df.columns.tolist()}")
665
-
666
- # بررسی ستون‌ها
667
- if 'original_text' not in df.columns or 'anonymized_text' not in df.columns:
668
- return f"❌ خطا: فایل باید شامل ستون‌های 'original_text' و 'anonymized_text' باشد"
669
-
670
- if len(df) == 0:
671
- return "❌ خطا: فایل خالی است"
672
-
673
- # پردازش هر ردیف
674
- results = []
675
- all_analysis = []
676
-
677
- for index, row in df.iterrows():
678
- print(f"\n📄 پردازش ردیف {index + 1} از {len(df)}")
679
-
680
- original_text = str(row['original_text']) if pd.notna(row['original_text']) else ""
681
- anonymized_text = str(row['anonymized_text']) if pd.notna(row['anonymized_text']) else ""
682
-
683
- if original_text.strip() == "" and anonymized_text.strip() == "":
684
- print("⚠️ ردیف خالی است، رد می‌شود")
685
- continue
686
-
687
- # تحلیل دقیق
688
- analysis = self.analyze_single_row(original_text, anonymized_text, index + 1)
689
- all_analysis.append(analysis)
690
-
691
- # ذخیره نتیجه
692
- result = {
693
- 'row_id': int(index),
694
- 'original_text': original_text,
695
- 'anonymized_text': anonymized_text,
696
- **{k: convert_to_serializable(v) for k, v in analysis['overall_metrics'].items()}
697
- }
698
-
699
- # اضافه کردن متریک‌های category
700
- for category, metrics in analysis['category_metrics'].items():
701
- for metric_name, value in metrics.items():
702
- result[f'{category.lower()}_{metric_name}'] = convert_to_serializable(value)
703
-
704
- results.append(result)
705
-
706
- if not results:
707
- return "❌ خطا: هیچ ردیف معتبری برای پردازش یافت نشد"
708
-
709
- return pd.DataFrame(results), all_analysis
710
-
711
- except Exception as e:
712
- return f"❌ خطا در پردازش: {str(e)}"
713
-
714
- # ===== رابط Gradio آپدیت شده =====
715
- def process_uploaded_file(file):
716
- """پردازش فایل آپلود شده"""
717
- if file is None:
718
- return "❌ لطفاً ابتدا فایل CSV را آپلود کنید.", None, ""
719
-
720
- print(f"\n🚀 شروع Error Analysis: {file.name}")
721
-
722
- benchmark = UpdatedAnonymizationBenchmark()
723
- result = benchmark.process_csv(file.name)
724
-
725
- if isinstance(result, str):
726
- return result, None, ""
727
-
728
- results_df, all_analysis = result
729
-
730
- # تولید گزارش Error Analysis برای بهبود برنامه اصلی
731
- error_analysis_report = benchmark.generate_error_analysis_report(all_analysis)
732
-
733
- # تولید لیست کامل انتیتی‌های مشکل‌دار
734
- error_entities_list = benchmark.generate_error_entities_list(all_analysis)
735
-
736
- # تولید گزارش نهایی
737
- total_rows = len(results_df)
738
-
739
- # محاسبه آمار کلی
740
- avg_precision = results_df['precision'].mean() if 'precision' in results_df.columns else 0
741
- avg_recall = results_df['recall'].mean() if 'recall' in results_df.columns else 0
742
- avg_f1 = results_df['f1_score'].mean() if 'f1_score' in results_df.columns else 0
743
- avg_accuracy = results_df['accuracy'].mean() if 'accuracy' in results_df.columns else 0
744
-
745
- total_original = results_df['total_original_entities'].sum() if 'total_original_entities' in results_df.columns else 0
746
- total_anonymized = results_df['total_anonymized_entities'].sum() if 'total_anonymized_entities' in results_df.columns else 0
747
- total_tp = results_df['total_tp'].sum() if 'total_tp' in results_df.columns else 0
748
- total_fp = results_df['total_fp'].sum() if 'total_fp' in results_df.columns else 0
749
- total_fn = results_df['total_fn'].sum() if 'total_fn' in results_df.columns else 0
750
-
751
- # گزارش نهایی با تمرکز روی Error Analysis
752
- report = f"""
753
- {'='*80}
754
- 🔍 ERROR ANALYSIS BENCHMARK - تشخیص دقیق مشکلات برای بهبود سیستم
755
- {'='*80}
756
-
757
- 📊 آمار سریع:
758
- • ردیف‌های پردازش شده: {total_rows}
759
- • کل Entities: {total_original} | کل کدهای تولید شده: {total_anonymized}
760
- • False Positives: {total_fp} | False Negatives: {total_fn}
761
- • Precision: {avg_precision:.4f} | Recall: {avg_recall:.4f}
762
-
763
- {error_analysis_report}
764
-
765
- {'='*80}
766
- ⚡ ACTION ITEMS برای بهبود سیستم ناشناس‌سازی:
767
- 1. الگوهای regex مشکل‌دار را بر اساس گزارش بالا اصلاح کنید
768
- 2. False Negatives → entities مهم از دست رفته، الگو باید گسترده‌تر شود
769
- 3. False Positives → الگو خیلی کلی است، محدودیت بیشتر اضافه کنید
770
- 4. جزئیات دقیق در بخش ERROR ANALYSIS ارائه شده است
771
- {'='*80}
772
- """
773
-
774
- # ذخیره نتایج
775
- try:
776
- results_df.to_csv("error_analysis_results.csv", index=False, encoding='utf-8-sig')
777
- print("✅ نتایج Error Analysis در فایل error_analysis_results.csv ذخیره شد")
778
- except Exception as e:
779
- print(f"⚠️ خطا در ذخیره فایل: {e}")
780
-
781
- # ستون‌های مهم برای نمایش
782
- display_columns = ['row_id', 'total_original_entities', 'total_anonymized_entities',
783
- 'total_tp', 'total_fp', 'total_fn', 'precision', 'recall', 'f1_score', 'accuracy']
784
-
785
- display_df = results_df[[col for col in display_columns if col in results_df.columns]]
786
-
787
- return report, display_df, error_entities_list
788
-
789
- def download_results():
790
- """دانلود نتایج"""
791
- if os.path.exists("error_analysis_results.csv"):
792
- return "error_analysis_results.csv"
793
- return None
794
-
795
- # ===== رابط اصلی =====
796
- def main():
797
- with gr.Blocks(title="Error Analysis Benchmark", theme=gr.themes.Soft()) as demo:
798
-
799
- gr.HTML("""
800
- <h1 style='text-align: center; color: #2E86AB; margin-bottom: 30px;'>
801
- 🔍 Error Analysis Benchmark - شناسایی دقیق مشکلات برای بهبود سیستم ناشناس‌سازی
802
- </h1>
803
- """)
804
-
805
- with gr.Row():
806
- with gr.Column():
807
- gr.HTML("""
808
- <div style='background: #fff3e0; padding: 15px; border-radius: 10px; margin-bottom: 15px;'>
809
- <h3>🔧 Error Analysis - هدف:</h3>
810
- <ul>
811
- <li><b>False Negatives:</b> Entities مهم که از دست رفته‌اند</li>
812
- <li><b>False Positives:</b> کدهای اضافی که غلط تولید شده‌اند</li>
813
- <li><b>Pattern Problems:</b> تشخیص الگوهای regex مشکل‌دار</li>
814
- <li><b>Root Cause:</b> علت اصلی مشکلات</li>
815
- <li><b>Suggestions:</b> پیشنهادات دقیق برای بهبود کد</li>
816
- <li><b>Action Items:</b> لیست کارهای لازم برای اصلاح</li>
817
- </ul>
818
- </div>
819
- """)
820
-
821
- file_input = gr.File(
822
- label="📁 فایل CSV خود را آپلود کنید",
823
- file_types=[".csv"],
824
- file_count="single"
825
- )
826
-
827
- process_btn = gr.Button("🔍 Error Analysis - تشخیص مشکلات دقیق", variant="primary", size="lg")
828
-
829
- # بخش اول: گزارش اصلی
830
- with gr.Row():
831
- with gr.Column():
832
- gr.HTML("<h3>📊 Error Analysis Report + پیشنهادات بهبود</h3>")
833
-
834
- results_output = gr.Textbox(
835
- label="Error Analysis Report - مشکلات دقیق و راه‌حل‌ها",
836
- lines=30,
837
- max_lines=35,
838
- interactive=False
839
- )
840
-
841
- # بخش دوم: لیست کامل انتیتی‌های مشکل‌دار
842
- with gr.Row():
843
- with gr.Column():
844
- gr.HTML("<h3>🔍 ERROR ANALYSIS - لیست کامل انتیتی‌های مشکل‌دار</h3>")
845
-
846
- error_analysis_output = gr.Textbox(
847
- label="لیست کامل False Negatives و False Positives",
848
- lines=20,
849
- max_lines=25,
850
- interactive=False,
851
- placeholder="لیست دقیق انتیتی‌های مشکل‌دار اینجا نمایش داده می‌شود..."
852
- )
853
-
854
- # بخش سوم: جدول نتایج
855
- with gr.Row():
856
- with gr.Column():
857
- gr.HTML("<h3>📋 جدول نتایج Error Analysis</h3>")
858
-
859
- results_table = gr.Dataframe(
860
- label="متریک‌های دقیق هر ردیف (Error Analysis)",
861
- interactive=False,
862
- wrap=True
863
- )
864
-
865
- # بخش چهارم: دانلود
866
- with gr.Row():
867
- with gr.Column():
868
- download_btn = gr.Button("💾 دانلود گزارش Error Analysis", variant="secondary")
869
- download_file = gr.File(label="فایل گزارش Error Analysis", visible=False)
870
-
871
- # Event handlers - همه متغیرها در scope هستند
872
- process_btn.click(
873
- fn=process_uploaded_file,
874
- inputs=[file_input],
875
- outputs=[results_output, results_table, error_analysis_output]
876
- )
877
-
878
- download_btn.click(
879
- fn=download_results,
880
- outputs=[download_file]
881
- )
882
-
883
- download_btn.click(
884
- fn=lambda: gr.update(visible=True),
885
- outputs=[download_file]
886
- )
887
-
888
- return demo
889
-
890
- demo = main()
891
-
892
- if __name__ == "__main__":
893
- port = int(os.getenv("PORT", "7860"))
894
- demo.launch(
895
- share=False,
896
- server_name="0.0.0.0",
897
- server_port=port,
898
- show_error=True
899
- )