Kapitel 5

🦅Verwendung in Swift

Alle Beispiele sind vollständige Funktionen mit Kommentaren und nutzen nur APIs, deren Signatur und Mindestversion in der Apple-Doku belegt ist. Sie laufen nicht im Browser – Core ML gibt es nur auf Apple-Geräten. Modellname im Beispiel: das Lehrmodell DigitMLP (Eingang pixels, Ausgänge classLabel, classLabel_probs).

⌨️Grundlagen

Swift

Generierte Klasse benutzen

nicht im Browser ausführbar
import CoreML

/// Klassifiziert 64 Rohpixel (0…16) mit dem von Xcode generierten Wrapper.
func classify(rawPixels: [Float]) throws -> (label: String, probability: Double) {
    let config = MLModelConfiguration()
    config.computeUnits = .all                 // .cpuOnly · .cpuAndGPU · .cpuAndNeuralEngine

    // Xcode erzeugt aus DigitMLP.mlpackage: DigitMLP, DigitMLPInput, DigitMLPOutput
    let model = try DigitMLP(configuration: config)

    // Eingang [1, 64] als Float32 – so wurde konvertiert (dtype=np.float32)
    let pixels = try MLMultiArray(shape: [1, 64], dataType: .float32)
    for (i, value) in rawPixels.enumerated() {
        pixels[i] = NSNumber(value: value / 16)   // gleiche Vorverarbeitung wie beim Training
    }

    let output = try model.prediction(pixels: pixels)
    let label = output.classLabel                  // z. B. "7"
    let probability = output.classLabel_probs[label] ?? 0
    return (label, probability)
}
📱 Mindestens: iOS 12 · macOS 10.14 (Konfiguration), .cpuAndNeuralEngine ab iOS 16
ℹ️ Klassen- und Methodennamen leitet Xcode aus Modellname und Feature-Namen ab (DigitMLP, DigitMLPInput, DigitMLPOutput, prediction(pixels:)).

🧮Datentypen für Ein- und Ausgaben

TypabwofürBeleg
MLMultiArrayiOS 11 · macOS 10.13Klassischer n-dimensionaler Zahlen-Container für TensorType-Eingänge/-Ausgänge (Float32, Double, Int32; ab iOS 16 auch Float16).📎 MLMultiArray📎 Image Input and Output
MLShapedArray<Scalar>iOS 15 · macOS 12Typsicheres Swift-Gegenstück; lässt sich in MLMultiArray umwandeln.📎 MLShapedArray
CVPixelBuffer(Core Video)Bildeingang für ImageType-Modelle – spart die teure Kopie in ein MLMultiArray.📎 Image Input and Output
MLTensoriOS 18 · macOS 15Mehrdimensionales Array mit eigenen Rechenoperationen, die effizient auf einem ML-Rechengerät laufen.📎 MLTensor
MLStateiOS 18 · macOS 15Zustandspuffer für Stateful Models (z. B. KV-Cache), prediction(from:using:).📎 MLState📎 Stateful Models

👁️Mit Vision: klassisch und neu

Vision übernimmt Skalieren, Zuschneiden und Orientierung des Bildes. Voraussetzung: ein Modell mit Bildeingang.
Swift

Vision (klassisch): VNCoreMLRequest

nicht im Browser ausführbar
import CoreML
import Vision

func classifyWithVision(cgImage: CGImage) throws {
    let mobileNet = try MobileNetV2(configuration: MLModelConfiguration())
    let vnModel = try VNCoreMLModel(for: mobileNet.model)

    let request = VNCoreMLRequest(model: vnModel) { request, error in
        guard let results = request.results as? [VNClassificationObservation] else { return }
        for observation in results.prefix(3) {
            // Vision reicht die Konfidenzen des Modells unverändert durch
            print(observation.identifier, observation.confidence)
        }
    }
    request.imageCropAndScaleOption = .centerCrop   // Vision skaliert/schneidet selbst zu

    let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
    try handler.perform([request])
}
📱 Mindestens: iOS 11 · macOS 10.13
ℹ️ Vision braucht ein Modell mit Bildeingang – das 8×8-DigitMLP (MultiArray) würde VNCoreMLModel(for:) ablehnen. Beispiel daher mit MobileNetV2 aus Apples Modellseite.
Swift

