Nem olyan régen szó volt a Hosszú munkamemóriájú Neurális Hálózatokról. Mai bejegyzésünkben egy gyakorlati példa megvalósítását fogjuk megnézni. Ehhez a Keras keretrendszert használjuk.
Keras
Mivel a blogon korábban nem volt szó a Kerasról, így érdemes talán egy kis áttekintéssel kezdeni. A Keras nem egy önálló könyvtár gépi tanulásra, hanem leginkább egy felhasználói felület más megvalósításokhoz. És ezért szeretjük. Lehet a háttérben TensorFlow (Google), Theano ( Université de Montréal ) vagy CNTK (Microsoft), nekünk mindegy. Ez lehetővé teszi, hogy olyan kódot írjunk, ami egyaránt használható különböző környezetekben.1
Keras modell létrehozása egy szekvenciális folyamat, amely során egyesével meghatározzuk a Neurális Háló rétegeit.
Két szintaxis használható erre. Vagy egy lépésben létrehozhatunk egy Sequential objektumot, a rétegek listájával együtt, pl:
from keras.models import Sequential from keras.layers import Dense, Activation model = Sequential([ Dense(32, input_shape=(784,)), Activation('relu'), Dense(10), Activation('softmax'), ])
Vagy először csak egy üres Sequential hozunk létre és később adjuk ehhez hozzá a rétegeket, pl:
from keras.models import Sequential from keras.layers import Dense, Activation model = Sequential() model.add(Dense(32, input_dim=784)) model.add(Activation('relu'))
Én személy szerint a második megoldást kedvelem, mert olvashatóbbnak tartom.
A fenti kód elég magától érthető. A mode.add() függvénnyel adunk új réteget a hálózathoz. A Keras rengeteg előre megírt rétegtípussal rendelkezik, a fenti példában a Dense egy teljesen kapcsolt réteg 32 neuronnal, ahol minden neuronnak 784 bemeneti tulajdonsága van. A réteg aktivációs függvénye pedig ReLu lesz.
Ezután a rövid Keras bevezető után nézzük meg magát a HMM megvalósítását.
Teszt adatok
Mint a Hosszú munkamemóriájú Hálózatokról szóló bejegyzésben említettem, ezeket gyakran idősorok előrejelzésére használják. Mi is ezt fogjuk tenni. Általában az interneten olvasható ismertetők, valamilyen szabadon elérhető valós adatsorral dolgoznak ilyen esetben. Például tőzsdei adatokkal.
Én inkább teljesen szintetikus adatsort fogok használni. Ennek egyik oka, hogy egy olyan esetet szeretnék bemutatni, amikor is több párhuzamos, független megfigyelésünk van ugyanarról a szituációról. Másrészt szeretném demonstrálni két eltérő HMM megvalósítás; a „statetless” és a „stateful”; közötti különbséget. Ehhez pedig szükségem lesz az adatok szabad módosításának lehetőségére.
Az adatok egy nagyon egyszerű modellt fognak követni: a t minta elvárt értéke az t-3 minta tulajdonságainak átlaga lesz. Nézzünk egy példát:
(1)
A konkrét teszt adatokhoz lebegőpontos számokat fogunk használni. Legyen két párhuzamos megfigyelésünk, és négy tulajdonságunk:
# tulajdonságok száma input_dim = 4 elso_megf = pd.DataFrame(np.random.rand(1000, input_dim)) elso_megf.columns = [ "tulajdonsag_"+str(i) for i in range(input_dim)] masodik_megf = pd.DataFrame(np.random.rand(1000, input_dim)) masodik_megf.columns = [ "tulajdonsag_"+str(i) for i in range(input_dim)] # eltoljuk az adatokat elltolas = 3 elso_megf_y = np.concatenate( ( np.zeros( elltolas), (elso_megf.mean(axis=1))[:-elltolas] ) ) masodik_megf_y = np.concatenate( ( np.zeros( elltolas), (masodik_megf.mean(axis=1))[:-elltolas] ) )
Nekem az első 15 minta a első megfigyelés (elso_megf[:15]) esetén a következő lett:
tulajdonsag_0 tulajdonsag_1 tulajdonsag_2 tulajdonsag_3 0 0.536188 0.413961 0.471675 0.238042 1 0.398762 0.607436 0.591594 0.618381 2 0.208290 0.935215 0.173247 0.471519 3 0.480678 0.779743 0.404191 0.231209 4 0.506022 0.651260 0.227147 0.890298 5 0.542125 0.989167 0.114589 0.696338 6 0.861277 0.466220 0.793839 0.399385 7 0.875724 0.826361 0.153811 0.912448 8 0.044427 0.839666 0.823994 0.473479 9 0.662939 0.656158 0.054839 0.558480 10 0.693141 0.893433 0.350043 0.508953 11 0.342473 0.745086 0.883296 0.375022 12 0.154625 0.866008 0.785388 0.251907 13 0.928991 0.551832 0.048275 0.268590 14 0.968454 0.359309 0.582049 0.770362
Ennek megfelelően az elvárt érték (elso_megf_y[:15]) pedig:
[0.0, 0.0, 0.0, 0.41496664773915976, 0.55404336656952, 0.4470678376510703, 0.4739553036189357, 0.5686817473305106, 0.5855544300554376, 0.6301802642526891, 0.6920859659447649, 0.5453913328649528, 0.4831039939366768, 0.611392289281034, 0.5864690478998569]
Ábrázolva ugyanez:

