atharvawarade9807 commited on
Commit
f4481f7
·
verified ·
1 Parent(s): 4e17014

Upload 44 files

Browse files
Files changed (44) hide show
  1. Version-3/cli.py +251 -0
  2. Version-3/models/SVM_model.pkl +3 -0
  3. Version-3/models/vectorizer.pkl +3 -0
  4. Version-3/outputs/2025-12-25_10-01-23/models/SVM_model.pkl +3 -0
  5. Version-3/outputs/2025-12-25_10-01-23/models/vectorizer.pkl +3 -0
  6. Version-3/outputs/2025-12-25_10-01-23/observations/DecisionTree_detailed_metrics.csv +6 -0
  7. Version-3/outputs/2025-12-25_10-01-23/observations/KNN_detailed_metrics.csv +6 -0
  8. Version-3/outputs/2025-12-25_10-01-23/observations/LogisticRegression_detailed_metrics.csv +6 -0
  9. Version-3/outputs/2025-12-25_10-01-23/observations/RandomForest_detailed_metrics.csv +6 -0
  10. Version-3/outputs/2025-12-25_10-01-23/observations/SVM_detailed_metrics.csv +6 -0
  11. Version-3/outputs/2025-12-25_10-01-23/observations/best_model_info.csv +12 -0
  12. Version-3/outputs/2025-12-25_10-01-23/observations/best_parameters.csv +29 -0
  13. Version-3/outputs/2025-12-25_10-01-23/observations/cross_validation_summary.csv +6 -0
  14. Version-3/outputs/2025-12-25_10-01-23/observations/model_comparison_summary.csv +6 -0
  15. Version-3/outputs/2025-12-25_10-01-23/observations/model_metadata.csv +2 -0
  16. Version-3/outputs/2025-12-25_14-02-05/models/SVM_model.pkl +3 -0
  17. Version-3/outputs/2025-12-25_14-02-05/models/vectorizer.pkl +3 -0
  18. Version-3/outputs/2025-12-25_14-02-05/observations/best_model_info.csv +12 -0
  19. Version-3/outputs/2025-12-25_14-02-05/observations/best_parameters.csv +29 -0
  20. Version-3/outputs/2025-12-25_14-02-05/observations/cross_validation_summary.csv +6 -0
  21. Version-3/outputs/2025-12-25_14-02-05/observations/model_comparison_summary.csv +6 -0
  22. Version-3/outputs/2025-12-25_14-02-05/observations/model_metadata.csv +2 -0
  23. Version-3/requirements.txt +8 -0
  24. Version-3/src/__init__.py +0 -0
  25. Version-3/src/__pycache__/__init__.cpython-313.pyc +0 -0
  26. Version-3/src/components/data_ingestion.py +21 -0
  27. Version-3/src/components/data_transformation.py +65 -0
  28. Version-3/src/components/model_training.py +253 -0
  29. Version-3/src/config/__pycache__/config.cpython-313.pyc +0 -0
  30. Version-3/src/config/config.py +41 -0
  31. Version-3/src/pipeline/__init__.py +0 -0
  32. Version-3/src/pipeline/__pycache__/__init__.cpython-313.pyc +0 -0
  33. Version-3/src/pipeline/__pycache__/prediction_pipeline.cpython-313.pyc +0 -0
  34. Version-3/src/pipeline/prediction_pipeline.py +135 -0
  35. Version-3/src/pipeline/training_pipeline.py +52 -0
  36. Version-3/src/utils/__init__.py +0 -0
  37. Version-3/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  38. Version-3/src/utils/__pycache__/email_utils.cpython-313.pyc +0 -0
  39. Version-3/src/utils/__pycache__/logger.cpython-313.pyc +0 -0
  40. Version-3/src/utils/__pycache__/state.cpython-313.pyc +0 -0
  41. Version-3/src/utils/email_utils.py +54 -0
  42. Version-3/src/utils/logger.py +34 -0
  43. Version-3/src/utils/state.py +24 -0
  44. Version-3/src/utils/utils.py +0 -0
