Martin Rodrigo Morales commited on
Commit
cefdf81
Β·
1 Parent(s): a8e77be

Complete rewrite: Clean structure with robust visualizations

Browse files
Files changed (1) hide show
  1. app.py +323 -208
app.py CHANGED
@@ -1,14 +1,13 @@
1
  #!/usr/bin/env python3
2
  """
3
- Gradio app for HuggingFace Spaces
4
- Sentiment analysis with MLflow metrics visualization
5
  """
6
 
7
  import gradio as gr
8
  import torch
9
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
10
  import numpy as np
11
- import plotly.express as px
12
  import plotly.graph_objects as go
13
  import pandas as pd
14
  from typing import Dict, List, Tuple
@@ -18,50 +17,68 @@ import logging
18
  logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
 
 
 
 
21
  class SentimentAnalyzer:
22
- """Sentiment analyzer for production"""
23
 
24
- def __init__(self):
25
- # Use the deployed model from HuggingFace Hub
26
- self.model_name = "MartinRodrigo/distilbert-sentiment-imdb"
27
  self.tokenizer = None
28
  self.model = None
 
29
  self.load_model()
30
 
31
  def load_model(self):
32
- """Load the fine-tuned model"""
33
  try:
34
  logger.info(f"Loading model: {self.model_name}")
35
  self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
36
  self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name)
37
- logger.info("Model loaded successfully!")
 
 
38
  except Exception as e:
39
- logger.error(f"Error loading model: {e}")
40
- # Fallback to base model
41
- logger.info("Falling back to base model...")
42
  self.model_name = "distilbert-base-uncased-finetuned-sst-2-english"
43
  self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
44
  self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name)
 
 
45
 
46
- def analyze_single(self, text: str) -> Dict:
47
- """Analyze sentiment of a single text"""
48
- if not text.strip():
49
  return {
50
- "sentiment": "Please enter some text",
51
  "confidence": 0.0,
52
- "probabilities": None
 
53
  }
54
 
55
  try:
56
- inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
 
 
 
 
 
 
 
 
57
 
 
58
  with torch.no_grad():
59
  outputs = self.model(**inputs)
60
- predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
61
 
62
- probs = predictions[0].numpy()
63
- predicted_class = np.argmax(probs)
64
- confidence = float(probs[predicted_class])
 
65
 
66
  sentiment = "POSITIVE" if predicted_class == 1 else "NEGATIVE"
67
 
@@ -69,270 +86,368 @@ class SentimentAnalyzer:
69
  "sentiment": sentiment,
70
  "confidence": confidence,
71
  "probabilities": {
72
- "Negative": float(probs[0]),
73
- "Positive": float(probs[1])
74
- }
 
75
  }
76
 
77
  except Exception as e:
78
- logger.error(f"Error in analysis: {e}")
79
  return {
80
- "sentiment": f"Error: {str(e)}",
81
  "confidence": 0.0,
82
- "probabilities": None
 
83
  }
84
 
85
- def analyze_batch(self, texts: List[str]) -> List[Dict]:
86
- """Analyze multiple texts"""
87
- results = []
88
- for text in texts:
89
- if text.strip():
90
- results.append(self.analyze_single(text))
91
- return results
92
 
93
- # Initialize analyzer
94
- analyzer = SentimentAnalyzer()
 
95
 
96
- def analyze_sentiment(text: str) -> Tuple[str, float, go.Figure]:
97
- """Main analysis function for Gradio"""
98
- result = analyzer.analyze_single(text)
99
 
100
- # Create figure even if no text to avoid None errors
101
- if result["probabilities"]:
102
- df = pd.DataFrame([
103
- {"Sentiment": "Negative", "Probability": result["probabilities"]["Negative"]},
104
- {"Sentiment": "Positive", "Probability": result["probabilities"]["Positive"]}
105
- ])
106
-
107
- fig = px.bar(
108
- df,
109
- x="Sentiment",
110
- y="Probability",
111
- color="Sentiment",
112
- color_discrete_map={"Negative": "#ff4444", "Positive": "#44ff44"},
113
- title="Sentiment Probability Distribution",
114
- text="Probability"
115
- )
116
- fig.update_traces(texttemplate='%{text:.2%}', textposition='outside')
117
- fig.update_layout(
118
- showlegend=False,
119
- height=300,
120
- yaxis_range=[0, 1],
121
- yaxis_title="Probability",
122
- xaxis_title="Sentiment"
123
- )
124
-
125
- output_text = f"**{result['sentiment']}** (Confidence: {result['confidence']:.1%})"
126
-
127
- else:
128
- # Create empty figure for error cases
129
- fig = go.Figure()
130
- fig.add_annotation(
131
- text="No data to display",
132
- xref="paper", yref="paper",
133
- x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  )
135
- fig.update_layout(height=300)
136
- output_text = result['sentiment']
137
 
