Zrobiłem sieć neuronową LSTM (RNN) z nadzorowanym uczeniem się do prognozowania zapasów danych. Problem polega na tym, dlaczego źle prognozuje na podstawie własnych danych treningowych? (uwaga: odtwarzalny przykład poniżej)
Stworzyłem prosty model do przewidywania ceny akcji na najbliższe 5 dni:
model = Sequential()
model.add(LSTM(32, activation='sigmoid', input_shape=(x_train.shape[1], x_train.shape[2])))
model.add(Dense(y_train.shape[1]))
model.compile(optimizer='adam', loss='mse')
es = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)
model.fit(x_train, y_train, batch_size=64, epochs=25, validation_data=(x_test, y_test), callbacks=[es])
Prawidłowe wyniki podano w y_test
(5 wartości), więc modeluj pociągi, patrząc wstecz na 90 poprzednich dni, a następnie przywróć wagi od najlepszego ( val_loss=0.0030
) wyniku patience=3
:
Train on 396 samples, validate on 1 samples
Epoch 1/25
396/396 [==============================] - 1s 2ms/step - loss: 0.1322 - val_loss: 0.0299
Epoch 2/25
396/396 [==============================] - 0s 402us/step - loss: 0.0478 - val_loss: 0.0129
Epoch 3/25
396/396 [==============================] - 0s 397us/step - loss: 0.0385 - val_loss: 0.0178
Epoch 4/25
396/396 [==============================] - 0s 399us/step - loss: 0.0398 - val_loss: 0.0078
Epoch 5/25
396/396 [==============================] - 0s 391us/step - loss: 0.0343 - val_loss: 0.0030
Epoch 6/25
396/396 [==============================] - 0s 391us/step - loss: 0.0318 - val_loss: 0.0047
Epoch 7/25
396/396 [==============================] - 0s 389us/step - loss: 0.0308 - val_loss: 0.0043
Epoch 8/25
396/396 [==============================] - 0s 393us/step - loss: 0.0292 - val_loss: 0.0056
Wynik prognozy jest niesamowity, prawda?
Jest tak, ponieważ algorytm przywrócił najlepsze wagi z epoki # 5. Okej, zapiszmy teraz ten model do .h5
pliku , cofnijmy się o 10 dni i przewidujmy ostatnie 5 dni (w pierwszym przykładzie wykonaliśmy model i sprawdziliśmy go w dniach 17-23 kwietnia, w tym weekendy wolne od pracy, teraz przetestujmy w dniach 2-8 kwietnia). Wynik:
Pokazuje absolutnie zły kierunek. Jak widzimy, dzieje się tak, ponieważ model został przeszkolony i najlepiej wykorzystał epokę nr 5 do walidacji ustawionej na 17–23 kwietnia, ale nie na 2-8. Jeśli spróbuję więcej trenować, bawiąc się epoką, którą wybiorę, cokolwiek zrobię, w przeszłości zawsze istnieje wiele przedziałów czasowych, które mają błędne prognozy.
Dlaczego model pokazuje błędne wyniki na własnych przeszkolonych danych? Przeszkoliłem dane, musi pamiętać, jak przewidywać dane na tym zestawie, ale źle przewiduje. Co też próbowałem:
- Używaj dużych zestawów danych z ponad 50 tys. Wierszy, 20-letnimi cenami akcji, dodając więcej lub mniej funkcji
- Twórz różne typy modeli, np. Dodawaj więcej ukrytych warstw, różne rozmiary partii, różne aktywacje warstw, rezygnacje, normalizacja partii
- Utwórz niestandardowe wywołanie zwrotne EarlyStopping, uzyskaj średnią wartość val_loss z wielu zestawów danych sprawdzania poprawności i wybierz najlepszy
Może coś mi umknęło? Co mogę poprawić?
Oto bardzo prosty i powtarzalny przykład. yfinance
pobiera dane giełdowe S&P 500.
"""python 3.7.7
tensorflow 2.1.0
keras 2.3.1"""
import numpy as np
import pandas as pd
from keras.callbacks import EarlyStopping, Callback
from keras.models import Model, Sequential, load_model
from keras.layers import Dense, Dropout, LSTM, BatchNormalization
from sklearn.preprocessing import MinMaxScaler
import plotly.graph_objects as go
import yfinance as yf
np.random.seed(4)
num_prediction = 5
look_back = 90
new_s_h5 = True # change it to False when you created model and want test on other past dates
df = yf.download(tickers="^GSPC", start='2018-05-06', end='2020-04-24', interval="1d")
data = df.filter(['Close', 'High', 'Low', 'Volume'])
# drop last N days to validate saved model on past
df.drop(df.tail(0).index, inplace=True)
print(df)
class EarlyStoppingCust(Callback):
def __init__(self, patience=0, verbose=0, validation_sets=None, restore_best_weights=False):
super(EarlyStoppingCust, self).__init__()
self.patience = patience
self.verbose = verbose
self.wait = 0
self.stopped_epoch = 0
self.restore_best_weights = restore_best_weights
self.best_weights = None
self.validation_sets = validation_sets
def on_train_begin(self, logs=None):
self.wait = 0
self.stopped_epoch = 0
self.best_avg_loss = (np.Inf, 0)
def on_epoch_end(self, epoch, logs=None):
loss_ = 0
for i, validation_set in enumerate(self.validation_sets):
predicted = self.model.predict(validation_set[0])
loss = self.model.evaluate(validation_set[0], validation_set[1], verbose = 0)
loss_ += loss
if self.verbose > 0:
print('val' + str(i + 1) + '_loss: %.5f' % loss)
avg_loss = loss_ / len(self.validation_sets)
print('avg_loss: %.5f' % avg_loss)
if self.best_avg_loss[0] > avg_loss:
self.best_avg_loss = (avg_loss, epoch + 1)
self.wait = 0
if self.restore_best_weights:
print('new best epoch = %d' % (epoch + 1))
self.best_weights = self.model.get_weights()
else:
self.wait += 1
if self.wait >= self.patience or self.params['epochs'] == epoch + 1:
self.stopped_epoch = epoch
self.model.stop_training = True
if self.restore_best_weights:
if self.verbose > 0:
print('Restoring model weights from the end of the best epoch')
self.model.set_weights(self.best_weights)
def on_train_end(self, logs=None):
print('best_avg_loss: %.5f (#%d)' % (self.best_avg_loss[0], self.best_avg_loss[1]))
def multivariate_data(dataset, target, start_index, end_index, history_size, target_size, step, single_step=False):
data = []
labels = []
start_index = start_index + history_size
if end_index is None:
end_index = len(dataset) - target_size
for i in range(start_index, end_index):
indices = range(i-history_size, i, step)
data.append(dataset[indices])
if single_step:
labels.append(target[i+target_size])
else:
labels.append(target[i:i+target_size])
return np.array(data), np.array(labels)
def transform_predicted(pr):
pr = pr.reshape(pr.shape[1], -1)
z = np.zeros((pr.shape[0], x_train.shape[2] - 1), dtype=pr.dtype)
pr = np.append(pr, z, axis=1)
pr = scaler.inverse_transform(pr)
pr = pr[:, 0]
return pr
step = 1
# creating datasets with look back
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df.values)
dataset = df_normalized[:-num_prediction]
x_train, y_train = multivariate_data(dataset, dataset[:, 0], 0,len(dataset) - num_prediction + 1, look_back, num_prediction, step)
indices = range(len(dataset)-look_back, len(dataset), step)
x_test = np.array(dataset[indices])
x_test = np.expand_dims(x_test, axis=0)
y_test = np.expand_dims(df_normalized[-num_prediction:, 0], axis=0)
# creating past datasets to validate with EarlyStoppingCust
number_validates = 50
step_past = 5
validation_sets = [(x_test, y_test)]
for i in range(1, number_validates * step_past + 1, step_past):
indices = range(len(dataset)-look_back-i, len(dataset)-i, step)
x_t = np.array(dataset[indices])
x_t = np.expand_dims(x_t, axis=0)
y_t = np.expand_dims(df_normalized[-num_prediction-i:len(df_normalized)-i, 0], axis=0)
validation_sets.append((x_t, y_t))
if new_s_h5:
model = Sequential()
model.add(LSTM(32, return_sequences=False, activation = 'sigmoid', input_shape=(x_train.shape[1], x_train.shape[2])))
# model.add(Dropout(0.2))
# model.add(BatchNormalization())
# model.add(LSTM(units = 16))
model.add(Dense(y_train.shape[1]))
model.compile(optimizer = 'adam', loss = 'mse')
# EarlyStoppingCust is custom callback to validate each validation_sets and get average
# it takes epoch with best "best_avg" value
# es = EarlyStoppingCust(patience = 3, restore_best_weights = True, validation_sets = validation_sets, verbose = 1)
# or there is keras extension with built-in EarlyStopping, but it validates only 1 set that you pass through fit()
es = EarlyStopping(monitor = 'val_loss', patience = 3, restore_best_weights = True)
model.fit(x_train, y_train, batch_size = 64, epochs = 25, shuffle = True, validation_data = (x_test, y_test), callbacks = [es])
model.save('s.h5')
else:
model = load_model('s.h5')
predicted = model.predict(x_test)
predicted = transform_predicted(predicted)
print('predicted', predicted)
print('real', df.iloc[-num_prediction:, 0].values)
print('val_loss: %.5f' % (model.evaluate(x_test, y_test, verbose=0)))
fig = go.Figure()
fig.add_trace(go.Scatter(
x = df.index[-60:],
y = df.iloc[-60:,0],
mode='lines+markers',
name='real',
line=dict(color='#ff9800', width=1)
))
fig.add_trace(go.Scatter(
x = df.index[-num_prediction:],
y = predicted,
mode='lines+markers',
name='predict',
line=dict(color='#2196f3', width=1)
))
fig.update_layout(template='plotly_dark', hovermode='x', spikedistance=-1, hoverlabel=dict(font_size=16))
fig.update_xaxes(showspikes=True)
fig.update_yaxes(showspikes=True)
fig.show()
df.drop(df.tail(10).index, inplace=True)
, pokazał ten sam zły wynik, co miałem.Odpowiedzi:
Chcesz, aby model nauczył się związku między wejściem a wyjściem zamiast zapamiętywania. Jeśli model zapamiętuje poprawne dane wyjściowe dla każdego wejścia, możemy powiedzieć, że są one niezgodne z danymi treningowymi. Często możesz zmusić model do przeregulowania, używając małego podzbioru danych, więc jeśli takie zachowanie chcesz zobaczyć, możesz to wypróbować.
źródło
PO postuluje interesujące odkrycie. Pozwolę sobie uprościć pierwotne pytanie w następujący sposób.
Jeśli model jest trenowany w określonym szeregu czasowym, dlaczego model nie może zrekonstruować danych z poprzednich szeregów czasowych, na których już był trenowany?
Odpowiedź jest osadzona w samym postępie szkolenia. Ponieważ
EarlyStopping
zastosowano go tutaj, aby uniknąć przeuczenia, najlepszy model zapisano w miejscuepoch=5
, o którymval_loss=0.0030
wspomniało PO. W tym przypadku utrata treningu jest równa0.0343
RMSE treningu0.185
. Ponieważ zestaw danych jest skalowany przy użyciuMinMaxScalar
, musimy cofnąć skalowanie RMSE, aby zrozumieć, co się dzieje.Stwierdzono, że minimalne i maksymalne wartości sekwencji czasowej to
2290
i3380
. Dlatego posiadanie0.185
jako RMSE treningu oznacza, że nawet dla zestawu treningowego przewidywane wartości mogą różnić się od podstawowych wartości prawdy o około0.185*(3380-2290)
, czyli~200
średnio o jednostki.To wyjaśnia, dlaczego istnieje duża różnica w przewidywaniu samych danych treningowych na poprzednim etapie czasowym.
Co powinienem zrobić, aby idealnie emulować dane treningowe?
Zadałem sobie to pytanie. Prosta odpowiedź brzmi: spraw, aby strata treningowa zbliżała się
0
, a więc przewyższa model.Po pewnym treningu zdałem sobie sprawę, że model z tylko 1 warstwą LSTM z
32
komórkami nie jest wystarczająco skomplikowany, aby zrekonstruować dane treningowe. Dlatego dodałem kolejną warstwę LSTM w następujący sposób.Model jest szkolony dla
1000
epok bez uwzględnieniaEarlyStopping
.Pod koniec
1000
tej epoki utrata treningu0.00047
jest znacznie niższa niż utrata treningu w twoim przypadku. Oczekujemy więc, że model lepiej zrekonstruuje dane treningowe. Poniżej znajduje się wykres prognoz dla 2-8 kwietnia.Ostatnia uwaga:
Szkolenie na konkretnej bazie danych niekoniecznie oznacza, że model powinien być w stanie idealnie zrekonstruować dane szkoleniowe. W szczególności, gdy metody takie jak wczesne zatrzymanie, regularyzacja i rezygnacja są wprowadzane w celu uniknięcia nadmiernego dopasowania, model jest bardziej uogólniony niż zapamiętywanie danych treningowych.
źródło
Zasadniczo Jeśli chcesz uzyskać lepszy wynik dla danych treningowych, dokładność treningu powinna być jak najwyższa. Powinieneś użyć lepszego modelu w odniesieniu do posiadanych danych. Zasadniczo powinieneś sprawdzić, czy Twoja dokładność treningu w tym celu jest niezależna od dokładności testu. Nazywa się to również nadmiernym dopasowaniem, które zapewnia większą dokładność danych treningowych niż danych testowych.
Wcześniejsze zatrzymanie może mieć wpływ na ten scenariusz, w którym wybierana jest najlepsza dokładność testu / walidacji, a nie dokładność szkolenia.
źródło
Krótka odpowiedź:
Zestaw:
Intuicja: Opisujesz priorytet wysokiej dokładności w danych treningowych. To opisuje nadmierne dopasowanie. Aby to zrobić, ustaw rozmiar partii na 1, wysokie epoki i przemieszaj.
źródło
Zobacz, co robisz:
źródło
To jest niedopasowane i poprawiam to, że potrzebuję dodać neurony do twoich ukrytych warstw. !! Kolejnym punktem jest próba aktywacji funkcji „relu”. Sigmoid nie daje dobrych wyników. Musisz także zdefiniować „softmax” w swojej warstwie wyjściowej.!
źródło
Po zmianie architektury modelu i optymalizatora na Adagrad mogłem w pewnym stopniu poprawić wyniki.
Powodem korzystania z optymalizatora Adagrad tutaj jest:
Dostosowuje szybkość uczenia się do parametrów, wykonując mniejsze aktualizacje (tj. Niskie wskaźniki uczenia się) dla parametrów związanych z często występującymi funkcjami oraz większe aktualizacje (tj. Wysokie wskaźniki uczenia się) dla parametrów związanych z rzadkimi funkcjami. Z tego powodu dobrze nadaje się do radzenia sobie z rzadkimi danymi.
Proszę odnieść się do kodu poniżej:
Prognozowanie zapasów jest bardzo trudnym zadaniem, więc zamiast trzymać się prognozy jednego modelu, możemy mieć kilka modeli współpracujących ze sobą, aby dokonać prognozy, a następnie w oparciu o maksymalny głosowany wynik podjąć wyzwanie, podobnie jak podejście do uczenia się zespołowego. Ponadto możemy zestawić ze sobą kilka modeli, takich jak:
Automatyczna enkoder z głębokim sprzężeniem zwrotnym w celu zmniejszenia wymiarów + Głęboka, rekurencyjna sieć neuronowa + ARIMA + Ekstremalne zwiększenie gradientu regresora
Adaboost + Bagging + Dodatkowe drzewa + Gradient Boost + Random Forest + XGB
Agenci uczący się o wzmocnieniach radzą sobie całkiem dobrze w Prognozie giełdowej:
Proszę znaleźć bardzo pomysłowy odnośnik tutaj .
źródło
Podejrzany nr 1 - Regularyzacja
Sieci neuronowe świetnie nadają się do dopasowania danych treningowych, w rzeczywistości istnieje eksperyment zastępujący etykiety CIFAR10 (zadanie klasyfikacji obrazu) (wartości y) losowymi etykietami w zbiorze danych szkoleniowych, a sieć dopasowuje losowe etykiety, co powoduje prawie zerową utratę.
Dlaczego więc nie dzieje się cały czas? regularyzacja .
regularyzacja (z grubsza) próbuje rozwiązać trudniejszy problem niż problem optymalizacji (strata), który zdefiniowaliśmy dla modelu.
niektóre typowe metody regularyzacji w sieciach neuronowych:
metody te pomagają zmniejszyć nadmierne dopasowanie i zwykle skutkują lepszą walidacją i wydajnością testu, ale skutkują niższą wydajnością pociągu (co nie ma znaczenia, jak wyjaśniono w ostatnim akapicie).
wydajność danych pociągu zwykle nie jest tak ważna i do tego używamy zestawu walidacyjnego.
Podejrzany # 2 - Rozmiar modelu
używasz pojedynczej warstwy LSTM z 32 jednostkami. to jest dość małe. spróbuj zwiększyć rozmiar, a nawet umieść dwie warstwy LSTM (lub dwukierunkową) i jestem pewien, że model i optymalizator prześcigną twoje dane tak długo, jak im na to pozwolisz - tj. usuniesz wczesne zatrzymanie, restore_last_weights i wszelkie inne regularyzacje określone powyżej.
Uwaga na temat złożoności problemu
próba przewidzenia przyszłych cen akcji na podstawie samej historii nie jest łatwym zadaniem, a nawet jeśli model (idealnie) pasuje idealnie do zestawu treningowego, prawdopodobnie nie zrobi nic użytecznego na zestawie testowym ani w prawdziwym świecie.
ML nie jest czarną magią, próbki x muszą być w jakiś sposób skorelowane z próbkami y, zwykle zakładamy, że (x, y) są pobierane z pewnego rozkładu razem.
Bardziej intuicyjny sposób myślenia o tym, gdy trzeba ręcznie oznaczyć obraz dla klasy pies / kot - to całkiem proste. ale czy możesz ręcznie „otagować” cenę akcji, patrząc tylko na historię tej akcji?
To intuicja, jak trudny jest ten problem.
Uwaga na temat przeuczenia
Nie należy ścigać się z wyższą wydajnością treningu, ponieważ prawie bezużyteczne jest próbowanie przewyższenia danych treningowych, ponieważ zwykle staramy się osiągać dobre wyniki z modelem na nowych niewidzialnych danych o właściwościach podobnych do danych pociągu. wszystko polega na próbie uogólnienia i poznania właściwości danych i korelacji z celem, czyli uczenia się :)
źródło