Creating an MLFeatureProvider class in iOS for an MLModel

Most examples, including within documentation, of using CoreML with iOS involve the creation of the Model under Xcode on a Mac and then inclusion of the Xcode generated MLFeatureProvider class into the iOS app and (re)compiling the app.  However, it’s also possible to download an uncompiled model directly into an iOS app  and then compile it (background tasks) - but there’s no MLFeatureProvider class.  The same applies when using CreateML in an iOS app (iOS 15 beta) - there’s no automatically generated MLFeatureProvider.  So how do you get one?  I’ve seen a few queries on here and elsewhere related to this problem, but couldn’t find any clear examples of a solution.  So after some experimentation, here’s my take on how to go about it:

Firstly, if you don’t know what features the Model uses, print the model description e.g. print("Model: ",mlModel!.modelDescription). Which gives Model:  

inputs: (

    "course : String",

    "lapDistance : Double",

    "cumTime : Double",

    "distance : Double",

    "lapNumber : Double",

    "cumDistance : Double",

    "lapTime : Double"

)

outputs: (

    "duration : Double"

)

predictedFeatureName: duration

............

A prediction is created by guard **let durationOutput = try? mlModel!.prediction(from: runFeatures) ** …… where runFeatures is an instance of a class that provides a set of feature names and the value of each feature to be used in making a prediction.  So, for my model that predicts run duration from course, lap number, lap time etc the RunFeatures class is:

class RunFeatures : MLFeatureProvider {

    var featureNames: Set = ["course","distance","lapNumber","lapDistance","cumDistance","lapTime","cumTime","duration"]

    var course : String = "n/a"

    var distance : Double = -0.0

    var lapNumber : Double = -0.0

    var lapDistance : Double = -0.0

    var cumDistance : Double = -0.0

    var lapTime : Double = -0.0

    var cumTime : Double = -0.0

    

    func featureValue(for featureName: String) -> MLFeatureValue? {

        switch featureName {

        case "distance":

            return MLFeatureValue(double: distance)

        case "lapNumber":

            return MLFeatureValue(double: lapNumber)

        case "lapDistance":

            return MLFeatureValue(double: lapDistance)

        case "cumDistance":

            return MLFeatureValue(double: cumDistance)

        case "lapTime":

            return MLFeatureValue(double: lapTime)

        case "cumTime":

            return MLFeatureValue(double: cumTime)

        case "course":

            return MLFeatureValue(string: course)

        default:

            return MLFeatureValue(double: -0.0)

        }

    }

}

Then in my DataModel, prior to prediction, I create an instance of RunFeatures with the input values on which I want to base the prediction:

var runFeatures = RunFeatures()

runFeatures.distance = 3566.0

runFeatures.lapNumber = 1.0

runFeatures.lapDistance = 1001.0

 runFeatures.lapTime = 468.0

 runFeatures.cumTime = 468.0

 runFeatures.cumDistance = 1001.0

 runFeatures.course = "Wishing Well Loop"

NOTE there’s no need to provide the output feature (“duration”) here, nor in the featureValue method above but it is required in featureNames.

Then get the prediction with guard let durationOutput = try? mlModel!.prediction(from: runFeatures) 

Regards,

Michaela

Post not yet marked as solved Up vote post of AncientCoder Down vote post of AncientCoder
1.5k views
  • thanks, this worked even in a Swift Playgrounds version 4

Add a Comment