Du Machine Learning sur iOS avec CoreML et React-Native

Si vous êtes comme moi, et que vous avez toujours rêvé de développer une application capable de différencier un hotdog du teckel de votre voisine grâce à votre Iphone, ce tutoriel est pour vous. Nous utiliserons pour ce faire React-Native, et le machine learning. C’est parti !
Les solutions de Machine Learning sont depuis longtemps disponibles dans le cloud via des API, mais elles demandaient une connexion internet et leur temps de traitement pouvait s’avérer long et coûteux à la fois.
Avec la sortie d’iOS 11, Apple a mis à disposition des développeurs sa librairie de Machine Learning, CoreML. Désormais, il n’est plus nécessaire d’avoir un serveur puissant pour traiter les requêtes ou faire appel à une API tierce. CoreML s’occupe de tout avec la puissance de calcul de votre smartphone.
Vous êtes curieux de savoir comment l’intégrer dans vos applications ? C’est plus simple que ce que vous pourriez penser.
Prenons comme exemple, la célèbre application parodique Not Hotdog, découverte dans la série Silicon Valley, qui, lorsque l’utilisateur pointe son smartphone vers un objet, lui dit instantanément si ce dernier regarde un hotdog ou non.
Not hotdog

Ce que vous allez apprendre dans cette partie

  • Installer votre environnement
  • Collecter des données
  • Créer votre modèle de classification d’image avec Turi Create
  • Reconnaître un hotdog grâce à la caméra de votre iPhone

Les prérequis

  • macOS 10.12+
  • ou Linux (avec glibc 2.12+)
  • ou Windows 10 (via WSL)

  • Python 2.7, 3.5 ou 3.6

  • une architecture x86_64

  • Xcode 9

  • iOS 11+

Qu’est-ce que Turi Create ?

Il s’agit d’un outil qui « simplifie le développement de modèles de machine learning personnalisés », pouvant être ensuite exploités dans des applications utilisant la librairie CoreML.
Turi Create se base sur le framework de Deep Learning MXNet d’Apache
Turi Create met à disposition des développeurs une technologie souple pour classifier des images, détecter des objets, concevoir des systèmes de recommandations, … etc.
L’outil est extrêmement simple d’utilisation, flexible et rapide.

Son installation

Comme pour beaucoup d’outils de Machine Learning, python est le langage utilisé. Mais ne vous en faites pas, le code est très simple à comprendre.
Il est recommandé d’utiliser un environnement virtuel, virtualenv, en entrant la commande

pip install virtualenv

Si vous n’avez pas pip, le gestionnaire de paquets de Python, vous pouvez l’installer grâce à Homebrew en entrant la commande

brew install pip

Puis,

// Créer un environnement virtuel Python
cd ~
virtualenv venv
// Activer votre environnement virtuel
source ~/venv/bin/activate
// Installer Turi Create
pip install -U turicreate

Collecter des données

Avant de pouvoir entraîner un modèle, il nous faut des données.
Ces données, on peut les trouver sur le site ImageNet qui est une très large base de données d’images, avec plus de 14 millions de résultats.
Pour notre projet, nous avons besoin de deux catégories d’images: Hotdog et… Not Hotdog.
Voici le lien pour la première catégorie: http://www.image-net.org/synset?wnid=n07697537.

On peut également récupérer tous les liens en utilisant l’API publique, via le bouton Download dans l’onglet Downloads

http://www.image-net.org/api/text/imagenet.synset.geturls?wnid=n07697537

Utilisation de turicreate-easy-scripts

Vous pourrez retrouver sur le repository turicreate-easy-scripts un ensemble de scripts qui vous faciliteront la vie.
Ainsi, après avoir cloner le repos et installé les dépendances du dossier download-image :

cd download-images
npm install

Vous pourrez executer la commande :

node imagenet-downloader.js http://www.image-net.org/api/text/imagenet.synset.geturls?wnid=n07697537 hotdog

Et obtenir un dossier rempli d’images de hotdogs.
Il ne reste plus qu’à faire la même chose pour Not Hotdog. Choisissez donc la/les catégories qui vous plaisent le plus et catégorisez-les comme not-hotdog

node imagenet-downloader.js http://image-net.org/api/text/imagenet.synset.geturls?wnid=n00021939 not-hotdog

Entraîner le modèle

Une fois les images téléchargées et catégorisées, il ne reste plus qu’à entraîner le modèle de classification.
Pour ce faire, vous pouvez utiliser le script python mis à disposition dans le repository téléchargé précédemment

cd train-model
python train-model.py


Vous obtiendrez au bout d’une dizaine de minutes (ou plus selon le nombre d’images à traiter) un fichier Classifier.mlmodel que l’on peut désormais pouvoir exploiter.
gif ml

Créer un projet React-Native

Tout d’abord, il faudra créer un nouveau projet React-Native.
Ouvrez votre terminal, naviguez dans votre dossier de projets et entrez la commande suivante :

react-native init notHotDog (ou tout autre nom)

Au bout de quelques minutes, tout sera installé et vous serez prêt à passer à la suite.

Installer la librairie CoreML

Nous allons utiliser la librairie react-native-core-ml-image

npm install --save react-native-core-ml-image
react-native link

Allez dans votre projet, puis dans le dossier “ios” et double-cliquez sur le fichier notHotDog.xcodeproj pour l’ouvrir dans Xcode

Configurer le projet

Par défaut, les projets en React-Native sont configurés pour utiliser principalement Objective-C. La librairie react-native-core-ml-image étant écrite en Swift, il va falloir changer quelques paramètres dans le projet
Tout d’abord, il va falloir ajouter un fichier Swift au projet


