Predicting Stock Price with LSTM

Koneoppiminen on löytänyt sovelluksiaan monilla mielenkiintoisilla aloilla näiden vuosien aikana. Osakemarkkinoiden kesyttäminen on yksi niistä. Olin ajatellut kokeilla sitä jo jonkin aikaa; lähinnä vakiinnuttaakseni työtietoni LSTM:stä. Ja vihdoin olen saanut projektin valmiiksi ja olen melko innoissani voidessani jakaa kokemukseni.

Motivaatio ja kohdeyleisö

Kirjoitan kokemuksistani useiden blogien aikana. Tämän sarjan tarkoituksena ei ole selittää LSTM:n tai koneoppimisen käsitteiden perusteita. Näin ollen oletan, että lukija on aloittanut matkansa koneoppimisen parissa ja hänellä on perusteet, kuten Python, SkLearnin, Kerasin, LSTM:n jne. tuntemus. Syynä on se, että on jo olemassa erinomaisia artikkeleita aiheista kuten ”Miten LSTM:t toimivat?” ihmisiltä, jotka ovat paljon pätevämpiä selittämään matematiikan taustalla. Aion kuitenkin jakaa linkkejä tällaisiin artikkeleihin aina, kun minusta tuntuu, että taustatieto saattaa puuttua. Vaikka markkinoilla on paljon artikkeleita, joissa kerrotaan, miten pörssikursseja ennustetaan tietyllä tietokannalla, kirjoittajat eivät useimmiten paljasta/selitä, miten he ovat päätyneet kyseiseen neuroverkon kokoonpanoon tai miten he ovat valinneet kyseisen hyperparametrien joukon. Tämän artikkelin todellisena tarkoituksena on siis kertoa tällaisista vaiheista, virheistäni ja joistakin vaiheista, joita pidin erittäin hyödyllisinä. Sinänsä tämä artikkeli ei rajoitu pelkästään osakekurssien ennustamisongelmaan.

Tässä ovat asiat, joita tarkastelemme :

  1. Datan lukeminen ja analysointi. (Pandas)
  2. Datan normalisointi. (SkLearn)
  3. Datan muuntaminen aikasarjoiksi ja valvotun oppimisen ongelma.
  4. Mallin luominen (Keras)
  5. Mallin hienosäätö (seuraavassa artikkelissa)
  6. Harjoittelu, ennustaminen ja tuloksen visualisointi.
  7. Vinkkejä & työkaluja, joita pidin erittäin hyödyllisinä (sarjan viimeinen artikkeli)

Huomaa, että tässä ensimmäisessä artikkelissa puhutaan LSTM:n esikäsittelyvaiheista ja terminologioista. Jos olet melko varma näistä vaiheista, voit hypätä seuraavaan artikkeliin.

Aloitetaan!

Datan lukeminen ja analysointi

Käytän tässä postauksessa GE:n historiallisia osakekurssidataa. Löydät datan kaggle-sivustoltani täältä. En muista datan lähdettä, koska olin ladannut sen kauan sitten. Voimme lukea datan kehykseen alla esitetyllä tavalla :

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

Kuten huomaatte, tietoja on noin 14060 kappaletta, joista kukin edustaa päivän pörssiattribuutteja yhtiölle. Katsotaanpa, miltä se näyttää piirroksessa :

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()

Näyttää siltä, että kurssit – Open (auki), Close (kiinni), Low (alhaalla), High (ylhäällä) – eivät poikkea toisistaan kovin paljon lukuun ottamatta Low (alhaalla) -hinnan ajoittaista pientä laskua.

Katsotaan nyt volyymin juoni :

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

Huh. Näitkö jotain mielenkiintoista? Aikajanalla on aikamoinen nousu transaktioiden määrässä noin 12000. päivän kohdalla, joka sattuu osumaan samaan aikaan osakekurssin äkillisen laskun kanssa. Ehkä voimme palata kyseiseen päivämäärään ja kaivaa esiin vanhoja uutisartikkeleita selvittääksemme, mikä sen aiheutti.

Katsotaan nyt, onko meillä nolla/Nan-arvoja, joista on syytä huolehtia. Kuten käy ilmi, meillä ei ole nolla-arvoja. Hienoa!

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

Datan normalisointi

