Criei uma rede neural LSTM (RNN) com aprendizado supervisionado para previsão de estoque de dados. O problema é por que ele prevê errado em seus próprios dados de treinamento? (nota: exemplo reproduzível abaixo)
Criei um modelo simples para prever o preço das ações nos próximos 5 dias:
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])
Os resultados corretos estão em y_test
(5 valores); portanto, o modelo treina, olhando para trás 90 dias anteriores e, em seguida, restaura os pesos do melhor ( val_loss=0.0030
) resultado com 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
O resultado da previsão é impressionante, não é?
Isso ocorre porque o algoritmo restaurou os melhores pesos da época # 5. Okey, agora vamos salvar esse modelo em .h5
arquivo, voltar 10 dias e prever os últimos 5 dias (no primeiro exemplo, fizemos o modelo e validamos nos dias 17 e 23 de abril, incluindo dias de folga nos fins de semana, agora vamos testar nos dias 2 e 8 de abril). Resultado:
Isso mostra uma direção absolutamente errada. Como vemos, isso ocorre porque o modelo foi treinado e levou a 5ª melhor época para validação, de 17 a 23 de abril, mas não de 2 a 8. Se eu tentar treinar mais, jogando com qual época escolher, o que quer que eu faça, sempre há muitos intervalos de tempo no passado que têm previsões erradas.
Por que o modelo mostra resultados incorretos em seus próprios dados treinados? Eu treinei dados, ele deve se lembrar de como prever dados nesse conjunto de conjuntos, mas prevê errado. O que eu também tentei:
- Use conjuntos de dados grandes com mais de 50 mil linhas, preços de ações de 20 anos, adicionando mais ou menos recursos
- Crie diferentes tipos de modelo, como adicionar mais camadas ocultas, diferentes tamanhos de lote, diferentes ativações de camadas, interrupções, normalização de lotes
- Crie retorno de chamada personalizado do EarlyStopping, obtenha val_loss médio de muitos conjuntos de dados de validação e escolha o melhor
Talvez eu perca alguma coisa? O que posso melhorar?
Aqui está um exemplo muito simples e reproduzível . yfinance
faz o download dos dados de ações do 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)
, ele mostrou o mesmo resultado ruim que eu.Respostas:
O OP postula uma descoberta interessante. Deixe-me simplificar a pergunta original da seguinte maneira.
Se o modelo é treinado em uma série temporal específica, por que o modelo não pode reconstruir dados de séries temporais anteriores, nos quais já foi treinado?
Bem, a resposta está embutida no próprio progresso do treinamento. Como
EarlyStopping
é usado aqui para evitar o ajuste excessivo, o melhor modelo é salvo emepoch=5
, ondeval_loss=0.0030
mencionado pelo OP. Nesse caso, a perda de treinamento é igual a0.0343
, ou seja, o RMSE do treinamento0.185
. Como o conjunto de dados é escalado usandoMinMaxScalar
, precisamos desfazer o dimensionamento do RMSE para entender o que está acontecendo.Os valores mínimo e máximo da sequência de tempo são
2290
e3380
. Portanto, ter0.185
como RMSE de treinamento significa que, mesmo para o conjunto de treinamentos, os valores previstos podem diferir dos valores de base verdade em aproximadamente0.185*(3380-2290)
, ou seja,~200
unidades em média.Isso explica por que há uma grande diferença ao prever os próprios dados de treinamento em uma etapa anterior.
O que devo fazer para emular perfeitamente os dados de treinamento?
Eu fiz essa pergunta de mim mesmo. A resposta simples é: fazer com que a perda de treinamento se aproxime
0
, isso está superando o modelo.Após algum treinamento, percebi que um modelo com apenas 1 camada LSTM que possui
32
células não é suficientemente complexo para reconstruir os dados de treinamento. Portanto, adicionei outra camada LSTM da seguinte maneira.E o modelo é treinado por
1000
épocas sem considerarEarlyStopping
.No final da
1000
época, temos uma perda de treinamento0.00047
muito menor do que a perda de treinamento no seu caso. Portanto, esperamos que o modelo reconstrua melhor os dados de treinamento. A seguir, é apresentado o gráfico de previsão de 2 a 8 de abril.Uma Nota Final:
O treinamento em um banco de dados específico não significa necessariamente que o modelo possa reconstruir perfeitamente os dados de treinamento. Especialmente, quando métodos como parada antecipada, regularização e desistência são introduzidos para evitar o super ajuste, o modelo tende a ser mais generalizável do que a memorizar os dados de treinamento.
fonte
Você deseja que o modelo aprenda a relação entre entrada e saída em vez de memorização. Se um modelo memoriza a saída correta para cada entrada, podemos dizer que está ajustando demais os dados de treinamento. Freqüentemente, você pode forçar o modelo a se ajustar demais, usando um pequeno subconjunto de dados; portanto, se esse é o comportamento que você deseja ver, tente isso.
fonte
Suspeito # 1 - Regularização
As redes neurais são ótimas em sobreajustar os dados de treinamento; na verdade, existe uma experimento substituindo os rótulos CIFAR10 (tarefa de classificação de imagens) (valores y) por rótulos aleatórios no conjunto de dados de treinamento e a rede se encaixa nos rótulos aleatórios, resultando em quase zero perda.
Então, por que não está acontecendo o tempo todo? regularização .
a regularização está (aproximadamente) tentando resolver problemas mais difíceis do que o problema de otimização (a perda) que definimos para o modelo.
alguns métodos comuns de regularização em redes neurais:
esses métodos ajudam a reduzir o excesso de ajustes e geralmente resultam em melhor validação e desempenho do teste, mas resultam em menor desempenho do trem (o que não importa, na verdade, conforme explicado no último parágrafo).
o desempenho dos dados do trem geralmente não é tão importante e, para isso, usamos o conjunto de validação.
Suspeito # 2 - Tamanho do modelo
você está usando uma única camada LSTM com 32 unidades. isso é bem pequeno. tente aumentar o tamanho e até coloque duas camadas LSTM (ou uma bidirecional) e tenho certeza de que o modelo e o otimizador superestimarão seus dados desde que você os permita - por exemplo, remova a parada antecipada, restore_last_weights e qualquer outra regularização especificada acima.
Nota sobre a complexidade do problema
tentar prever futuros preços das ações apenas olhando para o histórico não é uma tarefa fácil, e mesmo que o modelo possa (super) se encaixar perfeitamente no conjunto de treinamento, provavelmente não fará nada de útil no conjunto de testes ou no mundo real.
ML não é magia negra, as amostras x precisam ser correlacionadas de alguma forma com as tags y, geralmente assumimos que (x, y) são extraídas de alguma distribuição juntas.
Uma maneira mais intuitiva de pensar sobre isso, quando você precisa marcar uma imagem manualmente para a classe cão / gato - isso é bem simples. mas você pode "marcar" manualmente o preço das ações olhando apenas o histórico dessa ação?
Isso é alguma intuição sobre o quão difícil é esse problema.
Nota sobre sobreajuste
Não se deve perseguir um desempenho de treinamento mais alto, é quase inútil tentar superestimar os dados de treinamento, pois geralmente tentamos ter um bom desempenho com um modelo de novos dados não vistos com propriedades semelhantes aos dados do trem. a idéia é tentar generalizar e aprender as propriedades dos dados e correlação com o alvo, isso é o que é aprender :)
fonte
Como outros já disseram, você não deve esperar muito disso.
No entanto, encontrei o seguinte em seu código:
Você está recolocando o scaler sempre durante o treinamento e os testes. Você precisa salvar o sacler e transformar apenas os dados durante o teste; caso contrário, os resultados serão ligeiramente diferentes:
Set
shuffle=False
. Como você precisa manter a ordem do seu conjunto de dados.Set
batch_size=1
. Como será menos propenso a sobreajuste e o aprendizado será mais barulhento e o erro menos médio.Conjunto
epochs=50
ou mais.Com as configurações mencionadas acima, o modelo foi alcançado
loss: 0.0037 - val_loss: 3.7329e-04
.Verifique as seguintes amostras de previsão:
Entre 17/04/2020 -> 23/04/2020:
Entre 02/04/2020 -> 08/04/2020:
De 25/03/2020 -> 31/03/2020:
fonte
Basicamente, se você deseja obter melhores resultados para os dados de treinamento, sua precisão deve ser a mais alta possível. Você deve usar um modelo melhor em relação aos dados que possui. Basicamente, você deve verificar se a precisão do seu treinamento para esse fim, independentemente da precisão do teste. Isso também é chamado de ajuste excessivo, que fornece melhor precisão nos dados de treinamento, em vez dos dados de teste.
A parada antecipada pode ser afetada para este cenário em que a melhor precisão de teste / validação é obtida em vez da precisão do treinamento.
fonte
A resposta curta:
Conjunto:
Intuição: você está descrevendo a prioridade de alta precisão nos dados de treinamento. Isso está descrevendo o super ajuste. Para fazer isso, defina o tamanho do lote como 1, as épocas altas e a reprodução aleatória.
fonte
Depois de alterar a arquitetura do modelo e o otimizador para Adagrad, consegui melhorar os resultados até certo ponto.
O motivo para usar o otimizador Adagrad aqui é:
Ele adapta a taxa de aprendizado aos parâmetros, executando atualizações menores (por exemplo, baixas taxas de aprendizado) para parâmetros associados a recursos que ocorrem com freqüência e atualizações maiores (por exemplo, altas taxas de aprendizado) para parâmetros associados a recursos não frequentes. Por esse motivo, é adequado para lidar com dados esparsos.
Por favor, consulte o código abaixo:
A previsão de ações é uma tarefa muito desafiadora, e, em vez de seguir a previsão de um único modelo, podemos ter vários modelos trabalhando juntos para fazer uma previsão e, com base no resultado máximo votado, atender à chamada, semelhante a uma abordagem de aprendizado por conjunto. Além disso, podemos empilhar alguns modelos juntos, como:
Rede Neural de codificador automático com avanço profundo para reduzir as dimensões + Rede neural recorrente profunda + ARIMA + Regressor de gradiente de impulso extremo
Adaboost + Ensacamento + Árvores Extra + Impulso de Gradiente + Floresta Aleatória + XGB
Os agentes de aprendizado por reforço estão se saindo muito bem na Previsão de ações, como:
Por favor, encontre um link com muitos recursos aqui .
fonte
Veja o que você está fazendo:
fonte
É pouco adequado e, para melhorar, acho que você precisa adicionar neurônios em suas camadas ocultas. !! Outro ponto é tentar a função de ativação 'relu'. Sigmoid não dá bons resultados. Você também precisa definir 'softmax' na sua camada de saída.
fonte