Most, hogy megvannak az adataink, nézzük mi is az a korábban említett stateful és stateless megvalósítás.
Stateful vs. Stateless
Mint minden gépi tanulás esetében, először az adatainkat kell megfelelő formába hozni. A Keras több Ismétlődő Neurális Hálózat réteget alapból támogat, nekünk a Hosszú munkamemóriájú Hálózathoz a keras.layers.LSTM()-ra lesz szükségünk. Ez a réteg három dimenziós adatokat vár. Ezek a dimenziók pedig a „batch_size”, a „timesteps” és a „input_dim”. Nézzük mik ezek. Az első kettő magától értetődőnek tűnik: A timestemps, hogy milyen hosszúak az egyes minták az idő tengelyen, míg a “input dimenziók” a megfigyelés a tulajdonságai.
De mi a batch_size? A Keras úgynevezett batch-okban végzi a Visszajátszást. Először feldolgozza az össze egy batch-ban szereplő adatot, majd átlagolja a hibájukat és visszajátssza.
Az eredeti megfigyeléseket ennek megfelelően batch-okba kell rendeznünk. De miért nekünk és miért nem elég megadni egy egyszerű attribútum a batch_size-t?
A fő indoka ennek, hogy két különböző módon is elvégezhetjük ezt a darabolást, és sajnos ez a két módszer teljesen eltérő tanulást kíván.
Az egyik módszert stateless-nek a másikat stateful-nak nevezik angolul. A statless esetben a megfigyeléseket egy olyan mozgó ablak segítségével daraboljuk, ami mindig csak egyet lép az idő tengely mentén. Ennek megfelelően az egyes megfigyelések többször is előfordulnak a bemenetben. Minden egyes alkalommal más-más timesteps pozícióban. tehát a formázott adata valami ilyesmi lesz:

A második esetben ezzel szemben a mozgó ablak lépésnagysága megegyezik a második dimenzió nagyságával. Ebben az esetben az egyes megfigyelések mindig csak egyszer jelenek meg a bemenetben. Ebben az esetben a bemeneti adat valami ilyesmi lesz:

