Egyszerű neurális hálózatok építése Pythonban

László Harri Németh
Pythonarium
Published in
7 min readJun 13, 2019

--

Ebben a cikkben egyszerű neurális hálózatokat építek Python nyelven. A feladathoz kizárólag a következő Python könyvtárakat használom: numpy, tqdm.

A numpy csomag a tudományos számításokhoz használatos csomag a Pythonban. A tqdm pedig egy progress bar megjelenítő segéd csomag.

Az itt bemutatott neurális hálózatok egyszerű kétváltozós logikai függvényeket modelleznek. Kitérek arra, hogy 1 db neuronnal milyen logikai függvényeket lehet megvalósítani.

A weboldal teljes tartalma elérhető a Githubon.

Photo by Franck V. on Unsplash

Bevezető

Jelen cikknek nem célja, hogy általános alapfogalmakkal ismertesse meg az Olvasót a mesterséges intelligenciával, gépi tanulással, neurális hálózatokkal, mély tanulással kapcsolatban. A referenciákban felsorolt és egyéb szakirodalmakban alaposan lehet tájékozódni ezekkel a témákkal kapcsolatban.

A cikkben implementált neurális hálózatok logikai függvényeket valósítanak meg, az ÉS illetve VAGY függvényt, valamint a kizáró vagy, XOR függvényt. Látni fogjuk, hogy egyetlen neuronnal mit lehet megvalósítani, és mi az, amihez már több neuronból álló hálózat szükséges.

A cikk tartalmának értelmezéséhez a következő alapfogalmak ismerete szükséges, ezekkel csak érintőlegesen foglalkozom ebben a cikkben:

  • neurális hálózat
  • tanítási fázis
  • előreterjesztés (feed forward)
  • hiba-visszaterjesztés (backpropagation)
  • gradiensereszkedés (gradient descent)
  • aktivációs függvény (activation function)
  • szigmoid függvény / logisztikai függvény (sigmoid function / logistic function)
  • súlyok (weights)
  • predikció (prediction)
  • hipotézis függvény (hypothesis function)
  • tanulási ráta (learning rate)
  • vektor (vector)
  • mátrix (matrix)
  • alapvető lineáris algebrai ismeretek
Medical vector — created by freepik — www.freepik.comhttps://www.freepik.com/free-photos-vectors/medical

Egyetlen neuron

Az általam használt egyszerű mesterséges neuron modellje az alábbi ábrán látható. Három bemeneti változója van, de igazából csak kettő az, ami a valós bemenő adatokat tartalmazza, az x0 változó mindig 1-es értéket tartalmaz. A neuron által adott predikció a bemenő értékek, és a súlyok szorzata, ami egy lineáris függvényt ad, majd ennek eredményére a szigmoid függvény van alkalmazva, mint aktivációs függvény.

Az alábbi ábrán egyszeres aláhúzás jelöli a vektor típusú változókat (sorvektorokat és oszlopvektorokat).

A bemeneti adatokat x-szel jelölöm:

A súlyokat, jelen esetben egy vektort, w jelöli.

A hipotézis függvény h, vagyis ami a neurális hálózat kimenet egy adott x bemenetre, w súlyok mellett:

A szigmoid függvény, g:

Ha például a logikai ÉS műveletet szeretnénk megvalósítani, akkor intuitív módon felírhatunk egy olyan súlyvektort, amivel a neurális hálózat, ami jelen esetben csak egyetlen neuronból áll, kb. az ÉS műveletnek megfelelő eredményt ad a kimenetén. Hasonlóan járhatunk el más logikai műveletekkel is. Az alábbi ábrán a logikai ÉS és VAGY művelet egy neuronnal történő megvalósításához tartozó súlyokat láthatjuk. Érdemes észrevenni, hogy ha megfordítom a súlyok előjelét, akkor a végeredmény negáltját kapom, tehát így megvan további két függvény implementációja.

Az ÉS és a VAGY függvény igazságtáblája, és a neurális hálózat kimenete, ha az előbbi súlyokat használjuk:

