File size: 7,709 Bytes
16816fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""ScamShield NLP — Model training script.
This script retrains the calibrated Linear SVM model and TF-IDF vectorizer
using scikit-learn 1.4, compatible with Python 3.10. It uses publicly available
spam datasets to replicate the original model's performance.

Usage:
    python train_model.py
"""

import os
import joblib
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import LinearSVC
from sklearn.calibration import CalibratedClassifierCV
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score

# Import preprocessing function to ensure consistency
from preprocessing import clean_text

# Create models directory if it doesn't exist
MODELS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models")
os.makedirs(MODELS_DIR, exist_ok=True)

def load_and_prepare_datasets():
    """Load and combine publicly available spam datasets.
    Downloads and processes the SMS Spam Collection and other public datasets
    to create a training corpus similar to the original ScamShield dataset.
    """
    print("Loading datasets...")
    
    # First, load the UCI SMS Spam Collection (most widely used spam dataset)
    # We'll download it and process it
    try:
        # Try to fetch from UCI repository using ucimlrepo
        from ucimlrepo import fetch_ucirepo
        sms_spam = fetch_ucirepo(id=228)
        X = sms_spam.data.features
        y = sms_spam.data.targets
        
        # Convert to DataFrame
        df = pd.DataFrame({
            'text': X['v2'],
            'label': y['v1'].map({'ham': 0, 'spam': 1})
        })
        print(f"Loaded UCI SMS Spam Collection: {len(df)} messages")
        
    except Exception as e:
        print(f"Could not load from ucimlrepo, falling back to downloading directly: {e}")
        # Alternative: download the dataset directly
        import requests
        url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip"
        import zipfile
        import io
        
        r = requests.get(url)
        z = zipfile.ZipFile(io.BytesIO(r.content))
        with z.open('SMSSpamCollection') as f:
            lines = f.readlines()
            data = []
            for line in lines:
                line = line.decode('utf-8').strip()
                if line:
                    label, text = line.split('\t', 1)
                    data.append({'text': text, 'label': 1 if label == 'spam' else 0})
            df = pd.DataFrame(data)
            print(f"Loaded UCI SMS Spam Collection (direct download): {len(df)} messages")

    # Let's also add some additional phishing data to enrich the dataset
    # Try to load the phishing dataset from Hugging Face if available, else continue with what we have
    try:
        from datasets import load_dataset
        hf_dataset = load_dataset("ealvaradob/phishing-dataset", "combined", split="train")
        hf_df = pd.DataFrame(hf_dataset)
        # Only keep text and label columns, filter where label is 0 or 1
        hf_df = hf_df[hf_df['label'].isin([0, 1])][['text', 'label']]
        # Combine with our existing dataframe
        df = pd.concat([df, hf_df], ignore_index=True)
        print(f"Added Hugging Face phishing dataset: {len(hf_df)} messages, total now: {len(df)}")
    except Exception as e:
        print(f"Could not load Hugging Face dataset, continuing with SMS dataset only: {e}")

    # Remove duplicates
    df = df.drop_duplicates(subset=['text']).reset_index(drop=True)
    print(f"After removing duplicates: {len(df)} messages")
    
    # Apply the same preprocessing used in inference
    print("Applying text preprocessing...")
    df['cleaned_text'] = df['text'].apply(clean_text)
    
    # Remove any empty texts after preprocessing
    df = df[df['cleaned_text'].str.strip() != ''].reset_index(drop=True)
    print(f"Final dataset size after cleaning: {len(df)} messages")
    print(f"Legitimate messages (0): {len(df[df['label'] == 0])}, Scam messages (1): {len(df[df['label'] == 1])}")
    
    return df

def train_and_evaluate_model(df):
    """Train the TF-IDF vectorizer and calibrated LinearSVC model, then evaluate."""
    print("\nStarting model training...")
    
    # Split into train and test sets
    X_train, X_test, y_train, y_test = train_test_split(
        df['cleaned_text'], 
        df['label'], 
        test_size=0.2, 
        random_state=42, 
        stratify=df['label']
    )
    print(f"Train set size: {len(X_train)}, Test set size: {len(X_test)}")
    
    # Initialize and fit TF-IDF Vectorizer with the same parameters as original
    print("\nFitting TF-IDF vectorizer...")
    vectorizer = TfidfVectorizer(
        ngram_range=(1, 2),
        max_features=10000,
        min_df=2,
        sublinear_tf=True,
        lowercase=True
    )
    X_train_tfidf = vectorizer.fit_transform(X_train)
    X_test_tfidf = vectorizer.transform(X_test)
    print(f"TF-IDF vectorizer fitted. Vocabulary size: {len(vectorizer.vocabulary_)}")
    
    # Train LinearSVC and calibrate with CalibratedClassifierCV (same as original)
    print("\nTraining LinearSVC with calibration...")
    base_svm = LinearSVC(C=1.0, random_state=42, max_iter=10000)
    calibrated_svm = CalibratedClassifierCV(base_svm, cv=5)
    calibrated_svm.fit(X_train_tfidf, y_train)
    print("Model training complete.")
    
    # Evaluate on test set
    print("\nEvaluating model on test set...")
    y_pred = calibrated_svm.predict(X_test_tfidf)
    y_pred_proba = calibrated_svm.predict_proba(X_test_tfidf)[:, 1]
    
    # Calculate metrics
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)
    roc_auc = roc_auc_score(y_test, y_pred_proba)
    
    print(f"Model Performance Metrics:")
    print(f"  Accuracy:  {accuracy:.4f} ({accuracy*100:.2f}%)")
    print(f"  Precision: {precision:.4f} ({precision*100:.2f}%)")
    print(f"  Recall:    {recall:.4f} ({recall*100:.2f}%)")
    print(f"  F1-score:  {f1:.4f} ({f1*100:.2f}%)")
    print(f"  ROC-AUC:   {roc_auc:.4f} ({roc_auc*100:.2f}%)")
    
    # Calculate number of missed scams (false negatives)
    fn_count = np.sum((y_test == 1) & (y_pred == 0))
    print(f"  Missed scams (FN): {fn_count} / {len(y_test[y_test == 1])} ({fn_count/len(y_test[y_test == 1])*100:.2f}%)")
    
    return vectorizer, calibrated_svm

def save_models(vectorizer, model):
    """Save the trained vectorizer and model to the models directory."""
    print("\nSaving trained models...")
    
    vectorizer_path = os.path.join(MODELS_DIR, "tfidf_vectorizer.joblib")
    model_path = os.path.join(MODELS_DIR, "svm.joblib")
    
    # Save with joblib
    joblib.dump(vectorizer, vectorizer_path)
    joblib.dump(model, model_path)
    
    print(f"Vectorizer saved to: {vectorizer_path}")
    print(f"Model saved to: {model_path}")
    print("Models saved successfully!")

def main():
    """Main training workflow."""
    print("="*60)
    print("ScamShield NLP - Model Retraining Script")
    print("Using Python 3.10+ and scikit-learn 1.4")
    print("="*60)
    
    # Load and prepare datasets
    df = load_and_prepare_datasets()
    
    # Train and evaluate the model
    vectorizer, model = train_and_evaluate_model(df)
    
    # Save the models
    save_models(vectorizer, model)
    
    print("\n" + "="*60)
    print("Training process completed successfully!")
    print("The new models are now ready for use with the Gradio app.")
    print("="*60)

if __name__ == "__main__":
    main()