sentiment-analysis / streamlit_app.py
TinTinDo's picture
Upload 5 files
9262cf2 verified
Raw
History Blame Contribute Delete
6.09 kB
# app.py
import streamlit as st
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
from utils import load_model, clean_text, predict_sentiment
# ---- Page Config ----
st.set_page_config(
page_title="Sentiment Analysis App",
page_icon="πŸ“Š",
layout="wide"
)
# ---- CSS ----
st.markdown("""
<style>
.positive { color: #2ecc71; font-size: 2rem; font-weight: bold; }
.negative { color: #e74c3c; font-size: 2rem; font-weight: bold; }
.metric-card {
background: #1e2130;
border-radius: 12px;
padding: 24px;
text-align: center;
margin-top: 10px;
}
.footer { text-align: center; color: gray; font-size: 0.85rem; }
</style>
""", unsafe_allow_html=True)
# ---- Load Model ----
tokenizer, model, device = load_model()
# ---- Header ----
st.title("πŸ“Š Real-Time Sentiment Analysis")
st.markdown("*Fine-tuned **DistilBERT** Β· 91% accuracy Β· Built by Nguyen Tin Tin Do*")
st.divider()
# ---- Tabs ----
tab1, tab2, tab3 = st.tabs(["πŸ” Single Prediction", "πŸ“‹ Batch Prediction", "πŸ“ˆ Model Info"])
# ==============================
# TAB 1 β€” Single Prediction
# ==============================
with tab1:
user_input = st.text_area(
"Enter text to analyze:",
placeholder="e.g. This movie was absolutely amazing...",
height=150
)
if st.button("πŸš€ Analyze Sentiment", use_container_width=True):
if user_input.strip():
with st.spinner("Analyzing..."):
cleaned = clean_text(user_input)
result = predict_sentiment(cleaned, tokenizer, model, device)
sentiment = result["sentiment"]
confidence = result["confidence"]
latency = result["inference_time_ms"]
col1, col2, col3 = st.columns(3)
col1.metric("Sentiment", f"{'βœ…' if sentiment == 'Positive' else '❌'} {sentiment}")
col2.metric("Confidence", f"{confidence*100:.1f}%")
col3.metric("Latency", f"{latency}ms")
# Gauge chart
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=confidence * 100,
title={"text": "Confidence Score (%)"},
gauge={
"axis": {"range": [0, 100]},
"bar": {"color": "#2ecc71" if sentiment == "Positive" else "#e74c3c"},
"steps": [
{"range": [0, 50], "color": "#1a1a2e"},
{"range": [50, 100], "color": "#16213e"}
],
"threshold": {
"line": {"color": "white", "width": 3},
"value": 50
}
}
))
fig.update_layout(
height=280,
paper_bgcolor="rgba(0,0,0,0)",
font={"color": "white"}
)
st.plotly_chart(fig, use_container_width=True)
else:
st.warning("⚠️ Please enter some text first.")
# ==============================
# TAB 2 β€” Batch Prediction
# ==============================
with tab2:
batch_input = st.text_area(
"Enter multiple texts (one per line):",
placeholder="This film was great!\nTerrible acting...\nAbsolutely loved it!",
height=180
)
if st.button("πŸ”„ Analyze Batch", use_container_width=True):
texts = [t.strip() for t in batch_input.split("\n") if t.strip()]
if texts:
results = []
progress = st.progress(0)
for i, text in enumerate(texts):
cleaned = clean_text(text)
result = predict_sentiment(cleaned, tokenizer, model, device)
results.append({
"Text" : text[:60] + "..." if len(text) > 60 else text,
"Sentiment" : result["sentiment"],
"Confidence": f"{result['confidence']*100:.1f}%",
"Latency" : f"{result['inference_time_ms']}ms"
})
progress.progress((i + 1) / len(texts))
# Table
df = pd.DataFrame(results)
st.dataframe(df, use_container_width=True)
# Pie chart
counts = df["Sentiment"].value_counts()
fig = px.pie(
values=counts.values,
names=counts.index,
title=f"Sentiment Distribution ({len(texts)} texts)",
color=counts.index,
color_discrete_map={"Positive": "#2ecc71", "Negative": "#e74c3c"}
)
fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", font={"color": "white"})
st.plotly_chart(fig, use_container_width=True)
else:
st.warning("⚠️ Please enter at least one text.")
# ==============================
# TAB 3 β€” Model Info
# ==============================
with tab3:
st.markdown("""
### πŸ€– Model Details
| | |
|---|---|
| **Base Model** | DistilBERT (distilbert-base-uncased) |
| **Fine-tuned on** | IMDB Movie Reviews (50,000 samples) |
| **Training Epochs** | 3 |
| **Batch Size** | 16 |
| **Learning Rate** | 2e-5 |
| **Accuracy** | 91% |
| **F1-Score** | 0.89 |
### πŸ“Š Performance vs Baseline
| Model | Accuracy | F1-Score |
|---|---|---|
| TF-IDF + Logistic Regression | 77% | 0.77 |
| **DistilBERT (fine-tuned)** | **91%** | **0.89** |
### πŸ› οΈ Tech Stack
`Python` `HuggingFace Transformers` `FastAPI` `Streamlit` `Plotly` `Docker`
""")
# ---- Footer ----
st.divider()
st.markdown(
"<p class='footer'>Built by <strong>Nguyen Tin Tin Do</strong> Β· "
"<a href='https://github.com/NguyenTin'>GitHub</a> Β· "
"<a href='https://linkedin.com/in/nguyen-tin-tin-do'>LinkedIn</a></p>",
unsafe_allow_html=True
)