Forudsigelse af aktiekursen med LSTM

Maskinlæring har fundet anvendelse på mange interessante områder i disse år. At tæmme aktiemarkedet er et af dem. Jeg havde tænkt på at give det et skud i et stykke tid nu; mest for at størkne min arbejdskendskab til LSTM’er. Og endelig har jeg afsluttet projektet og er ret begejstret for at dele min erfaring.

Motivation og målgruppe

Jeg vil skrive om min erfaring over en række blogs. Formålet med denne serie er ikke at forklare det grundlæggende i LSTM eller Machine Learning-koncepter. Derfor vil jeg antage, at læseren har påbegyndt sin rejse med Machine Learning og har det grundlæggende som Python, kendskab til SkLearn, Keras, LSTM osv. Grunden er, at der allerede findes fremragende artikler om emner som “Hvordan fungerer LSTM’er?” af folk, der er langt mere kvalificerede til at forklare matematikken bag. Men jeg vil dele links til sådanne artikler, hvor jeg føler, at der måske mangler baggrundsviden. Mens der er masser af artikler derude, der fortæller dig, hvordan du kan forudsige aktiekurser på baggrund af et datasæt, afslører/forklarer forfatterne for det meste ikke, hvordan de nåede frem til den pågældende konfiguration for et neuralt netværk, eller hvordan de valgte det pågældende sæt hyperparametre. Så det egentlige formål med denne artikel er at dele sådanne trin, mine fejltagelser og nogle trin, som jeg fandt meget nyttige. Som sådan er denne artikel ikke begrænset til problemet med forudsigelse af aktiekursen.

Her er de ting, vi vil se på :

  1. Læsning og analyse af data. (Pandas)
  2. Normalisering af data. (SkLearn)
  3. Konvertering af data til tidsserier og superviseret læringsproblem.
  4. Skabelse af model (Keras)
  5. Finjustering af modellen (i næste artikel)
  6. Træning, forudsigelse og visualisering af resultatet.
  7. Tips & værktøjer, som jeg fandt meget nyttige (sidste artikel i serien)

Bemærk venligst, at denne første artikel taler om præprocesseringstrin og terminologier for LSTM. Hvis du er ret sikker på disse trin, kan du springe til næste artikel.

Lad os begynde!

Læsning og analyse af data

Jeg vil bruge de historiske aktiekursdata for GE til dette indlæg. Du kan finde dataene på mit kaggle-site her. Jeg kan ikke huske kilden til dataene, da jeg havde downloadet dem for længe siden. Vi kan læse dataene ind i rammen som vist nedenfor :

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

Som du kan se, er der omkring 14060 elementer, der hver repræsenterer en dags aktiemarkedsattributter for virksomheden. Lad os se, hvordan det ser ud på et plot :

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

Det ser ud til, at priserne – Open, Close, Low, High – ikke varierer ret meget fra hinanden, bortset fra lejlighedsvise mindre fald i Low-prisen.

Nu skal vi se på plottet for volumen :

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

Huh. Har du set noget interessant? Der er en ganske kraftig stigning i antallet af transaktioner omkring den 12000. dag på tidslinjen, hvilket tilfældigvis falder sammen med det pludselige fald i aktiekursen. Måske kan vi gå tilbage til netop den dato og grave gamle nyhedsartikler op for at finde ud af, hvad der forårsagede det.

Nu skal vi se, om vi har nogle null/Nan-værdier at bekymre os om. Det viser sig, at vi ikke har nogen nulværdier. Fint!

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

Normalisering af dataene

Dataene er ikke normaliseret, og intervallet for hver kolonne varierer, især Volume. Normalisering af data hjælper algoritmen med at konvergere, dvs. med at finde et lokalt/ globalt minimum effektivt. Jeg vil bruge MinMaxScaler fra Sci-kit Learn. Men før det skal vi opdele datasættet i trænings- og testdatasæt. Jeg vil også konvertere DataFrame til ndarray i processen.

Konvertering af data til tidsserier og overvåget læringsproblem

Dette er ret vigtigt og noget tricky. Det er her, at der er brug for viden LSTM. Jeg ville give en kort beskrivelse af nøglebegreber, der er nødvendige her, men jeg anbefaler stærkt at læse Andre karpathys blog her, som betragtes som en af de bedste ressourcer om LSTM derude og dette. Eller du kan også se Andrew Ng’s video (som i øvrigt også nævner Andre’s blog).

LSTM’er forbruger input i format ; et 3-dimensionelt array.

  • Batch Size siger, hvor mange prøver af input, du vil have dit neurale net til at se, før du opdaterer vægtene. Så lad os sige, at du har 100 prøver (input datasæt), og du ønsker at opdatere vægtene hver gang din NN har set et input. I det tilfælde vil batchstørrelsen være 1, og det samlede antal batches vil være 100. Hvis du på samme måde ønsker, at dit netværk skal opdatere vægtene, efter at det har set alle prøverne, ville batchstørrelsen være 100, og antallet af batches ville være 1. Det viser sig, at meget små batchstørrelser reducerer træningshastigheden, og på den anden side reducerer for store batchstørrelser (f.eks. hele datasættet) modellens evne til at generalisere til forskellige data, og det bruger også mere hukommelse. Men det tager færre trin at finde minima for din målfunktion. Så du er nødt til at afprøve forskellige værdier på dine data og finde det bedste sted. Det er et ret stort emne. Vi vil se, hvordan man søger disse på en lidt smartere måde i næste artikel.
  • Tidstrin definerer, hvor mange enheder tilbage i tiden du vil have dit netværk til at se. For eksempel hvis du arbejdede på et tegnprædiktionsproblem, hvor du har et tekstkorpus at træne på, og du beslutter dig for at fodre dit netværk med 6 tegn ad gangen. Så er dit tidstrin 6. I vores tilfælde bruger vi 60 som tidstrin, dvs. at vi vil se på 2 måneders data for at forudsige næste dags pris. Mere om dette senere.
  • Funktioner er antallet af attributter, der bruges til at repræsentere hvert tidstrin. Overvej eksemplet med tegnforudsigelse ovenfor, og antag, at du bruger en én-hot-kodet vektor af størrelse 100 til at repræsentere hvert tegn. Så er feature-størrelsen her 100.