138
- return output_text, result['confidence'], fig
 
 
 
 
 
 
 
 
139
 
140
- def analyze_batch_texts(text_input: str) -> Tuple[str, go.Figure]:
141
- """Analyze multiple texts separated by newlines"""
142
- if not text_input.strip():
143
  empty_fig = go.Figure()
144
- empty_fig.add_annotation(
145
- text="Please enter texts to analyze",
146
- xref="paper", yref="paper",
147
- x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
148
  )
149
- empty_fig.update_layout(height=400)
150
- return "Please enter some texts (one per line)", empty_fig
151
 
 
152
  texts = [line.strip() for line in text_input.split('\n') if line.strip()]
153
 
154
  if not texts:
155
  empty_fig = go.Figure()
156
- empty_fig.add_annotation(
157
- text="No valid texts found",
158
- xref="paper", yref="paper",
159
- x=0.5, y=0.5, showarrow=False
 
 
 
 
 
 
 
 
160
  )
161
- empty_fig.update_layout(height=400)
162
  return "No valid texts found", empty_fig
163
 
164
- results = analyzer.analyze_batch(texts)
 
165
 
166
- summary_lines = []
167
- plot_data = []
168
-
169
- for i, (text, result) in enumerate(zip(texts, results)):
170
- sentiment = result['sentiment']
171
- confidence = result['confidence']
172
- summary_lines.append(f"{i+1}. **{sentiment}** ({confidence:.1%}) - {text[:50]}{'...' if len(text) > 50 else ''}")
173
-
174
- plot_data.append({
175
- "Text": f"Text {i+1}",
176
- "Sentiment": sentiment,
177
- "Confidence": confidence
178
- })
179
 
180
  summary = "\n".join(summary_lines)
181
 
182
- # Always create a figure
183
- if plot_data:
184
- df = pd.DataFrame(plot_data)
185
- fig = px.bar(
186
- df,
187
- x="Text",
188
- y="Confidence",
189
- color="Sentiment",
190
- color_discrete_map={"NEGATIVE": "#ff4444", "POSITIVE": "#44ff44"},
191
- title="Batch Analysis Results",
192
- text="Confidence"
193
- )
194
- fig.update_traces(texttemplate='%{text:.1%}', textposition='outside')
195
- fig.update_layout(
196
- height=400,
197
- yaxis_range=[0, 1.1],
198
- yaxis_title="Confidence",
199
- xaxis_title="Text Number"
200
- )
201
- else:
202
- fig = go.Figure()
203
- fig.add_annotation(
204
- text="No results to display",
205
- xref="paper", yref="paper",
206
- x=0.5, y=0.5, showarrow=False
207
- )
208
- fig.update_layout(height=400)
209
 
210
- return summary, fig
211
- return summary, None
 
 
 
 
 
 
 
 
 
 
212
 
213
- # Demo examples
214
- EXAMPLES = [
215
- "🎬 This movie absolutely blew my mind! Best film I've seen this year!",
216
- "😞 Worst customer service ever. Total waste of money.",
217
- "πŸš€ Revolutionary AI technology! Incredible understanding of language.",
218
- "❌ I regret this purchase deeply. Poor quality materials.",
219
- "✈️ Amazing travel experience! The hotel exceeded expectations!",
220
- "🎡 Concert was phenomenal! Everything was absolutely perfect!"
221
  ]
222
 
223
- BATCH_EXAMPLE = """πŸ›οΈ This online store has amazing customer service!
224
- 😑 Terrible experience with their support team.
225
- ⭐ Outstanding quality! Exceeded all my expectations.
226
- πŸ’Έ Disappointed with this expensive purchase."""
 
 
 
 
227
 
