Neurale nett har blitt poppis igjen. Sist jeg lekte med slike, så forsøkte jeg å trene opp nett, implementert i Turbo C (2 disketter…)  på en 12 MHz IBM AT. Det var ikke spesielt moro. Sånn i etterpåklokskapens lys, så er det vel heller ikke helt utelukket at jeg kan ha blingsa litt på på et par av formlene.

Jeg nøyde meg derfor med å lese om maskinlæring og AI istedet. Jeg leste Neuromancer, Burning Chrome og Mona Lisa Overdrive med det ene øyet og alt for vanskelige bøker og papers med det andre. Jeg skal ærlig innrømme at jeg kanskje fikk mest ut av William Gibson sine romaner på den tiden, men det var likevel moro å skrape litt i overflata på algoritmene, slik at man kunne bli ufordragelig i baren i Bodegaen, når folk begynte å bli filosofiske, etter et par øl for mye.

Det var på starten av 90-tallet et ganske markant gap mellom science fiction-AI og AI i den virkelige verden. Selv om neurale nett var den nærmeste analogen man hadde til neuroner og synapser i biologiske systemer, så endte det gjerne opp i rene teoretiske øvelser. Vi manglet nok “oomph” under panseret på datamaskinene våre, til at vi kunne trene opp litt større nett, som gjorde særlig spennende saker . (Trening av nett er tungt, men kjøring av et ferdig trent nett, koker ned til et lite antall matriseoperasjoner, som man kan kjøre på en liten mikrokontroller med flyttallsstøtte, uten videre problem).

I dag, så er det litt anderledes. Har du handlet deg en datamaskin for å kunne “surfe på nett”, “bruke nettbank” eller “levere lottokupong”, så har du etter all sannsynlighet nok prosesseringskraft tilgjengelig for å kunne leke deg med enkel AI uten at maskina blir svett. Gapet er i ferd med å lukkes og jeg har en liten følelse av at det nå, gitt ressursene til eksempelvis Google, nå begynner å bli “sping tinglingly”smalt. Litt usikker på om våre venner, politikerne, er helt forberedt på scenariene vi nå vil få se, der den selvkjørende bilen din, eksempelvis velger å drepe deg, ved å kjøre i grøfta, fremfor å kjøre ned fotgjengeren i gangfeltet, eller når den første kunstige intelligensen blir installert i droner for å ta kill-beslutninger, slik at ikke soldatene skal få PTSD etter litt for mange timer foran skjermen.

Det eneste en AI forsøker å gjøre er å minimalisere kostfunksjonen sin – en funksjon spesifisert av mennesker – forhåpentligvis korrekt, og forhåpentligvis en funksjon, som ikke har utilsiktede sideeffekter.

Genetiske algoritmer, fuzzy logic og neurale nett var all the rage tidlig på 90-tallet, men så forsvant de under radaren min. Inntil i år. Fire ting skjedde (omtrent i denne rekkefølgen) :

  1. En kompis (Hei, Ståle !) sendte meg en link til “The Unreasonable Effectiveness of Recurrent Neural Networks”.
  2. “Deep Dream”-videoer og bilder begynte å poppe opp her og der i newsfeeden min.
  3. Jeg traff en freelance-koder i Roma, som jobbet med AI. Jeg beynttet anledningen til å spørre litt om det jeg trodde jeg hadde lært om NN på 90-tallet fremdeles var good shit.
  4. Google opensourcet TensorFlow.

Inspired, I became…

Det var på tide med et lite litteraturstudie igjen. Dette er vel strengt tatt pensum for kidsa på Gløshaugen, men siden jeg er mer i “halvstudert røver” / “old fart”-kategorien, så må jeg stadig vekk gjøre slike øvelser for å henge med.

Valget sto mellom å bruke tid på tensorflow, eller forsøke å plugge kunnskapshullene som oppsto når jeg prioriterte William Gibson sine romaner fremfor å forsøke forstå matematikken bak minimalisering av kost-funksjonen i et neuralt nett via backpropagation og deltaregelen.

Siden jeg er selværklært maker (begynner så smått å bli et litt utslitt begrep, men), så ønsket jeg også en implementasjon, som var såpass lettbeint at den kunne trenes på en PC (eks med styrings-/sensortelemetri-data fra en liten robot) og så lett kunne kjøre det trente nettet på svært billig embedded hardware.

