Baidarkaa commited on
Commit
cc6d0dd
·
verified ·
1 Parent(s): bc96b03

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +338 -36
app.py CHANGED
@@ -1,47 +1,349 @@
1
  import time
 
2
  import gradio as gr
3
- from transformers import pipeline
4
 
5
- TASK = "token-classification"
6
- MODEL_NAME = "tanaos/tanaos-NER-v1"
7
- pipe = pipeline(TASK, model=MODEL_NAME)
 
 
 
 
8
 
9
- MAX_CHARS = 2000
 
10
 
11
- def run(text: str):
12
- if text is None or not text.strip():
13
- return "Ошибка: пустой ввод.", None, None
14
- t0 = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  try:
16
- result = pipe(text)
17
- latency = round((time.time() - t0) * 1000, 1)
18
- return "OK", result, f"{latency} ms"
 
 
 
 
 
 
 
19
  except Exception as e:
20
- return f"Ошибка: {type(e).__name__}: {e}", None,
21
- None
22
-
23
- with gr.Blocks() as demo:
24
- gr.Markdown(f"""
25
- # NER-приложение (Hugging Face Spaces + Gradio)
26
- **Задача:** {TASK}
27
- **Модель:** {MODEL_NAME}
28
- """)
29
 
30
- inp = gr.Textbox(label="Введите текст", lines=6, placeholder="Скопируйте сюда текст...")
31
- btn = gr.Button("Обработать")
32
- status = gr.Textbox(label="Статус")
33
- out = gr.JSON(label="Результат модели")
34
- latency = gr.Textbox(label="Время ответа")
 
 
 
35
 
