Spaces:
Running on Zero
Running on Zero
| """ | |
| train.py | |
| Trains a prompt-injection classifier (TF-IDF + heuristics -> Logistic Regression) | |
| and evaluates it with security-appropriate metrics (recall on the injection | |
| class matters most -- a missed attack is worse than a false alarm). | |
| """ | |
| import pandas as pd | |
| import numpy as np | |
| import joblib | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.ensemble import RandomForestClassifier | |
| from sklearn.metrics import ( | |
| classification_report, confusion_matrix, precision_recall_curve, f1_score | |
| ) | |
| from features import build_features | |
| def load_data(path="data/prompts.csv"): | |
| df = pd.read_csv(path) | |
| return df["text"].tolist(), df["label"].tolist() | |
| def train_and_evaluate(): | |
| texts, labels = load_data() | |
| X_train_text, X_test_text, y_train, y_test = train_test_split( | |
| texts, labels, test_size=0.25, random_state=42, stratify=labels | |
| ) | |
| vectorizer = TfidfVectorizer( | |
| ngram_range=(1, 2), max_features=3000, lowercase=True, stop_words="english" | |
| ) | |
| X_train = build_features(X_train_text, vectorizer, fit=True) | |
| X_test = build_features(X_test_text, vectorizer, fit=False) | |
| models = { | |
| "logistic_regression": LogisticRegression(max_iter=1000, class_weight="balanced"), | |
| "random_forest": RandomForestClassifier(n_estimators=200, random_state=42, class_weight="balanced"), | |
| } | |
| results = {} | |
| for name, model in models.items(): | |
| model.fit(X_train, y_train) | |
| y_pred = model.predict(X_test) | |
| report = classification_report(y_test, y_pred, target_names=["benign", "injection"], output_dict=True) | |
| cm = confusion_matrix(y_test, y_pred) | |
| results[name] = {"model": model, "report": report, "cm": cm} | |
| print(f"\n=== {name} ===") | |
| print(classification_report(y_test, y_pred, target_names=["benign", "injection"])) | |
| print("Confusion matrix (rows=true, cols=pred) [benign, injection]:") | |
| print(cm) | |
| # Pick the model with the best recall on the injection class | |
| # (in security, missing an attack is worse than a false alarm) | |
| best_name = max(results, key=lambda n: results[n]["report"]["injection"]["recall"]) | |
| best_model = results[best_name]["model"] | |
| print(f"\nSelected model: {best_name} (highest recall on injection class)") | |
| joblib.dump(best_model, "models/classifier.joblib") | |
| joblib.dump(vectorizer, "models/vectorizer.joblib") | |
| print("Saved model + vectorizer to models/") | |
| return best_name, results | |
| if __name__ == "__main__": | |
| train_and_evaluate() | |