Search code examples
swiftcore-datacoreml

Save CoreML model Prediction MLMultiArray to Core Data


I want to save the MLMultiArray output generated from the CoreML model, to make it a persistent storage data.


let predictions = try model.prediction(from: input)

and the predictions is as below, as you can see, it's a MultiArray(Float32) type data. enter image description here

However, in Core Data, it does not support this type. I noticed there was a type named Transformable, but I don't know whether it could works.

enter image description here

So I wonder if there is a feasible way to persist this kind of data ?


Solution

  • Since MLMultiArray conforms to NSSecureCoding I was able to encode it to Data using NSKeyedArchiver, and decode using NSKeyedUnarchiver. The resulting Data instance could then be stored in Core Data or however else you might want to store it.

    Since I don't have your model, I just created an instance of MLMultiArray explicitly, and set some values.

    let predictions: MLMultiArray = try! .init(shape: [5, 5], dataType: .float32)
    
    let p = predictions.dataPointer.bindMemory(to: Float32.self, capacity: 25)
    for i in 0..<25 {
        p[i] = Float32(i)
    }
    

    I put the encoding/decoding code in functions, and made them generic, just in case you need it for other types:

    import Foundation
    
    func encode<T: NSSecureCoding>(_ value: T, secure: Bool = false) -> Data?
    {
        let archiver = NSKeyedArchiver(requiringSecureCoding: secure)
        predictions.encode(with: archiver)
        archiver.finishEncoding()
        return archiver.encodedData
    }
    
    func decode<T: NSSecureCoding>(_ data: Data, as type: T.Type) -> T?
    {
        guard let unarchiver = try? NSKeyedUnarchiver(forReadingFrom: data) else {
            return nil
        }
        
        return T.init(coder: unarchiver)
    }
    

    then to use it:

    guard let data = encode(predictions) else {
        fatalError("Encode failed")
    }
    
    // You can now save data to Core Data, or however else you want to persist it
    
    guard let recoveredPredictions = decode(data, as: MLMultiArray.self) else {
        fatalError("Decode failed")
    }
    

    I also wrote some code to test that it works:

    print(" original predictions: \(predictions)")
    print("recovered predictions: \(recoveredPredictions)")
    
    let r = recoveredPredictions.dataPointer
        .bindMemory(to: Float32.self, capacity: 25)
    for i in 0..<25
    {
        guard p[i] == r[i] else {
            fatalError("recoveredPredictions does not match predictions")
        }
    }
    
    print("Success")
    

    The output is

     original predictions: Float32 5 × 5 matrix
    [0,1,2,3,4;
     5,6,7,8,9;
     10,11,12,13,14;
     15,16,17,18,19;
     20,21,22,23,24]
    recovered predictions: Float32 5 × 5 matrix
    [0,1,2,3,4;
     5,6,7,8,9;
     10,11,12,13,14;
     15,16,17,18,19;
     20,21,22,23,24]
    Success