import gradio as gr import torch from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, pipeline ) import pandas as pd import numpy as np from datetime import datetime import warnings warnings.filterwarnings('ignore') # Constants MODEL_PATH = "./imdb_sentiment_final" TITLE = "đŸŽŦ IMDB Sentiment Classifier" DESCRIPTION = """ A binary sentiment classifier fine-tuned on IMDB movie reviews using LoRA. The model classifies text as **Positive** or **Negative** sentiment. """ # Device configuration device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Using device: {device}") # Load model and tokenizer @gr.Cache() def load_model(): """Load the model and tokenizer with caching""" print("Loading model...") try: tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) model = AutoModelForSequenceClassification.from_pretrained(MODEL_PATH) model.to(device) model.eval() print("Model loaded successfully!") return model, tokenizer except Exception as e: print(f"Error loading model: {e}") # Fallback to a default model from transformers import DistilBertForSequenceClassification model = DistilBertForSequenceClassification.from_pretrained("distilbert-base-uncased", num_labels=2) tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") model.to(device) model.eval() return model, tokenizer # Initialize model model, tokenizer = load_model() # Create inference pipeline sentiment_pipeline = pipeline( "text-classification", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1 ) def predict_sentiment(text, batch_size=1): """ Predict sentiment for single or multiple texts """ if not text or not text.strip(): return {"error": "Please enter some text"} # Single text if isinstance(text, str): try: result = sentiment_pipeline(text)[0] confidence = result['score'] label = "Positive" if result['label'] == "LABEL_1" else "Negative" return { "text": text[:200] + "..." if len(text) > 200 else text, "sentiment": label, "confidence": f"{confidence:.2%}", "icon": "😊" if label == "Positive" else "😞", "color": "green" if label == "Positive" else "red" } except Exception as e: return {"error": f"Prediction error: {str(e)}"} # Batch processing (if text is a list) else: results = [] for t in text: if t.strip(): try: result = sentiment_pipeline(t)[0] confidence = result['score'] label = "Positive" if result['label'] == "LABEL_1" else "Negative" results.append({ "text": t[:100] + "..." if len(t) > 100 else t, "sentiment": label, "confidence": f"{confidence:.2%}", "icon": "😊" if label == "Positive" else "😞" }) except: results.append({"text": t[:100], "sentiment": "Error", "confidence": "N/A"}) return {"batch_results": results} def batch_predict(input_text): """ Handle batch input from text area """ texts = [t.strip() for t in input_text.split('\n') if t.strip()] if not texts: return {"error": "No valid text provided"} if len(texts) == 1: return predict_sentiment(texts[0]) else: return predict_sentiment(texts) def analyze_file(file): """ Analyze sentiment from uploaded text file """ if file is None: return {"error": "No file uploaded"} try: # Read file if file.name.endswith('.txt'): with open(file.name, 'r', encoding='utf-8') as f: content = f.read() elif file.name.endswith('.csv'): df = pd.read_csv(file.name) if 'review' in df.columns: content = "\n".join(df['review'].astype(str).tolist()) else: content = "\n".join(df.iloc[:, 0].astype(str).tolist()) else: return {"error": "Unsupported file format. Use .txt or .csv"} texts = [t.strip() for t in content.split('\n') if t.strip()] if len(texts) > 100: # Limit for demo texts = texts[:100] results = [] for text in texts: result = predict_sentiment(text) if "error" not in result: results.append({ "Text": text[:50] + "..." if len(text) > 50 else text, "Sentiment": result['sentiment'], "Confidence": result['confidence'] }) if results: df_results = pd.DataFrame(results) summary = df_results['Sentiment'].value_counts() return { "results_table": df_results, "summary": f"Positive: {summary.get('Positive', 0)}, Negative: {summary.get('Negative', 0)}", "total_reviews": len(results) } else: return {"error": "No valid text found in file"} except Exception as e: return {"error": f"File processing error: {str(e)}"} # Example reviews for demo EXAMPLE_REVIEWS = [ "This movie was absolutely fantastic! The acting was superb and the storyline kept me engaged throughout.", "Terrible film. I regret wasting two hours of my life watching this boring nonsense.", "The cinematography was beautiful but the plot was weak and predictable.", "An emotional rollercoaster with brilliant performances from the entire cast.", "Not bad, but nothing special either. Average entertainment for a lazy Sunday." ] # Custom CSS for better styling custom_css = """ .gradio-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } .sentiment-positive { color: green; font-weight: bold; } .sentiment-negative { color: red; font-weight: bold; } .result-box { padding: 20px; border-radius: 10px; margin: 10px 0; background: #f5f5f5; } """ # Create Gradio interface with gr.Blocks(css=custom_css, title=TITLE) as demo: gr.HTML(f"""

