{ "cells": [ { "cell_type": "markdown", "id": "cell-001", "metadata": {}, "source": [ "# Module 6 — Lab 1: Auditing NSL-KDD Flow Records\n", "\n", " \n", "**Topics:** network flows, fixed split, labels, attack groups, leakage\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", "- distinguish network-flow features from targets and audit fields;\n", "- preserve the predefined NSL-KDD train/test split;\n", "- describe binary labels and the DoS, Probe, R2L, and U2R groups;\n", "- identify attack labels that appear only in the test set;\n", "- document imbalance, duplicates, and benchmark limitations." ] }, { "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\"]" ] }, { "cell_type": "markdown", "id": "cell-004", "metadata": {}, "source": [ "## 1. Load and validate the fixed split" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-005", "metadata": {}, "outputs": [], "source": [ "# TODO 1: load the prepared train and test tables.\n", "train_df = None\n", "test_df = None\n", "\n", "# TODO 2: create feature_cols and numeric_features without target/audit columns.\n", "feature_cols = None\n", "numeric_features = None\n", "\n", "print(\"Train shape:\", None)\n", "print(\"Test shape:\", None)\n", "print(\"Model features:\", None)" ] }, { "cell_type": "markdown", "id": "cell-006", "metadata": {}, "source": [ "## 2. Audit classes, groups, and unseen attack labels" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-007", "metadata": {}, "outputs": [], "source": [ "# TODO 3: calculate binary-label and attack-group counts for both splits.\n", "train_binary = None\n", "test_binary = None\n", "train_groups = None\n", "test_groups = None\n", "\n", "# TODO 4: find attack labels present in test but absent from training.\n", "train_attack_labels = None\n", "test_attack_labels = None\n", "unseen_test_attacks = None\n", "\n", "display(pd.DataFrame({\"train\": train_binary, \"test\": test_binary}))\n", "print(\"Attack labels seen only in test:\", unseen_test_attacks)" ] }, { "cell_type": "markdown", "id": "cell-008", "metadata": {}, "source": [ "## 3. Audit quality and leakage risks" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-009", "metadata": {}, "outputs": [], "source": [ "# TODO 5: build a per-feature table of missing counts and unique counts.\n", "quality_summary = None\n", "\n", "# TODO 6: count exact duplicate rows separately in train and test.\n", "duplicate_counts = None\n", "\n", "# TODO 7: list target/audit columns that must never enter the model.\n", "forbidden_model_columns = None\n", "print(duplicate_counts)\n", "print(\"Columns excluded from modeling:\", forbidden_model_columns)" ] }, { "cell_type": "markdown", "id": "cell-010", "metadata": {}, "source": [ "## Benchmark note\n", "\n", "NSL-KDD is useful for learning a reproducible IDS workflow. Its fixed test set includes attacks that are absent from training, but it remains a legacy benchmark and does not represent modern production traffic." ] }, { "cell_type": "markdown", "id": "cell-011", "metadata": {}, "source": [ "## Self-check" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-012", "metadata": {}, "outputs": [], "source": [ "assert len(feature_cols) == 41\n", "assert set(CATEGORICAL_FEATURES).issubset(feature_cols)\n", "assert set(train_df[\"binary_label\"].unique()) == {0, 1}\n", "assert set(test_df[\"attack_group\"].unique()).issubset({\"Normal\", \"DoS\", \"Probe\", \"R2L\", \"U2R\"})\n", "assert len(unseen_test_attacks) > 0\n", "assert set(feature_cols).isdisjoint(TARGET_COLUMNS)\n", "assert quality_summary.shape[0] == 41\n", "print(\"✓ Lab 1 completed\")" ] }, { "cell_type": "markdown", "id": "cell-013", "metadata": {}, "source": [ "## Reflection questions\n", "\n", "1. Why should the predefined train/test split be preserved?\n", "2. Why must `difficulty` and derived label columns be excluded from X?\n", "3. Why can binary accuracy hide failure on rare attack groups?" ] } ], "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 }