Le nom importe peu, il ne sera de toute façon pas utilisé. Un message apparaît alors vous proposant de créer un “Objective-C Bridging Header” : c’est le fichier qui sert à faire le lien entre Swift et les fichiers entête des Classes Objective-C

Enfin, la librairie étant écrite en Swift 4.0, il va falloir spécifier la version de Swift à utiliser (la 3.2 étant la version par défaut).
Cliquez sur la racine du projet (notHotDog), séléctionnez l’onglet “Build Settings”, puis tout en bas, changez la version du langage Swift à utiliser.

Importer le modèle CoreML dans le projet

Avant de passer à la partie programmation, il ne reste plus qu’à importer notre modèle de classification d’images dans le projet notHotDog.
Glissez-déposez le modèle Classifier.mlmodel et renommez-le notHotDog.mlmodelc (non, ce n’est pas une faute de frappe)

CoreML ne fonctionne pas directement avec les fichiers _.mlmodel, il faut d’abord les traduire en _.mlmodelc (c pour compiled), mais notre script Python s’en est déjà occupé. (cf. dernière ligne du fichier train_model.py)

# Export for use in Core ML
model.export_coreml('Classifier.mlmodel')

Autoriser l’accès à la caméra

Dans le fichier Info.plist, cliquez sur le petit plus à la droite de chaque entrée et ajoutez “Privacy – Camera Usage Description” comme montré ci-dessous

C’est tout pour la configuration ! Il ne reste plus qu’à implémenter tout cela.
gif code

Implémenter le code

La première chose à faire est d’importer la librairie react-native-core-ml-image dans le projet. Pour cet exemple, tout le code se situera dans le fichier App.js

import CoreMLImage from 'react-native-core-ml-image'

Ensuite, remplacez toute la méthode render() par ce qui vient ci-après :

render() {
    let classification = null;
    if (this.state.bestMatch) {
      if (this.state.bestMatch.identifier && this.state.bestMatch.identifier === "hotdog") {
        classification = "Hotdog";
      } else {
        classification = "Not hotdog";
      }
    }
    return (
      <View style={styles.container}>
          <CoreMLImage modelFile="notHotDog" onClassification={(evt) => this.onClassification(evt)}>
              <View style={styles.container}>
                <Text style={styles.info}>{classification}</Text>
              </View>
          </CoreMLImage>
      </View>
    );
  }

La méthode onClassification nous permet de recevoir des updates quand un nouvel objet a été classifié. Il renvoie les données suivantes :

[
{
identifier: "hotdog",
confidence: 0.87
},
{
identifier: "not-hotdog",
confidence: 0.4
}
]

Nous n’avons plus qu’à implémenter la méthode onClassification qui se charge de trouver la meilleure classification.

const BEST_MATCH_THRESHOLD = 0.5;
/** */
onClassification(classifications) {
    let bestMatch = null;
    if (classifications && classifications.length) {
      classifications.map(classification => {
        if (!bestMatch || classification.confidence > bestMatch.confidence) {
          bestMatch = classification;
        }
      });
      if (bestMatch.confidence >= BEST_MATCH_THRESHOLD) {
        this.setState({
          bestMatch: bestMatch
        });
      }
      else {
        this.setState({
          bestMatch: null
        });
      }
    }
    else {
      this.setState({
        bestMatch: null
      });
    }
  }

Si l’on se base sur les données précédentes, alors bestMatch vaudra

{
identifier: "hotdog",
confidence: 0.87
}

Voici le code complet:

import React, { Component } from "react";
import { Platform, StyleSheet, Text, View } from "react-native";
import idx from "idx";
const BEST_MATCH_THRESHOLD = 0.5;
import CoreMLImage from "react-native-core-ml-image";
export default class App extends Component<{}> {
  constructor() {
    super();
    this.state = {
      bestMatch: null
    };
  }
  onClassification(classifications) {
    let bestMatch = null;
    if (classifications && classifications.length) {
      classifications.map(classification => {
        if (!bestMatch || classification.confidence > bestMatch.confidence) {
          bestMatch = classification;
        }
      });
      if (bestMatch.confidence >= BEST_MATCH_THRESHOLD) {
        this.setState({
          bestMatch: bestMatch
        });
      } else {
        this.setState({
          bestMatch: null
        });
      }
    } else {
      this.setState({
        bestMatch: null
      });
    }
  }
  classify() {
    if (idx(this.state, _ => _.bestMatch.identifier)) {
      if (this.state.bestMatch.identifier === "hotdog") {
        return "Hotdog";
      } else {
        return "Not hotdog";
      }
    }
  }
  render() {
    return (
      <View style={styles.container}>
        <CoreMLImage
          modelFile="notHotDog"
          onClassification={evt => this.onClassification(evt)}
        >
          <View style={styles.container}>
            <Text style={styles.info}>{classify()}</Text>
          </View>
        </CoreMLImage>
      </View>
    );
  }
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "transparent"
  },
  info: {
    fontSize: 20,
    color: "#ffffff",
    textAlign: "center",
    fontWeight: "900",
    margin: 10
  }
});

Il ne vous reste plus qu’à éxecuter le code sur votre iPhone (la caméra ne fonctionnera pas sur le simulateur).
Si vous avez tout bien fait, l’app vous demandera la permission d’accéder à votre appareil photo et vous pourrez alors distinguer un hotdog du teckel de votre voisine.
Merci de m’avoir lu. Si l’article vous a plu, n’hésitez pas à le partager sur les réseaux sociaux !
Article écrit par Jérémie Zarca.