yt-comment-analyzer / model /model_loader.py
santu578's picture
Fix: Added sentence transformer model for KeyBERT
9ff5c45 verified
Raw
History Blame Contribute Delete
12.2 kB
from transformers import pipeline
from keybert import KeyBERT
from sentence_transformers import SentenceTransformer
import torch
import re
from collections import Counter
import numpy as np
import os
# Set cache directories
os.environ['TRANSFORMERS_CACHE'] = '/app/cache'
os.environ['HF_HOME'] = '/app/cache'
# Check if GPU is available
device = 0 if torch.cuda.is_available() else -1
print(f"Using device: {'GPU' if torch.cuda.is_available() else 'CPU'}")
# Load sentence transformer model FIRST (needed for KeyBERT)
print("Loading sentence transformer model...")
try:
sentence_model = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
print("Sentence transformer loaded successfully!")
except Exception as e:
print(f"Error loading sentence transformer: {e}")
# Fallback to a smaller model
sentence_model = SentenceTransformer('paraphrase-MiniLM-L3-v2', device='cpu')
# Load sentiment analysis model
print("Loading sentiment analysis model...")
try:
sentiment_model = pipeline(
"sentiment-analysis",
model="cardiffnlp/twitter-roberta-base-sentiment-latest",
device=device,
truncation=True,
max_length=512
)
print("Sentiment model loaded!")
except Exception as e:
print(f"Error loading sentiment model: {e}")
sentiment_model = None
# Load sarcasm detection model
print("Loading sarcasm detection model...")
try:
sarcasm_model = pipeline(
"text-classification",
model="cardiffnlp/twitter-roberta-base-irony",
device=device,
truncation=True,
max_length=512
)
print("Sarcasm model loaded!")
except Exception as e:
print(f"Error loading sarcasm model: {e}")
sarcasm_model = None
# Load emotion detection model
print("Loading emotion detection model...")
try:
emotion_model = pipeline(
"text-classification",
model="j-hartmann/emotion-english-distilroberta-base",
device=device,
truncation=True,
max_length=512
)
print("Emotion model loaded!")
except Exception as e:
print(f"Error loading emotion model: {e}")
emotion_model = None
# Load summarizer (optional - use smaller model for faster loading)
print("Loading summarizer model...")
try:
summarizer = pipeline(
"summarization",
model="facebook/bart-large-cnn",
device=device,
truncation=True,
max_length=1024
)
print("Summarizer loaded!")
except Exception as e:
print(f"Error loading summarizer: {e}")
# Fallback to smaller model
try:
summarizer = pipeline(
"summarization",
model="t5-small",
device=device
)
print("Fallback summarizer loaded!")
except:
summarizer = None
# Load keyword extractor with the sentence model
print("Loading keyword extractor...")
try:
kw_model = KeyBERT(model=sentence_model)
print("KeyBERT loaded successfully!")
except Exception as e:
print(f"Error loading KeyBERT: {e}")
kw_model = None
print("All models loaded successfully!")
# Positive and negative word lists for fallback
POSITIVE_WORDS = {
'love', '❤️', '💕', '💓', '💗', '💖', '💘', '💝',
'great', 'amazing', 'awesome', 'fantastic', 'wonderful',
'beautiful', 'perfect', 'excellent', 'brilliant',
'fan', 'favorite', 'favourite', 'best', 'good', 'nice',
'like', 'enjoy', 'appreciate', 'thank', 'thanks', 'legend'
}
NEGATIVE_WORDS = {
'hate', 'bad', 'terrible', 'awful', 'horrible', 'sucks',
'dislike', 'worst', 'poor', 'disappointing', 'waste',
'boring', 'useless', 'trash', 'garbage', 'cringe',
'overrated', 'hated', 'annoying', 'stupid', 'dumb'
}
def safe_truncate(text, max_length=512):
"""Safely truncate text to max_length characters"""
if not text:
return ""
if len(text) > max_length:
return text[:max_length]
return text
def clean_text(text: str) -> tuple:
"""Clean and normalize text"""
if not text:
return "", ""
text = ' '.join(text.split())
text_lower = text.lower()
return text, text_lower
def predict_sentiment(text: str) -> str:
"""
Sentiment prediction with fallback
"""
try:
if not text or len(text.strip()) < 2:
return "NEUTRAL"
# Clean and truncate
text = safe_truncate(text, 512)
text, text_lower = clean_text(text)
# Try model prediction first
if sentiment_model:
try:
result = sentiment_model(text)[0]
model_label = result['label']
model_score = result['score']
# Map model labels to our categories
if model_label == "LABEL_0":
return "NEGATIVE"
elif model_label == "LABEL_2":
return "POSITIVE"
elif model_label == "LABEL_1" and model_score > 0.7:
return "NEUTRAL"
except Exception as model_err:
print(f"Model error: {model_err}")
# Keyword-based analysis (fallback)
pos_count = sum(1 for word in POSITIVE_WORDS if word in text_lower)
neg_count = sum(1 for word in NEGATIVE_WORDS if word in text_lower)
if pos_count > neg_count and pos_count > 0:
return "POSITIVE"
elif neg_count > pos_count and neg_count > 0:
return "NEGATIVE"
return "NEUTRAL"
except Exception as e:
print(f"Error in sentiment analysis: {e}")
return "NEUTRAL"
def detect_sarcasm(text: str) -> str:
"""Detect sarcasm in comment"""
try:
if not text or not sarcasm_model:
return "NO"
text = safe_truncate(text, 512)
if len(text.strip()) < 3:
return "NO"
result = sarcasm_model(text)[0]
return "YES" if result['label'] == "LABEL_1" and result['score'] > 0.55 else "NO"
except Exception as e:
return "NO"
def detect_emotion(text: str) -> str:
"""Detect emotion in comment"""
try:
if not text:
return "neutral"
# Emoji-based fast detection
if '😭' in text or '😢' in text:
return "sadness"
elif '😊' in text or '😍' in text or '🥰' in text:
return "joy"
elif '😂' in text or '🤣' in text:
return "amusement"
elif '❤️' in text or '💕' in text:
return "love"
elif '🎉' in text or '🎊' in text:
return "excitement"
elif '😠' in text or '🤬' in text:
return "anger"
elif '😨' in text or '😱' in text:
return "fear"
# Model-based detection
if emotion_model:
text = safe_truncate(text, 512)
if len(text.strip()) > 2:
result = emotion_model(text)[0]
return result['label']
return "neutral"
except Exception as e:
return "neutral"
def generate_summary(texts: list) -> str:
"""Generate summary of all comments"""
try:
if not texts or not summarizer:
return "No comments to summarize"
sample_size = min(50, len(texts))
combined = " ".join(texts[:sample_size])
combined = safe_truncate(combined, 800)
if len(combined) < 50:
return "Not enough comments to generate summary"
summary = summarizer(combined, max_length=100, min_length=30, do_sample=False)
return summary[0]['summary_text']
except Exception as e:
print(f"Error in summary generation: {e}")
return "Comments analysis completed"
def extract_keywords(texts: list, top_n=15):
"""Extract keywords from comments"""
try:
if not texts:
return []
sample_size = min(150, len(texts))
combined = " ".join(texts[:sample_size])
combined = safe_truncate(combined, 1500)
if len(combined) < 20:
return []
# Use KeyBERT if available
if kw_model:
keywords = kw_model.extract_keywords(
combined,
keyphrase_ngram_range=(1, 2),
stop_words='english',
top_n=top_n
)
return [kw[0] for kw in keywords if kw and kw[0]]
# Fallback: simple word frequency
words = combined.lower().split()
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'is', 'are', 'was', 'were',
'to', 'for', 'of', 'in', 'on', 'at', 'by', 'with', 'without', 'i',
'you', 'he', 'she', 'it', 'we', 'they', 'this', 'that', 'these', 'those'}
word_freq = {}
for word in words:
word = word.strip('.,!?;:()[]{}"\'')
if len(word) > 2 and word not in stop_words and not word.isdigit():
word_freq[word] = word_freq.get(word, 0) + 1
sorted_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:top_n]
return [word for word, count in sorted_words]
except Exception as e:
print(f"Error in keyword extraction: {e}")
return []
def process_comments_in_batches(comments, batch_size=50):
"""Process comments in batches"""
total = len(comments)
if total == 0:
return
print(f"Processing {total} comments in batches of {batch_size}...")
for i in range(0, total, batch_size):
batch = comments[i:i+batch_size]
batch_results = []
for comment in batch:
try:
if not comment or len(comment.strip()) < 2:
batch_results.append({
"text": comment if comment else "",
"sentiment": "NEUTRAL",
"sarcasm": "NO",
"emotion": "neutral"
})
continue
sentiment = predict_sentiment(comment)
sarcasm = detect_sarcasm(comment)
emotion = detect_emotion(comment)
batch_results.append({
"text": comment,
"sentiment": sentiment,
"sarcasm": sarcasm,
"emotion": emotion
})
except Exception as e:
batch_results.append({
"text": comment if comment else "",
"sentiment": "NEUTRAL",
"sarcasm": "NO",
"emotion": "unknown"
})
yield batch_results
if (i // batch_size + 1) % 10 == 0 or (i + batch_size) >= total:
processed = min(i + batch_size, total)
print(f" Processed batch {i//batch_size + 1}/{(total + batch_size - 1)//batch_size} ({processed}/{total} comments)")
def get_batch_stats(results_batches):
"""Aggregate statistics from batch results"""
stats = {"positive": 0, "neutral": 0, "negative": 0}
all_results = []
for batch in results_batches:
for item in batch:
all_results.append(item)
if item["sentiment"] == "POSITIVE":
stats["positive"] += 1
elif item["sentiment"] == "NEGATIVE":
stats["negative"] += 1
else:
stats["neutral"] += 1
return stats, all_results
def get_sentiment_score(stats):
"""Calculate overall sentiment score"""
total = stats["positive"] + stats["neutral"] + stats["negative"]
if total == 0:
return 0
return ((stats["positive"] - stats["negative"]) / total) * 100