Predicting Stock Price with LSTM

Uczenie maszynowe znalazło w ostatnich latach swoje zastosowanie w wielu ciekawych dziedzinach. Oswajanie rynku akcji jest jedną z nich. Już od dłuższego czasu myślałem o tym, żeby spróbować; głównie po to, żeby ugruntować swoją wiedzę na temat LSTMów. I w końcu skończyłem projekt i jestem podekscytowany, że mogę podzielić się moimi doświadczeniami.

Motywacja i docelowa publiczność

Będę pisał o moich doświadczeniach w serii blogów. Celem tej serii nie jest wyjaśnienie podstaw LSTM lub koncepcji uczenia maszynowego. Dlatego założę, że czytelnik rozpoczął swoją podróż z Machine Learning i ma podstawy takie jak Python, znajomość SkLearn, Keras, LSTM itp. Powodem jest to, że istnieją już doskonałe artykuły na tematy takie jak „Jak działają LSTMy?” przez ludzi, którzy są znacznie bardziej wykwalifikowani, aby wyjaśnić matematykę za tym. Ale będę dzielił się linkami do takich artykułów, gdziekolwiek czuję, że może brakować wiedzy tła. Podczas gdy istnieje wiele artykułów mówiących o tym jak przewidzieć ceny akcji na podstawie zbioru danych, w większości przypadków autorzy nie ujawniają/wyjaśniają jak doszli do tej konkretnej konfiguracji sieci neuronowej lub jak wybrali ten konkretny zestaw hiperparametrów. Tak więc prawdziwym celem tego artykułu jest podzielenie się takimi krokami, moimi błędami i kilkoma krokami, które okazały się bardzo pomocne. Jako taki, ten artykuł nie jest ograniczony do problemu Stock Price Prediction.

Oto rzeczy, na które będziemy patrzeć :

  1. Czytanie i analiza danych. (Pandas)
  2. Normalizacja danych. (SkLearn)
  3. Konwersja danych do szeregów czasowych i problem uczenia nadzorowanego.
  4. Tworzenie modelu (Keras)
  5. Dokładne dostrajanie modelu (w następnym artykule)
  6. Trenowanie, przewidywanie i wizualizacja wyniku.
  7. Wskazówki &narzędzia, które uznałem za bardzo pomocne (ostatni artykuł z serii)

Proszę zauważyć, że ten pierwszy artykuł mówi o krokach wstępnego przetwarzania i terminologii LSTM. Jeśli jesteś dość pewny tych kroków, możesz przejść do następnego artykułu.

Zacznijmy!

Czytanie i analiza danych

W tym poście będę używał historycznych danych cen akcji GE. Dane można znaleźć w mojej witrynie kaggle tutaj. Nie pamiętam źródła danych, ponieważ pobrałem je dawno temu. Możemy odczytać dane do ramki jak pokazano poniżej :

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

Jak widać jest tam około 14060 pozycji, każda reprezentująca jeden dzień atrybutów giełdowych dla firmy. Zobaczmy, jak to wygląda na wykresie :

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

Wydaje się, że ceny – Otwarcie, Zamknięcie, Niskie, Wysokie – nie różnią się zbytnio od siebie, z wyjątkiem sporadycznych niewielkich spadków ceny Niskiej.

Teraz sprawdźmy wykres dla wolumenu :

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

Huh. Czy zauważyłeś coś ciekawego? Istnieje dość gwałtowny wzrost liczby transakcji około 12000th dzień na osi czasu, który zdarza się zbiega się z nagłym spadkiem cen akcji. Być może możemy cofnąć się do tej konkretnej daty i wykopać stare artykuły informacyjne, aby znaleźć przyczynę tego zjawiska.

Teraz zobaczmy, czy mamy jakieś wartości null/Nan, o które należy się martwić. Jak się okazuje, nie mamy żadnych wartości null. Świetnie!

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

Normalizacja danych