Version-3/cli.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ import sys
3
+ import os
4
+ import re
5
+ import mailbox
6
+ import argparse
7
+ import csv
8
+ from html import unescape
9
+ from bs4 import BeautifulSoup
10
+
11
+ MODEL_PATH = "models/SVM_model.pkl"
12
+ FEATURE_PATH = "models/vectorizer.pkl"
13
+
14
+ # ── text helpers ─────────────────────────────────────────────────────────────
15
+ def clean_text(text):
16
+ if not isinstance(text, str):
17
+ return text
18
+ text = re.sub(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\u200B-\u200F\uFEFF]', '', text)
19
+ text = text.encode("utf-16", "surrogatepass").decode("utf-16", "ignore")
20
+ return text[:32767]
21
+
22
+ def extract_body(msg):
23
+ texts = []
24
+ if msg.is_multipart():
25
+ for part in msg.walk():
26
+ if part.get_content_type() in ("text/plain", "text/html"):
27
+ payload = part.get_payload(decode=True)
28
+ if payload:
29
+ text = unescape(payload.decode(errors="ignore"))
30
+ text = BeautifulSoup(text, "html.parser").get_text(" ")
31
+ texts.append(text)
32
+ else:
33
+ payload = msg.get_payload(decode=True)
34
+ if payload:
35
+ text = unescape(payload.decode(errors="ignore"))
36
+ text = BeautifulSoup(text, "html.parser").get_text(" ")
37
+ texts.append(text)
38
+ combined = " ".join(texts)
39
+ combined = re.sub(r'[\r\n\t]+', ' ', combined)
40
+ combined = re.sub(r'\s+', ' ', combined)
41
+ return combined.strip()
42
+
43
+ # ── model ─────────────────────────────────────────────────────────────────────
44
+ def load_models():
45
+ for path in (MODEL_PATH, FEATURE_PATH):
46
+ if not os.path.exists(path):
47
+ print(f"[ERROR] File not found: {path}")
48
+ sys.exit(1)
49
+ vectorizer = pickle.load(open(FEATURE_PATH, "rb"))
50
+ model = pickle.load(open(MODEL_PATH, "rb"))
51
+ return vectorizer, model
52
+
53
+ def sigmoid(x):
54
+ import math
55
+ return 1 / (1 + math.exp(-x))
56
+
57
+ def predict(text, vectorizer, model):
58
+ """
59
+ Returns:
60
+ label : "Spam" or "Ham"
61
+ confidence : probability % if model supports it, else None
62
+ spam_score : 0-100 spam intensity score (always available)
63
+ """
64
+ cleaned = clean_text(text)
65
+ features = vectorizer.transform([cleaned])
66
+ pred = model.predict(features)[0]
67
+ label = "Spam" if str(pred) == "0" else "Ham"
68
+
69
+ # confidence via predict_proba (not all SVMs support this)
70
+ confidence = None
71
+ try:
72
+ proba = model.predict_proba(features)
73
+ # index 0 = spam class (label 0), index 1 = ham class (label 1)
74
+ confidence = round(float(proba[0][0]) * 100, 1)
75
+ except Exception:
76
+ pass
77
+
78
+ # spam score via decision function (always works for SVM)
79
+ # decision_function > 0 means ham, < 0 means spam for binary SVC
80
+ # we flip and sigmoid-scale so higher = more spammy
81
+ spam_score = None
82
+ try:
83
+ df_val = float(model.decision_function(features)[0])
84
+ # flip: spam has negative decision value in sklearn SVC (class 0)
85
+ spam_score = round(sigmoid(-df_val) * 100, 1)
86
+ except Exception:
87
+ pass
88
+
89
+ # fall back score to confidence if decision_function unavailable
90
+ if spam_score is None and confidence is not None:
91
+ spam_score = confidence
92
+
93
+ return label, confidence, spam_score
94
+
95
+ # ── display helpers ───────────────────────────────────────────────────────────
96
+ def score_bar(score, width=20):
97
+ """Visual bar: [████████░░░░░░░░░░░░] 42.3"""
98
+ if score is None:
99
+ return "N/A"
100
+ filled = round(score / 100 * width)
101
+ bar = "█" * filled + "░" * (width - filled)
102
+ return f"[{bar}] {score:.1f}%"
103
+
104
+ def risk_level(score):
105
+ if score is None:
106
+ return "Unknown"
107
+ if score >= 80:
108
+ return "HIGH RISK"
109
+ if score >= 50:
110
+ return "MEDIUM RISK"
111
+ return "LOW RISK"
112
+
113
+ def print_result(label, confidence, spam_score):
114
+ verdict = "*** SPAM ***" if label == "Spam" else "Ham (Safe) "
115
+ print(f"\n Verdict : {verdict}")
116
+ print(f" Spam Score : {score_bar(spam_score)}")
117
+ if confidence is not None:
118
+ print(f" Confidence : {confidence:.1f}%")
119
+ print(f" Risk Level : {risk_level(spam_score)}")
120
+ print()
121
+
122
+ # ── modes ─────────────────────────────────────────────────────────────────────
123
+ def mode_interactive(vectorizer, model):
124
+ print("\n=== Spam Email Detector ===")
125
+ print("Paste your email text. Press Enter twice to classify.")
126
+ print("Type 'quit' to exit.\n")
127
+ while True:
128
+ print("─" * 44)
129
+ lines = []
130
+ blank_count = 0
131
+ while True:
132
+ try:
133
+ line = input()
134
+ except EOFError:
135
+ break
136
+ if line.strip().lower() in ("quit", "exit"):
137
+ print("Goodbye.")
138
+ sys.exit(0)
139
+ if line.strip() == "":
140
+ blank_count += 1
141
+ if blank_count >= 2:
142
+ break
143
+ else:
144
+ blank_count = 0
145
+ lines.append(line)
146
+
147
+ text = "\n".join(lines).strip()
148
+ if not text:
149
+ print("[!] No text entered. Try again.\n")
150
+ continue
151
+
152
+ label, confidence, spam_score = predict(text, vectorizer, model)
153
+ print_result(label, confidence, spam_score)
154
+
155
+ def mode_single(text, vectorizer, model):
156
+ label, confidence, spam_score = predict(text, vectorizer, model)
157
+ print_result(label, confidence, spam_score)
158
+
159
+ def mode_mbox(mbox_path, vectorizer, model, output_csv):
160
+ if not os.path.exists(mbox_path):
161
+ print(f"[ERROR] File not found: {mbox_path}")
162
+ sys.exit(1)
163
+ print(f"Loading: {mbox_path}")
164
+ mbox = mailbox.mbox(mbox_path)
165
+ messages = list(mbox)
166
+ print(f"Found {len(messages)} emails. Classifying...\n")
167
+
168
+ results = []
169
+ spam_count = 0
170
+ scores = []
171
+
172
+ for i, msg in enumerate(messages, 1):
173
+ subject = clean_text(msg.get("Subject", "(no subject)"))
174
+ body = extract_body(msg)
175
+ label, confidence, spam_score = predict(body, vectorizer, model)
176
+
177
+ if label == "Spam":
178
+ spam_count += 1
179
+ if spam_score is not None:
180
+ scores.append(spam_score)
181
+
182
+ conf_str = f"{confidence:.1f}%" if confidence is not None else "N/A"
183
+ score_str = f"{spam_score:.1f}%" if spam_score is not None else "N/A"
184
+ risk = risk_level(spam_score)
185
+ marker = "SPAM" if label == "Spam" else "Ham "
186
+
187
+ results.append({
188
+ "Index": i,
189
+ "Subject": subject,
190
+ "Prediction": label,
191
+ "SpamScore": score_str,
192
+ "Confidence": conf_str,
193
+ "RiskLevel": risk,
194
+ })
195
+ print(f" [{i:>4}] {marker} Score:{score_str:>6} {risk:<11} {subject[:50]}")
196
+
197
+ ham_count = len(results) - spam_count
198
+ avg_score = round(sum(scores) / len(scores), 1) if scores else 0
199
+
200
+ print(f"\n{'─'*44}")
201
+ print(f" Total emails : {len(results)}")
202
+ print(f" Spam : {spam_count}")
203
+ print(f" Ham : {ham_count}")
204
+ print(f" Avg Spam Score : {avg_score}%")
205
+
206
+ if output_csv:
207
+ with open(output_csv, "w", newline="", encoding="utf-8") as f:
208
+ writer = csv.DictWriter(f, fieldnames=["Index","Subject","Prediction","SpamScore","Confidence","RiskLevel"])
209
+ writer.writeheader()
210
+ writer.writerows(results)
211
+ print(f"\n Saved to: {output_csv}")
212
+
213
+ # ── entry point ───────────────────────────────────────────────────────────────
214
+ def main():
215
+ parser = argparse.ArgumentParser(
216
+ description="Spam Email Detector — CMD",
217
+ formatter_class=argparse.RawTextHelpFormatter,
218
+ epilog="""
219
+ Examples:
220
+ python cli.py # interactive
221
+ python cli.py --text "You won a prize! Click now." # inline text
222
+ python cli.py --file email.txt # from file
223
+ python cli.py --mbox inbox.mbox --output results.csv # batch mbox
224
+ """
225
+ )
226
+ parser.add_argument("--text", help="Email text to classify (inline)")
227
+ parser.add_argument("--file", help="Path to a .txt file with email content")
228
+ parser.add_argument("--mbox", help="Path to an .mbox file for batch classification")
229
+ parser.add_argument("--output", help="CSV file to save mbox results (optional)")
230
+ args = parser.parse_args()
231
+
232
+ print("Loading models...", end=" ", flush=True)
233
+ vectorizer, model = load_models()
234
+ print("OK\n")
235
+
236
+ if args.mbox:
237
+ mode_mbox(args.mbox, vectorizer, model, args.output)
238
+ elif args.text:
239
+ mode_single(args.text, vectorizer, model)
240
+ elif args.file:
241
+ if not os.path.exists(args.file):
242
+ print(f"[ERROR] File not found: {args.file}")
243
+ sys.exit(1)
244
+ with open(args.file, "r", encoding="utf-8", errors="ignore") as f:
245
+ text = f.read()
246
+ mode_single(text, vectorizer, model)
247
+ else:
248
+ mode_interactive(vectorizer, model)
249
+
250
+ if __name__ == "__main__":
251
+ main()
Version-3/models/SVM_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6d4b7ec310866553c9aeeacf3f69abb592b584309cb1b9392281335cd73e8cf7
3
+ size 131573
Version-3/models/vectorizer.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7caca664beb75eeba23cc9f30dfcda1c0ada3a4f7bfd281452ef8aea2115310f
3
+ size 138244
Version-3/outputs/2025-12-25_10-01-23/models/SVM_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6d4b7ec310866553c9aeeacf3f69abb592b584309cb1b9392281335cd73e8cf7
3
+ size 131573
Version-3/outputs/2025-12-25_10-01-23/models/vectorizer.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d75c40fde3736879643d139e83e814309032478c54d5e9f07acb8051996ccef9
3
+ size 138244
Version-3/outputs/2025-12-25_10-01-23/observations/DecisionTree_detailed_metrics.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Metric,Value
2
+ Accuracy,0.9599282296650717
3
+ Precision,0.9588033625788168
4
+ Recall,0.9599282296650717
5
+ F1-Score,0.9588283410865664
6
+ CV Score,0.9834796073961313
Version-3/outputs/2025-12-25_10-01-23/observations/KNN_detailed_metrics.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Metric,Value
2
+ Accuracy,0.93122009569378
3
+ Precision,0.9362806772646151
4
+ Recall,0.93122009569378
5
+ F1-Score,0.9206571512502397
6
+ CV Score,0.9612898978068009
Version-3/outputs/2025-12-25_10-01-23/observations/LogisticRegression_detailed_metrics.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Metric,Value
2
+ Accuracy,0.9766746411483254
3
+ Precision,0.9767291368060467
4
+ Recall,0.9766746411483254
5
+ F1-Score,0.9759311514144211
6
+ CV Score,0.9876984264199944
Version-3/outputs/2025-12-25_10-01-23/observations/RandomForest_detailed_metrics.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Metric,Value
2
+ Accuracy,0.9706937799043063
3
+ Precision,0.9714374960389777
4
+ Recall,0.9706937799043063
5
+ F1-Score,0.9692103447020837
6
+ CV Score,0.9878592900446806
Version-3/outputs/2025-12-25_10-01-23/observations/SVM_detailed_metrics.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Metric,Value
2
+ Accuracy,0.979066985645933
3
+ Precision,0.9789275513603889
4
+ Recall,0.979066985645933
5
+ F1-Score,0.9785830328138276
6
+ CV Score,0.9891548320127734
Version-3/outputs/2025-12-25_10-01-23/observations/best_model_info.csv ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Attribute,Value
2
+ Best Model Name,SVM
3
+ Accuracy,0.979066985645933
4
+ Precision,0.9789275513603889
5
+ Recall,0.979066985645933
6
+ F1-Score,0.9785830328138276
7
+ CV Score,0.9891548320127734
8
+ Best Parameters,"{
9
+ ""C"": 10,
10
+ ""gamma"": ""scale"",
11
+ ""kernel"": ""linear""
12
+ }"
Version-3/outputs/2025-12-25_10-01-23/observations/best_parameters.csv ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Model,Best_Parameters,CV_Score
2
+ LogisticRegression,"{
3
+ ""C"": 100,
4
+ ""max_iter"": 100,
5
+ ""solver"": ""liblinear""
6
+ }",0.9876984264199944
7
+ DecisionTree,"{
8
+ ""criterion"": ""gini"",
9
+ ""max_depth"": null,
10
+ ""min_samples_leaf"": 1,
11
+ ""min_samples_split"": 2
12
+ }",0.9834796073961313
13
+ SVM,"{
14
+ ""C"": 10,
15
+ ""gamma"": ""scale"",
16
+ ""kernel"": ""linear""
17
+ }",0.9891548320127734
18
+ KNN,"{
19
+ ""metric"": ""manhattan"",
20
+ ""n_neighbors"": 3,
21
+ ""weights"": ""distance""
22
+ }",0.9612898978068009
23
+ RandomForest,"{
24
+ ""max_depth"": null,
25
+ ""max_features"": ""sqrt"",
26
+ ""min_samples_leaf"": 1,
27
+ ""min_samples_split"": 5,
28
+ ""n_estimators"": 50
29
+ }",0.9878592900446806
Version-3/outputs/2025-12-25_10-01-23/observations/cross_validation_summary.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Model,Best_CV_Score,Best_Parameters
2
+ LogisticRegression,0.9876984264199944,"{""C"": 100, ""max_iter"": 100, ""solver"": ""liblinear""}"
3
+ DecisionTree,0.9834796073961313,"{""criterion"": ""gini"", ""max_depth"": null, ""min_samples_leaf"": 1, ""min_samples_split"": 2}"
4
+ SVM,0.9891548320127734,"{""C"": 10, ""gamma"": ""scale"", ""kernel"": ""linear""}"
5
+ KNN,0.9612898978068009,"{""metric"": ""manhattan"", ""n_neighbors"": 3, ""weights"": ""distance""}"
6
+ RandomForest,0.9878592900446806,"{""max_depth"": null, ""max_features"": ""sqrt"", ""min_samples_leaf"": 1, ""min_samples_split"": 5, ""n_estimators"": 50}"
Version-3/outputs/2025-12-25_10-01-23/observations/model_comparison_summary.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Model,Accuracy,Precision,Recall,F1_Score,CV_Score,Is_Best_Model
2
+ SVM,0.979066985645933,0.9789275513603889,0.979066985645933,0.9785830328138276,0.9891548320127734,1
3
+ LogisticRegression,0.9766746411483254,0.9767291368060467,0.9766746411483254,0.9759311514144211,0.9876984264199944,0
4
+ RandomForest,0.9706937799043063,0.9714374960389777,0.9706937799043063,0.9692103447020837,0.9878592900446806,0
5
+ DecisionTree,0.9599282296650717,0.9588033625788168,0.9599282296650717,0.9588283410865664,0.9834796073961313,0
6
+ KNN,0.93122009569378,0.9362806772646151,0.93122009569378,0.9206571512502397,0.9612898978068009,0
Version-3/outputs/2025-12-25_10-01-23/observations/model_metadata.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ timestamp,best_model_name,best_model_params,best_model_metrics,all_models,tfidf_features,train_samples,test_samples
2
+ 2025-12-25_10-01-23,SVM,"{'C': 10, 'gamma': 'scale', 'kernel': 'linear'}","{'accuracy': 0.979066985645933, 'precision': 0.9789275513603889, 'recall': 0.979066985645933, 'f1_score': 0.9785830328138276, 'best_params': {'C': 10, 'gamma': 'scale', 'kernel': 'linear'}, 'best_cv_score': np.float64(0.9891548320127734)}","LogisticRegression, DecisionTree, SVM, KNN, RandomForest",6847,3900,1672
Version-3/outputs/2025-12-25_14-02-05/models/SVM_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6d4b7ec310866553c9aeeacf3f69abb592b584309cb1b9392281335cd73e8cf7
3
+ size 131573
Version-3/outputs/2025-12-25_14-02-05/models/vectorizer.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7caca664beb75eeba23cc9f30dfcda1c0ada3a4f7bfd281452ef8aea2115310f
3
+ size 138244
Version-3/outputs/2025-12-25_14-02-05/observations/best_model_info.csv ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Attribute,Value
2
+ Best Model Name,SVM
3
+ Accuracy,0.979066985645933
4
+ Precision,0.9789275513603889
5
+ Recall,0.979066985645933
6
+ F1-Score,0.9785830328138276
7
+ CV Score,0.9891548320127734
8
+ Best Parameters,"{
9
+ ""C"": 10,
10
+ ""gamma"": ""scale"",
11
+ ""kernel"": ""linear""
12
+ }"
Version-3/outputs/2025-12-25_14-02-05/observations/best_parameters.csv ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Model,Best_Parameters,CV_Score
2
+ LogisticRegression,"{
3
+ ""C"": 100,
4
+ ""max_iter"": 100,
5
+ ""solver"": ""liblinear""
6
+ }",0.9876984264199944
7
+ DecisionTree,"{
8
+ ""criterion"": ""gini"",
9
+ ""max_depth"": null,
10
+ ""min_samples_leaf"": 1,
11
+ ""min_samples_split"": 2
12
+ }",0.9834796073961313
13
+ SVM,"{
14
+ ""C"": 10,
15
+ ""gamma"": ""scale"",
16
+ ""kernel"": ""linear""
17
+ }",0.9891548320127734
18
+ KNN,"{
19
+ ""metric"": ""manhattan"",
20
+ ""n_neighbors"": 3,
21
+ ""weights"": ""distance""
22
+ }",0.9612898978068009
23
+ RandomForest,"{
24
+ ""max_depth"": null,
25
+ ""max_features"": ""sqrt"",
26
+ ""min_samples_leaf"": 1,
27
+ ""min_samples_split"": 5,
28
+ ""n_estimators"": 50
29
+ }",0.9878592900446806
Version-3/outputs/2025-12-25_14-02-05/observations/cross_validation_summary.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Model,Best_CV_Score,Best_Parameters
2
+ LogisticRegression,0.9876984264199944,"{""C"": 100, ""max_iter"": 100, ""solver"": ""liblinear""}"
3
+ DecisionTree,0.9834796073961313,"{""criterion"": ""gini"", ""max_depth"": null, ""min_samples_leaf"": 1, ""min_samples_split"": 2}"
4
+ SVM,0.9891548320127734,"{""C"": 10, ""gamma"": ""scale"", ""kernel"": ""linear""}"
5
+ KNN,0.9612898978068009,"{""metric"": ""manhattan"", ""n_neighbors"": 3, ""weights"": ""distance""}"
6
+ RandomForest,0.9878592900446806,"{""max_depth"": null, ""max_features"": ""sqrt"", ""min_samples_leaf"": 1, ""min_samples_split"": 5, ""n_estimators"": 50}"
Version-3/outputs/2025-12-25_14-02-05/observations/model_comparison_summary.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Model,Accuracy,Precision,Recall,F1_Score,CV_Score,Is_Best_Model
2
+ SVM,0.979066985645933,0.9789275513603889,0.979066985645933,0.9785830328138276,0.9891548320127734,1
3
+ LogisticRegression,0.9766746411483254,0.9767291368060467,0.9766746411483254,0.9759311514144211,0.9876984264199944,0
4
+ RandomForest,0.9706937799043063,0.9714374960389777,0.9706937799043063,0.9692103447020837,0.9878592900446806,0
5
+ DecisionTree,0.9599282296650717,0.9588033625788168,0.9599282296650717,0.9588283410865664,0.9834796073961313,0
6
+ KNN,0.93122009569378,0.9362806772646151,0.93122009569378,0.9206571512502397,0.9612898978068009,0
Version-3/outputs/2025-12-25_14-02-05/observations/model_metadata.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ timestamp,best_model_name,best_model_params,best_model_metrics,all_models,tfidf_features,train_samples,test_samples
2
+ 2025-12-25_14-02-05,SVM,"{'C': 10, 'gamma': 'scale', 'kernel': 'linear'}","{'accuracy': 0.979066985645933, 'precision': 0.9789275513603889, 'recall': 0.979066985645933, 'f1_score': 0.9785830328138276, 'best_params': {'C': 10, 'gamma': 'scale', 'kernel': 'linear'}, 'best_cv_score': np.float64(0.9891548320127734)}","LogisticRegression, DecisionTree, SVM, KNN, RandomForest",6847,3900,1672
Version-3/requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.115.6
2
+ uvicorn[standard]==0.34.0
3
+ python-multipart==0.0.20
4
+ pandas==2.2.3
5
+ beautifulsoup4==4.12.3
6
+ scikit-learn==1.6.1
7
+ pydantic==2.10.6
8
+ streamlit>=1.32.0
Version-3/src/__init__.py ADDED
File without changes
Version-3/src/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (162 Bytes). View file
 
