Por que a rede neural prevê errado em seus próprios dados de treinamento?

17

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 é?

insira a descrição da imagem aqui

Isso ocorre porque o algoritmo restaurou os melhores pesos da época # 5. Okey, agora vamos salvar esse modelo em .h5arquivo, 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:

insira a descrição da imagem aqui

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 . yfinancefaz 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()
sirjay
fonte
3
Os exemplos reproduzíveis são tão raros hoje em dia (em contraste com um monte de perguntas semelhantes sem) que é sem dúvida uma boa idéia anunciar sua existência no início de seu post (adicionado);)
desertnaut
7
O problema pode ser que você espera muita previsibilidade do mercado de ações. Se você treinou um modelo em uma sequência de 1 milhão de lançamentos de moedas e depois tentou prever o lançamento de moedas, não seria surpreendente que o modelo entendesse errado, mesmo que os lançamentos viessem dos dados de treinamento - o modelo não é esperado que memorize seus dados de treinamento e os regurgite.
user2357112 suporta Monica 27/04
2
Além do que o @ user2357112supportsMonica disse, seu modelo acertou na média, o que é realmente tudo o que eu esperaria que um modelo como esse realmente obtivesse (pelo menos com alguma consistência), e você está esperando muito em 5 dias dados. Você realmente precisa de muito mais dados para poder dizer com algum significado qual é o erro no seu modelo.
Aaron
Há muito mais parâmetros para ajustar o modelo. Eu tentei alguns deles como parada precoce (paciência = 20), aumento do número de épocas, aumento das unidades de LSTM de 32 para 64 etc. Os resultados foram muito melhores. verifique aqui github.com/jvishnuvardhan/Stackoverflow_Questions/blob/master/… . Conforme mencionado pelo @sirjay, adicionando mais recursos (atualmente apenas 4), adicionando mais camadas (lstm, batchnorm, dropout etc.), a execução da otimização de hiperparâmetros resultaria em um desempenho muito melhor.
Vishnuvardhan Janapati 28/04
@VishnuvardhanJanapati obrigado por verificar. Compilei seu código, salvei o modelo e depois o configurei df.drop(df.tail(10).index, inplace=True), ele mostrou o mesmo resultado ruim que eu.
sirjay 28/04

Respostas:

5

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 em epoch=5, onde val_loss=0.0030mencionado pelo OP. Nesse caso, a perda de treinamento é igual a 0.0343, ou seja, o RMSE do treinamento 0.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 2290e 3380. Portanto, ter 0.185como RMSE de treinamento significa que, mesmo para o conjunto de treinamentos, os valores previstos podem diferir dos valores de base verdade em aproximadamente 0.185*(3380-2290), ou seja, ~200unidades 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 32células não é suficientemente complexo para reconstruir os dados de treinamento. Portanto, adicionei outra camada LSTM da seguinte maneira.

model = Sequential()
model.add(LSTM(32, return_sequences=True, activation = 'sigmoid', input_shape=(x_train.shape[1], x_train.shape[2])))
# model.add(Dropout(0.2))
# model.add(BatchNormalization())
model.add(LSTM(units = 64, return_sequences=False,))
model.add(Dense(y_train.shape[1]))
model.compile(optimizer = 'adam', loss = 'mse')

E o modelo é treinado por 1000épocas sem considerar EarlyStopping.

model.fit(x_train, y_train, batch_size = 64, epochs = 1000, shuffle = True, validation_data = (x_test, y_test))

No final da 1000época, temos uma perda de treinamento 0.00047muito 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.

predição

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.

Achintha Ihalage
fonte
2

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.

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.

tensordude
fonte
2

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.

insira a descrição da imagem aqui

no lado esquerdo, podemos ver que, dadas as épocas suficientes, os rótulos aleatórios atingem cerca de 0 perdas - pontuação perfeita (para entender o aprendizado profundo, é necessário repensar a generalização de zhang et al 2016 )

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:

  • parada antecipada
  • cair fora
  • normalização de lote
  • queda de peso (por exemplo, normas l1 l2)
  • aumento de dados
  • adicionando ruído aleatório / gaussiano

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

ShmulikA
fonte
2

Como outros já disseram, você não deve esperar muito disso.

No entanto, encontrei o seguinte em seu código:

  1. 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:

    from sklearn.externals import joblib
    scaler_filename = "scaler.save"
    if new_s_h5:
        scaler = MinMaxScaler()
        df_normalized = scaler.fit_transform(df.values)
        joblib.dump(scaler, scaler_filename)
    
    else:
        scaler = joblib.load(scaler_filename)
        df_normalized = scaler.transform(df.values)
  2. Set shuffle=False. Como você precisa manter a ordem do seu conjunto de dados.

  3. Set batch_size=1. Como será menos propenso a sobreajuste e o aprendizado será mais barulhento e o erro menos médio.

  4. Conjunto epochs=50ou 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:

insira a descrição da imagem aqui

Entre 02/04/2020 -> 08/04/2020:

insira a descrição da imagem aqui

De 25/03/2020 -> 31/03/2020:

insira a descrição da imagem aqui

Yahya
fonte
1

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.

Demotte
fonte
1

A resposta curta:

Conjunto:

batch_size = 1
epochs = 200
shuffle = False

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.

Daniel Scott
fonte
1

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:

model = Sequential()
model.add(LSTM(units=100,return_sequences=True, kernel_initializer='random_uniform', input_shape=(x_train.shape[1], x_train.shape[2])))
model.add(Dropout(0.2))
model.add(LSTM(units=100,return_sequences=True, kernel_initializer='random_uniform'))
model.add(LSTM(units=100,return_sequences=True, kernel_initializer='random_uniform'))
model.add(Dropout(0.20))
model.add(Dense(units=25, activation='relu'))
model.add(Dense(y_train.shape[1]))

# compile model
model.compile(loss="mse", optimizer='adagrad', metrics=['accuracy'])
model.summary()

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:

  1. 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

  2. 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:

  1. Agente de comércio de tartarugas
  2. Agente de média móvel
  3. Agente de rolamento de sinal
  4. Agente de gradiente de política
  5. Agente de Q-learning
  6. Agente de estratégia de evolução

Por favor, encontre um link com muitos recursos aqui .

Rishab P.
fonte
adam também tem essas propriedades, na verdade adam é algum tipo de evolução do adagrad
ShmulikA
0

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.

Veja o que você está fazendo:

  1. Construindo um modelo com algumas camadas
  2. Modelo de treinamento com o training_data
  3. Quando você treinou o modelo, todos os parâmetros treináveis ​​são treinados (ou seja, os pesos do modelo foram salvos)
  4. Esses pesos agora representam a relação entre entrada e saída.
  5. Quando você prediz os mesmos dados de treinamento novamente, esse modelo treinado dessa vez usa pesos para obter a saída.
  6. A qualidade do seu modelo agora decide as previsões e, portanto, elas são diferentes dos resultados originais, mesmo que os dados sejam os mesmos.
Piyush Gupta
fonte
0

É 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.

Rahul Anand
fonte
Parece que você guarda os segredos para prever o mercado. O que mais ele deveria fazer?
Daniel Scott
2
softmax é para classificação, é um problema de regressão.
ShmulikA
@DanielScott você não entende. No fundo (bilhões de camadas abaixo), existe um problema de classificação que decide entre lucros ou perdas. Por que se importar em prever uma série temporal?
Sowmya