Egyszerű neuron implementációja

Az alábbi példaprogram egyetlen neuron tanítását valósítja meg. A gradiensereszkedést (gradient descent) használom, de a hibafüggvény parciális deriválása helyett egy közelítést alkalmazok a jól ismert módszerrel.

  1. inicializálás: véletlenszerű kezdő súly értékek beállítása

2. előreterjesztés (feed forward): kiszámolom a neuron kimeneteit minden egyes bemenetre.

3. hiba-visszaterjesztés (backpropagation): A hibafüggvény (amit J jelöl) aktuális értéke az aktuális súlyoktól függ. Ha az i. súlyt kicsit változtatom (egy kis számot hozzáadok, illetve egy kis számot kivonok belőle), ki tudom számolni, hogy mennyit változik a hibafüggvény értéke, ha a többi súlyt változatlanul hagyom. Ebből ki tudom számolni, hogy megközelítőleg mekkora a hibafüggvény meredeksége az. i. súly mentén, ha a többi súly változatlanul marad. Tehát tulajdonképpen a hibafüggvény parciális deriváltját számítom ki megközelítőleg. Ezekből a parciális deriváltakból összeállítok egy oszlopvektort. Az oszlopvektor sorainak száma megegyezik a súlyok számával, vagyis a bemenő változók számával (jelen esetben 3). A kiszámolt vektort megszorzom a tanulási rátával, és kivonom a súlyvektor jelenlegi értékéből. Ezzel megkapom az új súlyvektort.

Ezt a három lépést kell ismételni kellően sokszor ahhoz, hogy jó közelítéssel megtaláljuk a hibafüggvény minimumhelyét, tehát azokat a súlyokat, ahol a hibafüggvény a legkisebb.

A hibafüggvény, J (y jelöli a tényleges eredményt, h pedig a hipotézist):

A súlyvektor, ha az i. súlyt kicsit változtatom, és a többi súly változatlanul marad:

A hibafüggvény változásaiból összeállított vektor i. eleme, és maga a vektor:

Ez alapján az új súlyvektor:

A konstans értékek a következők:

A súlyokat minden iterációnál frissítem, így lépésről lépésre eljutok a hibafüggvény minimum értékének közelébe. Az alábbi példaprogram a logikai ÉS műveletet valósítja meg, 100.000 iterációval.

A 100.000. iteráció után a futtatás eredménye a következő:

Number of iterations: 100000
Weights (W0, W1, ... Wm):
[[-7.05683897]
[ 4.63961056]
[ 4.63961068]]
Output (h_W(x)_0, h_W(x)_1, ... h_W(x)_n):
[[8.60771325e-04]
[8.18688101e-02]
[8.18688009e-02]
[9.02240966e-01]]

Amint látható, az algoritmus által számolt súlyok nem állnak messze azoktól, amiket intuitívan felírtunk az előbb.

Photo by Roman Mager on Unsplash

Mire alkalmas egyetlen neuron?

Ha kétváltozós logikai függvényeket tekintjük, könnyen beláthatjuk, hogy összesen 16 ilyen függvényt lehet leírni (2 * 2 * 2 * 2 = 16). Ezek közül láttunk kettőt, amire működik az általunk implementált tanítási algoritmus. Ha kipróbáljuk, hogy a kizáró vagy, a XOR műveletre be tudja-e tanítani az algoritmusunk a neuront, látni fogjuk, hogy ez nem működik. Ehhez meg kell változtatni az y értékét a következő módon:

y = np.array([0,1,1,0])

Majd így kell lefuttatni a programot. A kimenet a következő lesz:

Number of iterations: 100000
Weights (W0, W1, ... Wm):
[[-1.84525106e-09]
[ 1.55567381e-09]
[ 1.55567959e-09]]
Output (h_W(x)_0, h_W(x)_1, ... h_W(x)_n):
[[0.5]
[0.5]
[0.5]
[0.5]]

