Una guida per Deeplearning4j

1. Introduzione

In questo articolo creeremo una semplice rete neurale con la libreria deeplearning4j (dl4j), uno strumento moderno e potente per l'apprendimento automatico.

Prima di iniziare, non che questa guida non richieda una profonda conoscenza di algebra lineare, statistica, teoria dell'apprendimento automatico e molti altri argomenti necessari per un ingegnere ML ben fondato.

2. Che cos'è il deep learning?

Le reti neurali sono modelli computazionali costituiti da strati di nodi interconnessi.

I nodi sono processori simili a neuroni di dati numerici. Prendono i dati dai loro input, applicano alcuni pesi e funzioni a questi dati e inviano i risultati agli output. Tale rete può essere addestrata con alcuni esempi dei dati di origine.

L'addestramento essenzialmente consiste nel salvare uno stato numerico (pesi) nei nodi che successivamente influisce sul calcolo. Gli esempi di addestramento possono contenere elementi di dati con caratteristiche e determinate classi note di questi elementi (ad esempio, "questo set di 16 × 16 pixel contiene una lettera" a "scritta a mano).

Al termine dell'addestramento, una rete neurale può ricavare informazioni da nuovi dati, anche se non ha mai visto questi particolari elementi di dati . Una rete ben modellata e ben addestrata può riconoscere immagini, lettere scritte a mano, parlato, elaborare dati statistici per produrre risultati per la business intelligence e molto altro ancora.

Le reti neurali profonde sono diventate possibili negli ultimi anni, con il progresso del calcolo parallelo e ad alte prestazioni. Tali reti differiscono dalle reti neurali semplici in quanto sono costituite da più livelli intermedi (o nascosti) . Questa struttura consente alle reti di elaborare i dati in un modo molto più complicato (in modo ricorsivo, ricorrente, convoluzionale, ecc.) E di estrarne molte più informazioni.

3. Impostazione del progetto

Per utilizzare la libreria, abbiamo bisogno almeno di Java 7. Inoltre, a causa di alcuni componenti nativi, funziona solo con la versione JVM a 64 bit.

Prima di iniziare con la guida, controlliamo se i requisiti sono soddisfatti:

$ 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)

Per prima cosa, aggiungiamo le librerie richieste al nostro file Maven pom.xml . Estrarremo la versione della libreria in una voce di proprietà (per l'ultima versione delle librerie, controlla il repository Maven Central):

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

Notare che la dipendenza nd4j-native-platform è una delle numerose implementazioni disponibili.

Si basa su librerie native disponibili per molte piattaforme diverse (macOS, Windows, Linux, Android, ecc.). Potremmo anche cambiare il backend alla piattaforma nd4j-cuda-8.0 , se volessimo eseguire calcoli su una scheda grafica che supporta il modello di programmazione CUDA.

4. Preparazione dei dati

4.1. Preparazione del file DataSet

Scriveremo il "Hello World" dell'apprendimento automatico - classificazione del set di dati del fiore dell'iride. Si tratta di una serie di dati raccolti dai fiori di specie diverse ( Iris setosa , Iris versicolor e Iris virginica ).

Queste specie differiscono in lunghezza e larghezza di petali e sepali. Sarebbe difficile scrivere un algoritmo preciso che classifichi un elemento di dati di input (cioè, determini a quale specie appartiene un particolare fiore). Ma una rete neurale ben addestrata può classificarla rapidamente e con pochi errori.

Utilizzeremo una versione CSV di questi dati, dove le colonne 0..3 contengono le diverse caratteristiche della specie e la colonna 4 contiene la classe del record, o la specie, codificata con un valore 0, 1 o 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. Vettorializzare e leggere i dati

Codifichiamo la classe con un numero perché le reti neurali funzionano con i numeri. La trasformazione di elementi di dati del mondo reale in serie di numeri (vettori) è chiamata vettorizzazione : deeplearning4j utilizza la libreria datavec per farlo.

Per prima cosa, usiamo questa libreria per inserire il file con i dati vettorizzati. Durante la creazione del CSVRecordReader , possiamo specificare il numero di righe da saltare (ad esempio, se il file ha una riga di intestazione) e il simbolo separatore (nel nostro caso una virgola):

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

Per scorrere i record, possiamo utilizzare una qualsiasi delle molteplici implementazioni dell'interfaccia DataSetIterator . I set di dati possono essere piuttosto grandi e la possibilità di impaginare o memorizzare nella cache i valori potrebbe tornare utile.

Ma il nostro piccolo set di dati contiene solo 150 record, quindi leggiamo tutti i dati in memoria contemporaneamente con una chiamata a iterator.next () .

Specifichiamo anche l'indice della colonna della classe che nel nostro caso è uguale al conteggio delle caratteristiche (4) e il numero totale delle classi (3).

Inoltre, tieni presente che abbiamo bisogno di mescolare il set di dati per eliminare l'ordine delle classi nel file originale.

Specifichiamo un seme casuale costante (42) invece della chiamata predefinita System.currentTimeMillis () in modo che i risultati del mescolamento siano sempre gli stessi. Questo ci permette di ottenere risultati stabili ogni volta che eseguiremo il programma:

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

4.3. Normalizzare e dividere

Un'altra cosa che dovremmo fare con i dati prima dell'addestramento è normalizzarli. La normalizzazione è un processo in due fasi:

  • raccolta di alcune statistiche sui dati (fit)
  • cambiare (trasformare) i dati in qualche modo per renderli uniformi

La normalizzazione può differire per diversi tipi di dati.

Ad esempio, se vogliamo elaborare immagini di varie dimensioni, dobbiamo prima raccogliere le statistiche sulle dimensioni e quindi ridimensionare le immagini a una dimensione uniforme.

Ma per i numeri, normalizzazione di solito significa trasformarli in una cosiddetta distribuzione normale. La classe NormalizerStandardize può aiutarci in questo:

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

Ora che i dati sono preparati, dobbiamo dividere il set in due parti.

La prima parte verrà utilizzata in una sessione di formazione. Useremo la seconda parte dei dati (che la rete non vedrebbe affatto) per testare la rete addestrata.

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

Ora creiamo una rete neurale dalla configurazione, inizializziamo ed eseguiamola:

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

Ora possiamo testare il modello addestrato utilizzando il resto del set di dati e verificare i risultati con metriche di valutazione per tre classi:

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

Se ora stampiamo eval.stats () , vedremo che la nostra rete è abbastanza brava a classificare i fiori di iris, sebbene abbia confuso la classe 1 con la classe 2 tre volte.

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) ========================================================================

Il generatore di configurazione fluente ci consente di aggiungere o modificare rapidamente i livelli della rete o di modificare alcuni altri parametri per vedere se il nostro modello può essere migliorato.

7. Conclusione

In questo articolo, abbiamo costruito una rete neurale semplice ma potente utilizzando la libreria deeplearning4j.

Come sempre, il codice sorgente dell'articolo è disponibile su GitHub.