DevxAman's picture
Update app.py
93158ef verified
Raw
History Blame Contribute Delete
9.13 kB
"""
HuggingFace Space App - News Sentiment Analyzer
Compatible with Gradio 6.20.0 (provided by Space) + ZeroGPU
"""
# IMPORTANT: `spaces` MUST be imported first, before torch/transformers or
# any other CUDA-touching library. ZeroGPU intercepts CUDA initialization,
# and importing it late causes:
# RuntimeError: CUDA has been initialized before importing the `spaces` package.
import spaces
import gradio as gr
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from scipy.special import softmax
import numpy as np
print("πŸš€ Loading Sentiment Models...")
# ============================================
# 1. LOAD ROBERTA MODEL (loaded on CPU; moved to GPU per-call by @spaces.GPU)
# ============================================
print("πŸ“₯ Loading RoBERTa model...")
roberta_model_name = "cardiffnlp/twitter-roberta-base-sentiment-latest"
roberta_tokenizer = AutoTokenizer.from_pretrained(roberta_model_name)
roberta_model = AutoModelForSequenceClassification.from_pretrained(roberta_model_name)
roberta_model.eval()
print("βœ… RoBERTa model loaded!")
# ============================================
# 2. LOAD SIEBERT MODEL
# ============================================
print("πŸ“₯ Loading Siebert model...")
siebert_model_name = "siebert/sentiment-roberta-large-english"
siebert_tokenizer = AutoTokenizer.from_pretrained(siebert_model_name)
siebert_model = AutoModelForSequenceClassification.from_pretrained(siebert_model_name)
siebert_model.eval()
print("βœ… Siebert model loaded!")
print("🎯 All models ready!")
# ============================================
# SENTIMENT ANALYSIS FUNCTIONS
# ============================================
def analyze_roberta(text, device):
"""Analyze sentiment using RoBERTa model"""
try:
if not text or len(text.strip()) < 5:
return {'negative': 0.33, 'neutral': 0.34, 'positive': 0.33}
model = roberta_model.to(device)
encoded = roberta_tokenizer(
text,
return_tensors='pt',
truncation=True,
max_length=512,
padding=True
).to(device)
with torch.no_grad():
output = model(**encoded)
scores = softmax(output.logits.cpu().numpy()[0])
return {
'negative': float(scores[0]),
'neutral': float(scores[1]),
'positive': float(scores[2])
}
except Exception as e:
print(f"❌ RoBERTa error: {e}")
return {'negative': 0.33, 'neutral': 0.34, 'positive': 0.33}
def analyze_siebert(text, device):
"""Analyze sentiment using Siebert model"""
try:
if not text or len(text.strip()) < 5:
return {'negative': 0.5, 'positive': 0.5}
model = siebert_model.to(device)
encoded = siebert_tokenizer(
text,
return_tensors='pt',
truncation=True,
max_length=512,
padding=True
).to(device)
with torch.no_grad():
output = model(**encoded)
scores = softmax(output.logits.cpu().numpy()[0])
return {
'negative': float(scores[0]),
'positive': float(scores[1])
}
except Exception as e:
print(f"❌ Siebert error: {e}")
return {'negative': 0.5, 'positive': 0.5}
@spaces.GPU(duration=30)
def get_ensemble_sentiment(text):
"""Combine both models for accurate results. Runs inside a ZeroGPU allocation."""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
roberta_result = analyze_roberta(text, device)
siebert_result = analyze_siebert(text, device)
siebert_neg = siebert_result['negative']
siebert_pos = siebert_result['positive']
diff = abs(siebert_pos - siebert_neg)
if diff < 0.2:
siebert_neutral = 1 - diff
siebert_neg = siebert_neg * (1 - siebert_neutral / 2)
siebert_pos = siebert_pos * (1 - siebert_neutral / 2)
else:
siebert_neutral = 0.1
siebert_neg = siebert_neg * 0.95
siebert_pos = siebert_pos * 0.95
ensemble_neg = (roberta_result['negative'] + siebert_neg) / 2
ensemble_neu = (roberta_result['neutral'] + siebert_neutral) / 2
ensemble_pos = (roberta_result['positive'] + siebert_pos) / 2
total = ensemble_neg + ensemble_neu + ensemble_pos
if total > 0:
ensemble_neg /= total
ensemble_neu /= total
ensemble_pos /= total
max_score = max(ensemble_neg, ensemble_neu, ensemble_pos)
if max_score == ensemble_neg:
label = 'Negative'
elif max_score == ensemble_pos:
label = 'Positive'
else:
label = 'Neutral'
return {
'sentiment': label,
'confidence': round(max_score, 4),
'negative_score': round(ensemble_neg, 4),
'neutral_score': round(ensemble_neu, 4),
'positive_score': round(ensemble_pos, 4)
}
def predict_single(text):
"""Predict sentiment for single text"""
if not text or len(text.strip()) < 5:
return "⚠️ Please enter some text to analyze."
result = get_ensemble_sentiment(text)
sentiment_emoji = {'Positive': 'βœ…', 'Negative': '❌', 'Neutral': 'βšͺ'}
emoji = sentiment_emoji.get(result['sentiment'], 'βšͺ')
confidence_pct = result['confidence'] * 100
if confidence_pct > 80:
confidence_color = "🟒"
elif confidence_pct > 60:
confidence_color = "🟑"
else:
confidence_color = "πŸ”΄"
return f"""
# πŸ“Š Sentiment Analysis Results
---
### {emoji} **Final Sentiment: {result['sentiment']}**
**Confidence:** {confidence_color} {confidence_pct:.1f}%
---
### πŸ“ˆ Score Breakdown
| Aspect | Score |
|--------|-------|
| βœ… Positive | {result['positive_score']*100:.1f}% |
| ❌ Negative | {result['negative_score']*100:.1f}% |
| βšͺ Neutral | {result['neutral_score']*100:.1f}% |
---
### πŸ“ Analyzed Text
> {text[:200]}{'...' if len(text) > 200 else ''}
"""
# ============================================
# API ENDPOINT FOR EXTERNAL CALLS
# ============================================
def api_predict(text: str) -> dict:
"""API endpoint for external calls (JSON response)"""
if not text or len(text.strip()) < 5:
return {
'error': 'Text too short or empty',
'sentiment': 'Neutral',
'confidence': 0.0
}
return get_ensemble_sentiment(text)
# ============================================
# GRADIO INTERFACE (Gradio 6.20.0)
# ============================================
with gr.Blocks(title="πŸ“° News Sentiment Analyzer") as demo:
gr.Markdown("""
# πŸ“° News Sentiment Analyzer
### 🎯 Ensemble Model: RoBERTa + Siebert
This analyzer combines two powerful models:
- **RoBERTa**: Twitter-based sentiment model (negative, neutral, positive)
- **Siebert**: Large-scale sentiment model (negative, positive)
""")
gr.api(api_predict, api_name="predict")
with gr.Row():
with gr.Column(scale=2):
text_input = gr.Textbox(
label="πŸ“ Enter News Text",
placeholder="Paste your news article here...",
lines=10,
max_lines=20
)
with gr.Row():
submit_btn = gr.Button("πŸ” Analyze Sentiment", variant="primary")
clear_btn = gr.Button("πŸ—‘οΈ Clear", variant="secondary")
gr.Markdown("### πŸ’‘ Try these examples:")
examples = [
["Apple reported record profits with revenue growth of 15% this quarter."],
["The company faces severe criticism over data breach affecting millions of users."],
["Government announced new policies to boost economic growth."],
["Defense ministry successfully tested new hypersonic missile system."],
["The bank reported $2 billion in losses due to failed investments."]
]
for example in examples:
gr.Examples(
inputs=text_input,
examples=[example]
)
with gr.Column(scale=1):
output = gr.Markdown(label="πŸ“ˆ Analysis Results", value="Enter text and click 'Analyze Sentiment' to see results here.")
submit_btn.click(
fn=predict_single,
inputs=text_input,
outputs=output
)
clear_btn.click(
fn=lambda: "",
inputs=[],
outputs=text_input
)
text_input.submit(
fn=predict_single,
inputs=text_input,
outputs=output
)
if __name__ == "__main__":
print("\n" + "=" * 60)
print("πŸš€ News Sentiment Analyzer")
print("=" * 60)
print("πŸ€– Models: RoBERTa + Siebert (ZeroGPU)")
print("=" * 60 + "\n")
demo.launch(theme="soft")