Version-3/src/components/data_ingestion.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from src.utils.logger import get_logger
3
+ from src.config.config import Config
4
+ from src.utils.state import TrainingState
5
+
6
+ logger = get_logger(__name__)
7
+
8
+ class DataIngestion:
9
+ def __init__(self):
10
+ self.config = Config()
11
+
12
+ def load_data(self, state: TrainingState) -> TrainingState:
13
+ try:
14
+ logger.info("Loading data")
15
+ state.training_data = pd.read_csv(self.config.training_data_path)
16
+ logger.info("Data loaded successfully")
17
+ return state
18
+ except Exception as e:
19
+ logger.error(f"Failed to load data: {str(e)}")
20
+ raise e
21
+
Version-3/src/components/data_transformation.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.utils.logger import get_logger
2
+ from src.config.config import Config
3
+ from src.utils.state import TrainingState
4
+ from sklearn.model_selection import train_test_split
5
+ from sklearn.feature_extraction.text import TfidfVectorizer
6
+
7
+ logger = get_logger(__name__)
8
+
9
+ class DataTransformation:
10
+ def __init__(self):
11
+ self.config = Config()
12
+
13
+ def transform_data(self, state: TrainingState) -> TrainingState:
14
+ logger.info("Data transformation started")
15
+ try:
16
+ data = state.training_data.copy()
17
+
18
+ # Encode labels: spam -> 0, ham -> 1
19
+ data.loc[data['Category'] == 'spam', 'Category'] = 0
20
+ data.loc[data['Category'] == 'ham', 'Category'] = 1
21
+
22
+ # Ensure Category column is integer type
23
+ data['Category'] = data['Category'].astype(int)
24
+
25
+ logger.info(f"Label encoding completed. Data shape: {data.shape}")
26
+ logger.info(f"Unique labels: {data['Category'].unique()}")
27
+ logger.info(f"Label dtype: {data['Category'].dtype}")
28
+
29
+ # Split features and target
30
+ X = data['Message']
31
+ y = data['Category']
32
+
33
+ # Convert y to numpy array of integers to ensure proper type
34
+ import numpy as np
35
+ y = np.array(y, dtype=int)
36
+
37
+ # Split into train and test sets (70:30 ratio)
38
+ X_train, X_test, y_train, y_test = train_test_split(
39
+ X, y, test_size=0.3, random_state=42, stratify=y
40
+ )
41
+
42
+ logger.info(f"Train/test split completed. Train size: {len(X_train)}, Test size: {len(X_test)}")
43
+
44
+ # Apply TF-IDF vectorization
45
+ tfidf_vectorizer = TfidfVectorizer(lowercase=True, stop_words='english')
46
+ X_train_tfidf = tfidf_vectorizer.fit_transform(X_train)
47
+ X_test_tfidf = tfidf_vectorizer.transform(X_test)
48
+
49
+ logger.info(f"TF-IDF transformation completed. Feature shape: {X_train_tfidf.shape}")
50
+
51
+ # Save to state
52
+ state.transformed_data = data
53
+ state.X_train = X_train
54
+ state.X_test = X_test
55
+ state.y_train = y_train
56
+ state.y_test = y_test
57
+ state.X_train_tfidf = X_train_tfidf
58
+ state.X_test_tfidf = X_test_tfidf
59
+ state.tfidf_vectorizer = tfidf_vectorizer
60
+
61
+ logger.info("Data transformation completed")
62
+ return state
63
+ except Exception as e:
64
+ logger.error(f"Failed to transform data: {str(e)}")
65
+ raise e
Version-3/src/components/model_training.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import json
4
+ import pickle
5
+ from datetime import datetime
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from sklearn.svm import SVC
11
+ from sklearn.tree import DecisionTreeClassifier
12
+ from sklearn.neighbors import KNeighborsClassifier
13
+ from sklearn.linear_model import LogisticRegression
14
+ from sklearn.ensemble import RandomForestClassifier, StackingClassifier
15
+ from sklearn.model_selection import GridSearchCV, RandomizedSearchCV, cross_val_score
16
+ from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report
17
+
18
+ from src.utils.logger import get_logger
19
+ from src.utils.state import TrainingState
20
+ from src.config.config import Config, ModelConfig
21
+
22
+ logger = get_logger(__name__)
23
+
24
+ class ModelTraining:
25
+ def __init__(self):
26
+ self.config = Config()
27
+ self.param_grids = ModelConfig.models
28
+
29
+ def save_pickle_files(self, state: TrainingState):
30
+ try:
31
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
32
+ output_dir = os.path.join(self.config.OUTPUT_BASE_DIR, timestamp)
33
+ models_dir = os.path.join(output_dir, "models")
34
+ observations_dir = os.path.join(output_dir, "observations")
35
+
36
+ os.makedirs(models_dir, exist_ok=True)
37
+ os.makedirs(observations_dir, exist_ok=True)
38
+
39
+ vectorizer_path = os.path.join(models_dir, "vectorizer.pkl")
40
+ with open(vectorizer_path, 'wb') as f:
41
+ pickle.dump(state.tfidf_vectorizer, f)
42
+ logger.info(f"Saved TF-IDF vectorizer: {vectorizer_path}")
43
+
44
+ best_model_path = os.path.join(models_dir, f"{state.best_model_name}_model.pkl")
45
+ with open(best_model_path, 'wb') as f:
46
+ pickle.dump(state.best_model, f)
47
+ logger.info(f"Saved best model: {state.best_model_name}_model.pkl")
48
+
49
+ metadata = {
50
+ 'timestamp': timestamp,
51
+ 'best_model_name': state.best_model_name,
52
+ 'best_model_params': str(state.best_params),
53
+ 'best_model_metrics': str(state.model_metrics[state.best_model_name]),
54
+ 'all_models': ', '.join(list(state.trained_models.keys())),
55
+ 'tfidf_features': state.X_train_tfidf.shape[1],
56
+ 'train_samples': len(state.y_train),
57
+ 'test_samples': len(state.y_test)
58
+ }
59
+
60
+ metadata_path = os.path.join(observations_dir, "model_metadata.csv")
61
+ pd.DataFrame([metadata]).to_csv(metadata_path, index=False)
62
+ logger.info(f"Saved metadata: {metadata_path}")
63
+
64
+ return output_dir
65
+
66
+ except Exception as e:
67
+ logger.error(f"Failed to save pickle files: {str(e)}")
68
+ raise
69
+
70
+ def save_metrics_to_csv(self, state: TrainingState, output_dir: str):
71
+ observations_dir = os.path.join(output_dir, "observations")
72
+ os.makedirs(observations_dir, exist_ok=True)
73
+
74
+ # 1. Model Comparison Summary
75
+ # ----------------------------------------------------------------------
76
+ metrics_data = []
77
+ for model_name, metrics in state.model_metrics.items():
78
+ metrics_data.append({
79
+ 'Model': model_name,
80
+ 'Accuracy': metrics['accuracy'],
81
+ 'Precision': metrics['precision'],
82
+ 'Recall': metrics['recall'],
83
+ 'F1_Score': metrics['f1_score'],
84
+ 'CV_Score': metrics.get('best_cv_score', 'N/A'),
85
+ 'Is_Best_Model': '1' if model_name == state.best_model_name else '0'
86
+ })
87
+
88
+ df_summary = pd.DataFrame(metrics_data)
89
+ df_summary = df_summary.sort_values('Accuracy', ascending=False)
90
+ summary_path = os.path.join(observations_dir, "model_comparison_summary.csv")
91
+ df_summary.to_csv(summary_path, index=False)
92
+ logger.info(f"Saved: model_comparison_summary.csv")
93
+
94
+ # 2. Best Parameters for Each Model
95
+ # ----------------------------------------------------------------------
96
+ params_data = []
97
+ for model_name, metrics in state.model_metrics.items():
98
+ best_params = metrics.get('best_params', {})
99
+ if isinstance(best_params, dict):
100
+ params_str = json.dumps(best_params, indent=2)
101
+ else:
102
+ params_str = str(best_params)
103
+
104
+ params_data.append({
105
+ 'Model': model_name,
106
+ 'Best_Parameters': params_str,
107
+ 'CV_Score': metrics.get('best_cv_score', 'N/A')
108
+ })
109
+
110
+ df_params = pd.DataFrame(params_data)
111
+ params_path = os.path.join(observations_dir, "best_parameters.csv")
112
+ df_params.to_csv(params_path, index=False)
113
+ logger.info(f"Saved: best_parameters.csv")
114
+
115
+ # 3. Cross-Validation Results Summary
116
+ # ----------------------------------------------------------------------
117
+ if state.cv_results:
118
+ cv_summary = []
119
+ for model_name, cv_data in state.cv_results.items():
120
+ cv_summary.append({
121
+ 'Model': model_name,
122
+ 'Best_CV_Score': cv_data.get('best_score', 'N/A'),
123
+ 'Best_Parameters': json.dumps(cv_data.get('best_params', {}))
124
+ })
125
+
126
+ df_cv = pd.DataFrame(cv_summary)
127
+ cv_path = os.path.join(observations_dir, "cross_validation_summary.csv")
128
+ df_cv.to_csv(cv_path, index=False)
129
+ logger.info(f"Saved: cross_validation_summary.csv")
130
+
131
+ # 4. Best Model Information
132
+ # ----------------------------------------------------------------------
133
+ best_model_info = {
134
+ 'Attribute': [
135
+ 'Best Model Name',
136
+ 'Accuracy',
137
+ 'Precision',
138
+ 'Recall',
139
+ 'F1-Score',
140
+ 'CV Score',
141
+ 'Best Parameters'
142
+ ],
143
+ 'Value': [
144
+ state.best_model_name,
145
+ state.model_metrics[state.best_model_name]['accuracy'],
146
+ state.model_metrics[state.best_model_name]['precision'],
147
+ state.model_metrics[state.best_model_name]['recall'],
148
+ state.model_metrics[state.best_model_name]['f1_score'],
149
+ state.model_metrics[state.best_model_name].get('best_cv_score', 'N/A'),
150
+ json.dumps(state.best_params, indent=2) if isinstance(state.best_params, dict) else str(state.best_params)
151
+ ]
152
+ }
153
+
154
+ df_best = pd.DataFrame(best_model_info)
155
+ best_path = os.path.join(observations_dir, "best_model_info.csv")
156
+ df_best.to_csv(best_path, index=False)
157
+ logger.info(f"Saved: best_model_info.csv")
158
+
159
+
160
+ def train_models(self, state: TrainingState, cv_folds: int = 5) -> TrainingState:
161
+ logger.info("Model training started")
162
+ logger.info(f"Using GridSearchCV with {cv_folds}-fold CV")
163
+
164
+ try:
165
+ X_train = state.X_train_tfidf
166
+ X_test = state.X_test_tfidf
167
+ y_train = state.y_train
168
+ y_test = state.y_test
169
+
170
+ trained_models, model_metrics, cv_results = {}, {}, {}
171
+
172
+ # Define model instances
173
+ models = {
174
+ 'LogisticRegression': LogisticRegression(random_state=42),
175
+ 'DecisionTree': DecisionTreeClassifier(random_state=42),
176
+ 'SVM': SVC(random_state=42),
177
+ 'KNN': KNeighborsClassifier(),
178
+ 'RandomForest': RandomForestClassifier(random_state=42)
179
+ }
180
+
181
+ for model_name, model in models.items():
182
+ start_time = time.time()
183
+ logger.info(f"\n{'='*60}")
184
+ logger.info(f"Training {model_name}...")
185
+
186
+ param_grid = self.param_grids.get(model_name, {})
187
+
188
+ search = GridSearchCV(model,
189
+ param_grid=param_grid,
190
+ cv=cv_folds,
191
+ scoring='f1',
192
+ n_jobs=-1
193
+ )
194
+
195
+ search.fit(X_train, y_train)
196
+ best_model = search.best_estimator_
197
+
198
+ y_pred = best_model.predict(X_test)
199
+
200
+ metrics = {
201
+ 'accuracy': accuracy_score(y_test, y_pred),
202
+ 'precision': precision_score(y_test, y_pred, average='weighted', zero_division=0),
203
+ 'recall': recall_score(y_test, y_pred, average='weighted', zero_division=0),
204
+ 'f1_score': f1_score(y_test, y_pred, average='weighted', zero_division=0),
205
+ 'best_params': search.best_params_,
206
+ 'best_cv_score': search.best_score_
207
+ }
208
+
209
+ trained_models[model_name] = best_model
210
+ model_metrics[model_name] = metrics
211
+ cv_results[model_name] = {
212
+ 'cv_scores': search.cv_results_,
213
+ 'best_params': search.best_params_,
214
+ 'best_score': search.best_score_
215
+ }
216
+
217
+ end_time = time.time()
218
+
219
+ logger.info(f"{model_name} - Training time: {end_time - start_time:.2f} seconds")
220
+ logger.info(f"{model_name} - Best Parameters: {search.best_params_}")
221
+ logger.info(f"{model_name} - CV Score: {search.best_score_:.4f}")
222
+ logger.info(f"{model_name} - Test Accuracy: {metrics['accuracy']:.4f}")
223
+ logger.info(f"{model_name} - Test Precision: {metrics['precision']:.4f}")
224
+ logger.info(f"{model_name} - Test Recall: {metrics['recall']:.4f}")
225
+ logger.info(f"{model_name} - Test F1-Score: {metrics['f1_score']:.4f}")
226
+
227
+ # Find best model based on F1-score
228
+ best_model_name = max(model_metrics, key=lambda x: model_metrics[x]['f1_score'])
229
+ best_model = trained_models[best_model_name]
230
+ best_params = model_metrics[best_model_name]['best_params']
231
+
232
+ logger.info(f"{'='*60}")
233
+ logger.info(f"BEST MODEL: {best_model_name}")
234
+ logger.info(f"Best F1-Score: {model_metrics[best_model_name]['f1_score']:.4f}")
235
+ logger.info(f"Best Parameters: {best_params}")
236
+ logger.info(f"{'='*60}")
237
+
238
+ state.trained_models = trained_models
239
+ state.model_metrics = model_metrics
240
+ state.best_model_name = best_model_name
241
+ state.best_model = best_model
242
+ state.best_params = best_params
243
+ state.cv_results = cv_results
244
+
245
+ output_dir = self.save_pickle_files(state)
246
+ self.save_metrics_to_csv(state, output_dir)
247
+ logger.info("\nModel training completed successfully")
248
+ logger.info(f"All outputs saved to: {output_dir}/")
249
+ return state
250
+
251
+ except Exception as e:
252
+ logger.error(f"Failed to train models: {str(e)}")
253
+ raise e
Version-3/src/config/__pycache__/config.cpython-313.pyc ADDED
Binary file (1.91 kB). View file
 
