Sentiment_class / app.py
eaglelandsonce's picture
Create app.py
48d659b verified
Raw
History Blame Contribute Delete
6.3 kB
#!/usr/bin/env python3
"""
Naive Bayes Spam Detector with Gradio UI
- Trains a simple sklearn Pipeline (CountVectorizer + MultinomialNB)
- Provides a text box to classify a single message
- Shows predicted label and spam probability
- Works offline (ships with a tiny embedded toy dataset) but can also read a local CSV if available
Optional local dataset:
If a file named 'sms_spam.csv' exists in the same folder with columns:
text,label
and labels in {'spam','ham'}
the app will train on that instead of the tiny toy set.
Author: ChatGPT (for Mike)
"""
from __future__ import annotations
import os
import csv
from typing import Tuple, List
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score
import gradio as gr
# ----------------------------
# 1) Load data (local CSV if present; else tiny toy dataset)
# ----------------------------
def load_dataset() -> Tuple[List[str], List[str]]:
csv_path = "sms_spam.csv"
if os.path.isfile(csv_path):
texts, labels = [], []
with open(csv_path, encoding="utf-8") as f:
reader = csv.DictReader(f)
required = {"text", "label"}
if not required.issubset(set(reader.fieldnames or [])):
raise ValueError(
"sms_spam.csv must contain columns: text,label"
)
for row in reader:
texts.append((row["text"] or "").strip())
labels.append((row["label"] or "").strip().lower())
# basic cleanup
labels = ["spam" if l == "spam" else "ham" for l in labels]
return texts, labels
# Fallback: tiny embedded toy dataset (for offline demo)
toy = [
("Free entry in 2 a wkly comp to win FA Cup final tkts", "spam"),
("URGENT! You have won a 1 week FREE membership", "spam"),
("Call now to claim your prize", "spam"),
("WINNER!! As a valued network customer you have been selected", "spam"),
("Congrats! You won a lottery. Reply to claim.", "spam"),
("See you at practice tonight.", "ham"),
("Are we still on for lunch today?", "ham"),
("I'll pick you up at 7.", "ham"),
("Don't forget the meeting tomorrow morning.", "ham"),
("Can you send the report by EOD?", "ham"),
("This is not a drill. Limited time offer!!!", "spam"),
("Happy birthday! Hope you have a great day!", "ham"),
("Reminder: dentist appointment at 3pm", "ham"),
("Get cheap meds without prescription now", "spam"),
("Double your income fast. Click here", "spam"),
]
texts, labels = zip(*toy)
return list(texts), list(labels)
X_texts, y_labels = load_dataset()
# ----------------------------
# 2) Train/test split and Pipeline
# ----------------------------
X_train, X_test, y_train, y_test = train_test_split(
X_texts, y_labels, test_size=0.25, random_state=42, stratify=y_labels
)
model = Pipeline(
steps=[
("vect", CountVectorizer(stop_words="english")),
("clf", MultinomialNB()),
]
)
model.fit(X_train, y_train)
# Evaluate simple accuracy for display
if len(X_test) > 0:
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
else:
accuracy = np.nan
# ----------------------------
# 3) Inference function for Gradio
# ----------------------------
def classify_message(message: str) -> Tuple[str, float]:
"""
Returns:
predicted_label ('spam' or 'ham'),
spam_probability (0-100 as percentage)
"""
if not message or not message.strip():
return "ham", 0.0
# predict_proba -> columns are in model.classes_
proba = model.predict_proba([message])[0]
# Find index for 'spam' class
classes = list(model.classes_)
if "spam" in classes:
spam_idx = classes.index("spam")
spam_prob = float(proba[spam_idx])
else:
# Shouldn't happen with our labels, but be safe
spam_prob = 1.0 - float(proba[0])
label = "spam" if spam_prob >= 0.5 else "ham"
return label, round(spam_prob * 100.0, 2)
# ----------------------------
# 4) Build Gradio UI
# ----------------------------
title = "📩 Naive Bayes Spam Classifier"
if not np.isnan(accuracy):
description = (
"A simple text classifier using CountVectorizer + MultinomialNB. "
f"Current test accuracy on this dataset: **{accuracy:.2f}**.\n\n"
"Type a message to see if it's predicted as **spam** or **ham**, "
"and view the spam probability."
)
else:
description = (
"A simple text classifier using CountVectorizer + MultinomialNB.\n\n"
"Type a message to see if it's predicted as **spam** or **ham**, "
"and view the spam probability."
)
examples = [
["Congratulations! You just won a $1,000 gift card. Click to claim."],
["Don't forget our meeting at 10am today."],
["URGENT! Your account has been compromised. Verify now."],
["See you at practice tonight."],
]
with gr.Blocks(title=title) as demo:
gr.Markdown(f"# {title}")
gr.Markdown(description)
with gr.Row():
with gr.Column(scale=2):
message = gr.Textbox(
label="message",
placeholder="Type or paste an SMS/email message here...",
lines=4,
)
btn = gr.Button("Submit", variant="primary")
clr = gr.Button("Clear")
with gr.Column(scale=1):
pred = gr.Textbox(label="Prediction (spam/ham)", interactive=False)
prob = gr.Textbox(label="Spam Probability (%)", interactive=False)
gr.Examples(
examples=examples,
inputs=[message],
label="Try some examples",
)
def _clear():
return gr.update(value=""), gr.update(value=""), gr.update(value="")
btn.click(classify_message, inputs=message, outputs=[pred, prob])
clr.click(_clear, outputs=[message, pred, prob])
# ----------------------------
# 5) Launch
# ----------------------------
if __name__ == "__main__":
# Set share=True if you want a public share link
demo.launch()