229 lines
6.9 KiB
Plaintext
229 lines
6.9 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-001",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Module 6 — Lab 5: A Supervised Intrusion Decision Tree\n",
|
|
"\n",
|
|
" \n",
|
|
"**Topics:** known-attack classification, fixed test set, tree rules, group recall\n",
|
|
"\n",
|
|
"> **Lab version:** Complete every `TODO`, run the cells in order, and use the self-check before opening the Solution."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-002",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Learning objectives\n",
|
|
"\n",
|
|
"After completing this lab, you will be able to:\n",
|
|
"\n",
|
|
"- train a binary classifier from labeled normal and attack records;\n",
|
|
"- apply categorical encoding inside a pipeline;\n",
|
|
"- evaluate the fixed test set without tuning on it;\n",
|
|
"- inspect Decision Tree rules and feature importance;\n",
|
|
"- compare aggregate attack recall with group-level recall."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "cell-003",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from pathlib import Path\n",
|
|
"import numpy as np\n",
|
|
"import pandas as pd\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"TRAIN_PATH = Path(\"data/nsl_kdd_train.csv\")\n",
|
|
"TEST_PATH = Path(\"data/nsl_kdd_test.csv\")\n",
|
|
"\n",
|
|
"missing_files = [str(path) for path in (TRAIN_PATH, TEST_PATH) if not path.exists()]\n",
|
|
"if missing_files:\n",
|
|
" raise FileNotFoundError(\n",
|
|
" \"Missing prepared NSL-KDD files: \" + \", \".join(missing_files) +\n",
|
|
" \". Follow data/README.md and run: python data/prepare_dataset.py\"\n",
|
|
" )\n",
|
|
"\n",
|
|
"TARGET_COLUMNS = [\"attack_label\", \"attack_group\", \"binary_label\", \"difficulty\"]\n",
|
|
"CATEGORICAL_FEATURES = [\"protocol_type\", \"service\", \"flag\"]\n",
|
|
"\n",
|
|
"\n",
|
|
"from sklearn.compose import ColumnTransformer\n",
|
|
"from sklearn.impute import SimpleImputer\n",
|
|
"from sklearn.pipeline import Pipeline\n",
|
|
"from sklearn.preprocessing import OneHotEncoder\n",
|
|
"from sklearn.tree import DecisionTreeClassifier, export_text\n",
|
|
"\n",
|
|
"\n",
|
|
"from sklearn.metrics import (\n",
|
|
" accuracy_score, average_precision_score, confusion_matrix,\n",
|
|
" f1_score, precision_score, recall_score,\n",
|
|
")\n",
|
|
"\n",
|
|
"\n",
|
|
"def security_metrics(y_true, y_pred):\n",
|
|
" tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()\n",
|
|
" return {\n",
|
|
" \"accuracy\": accuracy_score(y_true, y_pred),\n",
|
|
" \"attack_precision\": precision_score(y_true, y_pred, zero_division=0),\n",
|
|
" \"attack_recall\": recall_score(y_true, y_pred, zero_division=0),\n",
|
|
" \"attack_f1\": f1_score(y_true, y_pred, zero_division=0),\n",
|
|
" \"false_positives\": int(fp),\n",
|
|
" \"false_negatives\": int(fn),\n",
|
|
" \"false_positive_rate\": fp / (fp + tn) if fp + tn else 0.0,\n",
|
|
" \"alerts_per_10000\": 10000 * (fp + tp) / len(y_true),\n",
|
|
" }"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "cell-004",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# TODO 1: load the fixed split and create X_train/y_train/X_test/y_test.\n",
|
|
"train_df = None\n",
|
|
"test_df = None\n",
|
|
"feature_cols = None\n",
|
|
"numeric_features = None\n",
|
|
"X_train, y_train, X_test, y_test = None, None, None, None\n",
|
|
"\n",
|
|
"# TODO 2: build a ColumnTransformer and a DecisionTree pipeline using\n",
|
|
"# max_depth=8, min_samples_leaf=5, class_weight='balanced', random_state=42.\n",
|
|
"preprocessor = None\n",
|
|
"tree_pipeline = None\n",
|
|
"\n",
|
|
"# TODO 3: fit the pipeline on the fixed training split."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-005",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 1. Evaluate binary and attack-group performance"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "cell-006",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# TODO 4: predict labels/probabilities and calculate security metrics plus PR-AUC.\n",
|
|
"tree_predictions = None\n",
|
|
"tree_probabilities = None\n",
|
|
"tree_metrics = None\n",
|
|
"display(pd.Series(tree_metrics, name=\"Decision Tree\"))\n",
|
|
"\n",
|
|
"# TODO 5: calculate recall for DoS, Probe, R2L, and U2R.\n",
|
|
"group_recall = None\n",
|
|
"display(group_recall)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-007",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Interpret the bounded tree"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "cell-008",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# TODO 6: obtain transformed feature names, calculate top-12 impurity\n",
|
|
"# importances, export rules through depth 3, and plot the importance table.\n",
|
|
"fitted_preprocessor = None\n",
|
|
"fitted_tree = None\n",
|
|
"transformed_names = None\n",
|
|
"importance = None\n",
|
|
"top_importance = None\n",
|
|
"rule_text = None\n",
|
|
"print(rule_text)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-009",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Interpretation warning\n",
|
|
"\n",
|
|
"High performance on NSL-KDD does not establish production readiness. Tree rules can reflect benchmark artifacts, and attack-group recall can expose failures hidden by the binary score."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-010",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Self-check"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "cell-011",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"assert tree_pipeline.named_steps[\"tree\"].get_depth() <= 8\n",
|
|
"assert len(tree_predictions) == len(test_df)\n",
|
|
"assert set(tree_predictions).issubset({0, 1})\n",
|
|
"assert set(group_recall.index) == {\"DoS\", \"Probe\", \"R2L\", \"U2R\"}\n",
|
|
"assert isinstance(rule_text, str) and \"class:\" in rule_text\n",
|
|
"assert np.isclose(importance.sum(), 1.0)\n",
|
|
"assert len(top_importance) <= 12\n",
|
|
"print(\"✓ Lab 5 completed\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "cell-012",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Reflection questions\n",
|
|
"\n",
|
|
"1. How does supervised learning differ from normal-only novelty detection?\n",
|
|
"2. Which attack group is hardest for the tree, and why might binary metrics hide it?\n",
|
|
"3. Why should exported tree rules not be treated as universal IDS signatures?"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.6"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|