SentinelAI / src /ai /detection /isolation_forest.py
ACA050's picture
Upload 114 files
5a3b9db verified
Raw
History Blame Contribute Delete
3.9 kB
"""
Implements: 03_AI/00_AI_ARCHITECTURE.md (Isolation Forest)
"""
import pandas as pd
from sklearn.ensemble import IsolationForest
import pickle
import json
import logging
from pathlib import Path
from datetime import datetime
logger = logging.getLogger(__name__)
class IsolationForestEngine:
"""
Unsupervised ML model for unknown anomaly detection using engineered features.
"""
def __init__(self, model_dir: str):
self.model_dir = Path(model_dir)
self.model = None
self.version = "1.0.0"
self.features_used = [
"hour_of_day", "day_of_week", "is_weekend", "is_working_hour",
"is_failure", "country_encoded", "is_mfa", "time_since_last_login",
"rolling_failures_24h"
]
def train(self, features: pd.DataFrame):
logger.info(f"Training Isolation Forest model with {len(features)} samples...")
X = features[self.features_used].fillna(0)
self.model = IsolationForest(n_estimators=100, contamination=0.05, random_state=42)
self.model.fit(X)
self.persist()
def persist(self):
import hashlib
self.model_dir.mkdir(parents=True, exist_ok=True)
version_id = datetime.now().strftime("%Y%m%d_%H%M%S")
model_path = self.model_dir / f"trained_isolation_forest_{version_id}.pkl"
meta_path = self.model_dir / f"model_metadata_{version_id}.json"
with open(model_path, "wb") as f:
pickle.dump(self.model, f)
with open(model_path, "rb") as f:
checksum = hashlib.sha256(f.read()).hexdigest()
metadata = {
"model_version": version_id,
"training_dataset_version": "1.0",
"feature_schema_version": "1.0",
"training_date": datetime.now().isoformat(),
"feature_schema": self.features_used,
"hyperparameters": {"n_estimators": self.model.n_estimators, "contamination": self.model.contamination},
"performance_metrics": {"estimated_anomaly_rate": self.model.contamination},
"model_checksum": checksum,
"model_file": model_path.name
}
with open(meta_path, "w") as f:
json.dump(metadata, f)
self.version = version_id
logger.info(f"Isolation Forest model {version_id} persisted to {model_path}")
def load(self) -> bool:
meta_files = sorted(self.model_dir.glob("model_metadata_*.json"))
if not meta_files:
return False
latest_meta = meta_files[-1]
with open(latest_meta, "r") as f:
metadata = json.load(f)
model_path = self.model_dir / metadata["model_file"]
if model_path.exists():
with open(model_path, "rb") as f:
self.model = pickle.load(f)
self.version = metadata["model_version"]
logger.info(f"Pre-trained Isolation Forest model {self.version} loaded successfully.")
return True
return False
def predict(self, features: pd.DataFrame) -> pd.DataFrame:
if self.model is None:
if not self.load():
logger.warning("No saved model found. Initiating dynamic training...")
self.train(features)
logger.info("Executing Isolation Forest inference...")
X = features[self.features_used].fillna(0)
preds = self.model.predict(X)
scores = self.model.decision_function(X)
normalized_scores = (-scores + 0.5) * 100
normalized_scores = normalized_scores.clip(min=0, max=100)
return pd.DataFrame({
"event_id": features["event_id"],
"if_score": normalized_scores,
"if_prediction": (preds == -1).astype(int)
})