File size: 9,359 Bytes
1b2d28c
 
 
 
 
 
 
2f61676
 
 
 
1b2d28c
2f61676
1b2d28c
2f61676
1b2d28c
2f61676
1b2d28c
 
 
 
 
2f61676
 
 
1b2d28c
2f61676
 
1b2d28c
2f61676
 
 
 
 
1b2d28c
 
 
2f61676
1b2d28c
2f61676
 
 
1b2d28c
2f61676
 
 
 
 
 
 
 
 
 
1b2d28c
2f61676
1b2d28c
2f61676
1b2d28c
2f61676
1b2d28c
2f61676
 
1b2d28c
2f61676
 
 
 
 
1b2d28c
2f61676
 
 
 
1b2d28c
2f61676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1b2d28c
2f61676
 
 
 
1b2d28c
2f61676
 
1b2d28c
2f61676
 
 
1b2d28c
2f61676
 
1b2d28c
2f61676
 
 
 
1b2d28c
2f61676
 
 
1b2d28c
2f61676
 
 
1b2d28c
2f61676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1b2d28c
2f61676
1b2d28c
2f61676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
import streamlit as st
import pandas as pd
import numpy as np
import re
import nltk
import pickle
import os
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# ========== STREAMLIT SETUP ==========
st.set_page_config(
    page_title="🎬 IMDB Sentiment Analysis",
    page_icon="🎬",
    layout="centered"
)

st.markdown("""
<style>
    .prediction-box {
        padding: 25px;
        border-radius: 15px;
        margin: 25px 0;
        text-align: center;
        font-size: 1.4rem;
        font-weight: bold;
    }
    .positive { background-color: #d4edda; color: #155724; border: 2px solid #c3e6cb; }
    .negative { background-color: #f8d7da; color: #721c24; border: 2px solid #f5c6cb; }
    .header { text-align: center; margin-bottom: 30px; }
    .header h1 { color: #1f77b4; margin-bottom: 5px; }
    .header p { color: #666; font-size: 1.1rem; }
</style>
""", unsafe_allow_html=True)

st.markdown('<div class="header"><h1>🎬 IMDB Sentiment Analysis</h1><p>Powered by Naive Bayes β€’ No dataset upload required</p></div>', unsafe_allow_html=True)

# Status indicator
status = st.empty()
status.markdown('<span class="status-badge status-training">⏳ Loading model...</span>', unsafe_allow_html=True)

# ========== NLTK SETUP (Safe for HF Spaces) ==========
@st.cache_resource
def setup_nltk():
    try:
        nltk.data.find('tokenizers/punkt')
        nltk.data.find('corpora/stopwords')
    except LookupError:
        nltk.download('punkt', quiet=True, download_dir='/tmp/nltk')
        nltk.download('stopwords', quiet=True, download_dir='/tmp/nltk')
        nltk.data.path.append('/tmp/nltk')

setup_nltk()

# ========== TEXT PREPROCESSING ==========
def preprocess_text(text):
    text = re.sub(r'<.*?>', '', text)  # Remove HTML tags
    text = text.lower()
    text = re.sub(r'[^a-zA-Z\s]', '', text)  # Keep only letters/spaces
    text = ' '.join(text.split())  # Remove extra whitespace
    
    # Stopwords removal
    from nltk.corpus import stopwords
    stop_words = set(stopwords.words('english'))
    words = text.split()
    words = [w for w in words if w not in stop_words]
    
    # Stemming
    from nltk.stem.porter import PorterStemmer
    stemmer = PorterStemmer()
    words = [stemmer.stem(w) for w in words]
    
    return ' '.join(words)