228
- # Create Gradio interface
229
  with gr.Blocks(
230
- title="πŸ€– Transformer Sentiment Analysis",
231
  theme=gr.themes.Soft(
232
  primary_hue="blue",
233
- secondary_hue="purple",
234
- neutral_hue="slate"
235
- )
 
 
 
 
 
 
 
236
  ) as demo:
237
 
 
238
  gr.Markdown("""
239
- # πŸ€– Transformer Sentiment Analysis
240
 
241
- Advanced AI-powered sentiment analysis using **DistilBERT** fine-tuned on IMDB dataset.
242
 
243
- **Model Performance:**
244
- - 🎯 Accuracy: **80%** on test set
245
- - πŸ“Š F1 Score: **0.7981**
246
- - ⚑ Speed: ~100ms per prediction
247
- - 🧠 Parameters: 66M (DistilBERT)
248
  """)
249
 
 
250
  with gr.Tabs():
 
251
  with gr.TabItem("πŸ” Single Analysis"):
252
- gr.Markdown("""
253
- Analyze individual texts to get sentiment predictions with confidence scores.
254
- """)
255
 
256
  with gr.Row():
257
- with gr.Column(scale=2):
258
  single_input = gr.Textbox(
259
- label="πŸ’¬ Enter your text",
260
- placeholder="Type your text here...",
261
- lines=4
 
 
 
 
 
 
 
 
 
 
 
 
262
  )
263
- single_btn = gr.Button("πŸš€ Analyze Sentiment", variant="primary", size="lg")
264
 
265
- with gr.Column(scale=2):
266
- single_output = gr.Markdown(label="πŸ“‹ Result")
267
- confidence_score = gr.Number(label="🎯 Confidence", precision=3)
268
- probability_plot = gr.Plot(label="πŸ“Š Probabilities")
269
-
270
- gr.Examples(
271
- examples=EXAMPLES,
272
- inputs=single_input,
273
- label="πŸ’‘ Try these examples:"
274
- )
275
 
 
276
  with gr.TabItem("πŸ“Š Batch Processing"):
277
- gr.Markdown("""
278
- Process multiple texts at once. Enter one text per line.
279
- """)
280
 
281
  with gr.Row():
282
- with gr.Column(scale=2):
283
  batch_input = gr.Textbox(
284
- label="πŸ“ Multiple texts (one per line)",
285
  placeholder="Enter texts, one per line...",
286
- lines=8,
287
  value=BATCH_EXAMPLE
288
  )
289
- batch_btn = gr.Button("πŸš€ Process Batch", variant="primary", size="lg")
 
 
 
 
290
 
291
- with gr.Column(scale=2):
292
- batch_output = gr.Markdown(label="πŸ“ˆ Results")
293
- batch_plot = gr.Plot(label="πŸ“Š Analytics")
 
 
 
294
 
 
295
  with gr.TabItem("ℹ️ About"):
296
  gr.Markdown("""
297
  ## About This Model
298
 
299
  ### πŸ—οΈ Architecture
300
- - **Model:** DistilBERT (Distilled BERT)
301
  - **Parameters:** 66 million
302
- - **Training:** Fine-tuned on IMDB dataset
303
- - **Accuracy:** 80% on test set
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
- ### ⚑ Performance
306
- - **Speed:** ~100ms per prediction
307
- - **Batch Processing:** Supported
308
- - **Memory:** Optimized for production
309
 
310
- ### πŸš€ Tech Stack
311
  - **Framework:** PyTorch + Transformers
312
- - **Tracking:** MLflow experiments
313
  - **UI:** Gradio
314
-
315
- ### πŸ”— Links
316
- - **Model:** [MartinRodrigo/distilbert-sentiment-imdb](https://huggingface.co/MartinRodrigo/distilbert-sentiment-imdb)
317
- - **GitHub:** [transformer-sentiment-analysis](https://github.com/mrdesautu/ransformer-sentiment-analysis)
318
 
319
  ---
320
 
321
- Built with ❀️ using Transformers, MLflow, and Gradio
322
  """)
323
 
324
- # Event handlers
325
  single_btn.click(
326
- fn=analyze_sentiment,
327
- inputs=single_input,
328
- outputs=[single_output, confidence_score, probability_plot]
329
  )
330
 
331
  batch_btn.click(
332
- fn=analyze_batch_texts,
333
- inputs=batch_input,
334
- outputs=[batch_output, batch_plot]
335
  )
