Predikce ceny akcií pomocí LSTM

Strojové učení našlo v posledních letech uplatnění v mnoha zajímavých oblastech. Zkrocení akciového trhu je jednou z nich. Už delší dobu jsem přemýšlel, že bych to zkusil; hlavně proto, abych si upevnil pracovní znalosti o LSTM. A nakonec jsem projekt dokončil a docela rád se podělím o své zkušenosti.

Motivace a cílová skupina

O svých zkušenostech budu psát v sérii blogů. Účelem této série není vysvětlovat základy LSTM nebo koncepty strojového učení. Proto budu předpokládat, že čtenář začal svou cestu se strojovým učením a má základy jako Python, znalost SkLearn, Keras, LSTM atd. Důvodem je, že již existují vynikající články na témata jako „Jak fungují LSTM?“ od lidí, kteří jsou mnohem kvalifikovanější k vysvětlení matematiky, která za tím stojí. Ale budu sdílet odkazy na takové články všude tam, kde mám pocit, že by mohly chybět základní znalosti. Existuje sice spousta článků, které vám řeknou, jak předpovídat ceny akcií vzhledem k souboru dat, ale většinou autoři neprozradí/vysvětlí, jak dospěli k této konkrétní konfiguraci neuronové sítě nebo jak vybrali tuto konkrétní sadu hyperparametrů. Skutečným účelem tohoto článku je tedy podělit se o tyto kroky, své chyby a některé kroky, které mi velmi pomohly. Tento článek jako takový se neomezuje pouze na problém předpovídání cen akcií.

Tady jsou věci, na které se podíváme :

  1. Čtení a analýza dat. (Pandas)
  2. Normalizace dat. (SkLearn)
  3. Převod dat na časové řady a problém učení pod dohledem.
  4. Vytvoření modelu (Keras)
  5. Jemné vyladění modelu (v dalším článku)
  6. Trénování, predikce a vizualizace výsledku.
  7. Tipy & nástroje, které mi přišly velmi užitečné (poslední článek série)

Upozorňuji, že tento první článek hovoří o krocích předzpracování a terminologii LSTM. Pokud jste si těmito kroky poměrně jisti, můžete přeskočit na další článek.

Začněme!

Čtení a analýza dat

Pro tento příspěvek budu používat historická data o cenách akcií společnosti GE. Data najdete na mých stránkách kaggle zde. Zdroj dat si nepamatuji, protože jsem si je stáhl už dávno. Data můžeme načíst do rámce, jak je uvedeno níže :

df_ge = pd.read_csv(os.path.join(INPUT_PATH, "us.ge.txt"), engine='python')
df_ge.tail()

Jak vidíte, je zde asi 14060 položek, z nichž každá představuje jeden den burzovních atributů společnosti. Podívejme se, jak to vypadá na grafu :

from matplotlib import pyplot as pltplt.figure()
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.title('GE stock price history')
plt.ylabel('Price (USD)')
plt.xlabel('Days')
plt.legend(, loc='upper left')
plt.show()

Zdá se, že ceny – Open, Close, Low, High – se od sebe příliš neliší, až na občasné mírné poklesy ceny Low.

Nyní se podíváme na graf pro objem :

plt.figure()
plt.plot(df_ge)
plt.title('GE stock volume history')
plt.ylabel('Volume')
plt.xlabel('Days')
plt.show()

Huh. Viděli jste něco zajímavého? Kolem 12000. dne na časové ose dochází k poměrně prudkému nárůstu počtu transakcí, což se shoduje s náhlým poklesem ceny akcií. Možná se můžeme vrátit k tomuto konkrétnímu datu a vyhrabat staré zpravodajské články, abychom zjistili, co to způsobilo.

Nyní se podíváme, jestli máme nějaké nulové/nulové hodnoty, které by nás mohly znepokojovat. Jak se ukázalo, žádné nulové hodnoty nemáme. Skvělé!

print("checking if any null values are present\n", df_ge.isna().sum())

Normalizace dat

