Régression logistique en Java

1. Introduction

La régression logistique est un instrument important dans la boîte à outils des praticiens de l'apprentissage automatique (ML).

Dans ce didacticiel, nous explorerons l'idée principale de la régression logistique .

Commençons par un bref aperçu des paradigmes et des algorithmes de ML.

2. Aperçu

Le ML nous permet de résoudre des problèmes que nous pouvons formuler en termes respectueux de l'homme. Cependant, ce fait peut représenter un défi pour nous, développeurs de logiciels. Nous nous sommes habitués à aborder les problèmes que nous pouvons formuler en termes informatisés. Par exemple, en tant qu'êtres humains, nous pouvons facilement détecter les objets sur une photo ou établir l'ambiance d'une phrase. Comment formuler un tel problème pour un ordinateur?

Afin de trouver une solution, dans le ML, il existe une étape spéciale appelée formation . Au cours de cette étape, nous transmettons les données d'entrée à notre algorithme afin qu'il essaie de proposer un ensemble optimal de paramètres (les soi-disant poids). Plus nous pouvons fournir de données d'entrée à l'algorithme, plus nous pouvons en attendre des prédictions précises.

La formation fait partie d'un workflow ML itératif:

Nous commençons par acquérir des données. Souvent, les données proviennent de différentes sources. Par conséquent, nous devons faire en sorte qu'il soit du même format. Nous devons également contrôler que l'ensemble de données représente équitablement le domaine d'étude. Si le modèle n'a jamais été formé sur les pommes rouges, il peut difficilement le prédire.

Ensuite, nous devrions construire un modèle qui consommera les données et pourra faire des prédictions. En ML, il n'y a pas de modèles prédéfinis qui fonctionnent bien dans toutes les situations.

Lors de la recherche du modèle correct, il peut facilement arriver que nous construisions un modèle, le formions, voyions ses prédictions et rejetions le modèle parce que nous ne sommes pas satisfaits des prédictions qu'il fait. Dans ce cas, nous devrions prendre du recul et construire un autre modèle et répéter le processus à nouveau.

3. Paradigmes ML

En ML, en fonction du type de données d'entrée dont nous disposons, nous pouvons distinguer trois paradigmes principaux:

  • apprentissage supervisé (classification d'images, reconnaissance d'objets, analyse des sentiments)
  • apprentissage non supervisé (détection d'anomalies)
  • apprentissage par renforcement (stratégies de jeu)

Le cas que nous allons décrire dans ce tutoriel appartient à l'apprentissage supervisé.

4. Boîte à outils ML

Dans ML, il existe un ensemble d'outils que nous pouvons appliquer lors de la création d'un modèle. Citons quelques-uns d'entre eux:

  • Régression linéaire
  • Régression logistique
  • Les réseaux de neurones
  • Machine à vecteur de soutien
  • k-Voisins les plus proches

Nous pouvons combiner plusieurs outils lors de la construction d'un modèle à haute prédictivité. En fait, pour ce tutoriel, notre modèle utilisera la régression logistique et les réseaux de neurones.

5. Bibliothèques ML

Même si Java n'est pas le langage le plus populaire pour le prototypage de modèles ML,il a la réputation d'être un outil fiable pour créer des logiciels robustes dans de nombreux domaines, y compris le ML. Par conséquent, nous pouvons trouver des bibliothèques ML écrites en Java.

Dans ce contexte, on peut citer la bibliothèque standard de facto Tensorflow qui a également une version Java. Il convient également de mentionner une bibliothèque d'apprentissage en profondeur appelée Deeplearning4j. C'est un outil très puissant et nous allons également l'utiliser dans ce didacticiel.

6. Régression logistique sur la reconnaissance des chiffres

L'idée principale de la régression logistique est de construire un modèle qui prédit les étiquettes des données d'entrée aussi précisément que possible.

Nous entraînons le modèle jusqu'à ce que la fonction dite de perte ou fonction objective atteigne une valeur minimale. La fonction de perte dépend des prédictions réelles du modèle et des prévisions attendues (les étiquettes des données d'entrée). Notre objectif est de minimiser la divergence entre les prévisions réelles du modèle et celles attendues.

Si nous ne sommes pas satisfaits de cette valeur minimale, nous devons construire un autre modèle et effectuer à nouveau la formation.

In order to see logistic regression in action, we illustrate it on the recognition of handwritten digits. This problem has already become a classical one. Deeplearning4j library has a series of realistic examples which show how to use its API. The code-related part of this tutorial is heavily based on MNIST Classifier.