336
 
 
 
 
 
337
  if __name__ == "__main__":
338
- demo.launch()
 
 
 
 
1
  #!/usr/bin/env python3
2
  """
3
+ Sentiment Analysis App for HuggingFace Spaces
4
+ Clean, robust implementation with proper visualization
5
  """
6
 
7
  import gradio as gr
8
  import torch
9
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
10
  import numpy as np
 
11
  import plotly.graph_objects as go
12
  import pandas as pd
13
  from typing import Dict, List, Tuple
 
17
  logging.basicConfig(level=logging.INFO)
18
  logger = logging.getLogger(__name__)
19
 
20
+ # ============================================================================
21
+ # SENTIMENT ANALYZER CLASS
22
+ # ============================================================================
23
+
24
  class SentimentAnalyzer:
25
+ """Production-ready sentiment analyzer"""
26
 
27
+ def __init__(self, model_name: str = "MartinRodrigo/distilbert-sentiment-imdb"):
28
+ self.model_name = model_name
 
29
  self.tokenizer = None
30
  self.model = None
31
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
32
  self.load_model()
33
 
34
  def load_model(self):
35
+ """Load model from HuggingFace Hub"""
36
  try:
37
  logger.info(f"Loading model: {self.model_name}")
38
  self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
39
  self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name)
40
+ self.model.to(self.device)
41
+ self.model.eval()
42
+ logger.info(f"βœ“ Model loaded successfully on {self.device}")
43
  except Exception as e:
44
+ logger.error(f"βœ— Error loading model: {e}")
45
+ logger.info("Falling back to base DistilBERT model...")
 
46
  self.model_name = "distilbert-base-uncased-finetuned-sst-2-english"
47
  self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
48
  self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name)
49
+ self.model.to(self.device)
50
+ self.model.eval()
51
 
52
+ def predict(self, text: str) -> Dict:
53
+ """Predict sentiment for a single text"""
54
+ if not text or not text.strip():
55
  return {
56
+ "sentiment": "ERROR",
57
  "confidence": 0.0,
58
+ "probabilities": {"Negative": 0.5, "Positive": 0.5},
59
+ "error": "Please enter some text"
60
  }
61
 
62
  try:
63
+ # Tokenize
64
+ inputs = self.tokenizer(
65
+ text,
66
+ return_tensors="pt",
67
+ truncation=True,
68
+ max_length=512,
69
+ padding=True
70
+ )
71
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
72
 
73
+ # Predict
74
  with torch.no_grad():
75
  outputs = self.model(**inputs)
76
+ probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
77
 
78
+ # Extract results
79
+ probs_cpu = probs.cpu().numpy()[0]
80
+ predicted_class = int(np.argmax(probs_cpu))
81
+ confidence = float(probs_cpu[predicted_class])
82
 
83
  sentiment = "POSITIVE" if predicted_class == 1 else "NEGATIVE"
84
 
 
86
  "sentiment": sentiment,
87
  "confidence": confidence,
88
  "probabilities": {
89
+ "Negative": float(probs_cpu[0]),
90
+ "Positive": float(probs_cpu[1])
91
+ },
92
+ "error": None
93
  }
94
 
95
  except Exception as e:
96
+ logger.error(f"Prediction error: {e}")
97
  return {
98
+ "sentiment": "ERROR",
99
  "confidence": 0.0,
100
+ "probabilities": {"Negative": 0.5, "Positive": 0.5},
101
+ "error": str(e)
102
  }
103
 
104
+ def predict_batch(self, texts: List[str]) -> List[Dict]:
105
+ """Predict sentiment for multiple texts"""
106
+ return [self.predict(text) for text in texts if text.strip()]
 
 
 
 
107
 
108
+ # ============================================================================
109
+ # VISUALIZATION FUNCTIONS
110
+ # ============================================================================
111
 
112
+ def create_probability_chart(probabilities: Dict[str, float]) -> go.Figure:
113
+ """Create a bar chart for sentiment probabilities"""
114
+ fig = go.Figure()
115
 
