| | import gradio as gr |
| | import torch |
| | from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification |
| | import time |
| | from datetime import datetime |
| | import pandas as pd |
| | import numpy as np |
| | from typing import List, Dict, Tuple |
| | import logging |
| |
|
| | |
| | logging.basicConfig(level=logging.INFO) |
| | logger = logging.getLogger(__name__) |
| |
|
| | |
| | request_history = [] |
| | MAX_HISTORY = 10 |
| |
|
| | |
| | MODELS = { |
| | "rubert-tiny-sentiment": { |
| | "name": "cointegrated/rubert-tiny-sentiment-balanced", |
| | "description": "Лёгкая модель для русского языка (нейтрал/позитив/негатив)", |
| | "max_length": 512 |
| | }, |
| | "english-distilbert": { |
| | "name": "distilbert-base-uncased-finetuned-sst-2-english", |
| | "description": "Быстрая модель для английского языка (позитив/негатив)", |
| | "max_length": 512 |
| | }, |
| | "multilingual-xlm": { |
| | "name": "nlptown/bert-base-multilingual-uncased-sentiment", |
| | "description": "Мультиязычная модель (1-5 звёзд)", |
| | "max_length": 512 |
| | } |
| | } |
| |
|
| | class SentimentAnalyzer: |
| | def __init__(self): |
| | self.models = {} |
| | self.tokenizers = {} |
| | self.current_model = None |
| | self.current_model_name = None |
| | |
| | def load_model(self, model_key: str): |
| | """Загрузка модели по ключу""" |
| | if model_key in self.models and model_key == self.current_model_name: |
| | return self.models[model_key], self.tokenizers[model_key] |
| | |
| | model_info = MODELS[model_key] |
| | |
| | try: |
| | logger.info(f"Загрузка модели: {model_info['name']}") |
| | |
| | |
| | tokenizer = AutoTokenizer.from_pretrained(model_info['name']) |
| | model = AutoModelForSequenceClassification.from_pretrained(model_info['name']) |
| | |
| | |
| | sentiment_pipeline = pipeline( |
| | "sentiment-analysis", |
| | model=model, |
| | tokenizer=tokenizer, |
| | device=-1 |
| | ) |
| | |
| | |
| | self.models[model_key] = sentiment_pipeline |
| | self.tokenizers[model_key] = tokenizer |
| | self.current_model = sentiment_pipeline |
| | self.current_model_name = model_key |
| | |
| | return sentiment_pipeline, tokenizer |
| | |
| | except Exception as e: |
| | logger.error(f"Ошибка загрузки модели: {e}") |
| | raise |
| | |
| | def analyze_sentiment(self, |
| | text: str, |
| | model_key: str = "rubert-tiny-sentiment", |
| | return_raw: bool = False) -> Dict: |
| | """Анализ тональности текста""" |
| | |
| | if not text or not text.strip(): |
| | return {"error": "Текст не может быть пустым"} |
| | |
| | |
| | if len(text) > 2000: |
| | text = text[:2000] |
| | logger.warning(f"Текст обрезан до 2000 символов") |
| | |
| | start_time = time.time() |
| | |
| | try: |
| | |
| | pipeline_obj, tokenizer = self.load_model(model_key) |
| | |
| | |
| | result = pipeline_obj(text[:MODELS[model_key]["max_length"]]) |
| | |
| | processing_time = time.time() - start_time |
| | |
| | |
| | if isinstance(result, list): |
| | result = result[0] |
| | |
| | response = { |
| | "text": text, |
| | "sentiment": result.get('label', 'N/A'), |
| | "confidence": round(result.get('score', 0), 4), |
| | "processing_time": round(processing_time, 3), |
| | "model": model_key, |
| | "model_description": MODELS[model_key]["description"] |
| | } |
| | |
| | |
| | self._add_to_history(response) |
| | |
| | return response if return_raw else self._format_output(response) |
| | |
| | except Exception as e: |
| | logger.error(f"Ошибка анализа: {e}") |
| | return {"error": f"Ошибка обработки: {str(e)}"} |
| | |
| | def batch_analyze(self, texts: List[str], model_key: str = "rubert-tiny-sentiment") -> List[Dict]: |
| | """Пакетный анализ текстов""" |
| | results = [] |
| | for text in texts: |
| | if text and text.strip(): |
| | result = self.analyze_sentiment(text, model_key, return_raw=True) |
| | results.append(result) |
| | return results |
| | |
| | def _format_output(self, result: Dict) -> str: |
| | """Форматирование вывода""" |
| | if "error" in result: |
| | return f"❌ Ошибка: {result['error']}" |
| | |
| | |
| | sentiment = result["sentiment"].lower() |
| | emoji = "😊" if "pos" in sentiment or "позитив" in sentiment or "4" in sentiment or "5" in sentiment else \ |
| | "😐" if "нейтр" in sentiment or "3" in sentiment or "2" in sentiment else \ |
| | "😞" if "негатив" in sentiment or "neg" in sentiment or "1" in sentiment else "🤔" |
| | |
| | output = f""" |
| | {emoji} **Результат анализа тональности** |
| | |
| | 📝 **Текст:** {result['text'][:100]}... |
| | |
| | 🎭 **Тональность:** {result['sentiment']} |
| | 📊 **Уверенность:** {result['confidence'] * 100:.1f}% |
| | ⏱️ **Время обработки:** {result['processing_time']} сек. |
| | 🤖 **Модель:** {result['model_description']} |
| | """ |
| | return output |
| | |
| | def _add_to_history(self, result: Dict): |
| | """Добавление запроса в историю""" |
| | history_entry = { |
| | "timestamp": datetime.now().strftime("%H:%M:%S"), |
| | "text": result["text"][:50] + "..." if len(result["text"]) > 50 else result["text"], |
| | "sentiment": result.get("sentiment", "N/A"), |
| | "confidence": result.get("confidence", 0), |
| | "model": result.get("model", "N/A") |
| | } |
| | |
| | request_history.insert(0, history_entry) |
| | if len(request_history) > MAX_HISTORY: |
| | request_history.pop() |
| |
|
| | |
| | analyzer = SentimentAnalyzer() |
| |
|
| | |
| | EXAMPLES = [ |
| | ["Это просто потрясающий сервис! Очень доволен качеством обслуживания."], |
| | ["Ужасный опыт, никогда больше не вернусь. Все работает плохо."], |
| | ["Сегодня обычный день, ничего особенного не произошло."], |
| | ["The product is amazing! I love it so much!"], |
| | ["This is the worst purchase I've ever made."], |
| | ["El servicio es bastante aceptable, podría ser mejor."] |
| | ] |
| |
|
| | def analyze_text(text: str, model_choice: str, show_history: bool): |
| | """Основная функция для Gradio""" |
| | if not text or not text.strip(): |
| | return "⚠️ Пожалуйста, введите текст для анализа." |
| | |
| | |
| | result = analyzer.analyze_sentiment(text, model_choice) |
| | |
| | |
| | history_output = "" |
| | if show_history and request_history: |
| | history_df = pd.DataFrame(request_history) |
| | history_output = "\n\n--- 📜 История запросов ---\n" |
| | history_output += history_df.to_string(index=False) |
| | |
| | return result + history_output |
| |
|
| | def batch_process(file): |
| | """Обработка пакета текстов из файла""" |
| | try: |
| | if file.name.endswith('.txt'): |
| | with open(file.name, 'r', encoding='utf-8') as f: |
| | texts = [line.strip() for line in f if line.strip()] |
| | elif file.name.endswith('.csv'): |
| | df = pd.read_csv(file.name) |
| | |
| | texts = df.iloc[:, 0].dropna().astype(str).tolist() |
| | else: |
| | return "❌ Поддерживаются только TXT и CSV файлы" |
| | |
| | if not texts: |
| | return "❌ Файл не содержит текстов для анализа" |
| | |
| | |
| | results = analyzer.batch_analyze(texts[:20]) |
| | |
| | |
| | df_results = pd.DataFrame(results) |
| | |
| | |
| | output_file = "sentiment_results.csv" |
| | df_results.to_csv(output_file, index=False, encoding='utf-8') |
| | |
| | |
| | stats = df_results['sentiment'].value_counts() |
| | stats_text = "📊 **Статистика результатов:**\n" |
| | for sentiment, count in stats.items(): |
| | stats_text += f"{sentiment}: {count} текстов\n" |
| | |
| | return f"✅ Проанализировано {len(results)} текстов\n\n{stats_text}\n\n📥 Результаты сохранены в: {output_file}" |
| | |
| | except Exception as e: |
| | logger.error(f"Ошибка пакетной обработки: {e}") |
| | return f"❌ Ошибка обработки файла: {str(e)}" |
| |
|
| | def calculate_metrics(): |
| | """Расчет метрик качества на тестовых примерах""" |
| | test_cases = [ |
| | ("Это отличный продукт!", "POSITIVE"), |
| | ("Ужасное качество", "NEGATIVE"), |
| | ("Сегодня обычный день", "NEUTRAL"), |
| | ("I love this movie", "POSITIVE"), |
| | ("This is terrible", "NEGATIVE") |
| | ] |
| | |
| | correct = 0 |
| | results = [] |
| | |
| | for text, expected in test_cases: |
| | result = analyzer.analyze_sentiment(text, "rubert-tiny-sentiment", return_raw=True) |
| | predicted = result.get("sentiment", "") |
| | |
| | |
| | is_correct = expected.lower() in predicted.lower() or predicted.lower() in expected.lower() |
| | if is_correct: |
| | correct += 1 |
| | |
| | results.append({ |
| | "Текст": text[:30] + "...", |
| | "Ожидалось": expected, |
| | "Получено": predicted, |
| | "Верно": "✅" if is_correct else "❌" |
| | }) |
| | |
| | accuracy = correct / len(test_cases) |
| | |
| | return pd.DataFrame(results), f"📈 Accuracy на тестовых данных: {accuracy:.2%}" |
| |
|
| | |
| | with gr.Blocks(title="Анализатор тональности текста", theme=gr.themes.Soft()) as demo: |
| | gr.Markdown("# 📊 Анализатор тональности текста") |
| | gr.Markdown("Определите эмоциональную окраску текста: позитив, негатив или нейтрал") |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=2): |
| | text_input = gr.Textbox( |
| | label="Введите текст для анализа", |
| | placeholder="Напишите здесь ваш текст...", |
| | lines=5, |
| | max_lines=10 |
| | ) |
| | |
| | model_choice = gr.Dropdown( |
| | choices=list(MODELS.keys()), |
| | value="rubert-tiny-sentiment", |
| | label="Выберите модель", |
| | info="Выберите модель для анализа тональности" |
| | ) |
| | |
| | with gr.Row(): |
| | analyze_btn = gr.Button("🔍 Анализировать", variant="primary") |
| | clear_btn = gr.Button("Очистить") |
| | |
| | show_history = gr.Checkbox( |
| | label="Показать историю запросов", |
| | value=False |
| | ) |
| | |
| | gr.Markdown("### 📁 Пакетная обработка") |
| | file_input = gr.File( |
| | label="Загрузите TXT или CSV файл", |
| | file_types=[".txt", ".csv"] |
| | ) |
| | batch_btn = gr.Button("📊 Обработать файл") |
| | |
| | with gr.Column(scale=2): |
| | output_text = gr.Markdown(label="Результат анализа") |
| | |
| | gr.Markdown("### 📋 Примеры") |
| | gr.Examples( |
| | examples=EXAMPLES, |
| | inputs=[text_input], |
| | label="Попробуйте эти примеры:" |
| | ) |
| | |
| | gr.Markdown("### 📈 Метрики качества") |
| | metrics_btn = gr.Button("📏 Рассчитать метрики") |
| | metrics_output = gr.Markdown() |
| | metrics_table = gr.Dataframe(label="Результаты тестирования") |
| | |
| | |
| | analyze_btn.click( |
| | fn=analyze_text, |
| | inputs=[text_input, model_choice, show_history], |
| | outputs=output_text |
| | ) |
| | |
| | batch_btn.click( |
| | fn=batch_process, |
| | inputs=file_input, |
| | outputs=output_text |
| | ) |
| | |
| | metrics_btn.click( |
| | fn=calculate_metrics, |
| | outputs=[metrics_table, metrics_output] |
| | ) |
| | |
| | clear_btn.click( |
| | fn=lambda: ("", ""), |
| | outputs=[text_input, output_text] |
| | ) |
| | |
| | gr.Markdown("---") |
| | gr.Markdown("### ℹ️ Информация") |
| | gr.Markdown(""" |
| | - **Ограничения:** Максимальная длина текста - 2000 символов |
| | - **Модели:** Поддерживаются русский, английский и другие языки |
| | - **История:** Сохраняются последние 10 запросов |
| | - **Форматы:** Поддерживается пакетная обработка TXT и CSV файлов |
| | """) |
| |
|
| | if __name__ == "__main__": |
| | demo.launch(share=False, server_name="0.0.0.0", server_port=7860) |