En guide til deeplearning4j

1. Introduksjon

I denne artikkelen oppretter vi et enkelt nevralt nettverk med deeplearning4j (dl4j) biblioteket - et moderne og kraftig verktøy for maskinlæring.

Før vi begynner, ikke at denne guiden ikke krever dyp kunnskap om lineær algebra, statistikk, maskinlæringsteori og mange andre emner som er nødvendige for en velbegrunnet ML-ingeniør.

2. Hva er dyp læring?

Nevrale nettverk er beregningsmodeller som består av sammenkoblede lag med noder.

Noder er nevronlignende prosessorer av numeriske data. De tar data fra inngangene sine, bruker noen vekter og funksjoner på disse dataene og sender resultatene til utgangene. Slike nettverk kan trenes med noen eksempler på kildedataene.

Trening er i hovedsak å lagre noen numeriske tilstander (vekter) i nodene som senere påvirker beregningen. Treningseksempler kan inneholde dataelementer med funksjoner og visse kjente klasser av disse elementene (for eksempel “dette settet med 16 × 16 piksler inneholder et håndskrevet bokstav“ a ”).

Etter at treningen er ferdig, et nevralt nettverk kanhente informasjon fra nye data, selv om den ikke har sett disse spesifikke dataelementene før. Et godt modellert og godt trent nettverk kan gjenkjenne bilder, håndskrevne bokstaver, tale, behandle statistiske data for å produsere resultater for forretningsinformasjon og mye mer.

Dype nevrale nettverk ble mulig de siste årene, med fremskritt av høy ytelse og parallell databehandling. Slike nettverk skiller seg fra enkle nevrale nettverkde består av flere mellomliggende (eller skjulte) lag. Denne strukturen gjør at nettverk kan behandle data på en mye mer komplisert måte (på en rekursiv, tilbakevendende, konvolusjonell måte, etc.), og trekke ut mye mer informasjon fra den.

3. Sette opp prosjektet

For å bruke biblioteket trenger vi minst Java 7. På grunn av noen innfødte komponenter fungerer det bare med 64-biters JVM-versjonen.

Før vi begynner med guiden, la oss sjekke om kravene er oppfylt:

$ java -versjon java versjon "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)

Først, la oss legge til de nødvendige bibliotekene i Maven pom.xml fil. Vi trekker ut versjonen av biblioteket til en eiendomsoppføring (for den siste versjonen av bibliotekene, sjekk ut Maven Central-arkivet):

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

Noter det nd4j-native-plattform avhengighet er en av flere tilgjengelige implementeringer.

Den er avhengig av innfødte biblioteker tilgjengelig for mange forskjellige plattformer (macOS, Windows, Linux, Android, etc.). Vi kan også bytte backend til nd4j-cuda-8.0-plattform, hvis vi ønsket å utføre beregninger på et grafikkort som støtter CUDA-programmeringsmodell.

4. Klargjøre dataene

4.1. Klargjøre datasettfilen

Vi skriver "Hello World" for maskinlæring - klassifisering av datasettet for irisblomster. Dette er et sett med data som ble samlet fra blomster av forskjellige arter (Iris setosa, Iris versicolor, og Iris virginica).

Disse artene varierer i lengder og bredder på kronblad og kronblad. Det ville være vanskelig å skrive en presis algoritme som klassifiserer et inngangsdataelement (dvs. bestemmer hvilken art tilhører en bestemt blomst). Men et godt trent nevralt nettverk kan klassifisere det raskt og med små feil.

Vi skal bruke en CSV-versjon av disse dataene, der kolonnene 0..3 inneholder de forskjellige funksjonene til arten og kolonne 4 inneholder klassen til posten, eller arten, kodet med verdien 0, 1 eller 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. Vektorisere og lese dataene

Vi koder klassen med et tall fordi nevrale nettverk jobber med tall. Transformering av virkelige dataelementer til serie med tall (vektorer) kalles vektorisering - deeplearning4j bruker datavec-biblioteket til å gjøre dette.