116
+ sentiments = list(probabilities.keys())
117
+ values = list(probabilities.values())
118
+ colors = ["#ff4444" if s == "Negative" else "#44ff44" for s in sentiments]
119
+
120
+ fig.add_trace(go.Bar(
121
+ x=sentiments,
122
+ y=values,
123
+ marker_color=colors,
124
+ text=[f"{v:.1%}" for v in values],
125
+ textposition='outside',
126
+ textfont=dict(size=14, color='white'),
127
+ hovertemplate='<b>%{x}</b><br>Probability: %{y:.2%}<extra></extra>'
128
+ ))
129
+
130
+ fig.update_layout(
131
+ title={
132
+ 'text': "Sentiment Probability Distribution",
133
+ 'x': 0.5,
134
+ 'xanchor': 'center',
135
+ 'font': {'size': 16, 'color': 'white'}
136
+ },
137
+ xaxis_title="Sentiment",
138
+ yaxis_title="Probability",
139
+ yaxis_range=[0, 1],
140
+ height=350,
141
+ template="plotly_dark",
142
+ showlegend=False,
143
+ margin=dict(t=60, b=60, l=60, r=40),
144
+ paper_bgcolor='rgba(0,0,0,0)',
145
+ plot_bgcolor='rgba(0,0,0,0)'
146
+ )
147
+
148
+ return fig
149
+
150
+ def create_batch_chart(results: List[Dict], texts: List[str]) -> go.Figure:
151
+ """Create a bar chart for batch analysis results"""
152
+ fig = go.Figure()
153
+
154
+ text_labels = [f"Text {i+1}" for i in range(len(results))]
155
+ confidences = [r['confidence'] for r in results]
156
+ sentiments = [r['sentiment'] for r in results]
157
+ colors = ["#ff4444" if s == "NEGATIVE" else "#44ff44" for s in sentiments]
158
+
159
+ # Truncate texts for hover
160
+ hover_texts = [t[:100] + "..." if len(t) > 100 else t for t in texts]
161
+
162
+ fig.add_trace(go.Bar(
163
+ x=text_labels,
164
+ y=confidences,
165
+ marker_color=colors,
166
+ text=[f"{c:.1%}" for c in confidences],
167
+ textposition='outside',
168
+ textfont=dict(size=12, color='white'),
169
+ customdata=list(zip(sentiments, hover_texts)),
170
+ hovertemplate='<b>%{x}</b><br>' +
171
+ 'Sentiment: %{customdata[0]}<br>' +
172
+ 'Confidence: %{y:.1%}<br>' +
173
+ 'Text: %{customdata[1]}<extra></extra>'
174
+ ))
175
+
176
+ fig.update_layout(
177
+ title={
178
+ 'text': "Batch Analysis Results",
179
+ 'x': 0.5,
180
+ 'xanchor': 'center',
181
+ 'font': {'size': 16, 'color': 'white'}
182
+ },
183
+ xaxis_title="Text Number",
184
+ yaxis_title="Confidence",
185
+ yaxis_range=[0, 1.1],
186
+ height=400,
187
+ template="plotly_dark",
188
+ showlegend=False,
189
+ margin=dict(t=60, b=80, l=60, r=40),
190
+ paper_bgcolor='rgba(0,0,0,0)',
191
+ plot_bgcolor='rgba(0,0,0,0)'
192
+ )
193
+
194
+ return fig
195
+
196
+ # ============================================================================
197
+ # GRADIO INTERFACE FUNCTIONS
198
+ # ============================================================================
199
+
200
+ def analyze_text(text: str) -> Tuple[str, str, go.Figure]:
201
+ """Analyze single text and return formatted results"""
202
+ result = analyzer.predict(text)
203
+
204
+ if result['error']:
205
+ return (
206
+ f"⚠️ **Error:** {result['error']}",
207
+ "0.0",
208
+ create_probability_chart({"Negative": 0.5, "Positive": 0.5})
209
  )
 
 
210
 
211
+ # Format output
212
+ emoji = "😊" if result['sentiment'] == "POSITIVE" else "😞"
213
+ sentiment_text = f"{emoji} **{result['sentiment']}**"
214
+ confidence_text = f"{result['confidence']:.1%}"
215
+
216
+ # Create chart
217
+ chart = create_probability_chart(result['probabilities'])
218
+
219
+ return sentiment_text, confidence_text, chart
220
 
221
+ def analyze_batch(text_input: str) -> Tuple[str, go.Figure]:
222
+ """Analyze multiple texts"""
223
+ if not text_input or not text_input.strip():
224
  empty_fig = go.Figure()