đŸŽŦ IMDB Sentiment Classifier

Fine-tuned on 50K movie reviews using LoRA with DistilBERT (≤ 110M params)

Model Type: Binary Sentiment Classification (Positive/Negative)

""") with gr.Row(): with gr.Column(scale=2): # Single text input gr.Markdown("### 📝 Enter Your Movie Review") input_text = gr.Textbox( label="Review Text", placeholder="Type your movie review here...", lines=4, elem_id="input_text" ) with gr.Row(): submit_btn = gr.Button("Analyze Sentiment 🚀", variant="primary") clear_btn = gr.Button("Clear", variant="secondary") # Batch input gr.Markdown("### 📚 Batch Analysis") batch_input = gr.Textbox( label="Enter multiple reviews (one per line)", placeholder="Review 1\nReview 2\nReview 3\n...", lines=5 ) batch_btn = gr.Button("Analyze All", variant="secondary") # File upload gr.Markdown("### 📁 Upload File") file_input = gr.File( label="Upload .txt or .csv file", file_types=[".txt", ".csv"] ) file_btn = gr.Button("Analyze File", variant="secondary") # Examples gr.Markdown("### ✨ Example Reviews") gr.Examples( examples=EXAMPLE_REVIEWS, inputs=input_text, label="Click an example to try it out" ) with gr.Column(scale=1): # Results display gr.Markdown("### 📊 Results") result_box = gr.JSON(label="Analysis Result", elem_classes="result-box") # Visual output sentiment_output = gr.Label(label="Sentiment", num_top_classes=2) # Confidence meter confidence_output = gr.Slider( minimum=0, maximum=100, label="Confidence Level", interactive=False ) # Stats gr.Markdown("### 📈 Statistics") stats_output = gr.Markdown("") # Model info with gr.Accordion("â„šī¸ Model Information", open=False): gr.Markdown(f""" **Model Details:** - Base Model: DistilBERT-base-uncased - Parameters: ~66M (with LoRA adaptation) - Training: Fine-tuned on IMDB dataset - Task: Binary sentiment classification - Framework: PyTorch + Hugging Face Transformers **Last Updated:** {datetime.now().strftime('%Y-%m-%d')} **Note:** This is a demo model trained on a subset of data. """) # Functions to update all outputs def update_all_outputs(result): """Update all output components based on prediction result""" if "error" in result: return [ result, {"Positive": 0, "Negative": 0}, 50, f"**Error:** {result['error']}" ] elif "batch_results" in result: # For batch results positive_count = sum(1 for r in result['batch_results'] if r['sentiment'] == 'Positive') negative_count = sum(1 for r in result['batch_results'] if r['sentiment'] == 'Negative') avg_confidence = 75 # Placeholder summary = f"**Batch Analysis:** {len(result['batch_results'])} reviews\nPositive: {positive_count}, Negative: {negative_count}" return [ result, {"Positive": positive_count, "Negative": negative_count}, avg_confidence, summary ] else: # For single result confidence_percent = float(result['confidence'].strip('%')) / 100 sentiment_dict = {"Positive": 0, "Negative": 0} sentiment_dict[result['sentiment']] = 1 stats = f""" **Review:** {result['text']} **Sentiment:** {result['sentiment']} {result['icon']} **Confidence:** {result['confidence']} """ return [ result, sentiment_dict, confidence_percent * 100, stats ] # Set up event handlers submit_btn.click( fn=predict_sentiment, inputs=input_text, outputs=result_box ).then( fn=update_all_outputs, inputs=result_box, outputs=[result_box, sentiment_output, confidence_output, stats_output] ) clear_btn.click( fn=lambda: [None, {"Positive": 0, "Negative": 0}, 50, ""], outputs=[input_text, sentiment_output, confidence_output, stats_output] ) batch_btn.click( fn=batch_predict, inputs=batch_input, outputs=result_box ).then( fn=update_all_outputs, inputs=result_box, outputs=[result_box, sentiment_output, confidence_output, stats_output] ) file_btn.click( fn=analyze_file, inputs=file_input, outputs=result_box ).then( fn=update_all_outputs, inputs=result_box, outputs=[result_box, sentiment_output, confidence_output, stats_output] ) # Auto-run when example is clicked input_text.change( fn=predict_sentiment, inputs=input_text, outputs=result_box ).then( fn=update_all_outputs, inputs=result_box, outputs=[result_box, sentiment_output, confidence_output, stats_output] ) # Launch configuration if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7860, share=False, debug=True )