Comment déployer une IA CoreML avec Vapor
Vous avez envie de toucher à l'intelligence artificielle d'une façon simple ? CoreML est fait pour vous !

Cet article fait suite à l'article "Créer une intelligence artificielle en 5 minutes sans coder" qui nous a permis de créer un modèle de machine learning que l'on va intégrer et déployer via une API Rest ici.
Les outils d'Apple pour faire de l'intelligence artificielle sont époustouflants 😍. Mais malheureusement, ils ne fonctionnent que sur des appareils de la firme !
Alors pour contrer ça, il est possible de créer une API avec Vapor et CoreML, pour pouvoir profiter de ces outils sur d'autres plateformes (Windows, Android ou le web par exemple 😏)
0. Accéder à un Mac - Partez pas c'est pas si cher
Notre idée est de pouvoir profiter de CoreML sur n'importe quel système, on va donc créer une API Rest qui tournera sur un serveur Mac. Ce qui est cool c'est que Scaleway (et d'autres) offrent ce genre de service.

De notre coté, on a utilisé Scaleway qui permet de louer un Mac Mini M1 (donc performant) qui fera office de serveur avec sa propre IP pour pouvoir déployer notre API CoreML.
1. Installer Vapor
Avant de l'installer, commençons par expliquer ce que c'est !
Vapor est un framework web open-source développé en Swift, donc (malheureusement) utilisable uniquement si vous avez un Mac 🍏
Ce framework permet de créer des API Rest, des applications web ou encore de mettre en place du temps réel assez facilement, et tout ça en Swift 🔥
Pour installer Vapor, nous vous conseillons d'utiliser Homebrew, ce n'est pas indispensable, mais c'est un outil qui vous permettra d'installer des dépendances pour ce projet et que vous retrouverez régulièrement dans pleins de tutoriels pour installer des dépendances sur un Mac.
Si vous n'avez pas encore Homebrew d'installé, ça se passe par ici
Une fois que vous avez Homebrew, vous pouvez installer Vapor tout simplement avec ces 2 petites lignes :
brew tap vapor/tap
brew install vapor
2. Créer un projet Vapor
Pour créer un projet vapor, c'est très simple ! Dans un terminal, placez-vous où vous voulez le créer et tapez cette commande (CoreMLAPI est le nom du projet, choisissez ce que vous voulez) :
vapor new CoreMLAPI
3. Importer un modèle CoreML
Vous n'avez jamais créé de modèle CoreML ? Regardez notre article et créez votre propre modèle : Créer une intelligence artificielle en 5 minutes sans coder
Vous pouvez également utiliser un modèle déjà existant. Voici 2 petits liens sympas avec des modèles prêts à être utilisés 😎
- La librairie Apple : https://developer.apple.com/machine-learning/models/
- Awesome CoreML Models : https://github.com/likedan/Awesome-CoreML-Models
Pour ce tutoriel, nous avons déjà créé un modèle nommé "CatsDogsNet", qui parvient à savoir si l'image donnée représente un chat ou un chien.

Dans le dossier Resources, créez un dossier CoreML et ajoutez-y votre modèle (.mlmodel).
Une fois le fichier du modèle ajouté, nous allons le compiler pour pouvoir l'utiliser sur un projet Xcode. Pour ça, placez-vous à la racine de votre projet Vapor, et lancez cette commande :
xcrun coremlcompiler compile Resources/coreml/VotreModele.mlmodel Resources/coreml
Des fichiers ont du être créés et votre dossier CoreML doit maintenant contenir deux fichiers (un .mlmodel et un .mlmodelc) comme ici :

Pour finir, générons le code source de ce modèle pour pouvoir le modifier après en Swift si besoin.
Pour ça, toujours à la racine, lancez cette commande :
xcrun coremlcompiler generate Resources/coreml/VotreModele.mlmodel --language Swift Sources/App/
Un fichier nommé VotreModele a du être généré dans le dossier Sources/App
Ce fichier Swift permet de faire appel facilement aux fonctions du modèle depuis votre code.
4. Adapter le fichier CoreML
Maintenant que le modèle a été généré, nous allons faire petite modification pour bien l'inclure dans le projet.
Ouvrez le fichier VotreModel
dans Sources > App
Tout en haut du fichier, importez Vapor
import Vapor
Puis recherchez urlOfModelInThisBundle et remplacez son contenu par
class var urlOfModelInThisBundle : URL {
let directory = DirectoryConfiguration.detect()
return URL(fileURLWithPath: directory.workingDirectory)
.appendingPathComponent("Resources/coreml/VotreModele.mlmodelc",
isDirectory: true
)
}
5. Créer la route qui classifie
Dans le dossier Sources > App > Controllers
, ajoute un nouveau fichier "ImageClassificationController.swift"
avec ce contenu :
import Vapor
struct ImageClassificationController: RouteCollection {
// Création de la route d'API /classify_image qui collecte une image
func boot(routes: RoutesBuilder) throws {
routes.on(
.POST,
"classify_image",
body: .collect(maxSize: ByteCount(value: 2000*1024)),
use: imageClassification
)
}
// Décodage le fichier reçu pour pouvoir ensuite le classifier
func imageClassification(req: Request) throws -> EventLoopFuture<[ClassificationResult]> {
let eventLoop = req.eventLoop.makePromise(of: [ClassificationResult].self)
let request = try req.content.decode(ClassifyRequest.self)
let image = request.file
let data = Data(buffer: image.data)
guard let orientation = CGImagePropertyOrientation(rawValue: 0) else { throw Abort(.internalServerError) }
ImageClassifier().getClassification(forImageData: data, orientation: orientation) { result in
switch result {
case .success(let classificationResults):
eventLoop.succeed(classificationResults)
case .failure(let error):
eventLoop.fail(error)
}
}
return eventLoop.futureResult
}
}
Ajoutez ensuite la route dans le fichier routes.swift. Pour ça remplacez la fonction routes du fichier par :
func routes(_ app: Application) throws {
try app.register(collection: ClassificationController())
}
6. Classifier les images
Maintenant que l'on a le modèle et la route, il ne nous reste plus qu'à classifier, pour ça, commencez par créer un fichier ClassifyRequest dans lequel vous mettez :
import Vapor
struct ClassifyRequest: Content {
let image: File
}
Ce fichier va servir à convertir ce qu'on reçoit depuis la requête (le fichier de l'image)
On est bientôt au bout ! 😎
Pour finir, on va créer les 3 fichiers qui vont nous permettre de classifier ce qui est reçu par l'API :
Le fichier ImageClassfierError.swift
, qui permet d'avoir une raison en cas d'erreur dans la classification
enum ImageClassifierError: Error {
case nothingRecognised
case unableToClassify
case invalidImage
}
Le fichier ClassificationResult.swift
, qui permet de classifier les résultats avec un identifier
, et une confidence
.
Dans notre cas, l'identifier sera cats
, dogs
ou junk
, et la confidence
est le % de confiance du modèle dans la prédiction.
import Vapor
struct ClassificationResult: Content {
let identifier: String
let confidence: Float
}
Et enfin, le fichier le plus important : ImageClassifier.swift
, c'est lui qui va s'occuper de lancer la classification grâce au modèle.
import Vapor
import CoreML
import Vision
import CoreImage
class ImageClassifier {
typealias classificationCompletion = ((Result<[ClassificationResult], ImageClassifierError>) -> Void)
private var completionClosure: classificationCompletion?
// C'est ici que CoreML entre en jeu et fait initie le modèle
lazy var request: VNCoreMLRequest = {
let config = MLModelConfiguration()
config.computeUnits = .all
do {
let model = try VNCoreMLModel(for: VotreModele(configuration: config).model)
let request = VNCoreMLRequest(model: model, completionHandler: handleClassifications)
request.imageCropAndScaleOption = .centerCrop
return request
} catch {
fatalError("Failed to load the model. Error: \(error.localizedDescription)")
}
}()
// Requête faite avec le modèle pour classifier l'image reçue
func getClassification(forImageData imageData: Data, orientation: CGImagePropertyOrientation, completion: @escaping classificationCompletion) {
guard let ciImage = CIImage(data: imageData) else {
completion(.failure(.invalidImage))
return
}
completionClosure = completion
let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
do {
try handler.perform([self.request])
} catch {
print("Failed to perform classification.\n\(error.localizedDescription)")
}
}
}
private extension ImageClassifier {
func handleClassifications(forRequest request: VNRequest, error: Error?) {
guard
let results = request.results,
let classifications = results as? [VNClassificationObservation]
else {
debugPrint("Unable to classify image.\n\(error!.localizedDescription)")
completionClosure?(.failure(.unableToClassify))
return
}
// Filtrer et garder les classifications les plus hautes
if classifications.isEmpty {
completionClosure?(.failure(.nothingRecognised))
} else {
let topClassifications = classifications.prefix(2)
let results = topClassifications.map { classification in
ClassificationResult(identifier: classification.identifier, confidence: classification.confidence)
}
completionClosure?(.success(results))
}
}
}
7. Il n'y a plus qu'à tester
Votre API est prête, on peut lui passer un fichier (l'image), et elle doit nous retourner la classification de l'image (chat, chien ou junk) avec la précision.
Pour tester, il nous suffit d'exécuter notre projet Vapor avec Xcode (l'API doit se lancer en local avec http://localhost:8080/classify_image comme URL) puis d'utiliser un outil comme Postman pour faire une requête post avec un paramètre image qui est le fichier (un chat, un chien, ou n'importe quoi d'autre).

Vous pouvez aussi faire votre requête en utilisant curl, directement depuis un terminal.
Par exemple, nous allons le faire avec ce magnifique chat :

curl --form image='@monmagnifiquechat.png' http://localhost:8080/classify_image
Si tout s'est bien passé, vous devez avoir ce genre de résultats:
[{"confidence":0.99956685304641724,"identifier":"cat"},{"confidence":0.00034059779136441648,"identifier":"dog"}]
Et voilà ! Vous avez déployé votre premier réseau de neurone avec CoreML.
Ce process est applicable pour d'autres modèles comme de la classification de son, d'activité, de la détection d'objet, etc.
Si cela vous intéresse d'aller plus loin hésitez pas à nous contacter pour en discuter :)
Soutenez notre travail ❤️
🔥 Suivez nous sur Instagram, Facebook, LinkedIn ou abonnez-vous à notre newsletter en devenant membre de notre blog :)