Sentiment Analysis: Анализ тональности текста с LLM
nlpsentiment-analysisllmopensourcetext-classification
Введение
Sentiment Analysis (анализ тональности) — это задача определения эмоциональной окраски текста. LLM значительно упростили эту задачу, позволив перейти от простой классификации к детектированию сложных эмоций.
Типы анализа тональности
1. Полярность (Polarity)
Positive (Позитивный):
"Этот телефон просто отличный! Батарея держится весь день."
Negative (Негативный):
"Самый плохий сервис, который я видел. Никому не рекомендую."
Neutral (Нейтральный):
"Телефон был доставлен в понедельник."
2. Эмоции (Emotion Detection)
Joy (Радость):
"Я так счастлив! Мы выиграли матч!"
Anger (Гнев):
"Это просто возмущение! Где мой заказ?"
Sadness (Грусть):
"Мне так грустно. Мой питомец заболел."
Fear (Страх):
"Я беспокоюсь о будущем. Кризис усиливается."
Surprise (Удивление):
"Не могу поверить! Это лучший подарок в моей жизни!"
Disgust (Отвращение):
"Меня тошнит от этого безобразия."
3. Детекция субъективности
Objective (Объективный):
"Компания продала 1 млн устройств в Q3."
Subjective (Субъективный):
"Я думаю, это лучший смартфон на рынке."
Классические подходы
TF-IDF + Naive Bayes
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
# Обучение
model = Pipeline([
('tfidf', TfidfVectorizer(max_features=10000)),
('clf', MultinomialNB())
])
model.fit(train_texts, train_labels)
# Предсказание
prediction = model.predict(["Отличный продукт, очень доволен!"])
# → "positive"
LSTM для классификации
import torch
import torch.nn as nn
class SentimentLSTM(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.lstm = nn.LSTM(
embed_dim, hidden_dim,
num_layers=2,
bidirectional=True,
batch_first=True
)
self.fc = nn.Linear(hidden_dim * 2, num_classes)
self.dropout = nn.Dropout(0.5)
def forward(self, x):
embedded = self.dropout(self.embedding(x))
lstm_out, (hidden, cell) = self.lstm(embedded)
# Объединяем forward и backward hidden states
hidden = torch.cat((hidden[-2], hidden[-1]), dim=1)
return self.fc(self.dropout(hidden))
# Использование
model = SentimentLSTM(vocab_size=30000, embed_dim=128,
hidden_dim=256, num_classes=3)
BERT для sentiment
from transformers import AutoModelForSequenceClassification, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-multilingual")
model = AutoModelForSequenceClassification.from_pretrained(
"sentiment-model",
num_labels=3 # positive, negative, neutral
)
def analyze_sentiment(text):
inputs = tokenizer(
text, return_tensors="pt",
truncation=True, max_length=512
)
outputs = model(**inputs)
probabilities = torch.softmax(outputs.logits, dim=-1)
labels = ["negative", "neutral", "positive"]
sentiment = labels[torch.argmax(probabilities)]
confidence = torch.max(probabilities).item()
return {"sentiment": sentiment, "confidence": confidence}
Sentiment Analysis с LLM
Zero-shot sentiment
from openai import OpenAI
client = OpenAI()
def zero_shot_sentiment(text):
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": """Определи тональность текста.
Ответь одним словом: positive, negative или neutral."""
},
{"role": "user", "content": text}
]
)
return response.choices[0].message.content
# Использование
print(zero_shot_sentiment("Отличный телефон, батарея держит весь день!"))
# → "positive"
print(zero_shot_sentiment("Плохое качество, разбился при падении."))
# → "negative"
Детекция эмоций
def detect_emotions(text):
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": """Определи все эмоции в тексте.
Типы: joy, anger, sadness, fear, surprise, disgust
Ответь в формате JSON:
{
"emotions": [
{"emotion": "joy", "confidence": 0.9},
{"emotion": "surprise", "confidence": 0.6}
],
"dominant": "joy"
}"""
},
{"role": "user", "content": text}
],
response_format={"type": "json_object"}
)
return response.choices[0].message.content
# Результат:
# {
# "emotions": [
# {"emotion": "joy", "confidence": 0.95},
# {"emotion": "surprise", "confidence": 0.7}
# ],
# "dominant": "joy"
# }
Structured output для sentiment
def structured_sentiment(text):
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": f"Анализируй: {text}"}],
response_format={
"type": "json_object",
"schema": {
"sentiment": "positive | negative | neutral",
"confidence": 0.0,
"emotions": {"joy": 0.1, "anger": 0.0, ...},
"key_phrases": ["отличный", "доволен"],
"reasoning": "Текст содержит позитивные слова..."
}
}
)
Sentiment Analysis с локальными моделями
llama.cpp для sentiment
import requests
def local_sentiment(text):
prompt = f"""Определи тональность текста.
Текст: "{text}"
Ответь одним словом: positive, negative или neutral.
Текст: "Отличный продукт!"
Ответ:"""
response = requests.post(
"http://localhost:8080/completion",
json={
"prompt": prompt,
"max_tokens": 20,
"temperature": 0.1
}
)
return response.json()["response"].strip()
Fine-tuned модели для sentiment
from transformers import AutoModelForSequenceClassification, AutoTokenizer
# Multilingual BERT для sentiment
model_name = "cardiffnlp/twitter-xlm-roberta-base-sentiment"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
def analyze_multilingual_sentiment(text):
inputs = tokenizer(
text, return_tensors="pt",
truncation=True, max_length=512
)
with torch.no_grad():
outputs = model(**inputs)
probabilities = torch.softmax(outputs.logits, dim=-1)[0]
labels = ["negative", "neutral", "positive"]
results = {
label: prob.item() for label, prob in zip(labels, probabilities)
}
return {
"sentiment": max(results, key=results.get),
"confidence": max(results.values()),
"all_scores": results
}
DistilBERT для быстрого sentiment
from transformers import pipeline
# Готовый пайплайн
sentiment_pipeline = pipeline(
"sentiment-analysis",
model="distilbert-base-uncased-finetuned-sst-2-english"
)
result = sentiment_pipeline("I love this product! It works great.")
# → [{"label": "POSITIVE", "score": 0.9998}]
# Multilingual
multilingual_pipeline = pipeline(
"sentiment-analysis",
model="papluca/xlm-roberta-base-multilingual-sentiment"
)
result = multilingual_pipeline("Отличный телефон!")
# → [{"label": "POSITIVE", "score": 0.98}]
Batch sentiment analysis
Обработка большого объёма
import pandas as pd
from tqdm import tqdm
def batch_sentiment_analysis(texts, batch_size=32):
results = []
for i in tqdm(range(0, len(texts), batch_size)):
batch = texts[i:i+batch_size]
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Определи тональность каждого текста."},
{"role": "user", "content": "\n".join([f"{i}: {t}" for i, t in enumerate(batch)])}
],
response_format={"type": "json_object"}
)
results.extend(parse_batch_response(response))
return results
Анализ отзывов
def analyze_product_reviews(reviews):
"""Анализ отзывов о продукте"""
results = []
for review in reviews:
sentiment = zero_shot_sentiment(review["text"])
aspects = extract_aspects(review["text"])
results.append({
"review_id": review["id"],
"sentiment": sentiment,
"aspects": aspects,
"date": review["date"]
})
# Агрегация
positive_count = sum(1 for r in results if r["sentiment"] == "positive")
return {
"total": len(results),
"positive": positive_count,
"positive_ratio": positive_count / len(results),
"aspect_sentiment": aspects_analysis(results)
}
Aspect-based sentiment analysis
ABSA
Текст: "Батарея отличная, но экран тусклый."
Обычный sentiment: negative (из-за "но")
ABSA:
Battery: positive
Screen: negative
def aspect_based_sentiment(text):
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": """Извлеки аспекты и определи sentiment для каждого.
Ответь в формате JSON:
{
"aspects": [
{"aspect": "батарея", "sentiment": "positive", "text": "Батарея отличная"},
{"aspect": "экран", "sentiment": "negative", "text": "экран тусклый"}
]
}"""
},
{"role": "user", "content": text}
],
response_format={"type": "json_object"}
)
return response.choices[0].message.content
Sentiment tracking over time
Мониторинг тональности
import matplotlib.pyplot as plt
from datetime import datetime
def plot_sentiment_timeline(daily_sentiments):
dates = [s["date"] for s in daily_sentiments]
positive = [s["positive_ratio"] for s in daily_sentiments]
negative = [s["negative_ratio"] for s in daily_sentiments]
plt.figure(figsize=(12, 6))
plt.plot(dates, positive, label="Positive")
plt.plot(dates, negative, label="Negative")
plt.fill_between(dates, positive, alpha=0.3)
plt.fill_between(dates, negative, alpha=0.3)
plt.legend()
plt.title("Sentiment Timeline")
plt.show()
Практические применения
1. Мониторинг соцсетей
# Отслеживание тональности упоминаний бренда
mentions = get_twitter_mentions("MyBrand")
sentiments = [analyze_sentiment(m["text"]) for m in mentions]
alert_if_negative_ratio(sentiments, threshold=0.3)
2. Анализ поддержки
# Анализ качества поддержки по тикетам
tickets = load_support_tickets()
for ticket in tickets:
sentiment = analyze_sentiment(ticket["customer_message"])
if sentiment["sentiment"] == "negative" and sentiment["confidence"] > 0.8:
escalate(ticket)
3. Финанsentiment
# Анализ тональности новостей для трейдинга
news = load_financial_news()
sentiments = [analyze_sentiment(n["content"]) for n in news]
bullish_ratio = sum(1 for s in sentiments if s["sentiment"] == "positive")
bearish_ratio = sum(1 for s in sentiments if s["sentiment"] == "negative")
if bullish_ratio / len(sentiments) > 0.7:
signal = "BUY"
Open Source инструменты
Модели:
✅ BERT (Google)
✅ RoBERTa (Facebook)
✅ XLM-RoBERTa (multilingual)
✅ DistilBERT (быстрый BERT)
Библиотеки:
✅ VADER (простой, без обучения)
✅ TextBlob (Python)
✅ NLTK Sentiment
✅ Transformers (HuggingFace)
Бенчмарки:
✅ SST (Stanford Sentiment Treebank)
✅ IMDB Reviews
✅ Twitter Sentiment140
Итоги
Sentiment Analysis с LLM стала значительно точнее и гибче. Zero-shot подход работает для большинства задач, а fine-tuning улучшает результаты для специфичных доменов.
Ключевые выводы:
- Zero-shot sentiment с GPT-4o точен для большинства текстов
- ABSA (aspect-based) даёт детальную картину
- Локальные модели (BERT, DistilBERT) для приватных данных
- VADER — быстрый baseline для английского
- Multilingual модели для многоязычных данных