PMI25 commited on
Commit
c1ca391
·
verified ·
1 Parent(s): 803eff1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +358 -0
app.py ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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)