225
+ empty_fig.update_layout(
226
+ template="plotly_dark",
227
+ paper_bgcolor='rgba(0,0,0,0)',
228
+ annotations=[{
229
+ 'text': "Please enter texts to analyze",
230
+ 'xref': 'paper',
231
+ 'yref': 'paper',
232
+ 'x': 0.5,
233
+ 'y': 0.5,
234
+ 'showarrow': False,
235
+ 'font': {'size': 16, 'color': 'gray'}
236
+ }]
237
  )
238
+ return "Please enter texts (one per line)", empty_fig
 
239
 
240
+ # Split texts
241
  texts = [line.strip() for line in text_input.split('\n') if line.strip()]
242
 
243
  if not texts:
244
  empty_fig = go.Figure()
245
+ empty_fig.update_layout(
246
+ template="plotly_dark",
247
+ paper_bgcolor='rgba(0,0,0,0)',
248
+ annotations=[{
249
+ 'text': "No valid texts found",
250
+ 'xref': 'paper',
251
+ 'yref': 'paper',
252
+ 'x': 0.5,
253
+ 'y': 0.5,
254
+ 'showarrow': False,
255
+ 'font': {'size': 16, 'color': 'gray'}
256
+ }]
257
  )
 
258
  return "No valid texts found", empty_fig
259
 
260
+ # Analyze all texts
261
+ results = analyzer.predict_batch(texts)
262
 
263
+ # Create summary
264
+ summary_lines = ["### πŸ“Š Analysis Results\n"]
265
+ for i, (text, result) in enumerate(zip(texts, results), 1):
266
+ emoji = "😊" if result['sentiment'] == "POSITIVE" else "😞"
267
+ text_preview = text[:60] + "..." if len(text) > 60 else text
268
+ summary_lines.append(
269
+ f"{i}. {emoji} **{result['sentiment']}** ({result['confidence']:.1%}) - *{text_preview}*"
270
+ )
 
 
 
 
 
271
 
272
  summary = "\n".join(summary_lines)
273
 
