11 KiB
11 KiB
In [ ]:
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
DATA_PATH = Path("data/ctu13_botnet_flows.csv")
OUTPUT_DIR = Path("output")
OUTPUT_DIR.mkdir(exist_ok=True)
if not DATA_PATH.exists():
raise FileNotFoundError(
f"Missing {DATA_PATH}. Follow data/README.md and run "
"python data/prepare_ctu13.py <path-to-scenario.binetflow>"
)
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import (
ConfusionMatrixDisplay, accuracy_score, average_precision_score,
confusion_matrix, f1_score, precision_score, recall_score,
)
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
RANDOM_STATE = 42In [ ]:
# TODO 1: Load DATA_PATH into flows and inspect its shape and label counts.
flows = ...In [ ]:
categorical_features = ["protocol", "direction", "state"]
numeric_features = [
"duration", "src_port", "dst_port", "src_tos", "dst_tos",
"total_packets", "total_bytes", "src_bytes", "dst_bytes",
"bytes_per_packet", "src_byte_ratio", "start_hour",
"is_well_known_dst_port",
]
feature_columns = categorical_features + numeric_features
# TODO 2: Keep only Botnet and Normal rows in `labeled`.
# TODO 3: Put Background rows in a separate `background` table.
labeled = ...
background = ...
X = ...
y = ...
# TODO 4: Create stratified 60% train, 20% validation, and 20% test splits.
X_train_valid, X_test, y_train_valid, y_test = ...
X_train, X_valid, y_train, y_valid = ...In [ ]:
preprocessor = ColumnTransformer([
("categorical", Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encoder", OneHotEncoder(handle_unknown="ignore")),
]), categorical_features),
("numeric", SimpleImputer(strategy="median"), numeric_features),
])
detector = Pipeline([
("preprocessor", preprocessor),
("model", RandomForestClassifier(
n_estimators=200,
max_depth=12,
min_samples_leaf=3,
class_weight="balanced_subsample",
random_state=RANDOM_STATE,
n_jobs=-1,
)),
])
# TODO 5: Fit the pipeline on the training split.
# TODO 6: Calculate Botnet probabilities for the validation split.
...
validation_scores = ...In [ ]:
# TODO 7: Build threshold_table and select operating_threshold using the rule above.
threshold_table = ...
operating_threshold = ...In [ ]:
# TODO 8: Score the test split, apply operating_threshold, and create metrics_table.
test_scores = ...
test_predictions = ...
metrics_table = ...In [ ]:
# TODO 9: Score every flow and add botnet_score and botnet_alert columns.
# TODO 10: Aggregate flow count, score statistics, and alert count by src_addr.
all_scores = ...
source_triage = ...
top_flows = ...In [ ]:
# TODO 11: Export the three CSV files, two figures, and analyst summary.
analyst_summary = ...In [ ]:
expected_files = {
"ctu13_botnet_metrics.csv", "ctu13_source_triage.csv", "ctu13_top_flows.csv",
"ctu13_confusion_matrix.png", "ctu13_score_distribution.png",
"ctu13_analyst_summary.md",
}
assert set(flows["label_family"]).issubset({"Botnet", "Normal", "Background"})
assert not {"src_addr", "dst_addr", "raw_label", "label_family", "binary_label"}.intersection(feature_columns)
assert set(y_test.unique()) == {0, 1}
assert 0.0 <= operating_threshold <= 1.0
assert len(test_scores) == len(y_test)
assert set(source_triage.columns) >= {"src_addr", "flow_count", "botnet_alerts", "alert_rate"}
assert expected_files.issubset({path.name for path in OUTPUT_DIR.iterdir()})
print("✓ Lab 7 completed")