9.5 KiB
9.5 KiB
In [ ]:
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
DATA_PATH = Path("data/malware_features.csv")
if not DATA_PATH.exists():
raise FileNotFoundError(
"Missing data/malware_features.csv. Follow data/README.md, place the three "
"official UCI CSV files under data/raw/, and run: python data/prepare_dataset.py"
)
from sklearn.dummy import DummyClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score, accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.tree import DecisionTreeClassifier
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),
"malware_precision": precision_score(y_true, y_pred, zero_division=0),
"malware_recall": recall_score(y_true, y_pred, zero_division=0),
"malware_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,
}In [ ]:
df = pd.read_csv(DATA_PATH, low_memory=False)
feature_cols = [column for column in df.columns if column not in {"source", "label"}]
X = df[feature_cols].apply(pd.to_numeric, errors="coerce")
y = df["label"].astype(int)
X_train_full, X_test, y_train_full, y_test = train_test_split(
X, y, test_size=0.20, stratify=y, random_state=42
)
X_train, X_valid, y_train, y_valid = train_test_split(
X_train_full, y_train_full, test_size=0.25, stratify=y_train_full, random_state=42
)
print("Train/validation/test:", len(X_train), len(X_valid), len(X_test))In [ ]:
weight_policies = {
"unweighted": None,
"malware_x2": {0: 1, 1: 2},
"malware_x4": {0: 1, 1: 4},
}
validation_rows = []
fitted_candidates = {}
for name, weights in weight_policies.items():
model = Pipeline([
("imputer", SimpleImputer(strategy="median", add_indicator=True)),
("tree", DecisionTreeClassifier(
max_depth=4, min_samples_leaf=10, class_weight=weights, random_state=42
)),
])
model.fit(X_train, y_train)
predictions = model.predict(X_valid)
validation_rows.append({"policy": name, **security_metrics(y_valid, predictions)})
fitted_candidates[name] = model
validation_results = pd.DataFrame(validation_rows).set_index("policy")
display(validation_results.round(4))
selected_policy = validation_results.sort_values(
["malware_recall", "false_positive_rate", "malware_f1"],
ascending=[False, True, False],
).index[0]
selected_model = fitted_candidates[selected_policy]
print("Selected class-weight policy:", selected_policy)In [ ]:
validation_probabilities = selected_model.predict_proba(X_valid)[:, 1]
threshold_rows = []
for threshold in np.arange(0.20, 0.81, 0.05):
predictions = (validation_probabilities >= threshold).astype(int)
threshold_rows.append({"threshold": round(float(threshold), 2), **security_metrics(y_valid, predictions)})
threshold_results = pd.DataFrame(threshold_rows).set_index("threshold")
eligible = threshold_results[threshold_results["malware_recall"] >= 0.98]
pool = eligible if not eligible.empty else threshold_results.loc[[threshold_results["malware_recall"].idxmax()]]
selected_threshold = float(pool.sort_values(
["false_positive_rate", "malware_f1"], ascending=[True, False]
).index[0])
display(threshold_results.round(4))
print("Selected threshold:", selected_threshold)In [ ]:
final_model = Pipeline([
("imputer", SimpleImputer(strategy="median", add_indicator=True)),
("tree", DecisionTreeClassifier(
max_depth=4,
min_samples_leaf=10,
class_weight=weight_policies[selected_policy],
random_state=42,
)),
])
final_model.fit(X_train_full, y_train_full)
test_probabilities = final_model.predict_proba(X_test)[:, 1]
default_predictions = (test_probabilities >= 0.50).astype(int)
tuned_predictions = (test_probabilities >= selected_threshold).astype(int)
test_results = pd.DataFrame([
security_metrics(y_test, default_predictions),
security_metrics(y_test, tuned_predictions),
], index=["threshold_0.50", f"threshold_{selected_threshold:.2f}"])
display(test_results.round(4))In [ ]:
assert set(validation_results.index) == set(weight_policies)
assert 0.20 <= selected_threshold <= 0.80
assert len(test_probabilities) == len(y_test)
assert set(tuned_predictions).issubset({0, 1})
assert test_results.shape[0] == 2
assert final_model.named_steps["tree"].get_depth() <= 4
print("✓ Lab 4 completed")