Praxis: scikit-learn auf PlantVillage
Die komplette Strecke in einem Skript: Features aus der Features-Lektion, Split aus der Pipeline-Lektion, dazu Scaler + Logistic Regression. Das ist die Baseline, an der sich alle weiteren Verfahren messen lassen müssen.
Das komplette Skript
import numpy as np
from pathlib import Path
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
# ── 1. Pfade + Labels sammeln (siehe Pipeline-Lektion) ──
DATA_DIR = Path("plantvillage dataset/color")
pfade, labels = [], []
for ordner in sorted(DATA_DIR.iterdir()):
for bild in ordner.glob("*.jpg"):
pfade.append(bild)
labels.append(ordner.name)
X_train_p, X_test_p, y_train, y_test = train_test_split(
pfade, labels, test_size=0.20, stratify=labels, random_state=42
)
# ── 2. Features extrahieren (siehe Features-Lektion) ──
X_train = np.array([extrahiere_features(p) for p in X_train_p])
X_test = np.array([extrahiere_features(p) for p in X_test_p])
# ── 3. Skalieren: fit NUR auf Train ──
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# ── 4. Trainieren ──
modell = LogisticRegression(
max_iter=2000, # Default 100 reicht hier nicht
C=1.0, # Regularisierungsstärke (Tuning-Lektion)
n_jobs=-1, # alle CPU-Kerne
)
modell.fit(X_train, y_train)
# ── 5. Evaluieren ──
y_pred = modell.predict(X_test)
print(classification_report(y_test, y_pred, digits=3))Was dabei herauskommt
Mit 512 HSV-Histogramm-Features liegt die Accuracy typischerweise irgendwo zwischen 70 und 90 % — abhängig von Bins, Bildgröße und ob Textur-Features dazukommen. Das klingt nach viel für ein lineares Modell, ist aber vor allem ein Kompliment an PlantVillage: kontrollierte Fotos, farblich gut trennbare Klassen. Wichtiger als die eine Zahl: der Blick in den classification_report — welche Klassen funktionieren, welche nicht?
Bonus: Das Modell erklären
Der unterschätzte Vorteil der Logistic Regression: Man kann nachsehen, warum sie entscheidet. modell.coef_ hat eine Zeile pro Klasse — die größten Gewichte zeigen, welche Histogramm-Bins (= Farbbereiche) für eine Klasse sprechen:
klassen = modell.classes_
idx = list(klassen).index("Tomato___Late_blight")
gewichte = modell.coef_[idx]
top = np.argsort(gewichte)[-5:][::-1]
print("Stärkste Farb-Bins für Late Blight:", top)
# → die Bins lassen sich zu HSV-Bereichen zurückrechnen:
# bin // 64 = Hue-Bereich, (bin // 8) % 8 = Saturation, bin % 8 = ValueWarum eigentlich? — Warum max_iter=2000?
lbfgs iteriert maximal 100-mal. Bei 512 skalierten Features und 38 Klassen konvergiert er bis dahin oft nicht und wirft eine ConvergenceWarning. Die Gewichte sind dann ein willkürlicher Zwischenstand — das Modell funktioniert scheinbar, ist aber schlechter als es sein könnte. Mehr Iterationen erlauben kostet nur Zeit; die Warnung zu ignorieren kostet Qualität.Häufiger Denkfehler — Die Warnung wegfiltern statt beheben
warnings.filterwarnings("ignore") ist hier ein Klassiker. Eine ConvergenceWarning hat immer eine Ursache, meist eine von drei: zu wenige Iterationen (→ max_iter hoch), unskalierte Features (→ Scaler vergessen? Häufigster Fall!) oder fast-redundante Features (→ Regularisierung erhöhen, C senken). Die Warnung ist ein Diagnose-Werkzeug, kein Lärm.Tiefer rein — Sauberer: alles in eine sklearn-Pipeline
Scaler und Modell lassen sich zu einem Objekt koppeln — dann ist Leakage konstruktionsbedingt unmöglich, auch bei Cross-Validation:
make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000))
Bei cross_val_score wird der Scaler dann in jedem Fold nur auf dessen Trainings-Teil gefittet — genau das, was man von Hand leicht falsch macht. Für Abgaben und Paper-Code ist die Pipeline-Variante der Standard.