yujisium commited on
Commit
95a68c1
Β·
verified Β·
1 Parent(s): 11b9a48

Add eval_model.py for reproducible offline evaluation

Browse files
Files changed (1) hide show
  1. ai-backend/eval_model.py +130 -0
ai-backend/eval_model.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ eval_model.py β€” NLP Model Evaluation for Dark Patterns Detector
3
+ ================================================================
4
+ Drop this file into the ai-backend/ folder alongside dataset.csv and run:
5
+
6
+ pip install scikit-learn pandas matplotlib seaborn
7
+ python eval_model.py
8
+
9
+ Outputs:
10
+ β€’ Classification report (precision / recall / F1 per class)
11
+ β€’ Overall accuracy
12
+ β€’ Confusion matrix saved as confusion_matrix.png
13
+ β€’ Model comparison table (baseline vs improved TF-IDF settings)
14
+ """
15
+
16
+ import pandas as pd
17
+ import matplotlib
18
+ matplotlib.use("Agg") # headless β€” no display needed
19
+ import matplotlib.pyplot as plt
20
+ import seaborn as sns
21
+ import os, sys
22
+
23
+ from sklearn.feature_extraction.text import TfidfVectorizer
24
+ from sklearn.linear_model import LogisticRegression
25
+ from sklearn.pipeline import make_pipeline
26
+ from sklearn.model_selection import train_test_split, cross_val_score
27
+ from sklearn.metrics import (
28
+ classification_report, confusion_matrix, accuracy_score
29
+ )
30
+
31
+ # ── Load dataset ──────────────────────────────────────────────────────
32
+ script_dir = os.path.dirname(os.path.abspath(__file__))
33
+ dataset_path = os.path.join(script_dir, "dataset.csv")
34
+
35
+ if not os.path.exists(dataset_path):
36
+ print("❌ dataset.csv not found. Make sure this script is in ai-backend/")
37
+ sys.exit(1)
38
+
39
+ df = pd.read_csv(dataset_path).dropna(subset=["text", "Pattern Category"])
40
+ X = df["text"]
41
+ y = df["Pattern Category"]
42
+
43
+ print(f"βœ… Loaded {len(df)} samples across {y.nunique()} classes\n")
44
+ print("Class distribution:")
45
+ print(y.value_counts().to_string())
46
+ print()
47
+
48
+ # ── Train / test split ────────────────────────────────────────────────
49
+ X_train, X_test, y_train, y_test = train_test_split(
50
+ X, y, test_size=0.20, random_state=42, stratify=y
51
+ )
52
+
53
+ # ── Model 1: Baseline (as in production) ─────────────────────────────
54
+ model_baseline = make_pipeline(
55
+ TfidfVectorizer(ngram_range=(1, 2)),
56
+ LogisticRegression(C=10.0, class_weight="balanced", max_iter=1000)
57
+ )
58
+ model_baseline.fit(X_train, y_train)
59
+ y_pred_baseline = model_baseline.predict(X_test)
60
+
61
+ # ── Model 2: Improved (sublinear TF scaling + min_df pruning) ─────────
62
+ model_improved = make_pipeline(
63
+ TfidfVectorizer(
64
+ ngram_range=(1, 3),
65
+ sublinear_tf=True,
66
+ min_df=2,
67
+ max_features=50_000,
68
+ ),
69
+ LogisticRegression(C=5.0, class_weight="balanced", max_iter=1000, solver="saga")
70
+ )
71
+ model_improved.fit(X_train, y_train)
72
+ y_pred_improved = model_improved.predict(X_test)
73
+
74
+ # ── Print reports ─────────────────────────────────────────────────────
75
+ print("=" * 65)
76
+ print("BASELINE MODEL β€” TF-IDF(1,2) + LogReg(C=10)")
77
+ print("=" * 65)
78
+ print(f"Accuracy: {accuracy_score(y_test, y_pred_baseline):.4f}\n")
79
+ print(classification_report(y_test, y_pred_baseline))
80
+
81
+ print("=" * 65)
82
+ print("IMPROVED MODEL β€” TF-IDF(1,3, sublinear) + LogReg(C=5, saga)")
83
+ print("=" * 65)
84
+ print(f"Accuracy: {accuracy_score(y_test, y_pred_improved):.4f}\n")
85
+ print(classification_report(y_test, y_pred_improved))
86
+
87
+ # ── 5-fold cross-validation ───────────────────────────────────────────
88
+ cv_baseline = cross_val_score(model_baseline, X, y, cv=5, scoring="accuracy")
89
+ cv_improved = cross_val_score(model_improved, X, y, cv=5, scoring="accuracy")
90
+
91
+ print("5-Fold Cross-Validation Accuracy:")
92
+ print(f" Baseline : {cv_baseline.mean():.4f} Β± {cv_baseline.std():.4f}")
93
+ print(f" Improved : {cv_improved.mean():.4f} Β± {cv_improved.std():.4f}")
94
+ print()
95
+
96
+ # ── Confusion matrix plot ─────────────────────────────────────────────
97
+ labels = sorted(y.unique())
98
+ cm = confusion_matrix(y_test, y_pred_improved, labels=labels)
99
+
100
+ fig, ax = plt.subplots(figsize=(10, 8))
101
+ sns.heatmap(
102
+ cm,
103
+ annot=True, fmt="d", cmap="Blues",
104
+ xticklabels=labels, yticklabels=labels,
105
+ linewidths=0.5, linecolor="white",
106
+ ax=ax
107
+ )
108
+ ax.set_title("Confusion Matrix β€” Improved Model", fontsize=14, fontweight="bold", pad=14)
109
+ ax.set_ylabel("True Label", fontsize=12)
110
+ ax.set_xlabel("Predicted Label", fontsize=12)
111
+ plt.xticks(rotation=30, ha="right", fontsize=9)
112
+ plt.yticks(rotation=0, fontsize=9)
113
+ plt.tight_layout()
114
+
115
+ out_path = os.path.join(script_dir, "confusion_matrix.png")
116
+ plt.savefig(out_path, dpi=150)
117
+ print(f"πŸ“Š Confusion matrix saved β†’ {out_path}")
118
+
119
+ # ── Top features per class ────────────────────────────────────────────
120
+ print("\n── Top 8 TF-IDF features per class (Improved Model) ──")
121
+ tfidf = model_improved.named_steps["tfidfvectorizer"]
122
+ logreg = model_improved.named_steps["logisticregression"]
123
+ feat_names = tfidf.get_feature_names_out()
124
+
125
+ for i, cls in enumerate(logreg.classes_):
126
+ top_idx = logreg.coef_[i].argsort()[-8:][::-1]
127
+ top_feats = [feat_names[j] for j in top_idx]
128
+ print(f" {cls:<20}: {', '.join(top_feats)}")
129
+
130
+ print("\nβœ… Evaluation complete.")