Hice una red neuronal LSTM (RNN) con aprendizaje supervisado para la predicción del stock de datos. El problema es ¿por qué predice mal en sus propios datos de entrenamiento? (nota: ejemplo reproducible a continuación)
Creé un modelo simple para predecir el precio de las acciones en los próximos 5 días:
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])
Los resultados correctos están en y_test
(5 valores), por lo que los trenes modelo, mirando hacia atrás 90 días anteriores y luego restaurando los pesos del mejor val_loss=0.0030
resultado ( ) con 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
El resultado de la predicción es bastante impresionante, ¿no?
Esto se debe a que el algoritmo restauró los mejores pesos de la época # 5. Okey, ahora guardemos este modelo en el .h5
archivo, retrocedamos -10 días y pronostiquemos los últimos 5 días (en el primer ejemplo, hicimos el modelo y lo validamos del 17 al 23 de abril, incluidos los fines de semana libres, ahora vamos a probar el 2 al 8 de abril). Resultado:
Muestra una dirección absolutamente equivocada. Como vemos, eso se debe a que el modelo fue entrenado y tomó la mejor época para la validación establecida el 17-23 de abril, pero no el 2-8. Si trato de entrenar más, jugando con qué época elegir, haga lo que haga, siempre hay muchos intervalos de tiempo en el pasado que tienen una predicción incorrecta.
¿Por qué el modelo muestra resultados incorrectos en sus propios datos entrenados? Entrené datos, debe recordar cómo predecir datos en este conjunto, pero predice mal. Lo que también probé:
- Utilice grandes conjuntos de datos con más de 50k filas, precios de acciones de 20 años, agregando más o menos características
- Cree diferentes tipos de modelos, como agregar más capas ocultas, diferentes tamaños de lote, diferentes activaciones de capas, abandonos, normalización de baterías
- Cree una devolución de llamada personalizada de EarlyStopping, obtenga val_loss promedio de muchos conjuntos de datos de validación y elija el mejor
Tal vez extraño algo? ¿Qué puedo mejorar?
Aquí hay un ejemplo muy simple y reproducible . yfinance
descarga datos de stock de 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)
, mostró el mismo mal resultado que tenía.Respuestas:
El OP postula un hallazgo interesante. Permítanme simplificar la pregunta original de la siguiente manera.
Si el modelo está entrenado en una serie de tiempo particular, ¿por qué no puede reconstruir los datos de series de tiempo anteriores, en los que ya estaba entrenado?
Bueno, la respuesta está incrustada en el progreso del entrenamiento en sí. Dado que
EarlyStopping
se usa aquí para evitar el sobreajuste, el mejor modelo se guarda enepoch=5
,val_loss=0.0030
según lo mencionado por el OP. En este caso, la pérdida de entrenamiento es igual0.0343
, es decir, el RMSE del entrenamiento es0.185
. Dado que el conjunto de datos se escala utilizandoMinMaxScalar
, necesitamos deshacer la escala de RMSE para comprender lo que está sucediendo.Los valores mínimos y máximos de la secuencia de tiempo son
2290
y3380
. Por lo tanto, tener0.185
como RMSE de entrenamiento significa que, incluso para el conjunto de entrenamiento, los valores pronosticados pueden diferir de los valores de verdad del terreno en aproximadamente0.185*(3380-2290)
, es decir,~200
unidades en promedio.Esto explica por qué hay una gran diferencia al predecir los datos de entrenamiento en un paso de tiempo anterior.
¿Qué debo hacer para emular perfectamente los datos de entrenamiento?
Me hice esta pregunta a mí mismo. La respuesta simple es, hacer
0
que se acerque la pérdida de entrenamiento , eso es sobreajustar el modelo.Después de un entrenamiento, me di cuenta de que un modelo con solo 1 capa LSTM que tiene
32
celdas no es lo suficientemente complejo como para reconstruir los datos del entrenamiento. Por lo tanto, he agregado otra capa LSTM de la siguiente manera.Y el modelo está entrenado para
1000
épocas sin tener en cuentaEarlyStopping
.Al final de
1000
la época, tenemos una pérdida de entrenamiento0.00047
que es mucho menor que la pérdida de entrenamiento en su caso. Por lo tanto, esperaríamos que el modelo reconstruya mejor los datos de entrenamiento. A continuación se muestra el diagrama de predicción para el 2 al 8 de abril.Una nota final:
La capacitación en una base de datos en particular no necesariamente significa que el modelo debería ser capaz de reconstruir perfectamente los datos de capacitación. Especialmente, cuando se introducen métodos como la detención temprana, la regularización y el abandono para evitar el sobreajuste, el modelo tiende a ser más generalizable en lugar de memorizar datos de entrenamiento.
fuente
batch_size=1
es muy lento. Y sí,shuffle=False
habría tomado un tiempo más corto para sobreajustar porque los datos son secuenciales.Desea que el modelo aprenda la relación entre entrada y salida en lugar de memorización. Si un modelo memoriza la salida correcta para cada entrada, podemos decir que está sobre ajustando los datos de entrenamiento. A menudo, puede forzar que el modelo se sobreajuste utilizando un pequeño subconjunto de datos, por lo que si ese es el comportamiento que desea ver, puede intentarlo.
fuente
Sospechoso # 1 - Regularización
Las redes neuronales son excelentes para sobreajustar los datos de entrenamiento, en realidad hay un experimento que reemplaza las etiquetas CIFAR10 (tarea de clasificación de imágenes) (valores y) por etiquetas aleatorias en el conjunto de datos de entrenamiento y la red se ajusta a las etiquetas aleatorias, lo que resulta en una pérdida casi nula.
Entonces, ¿por qué no sucede todo el tiempo? regularización .
La regularización está (aproximadamente) tratando de resolver un problema más difícil que el problema de optimización (la pérdida) que definimos para el modelo.
Algunos métodos comunes de regularización en redes neuronales:
Estos métodos ayudan a reducir el sobreajuste y, por lo general, dan como resultado una mejor validación y rendimiento de la prueba, pero dan como resultado un menor rendimiento del tren (lo que en realidad no importa como se explica en el último párrafo).
El rendimiento de los datos del tren no suele ser tan importante y para eso utilizamos el conjunto de validación.
Sospechoso # 2 - Tamaño del modelo
Está utilizando una sola capa LSTM con 32 unidades. eso es bastante pequeño. intente aumentar el tamaño e incluso coloque dos capas LSTM (o una bidireccional) y estoy seguro de que el modelo y el optimizador sobreajustarán sus datos siempre que los permita, es decir, elimine la detención temprana, restablecer_pesos_pesos y cualquier otra regularización especificada anteriormente.
Nota sobre la complejidad del problema
tratar de predecir los futuros precios de las acciones simplemente mirando el historial no es una tarea fácil, e incluso si el modelo puede (ajustarse) perfectamente al conjunto de entrenamiento, probablemente no hará nada útil en el conjunto de prueba o en el mundo real.
ML no es magia negra, las muestras x deben correlacionarse de alguna manera con las etiquetas y, por lo general, suponemos que (x, y) se extraen juntas de alguna distribución.
Una forma más intuitiva de pensarlo, cuando necesita etiquetar una imagen manualmente para la clase de perro / gato, eso es bastante sencillo. pero ¿puedes "etiquetar" manualmente el precio de la acción mirando solo el historial de esa acción?
Eso es algo de intuición sobre lo difícil que es este problema.
Nota sobre sobreajuste
Uno no debería perseguir un rendimiento de entrenamiento más alto, es casi inútil tratar de sobreajustar los datos de entrenamiento, ya que generalmente tratamos de desempeñarnos bien con un modelo en datos nuevos no vistos con propiedades similares a los datos del entrenamiento. la idea es tratar de generalizar y aprender las propiedades de los datos y la correlación con el objetivo, eso es el aprendizaje :)
fuente
Básicamente, si desea obtener mejores resultados para los datos de entrenamiento, la precisión de su entrenamiento debe ser lo más alta posible. Debería usar un mejor modelo con respecto a los datos que tiene. Básicamente, debe verificar si la precisión de su entrenamiento para este propósito, independientemente de la precisión de la prueba. Esto también se conoce como sobreajuste, que proporciona una mayor precisión en los datos de entrenamiento en lugar de los datos de prueba.
La detención temprana podría afectar a este escenario en el que se toma la mejor precisión de prueba / validación en lugar de la precisión del entrenamiento.
fuente
La respuesta corta:
Conjunto:
Intuición: está describiendo la prioridad de alta precisión en los datos de entrenamiento. Esto describe el sobreajuste. Para hacer eso, establezca el tamaño del lote en 1, las épocas altas y alejándose.
fuente
Después de cambiar la arquitectura del modelo y el optimizador a Adagrad, pude mejorar los resultados hasta cierto punto.
La razón para usar el optimizador Adagrad aquí es:
Adapta la tasa de aprendizaje a los parámetros, realizando actualizaciones más pequeñas (es decir, tasas de aprendizaje bajas) para los parámetros asociados con las funciones que ocurren con frecuencia, y actualizaciones más grandes (es decir, tasas de aprendizaje altas) para los parámetros asociados con características poco frecuentes. Por esta razón, es muy adecuado para tratar con datos escasos.
Por favor, consulte el siguiente código :
La predicción de existencias es una tarea muy desafiante, por lo que, en lugar de apegarnos a la predicción de un solo modelo, podemos hacer que varios modelos trabajen juntos para hacer una predicción y luego, en función del resultado máximo votado, tome la decisión, similar a un enfoque de aprendizaje conjunto. Además, podemos apilar algunos modelos juntos como:
Red neuronal de codificador automático de alimentación profunda para reducir la dimensión + Red neuronal recurrente profunda + ARIMA + Regresor de gradiente de refuerzo extremo
Adaboost + Ensacado + Árboles adicionales + Aumento de gradiente + Bosque aleatorio + XGB
A los agentes de aprendizaje de refuerzo les está yendo bastante bien en la predicción de acciones como:
Encuentre un enlace muy ingenioso aquí .
fuente
Como ya han dicho otros, no debe esperar mucho de esto.
Sin embargo, encontré lo siguiente en su código:
Está volviendo a ajustar el escalador cada vez durante el entrenamiento y las pruebas. Debe guardar el sacler y solo transformar los datos durante las pruebas; de lo contrario, los resultados serán ligeramente diferentes:
Conjunto
shuffle=False
. Como debe mantener el orden de su conjunto de datos.Conjunto
batch_size=1
. Como será menos propenso al sobreajuste y el aprendizaje será más ruidoso y el error será menos promediado.Conjunto
epochs=50
o más.Con la configuración mencionada anteriormente, el modelo logrado
loss: 0.0037 - val_loss: 3.7329e-04
.Verifique las siguientes muestras de predicción:
Del 17/04/2020 -> 23/04/2020:
Del 02/04/2020 -> 08/04/2020:
Del 25/03/2020 -> 31/03/2020:
fuente
¡Es poco adecuado y para mejorar eso, creo que necesitas agregar neuronas a tus capas ocultas! Otro punto es probar la función de activación 'relu'. Sigmoid no da buenos resultados. También debe definir 'softmax' en su capa de salida.
fuente
Mira que haces:
fuente