102 lines
2.6 KiB
Python
102 lines
2.6 KiB
Python
"""
|
|
©AngelaMos | 2026
|
|
splitting.py
|
|
|
|
Stratified train/val/test splitting with SMOTE
|
|
oversampling for imbalanced attack data
|
|
|
|
prepare_training_data performs a 70/15/15 stratified split
|
|
preserving class ratios, extracts the normal-only subset
|
|
from training data for the autoencoder and isolation
|
|
forest, and conditionally applies SMOTE oversampling to
|
|
the training set when the minority class ratio falls below
|
|
the target strategy (default 0.3). SMOTE is skipped if the
|
|
minority class has fewer than k_neighbors+1 samples.
|
|
Returns a TrainingSplit dataclass with X_train, y_train,
|
|
X_val, y_val, X_test, y_test, and X_normal_train arrays
|
|
|
|
Connects to:
|
|
ml/orchestrator - called at the start of the training
|
|
pipeline
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import numpy as np
|
|
from imblearn.over_sampling import SMOTE
|
|
from sklearn.model_selection import train_test_split
|
|
|
|
|
|
@dataclass
|
|
class TrainingSplit:
|
|
"""
|
|
Result of stratified splitting with SMOTE oversampling
|
|
"""
|
|
|
|
X_train: np.ndarray
|
|
y_train: np.ndarray
|
|
X_val: np.ndarray
|
|
y_val: np.ndarray
|
|
X_test: np.ndarray
|
|
y_test: np.ndarray
|
|
X_normal_train: np.ndarray
|
|
|
|
|
|
def prepare_training_data(
|
|
X: np.ndarray,
|
|
y: np.ndarray,
|
|
train_ratio: float = 0.70,
|
|
val_ratio: float = 0.15,
|
|
smote_strategy: float = 0.3,
|
|
smote_k: int = 5,
|
|
random_state: int = 42,
|
|
) -> TrainingSplit:
|
|
"""
|
|
Split data into train/val/test with SMOTE on training set only
|
|
"""
|
|
n_classes = len(np.unique(y))
|
|
if n_classes < 2:
|
|
raise ValueError("y must contain at least 2 classes")
|
|
|
|
test_size = 1.0 - train_ratio
|
|
X_train, X_rem, y_train, y_rem = train_test_split(
|
|
X,
|
|
y,
|
|
test_size=test_size,
|
|
stratify=y,
|
|
random_state=random_state,
|
|
)
|
|
|
|
X_val, X_test, y_val, y_test = train_test_split(
|
|
X_rem,
|
|
y_rem,
|
|
test_size=0.5,
|
|
stratify=y_rem,
|
|
random_state=random_state,
|
|
)
|
|
|
|
X_normal_train = X_train[y_train == 0]
|
|
|
|
class_counts = np.bincount(y_train)
|
|
minority_count = class_counts.min()
|
|
majority_count = class_counts.max()
|
|
current_ratio = minority_count / majority_count
|
|
|
|
if (minority_count >= smote_k + 1 and current_ratio < smote_strategy):
|
|
sampler = SMOTE(
|
|
sampling_strategy=smote_strategy,
|
|
k_neighbors=smote_k,
|
|
random_state=random_state,
|
|
)
|
|
X_train, y_train = sampler.fit_resample(X_train, y_train)
|
|
|
|
return TrainingSplit(
|
|
X_train=X_train,
|
|
y_train=y_train,
|
|
X_val=X_val,
|
|
y_val=y_val,
|
|
X_test=X_test,
|
|
y_test=y_test,
|
|
X_normal_train=X_normal_train,
|
|
)
|