Dataa ei ole normalisoitu, ja kunkin sarakkeen vaihteluväli vaihtelee, erityisesti Volume. Datan normalisointi auttaa algoritmia konvergoitumaan eli löytämään paikallisen/ globaalin minimin tehokkaasti. Käytän MinMaxScaleria Sci-kit Learn -ohjelmasta. Sitä ennen meidän on kuitenkin jaettava tietokokonaisuus harjoittelu- ja testitietokokonaisuuksiin. Lisäksi aion muuntaa DataFrame:n ndarray:ksi prosessissa.

Datan muuntaminen aikasarjoiksi ja valvotun oppimisen ongelma

Tämä on melko tärkeää ja hieman hankalaa. Tässä tarvitaan tietoa LSTM:stä. Antaisin lyhyen kuvauksen keskeisistä käsitteistä, joita tässä tarvitaan, mutta suosittelen vahvasti lukemaan Andre Karpathyn blogin täältä, jota pidetään yhtenä parhaista resursseista LSTM:stä siellä ja tämän. Tai voit katsoa myös Andrew Ng:n videon (jossa muuten mainitaan myös Andren blogi).

LSTM:t kuluttavat syötteen muodossa ; 3-ulotteinen array.

  • Batch Size kertoo, kuinka monta näytettä syötteestä haluat neuroverkkosi näkevän ennen painojen päivittämistä. Sanotaan siis, että sinulla on 100 näytettä (syötetietokanta) ja haluat päivittää painot joka kerta, kun NN on nähnyt syötteen. Tällöin eräkoko olisi 1 ja erien kokonaismäärä olisi 100. Vastaavasti jos haluaisit verkon päivittävän painot sen jälkeen, kun se on nähnyt kaikki näytteet, eräkoko olisi 100 ja erien lukumäärä olisi 1. On käynyt ilmi, että hyvin pienen eräkoon käyttäminen vähentää harjoittelun nopeutta ja toisaalta liian suuren eräkoon käyttäminen (kuten koko tietokokonaisuus) vähentää mallin kykyä yleistää erilaisiin tietoihin ja kuluttaa enemmän muistia. Tavoitefunktion minimien löytäminen vie kuitenkin vähemmän vaiheita. Sinun on siis kokeiltava eri arvoja datallasi ja löydettävä paras mahdollinen arvo. Se on melko laaja aihe. Katsomme seuraavassa artikkelissa, miten näitä voidaan etsiä hieman älykkäämmin.
  • Aika-askeleet määrittelevät, kuinka monta yksikköä ajassa taaksepäin haluat verkkosi näkevän. Jos esimerkiksi työskentelisit merkkien ennustamisongelman parissa, jossa sinulla on tekstikorpus harjoiteltavana ja päätät syöttää verkollesi 6 merkkiä kerrallaan. Tällöin aika-askel on 6. Meidän tapauksessamme käytämme 60:tä aika-askelta eli tarkastelemme 2 kuukauden dataa ennustaaksemme seuraavan päivän hinnan. Tästä lisää myöhemmin.
  • Ominaisuudet on kunkin aika-askeleen esittämiseen käytettävien ominaisuuksien määrä. Tarkastellaan edellä olevaa merkkien ennustamista koskevaa esimerkkiä ja oletetaan, että käytät yhden pisteen koodattua vektoria, jonka koko on 100, edustamaan kutakin merkkiä. Tällöin ominaisuuksien koko on tässä 100.

Nyt kun olemme jonkin verran selvittäneet terminologiat pois tieltä, muunnetaan varastodatamme sopivaan muotoon. Oletetaan yksinkertaisuuden vuoksi, että valitsimme aika-askeleeksi 3 (haluamme, että verkostomme tarkastelee 3 päivän dataa ennustaakseen hinnan 4. päivänä), jolloin muodostaisimme tietokokonaisuutemme seuraavasti:

Näytteet 0-2 olisivat ensimmäinen syötteemme ja näytteen 3 sulkeutumishinta olisi sitä vastaava lähtöarvo; molemmat ympäröity vihreällä suorakulmiolla. Vastaavasti näytteet 1-3 olisivat toinen syötteemme ja näytteen 4 sulkeutumishinta olisi lähtöarvo; niitä edustaa sininen suorakulmio. Ja niin edelleen. Tähän mennessä meillä on siis matriisi, jonka muoto on (3, 5), jossa 3 on aika-askel ja 5 on ominaisuuksien lukumäärä. Mieti nyt, kuinka monta tällaista tulo-lähtö -paria on mahdollista yllä olevassa kuvassa? 4.

