File size: 6,297 Bytes
48d659b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/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()