bugfix
This commit is contained in:
@@ -9246,73 +9246,41 @@ class DataProcessor:
|
||||
# train_test_split, SimpleImputer, DecisionTreeClassifier,
|
||||
# accuracy_score, classification_report, confusion_matrix, export_text (sklearn).
|
||||
def train_technician_model(self, model_out=MODEL_FILE, imputer_out=IMPUTER_FILE, patterns_out=PATTERNS_FILE_JSON):
|
||||
"""
|
||||
Trainiert ein Decision Tree Modell zur Schaetzung der Servicetechniker-Buckets.
|
||||
Speichert das Modell, den Imputer und die Feature-Spalten.
|
||||
self.logger.info("Starte Training des Servicetechniker Decision Tree Modells...")
|
||||
|
||||
Args:
|
||||
model_out (str): Dateipfad zum Speichern des trainierten Modells (.pkl).
|
||||
imputer_out (str): Dateipfad zum Speichern des trainierten Imputers (.pkl).
|
||||
patterns_out (str): Dateipfad zum Speichern der Feature-Spaltenliste (.json).
|
||||
"""
|
||||
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
|
||||
self.logger.info("Starte Training des Servicetechniker Decision Tree Modells...") # <<< GEÄNDERT
|
||||
|
||||
# 1. Daten vorbereiten (nutzt die interne Methode prepare_data_for_modeling denselben Block)
|
||||
# 1. Daten vorbereiten
|
||||
df_model_ready = self.prepare_data_for_modeling()
|
||||
|
||||
# Wenn die Datenvorbereitung fehlschlug oder keinen DataFrame zurueckgab
|
||||
if df_model_ready is None or df_model_ready.empty:
|
||||
self.logger.error("Datenvorbereitung fuer Modelltraining fehlgeschlagen oder keine Daten. Training abgebrochen.") # <<< GEÄNDERT
|
||||
return # Beende die Methode
|
||||
self.logger.error("Datenvorbereitung fuer Modelltraining fehlgeschlagen oder keine Daten. Training abgebrochen.")
|
||||
return
|
||||
|
||||
|
||||
# Separate Features (X) und Target (y)
|
||||
# Identifikationsspalten und Zielspalte (muss konsistent mit prepare_data_for_modeling sein)
|
||||
# Feature Spalten und Zielspalte definieren
|
||||
identification_cols = ['name', 'Anzahl_Servicetechniker_Numeric']
|
||||
target_column = 'Techniker_Bucket'
|
||||
feature_columns_ml = [col for col in df_model_ready.columns if col not in identification_cols and col != target_column]
|
||||
|
||||
if not feature_columns_ml:
|
||||
self.logger.critical("FEHLER: Keine Feature-Spalten nach Datenvorbereitung gefunden. Training nicht moeglich.")
|
||||
return
|
||||
|
||||
# Feature Spalten sind alle Spalten im df_model_ready ausser den Identifikations- und der Zielspalte.
|
||||
feature_columns = [col for col in df_model_ready.columns if col not in identification_cols and col != target_column]
|
||||
# Stellen Sie sicher, dass es Feature-Spalten gibt (sollte durch prepare_data_for_modeling sichergestellt sein)
|
||||
if not feature_columns:
|
||||
self.logger.critical("FEHLER: Keine Feature-Spalten nach Datenvorbereitung gefunden. Training nicht moeglich.") # <<< GEÄNDERT
|
||||
return # Beende die Methode
|
||||
|
||||
# Erstellen Sie die Feature-Matrix X und den Zielvektor y
|
||||
X = df_model_ready[feature_columns]
|
||||
X = df_model_ready[feature_columns_ml]
|
||||
y = df_model_ready[target_column]
|
||||
|
||||
|
||||
self.logger.info(f"Daten fuer Training vorbereitet. X Shape: {X.shape}, y Shape: {y.shape}") # <<< GEÄNDERT
|
||||
# Logge die ersten paar Features auf Debug-Level (kann sehr lang sein)
|
||||
# self.logger.debug(f"Feature Spalten fuer Training ({len(feature_columns)}): {feature_columns[:10]}...")
|
||||
|
||||
self.logger.info(f"Daten fuer Training vorbereitet. X Shape: {X.shape}, y Shape: {y.shape}")
|
||||
|
||||
# 2. Split in Training und Test Set
|
||||
# test_size (z.B. 0.25 für 25% Testdaten), random_state fuer Reproduzierbarkeit.
|
||||
# stratify=y ist wichtig bei Klassifikationsproblemen mit ungleichen Klassen, um die
|
||||
# Klassenverteilung in Trainings- und Testset aehnlich zu halten.
|
||||
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
|
||||
|
||||
|
||||
self.logger.info(f"Daten gesplittet. Train Set: {len(X_train)} Zeilen, Test Set: {len(X_test)} Zeilen.") # <<< GEÄNDERT
|
||||
self.logger.info(f"Daten gesplittet. Train Set: {len(X_train)} Zeilen, Test Set: {len(X_test)} Zeilen.")
|
||||
|
||||
# 3. Imputation (Fehlende Werte ersetzen)
|
||||
# Verwenden Sie SimpleImputer (z.B. Median), um NaN-Werte zu ersetzen.
|
||||
# Median ist robust gegenueber Ausreissern. Alternativ: 'mean' oder 'most_frequent'.
|
||||
imputer = SimpleImputer(strategy='median')
|
||||
self.logger.info(f"Fitte Imputer mit Strategie '{imputer.strategy}' auf Trainingsdaten...") # <<< GEÄNDERT
|
||||
# Fitten Sie den Imputer NUR auf den Trainingsdaten, um Data Leakage zu vermeiden.
|
||||
imputer.fit(X_train) # Fitten Sie den Imputer auf X_train
|
||||
|
||||
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
|
||||
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): # Nur erstellen, wenn dirname nicht leer ist
|
||||
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)
|
||||
@@ -9320,69 +9288,87 @@ class DataProcessor:
|
||||
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
|
||||
|
||||
# Speichern Sie das trainierte Modell
|
||||
self.model = dt_classifier
|
||||
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
|
||||
|
||||
# Die Zeile "Numerische Features imputiert." war ein allgemeiner Kommentar.
|
||||
# Wichtig ist, dass X_train_imputed und X_test_imputed jetzt bereitstehen.
|
||||
|
||||
# 4. Decision Tree Training
|
||||
dt_classifier = DecisionTreeClassifier(random_state=42, class_weight='balanced')
|
||||
# Optional: Hyperparameter-Tuning ...
|
||||
|
||||
self.logger.info(f"Starte Training des Decision Tree Modells auf {X_train_imputed.shape[0]} Trainingsbeispielen mit {X_train_imputed.shape[1]} Features...")
|
||||
start_fit_time = time.time()
|
||||
dt_classifier.fit(X_train_imputed, y_train) # << MODELL WIRD HIER TRAINIERT
|
||||
end_fit_time = time.time()
|
||||
self.logger.info(f"Modelltraining abgeschlossen. Dauer: {end_fit_time - start_fit_time:.2f} Sekunden.")
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ HIER DEN CODE ZUM SPEICHERN DES MODELLS EINFÜGEN/VERSCHIEBEN ++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# Speichern Sie das trainierte Modell.
|
||||
self.model = dt_classifier # Zuweisung an self.model NACH dem Training
|
||||
try:
|
||||
model_dir = os.path.dirname(model_out)
|
||||
if model_dir and not os.path.exists(model_dir): # Nur erstellen, wenn dirname nicht leer ist
|
||||
if model_dir and not os.path.exists(model_dir):
|
||||
os.makedirs(model_dir, exist_ok=True)
|
||||
with open(model_out, 'wb') as f:
|
||||
pickle.dump(dt_classifier, f)
|
||||
pickle.dump(dt_classifier, f) # Das trainierte dt_classifier Objekt speichern
|
||||
self.logger.info(f"Decision Tree Modell erfolgreich gespeichert in '{model_out}'.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"FEHLER beim Speichern des Modells in '{model_out}': {e}")
|
||||
self.logger.debug(traceback.format_exc())
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ ENDE MODELLSPEICHERUNG ++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
# Speichern Sie die Liste der Feature-Spalten
|
||||
self._expected_features = feature_columns_ml # Stellen Sie sicher, dass feature_columns_ml hier korrekt ist
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ HIER DEN CODE ZUM SPEICHERN DER FEATURE-LISTE EINFÜGEN/VERSCHIEBEN +
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# Speichern Sie die Liste der Feature-Spalten (fuer die Vorhersage)
|
||||
# feature_columns_ml wurde bereits oben korrekt definiert
|
||||
self._expected_features = feature_columns_ml
|
||||
try:
|
||||
patterns_data = {"feature_columns": feature_columns_ml, "target_classes": list(dt_classifier.classes_)}
|
||||
patterns_data = {"feature_columns": self._expected_features, "target_classes": list(dt_classifier.classes_)}
|
||||
patterns_dir = os.path.dirname(patterns_out)
|
||||
if patterns_dir and not os.path.exists(patterns_dir): # Nur erstellen, wenn dirname nicht leer ist
|
||||
if patterns_dir and not os.path.exists(patterns_dir):
|
||||
os.makedirs(patterns_dir, exist_ok=True)
|
||||
with open(patterns_out, 'w', encoding='utf-8') as f:
|
||||
json.dump(patterns_data, f, indent=4, ensure_ascii=False)
|
||||
json.dump(patterns_data, f, indent=4, ensure_ascii=False)
|
||||
self.logger.info(f"Erwartete Feature-Spalten und Klassen erfolgreich gespeichert in '{patterns_out}'.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"FEHLER beim Speichern der Feature-Spalten in '{patterns_out}': {e}")
|
||||
self.logger.debug(traceback.format_exc())
|
||||
# Fahren Sie fort
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ ENDE FEATURE-LISTEN SPEICHERUNG +++++++++++++++++++++++++++++++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
# 5. Evaluation (Optional, aber empfohlen, um die Modellleistung zu bewerten)
|
||||
self.logger.info("Starte Modellevaluation...") # <<< GEÄNDERT
|
||||
|
||||
# Vorhersagen auf dem Testset
|
||||
self.logger.info("Starte Modellevaluation...")
|
||||
y_pred = dt_classifier.predict(X_test_imputed)
|
||||
|
||||
# Metriken berechnen und loggen
|
||||
accuracy = accuracy_score(y_test, y_pred)
|
||||
self.logger.info(f"Modell Genauigkeit auf dem Testset: {accuracy:.4f}") # <<< GEÄNDERT
|
||||
self.logger.info(f"Modell Genauigkeit auf dem Testset: {accuracy:.4f}")
|
||||
|
||||
class_report_labels = list(dt_classifier.classes_) # Sicherstellen, dass es eine Liste ist
|
||||
class_report = classification_report(y_test, y_pred, zero_division=0, labels=class_report_labels, target_names=[str(c) for c in class_report_labels])
|
||||
self.logger.info(f"Klassifikationsbericht auf dem Testset:\n{class_report}")
|
||||
|
||||
# Klassifikationsbericht
|
||||
# zero_division='warn' ist Standard, '0' gibt 0 fuer nicht vorhandene Klassen, 'none' wirft Fehler.
|
||||
class_report = classification_report(y_test, y_pred, zero_division=0, labels=dt_classifier.classes_, target_names=[str(c) for c in dt_classifier.classes_]) # Stelle sicher, dass Labels und Target-Namen konsistent sind
|
||||
self.logger.info(f"Klassifikationsbericht auf dem Testset:\n{class_report}") # <<< GEÄNDERT
|
||||
cm = confusion_matrix(y_test, y_pred, labels=class_report_labels)
|
||||
self.logger.info(f"Konfusionsmatrix auf dem Testset (Zeilen=Wahr, Spalten=Vorhersage):\n{cm}")
|
||||
|
||||
# Konfusionsmatrix
|
||||
# display_labels=dt_classifier.classes_ sorgt fuer korrekte Beschriftung
|
||||
cm = confusion_matrix(y_test, y_pred, labels=dt_classifier.classes_)
|
||||
self.logger.info(f"Konfusionsmatrix auf dem Testset (Zeilen=Wahr, Spalten=Vorhersage):\n{cm}") # <<< GEÄNDERT
|
||||
|
||||
# Entscheidungsregeln extrahieren (Optional, fuer Verstaendnis)
|
||||
try:
|
||||
# Beschraenken Sie die Tiefe fuer die Ausgabe, falls der Baum sehr tief ist
|
||||
# feature_names muessen der Reihenfolge in X_train_imputed entsprechen
|
||||
tree_rules = export_text(dt_classifier, feature_names=feature_columns, max_depth=7) # max_depth anpassen
|
||||
self.logger.info(f"Erste Regeln des Decision Tree (max Tiefe 7):\n{tree_rules}") # <<< GEÄNDERT
|
||||
except Exception as e:
|
||||
# Fange Fehler beim Exportieren der Regeln ab
|
||||
self.logger.warning(f"FEHLER beim Exportieren der Baumregeln: {e}") # <<< GEÄNDERT
|
||||
# Logge den Traceback.
|
||||
self.logger.debug(traceback.format_exc()) # <<< GEÄNDERT
|
||||
tree_rules = export_text(dt_classifier, feature_names=feature_columns_ml, max_depth=7)
|
||||
self.logger.info(f"Erste Regeln des Decision Tree (max Tiefe 7):\n{tree_rules}")
|
||||
except Exception as e_tree_rules: # Spezifischere Exception-Variable
|
||||
self.logger.warning(f"FEHLER beim Exportieren der Baumregeln: {e_tree_rules}")
|
||||
self.logger.debug(traceback.format_exc())
|
||||
|
||||
self.logger.info("Modelltraining und -evaluation abgeschlossen.") # <<< GEÄNDERT
|
||||
self.logger.info("Modelltraining und -evaluation abgeschlossen.")
|
||||
|
||||
# ==============================================================================
|
||||
# Ende DataProcessor Klasse Utility: ML Prep & Training Block
|
||||
|
||||
Reference in New Issue
Block a user