9.0 KiB
9.0 KiB
In [ ]:
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
TRAIN_PATH = Path("data/nsl_kdd_train.csv")
TEST_PATH = Path("data/nsl_kdd_test.csv")
missing_files = [str(path) for path in (TRAIN_PATH, TEST_PATH) if not path.exists()]
if missing_files:
raise FileNotFoundError(
"Missing prepared NSL-KDD files: " + ", ".join(missing_files) +
". Follow data/README.md and run: python data/prepare_dataset.py"
)
TARGET_COLUMNS = ["attack_label", "attack_group", "binary_label", "difficulty"]
CATEGORICAL_FEATURES = ["protocol_type", "service", "flag"]
from sklearn.ensemble import IsolationForest
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import OneClassSVM
from sklearn.metrics import (
accuracy_score, average_precision_score, confusion_matrix,
f1_score, precision_score, recall_score,
)
def security_metrics(y_true, y_pred):
tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
return {
"accuracy": accuracy_score(y_true, y_pred),
"attack_precision": precision_score(y_true, y_pred, zero_division=0),
"attack_recall": recall_score(y_true, y_pred, zero_division=0),
"attack_f1": f1_score(y_true, y_pred, zero_division=0),
"false_positives": int(fp),
"false_negatives": int(fn),
"false_positive_rate": fp / (fp + tn) if fp + tn else 0.0,
"alerts_per_10000": 10000 * (fp + tp) / len(y_true),
}In [ ]:
train_df = pd.read_csv(TRAIN_PATH, low_memory=False)
test_df = pd.read_csv(TEST_PATH, low_memory=False)
anomaly_features = [
"duration", "src_bytes", "dst_bytes", "wrong_fragment", "hot",
"num_failed_logins", "logged_in", "count", "srv_count",
"serror_rate", "rerror_rate", "same_srv_rate", "diff_srv_rate",
"dst_host_count", "dst_host_srv_count", "dst_host_same_srv_rate",
"dst_host_diff_srv_rate", "dst_host_serror_rate", "dst_host_rerror_rate",
]
normal = train_df.loc[train_df["binary_label"] == 0, anomaly_features]
normal_fit, normal_valid = train_test_split(normal, test_size=0.20, random_state=42)
normal_fit = normal_fit.sample(min(5000, len(normal_fit)), random_state=42)
X_test = test_df[anomaly_features]
y_test = test_df["binary_label"].astype(int)
print("Normal fit/validation/test:", len(normal_fit), len(normal_valid), len(X_test))In [ ]:
detectors = {
"One-Class SVM": Pipeline([
("scaler", StandardScaler()),
("model", OneClassSVM(kernel="rbf", gamma="scale", nu=0.02)),
]),
"Isolation Forest": Pipeline([
("scaler", StandardScaler()),
("model", IsolationForest(
n_estimators=200, max_samples="auto", contamination="auto",
random_state=42, n_jobs=-1,
)),
]),
}
for detector in detectors.values():
detector.fit(normal_fit)
print("Fitted detectors:", list(detectors))In [ ]:
comparison_rows = []
detector_outputs = {}
for name, detector in detectors.items():
validation_scores = -detector.decision_function(normal_valid)
test_scores = -detector.decision_function(X_test)
threshold = float(np.quantile(validation_scores, 0.99))
predictions = (test_scores > threshold).astype(int)
metrics = security_metrics(y_test, predictions)
metrics["pr_auc"] = average_precision_score(y_test, test_scores)
comparison_rows.append({"detector": name, "threshold": threshold, **metrics})
detector_outputs[name] = {
"validation_scores": validation_scores,
"test_scores": test_scores,
"predictions": predictions,
}
detector_comparison = pd.DataFrame(comparison_rows).set_index("detector")
display(detector_comparison.round(4))In [ ]:
group_rows = []
for name, output in detector_outputs.items():
for group in ["DoS", "Probe", "R2L", "U2R"]:
mask = test_df["attack_group"].eq(group).to_numpy()
group_rows.append({
"detector": name,
"attack_group": group,
"support": int(mask.sum()),
"recall": float(output["predictions"][mask].mean()) if mask.any() else np.nan,
})
group_recall = pd.DataFrame(group_rows).pivot(
index="attack_group", columns="detector", values="recall"
)
display(group_recall.round(4))In [ ]:
assert set(detectors) == {"One-Class SVM", "Isolation Forest"}
assert len(normal_fit) <= 5000
assert all(len(output["test_scores"]) == len(test_df) for output in detector_outputs.values())
assert detector_comparison.shape[0] == 2
assert set(group_recall.index) == {"DoS", "Probe", "R2L", "U2R"}
assert detector_comparison["false_positive_rate"].between(0, 1).all()
print("✓ Lab 4 completed")