PMI25 commited on
Commit
84c6dec
·
verified ·
1 Parent(s): 6709843

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -303
app.py CHANGED
@@ -2,357 +2,217 @@ import gradio as gr
2
  import torch
3
  from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
4
  import time
5
- from datetime import datetime
6
- import pandas as pd
7
- import numpy as np
8
- from typing import List, Dict, Tuple
9
- import logging
10
 
11
- # Настройка логирования
12
- logging.basicConfig(level=logging.INFO)
13
- logger = logging.getLogger(__name__)
14
-
15
- # Кэш для истории запросов
16
- request_history = []
17
- MAX_HISTORY = 10
18
-
19
- # Доступные модели для анализа тональности
20
  MODELS = {
21
- "rubert-tiny-sentiment": {
22
- "name": "cointegrated/rubert-tiny-sentiment-balanced",
23
- "description": "Лёгкая модель для русского языка (нейтрал/позитив/негатив)",
24
- "max_length": 512
25
- },
26
- "english-distilbert": {
27
- "name": "distilbert-base-uncased-finetuned-sst-2-english",
28
- "description": "Быстрая модель для английского языка (позитив/негатив)",
29
- "max_length": 512
30
- },
31
- "multilingual-xlm": {
32
- "name": "nlptown/bert-base-multilingual-uncased-sentiment",
33
- "description": "Мультиязычная модель (1-5 звёзд)",
34
- "max_length": 512
35
- }
36
  }
37
 
38
- class SentimentAnalyzer:
39
  def __init__(self):
40
- self.models = {}
41
- self.tokenizers = {}
42
- self.current_model = None
43
- self.current_model_name = None
44
-
45
- def load_model(self, model_key: str):
46
- """Загрузка модели по ключу"""
47
- if model_key in self.models and model_key == self.current_model_name:
48
- return self.models[model_key], self.tokenizers[model_key]
49
-
50
- model_info = MODELS[model_key]
51
-
52
- try:
53
- logger.info(f"Загрузка модели: {model_info['name']}")
54
-
55
- # Загружаем токенизатор и модель
56
- tokenizer = AutoTokenizer.from_pretrained(model_info['name'])
57
- model = AutoModelForSequenceClassification.from_pretrained(model_info['name'])
58
-
59
- # Создаем pipeline
60
- sentiment_pipeline = pipeline(
61
- "sentiment-analysis",
62
- model=model,
63
- tokenizer=tokenizer,
64
- device=-1 # CPU mode для Spaces
65
- )
66
-
67
- # Сохраняем в кэш
68
- self.models[model_key] = sentiment_pipeline
69
- self.tokenizers[model_key] = tokenizer
70
- self.current_model = sentiment_pipeline
71
- self.current_model_name = model_key
72
-
73
- return sentiment_pipeline, tokenizer
74
-
75
- except Exception as e:
76
- logger.error(f"Ошибка загрузки модели: {e}")
77
- raise
78
-
79
- def analyze_sentiment(self,
80
- text: str,
81
- model_key: str = "rubert-tiny-sentiment",
82
- return_raw: bool = False) -> Dict:
83
- """Анализ тональности текста"""
84
-
85
- if not text or not text.strip():
86
- return {"error": "Текст не может быть пустым"}
87
-
88
- # Ограничение длины текста
89
- if len(text) > 2000:
90
- text = text[:2000]
91
- logger.warning(f"Текст обрезан до 2000 символов")
92
 
93
  start_time = time.time()
94
 
95
  try:
96
- # Загружаем модель
97
- pipeline_obj, tokenizer = self.load_model(model_key)
98
-
99
- # Выполняем анализ
100
- result = pipeline_obj(text[:MODELS[model_key]["max_length"]])
 
 
 
 
 
101
 
102
- processing_time = time.time() - start_time
 
 
103
 
104
- # Форматируем результат
105
- if isinstance(result, list):
106
- result = result[0]
107
 
108
- response = {
109
- "text": text,
110
- "sentiment": result.get('label', 'N/A'),
111
- "confidence": round(result.get('score', 0), 4),
112
- "processing_time": round(processing_time, 3),
113
- "model": model_key,
114
- "model_description": MODELS[model_key]["description"]
115
- }
116
 
117
- # Добавляем в историю
118
- self._add_to_history(response)
119
 
120
- return response if return_raw else self._format_output(response)
121
 
122
  except Exception as e:
123
- logger.error(f"Ошибка анализа: {e}")
124
- return {"error": f"Ошибка обработки: {str(e)}"}
125
-
126
- def batch_analyze(self, texts: List[str], model_key: str = "rubert-tiny-sentiment") -> List[Dict]:
127
- """Пакетный анализ текстов"""
128
- results = []
129
- for text in texts:
130
- if text and text.strip():
131
- result = self.analyze_sentiment(text, model_key, return_raw=True)
132
- results.append(result)
133
- return results
134
-
135
- def _format_output(self, result: Dict) -> str:
136
- """Форматирование вывода"""
137
- if "error" in result:
138
- return f"❌ Ошибка: {result['error']}"
139
-
140
- # Определяем эмодзи для тональности
141
- sentiment = result["sentiment"].lower()
142
- emoji = "😊" if "pos" in sentiment or "позитив" in sentiment or "4" in sentiment or "5" in sentiment else \
143
- "😐" if "нейтр" in sentiment or "3" in sentiment or "2" in sentiment else \
144
- "😞" if "негатив" in sentiment or "neg" in sentiment or "1" in sentiment else "🤔"
145
-
146
- output = f"""
147
- {emoji} **Результат анализа тональности**
148
 
149
- 📝 **Текст:** {result['text'][:100]}...
 
150
 
151
- 🎭 **Тональность:** {result['sentiment']}
152
- 📊 **Уверенность:** {result['confidence'] * 100:.1f}%
153
- ⏱️ **Время обработки:** {result['processing_time']} сек.
154
- 🤖 **Модель:** {result['model_description']}
155
- """
156
- return output
157
-
158
- def _add_to_history(self, result: Dict):
159
- """Добавление запроса в историю"""
160
- history_entry = {
161
- "timestamp": datetime.now().strftime("%H:%M:%S"),
162
- "text": result["text"][:50] + "..." if len(result["text"]) > 50 else result["text"],
163
- "sentiment": result.get("sentiment", "N/A"),
164
- "confidence": result.get("confidence", 0),
165
- "model": result.get("model", "N/A")
166
- }
167
-
168
- request_history.insert(0, history_entry)
169
- if len(request_history) > MAX_HISTORY:
170
- request_history.pop()
171
-
172
- # Инициализация анализатора
173
- analyzer = SentimentAnalyzer()
174
-
175
- # Примеры для демонстрации
176
- EXAMPLES = [
177
- ["Это просто потрясающий сервис! Очень доволен качеством обслуживания."],
178
- ["Ужасный опыт, никогда больше не вернусь. Все работает плохо."],
179
- ["Сегодня обычный день, ничего особенного не произошло."],
180
- ["The product is amazing! I love it so much!"],
181
- ["This is the worst purchase I've ever made."],
182
- ["El servicio es bastante aceptable, podría ser mejor."]
183
- ]
184
-
185
- def analyze_text(text: str, model_choice: str, show_history: bool):
186
- """Основная функция для Gradio"""
187
- if not text or not text.strip():
188
- return "⚠️ Пожалуйста, введите текст для анализа."
189
-
190
- # Анализ тональности
191
- result = analyzer.analyze_sentiment(text, model_choice)
192
-
193
- # Добавляем историю если нужно
194
- history_output = ""
195
- if show_history and request_history:
196
- history_df = pd.DataFrame(request_history)
197
- history_output = "\n\n--- 📜 История запросов ---\n"
198
- history_output += history_df.to_string(index=False)
199
 
200
- return result + history_output
201
-
202
- def batch_process(file):
203
- """Обработка пакета текстов из файла"""
204
  try:
205
- if file.name.endswith('.txt'):
206
- with open(file.name, 'r', encoding='utf-8') as f:
207
- texts = [line.strip() for line in f if line.strip()]
208
- elif file.name.endswith('.csv'):
209
- df = pd.read_csv(file.name)
210
- # Предполагаем, что тексты в первом столбце
211
- texts = df.iloc[:, 0].dropna().astype(str).tolist()
212
- else:
213
- return "❌ Поддерживаются только TXT и CSV файлы"
214
-
215
- if not texts:
216
- return "❌ Файл не содержит текстов для анализа"
217
 
218
- # Анализируем все тексты
219
- results = analyzer.batch_analyze(texts[:20]) # Ограничиваем 20 текстами
 
220
 
221
- # Создаем DataFrame для отображения
222
- df_results = pd.DataFrame(results)
223
 
224
- # Сохраняем результаты
225
- output_file = "sentiment_results.csv"
226
- df_results.to_csv(output_file, index=False, encoding='utf-8')
227
 