Version-3/src/config/config.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+
3
+ @dataclass
4
+ class Config:
5
+ training_data_path: str = "data/dataset/dataset.csv"
6
+ validation_data_path: str = "data/dataset/All_mail_Including_Spam_and_Trash.mbox"
7
+ OUTPUT_BASE_DIR: str = "outputs"
8
+ model_path: str = "outputs/2025-12-25_14-02-05/models/SVM_model.pkl"
9
+ feature_path: str = "outputs/2025-12-25_14-02-05/models/vectorizer.pkl"
10
+
11
+ class ModelConfig:
12
+ models = {
13
+ 'LogisticRegression': {
14
+ 'C': [0.01, 0.1, 1, 10, 100],
15
+ 'solver': ['lbfgs', 'liblinear'],
16
+ 'max_iter': [100, 200, 300]
17
+ },
18
+ 'DecisionTree': {
19
+ 'criterion': ['gini', 'entropy'],
20
+ 'max_depth': [5, 10, 15, 20, None],
21
+ 'min_samples_split': [2, 5, 10],
22
+ 'min_samples_leaf': [1, 2, 4]
23
+ },
24
+ 'SVM': {
25
+ 'C': [0.1, 1, 10],
26
+ 'kernel': ['linear', 'rbf'],
27
+ 'gamma': ['scale', 'auto']
28
+ },
29
+ 'KNN': {
30
+ 'n_neighbors': [3, 5, 7, 9, 11],
31
+ 'weights': ['uniform', 'distance'],
32
+ 'metric': ['euclidean', 'manhattan']
33
+ },
34
+ 'RandomForest': {
35
+ 'n_estimators': [50, 100, 200],
36
+ 'max_depth': [10, 20, 30, None],
37
+ 'min_samples_split': [2, 5, 10],
38
+ 'min_samples_leaf': [1, 2, 4],
39
+ 'max_features': ['sqrt', 'log2']
40
+ }
41
+ }
Version-3/src/pipeline/__init__.py ADDED
File without changes
Version-3/src/pipeline/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (171 Bytes). View file
 
