Kapitel 4

📥Import & andere Richtungen

PyTorch ist nicht der einzige Weg zu Core ML – und nicht jeder Weg führt zurück. Ein Überblick mit den belegten Möglichkeiten und ihren Grenzen.

🧭Woher ein Core-ML-Modell kommen kann

🔶 TensorFlow / Keras

Direkt über ct.convert: tf.keras.Model, SavedModel, HDF5 (TF 2) bzw. eingefrorene Graphen (TF 1).

🌳 scikit-learn

Pipelines, Klassifikatoren und Regressoren über ct.converters.sklearn.convert – ergibt ein klassisches .mlmodel, kein ML Program.

✔ echte coremltools-Ausgabe DecisionTreeClassifier(max_depth=8, random_state=0) → Modelltyp pipelineClassifier, 9.337 B, Ausgänge ziffer, classProbability

🚀 XGBoost · LIBSVM

Baum-Ensembles mit ct.converters.xgboost.convert, SVMs aus LIBSVM – laut Übersicht von coremltools unterstützt.

🍏 Create ML

Trainiert auf dem Mac eigene Modelle – etwa zum Erkennen von Bildern, zum Auswerten von Text oder für Zusammenhänge in Zahlen – mit Swift oder der Create-ML-App, die zu Xcode gehört. Das Ergebnis ist direkt ein Core-ML-Modell – ganz ohne Konvertierung.

📦 Fertige Modelle

Apple bietet fertig konvertierte Modelle an (u. a. FastViT, MobileNetV2, ResNet-50, Depth Anything V2, DETR, DeepLabV3, BERT-SQuAD, YOLOv3). Auf Hugging Face veröffentlicht die Organisation apple weitere, z. B. apple/coreml-FastViT-MA36 oder apple/coreml-stable-diffusion-v1-5.

⛔ ONNX

  1. coremltools 3.x PyTorch-Modelle über ONNX: erst nach ONNX exportieren, dann den Konverter onnx-coreml aufrufen. 📎 FAQs
  2. coremltools 4 Unified Conversion API: PyTorch direkt, ohne ONNX. onnx-coreml ist eingefroren und wird nicht mehr gepflegt. 📎 FAQs
  3. coremltools 6.0 Release Notes: „Remove ONNX support.“ – der ONNX-Konverter ist entfernt. 📎 coremltools 6.0 Release Notes
  4. heute (9.0) Quellformate: TensorFlow 1/2, PyTorch (TorchScript, ExportedProgram) sowie Bäume/lineare Modelle/SVMs. ONNX-Modelle zuerst ins Ursprungsframework zurückholen. 📎 Source and Conversion Formats📎 What Is Core ML Tools?

🛠️Das .mlpackage in Xcode

Modell in den Projektnavigator ziehen – Xcode zeigt Metadaten, Ein-/Ausgänge, Vorschau und Performance und erzeugt Swift-Klassen.
DigitMLP.mlpackage  ──(Xcode-Build)──▶  DigitMLP.mlmodelc  (im App-Bundle)
        │
        └──(Codegenerierung)──▶  class DigitMLP        ← Modell
                                 class DigitMLPInput   ← Eingänge (pixels)
                                 class DigitMLPOutput  ← Ausgänge (classLabel, classLabel_probs)

Der Klassenname folgt dem Dateinamen: in der echten metadata.json unseres (als DigitMLP_fp16.mlpackage gespeicherten) Modells steht generatedClassName: "DigitMLP_fp16".

✔ echte coremltools-Ausgabe
Auszug aus DigitMLP.mlmodelc/metadata.json
{
 "generatedClassName": "DigitMLP_fp16",
 "availability": {
  "macOS": "14.0",
  "tvOS": "17.0",
  "visionOS": "1.0",
  "watchOS": "10.0",
  "iOS": "17.0",
  "macCatalyst": "17.0"
 },
 "storagePrecision": "Float16",
 "computePrecision": "Mixed (Float16, Float32, Float64, Int32)",
 "isUpdatable": "0",
 "mlProgramOperationTypeHistogram": {
  "Ios17.cast": 2,
  "Ios17.linear": 2,
  "Ios16.relu": 1,
  "Ios16.softmax": 1
 },
 "outputSchema": [
  {
   "name": "classLabel",
   "formattedType": "String"
  },
  {
   "name": "classLabel_probs",
   "formattedType": "Dictionary (String → Double)"
  }
 ]
}

