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ührbarimport 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
| Typ | ab | wofür | Beleg |
|---|---|---|---|
| MLMultiArray | iOS 11 · macOS 10.13 | Klassischer 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 12 | Typsicheres 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 |
| MLTensor | iOS 18 · macOS 15 | Mehrdimensionales Array mit eigenen Rechenoperationen, die effizient auf einem ML-Rechengerät laufen. | 📎 MLTensor |
| MLState | iOS 18 · macOS 15 | Zustandspuffer 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ührbarimport 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ührbarimport 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) }
}📱 Mindestens: iOS 18 · macOS 15
| VNCoreMLRequest | CoreMLRequest | |
|---|---|---|
| verfügbar ab | iOS 11 · macOS 10.13 | iOS 18 · macOS 15 |
| Modell-Hülle | VNCoreMLModel(for:) | CoreMLModelContainer(model:featureProvider:) |
| Ausführen | VNImageRequestHandler.perform([request]) + Completion-Handler | try await request.perform(on:orientation:) |
| Zuschneiden | imageCropAndScaleOption (.centerCrop, .scaleFit, .scaleFill …) | cropAndScaleAction (.centerCrop, .scaleToFit, .scaleToFill …) |
| Klassifikation | VNClassificationObservation | ClassificationObservation |
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ührbarimport 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())
}📱 Mindestens: compileModel async ab iOS 16 · macOS 13
⚠️
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ührbarimport 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.