Nu da vi har fået ryddet nogle af de opklarede terminologier af vejen, skal vi konvertere vores lagerdata til et passende format. Lad os antage, for enkelhedens skyld, at vi valgte 3 som tid vores tidstrin (vi vil have vores netværk til at se tilbage på 3 dages data for at forudsige prisen på 4. dag), så ville vi danne vores datasæt på denne måde:

Stikprøverne 0 til 2 ville være vores første input, og lukkekursen for stikprøve 3 ville være den tilsvarende outputværdi; begge er omgivet af et grønt rektangel. På samme måde vil prøve 1 til 3 være vores andet input, og lukkeprisen for prøve 4 vil være den tilsvarende udgangsværdi, repræsenteret af et blåt rektangel. Og så videre. Indtil nu har vi altså en matrix af formen (3, 5), hvor 3 er tidstrinnet og 5 er antallet af funktioner. Tænk nu over, hvor mange sådanne input-output-par er mulige i ovenstående billede? 4.

Bland også batchstørrelsen med dette. Lad os antage, at vi vælger en batch-størrelse på 2. Så vil input-output-par 1 (grønt rektangel) og par 2 (blåt rektangel) udgøre batch 1. Og så videre. Her er python-kodesnipslet til at gøre dette:

‘y_col_index’ er indekset for din udgangskolonne. Hvis du nu antager, at du efter konvertering af data til supervised learning-format, som vist ovenfor, har 41 prøver i dit træningsdatasæt, men din batchstørrelse er 20, så skal du trimme dit træningssæt for at fjerne de ulige prøver, der er udeladt. Jeg vil kigge efter en bedre måde at omgå dette på, men indtil videre er dette, hvad jeg har gjort:

Nu ved hjælp af ovenstående funktioner kan vi danne vores trænings-, validerings- og testdatasæt

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, hvor vores data er klar, kan vi koncentrere os om at opbygge modellen.

Skabelse af model

Vi vil bruge LSTM til denne opgave, som er en variant af Recurrent Neural Network (rekursivt neuralt netværk). Oprettelse af LSTM-modellen er så enkel som denne:

Nu, hvor du har din model kompileret og er klar til at blive trænet, skal du træne den som vist nedenfor. Hvis du undrer dig over, hvilke værdier du skal bruge for parametre som epochs, batchstørrelse osv., så fortvivl ikke, vi vil se, hvordan du finder ud af det i den næste artikel.

Træning af denne model (med fintunede hyperparametre) gav den bedste fejl på 3,27e-4 og den bedste valideringsfejl på 3,7e-4. Her er hvordan træningstab vs. valideringstab så ud:

Træningsfejl vs. valideringsfejl

Der er hvordan forudsigelsen så ud med ovenstående model:

forudsigelse vs. reelle data

Jeg har fundet ud af, at denne konfiguration for LSTM fungerer bedst ud af alle de kombinationer, jeg har prøvet (for dette datasæt), og jeg har prøvet mere end 100! Så spørgsmålet er, hvordan du lander på den perfekte (eller i næsten alle tilfælde, tæt på den perfekte) arkitektur til dit neurale netværk? Dette fører os til vores næste og vigtige afsnit, der fortsættes i den næste artikel.

Du kan finde alle de komplette programmer på min Github-profil her.

NOTAT: En ydmyg anmodning til læserne – I er alle velkomne til at komme i kontakt med mig på LinkedIn eller Twitter, men hvis du har en forespørgsel vedrørende mine blogs, bedes du skrive den i kommentarfeltet på respektive blog i stedet for personlige beskeder, så hvis andre har den samme forespørgsel, ville de finde den her selv, og jeg ville ikke skulle forklare den individuelt. Du er dog stadig velkommen til at sende forespørgsler, der ikke har noget med blogs eller generelle tekniske forespørgsler at gøre, til mig personligt. Tak 🙂

UPDATE 13/4/19

  1. Det er kommet til min viden, siden jeg har skrevet denne artikel, at min model, der blev brugt til denne blog, måske har været overfittet. Selv om jeg ikke har bekræftet det, er det sandsynligt. Så vær venligst forsigtig, mens du implementerer dette i dine projekter. Du kunne prøve ting som mindre epochs, mindre netværk, mere dropout osv.
  2. Jeg har brugt Sigmoid aktivering til sidste lag, som kan lide af begrænsning af ikke at kunne forudsige en pris større end ‘max’ pris i datasæt. Du kunne prøve ‘Lineær’ aktivering for sidste lag for at løse dette.
  3. Fikseret en skrivefejl i afsnittet “konvertering af data til tidsserier”.

Tak til læserne for at gøre mig opmærksom på disse.

UPDATE 21/1/2020

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.