Voorspellen van aandelenkoersen met LSTM

Machine-leren heeft in de loop der jaren op vele interessante gebieden zijn toepassingen gevonden. Het temmen van de aandelenmarkt is een van hen. Ik dacht er al een hele tijd aan om het eens te proberen; vooral om mijn kennis van LSTM’s te vergroten. En eindelijk heb ik het project afgerond en ben ik erg enthousiast om mijn ervaring te delen.

Motivatie en doelpubliek

Ik zal over mijn ervaring schrijven in een serie van blogs. Het doel van deze serie is niet om de basis van LSTM of Machine Learning concepten uit te leggen. Daarom ga ik ervan uit dat de lezer zijn/haar reis met Machine Learning is begonnen en de basisbeginselen heeft zoals Python, bekendheid met SkLearn, Keras, LSTM enz. De reden hiervoor is dat er al uitstekende artikelen bestaan over onderwerpen als “Hoe werken LSTM’s?” door mensen die veel beter gekwalificeerd zijn om de wiskunde erachter uit te leggen. Maar ik zal links naar dergelijke artikels delen, overal waar ik het gevoel heb dat er achtergrondkennis ontbreekt. Hoewel er veel artikelen zijn die je vertellen hoe je aandelenkoersen kunt voorspellen gegeven een dataset, onthullen/verklaren de auteurs meestal niet hoe ze tot die bepaalde configuratie voor een neuraal netwerk zijn gekomen of hoe ze die bepaalde set hyperparameters hebben gekozen. Het echte doel van dit artikel is dus om dergelijke stappen, mijn fouten en enkele stappen die ik zeer nuttig vond, te delen. Als zodanig, dit artikel is niet beperkt tot Stock Price Prediction probleem.

Hier zijn de dingen die we zullen kijken naar :

  1. Lezen en analyseren van gegevens. (Pandas)
  2. Normaliseren van de gegevens. (SkLearn)
  3. Omzetten van data naar tijdreeksen en supervised learning probleem.
  4. Maken van model (Keras)
  5. Fine tuning van het model (in het volgende artikel)
  6. Trainen, voorspellen en visualiseren van het resultaat.
  7. Tips & tools die ik erg nuttig vond (laatste artikel van de serie)

Let op dat dit eerste artikel het heeft over preprocessing stappen en terminologieën van LSTM. Als je deze stappen redelijk goed kent, kun je het volgende artikel overslaan.

Let’s begin!

Lezen en analyseren van de gegevens

Ik zal voor dit artikel de historische koersgegevens van GE gebruiken. U kunt de gegevens vinden in mijn kaggle site hier. Ik weet niet meer de bron van de gegevens omdat ik had gedownload het lang terug. We kunnen lezen van de gegevens in frame zoals hieronder weergegeven :

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

Zoals u kunt zien zijn er ongeveer 14060 items, die elk vertegenwoordigen een dag van de beurs attributen voor het bedrijf.

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

Het lijkt erop dat de koersen – Open, Dicht, Laag, Hoog – niet al te veel van elkaar verschillen, afgezien van incidentele lichte dalingen in de Laagkoers.

Nu laten we eens kijken naar de plot voor volume :

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

Huh. Heb je iets interessants gezien? Er is een sterke stijging van het aantal transacties rond de 12000e dag op de tijdlijn, die toevallig samenvalt met de plotselinge daling van de aandelenkoers. Misschien kunnen we teruggaan naar die specifieke datum en oude nieuwsartikelen opgraven om uit te zoeken wat de oorzaak was.

Nu eens kijken of we geen null/Nan waarden hebben om ons zorgen over te maken. Het blijkt dat we geen null waarden hebben. Geweldig!

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

Normaliseren van de data

