Spaces:
Running on Zero
Running on Zero
| """ | |
| 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} | |
| 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") |