Een gids voor Deeplearning4j

1. Inleiding

In dit artikel maken we een eenvoudig neuraal netwerk met de deeplearning4j (dl4j) -bibliotheek - een moderne en krachtige tool voor machine learning.

Voordat we beginnen, is het niet dat deze gids geen grondige kennis vereist van lineaire algebra, statistiek, machine learning-theorie en tal van andere onderwerpen die nodig zijn voor een goed onderlegde ML-engineer.

2. Wat is diep leren?

Neurale netwerken zijn computermodellen die bestaan ​​uit onderling verbonden lagen van knooppunten.

Knooppunten zijn neuronachtige processors van numerieke gegevens. Ze halen gegevens uit hun invoer, passen een aantal gewichten en functies toe op deze gegevens en sturen de resultaten naar de uitvoer. Een dergelijk netwerk kan worden getraind met enkele voorbeelden van de brongegevens.

Trainen is in wezen het opslaan van een numerieke toestand (gewichten) in de knooppunten, wat later de berekening beïnvloedt. Trainingsvoorbeelden kunnen gegevensitems met kenmerken en bepaalde bekende klassen van deze items bevatten (bijvoorbeeld "deze set van 16 × 16 pixels bevat een handgeschreven letter" a ").

Nadat de training is voltooid, een neuraal netwerk kaninformatie afleiden uit nieuwe gegevens, zelfs als het deze specifieke gegevensitems niet eerder heeft gezien. Een goed gemodelleerd en goed opgeleid netwerk kan afbeeldingen, handgeschreven brieven, spraak herkennen, statistische gegevens verwerken om resultaten voor business intelligence te produceren en nog veel meer.

Diepe neurale netwerken zijn de afgelopen jaren mogelijk geworden met de opkomst van high-performance en parallel computing. Dergelijke netwerken verschillen daarin van eenvoudige neurale netwerkenze bestaan ​​uit meerdere tussenliggende (of verborgen) lagen. Deze structuur stelt netwerken in staat om gegevens op een veel gecompliceerder manier te verwerken (op een recursieve, terugkerende, convolutionele manier, enz.), En er veel meer informatie uit te halen.

3. Het opzetten van het project

Om de bibliotheek te gebruiken hebben we minimaal Java 7 nodig. Bovendien werkt het, vanwege enkele native componenten, alleen met de 64-bit JVM-versie.

Laten we, voordat we met de gids beginnen, controleren of aan de vereisten is voldaan:

$ java -version java-versie "1.8.0_131" Java (TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot (TM) 64-bits server VM (build 25.131-b11, gemengde modus)

Laten we eerst de vereiste bibliotheken toevoegen aan onze Maven pom.xml het dossier. We extraheren de versie van de bibliotheek naar een eigenschapvermelding (voor de nieuwste versie van de bibliotheken, bekijk de Maven Central-repository):

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

Let daar op nd4j-native-platform afhankelijkheid is een van de verschillende beschikbare implementaties.

Het is gebaseerd op native bibliotheken die beschikbaar zijn voor veel verschillende platforms (macOS, Windows, Linux, Android, enz.). We kunnen ook de backend omzetten naar nd4j-cuda-8.0-platform, als we berekeningen wilden uitvoeren op een grafische kaart die het CUDA-programmeermodel ondersteunt.

4. Voorbereiden van de gegevens

4.1. Het DataSet-bestand voorbereiden

We schrijven de "Hallo wereld" van machine learning - classificatie van de irisbloemgegevensverzameling. Dit is een set gegevens die is verzameld uit de bloemen van verschillende soorten (Iris setosa, Iris versicolor, en Iris virginica).

Deze soorten verschillen in lengte en breedte van bloembladen en kelkblaadjes. Het zou moeilijk zijn om een ​​nauwkeurig algoritme te schrijven dat een invoergegevensitem classificeert (d.w.z. bepaalt tot welke soort een bepaalde bloem behoort). Maar een goed getraind neuraal netwerk kan het snel en met kleine fouten classificeren.

We gaan een CSV-versie van deze gegevens gebruiken, waarbij kolommen 0..3 de verschillende kenmerken van de soort bevatten en kolom 4 de klasse van het record, of de soort, gecodeerd met een waarde 0, 1 of 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. Vectoriseren en lezen van de gegevens

We coderen de klas met een nummer omdat neurale netwerken met nummers werken. Het omzetten van gegevensitems uit de echte wereld in getallenreeksen (vectoren) wordt vectorisatie genoemd - deeplearning4j gebruikt de datavec-bibliotheek om dit te doen.

Laten we eerst deze bibliotheek gebruiken om het bestand met de gevectoriseerde gegevens in te voeren. Bij het maken van het CSVRecordReader, kunnen we het aantal regels specificeren dat moet worden overgeslagen (als het bestand bijvoorbeeld een kopregel heeft) en het scheidingsteken (in ons geval een komma):

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

Om de records te herhalen, kunnen we een van de meerdere implementaties van het DataSetIterator koppel. De datasets kunnen behoorlijk groot zijn, en de mogelijkheid om de waarden te pagineren of in de cache op te slaan, kan van pas komen.

Maar onze kleine dataset bevat slechts 150 records, dus laten we alle gegevens in één keer in het geheugen lezen met een oproep van iterator.next ().

We specificeren ook de index van de klassekolom wat in ons geval hetzelfde is als het aantal functies (4) en het totale aantal lessen (3).

Merk ook op dat we moeten de dataset door elkaar halen om de klassevolgorde in het originele bestand te verwijderen.

We specificeren een constante willekeurige seed (42) in plaats van de standaardwaarde System.currentTimeMillis () call zodat de resultaten van het schudden altijd hetzelfde zijn. Dit stelt ons in staat om elke keer dat we het programma uitvoeren, stabiele resultaten te krijgen:

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

4.3. Normaliseren en splitsen

Een ander ding dat we moeten doen met de gegevens voordat we gaan trainen, is deze normaliseren. De normalisatie is een proces in twee fasen:

  • het verzamelen van enkele statistieken over de gegevens (fit)
  • de gegevens op de een of andere manier veranderen (transformeren) om ze uniform te maken

Normalisatie kan verschillen voor verschillende soorten gegevens.

Als we bijvoorbeeld afbeeldingen van verschillende formaten willen verwerken, moeten we eerst de groottestatistieken verzamelen en vervolgens de afbeeldingen schalen naar een uniform formaat.

Maar voor getallen betekent normalisatie meestal dat ze worden omgezet in een zogenaamde normale verdeling. De NormalizerStandardize de klas kan ons daarbij helpen:

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

Nu de gegevens zijn voorbereid, moeten we de set in twee delen splitsen.

Het eerste deel wordt gebruikt in een trainingssessie. We gebruiken het tweede deel van de gegevens (die het netwerk helemaal niet zou zien) om het getrainde netwerk te testen.

Dit zou ons in staat stellen om te controleren of de classificatie correct werkt. We nemen 65% van de gegevens (0,65) voor de training en de rest 35% voor het testen:

SplitTestAndTrain testAndTrain = allData.splitTestAndTrain (0,65); DataSet trainingData = testAndTrain.getTrain (); DataSet testData = testAndTrain.getTest ();

5. Voorbereiden van de netwerkconfiguratie

5.1. Vloeiende configuratiebouwer

Nu kunnen we een configuratie van ons netwerk bouwen met een mooie, vloeiende bouwer:

MultiLayerConfiguration-configuratie = nieuwe NeuralNetConfiguration.Builder () .iterations (1000) .activation (Activation.TANH) .weightInit (WeightInit.XAVIER) .learningRate (0.1). Regularization (true) .l2 (0.0001) .list () .layer ( 0, nieuwe DenseLayer.Builder (). NIn (FEATURES_COUNT) .nOut (3) .build ()) .layer (1, nieuwe DenseLayer.Builder (). NIn (3) .nOut (3) .build ()). layer (2, nieuwe OutputLayer.Builder (LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) .activation (Activation.SOFTMAX) .nIn (3) .nOut (CLASSES_COUNT) .build ()) .backprop (true) .pretrain (false) .build ( );

Zelfs met deze vereenvoudigde, vloeiende manier om een ​​netwerkmodel te bouwen, moet er veel worden verwerkt en moeten er veel parameters worden aangepast. Laten we dit model opsplitsen.

5.2. Netwerkparameters instellen

De iteraties builder-methode specificeert het aantal optimalisatie-iteraties.

De iteratieve optimalisatie betekent het uitvoeren van meerdere passages op de trainingsset totdat het netwerk convergeert naar een goed resultaat.

Gewoonlijk gebruiken we bij het trainen op echte en grote datasets meerdere tijdperken (volledige gegevensdoorgangen door het netwerk) en één iteratie voor elk tijdperk. Maar aangezien onze initiële dataset minimaal is, gebruiken we één epoch en meerdere iteraties.

De activering () is een functie die binnen een knooppunt wordt uitgevoerd om de uitvoer ervan te bepalen.

De eenvoudigste activeringsfunctie zou lineair zijn f (x) = x. Maar het blijkt dat alleen niet-lineaire functies netwerken in staat stellen complexe taken op te lossen door een paar knooppunten te gebruiken.

Er zijn veel verschillende activeringsfuncties beschikbaar die we kunnen opzoeken in het org.nd4j.linalg.activations.Activation opsomming. We kunnen indien nodig ook onze activeringsfunctie schrijven. Maar we zullen de meegeleverde hyperbolische tangens (tanh) -functie gebruiken.

De gewichtInit () methode specificeert een van de vele manieren om de begingewichten voor het netwerk in te stellen. De juiste begingewichten kunnen de resultaten van de training sterk beïnvloeden. Laten we, zonder al te veel in de wiskunde in te gaan, het instellen op een vorm van Gauss-verdeling (WeightInit.XAVIER), aangezien dit meestal een goede keuze is om mee te beginnen.

Alle andere methoden voor het initialiseren van het gewicht kunnen worden opgezocht in het org.deeplearning4j.nn.weights.WeightInit opsomming.

Leersnelheid is een cruciale parameter die een diepgaande invloed heeft op het leervermogen van het netwerk.

We zouden veel tijd kunnen besteden aan het aanpassen van deze parameter in een complexer geval. Maar voor onze eenvoudige taak zullen we een vrij significante waarde van 0,1 gebruiken en deze instellen met de learningRate () bouwer methode.

Een van de problemen bij het trainen van neurale netwerken is een geval van overfitting wanneer een netwerk de trainingsgegevens "onthoudt".

Dit gebeurt wanneer het netwerk buitensporig hoge gewichten instelt voor de trainingsgegevens en slechte resultaten oplevert voor andere gegevens.

Om dit probleem op te lossen, gaan we l2-regularisatie opzetten met de lijn . regularization (true) .l2 (0.0001). Regularisatie 'straft' het netwerk voor te grote gewichten en voorkomt overfitting.

5.3. Netwerklagen bouwen

Vervolgens creëren we een netwerk van dichte (ook bekend als volledig verbindende) lagen.

De eerste laag moet hetzelfde aantal knooppunten bevatten als de kolommen in de trainingsgegevens (4).

De tweede dichte laag bevat drie knooppunten. Dit is de waarde die we kunnen variëren, maar het aantal outputs in de vorige laag moet hetzelfde zijn.

De laatste uitvoerlaag moet het aantal knooppunten bevatten dat overeenkomt met het aantal klassen (3). De structuur van het netwerk wordt weergegeven in de afbeelding:

Na een succesvolle training hebben we een netwerk dat vier waarden ontvangt via de ingangen en een signaal naar een van de drie uitgangen stuurt. Dit is een eenvoudige classificatie.

Tot slot, om het opbouwen van het netwerk te voltooien, hebben we back-propagation opgezet (een van de meest effectieve trainingsmethoden) en pre-training met de lijn uitgeschakeld .backprop (true) .pretrain (false).

6. Een netwerk opzetten en trainen

Laten we nu een neuraal netwerk maken op basis van de configuratie, initialiseren en uitvoeren:

MultiLayerNetwork-model = nieuw MultiLayerNetwork (configuratie); model.init (); model.fit (trainingData);

Nu kunnen we het getrainde model testen door de rest van de dataset te gebruiken en de resultaten verifiëren met evaluatiestatistieken voor drie klassen:

INDArray-uitvoer = model.output (testData.getFeatureMatrix ()); Evaluatie-evaluatie = nieuwe evaluatie (3); eval.eval (testData.getLabels (), uitvoer);

Als we nu het eval.stats (), we zullen zien dat ons netwerk redelijk goed is in het classificeren van irisbloemen, hoewel het klasse 1 wel drie keer verwarde met klasse 2.

Voorbeelden gelabeld als 0 geclassificeerd per model als 0:19 keer Voorbeelden gelabeld als 1 geclassificeerd per model als 1:16 keer Voorbeelden gelabeld als 1 geclassificeerd per model als 2: 3 keer Voorbeelden gelabeld als 2 geclassificeerd per model als 2:15 keer == ======================== Scores ========================= =============== # klassen: 3 Nauwkeurigheid: 0,9434 Precisie: 0,9444 Terugroepen: 0,9474 F1-score: 0,9411 Precisie, terugroepen & F1: macrogemiddeld (gelijk gewogen gemiddelde van 3 klassen ) ==================================================== =======================

De vloeiende configuratiebouwer stelt ons in staat om snel lagen van het netwerk toe te voegen of te wijzigen, of enkele andere parameters aan te passen om te zien of ons model kan worden verbeterd.

7. Conclusie

In dit artikel hebben we een eenvoudig maar krachtig neuraal netwerk gebouwd met behulp van de deeplearning4j-bibliotheek.

Zoals altijd is de broncode voor het artikel beschikbaar op GitHub.