274
+ # Create chart
275
+ chart = create_batch_chart(results, texts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ return summary, chart
278
+
279
+ # ============================================================================
280
+ # INITIALIZE ANALYZER
281
+ # ============================================================================
282
+
283
+ logger.info("Initializing sentiment analyzer...")
284
+ analyzer = SentimentAnalyzer()
285
+
286
+ # ============================================================================
287
+ # EXAMPLES
288
+ # ============================================================================
289
 
290
+ SINGLE_EXAMPLES = [
291
+ ["This movie is absolutely fantastic! I loved every minute of it."],
292
+ ["Terrible experience. Waste of time and money."],
293
+ ["The product is okay, nothing special but it works."],
294
+ ["Best purchase ever! Highly recommend to everyone!"],
295
+ ["Disappointed with the quality. Expected much better."]
 
 
296
  ]
297
 
298
+ BATCH_EXAMPLE = """This movie is absolutely fantastic! I loved every minute of it.
299
+ Terrible experience. Waste of time and money.
300
+ The product is okay, nothing special but it works.
301
+ Best purchase ever! Highly recommend to everyone!"""
302
+
303
+ # ============================================================================
304
+ # GRADIO UI
305
+ # ============================================================================
306
 
 
307
  with gr.Blocks(
 
308
  theme=gr.themes.Soft(
309
  primary_hue="blue",
310
+ secondary_hue="green",
311
+ ),
312
+ css="""
313
+ .gradio-container {
314
+ max-width: 1200px !important;
315
+ }
316
+ #component-0 {
317
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
318
+ }
319
+ """
320
  ) as demo:
321
 
322
+ # Header
323
  gr.Markdown("""
324
+ # 🎭 Sentiment Analysis with DistilBERT
325
 
326
+ Analyze text sentiment with a fine-tuned transformer model trained on IMDB reviews.
327
 
328
+ **Model:** DistilBERT | **Accuracy:** 80% | **F1:** 0.7981
 
 
 
 
329
  """)
330
 
331
+ # Main interface
332
  with gr.Tabs():
333
+ # TAB 1: Single Analysis
334
  with gr.TabItem("πŸ” Single Analysis"):
335
+ gr.Markdown("### Analyze individual texts")
 
 
336
 
337
  with gr.Row():
338
+ with gr.Column(scale=1):
339
  single_input = gr.Textbox(
340
+ label="Enter your text",
341
+ placeholder="Type or paste your text here...",
342
+ lines=6,
343
+ max_lines=10
344
+ )
345
+ single_btn = gr.Button(
346
+ "πŸš€ Analyze",
347
+ variant="primary",
348
+ size="lg"
349
+ )
350
+
351
+ gr.Examples(
352
+ examples=SINGLE_EXAMPLES,
353
+ inputs=single_input,
354
+ label="Try these examples:"
355
  )
 
356
 
357
+ with gr.Column(scale=1):
358
+ single_sentiment = gr.Markdown(
359
+ label="Result",
360
+ value="*Results will appear here*"
361
+ )
362
+ single_confidence = gr.Textbox(
363
+ label="Confidence Score",
364
+ interactive=False
365
+ )
366
+ single_plot = gr.Plot(label="Probability Distribution")
367
 
368
+ # TAB 2: Batch Processing
369
  with gr.TabItem("πŸ“Š Batch Processing"):
370
+ gr.Markdown("### Process multiple texts at once (one per line)")
 
 
371
 
372
  with gr.Row():
373
+ with gr.Column(scale=1):
374
  batch_input = gr.Textbox(
375
+ label="Enter multiple texts (one per line)",
376
  placeholder="Enter texts, one per line...",
377
+ lines=10,
378
  value=BATCH_EXAMPLE
379
  )
380
+ batch_btn = gr.Button(
381
+ "πŸš€ Process Batch",
382
+ variant="primary",
383
+ size="lg"
384
+ )
385
 
386
+ with gr.Column(scale=1):
387
+ batch_results = gr.Markdown(
388
+ label="Results Summary",
389
+ value="*Results will appear here*"
390
+ )
391
+ batch_plot = gr.Plot(label="Batch Analytics")
392
 
393
+ # TAB 3: About
394
  with gr.TabItem("ℹ️ About"):
395
  gr.Markdown("""
396
  ## About This Model
397
 
398
  ### πŸ—οΈ Architecture
399
+ - **Base Model:** DistilBERT (Distilled BERT)
400
  - **Parameters:** 66 million
401
+ - **Training Data:** IMDB Movie Reviews (50k reviews)
402
+ - **Fine-tuning:** Binary sentiment classification
403
+
404
+ ### πŸ“Š Performance Metrics
405
+ - **Test Accuracy:** 80.0%
406
+ - **F1 Score:** 0.7981
407
+ - **Precision:** High
408
+ - **Recall:** Balanced
409
+
410
+ ### ⚑ Features
411
+ - Fast inference (~100ms per prediction)
412
+ - Batch processing support
413
+ - Interactive visualizations
414
+ - Production-ready deployment
415
 
416
+ ### πŸ”— Resources
417
+ - **Model Repository:** [MartinRodrigo/distilbert-sentiment-imdb](https://huggingface.co/MartinRodrigo/distilbert-sentiment-imdb)
418
+ - **Space:** [transformer-sentiment-analysis](https://huggingface.co/spaces/MartinRodrigo/transformer-sentiment-analysis)
419
+ - **GitHub:** [ransformer-sentiment-analysis](https://github.com/mrdesautu/ransformer-sentiment-analysis)
420
 
421
+ ### πŸ› οΈ Tech Stack
422
  - **Framework:** PyTorch + Transformers
 
423
  - **UI:** Gradio
424
+ - **Visualization:** Plotly
425
+ - **Tracking:** MLflow (local development)
 
 
426
 
427
  ---
428
 
429
+ Built with ❀️ by Martin Rodrigo
430
  """)
431
 
432
+ # Connect event handlers
433
  single_btn.click(
434
+ fn=analyze_text,
435
+ inputs=[single_input],
436
+ outputs=[single_sentiment, single_confidence, single_plot]
437
  )
438
 
439
  batch_btn.click(
440
+ fn=analyze_batch,
441
+ inputs=[batch_input],
442
+ outputs=[batch_results, batch_plot]
443
  )
444
 
445
+ # ============================================================================
446
+ # LAUNCH
447
+ # ============================================================================
448
+
449
  if __name__ == "__main__":
450
+ demo.launch(
451
+ share=False,
452
+ show_error=True
453
+ )