Data nejsou normalizovaná a rozsah jednotlivých sloupců se liší, zejména Objem. Normalizace dat pomáhá algoritmu v konvergenci, tj. v efektivním nalezení lokálního/globálního minima. Použiji MinMaxScaler ze Sci-kit Learn. Předtím však musíme datovou sadu rozdělit na trénovací a testovací datovou sadu. Také budu v průběhu převádět DataFrame na ndarray.

Převod dat na časové řady a problém učení pod dohledem

To je poměrně důležité a poněkud složité. Právě zde je potřeba využít znalosti LSTM. Uvedl bych stručný popis klíčových pojmů, které jsou zde potřeba, ale rozhodně doporučuji přečíst si blog Andreho karpathyho zde, který je považován za jeden z nejlepších zdrojů o LSTM, a toto. Nebo se můžete podívat i na video Andrewa Nga (kde je mimochodem zmíněn i Andreho blog).

LSTM spotřebovávají vstup ve formátu ; 3-rozměrné pole.

  • Batch Size říká, kolik vzorků vstupu chcete, aby vaše neuronová síť viděla před aktualizací vah. Řekněme tedy, že máte 100 vzorků (vstupní datová sada) a chcete aktualizovat váhy pokaždé, když vaše NN viděla vstup. V takovém případě by velikost dávky byla 1 a celkový počet dávek by byl 100. Podobně pokud byste chtěli, aby síť aktualizovala váhy poté, co viděla všechny vzorky, velikost dávky by byla 100 a počet dávek by byl 1. Jak se ukazuje, použití velmi malé velikosti dávky snižuje rychlost trénování a na druhou stranu použití příliš velké dávky (jako je celá datová sada) snižuje schopnost modelů zobecňovat na různá data a také spotřebovává více paměti. K nalezení minima účelové funkce je však zapotřebí méně kroků. Musíte tedy na datech vyzkoušet různé hodnoty a najít „sweet spot“. Je to poměrně rozsáhlé téma. V příštím článku si ukážeme, jak je hledat poněkud chytřejším způsobem.
  • Časové kroky definují, kolik jednotek zpět v čase chcete, aby vaše síť viděla. Například kdybyste pracovali na problému predikce znaků, kdy máte k dispozici textový korpus k trénování a rozhodnete se, že síť budete krmit 6 znaky najednou. Pak je váš časový krok 6. V našem případě použijeme jako časový krok 60, tj. budeme se dívat do dat za 2 měsíce, abychom předpověděli cenu příštího dne. Více o tom později.
  • Funkce je počet atributů použitých k reprezentaci každého časového kroku. Uvažujme výše uvedený příklad predikce znaků a předpokládejme, že k reprezentaci každého znaku použijeme jednočíselný kódovaný vektor o velikosti 100. Pak je zde velikost příznaků 100.

Teď, když jsme si trochu ujasnili terminologii, převedeme naše skladová data do vhodného formátu. Předpokládejme pro jednoduchost, že jsme zvolili čas 3 jako náš časový krok (chceme, aby se naše síť podívala zpětně na 3 dny dat, aby mohla předpovědět cenu 4. dne), pak bychom naši datovou sadu vytvořili takto:

Vzorky 0 až 2 by byly naším prvním vstupem a cena Close vzorku 3 by byla jeho odpovídající výstupní hodnotou; obojí ohraničené zeleným obdélníkem. Podobně vzorky 1 až 3 by byly naším druhým vstupem a Close cena vzorku 4 by byla odpovídající výstupní hodnotou; znázorněno modrým obdélníkem. A tak dále. Dosud tedy máme matici tvaru (3, 5), přičemž 3 je časový krok a 5 je počet funkcí. Nyní se zamyslete, kolik takových vstupně-výstupních dvojic je na výše uvedeném obrázku možné vytvořit? 4.

Také s tím smíchejte velikost dávky. Předpokládejme, že zvolíme velikost dávky 2. Pak by dvojice vstup-výstup 1 (zelený obdélník) a dvojice 2 (modrý obdélník) tvořily dávku jedna. A tak dále. Zde je úryvek kódu Pythonu, který to provede:

‚y_col_index‘ je index vašeho výstupního sloupce. Nyní předpokládejme, že po převedení dat do formátu pro učení pod dohledem, jak je uvedeno výše, máte v trénovací sadě dat 41 vzorků, ale velikost vaší dávky je 20, pak budete muset trénovací sadu oříznout, abyste odstranili liché vzorky, které zůstaly mimo. Budu hledat lepší způsob, jak to obejít, ale prozatím jsem to udělal takto:

Nyní pomocí výše uvedených funkcí vytvoříme naše trénovací, validační a testovací datové sady

x_t, y_t = build_timeseries(x_train, 3)
x_t = trim_dataset(x_t, BATCH_SIZE)
y_t = trim_dataset(y_t, BATCH_SIZE)
x_temp, y_temp = build_timeseries(x_test, 3)
x_val, x_test_t = np.split(trim_dataset(x_temp, BATCH_SIZE),2)
y_val, y_test_t = np.split(trim_dataset(y_temp, BATCH_SIZE),2)

Teď, když jsou naše data připravena, se můžeme soustředit na sestavení modelu.

Vytvoření modelu

Pro tuto úlohu použijeme LSTM, což je varianta rekurentní neuronové sítě. Vytvoření modelu LSTM je jednoduché takto:

Teď, když máte model sestavený a připravený k trénování, trénujte jej podle následujícího obrázku. Pokud vás zajímá, jaké hodnoty použít pro parametry, jako jsou epochy, velikost dávky atd., nebojte se, v dalším článku se podíváme, jak na to přijít.

Trénování tohoto modelu (s jemně vyladěnými hyperparametry) dalo nejlepší chybu 3,27e-4 a nejlepší chybu validace 3,7e-4. Zde je uvedeno, jak vypadala ztráta při trénování vs. ztráta při validaci:

Tréninková chyba vs. validační chyba

Takto vypadala předpověď s výše uvedeným modelem:

predikce vs reálná data

Zjistil jsem, že tato konfigurace pro LSTM funguje nejlépe ze všech kombinací, které jsem vyzkoušel (pro tento dataset), a to jsem jich vyzkoušel více než 100! Otázka tedy zní, jak přistoupit k dokonalé (nebo téměř ve všech případech téměř dokonalé) architektuře pro vaši neuronovou síť? Tím se dostáváme k další a důležité části, která bude pokračovat v příštím článku.

Všechny kompletní programy najdete na mém profilu na Githubu zde.

POZNÁMKA: Skromná prosba ke čtenářům – Všichni jste vítáni, abyste se se mnou spojili na LinkedIn nebo Twitteru, ale pokud máte dotaz týkající se mých blogů, napište ho prosím do sekce komentářů příslušného blogu místo do osobních zpráv, aby v případě, že má někdo další stejný dotaz, našel ho zde sám a já ho nemusel vysvětlovat jednotlivě. Stále však můžete posílat dotazy, které se netýkají blogů nebo obecných technických dotazů, mně osobně. Díky 🙂

DOPLNĚK 13/4/19

  1. Od doby, kdy jsem psal tento článek, jsem se dozvěděl, že můj model použitý pro tento blog mohl být nadhodnocen. Sice jsem to nepotvrdil, ale je to pravděpodobné. Buďte proto prosím opatrní při jeho implementaci do svých projektů. Mohli byste vyzkoušet věci jako menší počet epoch, menší síť, větší dropout atd.
  2. Pro poslední vrstvu jsem použil sigmoidní aktivaci, která může trpět omezením spočívajícím v tom, že není schopna předpovědět cenu vyšší než „max“ cena v datasetu. Pro vyřešení tohoto problému byste mohli zkusit ‚lineární‘ aktivaci pro poslední vrstvu.
  3. Opraven překlep v části „převod dat na časové řady“.

Děkuji čtenářům za upozornění.

DOPLNĚNO 21. 1. 2020

.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.