Vision (Swift-API ab iOS 18): CoreMLRequest

nicht im Browser ausführbar
import CoreML
import Vision

func topLabels(imageData: Data) async throws -> [(String, Float)] {
    let mobileNet = try MobileNetV2(configuration: MLModelConfiguration())
    let container = try CoreMLModelContainer(model: mobileNet.model)

    var request = CoreMLRequest(model: container)
    request.cropAndScaleAction = .centerCrop

    // perform(on:) gibt es für CGImage, CIImage, CVPixelBuffer, CMSampleBuffer, URL und Data
    let observations = try await request.perform(on: imageData, orientation: nil)
    return observations
        .compactMap { $0 as? ClassificationObservation }
        .sorted { $0.confidence > $1.confidence }
        .prefix(3)
        .map { ($0.identifier, $0.confidence) }
}
VNCoreMLRequestCoreMLRequest
verfügbar abiOS 11 · macOS 10.13iOS 18 · macOS 15
Modell-HülleVNCoreMLModel(for:)CoreMLModelContainer(model:featureProvider:)
AusführenVNImageRequestHandler.perform([request]) + Completion-Handlertry await request.perform(on:orientation:)
ZuschneidenimageCropAndScaleOption (.centerCrop, .scaleFit, .scaleFill …)cropAndScaleAction (.centerCrop, .scaleToFit, .scaleToFill …)
KlassifikationVNClassificationObservationClassificationObservation

Vision behandelt ein Modell als Klassifikator, wenn predictedFeatureName gesetzt ist; Konfidenzen werden unverändert durchgereicht (nicht auf 0…1 normalisiert).📎 VNCoreMLRequest📎 CoreMLRequest (Swift-Vision-API)📎 CoreMLModelContainer📎 ClassificationObservation📎 VNClassificationObservation

🌐Modelle zur Laufzeit laden und aktualisieren

  Server / CloudKit              Gerät
  ─────────────────              ─────────────────────────────────────────────────────────
  DigitMLP.mlmodel  ──download──▶ tmp/DigitMLP.mlmodel
                                    │ MLModel.compileModel(at:)   (async ab iOS 16)
                                    ▼
                                  tmp/…/DigitMLP.mlmodelc   (temporär!)
                                    │ FileManager.replaceItemAt
                                    ▼
                                  Application Support/DigitMLP.mlmodelc  (Cache, Backup bedenken)
                                    │ MLModel.load(contentsOf:configuration:)
                                    ▼
                                  MLModel  →  prediction(from:)
Swift

Modell zur Laufzeit laden, kompilieren, zwischenspeichern

nicht im Browser ausführbar
import CoreML

