Un Guide pour Deeplearning4j

1. Introduction

Dans cet article, nous allons créer un réseau de neurones simple avec la bibliothèque deeplearning4j (dl4j) - un outil moderne et puissant d’apprentissage automatique.

Avant de commencer, ce guide n’exige pas une connaissance approfondie de l’algèbre linéaire, des statistiques, de la théorie de l’apprentissage automatique et de nombreux autres sujets nécessaires à un ingénieur en ML expérimenté.

2. Qu’est-ce que l’apprentissage en profondeur?

Les réseaux de neurones sont des modèles informatiques composés de couches de nœuds interconnectées.

Les nœuds sont des processeurs neuronaux de données numériques. Ils prennent des données de leurs entrées, appliquent des poids et des fonctions à ces données et envoient les résultats aux sorties. Un tel réseau peut être formé avec quelques exemples des données sources.

S’entraîner consiste essentiellement à enregistrer un certain état numérique (poids) dans les nœuds, ce qui affecte ensuite le calcul. Les exemples d’apprentissage peuvent contenir des éléments de données avec des caractéristiques et certaines classes connues de ces éléments (par exemple, «cet ensemble de 16 × 16 pixels contient une lettre manuscrite« a »).

Une fois la formation terminée, un réseau de neurones peut extraire des informations à partir de nouvelles données, même s’il n’a pas vu ces éléments de données particuliers avant . Un réseau bien modélisé et bien formé peut reconnaître des images, des lettres manuscrites, des paroles, traiter des données statistiques afin de produire des résultats pour la veille stratégique, etc.

Les réseaux neuronaux profonds sont devenus possibles au cours des dernières années, avec le progrès du calcul parallèle et à haute performance. Ces réseaux diffèrent des réseaux de neurones simples en ce sens qu’ils se composent de plusieurs couches intermédiaires (ou cachées) . Cette structure permet aux réseaux de traiter les données de manière beaucoup plus compliquée (de manière récurrente, récurrente, convolutionnelle, etc.) et d’en extraire beaucoup plus d’informations.

3. Mise en place du projet

Pour utiliser la bibliothèque, il faut au moins Java 7. En raison de certains composants natifs, cela ne fonctionne qu’avec la version JVM 64 bits.

Avant de commencer avec le guide, vérifions si les conditions requises sont remplies:

$ java -version
java version "1.8.0__131"
Java(TM) SE Runtime Environment (build 1.8.0__131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

Premièrement, ajoutons les bibliothèques requises à notre fichier Maven pom.xml .

Nous allons extraire la version de la bibliothèque dans une entrée de propriété (pour la dernière version des bibliothèques, consultez la page https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org . deeplearning4j% 22[référentiel[Maven Central]):

<properties>
    <dl4j.version>0.9.1</dl4j.version>
</properties>

<dependencies>

    <dependency>
        <groupId>org.nd4j</groupId>
        <artifactId>nd4j-native-platform</artifactId>
        <version>${dl4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.deeplearning4j</groupId>
        <artifactId>deeplearning4j-core</artifactId>
        <version>${dl4j.version}</version>
    </dependency>
</dependencies>

Notez que la dépendance nd4j-native-platform fait partie des différentes implémentations disponibles.

Il repose sur des bibliothèques natives disponibles pour de nombreuses plates-formes différentes (macOS, Windows, Linux, Android, etc.). Nous pourrions également basculer le backend en nd4j-cuda-8.0-platform , si nous voulions exécuter des calculs sur une carte graphique prenant en charge le modèle de programmation CUDA.

4. Préparation des données

4.1. Préparation du fichier DataSet

Nous écrirons le «Bonjour tout le monde» de l’apprentissage automatique - classification de l’ensemble de données de fleurs https://en.wikipedia.org/wiki/Iris flower data set[iris]. Ceci est un ensemble de données qui ont été recueillies à partir de fleurs de différentes espèces ( Iris setosa , Iris versicolor et Iris virginica__).

Ces espèces diffèrent par la longueur et la largeur des pétales et des sépales. Il serait difficile d’écrire un algorithme précis qui classe un élément de données en entrée (c’est-à-dire qui détermine à quelle espèce appartient une fleur particulière). Mais un réseau de neurones bien formé peut le classer rapidement et avec peu d’erreurs.

Nous allons utiliser une version CSV de ces données, où les colonnes 0..3 contiennent les différentes caractéristiques de l’espèce et la colonne 4 contient la classe de l’enregistrement, ou l’espèce, codée avec la valeur 0, 1 ou 2:

5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
...
7.0,3.2,4.7,1.4,1
6.4,3.2,4.5,1.5,1
6.9,3.1,4.9,1.5,1
...

4.2. Vectoriser et lire les données

Nous codons la classe avec un nombre car les réseaux de neurones fonctionnent avec des nombres. La transformation d’éléments de données du monde réel en séries de nombres (vecteurs) est appelée vectorisation - deeplearning4j utilise la bibliothèque datavec pour le faire.

Commençons par utiliser cette bibliothèque pour entrer le fichier avec les données vectorisées. Lors de la création de CSVRecordReader , nous pouvons spécifier le nombre de lignes à ignorer (par exemple, si le fichier a une ligne d’en-tête) et le symbole de séparation (dans notre cas, une virgule):

try (RecordReader recordReader = new CSVRecordReader(0, ',')) {
    recordReader.initialize(new FileSplit(
      new ClassPathResource("iris.txt").getFile()));

   //...
}

Pour parcourir les enregistrements, nous pouvons utiliser l’une des multiples implémentations de l’interface DataSetIterator . Les jeux de données peuvent être assez volumineux et la possibilité de rechercher ou de mettre en cache les valeurs peut s’avérer utile.

Cependant, notre petit ensemble de données ne contient que 150 enregistrements. Lisons donc toutes les données en mémoire en une fois avec un appel de iterator.next () .

  • Nous spécifions également l’index de la colonne de classe qui, dans notre cas, est identique au nombre d’entités (4) et au nombre total de classes ** (3).

Notez également que nous devons mélanger le jeu de données pour supprimer le classement des classes dans le fichier d’origine.

Nous spécifions une valeur de départ aléatoire constante (42) au lieu de l’appel par défaut System.currentTimeMillis () afin que les résultats du brassage soient toujours les mêmes. Cela nous permet d’obtenir des résultats stables à chaque exécution du programme:

DataSetIterator iterator = new RecordReaderDataSetIterator(
  recordReader, 150, FEATURES__COUNT, CLASSES__COUNT);
DataSet allData = iterator.next();
allData.shuffle(42);

4.3. Normaliser et diviser

Une autre chose que nous devrions faire avec les données avant l’entraînement est de les normaliser. La normalisation est un processus en deux phases:

  • collecte de statistiques sur les données (ajustement)

  • changer (transformer) les données d’une manière quelconque pour les rendre uniformes

  • La normalisation peut différer selon le type de données. **

Par exemple, si nous voulons traiter des images de différentes tailles, nous devons d’abord collecter les statistiques de taille, puis redimensionner les images à une taille uniforme.

Mais pour les nombres, la normalisation signifie généralement de les transformer en une distribution dite normale. La classe NormalizerStandardize peut nous aider avec cela:

DataNormalization normalizer = new NormalizerStandardize();
normalizer.fit(allData);
normalizer.transform(allData);

Maintenant que les données sont préparées, nous devons scinder l’ensemble en deux parties.

La première partie sera utilisée dans une session de formation. Nous allons utiliser la deuxième partie des données (que le réseau ne verrait pas du tout) pour tester le réseau formé.

Cela nous permettrait de vérifier que la classification fonctionne correctement.

Nous prendrons 65% des données (0,65) pour la formation et laisserons les 35% restants pour les tests:

SplitTestAndTrain testAndTrain = allData.splitTestAndTrain(0.65);
DataSet trainingData = testAndTrain.getTrain();
DataSet testData = testAndTrain.getTest();

5. Préparation de la configuration du réseau

5.1. Fluent Configuration Builder

Maintenant, nous pouvons construire une configuration de notre réseau avec un constructeur élégant:

MultiLayerConfiguration configuration
  = new NeuralNetConfiguration.Builder()
    .iterations(1000)
    .activation(Activation.TANH)
    .weightInit(WeightInit.XAVIER)
    .learningRate(0.1)
    .regularization(true).l2(0.0001)
    .list()
    .layer(0, new DenseLayer.Builder().nIn(FEATURES__COUNT).nOut(3).build())
    .layer(1, new DenseLayer.Builder().nIn(3).nOut(3).build())
    .layer(2, new OutputLayer.Builder(
      LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
        .activation(Activation.SOFTMAX)
        .nIn(3).nOut(CLASSES__COUNT).build())
    .backprop(true).pretrain(false)
    .build();

Même avec cette manière simple et fluide de construire un modèle de réseau, il y a beaucoup à digérer et beaucoup de paramètres à ajuster. Brisons ce modèle.

5.2. Définition des paramètres réseau

  • La méthode de générateur iterations () spécifie le nombre d’itérations d’optimisation. **

L’optimisation itérative signifie effectuer plusieurs passes sur le jeu d’apprentissage jusqu’à ce que le réseau converge vers un bon résultat.

Habituellement, lors de la formation sur des jeux de données réels et volumineux, nous utilisons plusieurs périodes (passages complets de données via le réseau) et une itération pour chaque époque. Mais comme notre jeu de données initial est minimal, nous utiliserons une époque et plusieurs itérations.

La __activation () est une fonction qui s’exécute à l’intérieur d’un nœud pour déterminer sa sortie.

La fonction d’activation la plus simple serait linéaire f (x) = x. Mais il s’avère que seules des fonctions non linéaires permettent aux réseaux de résoudre des tâches complexes en utilisant quelques nœuds.

Il existe de nombreuses fonctions d’activation disponibles que nous pouvons rechercher dans le fichier org.nd4j.linalg.activations.Activation . Nous pourrions également écrire notre fonction d’activation si nécessaire. Mais nous allons utiliser la fonction tangente hyperbolique (tanh) fournie.

  • La méthode weightInit () spécifie l’une des nombreuses façons de configurer les poids initiaux pour le réseau. ** Des poids initiaux corrects peuvent affecter profondément les résultats de la formation. Sans trop entrer dans les calculs, fixons-le à une forme de distribution gaussienne ( WeightInit.XAVIER ), car c’est généralement un bon choix pour commencer.

Toutes les autres méthodes d’initialisation du poids peuvent être consultées dans l’en-tête org.deeplearning4j.nn.weights.WeightInit .

  • Le taux d’apprentissage ** est un paramètre crucial qui affecte profondément la capacité d’apprentissage du réseau.

Nous pourrions passer beaucoup de temps à peaufiner ce paramètre dans un cas plus complexe. Mais pour notre tâche simple, nous allons utiliser une valeur assez significative de 0.1 et la configurer avec la méthode learningRate () builder.

  • L’un des problèmes avec les réseaux de neurones d’apprentissage est un cas de surapprentissage ** lorsqu’un réseau «mémorise» les données d’entraînement.

Cela se produit lorsque le réseau définit des pondérations excessivement élevées pour les données d’apprentissage et produit des résultats erronés pour d’autres données.

  • Pour résoudre ce problème, nous allons configurer la régularisation l2 ** avec la ligne .regularization (true) .l2 (0.0001) . La régularisation «pénalise» le réseau pour des poids trop importants et évite les surajustements.

5.3. Construction de couches de réseau

Ensuite, nous créons un réseau de couches denses (également appelées connexions complètes).

La première couche doit contenir le même nombre de nœuds que les colonnes des données d’apprentissage (4).

La deuxième couche dense contiendra trois nœuds. C’est la valeur que nous pouvons modifier, mais le nombre de sorties dans la couche précédente doit être identique.

La couche de sortie finale doit contenir le nombre de nœuds correspondant au nombre de classes (3). La structure du réseau est montrée dans l’image:

lien:/uploads/Untitled-Diagram.png%20447w[]

Après une formation réussie, nous aurons un réseau qui reçoit quatre valeurs via ses entrées et envoie un signal à l’une de ses trois sorties.

Ceci est un classificateur simple.

Enfin, pour terminer la construction du réseau, nous avons configuré la propagation en arrière (l’une des méthodes d’entraînement les plus efficaces) et désactivé le pré-entraînement avec la ligne .backprop (true) .pretrain (false) .

6. Créer et former un réseau

Créons maintenant un réseau de neurones à partir de la configuration, initialisez-le et exécutez-le:

MultiLayerNetwork model = new MultiLayerNetwork(configuration);
model.init();
model.fit(trainingData);

Nous pouvons maintenant tester le modèle formé en utilisant le reste de l’ensemble de données et vérifier les résultats avec des métriques d’évaluation pour trois classes:

INDArray output = model.output(testData.getFeatureMatrix());
Evaluation eval = new Evaluation(3);
eval.eval(testData.getLabels(), output);

Si nous imprimons maintenant le eval.stats () , nous verrons que notre réseau est assez bon pour classer les fleurs d’iris, bien qu’il ait trompé la classe 1 pour la classe 2 à trois reprises.

Examples labeled as 0 classified by model as 0: 19 times
Examples labeled as 1 classified by model as 1: 16 times
Examples labeled as 1 classified by model as 2: 3 times
Examples labeled as 2 classified by model as 2: 15 times

==========================Scores========================================
# of classes: 3
Accuracy: 0.9434
Precision: 0.9444
Recall: 0.9474
F1 Score: 0.9411
Precision, recall & F1: macro-averaged (equally weighted avg. of 3 classes)
========================================================================

Le constructeur de configuration fluide nous permet d’ajouter ou de modifier rapidement des couches du réseau, ou d’ajuster quelques autres paramètres pour voir si notre modèle peut être amélioré.

7. Conclusion

Dans cet article, nous avons construit un réseau de neurones simple mais puissant en utilisant la bibliothèque deeplearning4j.

Comme toujours, le code source de l’article est disponible à l’adresse over sur GitHub .