Dane nie są znormalizowane i zakres dla każdej kolumny jest różny, szczególnie Volume. Normalizacja danych pomaga algorytmowi w konwergencji, tj. w znalezieniu lokalnego/globalnego minimum efektywnie. Użyję MinMaxScaler z Sci-kit Learn. Ale przed tym musimy podzielić zbiór danych na treningowy i testowy. Również będę konwertował DataFrame do ndarray w procesie.

Konwersja danych do serii czasowych i problem uczenia nadzorowanego

To jest dość ważne i nieco podstępne. Tu właśnie potrzebna jest wiedza LSTM. Dałbym krótki opis kluczowych pojęć, które są potrzebne tutaj, ale zdecydowanie polecam czytanie Andre karpathy’s blog tutaj, który jest uważany za jeden z najlepszych zasobów na LSTM tam i to. Możesz też obejrzeć film Andrew Ng’a (który przy okazji wspomina również blog Andre).

LSTMs zużywają dane wejściowe w formacie ; 3-wymiarowej tablicy.

  • Batch Size mówi, ile próbek danych wejściowych chcesz, aby twoja sieć neuronowa zobaczyła przed aktualizacją wag. Załóżmy więc, że masz 100 próbek (zestaw danych wejściowych) i chcesz aktualizować wagi za każdym razem, gdy NN zobaczy dane wejściowe. W tym przypadku rozmiar partii wynosiłby 1, a całkowita liczba partii wynosiłaby 100. Podobnie gdybyśmy chcieli, aby nasza sieć aktualizowała wagi po zobaczeniu wszystkich próbek, wielkość partii wynosiłaby 100, a liczba partii 1. Jak się okazuje, użycie bardzo małego rozmiaru partii zmniejsza szybkość treningu, a z drugiej strony użycie zbyt dużego rozmiaru partii (jak cały zbiór danych) zmniejsza zdolność modelu do generalizacji na różne dane, a także zużywa więcej pamięci. Ale to wymaga mniej kroków, aby znaleźć minima dla funkcji celu. Więc musisz wypróbować różne wartości na swoich danych i znaleźć słodki punkt. Jest to dość duży temat. Zobaczymy, jak wyszukiwać je w nieco inteligentniejszy sposób w następnym artykule.
  • Kroki czasowe definiują, ile jednostek wstecz w czasie chcesz, aby twoja sieć widziała. Na przykład, jeśli pracowałeś nad problemem przewidywania znaków, gdzie masz korpus tekstowy do trenowania i zdecydowałeś się podawać sieci 6 znaków na raz. W naszym przypadku użyjemy 60 jako krok czasowy, tzn. będziemy patrzeć na 2 miesiące danych, aby przewidzieć cenę następnego dnia. Więcej na ten temat później.
  • Cechy to liczba atrybutów używanych do reprezentowania każdego kroku czasowego. Rozważmy powyższy przykład przewidywania znaków i załóżmy, że używasz wektora zakodowanego w trybie one-hot o rozmiarze 100 do reprezentowania każdego znaku. Wtedy rozmiar cechy tutaj jest 100.

Teraz, gdy mamy już trochę wyjaśnionych terminologii z drogi, przekonwertujmy nasze dane magazynowe do odpowiedniego formatu. Załóżmy, dla uproszczenia, że wybraliśmy 3 jako czas nasz krok czasowy (chcemy, aby nasza sieć spojrzeć wstecz na 3 dni danych, aby przewidzieć cenę na 4 dzień), a następnie będziemy tworzyć nasz zbiór danych, jak to:

Próbki od 0 do 2 byłyby naszymi pierwszymi danymi wejściowymi, a cena zamknięcia próbki 3 byłaby odpowiadającą jej wartością wyjściową; obie zamknięte przez zielony prostokąt. Podobnie próbki od 1 do 3 byłyby naszymi drugimi danymi wejściowymi, a cena zamknięcia próbki 4 byłaby wartością wyjściową; reprezentowaną przez niebieski prostokąt. I tak dalej. Tak więc do tej pory mamy macierz o kształcie (3, 5), gdzie 3 to krok czasowy, a 5 to liczba cech. Zastanów się teraz ile takich par wejście-wyjście jest możliwych na powyższym obrazku? 4.