228
- # Статистика
229
- stats = df_results['sentiment'].value_counts()
230
- stats_text = "📊 **Статистика результатов:**\n"
231
- for sentiment, count in stats.items():
232
- stats_text += f"{sentiment}: {count} текстов\n"
233
 
234
- return f"✅ Проанализировано {len(results)} текстов\n\n{stats_text}\n\n📥 Результаты сохранены в: {output_file}"
235
 
 
 
236
  except Exception as e:
237
- logger.error(f"Ошибка пакетной обработки: {e}")
238
- return f"❌ Ошибка обработки файла: {str(e)}"
239
 
240
- def calculate_metrics():
241
- """Расчет метрик качества на тестовых примерах"""
242
- test_cases = [
243
- ("Это отличный продукт!", "POSITIVE"),
244
- ("Ужасное качество", "NEGATIVE"),
245
- ("Сегодня обычный день", "NEUTRAL"),
246
- ("I love this movie", "POSITIVE"),
247
- ("This is terrible", "NEGATIVE")
248
- ]
249
-
250
- correct = 0
251
- results = []
252
-
253
- for text, expected in test_cases:
254
- result = analyzer.analyze_sentiment(text, "rubert-tiny-sentiment", return_raw=True)
255
- predicted = result.get("sentiment", "")
256
-
257
- # Упрощенная проверка (в реальности нужна нормализация)
258
- is_correct = expected.lower() in predicted.lower() or predicted.lower() in expected.lower()
259
- if is_correct:
260
- correct += 1
261
-
262
- results.append({
263
- "Текст": text[:30] + "...",
264
- "Ожидалось": expected,
265
- "Получено": predicted,
266
- "Верно": "✅" if is_correct else "❌"
267
- })
268
-
269
- accuracy = correct / len(test_cases)
270
-
271
- return pd.DataFrame(results), f"📈 Accuracy на тестовых данных: {accuracy:.2%}"
272
 
273
- # Создание интерфейса Gradio
274
- with gr.Blocks(title="Анализатор тональности текста", theme=gr.themes.Soft()) as demo:
275
- gr.Markdown("# 📊 Анализатор тональности текста")
276
- gr.Markdown("Определите эмоциональную окраску текста: позитив, негатив или нейтрал")
277
 
278
  with gr.Row():
279
  with gr.Column(scale=2):
 
 
 
 
 
 
 
280
  text_input = gr.Textbox(
281
- label="Введите текст для анализа",
282
- placeholder="Напишите здесь ваш текст...",
283
  lines=5,
284
  max_lines=10
285
  )
286
 
287
- model_choice = gr.Dropdown(
288
- choices=list(MODELS.keys()),
289
- value="rubert-tiny-sentiment",
290
- label="Выберите модель",
291
- info="Выберите модель для анализа тональности"
292
- )
293
-
294
- with gr.Row():
295
- analyze_btn = gr.Button("🔍 Анализировать", variant="primary")
296
- clear_btn = gr.Button("Очистить")
297
 
298
- show_history = gr.Checkbox(
299
- label="Показать историю запросов",
300
- value=False
 
 
301
  )
302
-
303
- gr.Markdown("### 📁 Пакетная обработка")
304
- file_input = gr.File(
305
- label="Загрузите TXT или CSV файл",
306
- file_types=[".txt", ".csv"]
 
 
 
 
 
 
 
307
  )
308
- batch_btn = gr.Button("📊 Обработать файл")
309
 
310
- with gr.Column(scale=2):
311
- output_text = gr.Markdown(label="Результат анализа")
312
-
313
- gr.Markdown("### 📋 Примеры")
314
- gr.Examples(
315
- examples=EXAMPLES,
316
- inputs=[text_input],
317
- label="Попробуйте эти примеры:"
318
  )
319
-
320
- gr.Markdown("### 📈 Метрики качества")
321
- metrics_btn = gr.Button("📏 Рассчитать метрики")
322
- metrics_output = gr.Markdown()
323
- metrics_table = gr.Dataframe(label="Результаты тестирования")
324
 
325
- # Обработчики событий
326
- analyze_btn.click(
327
- fn=analyze_text,
328
- inputs=[text_input, model_choice, show_history],
329
- outputs=output_text
330
  )
331
 
332
- batch_btn.click(
333
- fn=batch_process,
334
- inputs=file_input,
335
- outputs=output_text
336
- )
337
-
338
- metrics_btn.click(
339
- fn=calculate_metrics,
340
- outputs=[metrics_table, metrics_output]
341
- )
342
-
343
- clear_btn.click(
344
- fn=lambda: ("", ""),
345
- outputs=[text_input, output_text]
346
- )
347
 