Tehát a program ugyan kiszámolta a súlyértékeket, de a kimenet minden esetben 0.5, amit a szigmoid függvény 0-nál vesz fel. Ez így nem jó, ez a neuron nem képes visszaadni a XOR művelet eredményét. Ennek az az oka, hogy a XOR művelet nem monoton művelet.

A kétváltozós logikai függvényeket az alábbi táblázat írja le, jelöltem, hogy melyik függvény monoton, és melyik nem.

Ahogy Bernd Steinbach és Roman Kohut publikációjából kiderül, egyetlen neuron ilyen módon történő kialakításával a monoton függvényeket lehet leírni. Már a legegyszerűbb nem-monoton logikai függvény leírása sem lehetséges egyetlen ilyen neuronnal.

Több neuronból álló hálózat

A megoldást több neuron alkalmazása jelenti. Na de mennyi neuron, és milyen elrendezésben? Az alábbi példában egy olyan hálózatot mutatok be, ami a XOR függvényt valósítja meg.

Tulajdonképpen a XOR függvényt össze lehet rakni a következő módon, monoton függvények segítségével:

Tehát a fenti neurális hálózatban

  • a 0. számú neuron (aminek W0 a súlyvektora) a NOT x0 AND x1 függvényt,
  • az 1. számú neuron (aminek W1 a súlyvektora) a NOT x1 AND x0 függvényt,
  • az 2. számú neuron (aminek W2 a súlyvektora) az a1 OR a2 függvényt

valósíthatja meg. Ez egy megvalósítás, de látni fogjuk, hogy más megvalósítás is létezik.

Ebben a hálózatban a predikció kiszámítása összetettebb.

Több neuronból álló hálózat tanítása

Az alábbi programban a fent ábrázolt, 3 neuronból álló hálózat tanítását implementáltam. A hálózat a XOR függvényt valósítja meg. A lépések ugyanazok, mint az előző hálózatnál, csak a kalkuláció bonyolultabb. A backpropagation lépésnél is ugyanazt a módszert alkalmaztam.

Most 1.000.000 iterációt futtatok. A 3 neuronból álló hálózatunk tanítása már látványosan lassabb, mint az előbbi, egyetlen neuron tanítása.

1853.17it/s]Number of iterations: 1000000
Weights (W0, W1, ... Wm):
[[ -7.04256823 -2.86741693 -4.42544641]
[ 4.587206 6.4488553 -10.25340688]
[ 4.59143431 6.46611348 9.56319587]]
Output (h_W(x)_0, h_W(x)_1, ... h_W(x)_n):
[0.01945483 0.98319957 0.98318054 0.0174142 ]

Látható, hogy a hálózat nem úgy valósítja meg a függvényt, mint fent az intuitív példában írtam. Ha a súlymátrixot megnézzük, láthatjuk, hogy az első oszlop, ami a w0 súlyokat jelenti, az ÉS függvényt valósítja meg. A második oszlop (w1) a VAGY függvénynek felel meg. A harmadik súly-oszlop pedig a következő függvénynek felel meg:

Tehát a neurális hálózatunk a következő függvényt valósítja meg:

Ha végiggondoljuk, ez valóban a XOR függvény. Tehát ezt találta meg a 3 neuronból álló hálózatunk.

Photo by Fahrul Azmi on Unsplash

Referenciák

Bernd Steinbach, Roman Kohut — Neural Networks — A Model of Boolean Functions: https://pdfs.semanticscholar.org/74f4/e337add835e4122cdc62179653b6bcf776b0.pdf

Martin Anthony — Boolean Functions and Artificial Neural Networks: http://www.cdam.lse.ac.uk/Reports/Files/cdam-2003-01.pdf

Neural Networks: What can a network representhttp://www.cs.cmu.edu/~bhiksha/courses/deeplearning/Spring.2018/www/slides/lec2.universal.pdf

--

--

László Harri Németh
Pythonarium

Software developer. Python, SQL, ABAP, Swift, Javascript, Java, C, C++, Ruby, noSQL, Bash, Linux. http://nlharri.hu http://github.nlharri.hu hello@nlharri.hu