CSAI2026 labs: Lab-2..Lab-6 (Practical + Solution, data), Module-2..Module-6 notebooks and datasets

This commit is contained in:
mikrotik
2026-10-12 19:16:14 +07:00
commit 2fefa615cc
184 changed files with 1016761 additions and 0 deletions
@@ -0,0 +1,709 @@
{
"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",
"> **Solution version:** Run the cells in order, compare the outputs with your work, and read the suggested answers."
]
},
{
"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": 1,
"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": 2,
"id": "cell-005",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Train shape: (125973, 45)\n",
"Test shape: (22544, 45)\n",
"Model features: 41\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>duration</th>\n",
" <th>protocol_type</th>\n",
" <th>service</th>\n",
" <th>flag</th>\n",
" <th>src_bytes</th>\n",
" <th>dst_bytes</th>\n",
" <th>land</th>\n",
" <th>wrong_fragment</th>\n",
" <th>urgent</th>\n",
" <th>hot</th>\n",
" <th>...</th>\n",
" <th>dst_host_same_src_port_rate</th>\n",
" <th>dst_host_srv_diff_host_rate</th>\n",
" <th>dst_host_serror_rate</th>\n",
" <th>dst_host_srv_serror_rate</th>\n",
" <th>dst_host_rerror_rate</th>\n",
" <th>dst_host_srv_rerror_rate</th>\n",
" <th>attack_label</th>\n",
" <th>attack_group</th>\n",
" <th>binary_label</th>\n",
" <th>difficulty</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0</td>\n",
" <td>tcp</td>\n",
" <td>ftp_data</td>\n",
" <td>sf</td>\n",
" <td>491</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0.17</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.05</td>\n",
" <td>0.00</td>\n",
" <td>normal</td>\n",
" <td>Normal</td>\n",
" <td>0</td>\n",
" <td>20</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>0</td>\n",
" <td>udp</td>\n",
" <td>other</td>\n",
" <td>sf</td>\n",
" <td>146</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0.88</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>normal</td>\n",
" <td>Normal</td>\n",
" <td>0</td>\n",
" <td>15</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>0</td>\n",
" <td>tcp</td>\n",
" <td>private</td>\n",
" <td>s0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>1.00</td>\n",
" <td>1.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>neptune</td>\n",
" <td>DoS</td>\n",
" <td>1</td>\n",
" <td>19</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>0</td>\n",
" <td>tcp</td>\n",
" <td>http</td>\n",
" <td>sf</td>\n",
" <td>232</td>\n",
" <td>8153</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0.03</td>\n",
" <td>0.04</td>\n",
" <td>0.03</td>\n",
" <td>0.01</td>\n",
" <td>0.00</td>\n",
" <td>0.01</td>\n",
" <td>normal</td>\n",
" <td>Normal</td>\n",
" <td>0</td>\n",
" <td>21</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>0</td>\n",
" <td>tcp</td>\n",
" <td>http</td>\n",
" <td>sf</td>\n",
" <td>199</td>\n",
" <td>420</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>...</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>0.00</td>\n",
" <td>normal</td>\n",
" <td>Normal</td>\n",
" <td>0</td>\n",
" <td>21</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5 rows × 45 columns</p>\n",
"</div>"
],
"text/plain": [
" duration protocol_type service flag src_bytes dst_bytes land \\\n",
"0 0 tcp ftp_data sf 491 0 0 \n",
"1 0 udp other sf 146 0 0 \n",
"2 0 tcp private s0 0 0 0 \n",
"3 0 tcp http sf 232 8153 0 \n",
"4 0 tcp http sf 199 420 0 \n",
"\n",
" wrong_fragment urgent hot ... dst_host_same_src_port_rate \\\n",
"0 0 0 0 ... 0.17 \n",
"1 0 0 0 ... 0.88 \n",
"2 0 0 0 ... 0.00 \n",
"3 0 0 0 ... 0.03 \n",
"4 0 0 0 ... 0.00 \n",
"\n",
" dst_host_srv_diff_host_rate dst_host_serror_rate \\\n",
"0 0.00 0.00 \n",
"1 0.00 0.00 \n",
"2 0.00 1.00 \n",
"3 0.04 0.03 \n",
"4 0.00 0.00 \n",
"\n",
" dst_host_srv_serror_rate dst_host_rerror_rate dst_host_srv_rerror_rate \\\n",
"0 0.00 0.05 0.00 \n",
"1 0.00 0.00 0.00 \n",
"2 1.00 0.00 0.00 \n",
"3 0.01 0.00 0.01 \n",
"4 0.00 0.00 0.00 \n",
"\n",
" attack_label attack_group binary_label difficulty \n",
"0 normal Normal 0 20 \n",
"1 normal Normal 0 15 \n",
"2 neptune DoS 1 19 \n",
"3 normal Normal 0 21 \n",
"4 normal Normal 0 21 \n",
"\n",
"[5 rows x 45 columns]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"train_df = pd.read_csv(TRAIN_PATH, low_memory=False)\n",
"test_df = pd.read_csv(TEST_PATH, low_memory=False)\n",
"feature_cols = [column for column in train_df.columns if column not in TARGET_COLUMNS]\n",
"numeric_features = [column for column in feature_cols if column not in CATEGORICAL_FEATURES]\n",
"\n",
"assert list(train_df.columns) == list(test_df.columns)\n",
"print(\"Train shape:\", train_df.shape)\n",
"print(\"Test shape:\", test_df.shape)\n",
"print(\"Model features:\", len(feature_cols))\n",
"display(train_df.head())"
]
},
{
"cell_type": "markdown",
"id": "cell-006",
"metadata": {},
"source": [
"## 2. Audit classes, groups, and unseen attack labels"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "cell-007",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>train</th>\n",
" <th>test</th>\n",
" </tr>\n",
" <tr>\n",
" <th>binary_label</th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>67343</td>\n",
" <td>9711</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>58630</td>\n",
" <td>12833</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" train test\n",
"binary_label \n",
"0 67343 9711\n",
"1 58630 12833"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>train</th>\n",
" <th>test</th>\n",
" </tr>\n",
" <tr>\n",
" <th>attack_group</th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>DoS</th>\n",
" <td>45927</td>\n",
" <td>7460</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Normal</th>\n",
" <td>67343</td>\n",
" <td>9711</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Probe</th>\n",
" <td>11656</td>\n",
" <td>2421</td>\n",
" </tr>\n",
" <tr>\n",
" <th>R2L</th>\n",
" <td>995</td>\n",
" <td>2885</td>\n",
" </tr>\n",
" <tr>\n",
" <th>U2R</th>\n",
" <td>52</td>\n",
" <td>67</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" train test\n",
"attack_group \n",
"DoS 45927 7460\n",
"Normal 67343 9711\n",
"Probe 11656 2421\n",
"R2L 995 2885\n",
"U2R 52 67"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Attack labels seen only in test: ['apache2', 'httptunnel', 'mailbomb', 'mscan', 'named', 'processtable', 'ps', 'saint', 'sendmail', 'snmpgetattack', 'snmpguess', 'sqlattack', 'udpstorm', 'worm', 'xlock', 'xsnoop', 'xterm']\n"
]
}
],
"source": [
"train_binary = train_df[\"binary_label\"].value_counts().sort_index()\n",
"test_binary = test_df[\"binary_label\"].value_counts().sort_index()\n",
"train_groups = train_df[\"attack_group\"].value_counts()\n",
"test_groups = test_df[\"attack_group\"].value_counts()\n",
"\n",
"train_attack_labels = set(train_df.loc[train_df[\"binary_label\"] == 1, \"attack_label\"])\n",
"test_attack_labels = set(test_df.loc[test_df[\"binary_label\"] == 1, \"attack_label\"])\n",
"unseen_test_attacks = sorted(test_attack_labels - train_attack_labels)\n",
"\n",
"display(pd.DataFrame({\"train\": train_binary, \"test\": test_binary}).fillna(0).astype(int))\n",
"display(pd.DataFrame({\"train\": train_groups, \"test\": test_groups}).fillna(0).astype(int))\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": 4,
"id": "cell-009",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'train_exact_duplicates': 0, 'test_exact_duplicates': 0}\n",
"Columns excluded from modeling: ['attack_group', 'attack_label', 'binary_label', 'difficulty']\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>train_missing</th>\n",
" <th>test_missing</th>\n",
" <th>train_unique</th>\n",
" <th>test_unique</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>num_outbound_cmds</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>is_host_login</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>is_guest_login</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>land</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>logged_in</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>root_shell</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>protocol_type</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>wrong_fragment</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>num_shells</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>3</td>\n",
" <td>4</td>\n",
" </tr>\n",
" <tr>\n",
" <th>su_attempted</th>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>3</td>\n",
" <td>3</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" train_missing test_missing train_unique test_unique\n",
"num_outbound_cmds 0 0 1 1\n",
"is_host_login 0 0 2 2\n",
"is_guest_login 0 0 2 2\n",
"land 0 0 2 2\n",
"logged_in 0 0 2 2\n",
"root_shell 0 0 2 2\n",
"protocol_type 0 0 3 3\n",
"wrong_fragment 0 0 3 3\n",
"num_shells 0 0 3 4\n",
"su_attempted 0 0 3 3"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"quality_summary = pd.DataFrame({\n",
" \"train_missing\": train_df[feature_cols].isna().sum(),\n",
" \"test_missing\": test_df[feature_cols].isna().sum(),\n",
" \"train_unique\": train_df[feature_cols].nunique(dropna=True),\n",
" \"test_unique\": test_df[feature_cols].nunique(dropna=True),\n",
"})\n",
"duplicate_counts = {\n",
" \"train_exact_duplicates\": int(train_df.duplicated().sum()),\n",
" \"test_exact_duplicates\": int(test_df.duplicated().sum()),\n",
"}\n",
"forbidden_model_columns = sorted(set(TARGET_COLUMNS) & set(train_df.columns))\n",
"\n",
"print(duplicate_counts)\n",
"print(\"Columns excluded from modeling:\", forbidden_model_columns)\n",
"display(quality_summary.sort_values(\"train_unique\").head(10))"
]
},
{
"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": 5,
"id": "cell-012",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✓ Lab 1 completed\n"
]
}
],
"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": [
"## Suggested answers\n",
"\n",
"1. **Why keep the fixed split?** It preserves the benchmark design and exposes attack labels that training never saw.\n",
"2. **Why exclude `difficulty`?** It was derived from previous classifiers and is not a network measurement available to a real detector.\n",
"3. **Why inspect group counts?** Binary metrics can hide poor detection of rare R2L or U2R records."
]
}
],
"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
}