Sekoitetaan myös eräkoko tähän. Oletetaan, että valitsemme eräkooksi 2. Tällöin tulo-lähtö pari 1 (vihreä suorakulmio) ja pari 2 (sininen suorakulmio) muodostaisivat erän yksi. Ja niin edelleen. Tässä on python-koodinpätkä tätä varten:

’y_col_index’ on tulossarakkeesi indeksi. Oletetaan nyt, että kun olet muuntanut datan valvotun oppimisen muotoon, kuten edellä on esitetty, sinulla on 41 näytettä harjoitteluaineistossasi, mutta eräkokosi on 20, niin sinun on leikattava harjoittelujoukkosi poistaaksesi parittomat näytteet, jotka ovat jääneet pois. Etsin paremman tavan kiertää tämä, mutta toistaiseksi olen tehnyt näin:

Muodostetaan nyt edellä mainittujen funktioiden avulla train-, validointi- ja testidatasetit

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)

Nyt kun datamme on valmista, voimme keskittyä mallin rakentamiseen.

Mallin luominen

Käytämme tähän tehtävään LSTM:ää, joka on eräs muunnelma recurrent-neuraalisesta verkosta. LSTM-mallin luominen on yhtä yksinkertaista kuin tämä:

Nyt kun olet saanut mallisi koottua ja valmiina koulutettavaksi, kouluta se alla esitetyllä tavalla. Jos mietit, mitä arvoja kannattaa käyttää parametreille, kuten epochit, eräkoko jne., älä huoli, katsomme, miten ne selvitetään seuraavassa artikkelissa.

Tämän mallin kouluttaminen (hienosäädetyillä hyperparametreilla) antoi parhaaksi virheeksi 3,27e-4 ja parhaaksi validointivirheeksi 3,7e-4. Tältä näytti Training loss vs Validation loss:

Training error vs Validation error

Tältä näytti ennuste edellä mainitulla mallilla:

ennuste vs todellinen data

Olen havainnut, että tämä konfiguraatio LSTM:lle toimii parhaiten kaikista kokeilemistani kombinaatioista (tälle datasetille), ja olen kokeillut yli 100! Kysymys kuuluukin, miten päädyt täydelliseen (tai lähes kaikissa tapauksissa lähes täydelliseen) arkkitehtuuriin neuroverkkoa varten? Tämä johdattaa meidät seuraavaan ja tärkeään osioon, jota jatketaan seuraavassa artikkelissa.

Löydät kaikki täydelliset ohjelmat Github-profiilistani täältä.

HUOMAUTUS: Nöyrä pyyntö lukijoille – Olette kaikki tervetulleita pitämään yhteyttä minuun LinkedInissä tai Twitterissä, mutta jos teillä on kysyttävää liittyen blogeihini, lähetäthän sen kommenttiosioon kyseisessä blogissa henkilökohtaisen viestin sijaan, jotta jos jollakulla muulla henkilöllisellä on sama kysyttävää, hän löytäisi sen täältä itsestään eikä minun tarvitsisi selitellä sitä erikseen. Olet kuitenkin edelleen tervetullut lähettämään minulle henkilökohtaisesti kyselyitä, jotka eivät liity blogeihin tai yleisiin teknisiin kysymyksiin. Kiitos 🙂

PÄIVITYS 13/4/19

  1. Tämän artikkelin kirjoittamisen jälkeen on tullut tietooni, että tässä blogissa käyttämäni malli on saattanut olla ylisovitettu. Vaikka en ole vahvistanut sitä, se on todennäköistä. Ole siis varovainen toteuttaessasi tätä projekteissasi. Voisit kokeilla esimerkiksi pienempiä epookkeja, pienempää verkkoa, enemmän pudotuksia jne.
  2. Olen käyttänyt Sigmoid-aktivointia viimeisessä kerroksessa, joka saattaa kärsiä siitä rajoituksesta, että se ei pysty ennustamaan hintaa, joka on suurempi kuin ’max’-hinta datasetissä. Voit kokeilla ’Lineaarista’ aktivointia viimeiselle kerrokselle ratkaistaksesi tämän.
  3. Korjattu kirjoitusvirhe osiossa ”datan muuntaminen aikasarjoiksi”.

Kiitokset lukijoille, jotka ovat tuoneet nämä huomioni tietoisuuteeni.

PÄIVITYS 21.1.2020

Vastaa

Sähköpostiosoitettasi ei julkaista.