CoreML + SwiftUI demo App to demonstrate the potentiality of the SwiftCoreMLTools library (https://github.com/JacopoMangiavacchi/SwiftCoreMLTools) to fully create and train on iOS devices a Convolutional Neural Network for the MNIST dataset.
The MNIST dataset (Creator: Yann LeCun, Corinna Cortes) of handwritten digits, available from this page MNIST dataset, has a training set of 60,000 examples, and a test set of 10,000 examples. It is a subset of a larger set available from NIST. The digits have been size-normalized and centered in a fixed-size image.
It is a good database for people who want to try learning techniques and pattern recognition methods on real-world data while spending minimal efforts on preprocessing and formatting.
public func prepareModel() {
let coremlModel = Model(version: 4,
shortDescription: "MNIST-Trainable",
author: "Jacopo Mangiavacchi",
license: "MIT",
userDefined: ["SwiftCoremltoolsVersion" : "0.0.12"]) {
Input(name: "image", shape: [1, 28, 28])
Output(name: "output", shape: [10], featureType: .float)
TrainingInput(name: "image", shape: [1, 28, 28])
TrainingInput(name: "output_true", shape: [1], featureType: .int)
NeuralNetwork(losses: [CategoricalCrossEntropy(name: "lossLayer",
input: "output",
target: "output_true")],
optimizer: Adam(learningRateDefault: 0.0001,
learningRateMax: 0.3,
miniBatchSizeDefault: 128,
miniBatchSizeRange: [128],
beta1Default: 0.9,
beta1Max: 1.0,
beta2Default: 0.999,
beta2Max: 1.0,
epsDefault: 0.00000001,
epsMax: 0.00000001),
epochDefault: UInt(self.epoch),
epochSet: [UInt(self.epoch)],
shuffle: true) {
Convolution(name: "conv1",
input: ["image"],
output: ["outConv1"],
outputChannels: 32,
kernelChannels: 1,
nGroups: 1,
kernelSize: [3, 3],
stride: [1, 1],
dilationFactor: [1, 1],
paddingType: .valid(borderAmounts: [EdgeSizes(startEdgeSize: 0, endEdgeSize: 0),
EdgeSizes(startEdgeSize: 0, endEdgeSize: 0)]),
outputShape: [],
deconvolution: false,
updatable: true)
ReLu(name: "relu1",
input: ["outConv1"],
output: ["outRelu1"])
Pooling(name: "pooling1",
input: ["outRelu1"],
output: ["outPooling1"],
poolingType: .max,
kernelSize: [2, 2],
stride: [2, 2],
paddingType: .valid(borderAmounts: [EdgeSizes(startEdgeSize: 0, endEdgeSize: 0),
EdgeSizes(startEdgeSize: 0, endEdgeSize: 0)]),
avgPoolExcludePadding: true,
globalPooling: false)
Convolution(name: "conv2",
input: ["outPooling1"],
output: ["outConv2"],
outputChannels: 32,
kernelChannels: 32,
nGroups: 1,
kernelSize: [2, 2],
stride: [1, 1],
dilationFactor: [1, 1],
paddingType: .valid(borderAmounts: [EdgeSizes(startEdgeSize: 0, endEdgeSize: 0),
EdgeSizes(startEdgeSize: 0, endEdgeSize: 0)]),
outputShape: [],
deconvolution: false,
updatable: true)
ReLu(name: "relu2",
input: ["outConv2"],
output: ["outRelu2"])
Pooling(name: "pooling2",
input: ["outRelu2"],
output: ["outPooling2"],
poolingType: .max,
kernelSize: [2, 2],
stride: [2, 2],
paddingType: .valid(borderAmounts: [EdgeSizes(startEdgeSize: 0, endEdgeSize: 0),
EdgeSizes(startEdgeSize: 0, endEdgeSize: 0)]),
avgPoolExcludePadding: true,
globalPooling: false)
Flatten(name: "flatten1",
input: ["outPooling2"],
output: ["outFlatten1"],
mode: .last)
InnerProduct(name: "hidden1",
input: ["outFlatten1"],
output: ["outHidden1"],
inputChannels: 1152,
outputChannels: 500,
updatable: true)
ReLu(name: "relu3",
input: ["outHidden1"],
output: ["outRelu3"])
InnerProduct(name: "hidden2",
input: ["outRelu3"],
output: ["outHidden2"],
inputChannels: 500,
outputChannels: 10,
updatable: true)
Softmax(name: "softmax",
input: ["outHidden2"],
output: ["output"])
}
}
let coreMLData = coremlModel.coreMLData
try! coreMLData!.write(to: coreMLModelUrl)
}
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_uniform', input_shape=in_shape))
model.add(MaxPool2D((2, 2), strides=(2,2)))
model.add(Conv2D(32, (2, 2), activation='relu', kernel_initializer='he_uniform', input_shape=in_shape))
model.add(MaxPool2D((2, 2), strides=(2,2)))
model.add(Flatten())
model.add(Dense(500, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(n_classes, activation='softmax'))
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 12, 12, 32) 4128
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 6, 6, 32) 0
_________________________________________________________________
flatten (Flatten) (None, 1152) 0
_________________________________________________________________
dense (Dense) (None, 500) 576500
_________________________________________________________________
dense_1 (Dense) (None, 10) 5010
=================================================================
Total params: 585,958 Trainable params: 585,958 Non-trainable params: 0
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(trainX, trainy, epochs=10, batch_size=128, verbose=1)
Train on 60000 samples
Epoch 1/10
60000/60000 [==============================] - 16s 266us/sample - loss: 0.1441 - accuracy: 0.9563
[...]
Epoch 10/10
60000/60000 [==============================] - 15s 257us/sample - loss: 0.0043 - accuracy: 0.9987
Time: 157.58382892608643 seconds
Wall time: 2min 37s