Das Op-Histogramm zeigt die Opset-Versionen (z. B. Ios17.linear, Ios16.relu); classLabel_probs ist ein Wörterbuch String → Double.

🐍Vor dem Einbau: in Python testen

Python

In Python testen und mit PyTorch vergleichen (nur macOS)

✔ echte coremltools-Ausgabe
x = np.random.rand(1, 64).astype(np.float32)
m = ct.models.MLModel("DigitMLP.mlpackage", compute_units=ct.ComputeUnit.CPU_ONLY)
out = m.predict({"pixels": x})
print(out["classLabel"], out["classLabel_probs"])

with torch.no_grad():
    ref = model(torch.from_numpy(x)).numpy()[0]
probs = np.array([out["classLabel_probs"][str(i)] for i in range(10)])
np.testing.assert_allclose(probs, ref, atol=1e-3)   # FP16-Modell: ~1e-3, FP32-Modell: ~1e-6
⚠️
predict() nur auf macOS
coremltools ruft für Vorhersagen das Core-ML-Framework auf – das gibt es nur unter macOS. Konvertieren und Optimieren geht auch unter Linux. 📎 Model Prediction
Python

Andere Quellen: TensorFlow/Keras, scikit-learn, XGBoost

# TensorFlow 2 / Keras: tf.keras.Model direkt übergeben
mlmodel = ct.convert(keras_model, convert_to="mlprogram")

# scikit-learn (Baum, lineare Modelle, SVM, Pipelines …) → .mlmodel
tree_ml = ct.converters.sklearn.convert(tree, [f"p{i}" for i in range(64)], "ziffer")
tree_ml.save("DigitTree.mlmodel")

# XGBoost
xgb_ml = ct.converters.xgboost.convert(booster)
ℹ️ scikit-learn-Zeile so ausgeführt (DecisionTreeClassifier, max_depth=8).

↩️Und zurück zu PyTorch?

Kurz: nein. Weder coremltools noch PyTorch dokumentieren einen Konverter von Core ML nach PyTorch.

Warum der Rückweg fehlt

  • Die Konvertierung ist verlustbehaftet: Python-Code, Modulstruktur, Trainingslogik, Optimizer-Zustand und Autograd existieren im MIL-Programm nicht mehr – nur Rechen-Ops und Konstanten.
  • Der Graph wird beim Konvertieren vereinfacht (Ops zusammengefasst, Typen gecastet); aus linear + const lässt sich nicht eindeutig sagen, welche PyTorch-Module es waren.
  • Nach FP16 oder Kompression sind die Originalgewichte nicht wiederherstellbar – nur ihre Näherung.

Was geht: Spec lesen (get_spec()), Ops auflisten, Gewichte auslesen – und damit ein von Hand nachgebautes PyTorch-Modell befüllen. Die Quelle bleibt also am besten das PyTorch-Modell selbst.

Python

Gewichte aus einem Core-ML-Modell auslesen

✔ echte coremltools-Ausgabe
m = ct.models.MLModel("DigitMLP_fp32.mlpackage")
spec = m.get_spec()
print(spec.WhichOneof("Type"))          # 'mlProgram'
print(spec.description.input)           # Eingangs-Beschreibung (Protobuf)

meta = cto.coreml.get_weights_metadata(m, weight_threshold=256)
for name, w in meta.items():
    print(name, w.val.shape, [op.name for op in w.child_ops])
# m_fc1_weight (32, 64) ['linear_0']
# m_fc2_weight (10, 32) ['linear_1']
✔ echte coremltools-Ausgabe

Aus dem FP32-Paket zurückgelesen: m_fc1_weight 32×64, m_fc2_weight 10×32 – maximale Abweichung von fc1 zur PyTorch-Matrix: 0. Beim FP16-Modell wären es die auf Float16 gerundeten Werte.