Feat: Umstellung von Decision Tree auf RandomForest für ML-Technikerschätzung

- Umstellung des ML-Modells: Der Algorithmus zur Schätzung der Servicetechniker-Buckets wurde von einem einzelnen Decision Tree auf einen RandomForestClassifier umgestellt. Ziel ist eine höhere Vorhersagegenauigkeit und bessere Generalisierungsfähigkeit durch die Nutzung eines Ensemble-Modells.
- Verbesserte Modellevaluation: Die Ausgabe der Baumregeln (spezifisch für Decision Trees) wurde durch die Analyse und Ausgabe der Feature Importance ersetzt. Dies gibt Aufschluss darüber, welche Features (z.B. Log-Umsatz, Branche, Gruppenzugehörigkeit) den größten Einfluss auf die Vorhersagen des RandomForest-Modells haben.
- Code-Anpassungen: Die Methode `train_technician_model` wurde entsprechend überarbeitet, um den RandomForestClassifier zu instanziieren, zu trainieren, zu speichern und zu evaluieren. Der `import` für `RandomForestClassifier` wurde hinzugefügt.
This commit is contained in:
2025-06-18 12:12:08 +00:00
parent 724d073f31
commit 7e1f39618c

View File

@@ -54,6 +54,7 @@ import unicodedata # Fuer Text-Normalisierung
# Bibliotheken fuer Datenanalyse und ML
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.impute import SimpleImputer
from sklearn.tree import DecisionTreeClassifier, export_text
@@ -9206,43 +9207,37 @@ class DataProcessor:
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...")
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# +++ ANPASSUNG HIER: RandomForest statt Decision Tree ++++++++++++++++++
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# 4. Random Forest Training
# n_estimators=100 ist ein guter Standardwert.
# class_weight='balanced' hilft bei unausgewogenen Klassen.
# n_jobs=-1 nutzt alle verfügbaren CPU-Kerne und kann das Training beschleunigen.
rf_classifier = RandomForestClassifier(n_estimators=100, random_state=42, class_weight='balanced', n_jobs=-1)
self.logger.info(f"Starte Training des Random Forest Modells auf {X_train_imputed.shape[0]} Trainingsbeispielen...")
start_fit_time = time.time()
dt_classifier.fit(X_train_imputed, y_train) # << MODELL WIRD HIER TRAINIERT
rf_classifier.fit(X_train_imputed, y_train) # << HIER WIRD DER RF 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
# Modell speichern
self.model = rf_classifier # Zuweisung des neuen Modells
try:
model_dir = os.path.dirname(model_out)
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) # Das trainierte dt_classifier Objekt speichern
self.logger.info(f"Decision Tree Modell erfolgreich gespeichert in '{model_out}'.")
pickle.dump(rf_classifier, f) # Das trainierte rf_classifier Objekt speichern
self.logger.info(f"Random Forest 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 ++++++++++++++++++++++++++++++++++++++++++++
# +++ ENDE ANPASSUNG ++++++++++++++++++++++++++++++++++++++++++++++++++++
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# +++ 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
# Feature-Liste speichern (bleibt unverändert)
self._expected_features = feature_columns_ml
try:
patterns_data = {"feature_columns": self._expected_features, "target_classes": list(dt_classifier.classes_)}
@@ -9273,11 +9268,17 @@ class DataProcessor:
self.logger.info(f"Konfusionsmatrix auf dem Testset (Zeilen=Wahr, Spalten=Vorhersage):\n{cm}")
try:
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("Feature Importance (Top 15):")
importances = rf_classifier.feature_importances_
feature_importance_df = pd.DataFrame({
'Feature': feature_columns_ml,
'Importance': importances
}).sort_values(by='Importance', ascending=False)
# Ausgabe der Top 15 Features
self.logger.info(f"\n{feature_importance_df.head(15).to_string(index=False)}")
except Exception as e_feat_imp:
self.logger.warning(f"FEHLER beim Berechnen/Anzeigen der Feature Importance: {e_feat_imp}")
self.logger.info("Modelltraining und -evaluation abgeschlossen.")