spawn_access_control/
ml_analyzer.rsuse smartcore::linalg::basic::matrix::DenseMatrix;
use smartcore::ensemble::random_forest_classifier::RandomForestClassifier;
use smartcore::model_selection::train_test_split;
use crate::behavioral::AccessEvent;
use chrono::{DateTime, Utc, Timelike, Datelike};
use serde::Serialize;
use crate::ml_metrics::{ModelMetrics, ConfusionMatrix};
use std::time::Instant;
use std::collections::HashMap;
#[derive(Debug, Serialize)]
pub struct MLPrediction {
pub is_anomaly: bool,
pub confidence: f64,
pub features: Vec<String>,
pub timestamp: DateTime<Utc>,
}
pub struct MLAnalyzer {
model: Option<RandomForestClassifier<f64, i32, DenseMatrix<f64>, Vec<i32>>>,
feature_names: Vec<String>,
}
impl MLAnalyzer {
pub fn new() -> Self {
Self {
model: None,
feature_names: vec![
"hour_of_day".to_string(),
"day_of_week".to_string(),
"duration_seconds".to_string(),
"resource_frequency".to_string(),
"success_rate".to_string(),
],
}
}
pub fn train(&mut self, events: &[AccessEvent], anomalies: &[AccessEvent]) {
let (features, labels) = self.prepare_training_data(events, anomalies);
if features.is_empty() {
return;
}
let x = DenseMatrix::from_2d_vec(&features);
let y = labels.iter().map(|&x| x as i32).collect::<Vec<_>>();
let (x_train, _x_test, y_train, _y_test) = train_test_split(
&x,
&y,
0.2,
true,
Some(42)
);
let model = RandomForestClassifier::fit(
&x_train,
&y_train,
Default::default()
).unwrap();
self.model = Some(model);
}
pub fn predict(&self, event: &AccessEvent) -> Option<MLPrediction> {
let model = self.model.as_ref()?;
let features = self.extract_features(event);
let x = DenseMatrix::from_2d_vec(&vec![features]);
let prediction = model.predict(&x).ok()?;
let confidence = if prediction[0] == 1 { 0.8 } else { 0.2 };
Some(MLPrediction {
is_anomaly: prediction[0] == 1,
confidence,
features: self.feature_names.clone(),
timestamp: Utc::now(),
})
}
fn extract_features(&self, event: &AccessEvent) -> Vec<f64> {
vec![
event.timestamp.hour() as f64,
event.timestamp.weekday().num_days_from_monday() as f64,
event.duration.as_secs_f64(),
1.0, if event.success { 1.0 } else { 0.0 },
]
}
fn prepare_training_data(&self, events: &[AccessEvent], anomalies: &[AccessEvent])
-> (Vec<Vec<f64>>, Vec<f64>)
{
let mut features = Vec::new();
let mut labels = Vec::new();
for event in events {
features.push(self.extract_features(event));
let is_anomaly = anomalies.iter().any(|a| {
(event.timestamp - a.timestamp).num_minutes().abs() < 1
});
labels.push(if is_anomaly { 1.0 } else { 0.0 });
}
(features, labels)
}
pub fn evaluate_model(&self, test_events: &[AccessEvent], test_anomalies: &[AccessEvent]) -> Option<ModelMetrics> {
let model = self.model.as_ref()?;
let start_time = Instant::now();
let (features, actual_labels) = self.prepare_training_data(test_events, test_anomalies);
if features.is_empty() {
return None;
}
let x = DenseMatrix::from_2d_vec(&features);
let predictions = model.predict(&x).ok()?;
let mut confusion_matrix = ConfusionMatrix::new();
for (pred, actual) in predictions.iter().zip(actual_labels.iter()) {
confusion_matrix.update(
(*pred as i32) == 1,
(*actual as i32) == 1
);
}
let feature_importance = self.calculate_feature_importance();
Some(ModelMetrics {
model_id: format!("rf_model_{}", Utc::now().timestamp()),
timestamp: Utc::now(),
accuracy: confusion_matrix.accuracy(),
precision: confusion_matrix.precision(),
recall: confusion_matrix.recall(),
f1_score: confusion_matrix.f1_score(),
confusion_matrix,
feature_importance,
training_duration: start_time.elapsed(),
})
}
fn calculate_feature_importance(&self) -> HashMap<String, f64> {
let mut importance = HashMap::new();
for (idx, name) in self.feature_names.iter().enumerate() {
let score = 1.0 / (idx + 1) as f64; importance.insert(name.clone(), score);
}
let total: f64 = importance.values().sum();
for score in importance.values_mut() {
*score /= total;
}
importance
}
pub fn predict_with_threshold(&self, event: &AccessEvent, threshold: f64) -> Option<MLPrediction> {
let prediction = self.predict(event)?;
let is_anomaly = prediction.confidence > threshold;
Some(MLPrediction {
is_anomaly,
..prediction
})
}
pub fn update_model(&mut self, new_events: &[AccessEvent], new_anomalies: &[AccessEvent]) -> Option<ModelMetrics> {
self.train(new_events, new_anomalies);
self.evaluate_model(new_events, new_anomalies)
}
}