6.1. Input Data

As the input data, we use the well-known MNIST database of handwritten digits. As the input data, we have 28×28 pixel grey-scale images. Each image has a natural label which is the digit that the image represents:

In order to estimate the efficiency of the model that we're going to build, we split the input data into training and test sets:

DataSetIterator train = new RecordReaderDataSetIterator(...); DataSetIterator test = new RecordReaderDataSetIterator(...);

Once we have the input images labeled and split into the two sets, the “data elaboration” stage is over and we may pass to the “model building”.

6.2. Model Building

As we've mentioned, there are no models that work well in every situation. Nevertheless, after many years of research in ML, scientists have found models that perform very well in recognizing handwritten digits. Here, we use the so-called LeNet-5 model.

LeNet-5 is a neural network that consists of a series of layers that transform the 28×28 pixel image into a ten-dimensional vector:

The ten-dimensional output vector contains probabilities that the input image's label is either 0, or 1, or 2, and so on.

For example, if the output vector has the following form:

{0.1, 0.0, 0.3, 0.2, 0.1, 0.1, 0.0, 0.1, 0.1, 0.0}

it means that the probability of the input image to be zero is 0.1, to one is 0, to be two is 0.3, etc. We see that the maximal probability (0.3) corresponds to label 3.

Let's dive into details of model building. We omit Java-specific details and concentrate on ML concepts.

We set up the model by creating a MultiLayerNetwork object:

MultiLayerNetwork model = new MultiLayerNetwork(config);

In its constructor, we should pass a MultiLayerConfiguration object. This is the very object that describes the geometry of the neural network. In order to define the network geometry, we should define every layer.

Let's show how we do this with the first and the second one:

ConvolutionLayer layer1 = new ConvolutionLayer .Builder(5, 5).nIn(channels) .stride(1, 1) .nOut(20) .activation(Activation.IDENTITY) .build(); SubsamplingLayer layer2 = new SubsamplingLayer .Builder(SubsamplingLayer.PoolingType.MAX) .kernelSize(2, 2) .stride(2, 2) .build();

We see that layers' definitions contain a considerable amount of ad-hoc parameters which impact significantly on the whole network performance. This is exactly where our ability to find a good model in the landscape of all ones becomes crucial.

Now, we are ready to construct the MultiLayerConfiguration object:

MultiLayerConfiguration config = new NeuralNetConfiguration.Builder() // preparation steps .list() .layer(layer1) .layer(layer2) // other layers and final steps .build();

that we pass to the MultiLayerNetwork constructor.

6.3. Training

The model that we constructed contains 431080 parameters or weights. We're not going to give here the exact calculation of this number, but we should be aware that just the first layer has more than 24x24x20 = 11520 weights.

The training stage is as simple as:

model.fit(train); 

Initially, the 431080 parameters have some random values, but after the training, they acquire some values that determine the model performance. We may evaluate the model's predictiveness:

Evaluation eval = model.evaluate(test); logger.info(eval.stats());

The LeNet-5 model achieves quite a high accuracy of almost 99% even in just a single training iteration (epoch). If we want to achieve higher accuracy, we should make more iterations using a plain for-loop:

for (int i = 0; i < epochs; i++) { model.fit(train); train.reset(); test.reset(); } 

6.4. Prediction

Now, as we trained the model and we are happy with its predictions on the test data, we can try the model on some absolutely new input. To this end, let's create a new class MnistPrediction in which we'll load an image from a file that we select from the filesystem:

INDArray image = new NativeImageLoader(height, width, channels).asMatrix(file); new ImagePreProcessingScaler(0, 1).transform(image);

The variable image contains our picture being reduced to 28×28 grayscale one. We can feed it to our model:

INDArray output = model.output(image);

The variable output will contain the probabilities of the image to be zero, one, two, etc.

Jouons maintenant un peu et écrivons un chiffre 2, numérisons cette image et nourrissons-la du modèle. Nous pouvons obtenir quelque chose comme ceci:

Comme nous le voyons, le composant avec une valeur maximale de 0,99 a un indice deux. Cela signifie que le modèle a correctement reconnu notre chiffre manuscrit.

7. Conclusion

Dans ce didacticiel, nous avons décrit les concepts généraux de l'apprentissage automatique. Nous avons illustré ces concepts sur un exemple de régression logistique que nous avons appliqué à une reconnaissance manuscrite de chiffres.

Comme toujours, nous pouvons trouver les extraits de code correspondants sur notre référentiel GitHub.