Feat: Hyperparameter-Tuning mit GridSearchCV und SMOTE-Pipeline für ML
- Implementierung von Hyperparameter-Tuning: Der Trainingsprozess (`train_technician_model`) verwendet nun `GridSearchCV` von scikit-learn, um systematisch die besten Hyperparameter für das RandomForest-Modell zu finden. - Integration einer imblearn-Pipeline: SMOTE (zur Klassen-Balancierung) und der RandomForestClassifier wurden in eine `imblearn.pipeline.Pipeline` integriert. Dies stellt sicher, dass das Oversampling bei der Kreuzvalidierung korrekt nur auf den Trainings-Folds angewendet wird, um Datenlecks zu vermeiden. - Erweiterte Modellevaluation: Der beste durch GridSearchCV gefundene Estimator wird nun für die finale Evaluation auf dem Testset verwendet und als finales Modell gespeichert. Die besten gefundenen Parameter und die Cross-Validation-Genauigkeit werden geloggt. - Code-Struktur: Die `train_technician_model`-Methode wurde umfassend überarbeitet, um die neue Pipeline- und GridSearchCV-Logik zu implementieren. Entsprechende Imports (`GridSearchCV`, `ImbPipeline`) wurden hinzugefügt.
This commit is contained in:
@@ -61,6 +61,8 @@ from sklearn.impute import SimpleImputer
|
|||||||
from sklearn.tree import DecisionTreeClassifier, export_text
|
from sklearn.tree import DecisionTreeClassifier, export_text
|
||||||
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
|
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
|
||||||
import concurrent.futures # Fuer parallele Verarbeitung
|
import concurrent.futures # Fuer parallele Verarbeitung
|
||||||
|
from sklearn.model_selection import GridSearchCV
|
||||||
|
from imblearn.pipeline import Pipeline as ImbPipeline # Alias, um Kollision mit sklearn.pipeline zu vermeiden
|
||||||
|
|
||||||
# Spezifische externe Tools
|
# Spezifische externe Tools
|
||||||
try:
|
try:
|
||||||
@@ -9219,51 +9221,66 @@ class DataProcessor:
|
|||||||
X_test_imputed = pd.DataFrame(X_test_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
|
||||||
|
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
# +++ NEU: Klassen-Balancierung mit SMOTE auf den Trainingsdaten ++++++++
|
# +++ ANPASSUNG HIER: GridSearchCV mit Pipeline für SMOTE & RandomForest +
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
self.logger.info("Führe Klassen-Balancierung mit SMOTE auf den Trainingsdaten durch...")
|
# 4. Erstellen einer Pipeline und Definieren des Parameter-Grids
|
||||||
self.logger.info(f"Klassenverteilung VOR SMOTE:\n{y_train.value_counts()}")
|
|
||||||
|
|
||||||
smote = SMOTE(random_state=42)
|
# Schritt 1: SMOTE für Klassen-Balancierung
|
||||||
# Wichtig: SMOTE wird auf die imputierten Trainingsdaten angewendet
|
# Schritt 2: RandomForestClassifier als Modell
|
||||||
X_train_resampled, y_train_resampled = smote.fit_resample(X_train_imputed, y_train)
|
pipeline = ImbPipeline([
|
||||||
|
('smote', SMOTE(random_state=42)),
|
||||||
|
('classifier', RandomForestClassifier(random_state=42, n_jobs=-1)) # class_weight nicht nötig bei SMOTE
|
||||||
|
])
|
||||||
|
|
||||||
self.logger.info(f"Klassenverteilung NACH SMOTE:\n{y_train_resampled.value_counts()}")
|
# Definieren der Hyperparameter, die getestet werden sollen.
|
||||||
self.logger.info(f"Größe des Trainingssets nach Resampling: {len(X_train_resampled)} Zeilen.")
|
# WICHTIG: Die Parameternamen müssen mit dem Namen des Schritts in der Pipeline beginnen (z.B. 'classifier__...')
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
param_grid = {
|
||||||
# +++ ENDE SMOTE-BLOCK ++++++++++++++++++++++++++++++++++++++++++++++++++
|
'classifier__n_estimators': [100, 200], # Anzahl der Bäume
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
'classifier__max_depth': [10, 20, None], # Maximale Tiefe der Bäume (None = unbegrenzt)
|
||||||
|
'classifier__min_samples_split': [2, 5], # Mindestanzahl Samples für einen Split
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
'classifier__min_samples_leaf': [1, 2] # Mindestanzahl Samples in einem Blatt
|
||||||
# +++ ANPASSUNG HIER: RandomForest statt Decision Tree ++++++++++++++++++
|
}
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
# HINWEIS: Dies sind 2 * 3 * 2 * 2 = 24 Kombinationen.
|
||||||
# 4. Random Forest Training
|
# Mit cv=3 (siehe unten) werden 24 * 3 = 72 Modelle trainiert. Dies kann dauern!
|
||||||
# n_estimators=100 ist ein guter Standardwert.
|
# Für einen schnellen Test können Sie die Anzahl der Optionen reduzieren.
|
||||||
# class_weight='balanced' hilft bei unausgewogenen Klassen.
|
|
||||||
# n_jobs=-1 nutzt alle verfügbaren CPU-Kerne und kann das Training beschleunigen.
|
# Initialisieren von GridSearchCV
|
||||||
rf_classifier = RandomForestClassifier(n_estimators=100, random_state=42, class_weight='balanced', n_jobs=-1)
|
# cv=3 bedeutet 3-fache Kreuzvalidierung.
|
||||||
|
# scoring='accuracy' bedeutet, dass die beste Kombination anhand der Genauigkeit ausgewählt wird.
|
||||||
self.logger.info(f"Starte Training des Random Forest Modells auf {X_train_imputed.shape[0]} Trainingsbeispielen...")
|
# verbose=2 gibt detaillierte Log-Ausgaben während der Suche.
|
||||||
|
grid_search = GridSearchCV(estimator=pipeline, param_grid=param_grid, cv=3, scoring='accuracy', verbose=2, n_jobs=-1)
|
||||||
|
|
||||||
|
self.logger.info("Starte Hyperparameter-Tuning mit GridSearchCV...")
|
||||||
start_fit_time = time.time()
|
start_fit_time = time.time()
|
||||||
rf_classifier.fit(X_train_imputed, y_train) # << HIER WIRD DER RF TRAINIERT
|
# 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)
|
||||||
end_fit_time = time.time()
|
end_fit_time = time.time()
|
||||||
self.logger.info(f"Modelltraining abgeschlossen. Dauer: {end_fit_time - start_fit_time:.2f} Sekunden.")
|
self.logger.info(f"GridSearchCV-Suche abgeschlossen. Dauer: {end_fit_time - start_fit_time:.2f} Sekunden.")
|
||||||
|
|
||||||
|
# Beste Parameter und bestes Modell ausgeben und speichern
|
||||||
|
self.logger.info(f"Beste gefundene Parameter: {grid_search.best_params_}")
|
||||||
|
self.logger.info(f"Beste Cross-Validation Accuracy: {grid_search.best_score_:.4f}")
|
||||||
|
|
||||||
|
# Das beste Modell ist das, das mit den besten Parametern auf den *gesamten* Trainingsdaten trainiert wurde.
|
||||||
|
best_classifier = grid_search.best_estimator_
|
||||||
|
|
||||||
# Modell speichern
|
# Modell speichern
|
||||||
self.model = rf_classifier # Zuweisung des neuen Modells
|
self.model = best_classifier # Zuweisung des besten gefundenen Modells
|
||||||
try:
|
try:
|
||||||
model_dir = os.path.dirname(model_out)
|
model_dir = os.path.dirname(model_out)
|
||||||
if model_dir and not os.path.exists(model_dir):
|
if model_dir and not os.path.exists(model_dir):
|
||||||
os.makedirs(model_dir, exist_ok=True)
|
os.makedirs(model_dir, exist_ok=True)
|
||||||
with open(model_out, 'wb') as f:
|
with open(model_out, 'wb') as f:
|
||||||
pickle.dump(rf_classifier, f) # Das trainierte rf_classifier Objekt speichern
|
pickle.dump(best_classifier, f) # Das beste Modell speichern
|
||||||
self.logger.info(f"Random Forest Modell erfolgreich gespeichert in '{model_out}'.")
|
self.logger.info(f"Bestes RandomForest Modell erfolgreich gespeichert in '{model_out}'.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"FEHLER beim Speichern des Modells in '{model_out}': {e}")
|
self.logger.error(f"FEHLER beim Speichern des besten Modells in '{model_out}': {e}")
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
# +++ ENDE ANPASSUNG ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
# +++ ENDE ANPASSUNG ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
|
||||||
# Feature-Liste speichern (bleibt unverändert)
|
# Feature-Liste speichern (bleibt unverändert)
|
||||||
self._expected_features = feature_columns_ml
|
self._expected_features = feature_columns_ml
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user