Nem olyan régen szó volt a Mesterséges Neurális Hálózatok egy sorozatokra kifejlesztett változatáról, az Ismétlődő Neurális Hálózatról. Amint említettük, ez a típus hatványozottan szenved az Eltűnő Gradiens Problémától. Ennek a nehézségnek a megoldására született meg a “Hosszú Munkamemóriájú” (HMM, angolul: Long short-term memory) Neurális Hálózat. Mai bejegyzésünkben ezt megvizsgáljuk meg.
A HMM mai formája 1997 és 2000 között született meg Sepp Hochreiter, Jürgen Schmidhuber és Felix Gers munkája során. Érdemes megemlíteni, hogy Hochreiter volt az aki először definiálta az Eltűnő Gradiens Problémát.1
A 2010-es évek végére az HMM igazi sikersztori lett. Sajnos csak 2016-os adatokat találtam, de ezek szerint 3 évvel ezelőtt olyan jól ismert és használt alkalmazásokban használták, mint a Google Translate, az iPhone „Quicktype” funkciója, a Siri vagy az Amazon Alexa.
A HMM szoros rokonságban áll az Ismétlődő Neurális Hálózattal, sokszor annak egy altípusának tekintik. Itt is egyesével dolgozzuk fel a megfigyeléseket:2

Ami eltérő: az a hálózat felépítése; a zöld dobozok sora. Ez nem egy egyszerű Feed Forward Hálózat, hanem egy sokkal összetettebb rendszer:

Amint látható, az egy tanh aktivációs függvény helyett itt két tanh és három sigmoid függvényünk is van. A rendszert négy részre oszthatjuk:

Nézzük mit is csinálnak ezek a részek részletesebben.
Cella állapot
In medias res, kezdjük a cella állapottal! Ez a rész kicsit olyan, mint egy futószalag, ami az információt kis módosítással szállítja az egyik megfigyeléstől a másik felé. Szerintem ez a legérdekesebb része a koncepciónak. Vegyük észre, hogy itt úgy tudunk információt továbbítani a különféle megfigyelések között, hogy azokat nem küldjük többszörösen át az aktivációs függvényen. Ez az, aminek a segítségével a HMM megoldja az Eltűnő Gradiens Problémát. A Felejtés kapuja és a Bemeneti kapuk arra szolgálnak, hogy az adatokat helyezzenek fel vagy vegyenek le erről a futószalagról:

Valamelyest előreszaladva a cella állapot végső értékét így számoljuk:
(1)
Ahol:
— az új cella állapot
— a felejtés kapujának kimenete, lsd. Felejtés kapuja
— a korábbi cella állapot
— a bemeneti kapu eredménye, lsd. Bemeneti kapu
— a tangh aktivációs függvény eredménye, lsd: Bemeneti kapu
— a Hadamard-szorzat jele
Felejtés kapuja
Ennek a misztikusan hangzó nevű résznek a dolga eldönteni, hogy korábbi cella állapotok közül mennyi információt őrzünk meg, illetve felejtünk el. Mivel ez egy sigmoid függvény, a kimeneti értéke 0 és 1 között van, azaz minél kisebb a kimenet annál kevésbé fogjuk a későbbiekben figyelembe venni az adott információt:

A számítása ennek megfelelőn:
(2)
Ahol:
— a felejtés kapujának eredménye
— a felejtés kapujának súlyai
— a korábbi rejtett állapot
— a jelenlegi megfigyelés
— a felejtés kapujának eltolása
Bemeneti kapu
Ez a kapu annak eldöntésére szolgál, hogy menyi információt őrizzünk meg a jelenlegi megfigyelésből. A sigmoid eldönti: mi fontos, mi nem, míg a tanh kiszámítja a cella állapot frissítésének értékét:

A fentieknek megfelelően a bementi kapu eredményének számítása:
(3)
Ahol:
— a bementi kapu eredménye
— a bemeneti kapu súlyai
— a bemeneti kapu eltolása
Az aktivációs függvény pedig a szokásos:
(4)
Ahol:
— a javasolt állapot a
-re
— az aktivációs függvény súlyai
— az aktivációs függvény eltolása
Kimenti kapu
Ez a réteg dönti el, hogy a következő megfigyelés esetén mi legyen a Rejtett állapot. Ezt az állapotot használjuk előrejelzésre is.

A kimeneti kapu eredményének számítása:
(5)
Ahol:
-
— a kimeneti kapu eredménye
— a kimeneti kapu súlyai
— a kimeneti kapu eltolása
Végül pedig a végső Rejtett állapot:
(6)
Naiv Python Lejátszás
Végezetül nézzünk meg egy egyszerű Python implementációt a HMM lejátszás szakaszára:
import numpy as np # kezdeti sulyok wf = np.array([0,0]) bf = -100 wi=np.array([0,100]) bi = 100 wo=np.array([0,100]) bo = 0 wc = np.array([-100,50]) bc = 0 # sigmoid függvény def sigmoid(x): return 1/(1+np.exp(-x)) # LSTM cella # attr: # x -- igazából [ h_{t-1}, x] def lstmcell(x, ct): # felejtés kapuja ft = sigmoid(np.dot(wf, x)+bf) # bemeneti kapu it = sigmoid(np.dot(wi, x)+bi) # kimeneti kapu ot = sigmoid(np.dot(wo, x)+bo) # cella állapot ct = ft*ct+it * np.tanh(np.dot(wc, x)+bc) # rejtett állapot ht = ot*np.tanh(ct) return ht, ct # megfigyelések xs = np.array([0, 0, 1, 1, 1, 0]) # kezdeti rejtett állapot htx = [0] # kezdeti cella állapot ctx = np.array([0]) # eredmény result = [] # végiglépkedünk a megfigyeléseken for i in xs: htx.append(i) htx, ctx = lstmcell(np.array(htx), ctx) htx = htx.tolist() htx[0] = htx[0] result.append(htx[0]) # eredmény print(result)
Végszó
A fentiekben megnéztük, hogyan működik egy tipikus HMM, de mint mindennek a Mesterséges Neurális Hálózatok területén, ennek is több változata alakult ki. A konkrét könyvtárak alkalmazása során érdemes átolvasni a dokumentációt, hogy lássuk, mennyiben tipikus az adott megvalósítás.
Lábjegyzet
- S. Hochreiter: Untersuchungen zu dynamischen neuronalen Netzen. Diploma thesis, Institut f. Informatik, Technische Univ. Munich, 1991.
- Mint a Ismétlődő Neurális Hálózat esetén ebben a bejegyzésben is Michael Nguyen Illustrated Guide to LSTM’s and GRU’s munkájának ábráit használom.
Irodalom
- Christopher Olah: Understanding LSTM Networks
- Michael Nguyen: Illustrated Guide to LSTM’s and GRU’s: A step by step explanation
- Shi Yan: Understanding LSTM and its diagrams