36
- btn.click(run, inputs=inp, outputs=[status, out, latency])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- gr.Examples(
39
- examples=[
40
- ["I love this product! It works great."],
41
- ["This is the worst experience ever."],
42
- ["It's okay, nothing special."]
43
- ],
44
- inputs=inp
45
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- demo.launch()
 
 
 
 
 
 
1
  import time
2
+ import re
3
  import gradio as gr
4
+ from transformers import pipeline, AutoTokenizer, AutoModelForTokenClassification
5
 
6
+ # ================== КОНФИГУРАЦИЯ ==================
7
+ # Можно легко добавить новые модели
8
+ MODELS = {
9
+ "Davlan/xlm-roberta-large-ner-hrl": "Многоязычная (50+ языков, 7 типов сущностей)",
10
+ "Babelscape/wikineural-multilingual-ner": "Многоязычная (википедия, 4 типа)",
11
+ "ai-forever/ruBert-base-ner": "Русская (4 типа сущностей)",
12
+ }
13
 
14
+ # Выбранная модель по умолчанию
15
+ DEFAULT_MODEL = "Davlan/xlm-roberta-large-ner-hrl"
16
 
17
+ # Цвета для разных типов сущностей (для красивого отображения)
18
+ ENTITY_COLORS = {
19
+ "PER": "#FF6B6B", # персона
20
+ "ORG": "#4ECDC4", # организация
21
+ "LOC": "#FFD166", # локация
22
+ "MISC": "#06D6A0", # прочее
23
+ "PERSON": "#FF6B6B",
24
+ "ORGANIZATION": "#4ECDC4",
25
+ "LOCATION": "#FFD166",
26
+ "DATE": "#118AB2",
27
+ "TIME": "#073B4C",
28
+ }
29
+
30
+ MAX_CHARS = 2000 # ограничение длины текста
31
+
32
+ # ================== ИНИЦИАЛИЗАЦИЯ ==================
33
+ def load_model(model_name):
34
+ """Загрузка модели и токенизатора"""
35
  try:
36
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
37
+ model = AutoModelForTokenClassification.from_pretrained(model_name)
38
+ nlp_pipeline = pipeline(
39
+ "ner",
40
+ model=model,
41
+ tokenizer=tokenizer,
42
+ aggregation_strategy="simple", # группируем токены
43
+ device=-1 # CPU (для Space)
44
+ )
45
+ return nlp_pipeline
46
  except Exception as e:
47
+ raise Exception(f"Ошибка загрузки модели: {str(e)}")
 
 
 
 
 
 
 
 
48
 
49
+ # Загружаем модель по умолчанию
50
+ try:
51
+ pipe = load_model(DEFAULT_MODEL)
52
+ current_model_name = DEFAULT_MODEL
53
+ except Exception as e:
54
+ print(f"Warning: {e}")
55
+ pipe = None
56
+ current_model_name = None
57
 
58
+ # ================== ОСНОВНАЯ ФУНКЦИЯ ==================
59
+ def extract_entities(text, model_choice):
60
+ global pipe, current_model_name
61
+
62
+ # Проверка ввода
63
+ if not text or not text.strip():
64
+ return "⚠️ Введите текст для анализа", None, None, None, None
65
+
66
+ text = text.strip()
67
+
68
+ # Ограничение длины
69
+ if len(text) > MAX_CHARS:
70
+ text = text[:MAX_CHARS]
71
+
72
+ # Загрузка новой модели при необходимости
73
+ if model_choice != current_model_name:
74
+ try:
75
+ pipe = load_model(model_choice)
76
+ current_model_name = model_choice
77
+ except Exception as e:
78
+ return f"❌ Ошибка загрузки модели: {str(e)}", None, None, None, None
79
+
80
+ # Измерение времени
81
+ start_time = time.time()
82
+
83
+ try:
84
+ # Выполнение NER
85
+ entities = pipe(text)
86
+ latency = round((time.time() - start_time) * 1000, 1)
87
+
88
+ # Форматирование результатов
89
+ if not entities:
90
+ formatted_result = "Сущности не обнаружены"
91
+ html_output = "<p>Сущности не обнаружены</p>"
92
+ else:
93
+ # 1. Структурированный список
94
+ formatted_result = []
95
+ for entity in entities:
96
+ entity_info = {
97
+ "Текст": entity['word'],
98
+ "Тип": entity['entity_group'],
99
+ "Уверенность": round(entity['score'], 3),
100
+ "Позиция": f"{entity['start']}-{entity['end']}"
101
+ }
102
+ formatted_result.append(entity_info)
103
+
104
+ # 2. Подсветка в тексте (HTML)
105
+ html_parts = []
106
+ last_end = 0
107
+
108
+ for entity in sorted(entities, key=lambda x: x['start']):
109
+ # Текст до сущности
110
+ if entity['start'] > last_end:
111
+ html_parts.append(text[last_end:entity['start']])
112
+
113
+ # Сущность с подсветкой
114
+ color = ENTITY_COLORS.get(entity['entity_group'], "#CCCCCC")
115
+ html_parts.append(
116
+ f'<span style="background-color: {color}; padding: 2px 4px; '
117
+ f'border-radius: 3px; margin: 2px;" title="{entity["entity_group"]} '
118
+ f'(уверенность: {entity["score"]:.2f})">{text[entity["start"]:entity["end"]]}</span>'
119
+ )
120
+
121
+ last_end = entity['end']
122
+
123
+ # Остаток текста
124
+ if last_end < len(text):
125
+ html_parts.append(text[last_end:])
126
+
127
+ html_output = '<div style="line-height: 1.8; font-size: 16px;">' + ''.join(html_parts) + '</div>'
128
+
129
+ # 3. Статистика
130
+ stats = {}
131
+ for entity in entities:
132
+ etype = entity['entity_group']
133
+ stats[etype] = stats.get(etype, 0) + 1
134
+
135
+ stats_text = " | ".join([f"{k}: {v}" for k, v in stats.items()])
136
+
137
+ return (
138
+ "✅ Анализ завершен",
139
+ formatted_result,
140
+ html_output,
141
+ stats_text,
142
+ f"{latency} мс"
143
+ )
144
+
145
+ except Exception as e:
146
+ return f"❌ Ошибка: {str(e)}", None, None, None, None
147
 
148
+ # ================== ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ==================
149
+ def anonymize_text(text, entities):
150
+ """Базовая анонимизация текста (для демонстрации)"""
151
+ if not entities:
152
+ return text
153
+
154
+ result = text
155
+ # Заменяем в обратном порядке, чтобы не сбивались индексы
156
+ for entity in sorted(entities, key=lambda x: x['start'], reverse=True):
157
+ if entity['entity_group'] in ['PER', 'PERSON']:
158
+ replacement = '[ЛИЦО]'
159
+ elif entity['entity_group'] in ['ORG', 'ORGANIZATION']:
160
+ replacement = '[ОРГАНИЗАЦИЯ]'
161
+ elif entity['entity_group'] in ['LOC', 'LOCATION']:
162
+ replacement = '[МЕСТО]'
163
+ else:
164
+ replacement = f'[{entity["entity_group"]}]'
165
+
166
+ result = result[:entity['start']] + replacement + result[entity['end']:]
167
+
168
+ return result
169
+
170
+ def batch_process(files):
171
+ """Обработка нескольких файлов"""
172
+ if not files:
173
+ return "⚠️ Загрузите файлы", []
174
+
175
+ results = []
176
+ for file_info in files:
177
+ try:
178
+ with open(file_info.name, 'r', encoding='utf-8') as f:
179
+ text = f.read(MAX_CHARS) # ограничиваем длину
180
+ _, entities, _, stats, _ = extract_entities(text, DEFAULT_MODEL)
181
+ results.append({
182
+ "Файл": file_info.name,
183
+ "Сущности": len(entities) if entities else 0,
184
+ "Статистика": stats
185
+ })
186
+ except Exception as e:
187
+ results.append({
188
+ "Файл": file_info.name,
189
+ "Ошибка": str(e)
190
+ })
191
+
192
+ return "✅ Обработка завершена", results
193
+
194
+ # ================== ИНТЕРФЕЙС GRADIO ==================
195
+ with gr.Blocks(title="NER — Извлечение сущностей", theme=gr.themes.Soft()) as demo:
196
+
197
+ # Заголовок
198
+ gr.Markdown("""
199
+ # 🔍 Извлечение именованных сущностей (NER)
200
+ **Распознавание имен, организаций, локаций и других сущностей в тексте**
201
+
202
+ *Используются модели из Hugging Face Hub*
203
+ """)
204
+
205
+ # Основные вкладки
206
+ with gr.Tabs():
207
+ # Вкладка 1: Основной анализ
208
+ with gr.TabItem("📝 Анализ текста"):
209
+ with gr.Row():
210
+ with gr.Column(scale=2):
211
+ # Выбор модели
212
+ model_dropdown = gr.Dropdown(
213
+ choices=list(MODELS.keys()),
214
+ value=DEFAULT_MODEL,
215
+ label="Выберите модель",
216
+ info="Разные модели поддерж��вают разные языки и типы сущностей"
217
+ )
218
+
219
+ # Поле ввода
220
+ text_input = gr.Textbox(
221
+ label="Введите текст для анализа",
222
+ placeholder="Пример: Компания Microsoft, основанная Биллом Гейтсом, находится в Редмонде, штат Вашингтон.",
223
+ lines=8,
224
+ max_length=MAX_CHARS
225
+ )
226
+
227
+ # Кнопки
228
+ with gr.Row():
229
+ analyze_btn = gr.Button("🔎 Анализировать", variant="primary")
230
+ clear_btn = gr.Button("🗑️ Очистить")
231
+
232
+ # Примеры
233
+ gr.Examples(
234
+ examples=[
235
+ ["Apple решила открыть новый офис в Париже, где Тим Кук встретится с президентом Франции."],
236
+ ["Вчера в Москве прошла встреча представителей Google и Яндекса, обсудили ИИ с Илоном Маском."],
237
+ ["Президент США Джо Байден выступил в Белом доме на встрече с генеральным директором Tesla."],
238
+ ["The Eiffel Tower in Paris is visited by millions of tourists every year from all over the world."]
239
+ ],
240
+ inputs=text_input,
241
+ label="Примеры текстов"
242
+ )
243
+
244
+ with gr.Column(scale=3):
245
+ # Статус
246
+ status = gr.Textbox(label="Статус")
247
+
248
+ # Результаты в разных форматах
249
+ with gr.Tab("📊 Структурированный"):
250
+ result_json = gr.JSON(label="Найденные сущности")
251
+
252
+ with gr.Tab("🎨 Визуализация"):
253
+ result_html = gr.HTML(label="Текст с подсветкой сущностей")
254
+
255
+ # Статистика
256
+ stats_output = gr.Textbox(label="Статистика")
257
+
258
+ # Производительность
259
+ with gr.Row():
260
+ latency_output = gr.Textbox(label="Время обработки")
261
+
262
+ # Анонимизация
263
+ with gr.Accordion("🛡️ Анонимизация текста", open=False):
264
+ anonymized_text = gr.Textbox(
265
+ label="Анонимизированный текст",
266
+ lines=4,
267
+ interactive=False
268
+ )
269
+ anonymize_btn = gr.Button("Анонимизировать")
270
+
271
+ # Обработчики кнопок
272
+ analyze_btn.click(
273
+ fn=extract_entities,
274
+ inputs=[text_input, model_dropdown],
275
+ outputs=[status, result_json, result_html, stats_output, latency_output]
276
+ )
277
+
278
+ clear_btn.click(
279
+ fn=lambda: ["", None, None, None, None, ""],
280
+ outputs=[text_input, result_json, result_html, stats_output, latency_output, anonymized_text]
281
+ )
282
+
283
+ anonymize_btn.click(
284
+ fn=lambda text, entities: anonymize_text(text, entities) if entities else "Сначала выполните анализ",
285
+ inputs=[text_input, result_json],
286
+ outputs=anonymized_text
287
+ )
288
+
289
+ # Вкладка 2: Пакетная обработка
290
+ with gr.TabItem("📁 Пакетная обработка"):
291
+ gr.Markdown("### Загрузите текстовые файлы (.txt)")
292
+
293
+ file_input = gr.File(
294
+ label="Выберите файлы",
295
+ file_count="multiple",
296
+ file_types=[".txt"]
297
+ )
298
+
299
+ batch_btn = gr.Button("🚀 Обработать файлы", variant="primary")
300
+
301
+ batch_status = gr.Textbox(label="Статус обработки")
302
+ batch_results = gr.JSON(label="Результаты")
303
+
304
+ batch_btn.click(
305
+ fn=batch_process,
306
+ inputs=file_input,
307
+ outputs=[batch_status, batch_results]
308
+ )
309
+
310
+ # Вкладка 3: О приложении
311
+ with gr.TabItem("ℹ️ О приложении"):
312
+ gr.Markdown(f"""
313
+ ## Информация о приложении
314
+
315
+ **Поддерживаемые модели:**
316
+ {chr(10).join([f"- **{k}**: {v}" for k, v in MODELS.items()])}
317
+
318
+ **Типы сущностей:**
319
+ - **PER/PERSON**: Персона, человек
320
+ - **ORG/ORGANIZATION**: Организация, компания
321
+ - **LOC/LOCATION**: Локация, место
322
+ - **MISC**: Прочие сущности
323
+ - **DATE/TIME**: Даты и время
324
+
325
+ **Ограничения:**
326
+ - Максимальная длина текста: {MAX_CHARS} символов
327
+ - Работает на CPU (бесплатно, но медленнее)
328
+ - Модели могут ошибаться, особенно на сложных текстах
329
+
330
+ **Рекомендации по использованию:**
331
+ 1. Для русских текстов используйте `ai-forever/ruBert-base-ner`
332
+ 2. Для многоязычных текстов используйте `Davlan/xlm-roberta-large-ner-hrl`
333
+ 3. Текст должен быть грамотно написан для лучшего распознавания
334
+ """)
335
+
336
+ # Футер
337
+ gr.Markdown("---")
338
+ gr.Markdown("""
339
+ <div style="text-align: center; color: #666;">
340
+ <small>NLP приложение для извлечения сущностей | Hugging Face Spaces + Gradio</small>
341
+ </div>
342
+ """)
343
 
344
+ # ================== ЗАПУСК ==================
345
+ if __name__ == "__main__":
346
+ demo.launch(
347
+ server_name="0.0.0.0",
348
+ share=False
349
+ )