Version-3/src/pipeline/__pycache__/prediction_pipeline.cpython-313.pyc ADDED
Binary file (7.67 kB). View file
 
Version-3/src/pipeline/prediction_pipeline.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mailbox
2
+ import pickle
3
+ import time
4
+ import pandas as pd
5
+ from typing import Dict, List, Optional
6
+ from pathlib import Path
7
+
8
+ from src.utils.state import PredictionState
9
+ from src.utils.logger import get_logger
10
+ from src.config.config import Config
11
+ from src.utils.email_utils import extract_body, all_recipients, clean_text
12
+
13
+ logger = get_logger(__name__)
14
+
15
+ class PredictionPipeline:
16
+ def __init__(self, load_models: bool = True):
17
+ self.config = Config()
18
+ self.mailbox = None
19
+ self.feature_transformer = None
20
+ self.model = None
21
+
22
+ if load_models:
23
+ self._load_models()
24
+
25
+ def _load_models(self) -> None:
26
+
27
+ logger.info("Loading models...")
28
+ self.feature_transformer = pickle.load(open(self.config.feature_path, "rb"))
29
+ self.model = pickle.load(open(self.config.model_path, "rb"))
30
+ logger.info("Models loaded successfully")
31
+
32
+ def predict_single_email(self, email_body: str) -> Dict:
33
+ if self.model is None or self.feature_transformer is None:
34
+ self._load_models()
35
+
36
+ cleaned_body = clean_text(email_body)
37
+ features = self.feature_transformer.transform([cleaned_body])
38
+ prediction = self.model.predict(features)
39
+ prediction_label = "Spam" if str(prediction[0]) == "0" else "Ham"
40
+
41
+ try:
42
+ prediction_proba = self.model.predict_proba(features)
43
+ confidence = float(max(prediction_proba[0])) * 100
44
+ except:
45
+ confidence = None
46
+
47
+ return {
48
+ 'prediction': prediction_label,
49
+ 'confidence': confidence,
50
+ 'raw_prediction': int(prediction[0])
51
+ }
52
+
53
+ def load_mailbox(self, mailbox_path: str) -> None:
54
+ """Load MBOX file"""
55
+
56
+ logger.info(f"Loading mailbox from {mailbox_path}")
57
+ self.mailbox = mailbox.mbox(mailbox_path)
58
+ logger.info(f"Loaded mailbox from {mailbox_path}")
59
+
60
+ def process_mailbox(self, mailbox_path: Optional[str] = None) -> List[Dict]:
61
+ if mailbox_path:
62
+ self.load_mailbox(mailbox_path)
63
+
64
+ if self.mailbox is None:
65
+ raise ValueError("No mailbox loaded. Call load_mailbox() first.")
66
+
67
+ logger.info("Processing mailbox")
68
+ data = []
69
+
70
+ for message in self.mailbox:
71
+ labels = (message.get("X-Gmail-Labels") or "").lower()
72
+ category = (
73
+ "Spam" if "spam" in labels else
74
+ "Promotions" if "category_promotions" in labels else
75
+ "Social" if "category_social" in labels else
76
+ "Updates" if "category_updates" in labels else
77
+ "Inbox"
78
+ )
79
+ time_str = message.get("Date", "")
80
+ recipients = clean_text(all_recipients(message))
81
+ subject = clean_text(message.get("Subject", ""))
82
+ body = clean_text(extract_body(message))
83
+ direction = "Sent" if "Sent" in (message.get("X-Gmail-Labels") or "") else "Received"
84
+
85
+ data.append({
86
+ "Time": time_str,
87
+ "Recipients": recipients,
88
+ "Subject": subject,
89
+ "Body": body,
90
+ "Category": category,
91
+ "Direction": direction
92
+ })
93
+
94
+ logger.info(f"Processed {len(data)} emails from mailbox")
95
+ self.mailbox.close()
96
+
97
+ return data
98
+
99
+ def run_prediction(self, mail_data: List[Dict]) -> List[Dict]:
100
+ if self.model is None or self.feature_transformer is None:
101
+ self._load_models()
102
+
103
+ start_time = time.time()
104
+ logger.info("Running predictions")
105
+
106
+ for mail in mail_data:
107
+ body_text = mail.get('Body', '')
108
+ features = self.feature_transformer.transform([body_text])
109
+ prediction = self.model.predict(features)
110
+ prediction_label = "Spam" if str(prediction[0]) == "0" else "Ham"
111
+ mail["Prediction"] = prediction_label
112
+
113
+ end_time = time.time()
114
+ logger.info(f"Prediction completed in {end_time - start_time:.2f} seconds")
115
+
116
+ return mail_data
117
+
118
+ def predict_mbox_file(self, mailbox_path: str, output_path: Optional[str] = None) -> pd.DataFrame:
119
+ mail_data = self.process_mailbox(mailbox_path)
120
+ mail_data = self.run_prediction(mail_data)
121
+ df = pd.DataFrame(mail_data)
122
+ if output_path:
123
+ df.to_csv(output_path, index=False)
124
+ logger.info(f"Predictions saved to {output_path}")
125
+ return df
126
+
127
+
128
+ def run_legacy_pipeline(state: PredictionState) -> None:
129
+ pipeline = PredictionPipeline(load_models=False)
130
+ pipeline.load_mailbox(state.mailbox_path)
131
+ mail_data = pipeline.process_mailbox()
132
+ state.mail_data = mail_data
133
+ state.mail_data = pipeline.run_prediction(state.mail_data)
134
+ df = pd.DataFrame(state.mail_data)
135
+ df.to_csv("data/predictions.csv", index=False)
Version-3/src/pipeline/training_pipeline.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.components.data_ingestion import DataIngestion
2
+ from src.components.data_transformation import DataTransformation
3
+ from src.components.model_training import ModelTraining
4
+ from src.utils.state import TrainingState
5
+ from src.utils.logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+ class TrainingPipeline:
10
+ """Complete training pipeline for spam classification"""
11
+
12
+ def __init__(self):
13
+ self.state = TrainingState()
14
+
15
+ def run_pipeline(self, cv_folds: int = 5):
16
+ try:
17
+ logger.info("Initiating training pipeline")
18
+ ingestion = DataIngestion()
19
+ self.state = ingestion.load_data(self.state)
20
+ logger.info(f"Data loaded successfully: {self.state.training_data.shape}")
21
+ logger.info(f"Columns: {self.state.training_data.columns.tolist()}")
22
+ logger.info(f"Sample size: {len(self.state.training_data)} emails")
23
+
24
+ transformation = DataTransformation()
25
+ self.state = transformation.transform_data(self.state)
26
+ logger.info(f"Data transformation completed")
27
+ logger.info(f"Training set: {len(self.state.X_train)} samples")
28
+ logger.info(f"Test set: {len(self.state.X_test)} samples")
29
+ logger.info(f"TF-IDF features: {self.state.X_train_tfidf.shape[1]}")
30
+
31
+ trainer = ModelTraining()
32
+ self.state = trainer.train_models(
33
+ self.state,
34
+ cv_folds=cv_folds
35
+ )
36
+
37
+ logger.info("\n" + "="*70)
38
+ logger.info("Training pipeline completed successfully")
39
+ logger.info("="*70)
40
+ logger.info(f"All metrics saved to 'results/' directory")
41
+ logger.info(f"Best model: {self.state.best_model_name}")
42
+ logger.info(f"Best F1-Score: {self.state.model_metrics[self.state.best_model_name]['f1_score']:.4f}")
43
+
44
+ return self.state
45
+
46
+ except Exception as e:
47
+ logger.error(f"Pipeline failed: {str(e)}")
48
+ raise e
49
+
50
+ if __name__ == "__main__":
51
+ pipeline = TrainingPipeline()
52
+ pipeline.run_pipeline(cv_folds=5)
Version-3/src/utils/__init__.py ADDED
File without changes
Version-3/src/utils/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (168 Bytes). View file
 
