Kapitel 7
🧪Simulation im Browser
Core ML läuft nicht im Browser. Hier rechnet deshalb eine TypeScript-Nachbildung desselben Mini-Modells – mit den echten Gewichten und den echten Kompressionsdaten aus coremltools. Wo möglich, steht die echte Core-ML-Vorhersage daneben.
✏️Ziffern-Labor
Ziffer zeichnen (Maus oder Finger) oder ein Testbild wählen. Jede Karte zeigt die Vorhersage einer Variante; Klick auf eine Karte zeigt ihre Vorwärtsrechnung.
✏️ 8×8-Eingabe
Testbild #1 (Label 7)Pinsel:
Testbilder aus load_digits (nicht trainiert):
🧪 Simulation im Browser✔ echte coremltools-Ausgabe
🔬 Vorwärtsrechnung Schritt für Schritt – Int4 linear
Namen wie die Ops im echten MIL-Programm. Blau = positiv, Rot = negativ (Farbstärke relativ zum größten Betrag der Zeile).
pixelsEingang [1, 64] · Rohwert/16
linear_0fc1: W₁·x + b₁ → [1, 32]
relunegative Werte → 0
linear_1fc2: W₂·h + b₂ → [1, 10] (Logits)
softmaxWahrscheinlichkeiten, Summe 1
🧪
Was hier wirklich passiert
Die Browser-Rechnung nutzt dieselben Gewichte wie das echte Modell. Int8, Int4 und Pruning werden hier bitgenau wie coremltools nachgerechnet, die 4-Bit-Palette verwendet die echte Lookup-Tabelle aus der Konvertierung. FP16 ist eine Nachbildung (alle Werte auf Float16 gerundet) – nicht bitgenau zur Core-ML-Laufzeit, aber in den Tests auf ≤ 2·10⁻³ an den echten Core-ML-Ausgaben. Die „Core ML echt“-Werte stammen aus MLModel.predict auf macOS (CPU_ONLY).🕸️So sieht der MIL-Graph wirklich aus
Keine Handzeichnung: Op-Listen und model.mil stammen direkt aus der Konvertierung mit coremltools 9.0.
✔ echte coremltools-Ausgabe
- castcast_1→ fp16 [1, 64]x = pixels · dtype = pixels_to_fp16_dtype_0
- linearlinear_0_cast_fp16→ fp16 [1, 32]x = pixels_to_fp16 · weight = m_fc1_weight_to_fp16 · bias = m_fc1_bias_to_fp16
- reluinput_cast_fp16→ fp16 [1, 32]x = linear_0_cast_fp16
- linearlinear_1_cast_fp16→ fp16 [1, 10]x = input_cast_fp16 · weight = m_fc2_weight_to_fp16 · bias = m_fc2_bias_to_fp16
- softmaxop_14_cast_fp16→ fp16 [1, 10]x = linear_1_cast_fp16 · axis = var_12
- castvar_14_cast_to_fp32→ fp32 [1, 10]x = var_14 · dtype = var_14_cast_to_fp32_dtype_0
- classifyclassify_0→ string (Skalar), dict (Skalar)probabilities = var_14_cast_to_fp32 · classes = const_0
📋Steckbrief des Lehrmodells
| Architektur | Linear(64→32) → ReLU → Linear(32→10) → Softmax |
| Parameter | 2.410 (fc1 2 048 + 32, fc2 320 + 10) |
| Daten | scikit-learn load_digits (UCI Optical Recognition of Handwritten Digits, 8×8, Werte 0–16) |
| Aufteilung | 1437 Training / 360 Test – train_test_split(test_size=0.2, random_state=0, stratify=y) |
| Training | torch.manual_seed(0), Adam lr=0.01, 300 Epochen Full-Batch, Cross-Entropy |
| Genauigkeit | Training 100,0 %, Test 96,7 % |
| Werkzeuge | coremltools 9.0, torch 2.7.0, numpy 2.2.6, Python 3.12.14, macOS 27.0 |
🧪
Wie genau ist die Browser-Rechnung?
- FP32: Abweichung zu PyTorch und Core ML ≤ 10⁻⁶ (Test).
- Int8 / Int4 / Pruning: Ganzzahlen, Skalen und Gewichte bitgenau wie coremltools (Test), Ausgaben ≤ 10⁻⁵ zu Core ML.
- Palette 4 Bit: echte LUT + Indizes aus dem Modell, Ausgaben ≤ 10⁻⁵ zu Core ML.
- FP16: Nachbildung, ≤ 2·10⁻³ zu Core ML (CPU); die Laufzeit auf GPU/Neural Engine kann anders runden.
💡
Nachvollziehbar
Das Erzeugungsskript liegt im Repo unter scripts/golden/gen_golden.py, die Rohdaten unter tests/golden/. Die Tests vergleichen die Browser-Rechnung mit diesen Dateien. Ein erneuter Lauf des Skripts lieferte bitgleiche Ergebnisse – bis auf die k-means-Lookup-Tabelle der Palettisierung (Abweichung ≈ 10⁻⁷, k-means ist nicht streng deterministisch).