Zmieszaj z tym również wielkość partii. Załóżmy, że wybierzemy wielkość partii 2. Wtedy para wejście-wyjście 1 (zielony prostokąt) i para 2 (niebieski prostokąt) będą stanowiły partię pierwszą. I tak dalej. Oto fragment kodu Pythona, aby to zrobić:

’y_col_index’ to indeks twojej kolumny wyjściowej. Teraz załóżmy, że po przekonwertowaniu danych na format uczenia nadzorowanego, jak pokazano powyżej, masz 41 próbek w zestawie danych szkoleniowych, ale rozmiar partii wynosi 20, wtedy będziesz musiał przyciąć zestaw treningowy, aby usunąć nieparzyste próbki pozostawione na zewnątrz. Będę szukał lepszego sposobu na obejście tego problemu, ale na razie to jest to co zrobiłem:

Teraz używając powyższych funkcji utwórzmy nasze zestawy danych trenujących, walidacyjnych i testowych

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)

Teraz, gdy nasze dane są gotowe możemy skoncentrować się na budowaniu modelu.

Tworzenie modelu

Do tego zadania będziemy używać LSTM, który jest odmianą rekurencyjnej sieci neuronowej. Tworzenie modelu LSTM jest tak proste jak poniżej:

Teraz, gdy masz już swój model skompilowany i gotowy do wytrenowania, wytrenuj go jak pokazano poniżej. Jeśli zastanawiasz się, jakich wartości użyć dla parametrów takich jak epoki, rozmiar partii itp., nie martw się, zobaczymy, jak to zrobić w następnym artykule.

Trenowanie tego modelu (z dobrze dostrojonymi hiperparametrami) dało najlepszy błąd 3,27e-4 i najlepszy błąd walidacji 3,7e-4. Oto jak wyglądała strata szkoleniowa vs strata walidacyjna:

Błąd szkoleniowy vs błąd walidacyjny

Tak wyglądała predykcja przy użyciu powyższego modelu:

prediction vs real data

Odkryłem, że ta konfiguracja dla LSTM działa najlepiej ze wszystkich kombinacji jakie wypróbowałem (dla tego zbioru danych), a wypróbowałem ponad 100! Więc pytanie brzmi, jak wylądować na idealnej (lub w prawie wszystkich przypadkach, bliskiej ideału) architekturze dla twojej sieci neuronowej? To prowadzi nas do naszej następnej i ważnej sekcji, która będzie kontynuowana w następnym artykule.

Wszystkie kompletne programy można znaleźć na moim profilu Github tutaj.

UWAGA: Pokorna prośba do czytelników – Wszyscy jesteście mile widziani, aby połączyć się ze mną na LinkedIn lub Twitterze, ale jeśli macie pytania dotyczące moich blogów, proszę zamieścić je w sekcji komentarzy na danym blogu zamiast osobistych wiadomości, tak aby jeśli ktoś inny ma takie samo pytanie, znalazł je tutaj i nie musiałbym wyjaśniać go indywidualnie. Jednakże, nadal zapraszam do wysyłania zapytań niezwiązanych z blogami lub ogólnych zapytań technicznych, do mnie osobiście. Dzięki 🙂

UPDATE 13/4/19

  1. Dotarło do mnie, odkąd napisałem ten artykuł, że mój model użyty do tego bloga mógł być przepasowany. Chociaż nie potwierdziłem tego, jest to prawdopodobne. Więc proszę być ostrożnym podczas wdrażania tego w swoich projektach. Możesz wypróbować takie rzeczy jak mniejsza liczba epok, mniejsza sieć, więcej dropout itp.
  2. Użyłem aktywacji Sigmoid dla ostatniej warstwy, która może cierpieć z powodu ograniczenia polegającego na tym, że nie jest w stanie przewidzieć ceny większej niż 'maksymalna’ cena w zbiorze danych. Możesz spróbować aktywacji 'Linear’ dla ostatniej warstwy, aby to rozwiązać.
  3. Poprawiłem literówkę w sekcji „konwertowanie danych do szeregu czasowego”.

Dziękuję czytelnikom za zwrócenie mi na to uwagi.

UPDATE 21/1/2020

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.