Version-3/src/utils/__pycache__/email_utils.cpython-313.pyc ADDED
Binary file (2.94 kB). View file
 
Version-3/src/utils/__pycache__/logger.cpython-313.pyc ADDED
Binary file (1.58 kB). View file
 
Version-3/src/utils/__pycache__/state.cpython-313.pyc ADDED
Binary file (2.02 kB). View file
 
Version-3/src/utils/email_utils.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from html import unescape
3
+ from email.utils import getaddresses
4
+ from bs4 import BeautifulSoup
5
+
6
+ # ----------------------------------------------------------------------------
7
+ # Function to extract email body content
8
+ # ----------------------------------------------------------------------------
9
+ def extract_body(msg):
10
+ texts = []
11
+
12
+ if msg.is_multipart():
13
+ for part in msg.walk():
14
+ if part.get_content_type() in ("text/plain", "text/html"):
15
+ payload = part.get_payload(decode=True)
16
+ if payload:
17
+ text = payload.decode(errors="ignore")
18
+ text = unescape(text)
19
+ text = BeautifulSoup(text, "html.parser").get_text(" ")
20
+ texts.append(text)
21
+ else:
22
+ payload = msg.get_payload(decode=True)
23
+ if payload:
24
+ text = unescape(payload.decode(errors="ignore"))
25
+ text = BeautifulSoup(text, "html.parser").get_text(" ")
26
+ texts.append(text)
27
+
28
+ clean = " ".join(texts)
29
+ clean = re.sub(r'\\+', ' ', clean)
30
+ clean = re.sub(r'[\r\n\t]+', ' ', clean)
31
+ clean = re.sub(r'\s+', ' ', clean)
32
+ return clean.strip()
33
+
34
+ # ----------------------------------------------------------------------------
35
+ # Function to extract all recipients from email headers
36
+ # ----------------------------------------------------------------------------
37
+ def all_recipients(msg):
38
+ fields = []
39
+ for h in ["From", "To", "Cc", "Bcc"]:
40
+ fields.extend(getaddresses([msg.get(h, "")]))
41
+ return ", ".join(sorted(set(addr for _, addr in fields if addr)))
42
+
43
+ # ----------------------------------------------------------------------------
44
+ # Function to clean text for Excel compatibility
45
+ # ----------------------------------------------------------------------------
46
+ def clean_text(text):
47
+ if not isinstance(text, str):
48
+ return text
49
+ text = re.sub(r'[\x00-\x08\x0B-\x0C\x0E-\x1F\u200B\u200C\u200D\u200E\u200F\uFEFF]', '', text)
50
+ text = text.encode("utf-16", "surrogatepass").decode("utf-16", "ignore")
51
+ text = text[:32767]
52
+ if text.startswith(("=", "+", "-", "@")):
53
+ text = "'" + text
54
+ return text
Version-3/src/utils/logger.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from pathlib import Path
3
+ from datetime import datetime
4
+
5
+ # Global variable to store the log file path for the current run
6
+ _LOG_FILE = None
7
+
8
+ def get_logger(name: str):
9
+ global _LOG_FILE
10
+
11
+ logger = logging.getLogger(name)
12
+ logger.setLevel(logging.INFO)
13
+
14
+ if logger.handlers:
15
+ return logger
16
+
17
+ # Create log file path only once for the entire pipeline run
18
+ if _LOG_FILE is None:
19
+ date_dir = datetime.now().strftime("%Y-%m-%d")
20
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
21
+ log_dir = Path("logs") / date_dir
22
+ log_dir.mkdir(parents=True, exist_ok=True)
23
+ _LOG_FILE = log_dir / f"{timestamp}.log"
24
+
25
+ handler = logging.FileHandler(_LOG_FILE, encoding="utf-8")
26
+ formatter = logging.Formatter(
27
+ "[%(asctime)s]: %(filename)s - Line %(lineno)d: %(levelname)s: %(message)s",
28
+ datefmt="%Y-%m-%d %H:%M:%S"
29
+ )
30
+ handler.setFormatter(formatter)
31
+
32
+ logger.addHandler(handler)
33
+ logger.propagate = False
34
+ return logger
Version-3/src/utils/state.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, List, Dict, Any
2
+ import pandas as pd
3
+
4
+ class TrainingState:
5
+ training_data_path: Optional[str] = None
6
+ training_data: Optional[pd.DataFrame] = None
7
+ transformed_data: Optional[pd.DataFrame] = None
8
+ X_train: Optional[pd.Series] = None
9
+ X_test: Optional[pd.Series] = None
10
+ y_train: Optional[pd.Series] = None
11
+ y_test: Optional[pd.Series] = None
12
+ X_train_tfidf: Optional[Any] = None
13
+ X_test_tfidf: Optional[Any] = None
14
+ tfidf_vectorizer: Optional[Any] = None
15
+ trained_models: Optional[Dict[str, Any]] = None
16
+ model_metrics: Optional[Dict[str, Dict[str, float]]] = None
17
+ best_model_name: Optional[str] = None
18
+ best_model: Optional[Any] = None
19
+ best_params: Optional[Dict[str, Any]] = None
20
+ cv_results: Optional[Dict[str, Any]] = None
21
+
22
+ class PredictionState:
23
+ mailbox_path: Optional[str] = None
24
+ mail_data: Optional[List[Dict[str, str]]] = None
Version-3/src/utils/utils.py ADDED
File without changes