De data is niet genormaliseerd en het bereik voor elke kolom varieert, met name Volume. Het normaliseren van de gegevens helpt het algoritme bij het convergeren, d.w.z. bij het efficiënt vinden van een lokaal/wereldwijd minimum. Ik zal MinMaxScaler van Sci-kit Learn gebruiken. Maar daarvoor moeten we de dataset splitsen in training en testing datasets. Ook zal ik het DataFrame omzetten naar ndarray in het proces.

Gegevens omzetten naar tijdreeksen en het probleem van gesuperviseerd leren

Dit is vrij belangrijk en enigszins lastig. Dit is waar de kennis LSTM nodig is. Ik zou een korte beschrijving willen geven van de belangrijkste concepten die hier nodig zijn, maar ik raad sterk aan om Andre karpathy’s blog hier te lezen, die wordt beschouwd als een van de beste bronnen over LSTM die er zijn en dit. Of u kunt Andrew Ng’s video ook bekijken (die overigens Andre’s blog ook noemt).

LSTM’s consumeren input in formaat ; een 3-dimensionale array.

  • Batch Size zegt hoeveel monsters van input je wilt dat je Neural Net te zien voordat het bijwerken van de gewichten. Dus stel je hebt 100 monsters (input dataset) en je wilt de gewichten bijwerken elke keer dat je NN een input heeft gezien. In dat geval zou de batchgrootte 1 zijn en het totaal aantal batches 100. Als je wilt dat je netwerk de gewichten update nadat het alle monsters heeft gezien, dan zou de batchgrootte 100 zijn en het aantal batches 1. Het blijkt dat het gebruik van een zeer kleine batchgrootte de snelheid van de training vermindert en aan de andere kant vermindert het gebruik van een te grote batchgrootte (zoals de hele dataset) het vermogen van het model om te generaliseren naar verschillende gegevens en het verbruikt ook meer geheugen. Maar het kost minder stappen om de minima voor de objectieve functie te vinden. Je moet dus verschillende waarden op je gegevens uitproberen en de sweet spot vinden. Het is nogal een groot onderwerp. We zullen in het volgende artikel zien hoe je deze op een wat slimmere manier kunt zoeken.
  • Tijdstappen definiëren hoeveel eenheden terug in de tijd je je netwerk wilt laten zien. Bijvoorbeeld als je werkt aan een karakter voorspellingsprobleem waar je een tekst corpus hebt om op te trainen en je besluit om je netwerk 6 karakters per keer te laten zien. Dan is je tijdstap 6. In ons geval gebruiken we 60 als tijdstap, d.w.z. we kijken naar 2 maanden data om de prijs van de volgende dag te voorspellen. Hierover later meer.
  • Features is het aantal attributen dat wordt gebruikt om elke tijdstap weer te geven. Beschouw het karaktervoorspellingsvoorbeeld hierboven, en veronderstel dat u een één-hot gecodeerde vector van grootte 100 gebruikt om elk karakter voor te stellen. Dan is de kenmerkgrootte hier 100.

Nu we de terminologie enigszins hebben opgehelderd, laten we onze voorraadgegevens omzetten in een geschikt formaat. Laten we voor het gemak aannemen dat we 3 als onze tijdstap hebben gekozen (we willen dat ons netwerk op 3 dagen gegevens terugkijkt om de prijs op de 4e dag te voorspellen), dan zouden we onze dataset als volgt samenstellen:

Samples 0 tot 2 zouden onze eerste input zijn en de dichte prijs van sample 3 zou de overeenkomstige outputwaarde zijn; beide omsloten door een groene rechthoek. Op dezelfde manier zouden de steekproeven 1 tot en met 3 onze tweede invoer vormen en zou de sluitkoers van steekproef 4 de uitvoerwaarde zijn; weergegeven door een blauwe rechthoek. Enzovoort. Dus tot nu toe hebben we een matrix met de vorm (3, 5), waarbij 3 de tijdstap is en 5 het aantal kenmerken. Bedenk nu hoeveel van zulke input-output paren mogelijk zijn in de bovenstaande afbeelding? 4.