348
  gr.Markdown("---")
349
- gr.Markdown("### ℹ️ Информация")
350
  gr.Markdown("""
351
- - **Ограничения:** Максимальная длина текста - 2000 символов
352
- - **Модели:** Поддерживаются русский, английский и другие языки
353
- - **История:** Сохраняются последние 10 запросов
354
- - **Форматы:** Поддерживается пакетная обработка TXT и CSV файлов
355
  """)
356
 
357
  if __name__ == "__main__":
358
- demo.launch(share=False, server_name="0.0.0.0", server_port=7860)
 
2
  import torch
3
  from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
4
  import time
5
+ from typing import Dict, Tuple
6
+ import warnings
7
+ warnings.filterwarnings('ignore')
 
 
8
 
9
+ # Конфигурация
10
+ MAX_LENGTH = 1000
 
 
 
 
 
 
 
11
  MODELS = {
12
+ "cointegrated/rubert-tiny2": "Лёгкая модель (быстрая)",
13
+ "s-nlp/rubert-tiny-cased-rured": "Специализированная для классификации",
14
+ "ai-forever/ruBert-base": "Точная модель (медленнее)"
15
+ }
16
+ LABELS = {
17
+ 0: "Политика",
18
+ 1: "Экономика",
19
+ 2: "Наука и технологии",
20
+ 3: "Культура и искусство",
21
+ 4: "Спорт",
22
+ 5: "Здоровье и медицина",
23
+ 6: "Образование",
24
+ 7: "Разное"
 
 
25
  }
26
 
27
+ class TopicClassifier:
28
  def __init__(self):