# ========== DATASET LOADING (Auto-download fallback) ==========
@st.cache_data
def load_dataset():
    """Load IMDB dataset - tries HF Datasets first, falls back to embedded mini-dataset"""
    try:
        # Try loading from Hugging Face Datasets (no CSV needed!)
        from datasets import load_dataset
        dataset = load_dataset("imdb", split="train[:1200]")
        df = pd.DataFrame({
            "review": dataset["text"],
            "sentiment": ["positive" if l == 1 else "negative" for l in dataset["label"]]
        })
        status.markdown('<span class="status-badge status-ready">βœ… Using IMDB dataset (1,200 samples)</span>', unsafe_allow_html=True)
        return df
    except Exception as e:
        # Fallback: Embedded mini-dataset (always works!)
        status.markdown('<span class="status-badge status-ready">βœ… Using embedded dataset (100 samples)</span>', unsafe_allow_html=True)
        mini_data = """review,sentiment
"A wonderful little production.",positive
"This is a very strange movie.",negative
"Very good!",positive
"Terrible acting",negative
"Brilliant direction",positive
"Awful dialogue",negative
"Fantastic performances",positive
"Boring plot",negative
"Emotional rollercoaster",positive
"Predictable ending",negative
"Visually stunning",positive
"Confusing storyline",negative
"Powerful message",positive
"Uninspired acting",negative
"Perfect pacing",positive
"Dragged on forever",negative
"Masterpiece!",positive
"Total garbage",negative
"Captivating from start to finish",positive
"Couldn't finish it",negative
"Oscar-worthy",positive
"Should be banned",negative
"Beautiful cinematography",positive
"Horrible soundtrack",negative
"Thought-provoking",positive
"Mind-numbingly dull",negative
"Instant classic",positive
"Instant regret",negative
"Flawless execution",positive
"Full of flaws",negative
"Left me speechless",positive
"Left me sleeping",negative
"Pure joy",positive
"Pure torture",negative
"Will watch again",positive
"Will never recover",negative
"Exceeded expectations",positive
"Failed completely",negative
"Hidden gem",positive
"Overhyped trash",negative
"Emotional depth",positive
"Emotionally vacant",negative
"Perfect casting",positive
"Miscast disaster",negative
"Artistic triumph",positive
"Artistic failure",negative
"Hauntingly beautiful",positive
"Hauntingly bad",negative
"Unforgettable",positive
"Instantly forgettable",negative
"Chills down my spine",positive
"Chills from boredom",negative
"Standing ovation",positive
"Walking out early",negative
"Cinematic poetry",positive
"Cinematic crime",negative
"Must see",positive
"Must skip",negative
"Life-changing",positive
"Waste of life",negative
"Perfection",positive
"Imperfect mess",negative
"Brilliant",positive
"Brainless",negative
"Captivating",positive
"Captivity",negative
"Enchanting",positive
"Enraging",negative
"Exhilarating",positive
"Exhausting",negative
"Mesmerizing",positive
"Mind-numbing",negative
"Riveting",positive
"Repulsive",negative
"Soul-stirring",positive
"Soul-crushing",negative
"Transcendent",positive
"Transcendently bad",negative
"Unmissable",positive
"Unwatchable",negative
"Visceral",positive
"Vapid",negative
"Wow",positive
"Ugh",negative
"Yes!",positive
"No!",negative
"Amazing movie!",positive
"Waste of time",negative
"Emotional rollercoaster",positive
"Predictable ending",negative
"Visually stunning",positive
"Confusing storyline",negative
"Powerful message",positive
"Uninspired acting",negative
"Perfect pacing",positive
"Dragged on forever",negative
"""
        from io import StringIO
        return pd.read_csv(StringIO(mini_data))

# ========== MODEL TRAINING (Cached) ==========
@st.cache_resource
def train_model():
    df = load_dataset()
    
    # Preprocess reviews
    df['clean_review'] = df['review'].apply(preprocess_text)
    
    # Vectorize
    vectorizer = CountVectorizer(max_features=1000)
    X = vectorizer.fit_transform(df['clean_review'])
    
    # Encode labels
    y = df['sentiment'].map({'positive': 1, 'negative': 0}).values
    
    # Split data CORRECTLY (fixes notebook bug!)
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )
    
    # Train model
    model = MultinomialNB()
    model.fit(X_train, y_train)
    
    # Calculate accuracy
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    
    return model, vectorizer, accuracy

# Load model (shows status during first run)
try:
    model, vectorizer, accuracy = train_model()
    status.empty()  # Clear status after success
except Exception as e:
    status.error(f"❌ Error loading model: {str(e)}")
    st.stop()

# ========== UI ==========
st.subheader("πŸ“ Enter Your Movie Review")
user_input = st.text_area(
    "Type your review below:",
    height=120,
    placeholder="Example: 'This movie was absolutely fantastic! The acting was superb...'"
)

if st.button("πŸ” Analyze Sentiment", type="primary", use_container_width=True):
    if not user_input.strip():
        st.warning("⚠️ Please enter a review first!")
    else:
        with st.spinner("Analyzing sentiment..."):
            # Preprocess & predict
            clean_text = preprocess_text(user_input)
            X = vectorizer.transform([clean_text])
            pred = model.predict(X)[0]
            proba = model.predict_proba(X)[0]
            confidence = max(proba) * 100
            
            # Display result
            if pred == 1:
                st.markdown(f"""
                <div class="prediction-box positive">
                    😊 POSITIVE REVIEW<br>
                    <span style="font-size: 1.1rem; font-weight: normal;">Confidence: {confidence:.1f}%</span>
                </div>
                """, unsafe_allow_html=True)
            else:
                st.markdown(f"""
                <div class="prediction-box negative">
                    😞 NEGATIVE REVIEW<br>
                    <span style="font-size: 1.1rem; font-weight: normal;">Confidence: {confidence:.1f}%</span>
                </div>
                """, unsafe_allow_html=True)

# Examples section
st.markdown("---")
st.subheader("πŸ’‘ Try These Examples")
cols = st.columns(2)
with cols[0]:
    if st.button("πŸ‘ Positive Example"):
        st.session_state.user_input = "This movie was absolutely fantastic! The acting was superb and the plot kept me engaged throughout."
        st.rerun()
with cols[1]:
    if st.button("πŸ‘Ž Negative Example"):
        st.session_state.user_input = "Terrible waste of time. Poor acting, boring storyline, and awful special effects."
        st.rerun()

# Footer with accuracy
st.markdown("<hr>", unsafe_allow_html=True)
st.markdown(
    f"<div style='text-align: center; color: #888; font-size: 0.9rem;'>"
    f"✨ Model accuracy: {accuracy*100:.1f}% β€’ No dataset upload required β€’ Built with Streamlit"
    "</div>",
    unsafe_allow_html=True
)