La oss først bruke dette biblioteket til å legge inn filen med vektoriserte data. Når du oppretter CSVRecordReader, kan vi spesifisere antall linjer som skal hoppes over (for eksempel hvis filen har en overskriftslinje) og skilletegn (i vårt tilfelle et komma):

prøv (RecordReader recordReader = ny CSVRecordReader (0, ',')) {recordReader.initialize (ny FileSplit (ny ClassPathResource ("iris.txt"). getFile ())); //…}

For å iterere over postene, kan vi bruke noen av de mange implementeringene av DataSetIterator grensesnitt. Datasettene kan være ganske massive, og muligheten til å side eller cache verdiene kan komme til nytte.

Men det lille datasettet vårt inneholder bare 150 poster, så la oss lese alle dataene i minnet med en gang iterator.next ().

Vi spesifiserer også indeksen til klassekolonnen som i vårt tilfelle er det samme som antall funksjoner (4) og totalt antall klasser (3).

Vær også oppmerksom på at vi må stokke datasettet for å bli kvitt klassebestillingen i originalfilen.

Vi spesifiserer et konstant tilfeldig frø (42) i stedet for standard System.currentTimeMillis () ring slik at resultatene av stokkingen alltid vil være de samme. Dette gjør at vi kan få stabile resultater hver gang vi kjører programmet:

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

4.3. Normalisering og splitting

En annen ting vi bør gjøre med dataene før trening er å normalisere dem. Normaliseringen er en tofaseprosess:

  • innsamling av litt statistikk om dataene (passform)
  • endre (transformere) dataene på en eller annen måte for å gjøre det enhetlig

Normalisering kan variere for forskjellige typer data.

For eksempel, hvis vi ønsker å behandle bilder i forskjellige størrelser, bør vi først samle inn størrelsesstatistikken og deretter skalere bildene til en jevn størrelse.

Men for tall betyr normalisering vanligvis å transformere dem til en såkalt normalfordeling. De Normaliser Standardiser klassen kan hjelpe oss med det:

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

Nå som dataene er klargjort, må vi dele settet i to deler.

Den første delen vil bli brukt i en treningsøkt. Vi bruker den andre delen av dataene (som nettverket ikke ser i det hele tatt) for å teste det trente nettverket.

Dette vil tillate oss å verifisere at klassifiseringen fungerer som den skal. Vi tar 65% av dataene (0,65) for opplæringen og lar resten være 35% for testingen:

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

5. Klargjøre nettverkskonfigurasjonen

5.1. Flytende konfigurasjonsbygger

Nå kan vi bygge en konfigurasjon av nettverket vårt med en fancy flytende byggherre:

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

Selv med denne forenklede flytende måten å bygge en nettverksmodell på, er det mye å fordøye og mange parametere å finjustere. La oss bryte ned denne modellen.

5.2. Angi nettverksparametere

De iterasjoner () byggemetode angir antall optimaliseringsgjentakelser.

Den iterative optimaliseringen betyr å utføre flere pass på treningssettet til nettverket konvergerer til et godt resultat.

Vanligvis, når vi trener på ekte og store datasett, bruker vi flere epoker (fullstendige overføringer av data gjennom nettverket) og en iterasjon for hver epoke. Men siden vårt første datasett er minimalt, bruker vi en epoke og flere iterasjoner.

De aktivering () er en funksjon som kjører inne i en node for å bestemme utdataene.

Den enkleste aktiveringsfunksjonen vil være lineær f (x) = x. Men det viser seg at bare ikke-lineære funksjoner tillater nettverk å løse komplekse oppgaver ved å bruke noen få noder.

Det er mange forskjellige aktiveringsfunksjoner tilgjengelig som vi kan slå opp i org.nd4j.linalg.activations.Activation enum. Vi kan også skrive aktiveringsfunksjonen vår om nødvendig. Men vi bruker den medfølgende hyperbolske tangensfunksjonen (tanh).

De weightInit () metoden angir en av de mange måtene å sette opp de første vektene for nettverket. Korrekte innledende vekter kan påvirke resultatene av treningen dypt. Uten å gå for mye inn i matematikken, la oss sette den på en form for gaussisk fordeling (WeightInit.XAVIER), da dette vanligvis er et godt valg for en start.

Alle andre metoder for vektinitialisering kan slås opp i org.deeplearning4j.nn.weights.WeightInit enum.

Læringsgrad er en avgjørende parameter som sterkt påvirker nettets evne til å lære.

Vi kan bruke mye tid på å tilpasse denne parameteren i et mer komplekst tilfelle. Men for vår enkle oppgave, bruker vi en ganske betydelig verdi på 0,1 og setter den opp med learningRate () byggemetode.

Et av problemene med å trene nevrale nettverk er et tilfelle av overmontering når et nettverk "lagrer" treningsdataene.

Dette skjer når nettverket setter for høye vekter for treningsdataene og gir dårlige resultater på andre data.

For å løse dette problemet skal vi sette opp regulering av l2 med linjen .regularization (true) .l2 (0.0001). Regularisering “straffer” nettverket for for store vekter og forhindrer overmontering.

5.3. Bygg nettverkslag

Deretter lager vi et nettverk av tette (også kjent som fullkobling) lag.

Det første laget skal inneholde samme mengde noder som kolonnene i treningsdataene (4).

Det andre tette laget vil inneholde tre noder. Dette er verdien vi kan variere, men antall utganger i forrige lag må være det samme.

Det endelige utgangslaget skal inneholde antall noder som samsvarer med antall klasser (3). Strukturen til nettverket er vist på bildet:

Etter vellykket opplæring har vi et nettverk som mottar fire verdier via inngangene og sender et signal til en av de tre utgangene. Dette er en enkel klassifikator.

Til slutt, for å fullføre byggingen av nettverket, setter vi opp forplantning (en av de mest effektive treningsmetodene) og deaktiverer trening med linjen .backprop (true) .pretrain (false).

6. Opprette og trene et nettverk

La oss nå opprette et nevralt nettverk fra konfigurasjonen, initialisere og kjøre det:

MultiLayerNetwork-modell = ny MultiLayerNetwork (konfigurasjon); modell.init (); model.fit (trainingData);

Nå kan vi teste den trente modellen ved å bruke resten av datasettet og verifisere resultatene med evalueringsberegninger for tre klasser:

INDArray-utgang = modell.utgang (testData.getFeatureMatrix ()); Evalueringseval = ny evaluering (3); eval.eval (testData.getLabels (), output);

Hvis vi nå skriver ut eval.stats (), får vi se at nettverket vårt er ganske bra på å klassifisere irisblomster, selv om det gjorde feil på klasse 1 for klasse 2 tre ganger.

Eksempler merket som 0 klassifisert etter modell som 0: 19 ganger Eksempler merket som 1 klassifisert etter modell som 1: 16 ganger Eksempler merket som 1 klassifisert etter modell som 2: 3 ganger Eksempler merket som 2 klassifisert etter modell som 2: 15 ganger == ========================= Poeng =========================== =============== Antall klasser: 3 Nøyaktighet: 0,9434 Presisjon: 0,9444 Tilbakekalling: 0,9474 F1-poengsum: 0,9411 Presisjon, tilbakekalling og F1: makro-gjennomsnitt (like vektet gjennomsnitt av 3 klasser ) ===================================================== =========================

Den flytende konfigurasjonsbyggeren lar oss legge til eller endre lag i nettverket raskt, eller tilpasse noen andre parametere for å se om modellen vår kan forbedres.

7. Konklusjon

I denne artikkelen har vi bygget et enkelt, men kraftig nevralt nettverk ved hjelp av deeplearning4j-biblioteket.

Som alltid er kildekoden for artikkelen tilgjengelig på GitHub.