29
+ self.models: Dict = {}
30
+ self.tokenizers: Dict = {}
31
+
32
+ def load_model(self, model_name: str):
33
+ """Загрузка модели по требованию"""
34
+ if model_name not in self.models:
35
+ try:
36
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
37
+ model = AutoModelForSequenceClassification.from_pretrained(
38
+ model_name,
39
+ num_labels=len(LABELS)
40
+ )
41
+
42
+ # Настройка для CPU
43
+ model.eval()
44
+
45
+ self.models[model_name] = model
46
+ self.tokenizers[model_name] = tokenizer
47
+
48
+ print(f"Модель {model_name} загружена успешно")
49
+ except Exception as e:
50
+ raise Exception(f"Ошибка загрузки модели: {str(e)}")
51
+
52
+ def predict(self, text: str, model_name: str) -> Tuple[Dict, float]:
53
+ """Предсказание темы текста"""
54
+ if not text.strip():
55
+ raise ValueError("Текст не может быть пустым")
56
+
57
+ if len(text) > MAX_LENGTH:
58
+ text = text[:MAX_LENGTH]
59
+ gr.Warning(f"Текст обрезан до {MAX_LENGTH} символов")
60
+
61
+ self.load_model(model_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  start_time = time.time()
64
 
65
  try:
66
+ tokenizer = self.tokenizers[model_name]
67
+ model = self.models[model_name]
68
+
69
+ inputs = tokenizer(
70
+ text,
71
+ return_tensors="pt",
72
+ truncation=True,
73
+ max_length=512,
74
+ padding=True
75
+ )
76
 
77
+ with torch.no_grad():
78
+ outputs = model(**inputs)
79
+ predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
80
 
81
+ scores = predictions[0].tolist()
82
+ results = {LABELS[i]: round(score * 100, 2) for i, score in enumerate(scores)}
 
83
 
84
+ # Сортировка по уверенности
85
+ sorted_results = dict(sorted(results.items(), key=lambda x: x[1], reverse=True))
 
 
 
 
 
 
86
 
87
+ latency = round((time.time() - start_time) * 1000, 2) # в мс
 
88
 
89
+ return sorted_results, latency
90
 
91
  except Exception as e:
92
+ raise Exception(f"Ошибка при обработке: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ # Инициализация классификатора
95
+ classifier = TopicClassifier()
96
 
97
+ def process_text(text: str, model_choice: str) -> Tuple[str, str, str]:
98
+ """Обработка текста с выбранной моделью"""
99
+ if not text.strip():
100
+ return "⚠️ Введите текст для анализа", "", "0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
 
 
 
 
102
  try:
103
+ predictions, latency = classifier.predict(text, model_choice)
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ # Форматирование результатов
106
+ top_topic = list(predictions.keys())[0]
107
+ top_score = predictions[top_topic]
108
 
109
+ result_text = f"🎯 **Основная тема:** {top_topic} ({top_score}%)\n\n"
110
+ result_text += "📊 **Распределение тем:**\n"
111
 
112
+ for topic, score in predictions.items():
113
+ result_text += f"• {topic}: {score}%\n"
 
114
 
115
+ # Подготовка JSON для отладки
116
+ json_output = "{\n"
117
+ for topic, score in predictions.items():
118
+ json_output += f' "{topic}": {score},\n'
119
+ json_output = json_output.rstrip(",\n") + "\n}"
120
 
121
+ return result_text, json_output, str(latency)
122
 
123
+ except ValueError as e:
124
+ return f"❌ {str(e)}", "", "0"
125
  except Exception as e:
126
+ return f"⚠️ Ошибка: {str(e)}", "", "0"
 
127
 
128
+ # Примеры текстов
129
+ examples = [
130
+ ["Российская экономика показала рост в третьем квартале благодаря увеличению экспорта нефти и газа."],
131
+ ["Ученые создали новый материал для солнечных батарей с эффективностью 45%."],
132
+ ["На чемпионате мира по футболу сборная Бразилии одержала победу со счетом 3:1."],
133
+ ["В музее открылась выставка современных художников, посвященная проблемам экологии."],
134
+ ["Минздрав рекомендовал новые правила вакцинации для населения старше 60 лет."]
135
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ # Создание интерфейса
138
+ with gr.Blocks(title="Классификатор тем текста", theme=gr.themes.Soft()) as demo:
139
+ gr.Markdown("# 🎯 Классификатор тематики текста")
140
+ gr.Markdown("Определите основную тему вашего текста с помощью ИИ-моделей")
141
 
142
  with gr.Row():
143
  with gr.Column(scale=2):
144
+ model_selector = gr.Dropdown(
145
+ choices=list(MODELS.keys()),
146
+ value=list(MODELS.keys())[0],
147
+ label="📋 Выберите модель",
148
+ info="Каждая модель имеет разный баланс скорости и точности"
149
+ )
150
+
151
  text_input = gr.Textbox(
152
+ label="📝 Введите текст для анализа",
153
+ placeholder="Введите текст на русском языке...",
154
  lines=5,
155
  max_lines=10
156
  )
157
 
158
+ process_btn = gr.Button("🔍 Анализировать текст", variant="primary")
 
 
 
 
 
 
 
 
 
159
 
160
+ gr.Markdown("### 📋 Примеры текстов")
161
+ gr.Examples(
162
+ examples=examples,
163
+ inputs=text_input,
164
+ label="Нажмите на пример для быстрой загрузки"
165
  )
166
+
167
+ with gr.Column(scale=3):
168
+ with gr.Row():
169
+ latency_display = gr.Textbox(
170
+ label="⏱️ Время обработки",
171
+ value="0",
172
+ interactive=False
173
+ )
174
+ latency_display.info = "мсек"
175
+
176
+ output_text = gr.Markdown(
177
+ label="📊 Результаты классификации"
178
  )
 
179
 
180
+ json_output = gr.Code(
181
+ label="📄 JSON-формат результатов",
182
+ language="json",
183
+ interactive=False
 
 
 
 
184
  )
 
 
 
 
 
185
 
186
+ # Обработка событий
187
+ process_btn.click(
188
+ fn=process_text,
189
+ inputs=[text_input, model_selector],
190
+ outputs=[output_text, json_output, latency_display]
191
  )
192
 
193
+ # Дополнительная информация
194
+ with gr.Accordion("ℹ️ Информация о моделях", open=False):
195
+ gr.Markdown("""
196
+ **Доступные модели:**
197
+
198
+ 1. **cointegrated/rubert-tiny2** - Быстрая и легкая модель, идеально подходит для CPU
199
+ 2. **s-nlp/rubert-tiny-cased-rured** - Специализирована для тематической классификации
200
+ 3. **ai-forever/ruBert-base** - Самая точная, но требует больше времени
201
+
202
+ **Ограничения:**
203
+ - Максимальная длина текста: 1000 символов
204
+ - Только русский язык
205
+ - Автоматическое определение 8 основных тем
206
+ """)
 
207
 
208
  gr.Markdown("---")
209
+ gr.Markdown("### 📌 Инструкция")
210
  gr.Markdown("""
211
+ 1. Выберите модель из списка
212
+ 2. Введите или вставьте текст для анализа
213
+ 3. Нажмите кнопку "Анализировать текст"
214
+ 4. Получите результаты классификации и время обработки
215
  """)
216
 
217
  if __name__ == "__main__":
218
+ demo.launch(server_name="0.0.0.0", server_port=7860)