Meng hiermee ook de partijgrootte. Laten we aannemen dat we een batchgrootte kiezen van 2. Dan zouden input-output paar 1 (groene rechthoek) en paar 2 (blauwe rechthoek) batch één vormen. En zo verder. Hier volgt een stukje python-code om dit te doen:

‘y_col_index’ is de index van uw uitvoerkolom. Stel nu dat je na het omzetten van de data in een supervised learning formaat, zoals hierboven getoond, 41 samples in je training dataset hebt maar je batch size is 20, dan zul je je training set moeten trimmen om de oneven samples te verwijderen die zijn weggelaten. Ik zal zoeken naar een betere manier om dit te omzeilen, maar voor nu is dit wat ik heb gedaan:

Nu met behulp van de bovenstaande functies laten we onze train, validatie en test datasets samenstellen

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)

Nu onze gegevens klaar zijn kunnen we ons concentreren op het bouwen van het model.

Model maken

We zullen LSTM gebruiken voor deze taak, wat een variatie is van Recurrent Neural Network. Het maken van een LSTM-model is zo eenvoudig als dit:

Nu u uw model hebt samengesteld en klaar bent om te worden getraind, kunt u het trainen zoals hieronder wordt getoond. Als u zich afvraagt welke waarden u moet gebruiken voor parameters zoals epochs, batchgrootte enzovoort, hoeft u zich geen zorgen te maken: we zullen in het volgende artikel zien hoe u deze waarden kunt bepalen.

Het trainen van dit model (met fijnafgestemde hyperparameters) gaf een beste fout van 3,27e-4 en een beste validatiefout van 3,7e-4. Hier ziet u hoe het Trainingsverlies vs Validatieverlies eruitzag:

Trainingfout vs Validatiefout

Zo zag de voorspelling eruit met bovenstaand model:

Voorspelling vs echte data

Ik heb ontdekt dat deze configuratie voor LSTM het beste werkt van alle combinaties die ik heb geprobeerd (voor deze dataset), en ik heb er meer dan 100 geprobeerd! De vraag is dus hoe je de perfecte (of in bijna alle gevallen, bijna perfecte) architectuur voor je neuraal netwerk vindt. Dit leidt ons naar ons volgende en belangrijke deel, dat in het volgende artikel wordt vervolgd.

U kunt alle volledige programma’s hier op mijn Github profiel vinden.

NOTE: Een nederig verzoek aan de lezers – U bent allen welkom om contact met mij op te nemen op LinkedIn of Twitter, maar als u een vraag over mijn blogs hebt, plaats die dan alstublieft in het commentaargedeelte van de betreffende blog in plaats van in persoonlijke berichten, zodat als iemand anders dezelfde vraag heeft, hij of zij die hier zelf zou vinden, en ik die niet afzonderlijk zou hoeven uit te leggen. Echter, je bent nog steeds welkom om vragen die niet gerelateerd zijn aan blogs of algemene technische vragen, naar mij persoonlijk te sturen. Bedankt 🙂

UPDATE 13/4/19

  1. Het is mij ter ore gekomen, sinds ik dit artikel heb geschreven, dat mijn model dat voor deze blog is gebruikt, mogelijk overfitted is. Hoewel ik het niet heb bevestigd, is het waarschijnlijk. Dus wees voorzichtig bij het implementeren van dit in uw projecten. U kunt dingen proberen zoals minder tijdpochs, een kleiner netwerk, meer uitval, enz.
  2. Ik heb Sigmoid activering gebruikt voor de laatste laag, die kan lijden onder de beperking dat het niet in staat is om een prijs te voorspellen die groter is dan de ‘max’ prijs in de dataset. U zou ‘Lineaire’ activering voor de laatste laag kunnen proberen om dit op te lossen.
  3. Verholpen met een typfout in “omzetten van gegevens naar tijdreeksen” sectie.

Bedankt aan de lezers voor het onder mijn aandacht brengen van deze zaken.

UPDATE 21/1/2020

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.