Home

Awesome

CoreML-TransferLearning-Demo

Swift for TensorFlow Notebook and CoreML + SwiftUI demo App to demonstrate the potentiality of the SwiftCoreMLTools library (https://github.com/JacopoMangiavacchi/SwiftCoreMLTools) to adopt Transfer Learning technique and train on devices tabular data with multiple Numerical and Categorical Features using Embedding and Multi Input.

The App

Models architecture

The App

The App

Dataset

The Boston housing price dataset (Creator: Harrison, D. and Rubinfeld, D.L. )has 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

Attribute Information (in order):

Data transformations

S4TF Model

struct MultiInputs<N: Differentiable, C>: Differentiable {
  var numerical: N
  
  @noDerivative
  var categorical: C

  @differentiable
  init(numerical: N, categorical: C) {
    self.numerical = numerical
    self.categorical = categorical
  }
}

struct RegressionModel: Module {
    var numericalLayer = Dense<Float>(inputSize: 11, outputSize: 32, activation: relu)
    var embedding1 = Embedding<Float>(vocabularySize: 2, embeddingSize: 2)
    var embedding2 = Embedding<Float>(vocabularySize: 9, embeddingSize: 5)
    var embeddingLayer = Dense<Float>(inputSize: (4 + 45), outputSize: 64, activation: relu)
    var allInputConcatLayer = Dense<Float>(inputSize: (32 + 64), outputSize: 128, activation: relu)
    var hiddenLayer = Dense<Float>(inputSize: 128, outputSize: 32, activation: relu)
    var outputLayer = Dense<Float>(inputSize: 32, outputSize: 1)

    @differentiable
    func callAsFunction(_ input: MultiInputs<[Tensor<Float>], [Tensor<Int32>]>) -> Tensor<Float> {
        let numericalInput = numericalLayer(input.numerical[0])
        let embeddingOutput1 = embedding1(input.categorical[0])
        let embeddingOutput1Reshaped = embeddingOutput1.reshaped(to: 
            TensorShape([embeddingOutput1.shape[0], embeddingOutput1.shape[1] * embeddingOutput1.shape[2]]))
        let embeddingOutput2 = embedding2(input.categorical[1])
        let embeddingOutput2Reshaped = embeddingOutput2.reshaped(to: 
            TensorShape([embeddingOutput2.shape[0], embeddingOutput2.shape[1] * embeddingOutput2.shape[2]]))
        let embeddingConcat = Tensor<Float>(concatenating: [embeddingOutput1Reshaped, embeddingOutput2Reshaped], alongAxis: 1)
        let embeddingInput = embeddingLayer(embeddingConcat)
        let allConcat = Tensor<Float>(concatenating: [numericalInput, embeddingInput], alongAxis: 1)
        return allConcat.sequenced(through: allInputConcatLayer, hiddenLayer, outputLayer)
    }
}

var model = RegressionModel()

SwiftCoreMLTools Export Trained Model

let coremlModel = Model(version: 4,
                        shortDescription: "Regression",
                        author: "Jacopo Mangiavacchi",
                        license: "MIT",
                        userDefined: ["SwiftCoremltoolsVersion" : "0.0.6"]) {
    Input(name: "numericalInput", shape: [11])
    Input(name: "categoricalInput1", shape: [1])
    Input(name: "categoricalInput2", shape: [1])
    Output(name: "output", shape: [1])
    NeuralNetwork {
        Embedding(name: "embedding1",
                     input: ["categoricalInput1"],
                     output: ["outEmbedding1"],
                     weight: model.embedding1.embeddings.transposed().flattened().scalars,
                     inputDim: 2,
                     outputChannels: 2)
        Permute(name: "permute1",
                     input: ["outEmbedding1"],
                     output: ["outPermute1"],
                     axis: [2, 1, 0, 3])
        Flatten(name: "flatten1",
                     input: ["outPermute1"],
                     output: ["outFlatten1"],
                     mode: .last)
        Embedding(name: "embedding2",
                     input: ["categoricalInput2"],
                     output: ["outEmbedding2"],
                     weight: model.embedding2.embeddings.transposed().flattened().scalars,
                     inputDim: 9,
                     outputChannels: 5)
        Permute(name: "permute2",
                     input: ["outEmbedding2"],
                     output: ["outPermute2"],
                     axis: [2, 1, 0, 3])
        Flatten(name: "flatten2",
                     input: ["outPermute2"],
                     output: ["outFlatten2"],
                     mode: .last)
        Concat(name: "concat",
                     input: ["numericalInput", "outFlatten1", "outFlatten2"],
                     output: ["outConcat"])
        InnerProduct(name: "dense1",
                     input: ["outConcat"],
                     output: ["outDense1"],
                     weight: model.allInputConcatLayer.weight.transposed().flattened().scalars,
                     bias: model.allInputConcatLayer.bias.flattened().scalars,
                     inputChannels: 11 + 2 + 5,
                     outputChannels: 64)
        ReLu(name: "Relu1",
             input: ["outDense1"],
             output: ["outRelu1"])
        InnerProduct(name: "dense2",
                     input: ["outRelu1"],
                     output: ["outDense2"],
                     weight: model.hiddenLayer.weight.transposed().flattened().scalars,
                     bias: model.hiddenLayer.bias.flattened().scalars,
                     inputChannels: 64,
                     outputChannels: 32)
        ReLu(name: "Relu2",
             input: ["outDense2"],
             output: ["outRelu2"])
        InnerProduct(name: "dense3",
                     input: ["outRelu2"],
                     output: ["output"],
                     weight: model.outputLayer.weight.transposed().flattened().scalars,
                     bias: model.outputLayer.bias.flattened().scalars,
                     inputChannels: 32,
                     outputChannels: 1)
    }
}

SwiftCoreMLTools Export Trainable Model

let coremlModel = Model(version: 4,
                        shortDescription: "Regression",
                        author: "Jacopo Mangiavacchi",
                        license: "MIT",
                        userDefined: ["SwiftCoremltoolsVersion" : "0.0.6"]) {
    Input(name: "numericalInput", shape: [11])
    Input(name: "categoricalInput1", shape: [1])
    Input(name: "categoricalInput2", shape: [1])
    Output(name: "output", shape: [1])
    TrainingInput(name: "numericalInput", shape: [11])
    TrainingInput(name: "categoricalInput1", shape: [1])
    TrainingInput(name: "categoricalInput2", shape: [1])
    TrainingInput(name: "output_true", shape: [1])
    NeuralNetwork(losses: [MSE(name: "lossLayer",
                               input: "output",
                               target: "output_true")],
                  optimizer: SGD(learningRateDefault: 0.001,
                                 learningRateMax: 0.3,
                                 miniBatchSizeDefault: 32,
                                 miniBatchSizeRange: [32],
                                 momentumDefault: 0,
                                 momentumMax: 1.0),
                  epochDefault: 500,
                  epochSet: [500],
                  shuffle: true) {
        Embedding(name: "embedding1",
                     input: ["categoricalInput1"],
                     output: ["outEmbedding1"],
                     weight: model.embedding1.embeddings.transposed().flattened().scalars,
                     inputDim: 2,
                     outputChannels: 2)
        Permute(name: "permute1",
                     input: ["outEmbedding1"],
                     output: ["outPermute1"],
                     axis: [2, 1, 0, 3])
        Flatten(name: "flatten1",
                     input: ["outPermute1"],
                     output: ["outFlatten1"],
                     mode: .last)
        Embedding(name: "embedding2",
                     input: ["categoricalInput2"],
                     output: ["outEmbedding2"],
                     weight: model.embedding2.embeddings.transposed().flattened().scalars,
                     inputDim: 9,
                     outputChannels: 5)
        Permute(name: "permute2",
                     input: ["outEmbedding2"],
                     output: ["outPermute2"],
                     axis: [2, 1, 0, 3])
        Flatten(name: "flatten2",
                     input: ["outPermute2"],
                     output: ["outFlatten2"],
                     mode: .last)
        Concat(name: "concat",
                     input: ["numericalInput", "outFlatten1", "outFlatten2"],
                     output: ["outConcat"])
        InnerProduct(name: "dense1",
                     input: ["outConcat"],
                     output: ["outDense1"],
                     inputChannels: 11 + 2 + 5,
                     outputChannels: 64,
                     updatable: true)
        ReLu(name: "Relu1",
             input: ["outDense1"],
             output: ["outRelu1"])
        InnerProduct(name: "dense2",
                     input: ["outRelu1"],
                     output: ["outDense2"],
                     inputChannels: 64,
                     outputChannels: 32,
                     updatable: true)
        ReLu(name: "Relu2",
             input: ["outDense2"],
             output: ["outRelu2"])
        InnerProduct(name: "dense3",
                     input: ["outRelu2"],
                     output: ["output"],
                     inputChannels: 32,
                     outputChannels: 1,
                     updatable: true)
    }
}