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 pour l'apprentissage automatique.

Avant de commencer, non pas que ce guide ne nécessite 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 pour un ingénieur ML bien rodé.

2. Qu'est-ce que le Deep Learning?

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

Les nœuds sont des processeurs de données numériques semblables à des neurones. Ils prennent des données de leurs entrées, appliquent certains poids et 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.

L'entraînement consiste essentiellement à enregistrer un état numérique (poids) dans les nœuds, ce qui affecte plus tard le calcul. Les exemples de formation 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 l'entraînement terminé, un réseau neuronal peut dériver des informations à partir de nouvelles données, même s'il n'a jamais vu ces éléments de données particuliers auparavant . Un réseau bien modélisé et bien formé peut reconnaître des images, des lettres manuscrites, des discours, traiter des données statistiques pour produire des résultats pour l'intelligence d'affaires, et bien plus encore.

Les réseaux de neurones profonds sont devenus possibles ces dernières années, avec l'avancée du calcul haute performance et parallèle. Ces réseaux diffèrent des réseaux de neurones simples en ce 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écursive, récurrente, convolutive, etc.), et d'en extraire beaucoup plus d'informations.

3. Configuration du projet

Pour utiliser la bibliothèque, nous avons besoin d'au moins Java 7. De plus, en raison de certains composants natifs, il ne fonctionne qu'avec la version JVM 64 bits.

Avant de commencer avec le guide, vérifions si les exigences 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)

Tout d'abord, 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 le référentiel Maven Central):

 0.9.1    org.nd4j nd4j-native-platform ${dl4j.version}   org.deeplearning4j deeplearning4j-core ${dl4j.version}  

Notez que la dépendance nd4j-native-platform est l'une des nombreuses implémentations disponibles.

Il s'appuie sur des bibliothèques natives disponibles pour de nombreuses plates-formes différentes (macOS, Windows, Linux, Android, etc.). Nous pourrions également basculer le backend vers la plate-forme nd4j-cuda-8.0 , 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 "Hello World" de l'apprentissage automatique - classification de l'ensemble de données sur les fleurs d'iris. Il s'agit d'un ensemble de données 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 classifie une donnée d'entrée (c.-à-d., Détermine à quelle espèce appartient une fleur particulière). Mais un réseau de neurones bien formé peut le classer rapidement et avec de petites 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 une 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 encodons la classe avec un nombre car les réseaux de neurones fonctionnent avec des nombres. Transformer des éléments de données du monde réel en séries de nombres (vecteurs) s'appelle la vectorisation - deeplearning4j utilise la bibliothèque datavec pour ce faire.

Tout d'abord, utilisons cette bibliothèque pour saisir le fichier avec les données vectorisées. Lors de la création du CSVRecordReader , nous pouvons spécifier le nombre de lignes à sauter (par exemple, si le fichier a une ligne d'en-tête) et le symbole du séparateur (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 ensembles de données peuvent être assez volumineux et la possibilité de paginer ou de mettre en cache les valeurs peut être utile.

Mais notre petit jeu de données ne contient que 150 enregistrements, alors lisons toutes les données en mémoire à la fois avec un appel à iterator.next () .

Nous spécifions également l'index de la colonne de classe qui dans notre cas est le même que le nombre de fonctionnalités (4) et le nombre total de classes (3).

Notez également que nous devons mélanger l'ensemble de données pour nous débarrasser de l'ordre des classes dans le fichier d'origine.

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

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

4.3. Normalisation et fractionnement

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 quelques statistiques sur les données (ajustement)
  • changer (transformer) les données d'une manière ou d'une autre pour les rendre uniformes

La normalisation peut différer pour différents types 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 mettre à l'échelle les images à une taille uniforme.

Mais pour les nombres, la normalisation signifie généralement 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 diviser l'ensemble en deux parties.

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

This would allow us to verify that the classification works correctly. We will take 65% of the data (0.65) for the training and leave the rest 35% for the testing:

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

5. Preparing the Network Configuration

5.1. Fluent Configuration Builder

Now we can build a configuration of our network with a fancy fluent builder:

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();

Even with this simplified fluent way of building a network model, there’s a lot to digest and a lot of parameters to tweak. Let’s break this model down.

5.2. Setting Network Parameters

The iterations() builder method specifies the number of optimization iterations.

The iterative optimization means performing multiple passes on the training set until the network converges to a good result.

Usually, when training on real and large datasets, we use multiple epochs (complete passes of data through the network) and one iteration for each epoch. But since our initial dataset is minimal, we'll use one epoch and multiple iterations.

The activation() is a function that runs inside a node to determine its output.

The simplest activation function would be linear f(x) = x. But it turns out that only non-linear functions allow networks to solve complex tasks by using a few nodes.

There are lots of different activation functions available which we can look up in the org.nd4j.linalg.activations.Activation enum. We could also write our activation function if needed. But we'll use the provided hyperbolic tangent (tanh) function.

The weightInit() method specifies one of the many ways to set up the initial weights for the network. Correct initial weights can profoundly affect the results of the training. Without going too much into the math, let’s set it to a form of Gaussian distribution (WeightInit.XAVIER), as this is usually a good choice for a start.

All other weight initialization methods can be looked up in the org.deeplearning4j.nn.weights.WeightInit enum.

Learning rate is a crucial parameter that profoundly affects the ability of the network to learn.

We could spend a lot of time tweaking this parameter in a more complex case. But for our simple task, we'll use a pretty significant value of 0.1 and set it up with the learningRate() builder method.

One of the problems with training neural networks is a case of overfitting when a network “memorizes” the training data.

This happens when the network sets excessively high weights for the training data and produces bad results on any other data.

To solve this issue, we’re going to set up l2 regularization with the line .regularization(true).l2(0.0001). Regularization “penalizes” the network for too large weights and prevents overfitting.

5.3. Building Network Layers

Next, we create a network of dense (also known as fully connect) layers.

The first layer should contain the same amount of nodes as the columns in the training data (4).

The second dense layer will contain three nodes. This is the value we can variate, but the number of outputs in the previous layer has to be the same.

The final output layer should contain the number of nodes matching the number of classes (3). The structure of the network is shown in the picture:

After successful training, we'll have a network that receives four values via its inputs and sends a signal to one of its three outputs. This is a simple classifier.

Finally, to finish building the network, we set up back propagation (one of the most effective training methods) and disable pre-training with the line .backprop(true).pretrain(false).

6. Creating and Training a Network

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

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

Nous pouvons maintenant tester le modèle entraîné 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 eval.stats () , nous verrons que notre réseau est assez bon pour classer les fleurs d'iris, bien qu'il ait confondu la classe 1 avec la classe 2 trois fois.

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 générateur de configuration fluide nous permet d'ajouter ou de modifier rapidement des couches du réseau, ou d'ajuster certains autres paramètres pour voir si notre modèle peut être amélioré.

7. Conclusion

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

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