Napjaink igen felkapott elnevezéseinek egyike minden bizonnyal a Mesterséges Neurális Hálózat (MNH), avagy Artificial Neural Networks (ANN). Ami jórészt annak köszönhető, hogy e technológiának a mindennapi életben való alkalmazásai az emberek egy jelentős részét lenyűgözi. Az MNH az alapja sok arcfelismerő, képfeldolgozó megoldásnak, a Google-fordítónak vagy a Facebook-hirdetés optimalizációjának, hogy csak néhányat említsek. Mai posztomban a legegyszerűbb MNH-t, az un. Feedforward-típust törekszem egy kicsit részletesében bemutatni.
In medias res, kezdjük a neurális hálózat részeivel! Ehhez rajzoltam egy diagramot:
![](https://sajozsattilahome.files.wordpress.com/2019/10/ann_1.png?w=1024)
Neurális hálózat részei
Elsőre talán bonyolultnak látszik, de valójában nagyon egyszerű. Ha jól megnézzük a fenti ábrát, láthatjuk, hogy csupán két fajta objektumból épül fel:
- a körök — az úgynevezett neuronok. Ezek végzik a számítást.
- a nyilak — a neuronok közötti kapcsolatokat jelölik. A fenti ábrán minden neuron kapcsolódik minden neuronhoz a következő rétegben. Ezt az elrendezést „teljesen csatolt” rendszernek nevezzük. (Van olyan eset is, amikor ez nem így van. Ilyenkor „részlegesen csatolt” a rendszer, de a mi példánk nem ilyen.)
Az alapelemek nem véletlenszerűen helyezkednek el a térben, hanem rétegekbe rendezve. Három rétegtípusról beszélhetünk:
- Bemeneti réteg — gondolom, senkit nem lepek meg, ha azt mondom, hogy a megfigyelt adatok itt érkeznek a hálózatba. Ebből a rétegből jobbára csak egy van (a kivételekkel most nem foglalkozunk). A rétegben elhelyezkedő neuronok mind egy-egy tulajdonságot, megfigyelési dimenziót jelölnek.
- Rejtett réteg — a számítások helye. Ennek a rétegnek a célja modellezni a bemeneti rétegben feltüntetett tulajdonságok kapcsolatát. (A fenti példában egy rejtett réteg van, de voltaképpen akármennyi követheti egymást, akár elhagyhatjuk is ezt a réteget.).
- Kimeneti réteg — beszédes neve szerint ez az utolsó réteg, az MNH eredményének helye.
Most nézzük a többi kifejezést az ábrán:
- xn — az n tulajdonság megfigyelt értéke
- w0m — a rejtett réteg neuronjára jellemző eltolás
- wnm — a bemeneti és a rejtett réteg közötti kapcsolatokhoz rendelt súlyok. Az indexek a rétegeknek megfelelő neuronokat jelölik. Pl: w12 a bementi réteg 1. neuronját köti össze a rejtett réteg 2. neuronjával.
- fr — a rejtett réteg aktivációs függvénye. Erről később lesz szó. Most csak azt vegyük észre, hogy a réteg összes neuronján megegyezik.
- zm — az aktivációs függvény – az előző réteg kimenetéből és a súlyokból számolt – bemenete.
- v0p — a kimeneti réteg neuronjára jellemző eltolás.
- vmp — a rejtett és a kimeneti réteg közötti kapcsolatok súlyai. Az indexelés ugyanazon az elven működik, mint a wnm -nél.
- fk — a kimeneti réteg aktivációs függvénye, hasonlóan a rejtett réteghez, a rétegen belül minden neuronon ugyanaz.
- up — a kimeneti réteg aktivációs függvényének bemenete.
- op — a hálózat végső eredménye.
Most, hogy ismerjük a MNH részeit, nézzük, hogyan mozog az adat a rendszeren belül. Ehhez idézzük fel, hogy minden felügyelettel végrehajtott tanulás lényegében három részből áll:
- Alkalmazzuk a már meglévő ismereteinket egy megfigyelésen, azaz az eddigi ismereteink alapján kiszámoljuk, hogy mi lehet a megfigyelés eredménye.
- Összevetjük az előző lépésben kiszámított eredményt a tényleges eredménnyel. Ha a kettő nem egyezik meg, akkor kalkuláljuk az eltérés mennyiségét és irányát. Lényegében Loss kalkulációt végzünk.
- Korrigáljuk az ismereteinket, annak megfelelően, hogy mekkora és milyen az előző pontban kiszámított hiba.
A Feedforward-típusú MNH első lépését nevezzük Lejátszásnak (Forward propagation), a másodikat a Hibaszámításnak, míg az utolsót a Visszajátszásnak (Back propagation). Gondolom, senkit se lepek meg, ha elárulom, hogy a lejátszás során az adatok a Bemeneti réteg felől a Kimeneti réteg felé haladnak. Az első ábrám ezt a lépést szemlélteti. Ezzel szemben a Visszajátszás során az ellenkező irányba haladunk:
![](https://sajozsattilahome.files.wordpress.com/2019/10/ann_2.png?w=1024)
Visszajátszás
Most, hogy ismerjük a hálózat alapvető felépítését, nézzük meg, mi a konkrét kivitelezése az egyes lépéseknek.
Lejátszás — Előrejelzés
Mint fentebb említettem, az összes számítás a neuronokban történik. De mit is csinál egy neuron? Lényegében hipersíkokat állít elő. Magát a konkrét számítást, amit végez, így lehetne megjeleníteni:
![](https://sajozsattilahome.files.wordpress.com/2019/07/blog_7-1.png)
Mit csinál egy neuron
A fenti ábra a j rejtett rétegbeli neuron működését szemlélteti, de minden, nem a Bemeneti rétegben elhelyezkedő neuron ugyanígy működik. Első lépésben összeadja a bemeneti adatok súlyozott értekét. Ez lesz a zj . Vegyük észre, hogy van egy bemeneti érték, ami mindig 1, ez az eltolás. Vagyis:
Majd ezen a zj-én alkalmazza a fr aktivációs függvényt. És ezzel kész is van. A függvény kimeneti értéke már megy is a neuron kimenetére. Miért kell az aktivációs függvény? Azért használjuk, hogy a lineárisan nem elválasztható feladatokat is meg tudjunk oldani.
Hogy ne csak elméletben legyünk képesek megoldani ezt a feladatot, számszerűsítsük a fenti modellt, valahogy így:
Mivel elég kusza lett volna, ha minden számot feltűntetek a fenti ábrán, csak néhány értéket ábrázoltam. A teljes kezdeti állapot legyen a következő:
Nézzük az első neuront a Rejtett rétegben. Ennek a z1 értéke az (1) alapján:
Aki egy kicsit járatos a lineáris algebrában, az már biztosan rájött, hogy nem kell minden neuront külön-külön kiszámítanunk, hanem a számítást rétegenként egyszerre elvégezhetjük:
import numpy as np # megfigyelés
x = np.array([.05, .1])
# rejtet réteg súlyai
w = np.array([[.25,.35],[.55, .65],[.85, .95]])
# rejtett réteg eltolás
w0 = np.array([[.15],[.45],[.75]])
# rejtett rétek z érték
z = (np.concatenate((w0, w), axis=1))@np.insert(x,0,1,axis=0)
Aminek az eredménye:
A következő lépésben át kell engednünk ezt az eredményt a rejtett réteg aktivációs függvényén. Ez a 3. ábra szerint, egy tanh függvény. Vagyis:
Ugye, ez az első neuron esetén:
A teljes rejtett rétegre:
class Tanh(): def run(self, x): return 1-(2/(np.exp(2*x)+1))
def derivate(self, x): return 4*np.exp(2*x)/(np.exp(2*x)+1)**2
# rejtett réteg aktivációs függvény f_r = Tanh()
# rejtett réteg kimenet
o_r = f_r.run(z)
Az eredmény pedig:
A Rejtett réteget be is fejeztük. Most ismételjük meg ugyanezeket a lépéseket a Kimeneti rétegben. A lépések ugyanezek, az egyetlen különbség, hogy az aktivációs függvény ReLU lesz:
Ne is húzzuk az időt, nézzük mi a Kimeneti réteg eredménye:
class ReLU(): def run(self, x): return np.maximum(0,x)
def derivate(self, x): x[x0] = 1 return x
# kimeneti réteg súlyai v = np.array([[0.3,0.4,0.5],[0.7,0.8,0.9]])
# kimeneti rétek eltolása
v0 = np.array([[.2],[.6]])
# kimeneti réteg
u = (np.concatenate((v0, v), axis=1))@np.insert(o_r,0,1,axis=0)
f_k = ReLU()
o = f_k.run(u)
Aminek az eredményei:
Mind a két szám pozitív, így nem meglepő módon a ReLU aktivációs függvény nem változtat rajtuk:
Végére is értünk a Lejátszási szakasznak.
A következő lépésben meg kell néznünk, hogy mekkora hibát követtünk el, és a beállításokat ennek megfelelően kell frissítenünk. Ezt fogjuk a következő blogbejegyzésben megtárgyalni.
Irodalom
- Assaad Moawad: Neural networks and back-propagation explained in a simple way
- Matt Mazur: A Step by Step Backpropagation Example
- John McGonagle, George Shaikouski, Christopher Williams, Andrew Hsu, Jimin Khim: Backpropagation
A bejegyzés trackback címe:
Kommentek:
A hozzászólások a vonatkozó jogszabályok értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a Felhasználási feltételekben és az adatvédelmi tájékoztatóban.