A stateless és stateful nevek nem véletlenek. Emlékezzünk vissza a Hosszú munkamemóriájú Neurális Hálózatok bejegyzésre. Ott a Python kód 35-38 sora definiálja a htx és a ctx kezdeti értékét. Ezek az első megfigyelés bemeneti Rejtett állapota és Cella állapota. Vagyis a cellák állapota mielőtt még bármiféle megfigyelést végeztünk volna.
Ha belegondolunk, akkor látjuk, hogy stateful esetben csak egyszer kell végrehajtanunk ezt a kezdeti iniciálást. Minden további nélkül használhatjuk az első batch utolsó Cella és Rejtett állapotát a következő batch kezdeti állapotának, mivel az adatok pontosan követik egymást időben.
Ezzel szemben a stateless esetben az adatok ismétlődése miatt, ezt az iniciálást minden egyes batch során meg kell ismételni. Ez magával hozza azt is, hogy stateless esetben nyugodtan megváltoztathatjuk az egyes batch-ok sorrendjét.
Mi ennek a következménye? Stateless esetben nem vagyunk képesek a batch határán túli adatok között összefüggést találni. Ezzel szemben a stateful lényegében az egész idősoron átível.
Vagyis válaszuk mindig a stateful megoldást? A válasz nem ilyen egyértelmű. Sajnos, amíg a stateless memória igénye megegyezik a HMM-ban lévő párhuzamos cellák nagyságával, addig a stateful sokkal több memóriát igényel. Mivel tárolnia kell az összes kimeneti állapotot minden batch-ra.2
Az elméleti áttekintés után lássuk, hogy valósítjuk ezt meg.
Adat formázás
Az adatok formázására egy függvényt fogunk használni a, format_data_for_lstm-nek fogom nevezni:3
def format_data_for_lstm(data, n_in=1, n_out=1, stateful=False, dropnan=True): n_vars = 1 if type(data) is list else data.shape[1] df = pd.DataFrame(data) cols, names = list(), list() # input sequence (t-n, ... t-1) for i in range(n_in, 0, -1): cols.append(df.shift(i)) names += [('tulajdonsag_%d(t-%d)' % (j+1, i)) for j in range(n_vars)] # put it all together agg = pd.concat(cols, axis=1) agg.columns = names # drop rows with NaN values if dropnan: agg.dropna(inplace=True) if stateful: indexes = [ x for x in agg.index if x%n_in != 0 ] agg.drop(indexes, inplace=True) return agg
A függvénynek a következő argumentumai vannak:
- data — az X értékek
- n_in — a timesteps érték
- n_out — az input dimenzió
- stateful — az adatokat stateful típusra formázzuk vagy nem
- dropnan — az első megfigyelések törlése amikhez nincs elvárt érték
A fenti függvény segítségével már egyszerűen formázhatjuk az adatokat stateful esetre:
timesteps = 2 df_stateful_elso = format_data_for_lstm(elso_megf, timesteps, input_dim, stateful=True) df_stateful_masodik = format_data_for_lstm(masodik_megf, timesteps, input_dim, stateful=True) X = df_stateful_elso.values X = X.reshape((-1, timesteps, input_dim)) y = elso_megf_y[df_stateful_elso.index]
Ami után az első megfigyelés formázott adatai (X) így fognak kinézni:
[[[0.53618786 0.41396133 0.4716755 0.2380419 ] [0.39876152 0.60743647 0.59159403 0.61838145]] [[0.20829007 0.93521533 0.17324709 0.47151886] [0.48067848 0.77974293 0.40419086 0.23120893]] [[0.50602156 0.6512601 0.2271472 0.89029813] [0.54212458 0.98916656 0.11458854 0.69633804]] [[0.86127652 0.46622032 0.79383909 0.39938513] [0.87572365 0.82636121 0.15381122 0.91244778]] [[0.04442662 0.83966647 0.82399368 0.47347856] [0.66293888 0.65615843 0.05483879 0.55847987]] [[0.69314093 0.89343283 0.3500426 0.5089528 ] [0.34247279 0.74508568 0.88329551 0.37502221]] [[0.15462517 0.86600775 0.78538763 0.25190678] [0.92899051 0.55183228 0.04827549 0.26859024]] [[0.96845425 0.35930915 0.58204914 0.77036177] [0.35858779 0.25126132 0.78227428 0.10191228]] [[0.3199399 0.54808626 0.33866681 0.88059108] [0.45057087 0.11480907 0.67472037 0.24806012]] [[0.23261756 0.27454501 0.81644388 0.90456626] [0.94199834 0.61457519 0.01125013 0.3100185 ]] [[0.62405929 0.05315773 0.19859156 0.29736614] [0.77842119 0.00337613 0.58566533 0.14927899]] [[0.14772656 0.7873446 0.55807901 0.25479185] [0.96009697 0.03324109 0.83040832 0.95849892]] [[0.99286448 0.7146162 0.78408361 0.06211432] [0.23666864 0.88196782 0.16567122 0.17162786]] [[0.81329957 0.09190536 0.4850661 0.09250549] [0.70977633 0.44163292 0.39582176 0.80150034]] [[0.36031966 0.14417406 0.37153742 0.55888836] [0.91760244 0.00561094 0.60202696 0.23423187]]]
Az ennek megfelelő elvárt értékek (y) pedig:
[0. 0.55404337 0.4739553 0.58555443 0.69208597 0.48310399 0.58646905 0.44942213 0.37350892 0.37204011 0.46946054 0.37918541 0.69556133 0.36398388 0.58718284]
Ugyanezek az adatok stateless alakba formázva:
df_stateless_elso = format_data_for_lstm(elso_megf, timesteps, input_dim, stateful=False) df_stateless_masodik = format_data_for_lstm(masodik_megf, timesteps, input_dim, stateful=False) X = df_stateless_elso.values X = X.reshape((-1, timesteps, input_dim)) y = elso_megf_y[df_stateless_elso.index]
[[[0.53618786 0.41396133 0.4716755 0.2380419 ] [0.39876152 0.60743647 0.59159403 0.61838145]] [[0.39876152 0.60743647 0.59159403 0.61838145] [0.20829007 0.93521533 0.17324709 0.47151886]] [[0.20829007 0.93521533 0.17324709 0.47151886] [0.48067848 0.77974293 0.40419086 0.23120893]] [[0.48067848 0.77974293 0.40419086 0.23120893] [0.50602156 0.6512601 0.2271472 0.89029813]] [[0.50602156 0.6512601 0.2271472 0.89029813] [0.54212458 0.98916656 0.11458854 0.69633804]] [[0.54212458 0.98916656 0.11458854 0.69633804] [0.86127652 0.46622032 0.79383909 0.39938513]] [[0.86127652 0.46622032 0.79383909 0.39938513] [0.87572365 0.82636121 0.15381122 0.91244778]] [[0.87572365 0.82636121 0.15381122 0.91244778] [0.04442662 0.83966647 0.82399368 0.47347856]] [[0.04442662 0.83966647 0.82399368 0.47347856] [0.66293888 0.65615843 0.05483879 0.55847987]] [[0.66293888 0.65615843 0.05483879 0.55847987] [0.69314093 0.89343283 0.3500426 0.5089528 ]] [[0.69314093 0.89343283 0.3500426 0.5089528 ] [0.34247279 0.74508568 0.88329551 0.37502221]] [[0.34247279 0.74508568 0.88329551 0.37502221] [0.15462517 0.86600775 0.78538763 0.25190678]] [[0.15462517 0.86600775 0.78538763 0.25190678] [0.92899051 0.55183228 0.04827549 0.26859024]] [[0.92899051 0.55183228 0.04827549 0.26859024] [0.96845425 0.35930915 0.58204914 0.77036177]] [[0.96845425 0.35930915 0.58204914 0.77036177] [0.35858779 0.25126132 0.78227428 0.10191228]]]
[0. 0.41496665 0.55404337 0.44706784 0.4739553 0.56868175 0.58555443 0.63018026 0.69208597 0.54539133 0.48310399 0.61139229 0.58646905 0.51448183 0.44942213]
Mit látunk itt? Vegyük észre, hogy mivel a timesteps értéke kettő, ezért a branch a t-1 és a t-2 mintákat tartalmazza. Vagyis a t-3, ami alapján az elvárt értéket számoljuk, nincs benne a branch-ban.
Ennek alapján azt várjuk, hogy a stateless megvalósítás nem fog megbirkózni az előrejelzés feladatával, ezzel szemben a stateful sikeresen fogja venni az akadályt. A következő részben megnézzük igazunk van-e.
Lábjegyzet
- Ha valakit bővebben érdekel mi másért szerethetjük a Keras akkor ajánlom a Why use Keras? írást.
- Ez az oka, miért kell statefull esetben a Kerasban megadni a batch_size értéket. A Keras előre lefoglalja a memóriát a cellák kimenetének, amihez statefull esetben szüksége van batch_size-ra is.
- A függvény Jason Brownlee: Multivariate Time Series Forecasting with LSTMs in Keras cikkének series_to_supervised függvényének módosítása.
Irodalom
- Christopher Olah: Understanding LSTM Networks
- Jason Brownlee: Multivariate Time Series Forecasting with LSTMs in Keras
- Jason Brownlee: Time Series Prediction with LSTM Recurrent Neural Networks in Python with Keras
- Keras dokumentáció: Lstm stateful
- Mohammad Fneish: Keras_LSTM_Diagram
- Shiva Verma: Understanding Input and Output shapes in LSTM | Keras
- Shi Yan: Understanding LSTM and its diagrams
“Hosszú munkamemóriájú Neurális Hálózat Kerassal — 1. rész” bejegyzéshez egy hozzászólás