Section 11/161 menit

11. On-Device Model Personalization

11. On-Device Model Personalization

Core ML mendukung on-device fine-tuning — model bisa di-update dengan data pengguna tanpa data meninggalkan device.

Updatable Model dengan Core ML

swift
import CoreML

class PersonalizedClassifier {
    private var modelURL: URL
    private var updatableModel: MLUpdateTask?
    
    init() throws {
        // Model harus di-mark sebagai "updatable" di coremltools
        guard let url = Bundle.main.url(forResource: "UpdatableClassifier", withExtension: "mlmodelc") else {
            throw ModelError.notFound
        }
        // Copy ke Documents karena Bundle tidak writable
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        modelURL = documentsURL.appendingPathComponent("UpdatableClassifier.mlmodelc")
        
        if !FileManager.default.fileExists(atPath: modelURL.path) {
            try FileManager.default.copyItem(at: url, to: modelURL)
        }
    }
    
    // Fine-tune dengan data baru dari pengguna
    func train(withSamples samples: [(image: CGImage, label: String)]) async throws {
        var trainingData: [String: [MLFeatureValue]] = [
            "image": [],
            "label": []
        ]
        
        for sample in samples {
            let buffer = try MLFeatureValue(cgImage: sample.image, pixelsWide: 224, pixelsHigh: 224,
                                            pixelFormatType: kCVPixelFormatType_32BGRA, options: nil)
            trainingData["image"]!.append(buffer)
            trainingData["label"]!.append(MLFeatureValue(string: sample.label))
        }
        
        let trainingBatch = try MLArrayBatchProvider(
            dictionary: trainingData.mapValues { $0 }
        )
        
        let updateConfig = MLUpdateProgressHandlers(
            forEvents: [.trainingBegin, .epochEnd, .trainingEnd],
            progressHandler: { context in
                if context.event == .epochEnd {
                    print("Epoch \(context.metrics[.epochIndex]!): loss = \(context.metrics[.lossValue]!)")
                }
            },
            completionHandler: { [weak self] context in
                if context.task.error == nil {
                    // Simpan model yang sudah di-update
                    try? context.model.write(to: self!.modelURL)
                    print("Model updated successfully")
                }
            }
        )
        
        let updateTask = try MLUpdateTask(
            forModelAt: modelURL,
            trainingData: trainingBatch,
            configuration: nil,
            progressHandlers: updateConfig
        )
        
        updateTask.resume()
    }
}