/// Lädt ein .mlmodel herunter, kompiliert es einmalig und cached das .mlmodelc.
func loadRemoteModel(from remoteURL: URL) async throws -> MLModel {
    let fm = FileManager.default
    let support = try fm.url(for: .applicationSupportDirectory, in: .userDomainMask,
                             appropriateFor: nil, create: true)
    let permanentURL = support.appendingPathComponent("DigitMLP.mlmodelc")

    if !fm.fileExists(atPath: permanentURL.path) {
        // 1. Herunterladen (URLSession, CloudKit …)
        let (tempFile, _) = try await URLSession.shared.download(from: remoteURL)
        let modelFile = fm.temporaryDirectory.appendingPathComponent("DigitMLP.mlmodel")
        try? fm.removeItem(at: modelFile)
        try fm.moveItem(at: tempFile, to: modelFile)

        // 2. Auf dem Gerät kompilieren – Ergebnis liegt zunächst an einem temporären Ort
        let compiledURL = try await MLModel.compileModel(at: modelFile)

        // 3. Dauerhaft ablegen, damit nicht bei jedem Start neu kompiliert wird
        _ = try fm.replaceItemAt(permanentURL, withItemAt: compiledURL)
    }
    return try await MLModel.load(contentsOf: permanentURL, configuration: MLModelConfiguration())
}
⚠️
On-Device-Training: Status
MLUpdateTask (ab iOS 13) ist in der Apple-Doku weiterhin nicht als veraltet markiert, funktioniert aber nur mit als „updatable“ markierten NeuralNetwork-Modellen und einem kompilierten .mlmodelc. ML Programs unterstützen On-Device-Update laut coremltools-Guide nicht. 📎 MLUpdateTask📎 Personalizing a Model with On-Device…📎 Comparing ML Programs and Neural Net…
💡
Model Deployment (MLModelCollection)
Der frühere Weg, Modelle über Apples Core-ML-Model-Deployment zu verteilen (MLModelCollection, ab iOS 14), ist seit iOS 17.4 als veraltet markiert – eigener Download wie oben ist der dokumentierte Weg. 📎 MLModelCollection (Core ML Model Dep…📎 Downloading and Compiling a Model on…

📱Komplettes SwiftUI-Beispiel

Bild aus der Fotomediathek wählen → mit Vision + Core ML klassifizieren → Top 3 anzeigen.
Swift

SwiftUI: Bild auswählen → klassifizieren

nicht im Browser ausführbar
import SwiftUI
import PhotosUI
import CoreML
import Vision

struct Prediction: Identifiable {
    let id = UUID()
    let label: String
    let confidence: Float
}

@MainActor
final class ImageClassifier: ObservableObject {
    @Published var predictions: [Prediction] = []
    @Published var errorText: String?
    private var request: CoreMLRequest?

    init() {
        do {
            let config = MLModelConfiguration()
            config.computeUnits = .all
            let model = try MobileNetV2(configuration: config).model
            var request = CoreMLRequest(model: try CoreMLModelContainer(model: model))
            request.cropAndScaleAction = .centerCrop
            self.request = request
        } catch {
            errorText = error.localizedDescription
        }
    }

    func classify(_ data: Data) async {
        guard let request else { return }
        do {
            let observations = try await request.perform(on: data, orientation: nil)
            predictions = observations
                .compactMap { $0 as? ClassificationObservation }
                .sorted { $0.confidence > $1.confidence }
                .prefix(3)
                .map { Prediction(label: $0.identifier, confidence: $0.confidence) }
        } catch {
            errorText = error.localizedDescription
        }
    }
}

struct ContentView: View {
    @StateObject private var classifier = ImageClassifier()
    @State private var selection: PhotosPickerItem?
    @State private var image: UIImage?

    var body: some View {
        VStack(spacing: 16) {
            PhotosPicker("Bild auswählen", selection: $selection, matching: .images)
                .buttonStyle(.borderedProminent)

            if let image {
                Image(uiImage: image).resizable().scaledToFit().frame(maxHeight: 260)
            }
            ForEach(classifier.predictions) { p in
                HStack {
                    Text(p.label)
                    Spacer()
                    Text(p.confidence, format: .percent.precision(.fractionLength(1)))
                }
            }
            if let errorText = classifier.errorText {
                Text(errorText).foregroundStyle(.red)
            }
        }
        .padding()
        .onChange(of: selection) { _, item in
            Task {
                guard let data = try? await item?.loadTransferable(type: Data.self) else { return }
                image = UIImage(data: data)
                await classifier.classify(data)
            }
        }
    }
}
📱 Mindestens: iOS 18 (CoreMLRequest), PhotosPicker ab iOS 16
ℹ️ Modell MobileNetV2 von Apples Modellseite ins Projekt ziehen; Xcode erzeugt die Klasse MobileNetV2.