Jeg fyrte så opp C++-kompilatoren og skred til verket. Første forsøk bar galt av sted i.o.m. at jeg lot OO-nissen i meg få slippe til med helt feil abstraksjoner (Jeg er et barn av OO-generasjonen, men kjenner litt på en dragning mot funksjonell programmering). Jeg modellerte min forståelse av et NN, Dvs “nettverk”, “lag”, “inputlag”, “skjult lag”, “outputlag” etc. Koden eksploderte og ting ble ikke spesielt pent.

Det gikk 2-3 kvelder før jeg innså at de eneste abstraksjonene jeg trengte var “nettverk”, “treningssett”, “matrise” og “vektor”.  Dette ga en 1:1 mapping med begrepsverdenen i litteraturen og algoritmene. Koden ble da tight. Vi snakker 3-400 linjer (inludert tracing / graphvizoutput) for implementasjon av klasser som lar deg spesifisere nettverk med vilkårlig antall lag og vilkårlig antall noder i hvert lag. Eksempelvis så vil følgende deklarasjon resultere i et nettverk med 3 lag, der man har 5 inputnoder, 5 skjulte noder og ett outputnode:

   Network n({5, 5, 1});

Ønsker man en visuell representasjon av nettverket, så kan man generere en graf-representasjon for GraphViz:

   n.ExportAsDigraph("d:\\MyNetwork.gv");

Formatet for treningssettene er en liste med kommaseparerte inputverdier -> liste med kommaseparerte output. Eksempelvis, så vil treningssettet for sannhetstabellen for XOR, se slik ut:

   # XOR
   1,1 -> 0
   1,0 -> 1
   0,1 -> 1
   0,0 -> 0

Kode for trening av nettverket vil kunne se noenlunde slik ut:

   TrainingSet set("MySet.training");
   int iter = n.Train(set, learningrate, maxError, maxIterations);

Jeg brukte enkle nettverk for å debugge funksjonene for backpropagation-algoritmen. Treningssettene under debuggingsesjoner var typisk sannhetstabeller for AND, OR og XOR.

Men,…

En ting er enkle mappinger mellom input og output. Det som ga meg TOTAL BAKOVERSVEIS var når jeg forsøkte meg på et treningssett, som i praksis krevde at nettet var translasjonsinvariant. Jeg bestemte meg for å sjekke om koden ville klare å identifisere bitpar i et ringbuffer. Eksempelvis klassifisere 11000, 01100, 00110, 00011 og 10001, som samme greie, men forkaste alle andre bitmønster. Jeg bestemte meg for å teste med et 5:5:1 nett:

network2

Og dette er trace-output fra nettet…

1 1 0 0 0 -> 0.98942
0 1 1 0 0 -> 0.955088
0 0 1 1 0 -> 0.994225
0 0 0 1 1 -> 0.956921
1 0 0 0 1 -> 0.946448
0 0 0 0 0 -> 8.01082e-007
1 1 1 0 0 -> 0.000139072
0 1 1 1 0 -> 0.000123884
0 0 1 1 1 -> 0.00241042
1 0 0 1 1 -> 0.000115517
1 1 0 0 1 -> 0.00316957
0 0 0 0 0 -> 8.01082e-007
1 0 0 0 0 -> 0.0152408
0 1 0 0 0 -> 0.000225992
0 0 1 0 0 -> 0.000161386
0 0 0 1 0 -> 0.0165893
0 0 0 0 1 -> 0.0240119
1 1 1 1 1 -> 0.00393661
1 1 1 1 0 -> 0.000946888
0 1 1 1 1 -> 4.03198e-005
1 0 1 1 1 -> 0.00302191
1 1 0 1 1 -> 0.00172643
1 1 1 0 1 -> 5.37666e-005
1 1 1 1 0 -> 0.000946888
Training result : PASSED in 227 iterations.
Layering structure : 5 5 1

matrix[i, j]
-7.381501e-001 4.910103e-001 2.348347e+000 5.589301e-001 1.393025e-001
6.232929e+000 -3.010492e+000 -3.575540e+000 6.308999e+000 -2.263603e+000
7.602698e+000 -4.410647e+000 -4.660858e+000 7.579302e+000 -3.254401e+000
2.161218e-001 5.300884e+000 -3.041684e-001 -8.590031e+000 2.228310e+000
-8.501116e+000 2.924318e-002 4.881199e+000 5.905061e-002 2.334400e+000

matrix[k, l]
3.230551e+000 8.396336e+000 1.152779e+001 1.566610e+001 1.500300e+001

Tankene går til åpningsstrofene i “For What it’s worth” (Buffalo Springfield). “There’s something happening here. What it is ain’t exactly clear…”

Next up: “Andre greier jeg ikke lærte på skolen . D.v.s RNN, Markovmodeller & annen crazy shit vi trenger å implementere Skynet”