#!/usr/bin/env python3 import pandas as pd from sqlalchemy import create_engine from sqlalchemy.sql import text from sklearn.ensemble import IsolationForest from sklearn.neighbors import LocalOutlierFactor from sklearn.svm import OneClassSVM from sklearn.feature_extraction.text import TfidfVectorizer from joblib import dump, load import logging import gc import os import time from datetime import datetime, timedelta import numpy as np import argparse # Aggiunto per gestire gli argomenti da linea di comando import sys import traceback import threading # Configurazione del logging migliorata logging.basicConfig( level=logging.DEBUG, # Cambiato da INFO a DEBUG format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(sys.stdout), logging.FileHandler('analisys_debug.log') # File di log separato ] ) # Cartella per i modelli MODEL_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models') try: os.makedirs(MODEL_DIR, exist_ok=True) logging.debug(f"Directory models creata/verificata: {MODEL_DIR}") except Exception as e: logging.error(f"Errore nella creazione della directory models: {e}") # Fallback alla directory corrente MODEL_DIR = os.path.join(os.getcwd(), 'models') try: os.makedirs(MODEL_DIR, exist_ok=True) logging.debug(f"Directory models creata come fallback in: {MODEL_DIR}") except Exception as e2: logging.error(f"Impossibile creare la directory models anche come fallback: {e2}") MODEL_DIR = '.' # Usa la directory corrente come ultima risorsa # Percorsi dei modelli IF_MODEL_PATH = os.path.join(MODEL_DIR, 'isolation_forest.joblib') LOF_MODEL_PATH = os.path.join(MODEL_DIR, 'lof.joblib') SVM_MODEL_PATH = os.path.join(MODEL_DIR, 'svm.joblib') ENSEMBLE_MODEL_PATH = os.path.join(MODEL_DIR, 'ensemble_weights.joblib') PREPROCESSOR_PATH = os.path.join(MODEL_DIR, 'preprocessor.joblib') ACCUMULATED_DATA_PATH = os.path.join(MODEL_DIR, 'accumulated_data.pkl') LAST_TRAINING_PATH = os.path.join(MODEL_DIR, 'last_training.txt') # Parametri di configurazione TRAINING_FREQUENCY_HOURS = 12 # Riaddestra ogni 12 ore CONTINUOUS_LEARNING = True # Variabili globali per il tracciamento dell'avanzamento progress_status = { 'in_progress': False, 'operation': '', 'start_time': None, 'current_step': 0, 'total_steps': 0, 'details': '', 'last_update': 0 } def start_progress_tracking(operation, total_steps=100): """ Inizia il tracciamento di un'operazione lunga """ global progress_status progress_status['in_progress'] = True progress_status['operation'] = operation progress_status['start_time'] = time.time() progress_status['current_step'] = 0 progress_status['total_steps'] = total_steps progress_status['details'] = 'Inizializzazione...' progress_status['last_update'] = 0 # Avvia un thread separato per il monitoraggio dell'avanzamento threading.Thread(target=progress_reporter, daemon=True).start() logging.info(f"Avvio: {operation} (totale passi previsti: {total_steps})") def update_progress(step=None, increment=1, details=''): """ Aggiorna lo stato di avanzamento di un'operazione lunga """ global progress_status if not progress_status['in_progress']: return if step is not None: progress_status['current_step'] = step else: progress_status['current_step'] += increment if details: progress_status['details'] = details # Forza un aggiornamento immediato se details è stato fornito if details and (time.time() - progress_status['last_update']) > 5: report_progress() def end_progress_tracking(success=True): """ Termina il tracciamento dell'avanzamento """ global progress_status if not progress_status['in_progress']: return elapsed = time.time() - progress_status['start_time'] if success: logging.info(f"Completato: {progress_status['operation']} in {elapsed:.1f} secondi") else: logging.error(f"Fallito: {progress_status['operation']} dopo {elapsed:.1f} secondi") progress_status['in_progress'] = False progress_status['current_step'] = progress_status['total_steps'] report_progress(force=True) def report_progress(force=False): """ Riporta lo stato di avanzamento attuale """ global progress_status if not progress_status['in_progress'] and not force: return current_time = time.time() if not force and (current_time - progress_status['last_update']) < 30: return # Non mostrare aggiornamenti più frequenti di 30 secondi elapsed = current_time - progress_status['start_time'] percent = (progress_status['current_step'] / progress_status['total_steps']) * 100 if progress_status['total_steps'] > 0 else 0 # Calcola il tempo rimanente (se possibile) remaining = "N/A" if percent > 0: remaining_seconds = (elapsed / percent) * (100 - percent) if remaining_seconds < 60: remaining = f"{remaining_seconds:.0f} secondi" elif remaining_seconds < 3600: remaining = f"{remaining_seconds/60:.1f} minuti" else: remaining = f"{remaining_seconds/3600:.1f} ore" message = f"Progresso {progress_status['operation']}: {percent:.1f}% completato, {elapsed:.1f} secondi trascorsi, tempo rimasto: {remaining}" if progress_status['details']: message += f" - {progress_status['details']}" logging.info(message) progress_status['last_update'] = current_time def progress_reporter(): """ Thread che riporta periodicamente i progressi """ while progress_status['in_progress']: report_progress() time.sleep(5) # Controlla ogni 5 secondi, ma riporta solo ogni 30 def extract_time_features(df): """ Estrae caratteristiche temporali dai dati """ total_rows = len(df) batch_size = max(1, total_rows // 10) # Dividi in circa 10 batch per i report logging.info("Estrazione delle caratteristiche temporali...") # Converti timestamp in ora del giorno e giorno della settimana if 'Timestamp' not in df.columns and 'Data' in df.columns and 'Ora' in df.columns: # Crea timestamp dalle colonne Data e Ora df['Data'] = pd.to_datetime(df['Data'], errors='coerce') df['Ora'] = pd.to_timedelta(df['Ora'].astype(str), errors='coerce') df['Timestamp'] = df['Data'] + df['Ora'] if 'Timestamp' in df.columns: df['hour_of_day'] = df['Timestamp'].dt.hour df['day_of_week'] = df['Timestamp'].dt.dayofweek else: logging.warning("Colonna Timestamp non presente nei dati. Impossibile estrarre caratteristiche temporali.") df['hour_of_day'] = 0 df['day_of_week'] = 0 # Inizializza colonne per le nuove caratteristiche df['time_since_last'] = np.nan df['events_last_hour'] = 0 df['events_last_day'] = 0 # Estrai IP attaccante da Messaggio2 se disponibile try: update_progress(details="Estrazione IP e porte dagli attaccanti") # Messaggio2 contiene IP e porta dell'attaccante df['IP_Attaccante'] = df['Messaggio2'].apply(lambda x: x.split(':')[0] if pd.notna(x) and ':' in str(x) else None) df['Porta_Attaccante'] = df['Messaggio2'].apply(lambda x: x.split(':')[1] if pd.notna(x) and ':' in str(x) else None) # Messaggio3 contiene IP e porta dell'attaccato df['IP_Attaccato'] = df['Messaggio3'].apply(lambda x: x.split(':')[0] if pd.notna(x) and ':' in str(x) else None) df['Porta_Attaccato'] = df['Messaggio3'].apply(lambda x: x.split(':')[1] if pd.notna(x) and ':' in str(x) else None) logging.debug(f"Estratto IP attaccante per {len(df[df['IP_Attaccante'].notna()])} record") except Exception as e: logging.error(f"Errore nell'estrazione degli IP dai messaggi: {e}") # Calcola caratteristiche solo per IP dell'attaccante ip_column = 'IP_Attaccante' if ip_column in df.columns and not df[ip_column].isna().all(): logging.debug(f"Calcolo statistiche per l'IP dell'attaccante") update_progress(details="Calcolo statistiche per IP attaccanti") # Ordina per IP e timestamp df_sorted = df.sort_values([ip_column, 'Timestamp']).dropna(subset=[ip_column]) # Per ogni IP, calcola il tempo tra eventi consecutivi df['time_since_last'] = np.nan df['events_last_hour'] = 0 df['events_last_day'] = 0 # Conta quanti IP unici ci sono per stimare il lavoro unique_ips = df_sorted[ip_column].nunique() update_progress(details=f"Analisi di {unique_ips} IP univoci") ip_counter = 0 for ip, group in df_sorted.groupby(ip_column): ip_counter += 1 if ip_counter % 100 == 0: # Aggiorna ogni 100 IP update_progress(details=f"Analizzati {ip_counter}/{unique_ips} IP") if len(group) > 1: # Calcola il tempo tra eventi consecutivi group = group.copy() group['time_since_last'] = group['Timestamp'].diff().dt.total_seconds() # Aggiorna il DataFrame originale df.loc[group.index, 'time_since_last'] = group['time_since_last'] # Conta eventi nell'ultima ora e giorno per ogni IP for idx, row in group.iterrows(): current_time = row['Timestamp'] one_hour_ago = current_time - timedelta(hours=1) one_day_ago = current_time - timedelta(days=1) # Conta eventi nell'ultima ora events_last_hour = len(group[(group['Timestamp'] > one_hour_ago) & (group['Timestamp'] <= current_time)]) # Conta eventi nell'ultimo giorno events_last_day = len(group[(group['Timestamp'] > one_day_ago) & (group['Timestamp'] <= current_time)]) df.loc[idx, 'events_last_hour'] = events_last_hour df.loc[idx, 'events_last_day'] = events_last_day update_progress(details="Calcolo statistiche aggregate per IP") # Calcola statistiche per IP manualmente ip_stats = pd.DataFrame() # Raggruppa i dati per IP per calcolare le statistiche ip_groups = df_sorted.groupby(ip_column) # Calcola statistiche ip_stats['time_since_last_mean'] = ip_groups['time_since_last'].mean() ip_stats['time_since_last_std'] = ip_groups['time_since_last'].std().fillna(0) ip_stats['time_since_last_min'] = ip_groups['time_since_last'].min() ip_stats['time_since_last_max'] = ip_groups['time_since_last'].max() ip_stats['events_last_hour_max'] = ip_groups['events_last_hour'].max() ip_stats['events_last_day_max'] = ip_groups['events_last_day'].max() # Resetta l'indice per avere IP come colonna ip_stats = ip_stats.reset_index() # Merge con il DataFrame originale update_progress(details="Merge delle statistiche con il dataset principale") df = df.merge(ip_stats, on=ip_column, how='left') # Gestisci valori NaN in tutte le feature update_progress(details="Pulizia valori mancanti") for col in df.columns: if col.startswith('time_since_last') or col.startswith('events_last'): df[col] = df[col].fillna(0) logging.info("Caratteristiche temporali estratte con successo.") return df def connect_to_database(): """ Connette al database MySQL usando le credenziali da variabili d'ambiente """ try: logging.info("Connessione al database...") db_user = os.environ.get('MYSQL_USER', 'root') db_password = os.environ.get('MYSQL_PASSWORD', 'Hdgtejskjjc0-') db_host = os.environ.get('MYSQL_HOST', 'localhost') db_name = os.environ.get('MYSQL_DATABASE', 'LOG_MIKROTIK') connection_string = f"mysql+mysqlconnector://{db_user}:{db_password}@{db_host}/{db_name}" engine = create_engine(connection_string) return engine except Exception as e: logging.error(f"Errore nella connessione al database: {e}") return None def extract_new_data(engine, window_minutes=90): """ Estrae nuovi dati dal database per l'addestramento del modello """ try: logging.info(f"Estrazione dei dati degli ultimi {window_minutes} minuti...") query = text(""" SELECT * FROM Esterna WHERE TIMESTAMP(Data, Ora) >= DATE_SUB(NOW(), INTERVAL :window MINUTE) """) df = pd.read_sql(query, engine, params={"window": window_minutes}) logging.info(f"Estratti {len(df)} record dalla tabella Esterna") return df except Exception as e: logging.error(f"Errore nell'estrazione dei dati: {e}") return pd.DataFrame() def save_model_timestamp(): """ Salva il timestamp dell'ultimo addestramento del modello """ try: engine = connect_to_database() if not engine: return False with engine.connect() as conn: # Crea la tabella se non esiste create_table_query = text(""" CREATE TABLE IF NOT EXISTS model_metadata ( id INT AUTO_INCREMENT PRIMARY KEY, model_name VARCHAR(50) NOT NULL, last_trained TIMESTAMP DEFAULT CURRENT_TIMESTAMP, model_path VARCHAR(255), UNIQUE KEY unique_model (model_name) ) """) conn.execute(create_table_query) # Aggiorna o inserisci il timestamp upsert_query = text(""" INSERT INTO model_metadata (model_name, last_trained, model_path) VALUES ('ensemble', NOW(), :model_path) ON DUPLICATE KEY UPDATE last_trained = NOW(), model_path = :model_path """) conn.execute(upsert_query, {"model_path": ENSEMBLE_MODEL_PATH}) logging.info("Timestamp di addestramento del modello salvato con successo") return True except Exception as e: logging.error(f"Errore nel salvare il timestamp di addestramento: {e}") return False def needs_training(force_training=False): """ Verifica se il modello deve essere riaddestrato (ogni 12 ore) """ if force_training: logging.info("Riaddestramento forzato richiesto.") return True try: engine = connect_to_database() if not engine: return True with engine.connect() as conn: # Verifica se la tabella esiste try: query = text(""" SELECT last_trained FROM model_metadata WHERE model_name = 'ensemble' """) result = conn.execute(query).fetchone() if not result: logging.info("Nessun dato di addestramento precedente trovato, riaddestramento necessario") return True last_trained = result[0] now = datetime.now() # Se l'ultimo addestramento è più vecchio di 12 ore, riaddestra hours_diff = (now - last_trained).total_seconds() / 3600 if hours_diff >= 12: logging.info(f"Ultimo addestramento: {last_trained}, {hours_diff:.1f} ore fa. Riaddestramento necessario") return True else: logging.info(f"Ultimo addestramento: {last_trained}, {hours_diff:.1f} ore fa. Riaddestramento non necessario") return False except Exception as e: logging.warning(f"Errore nel controllo della tabella model_metadata: {e}") return True except Exception as e: logging.error(f"Errore nel verificare se il modello deve essere riaddestrato: {e}") return True def update_last_training_time(): """ Aggiorna il timestamp dell'ultimo addestramento """ with open(LAST_TRAINING_PATH, 'w') as f: f.write(datetime.now().isoformat()) def train_models(X): """ Addestra i modelli di anomalia sui dati forniti """ start_progress_tracking("addestramento modelli", 4) logging.info("Addestramento modelli in corso...") try: # Addestra Isolation Forest update_progress(1, details=f"Addestramento Isolation Forest su {X.shape[0]} record con {X.shape[1]} feature") if_model = IsolationForest( n_estimators=200, contamination=0.05, # Aumentato da 0.02 a 0.05 (5% di anomalie attese) random_state=42, n_jobs=-1 # Usa tutti i core disponibili ) if_model.fit(X) logging.info("Isolation Forest addestrato con successo") # Addestra Local Outlier Factor update_progress(2, details="Addestramento Local Outlier Factor") lof_model = LocalOutlierFactor( n_neighbors=20, contamination=0.02, novelty=True, # Deve essere True per predire nuovi dati n_jobs=-1 ) lof_model.fit(X) logging.info("Local Outlier Factor addestrato con successo") # Addestra One-Class SVM update_progress(3, details="Addestramento One-Class SVM") svm_model = OneClassSVM( kernel='rbf', gamma='auto', nu=0.02 # Simile a contamination ) svm_model.fit(X) logging.info("One-Class SVM addestrato con successo") # Salva i modelli update_progress(4, details="Salvataggio modelli su disco") dump(if_model, IF_MODEL_PATH) dump(lof_model, LOF_MODEL_PATH) dump(svm_model, SVM_MODEL_PATH) # Definisci pesi per l'ensemble (da ottimizzare con validazione) ensemble_weights = { 'isolation_forest': 0.40, 'lof': 0.35, 'svm': 0.25 } dump(ensemble_weights, ENSEMBLE_MODEL_PATH) logging.info("Tutti i modelli salvati con successo") end_progress_tracking() return if_model, lof_model, svm_model, ensemble_weights except Exception as e: end_progress_tracking(success=False) logging.error(f"Errore nell'addestramento dei modelli: {e}") return None, None, None, None def test_database_connection(conn_string=None): """ Testa la connessione al database e verifica la struttura della tabella Fibra """ try: if not conn_string: # Usa le stesse credenziali di connect_to_database db_user = os.environ.get('MYSQL_USER', 'root') db_password = os.environ.get('MYSQL_PASSWORD', 'Hdgtejskjjc0-') db_host = os.environ.get('MYSQL_HOST', 'localhost') db_name = os.environ.get('MYSQL_DATABASE', 'LOG_MIKROTIK') conn_string = f"mysql+mysqlconnector://{db_user}:{db_password}@{db_host}/{db_name}" logging.debug(f"Test di connessione al database con: {conn_string}") # Test connessione di base engine = create_engine(conn_string) with engine.connect() as conn: # Verifica connessione di base result = conn.execute(text("SELECT 1")).fetchone() if result and result[0] == 1: logging.debug("Test connessione di base superato!") # Elenca tabelle disponibili tables = conn.execute(text("SHOW TABLES")).fetchall() logging.debug(f"Tabelle disponibili: {[t[0] for t in tables]}") # Verifica struttura tabella Fibra columns = conn.execute(text("SHOW COLUMNS FROM Fibra")).fetchall() column_names = [c[0] for c in columns] logging.debug(f"Struttura tabella Fibra: {column_names}") # Conta record nella tabella count = conn.execute(text("SELECT COUNT(*) FROM Fibra")).fetchone()[0] logging.debug(f"Tabella Fibra contiene {count} record") # Ottieni un esempio sample = conn.execute(text("SELECT * FROM Fibra LIMIT 1")).fetchone() logging.debug(f"Esempio di dati da Fibra: {sample}") return engine else: logging.error("Test di connessione al database fallito") return None except Exception as e: logging.error(f"Errore nel test di connessione al database: {e}") traceback.print_exc() return None def extract_data_sample(engine, table='Esterna', limit=5): """ Estrae un campione di dati dalla tabella specificata """ try: query = text(f"SELECT * FROM {table} LIMIT :limit") sample = pd.read_sql(query, engine, params={"limit": limit}) return sample except Exception as e: logging.error(f"Errore nell'estrazione del campione di dati: {e}") return pd.DataFrame() def extract_data_for_training(engine, window_hours=12, max_records=100000, batch_size=10000): """ Estrae dati per l'addestramento del modello con una finestra temporale Implementa un sistema di paginazione per evitare blocchi con grandi dataset Args: engine: Connessione al database window_hours: Finestra temporale in ore per estrarre i dati max_records: Numero massimo di record da estrarre in totale batch_size: Dimensione di ogni batch """ try: start_progress_tracking(f"estrazione dati ({window_hours} ore)", 100) update_progress(1, details=f"Preparazione query con limite {max_records} record") logging.info(f"Estrazione dei dati delle ultime {window_hours} ore (max {max_records} record)...") # Query di conteggio per stimare il totale count_query = text(""" SELECT COUNT(*) AS total FROM Esterna WHERE TIMESTAMP(Data, Ora) >= DATE_SUB(NOW(), INTERVAL :window HOUR) """) total_count = 0 with engine.connect() as conn: result = conn.execute(count_query, {"window": window_hours}).fetchone() total_count = result[0] if result else 0 # Limita al massimo specificato total_count = min(total_count, max_records) logging.info(f"Trovati {total_count} record totali, ne estraggo al massimo {max_records}") # Estrae dati in batch offset = 0 all_data = [] batches_extracted = 0 while offset < total_count: curr_batch_size = min(batch_size, total_count - offset) percent_complete = (offset / total_count) * 100 if total_count > 0 else 0 update_progress(int(percent_complete), details=f"Estrazione batch {batches_extracted+1} ({offset}/{total_count} record, {percent_complete:.1f}%)") query = text(""" SELECT * FROM Esterna WHERE TIMESTAMP(Data, Ora) >= DATE_SUB(NOW(), INTERVAL :window HOUR) ORDER BY ID ASC LIMIT :batch_size OFFSET :offset """) batch_data = pd.read_sql(query, engine, params={ "window": window_hours, "batch_size": curr_batch_size, "offset": offset }) if batch_data.empty: logging.warning(f"Batch vuoto ricevuto al offset {offset}, interrompo estrazione") break batches_extracted += 1 logging.info(f"Estratto batch {batches_extracted}: {len(batch_data)} record") all_data.append(batch_data) offset += len(batch_data) # Aggiorna lo stato di avanzamento update_progress(int((offset / total_count) * 100), details=f"Completato batch {batches_extracted} ({offset}/{total_count} record)") # Controlla se abbiamo raggiunto il massimo if offset >= max_records: logging.info(f"Raggiunto il limite massimo di {max_records} record") break # Ferma dopo 10 batch per sicurezza se non abbiamo ancora raggiunto il totale if batches_extracted >= 10 and offset < total_count: logging.warning(f"Fermata estrazione dopo {batches_extracted} batch per evitare sovraccarico") break # Combina tutti i batch in un unico DataFrame if all_data: df = pd.concat(all_data, ignore_index=True) logging.info(f"Estratti {len(df)} record in totale tramite {batches_extracted} batch") update_progress(100, details=f"Estrazione completata: {len(df)} record in {batches_extracted} batch") else: df = pd.DataFrame() logging.warning("Nessun dato estratto!") update_progress(100, details="Estrazione completata: nessun dato trovato") end_progress_tracking() return df except Exception as e: end_progress_tracking(success=False) logging.error(f"Errore nell'estrazione dei dati per l'addestramento: {e}") import traceback logging.error(traceback.format_exc()) return pd.DataFrame() def process_text_features(df): """ Processa le feature testuali utilizzando TF-IDF """ try: logging.info("Elaborazione delle caratteristiche testuali...") # Messaggio1 contiene il protocollo if 'Messaggio1' in df.columns: df['Protocollo'] = df['Messaggio1'].fillna('') else: df['Protocollo'] = '' # Preparazione del vettorizzatore TF-IDF per il protocollo tfidf_vectorizer = TfidfVectorizer(max_features=50, stop_words='english') X_protocol = tfidf_vectorizer.fit_transform(df['Protocollo']) # Converti in DataFrame tfidf_df = pd.DataFrame( X_protocol.toarray(), columns=[f'protocol_tfidf_{i}' for i in range(X_protocol.shape[1])], index=df.index ) # Concatena con il dataframe originale result_df = pd.concat([df, tfidf_df], axis=1) logging.info(f"Feature testuali estratte con successo: {tfidf_df.shape[1]} nuove colonne") return result_df, tfidf_vectorizer except Exception as e: logging.error(f"Errore nell'elaborazione delle caratteristiche testuali: {e}") return df, None def process_categorical_features(df): """ Processa le feature categoriche """ try: logging.info("Elaborazione delle caratteristiche categoriche...") categorical_features = {} # One-hot encoding per 'Host' se presente if 'Host' in df.columns: host_dummies = pd.get_dummies(df['Host'], prefix='host', dummy_na=True) categorical_features['host'] = host_dummies.columns.tolist() df = pd.concat([df, host_dummies], axis=1) # Encoding per IP_Attaccante se presente if 'IP_Attaccante' in df.columns and df['IP_Attaccante'].notna().any(): from category_encoders import HashingEncoder he = HashingEncoder(cols=['IP_Attaccante'], n_components=15) ip_att_encoded = he.fit_transform(df[['IP_Attaccante']].fillna('unknown')) categorical_features['IP_Attaccante'] = ip_att_encoded.columns.tolist() df = pd.concat([df, ip_att_encoded], axis=1) logging.info("Caratteristiche categoriche elaborate con successo.") return df, categorical_features except Exception as e: logging.error(f"Errore nell'elaborazione delle caratteristiche categoriche: {e}") return df, {} def prepare_data_for_model(df): """ Prepara i dati per il modello """ try: total_steps = 4 # Numero di passi principali start_progress_tracking("preparazione dati", total_steps) logging.info("Preparazione dei dati per il modello...") # Assicurati che il DataFrame non sia vuoto if df.empty: logging.error("DataFrame vuoto. Impossibile preparare i dati per il modello.") end_progress_tracking(success=False) return None, None # Prepara le feature temporali update_progress(1, details=f"Estrazione caratteristiche temporali per {len(df)} record") df = extract_time_features(df) # Processa le feature testuali update_progress(2, details="Elaborazione caratteristiche testuali") df, text_vectorizer = process_text_features(df) # Processa le feature categoriche update_progress(3, details="Elaborazione caratteristiche categoriche") df, categorical_features = process_categorical_features(df) # Seleziona le colonne e finalizza update_progress(4, details="Selezione feature e finalizzazione") # Seleziona le colonne numeriche per il modello numeric_cols = [col for col in df.columns if df[col].dtype in ['int64', 'float64']] # Seleziona le colonne dummy (one-hot) dummy_cols = [] for cols in categorical_features.values(): dummy_cols.extend(cols) # Seleziona le colonne tfidf tfidf_cols = [col for col in df.columns if col.startswith('protocol_tfidf_')] # Combina tutte le colonne feature_cols = numeric_cols + dummy_cols + tfidf_cols # Rimuovi colonne non necessarie per il modello excluded_cols = ['ID', 'Data', 'Ora', 'Timestamp', 'Host', 'IndirizzoIP', 'Messaggio1', 'Messaggio2', 'Messaggio3', 'Messaggio4', 'IP_Attaccante', 'IP_Attaccato', 'Porta_Attaccante', 'Porta_Attaccato', 'Protocollo'] feature_cols = [col for col in feature_cols if col not in excluded_cols] logging.debug(f"Feature utilizzate per il modello: {feature_cols}") # Crea DataFrame delle feature X = df[feature_cols].copy() # Gestisci eventuali valori mancanti X.fillna(0, inplace=True) # Crea un oggetto preprocessore che contiene tutte le informazioni necessarie preprocessor = { 'feature_columns': feature_cols, 'text_vectorizer': text_vectorizer, 'categorical_features': categorical_features } # Salva il preprocessor su disco try: logging.info(f"Salvataggio preprocessor in {PREPROCESSOR_PATH}") dump(preprocessor, PREPROCESSOR_PATH) logging.info("Preprocessor salvato con successo") except Exception as e: logging.error(f"Errore nel salvataggio del preprocessor: {e}") logging.info(f"Dati preparati con successo: {X.shape[0]} esempi, {X.shape[1]} feature") end_progress_tracking() return X, preprocessor except Exception as e: end_progress_tracking(success=False) logging.error(f"Errore nella preparazione dei dati: {e}") import traceback logging.error(traceback.format_exc()) return None, None def main(): # Parsing degli argomenti da linea di comando parser = argparse.ArgumentParser(description='Analisi comportamentale del traffico di rete') parser.add_argument('--force-training', action='store_true', help='Forza il riaddestramento del modello indipendentemente da quando è stato addestrato l\'ultima volta') parser.add_argument('--test', action='store_true', help='Esegue un test delle funzionalità senza addestramento o predizione') parser.add_argument('--time-window', type=str, default='12 HOUR', help='Finestra temporale per l\'estrazione dei dati (es. "12 HOUR", "30 MINUTE")') parser.add_argument('--max-records', type=int, default=100000, help='Numero massimo di record da estrarre (default: 100000)') parser.add_argument('--batch-size', type=int, default=10000, help='Dimensione di ciascun batch per l\'estrazione (default: 10000)') args = parser.parse_args() # Stampa le opzioni logging.info(f"Opzioni: force_training={args.force_training}, test={args.test}, time_window={args.time_window}, max_records={args.max_records}") # Se in modalità test, esegui solo le funzioni di diagnostica if args.test: logging.info("MODALITÀ TEST - esecuzione diagnostica") # Test di connessione al database engine = test_database_connection() if not engine: logging.error("Test database fallito - impossibile continuare") sys.exit(1) # Test di estrazione dati logging.info("Test: estrazione di un campione di dati...") sample_df = extract_data_sample(engine) logging.info(f"Test di estrazione dati riuscito. Colonne: {sample_df.columns.tolist()}") if not sample_df.empty: logging.info(f"Esempio record: {sample_df.iloc[0].to_dict()}") # Test percorsi modelli logging.info("Test: controllo percorsi per i modelli...") logging.info(f"MODEL_DIR esiste: {os.path.exists(MODEL_DIR)}") # Test sistema di tracciamento progressi logging.info("Test: sistema di tracciamento progressi") start_progress_tracking("test tracciamento", 5) for i in range(5): update_progress(i+1, details=f"Passo di test {i+1}") time.sleep(2) # Simula un'operazione che richiede tempo end_progress_tracking() logging.info("Modalità test completata") sys.exit(0) # Ottieni una connessione al database engine = connect_to_database() if not engine: logging.error("Impossibile connettersi al database. Verificare le configurazioni e riprovare.") sys.exit(1) # Controlla se è necessario riaddestramento if needs_training(args.force_training): start_progress_tracking("processo di addestramento completo", 3) update_progress(1, details="Verifica necessità di addestramento") logging.info("Inizio addestramento dei modelli...") # Estrai dati per addestramento update_progress(details="Configurazione parametri di estrazione dati") time_window_value = int(args.time_window.split(' ')[0]) time_window_unit = args.time_window.split(' ')[1] if time_window_unit.upper() == 'HOUR': df = extract_data_for_training(engine, time_window_value, args.max_records, args.batch_size) elif time_window_unit.upper() == 'MINUTE': df = extract_data_for_training(engine, time_window_value / 60, args.max_records, args.batch_size) else: logging.error(f"Unità di tempo non supportata: {time_window_unit}") end_progress_tracking(success=False) sys.exit(1) if df.empty: logging.error("Nessun dato estratto per l'addestramento. Verifica la connessione al database.") end_progress_tracking(success=False) sys.exit(1) update_progress(2, details="Preparazione dati per il modello") # Prepara i dati X, preprocessor = prepare_data_for_model(df) if X is None: logging.error("Errore nella preparazione dei dati. Impossibile addestrare i modelli.") end_progress_tracking(success=False) sys.exit(1) update_progress(3, details="Addestramento modelli") # Addestra i modelli models = train_models(X) if all(m is not None for m in models): logging.info("Addestramento completato con successo!") # Aggiorna timestamp dell'ultimo addestramento save_model_timestamp() update_last_training_time() end_progress_tracking() else: logging.error("Errore nell'addestramento dei modelli.") end_progress_tracking(success=False) sys.exit(1) else: logging.info("Addestramento non necessario, utilizzo dei modelli esistenti.") logging.info("Analisi completata con successo.") if __name__ == "__main__": main()