Transfer Learning statt Training von Grund auf: ein fertig vortrainiertes Modell wird auf deine Pizza-Aufgabe angepasst. Ca. 45 Minuten.
ResNet18 hat 18 Schichten und ~11,2M auf ImageNet vortrainierte Parameter. Nur der neue Klassifikationskopf wird auf deinen Daten trainiert:
Pretrained Backbone (Layers 1-17) New Classifier Head +-----------------------------------------+ +---------------------+ | | | | | Layer 1-4: Low-level features | | Linear: 512 -> 256 | | (edges, colors, textures) | | ReLU | | | | Dropout(0.3) | | Layer 5-10: Mid-level features | | Linear: 256 -> 1 | | (shapes, patterns) | | Sigmoid | | | | | | Layer 11-17: High-level features | | Output: | | (objects, scenes) | | 0 = not pizza | | | | 1 = pizza | +-----------------------------------------+ +---------------------+ Frozen (pretrained on ImageNet) Trained on your data
Bei jedem neuen Terminal: diese zwei Zeilen zuerst (Bash, WSL2/Linux).
cd ~/projects/bpm-pizza-ml source .venv/bin/activate
Der Check prüft Abhängigkeiten, data/v1, data/v2 und test_images/:
python3 check_environment.py
Die Ausgabe endet mit:
All dependencies and data are present. You are ready to start.
Neu in WSL? Kurze Einführung („Was ist WSL und wie starte ich es?“): siehe Aufgabe 1
Vier ResNet18-Modelle, EIN Block: ein Trainingslauf pro Zeile (v1 oder v2, 10 oder 50 Epochs):
python3 train-pizza-finetuning.py --data-dir ./data/v1 --epochs 10 --output pizza_resnet18_v1_10.pth python3 train-pizza-finetuning.py --data-dir ./data/v1 --epochs 50 --output pizza_resnet18_v1_50.pth python3 train-pizza-finetuning.py --data-dir ./data/v2 --epochs 10 --output pizza_resnet18_v2_10.pth python3 train-pizza-finetuning.py --data-dir ./data/v2 --epochs 50 --output pizza_resnet18_v2_50.pth
So sieht die Ausgabe eines einzelnen Trainingslaufs typischerweise aus (Beispiel, gekürzt):
Loading ResNet18 (pretrained=True)... Trainable parameters: N,NNN (0.NNM) Starting training for 10 epochs... ====================================================================== Epoch 1/10 | Train Loss: 0.NNNN, Acc: NN.NN% | Test Loss: 0.NNNN, Acc: NN.NN% -> Saved best model (acc: NN.NN%) ... Epoch 10/10 | Train Loss: 0.NNNN, Acc: NN.NN% | Test Loss: 0.NNNN, Acc: NN.NN% ====================================================================== Training complete! Best accuracy: NN.NN% Model saved to: pizza_resnet18_v1_10.pth
Lesehilfe: Links Train Loss und Acc auf den Trainingsbildern, rechts Test Loss und Acc auf zurückgehaltenen Testbildern. Nur die rechte zählt — sie steht am Ende als Best accuracy, und diese Zahl trägst du unten ein. Trainable parameters ist klein: nur der neue Kopf wird trainiert. Beim ersten Lauf lädt torchvision die ResNet18-Gewichte einmalig (~44 MB).
Notiere deine Trainingsergebnisse:
| Modell | Daten | Epochs | Trainingsdauer | Testgenauigkeit |
|---|---|---|---|---|
pizza_resnet18_v1_10.pth |
v1 | 10 | ___ | ___% |
pizza_resnet18_v1_50.pth |
v1 | 50 | ___ | ___% |
pizza_resnet18_v2_10.pth |
v2 | 10 | ___ | ___% |
pizza_resnet18_v2_50.pth |
v2 | 50 | ___ | ___% |
EIN Block statt 16 Befehlen: pro Testbild eine Tabelle mit allen vier Modellen untereinander.
if [ -t 1 ]; then GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m' else GREEN=""; RED=""; RESET=""; fi declare -A EXPECTED=( [pizza_margherita]="PIZZA" [pizza_supreme]="PIZZA" [ice]="NOT PIZZA" [flammkuchen]="NOT PIZZA" ) for img in pizza_margherita pizza_supreme ice flammkuchen; do echo "=======================================================" echo "Image: $img.jpg (expected: ${EXPECTED[$img]})" echo "=======================================================" printf '%-26s %-12s %-12s %s\n' "MODEL" "RESULT" "CONFIDENCE" "STATUS" printf '%.0s-' {1..64}; echo for model in pizza_resnet18_v1_10 pizza_resnet18_v1_50 \ pizza_resnet18_v2_10 pizza_resnet18_v2_50; do out=$(python3 train-pizza-finetuning.py --predict test_images/$img.jpg --output $model.pth) result=$(echo "$out" | grep -i '^Result:' | sed 's/^Result: *//') conf=$( echo "$out" | grep -i '^Confidence:' | sed 's/^Confidence: *//') if [ -z "$result" ]; then result="-"; conf="-" status=$(printf '%-6s' "ERROR"); color=$RED elif [ "$result" = "${EXPECTED[$img]}" ]; then status=$(printf '%-6s' "OK"); color=$GREEN else status=$(printf '%-6s' "WRONG"); color=$RED fi printf '%-26s %-12s %-12s %s%s%s\n' \ "$model" "$result" "$conf" "$color" "$status" "$RESET" done echo done
flammkuchen.jpg sieht Pizza sehr ähnlich. Achte darauf, welche Modelle sich täuschen lassen.
RESULT, NN.NN% und OK|WRONG stehen für deine eigenen Werte:
======================================================= Image: pizza_margherita.jpg (expected: PIZZA) ======================================================= MODEL RESULT CONFIDENCE STATUS ---------------------------------------------------------------- pizza_resnet18_v1_10 RESULT NN.NN% OK|WRONG pizza_resnet18_v1_50 RESULT NN.NN% OK|WRONG pizza_resnet18_v2_10 RESULT NN.NN% OK|WRONG pizza_resnet18_v2_50 RESULT NN.NN% OK|WRONG
Lesehilfe: RESULT ist das Urteil, CONFIDENCE die Konfidenz in genau dieses Urteil — daher immer mindestens 50 Prozent; Werte knapp darüber heißen: das Modell rät fast. STATUS vergleicht das Urteil mit dem erwarteten Label aus der Kopfzeile: OK grün, WRONG rot. Grün heißt „richtig“, nicht „Pizza“ — ein korrektes NOT PIZZA bei ice.jpg ist grün. ERROR heißt: Vorhersage fehlgeschlagen.
Woher die Werte kommen: jede Vorhersage druckt diese zwei Zeilen.
Loading ResNet18 model from pizza_resnet18_v1_10.pth... Result: PIZZA Confidence: NN.NN%
Du willst sehen, was klassifiziert wird? Testbilder & Trainingsdaten: Galerie in Aufgabe 1
Ein fertiges Skript macht alles auf einmal:
./run_all_tests_finetuning.sh
Trainieren, testen, Vergleichstabelle — hier maskiert:
============================================================ Running Inference ============================================================ --- v1_10 --- pizza_margherita: RESULT NN.NN% [OK|WRONG] pizza_supreme: RESULT NN.NN% [OK|WRONG] ice: RESULT NN.NN% [OK|WRONG] flammkuchen: RESULT NN.NN% [OK|WRONG] --- v1_50 --- ... ============================================================ COMPARISON TABLE ============================================================ Image | v1+10ep | v1+50ep | v2+10ep | v2+50ep ---------------------+------------------------+------------------------+------------------------+----------------------- pizza_margherita | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) pizza_supreme | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) ice | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) flammkuchen | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%) | RESULT (NN.NN%)
RESULT und NN.NN oben sind Platzhalter — der Lauf trägt deine eigenen Werte ein.
Trage deine Ergebnisse in die folgende Tabelle ein:
| Metrik | v1+10 Ep. | v1+50 Ep. | v2+10 Ep. | v2+50 Ep. |
|---|---|---|---|---|
| Testgenauigkeit | ___% | ___% | ___% | ___% |
| pizza_margherita.jpg | ___ | ___ | ___ | ___ |
| pizza_supreme.jpg | ___ | ___ | ___ | ___ |
| ice.jpg | ___ | ___ | ___ | ___ |
| flammkuchen.jpg | ___ | ___ | ___ | ___ |
🖨️ Leere Vergleichstabelle zum Ausdrucken
Kombiniere deine Ergebnisse aus Aufgabe 1 (TinyVGG, von Grund auf) und Aufgabe 2 (ResNet18, Fine-Tuning):
| Metrik | TinyVGG (Aufgabe 1) | ResNet18 (Aufgabe 2) | ||||||
|---|---|---|---|---|---|---|---|---|
| v1+10 | v1+50 | v2+10 | v2+50 | v1+10 | v1+50 | v2+10 | v2+50 | |
| Architektur | TinyVGG (~8,6M) | ResNet18 (~11,2M) | ||||||
| Ansatz | Von Grund auf | Fine-Tuning | ||||||
| Daten | v1 | v1 | v2 | v2 | v1 | v1 | v2 | v2 |
| Epochs | 10 | 50 | 10 | 50 | 10 | 50 | 10 | 50 |
| Testgenauigkeit | ___% | ___% | ___% | ___% | ___% | ___% | ___% | ___% |
| pizza_margherita.jpg | ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
| pizza_supreme.jpg | ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
| ice.jpg | ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
| flammkuchen.jpg | ___ | ___ | ___ | ___ | ___ | ___ | ___ | ___ |
TinyVGG-Referenzwerte stehen nur dort: Zur Lösung in Aufgabe 1
| ResNet18 | ||||
|---|---|---|---|---|
| v1+10 | v1+50 | v2+10 | v2+50 | |
| Testgenauigkeit | 85.60% | 84.80% | 95.00% | 95.40% |
| ResNet18 | ||||
|---|---|---|---|---|
| v1+10 | v1+50 | v2+10 | v2+50 | |
| pizza_margherita.jpg | PIZZA 76% | PIZZA 78% | PIZZA 98% | PIZZA 100% |
| pizza_supreme.jpg | PIZZA 78% | PIZZA 77% | PIZZA 71% | PIZZA 94% |
| ice.jpg | NOT PIZZA 84% | NOT PIZZA 67% | NOT PIZZA 100% | NOT PIZZA 100% |
| flammkuchen.jpg | PIZZA 61% | PIZZA 74% | NOT PIZZA 74% | PIZZA 64% |
| Modell | Erwartete Genauigkeit |
|---|---|
| ResNet18 v1+10 | ~80–85% |
| ResNet18 v1+50 | ~82–88% |
| ResNet18 v2+10 | ~92–96% |
| ResNet18 v2+50 | ~94–98% |
| Modell | TinyVGG (Aufgabe 1) | ResNet18 (Aufgabe 2) |
|---|---|---|
| v1+10 (unsauber, wenig Training) | ~65–70% | ~80–85% |
| v1+50 (unsauber, mehr Training) | ~65–75% | ~82–88% |
| v2+10 (sauber, wenig Training) | ~75–80% | ~92–96% |
| v2+50 (sauber, mehr Training) | ~80–90% | ~94–98% |
Bereits mit 10 Epochs und sauberen Daten erreicht ResNet18 (~92–96%) bessere Ergebnisse als TinyVGG mit 50 Epochs (~80–90%). Transfer Learning kompensiert fehlende Trainingszeit massiv.
Selbst mit unsauberen Daten schlägt ResNet18 das TinyVGG mit sauberen Daten. Aber der Sprung von v1 zu v2 ist bei beiden Modellen signifikant. Transfer Learning macht schlechte Daten nicht irrelevant — es verschiebt nur die Baseline nach oben.