¿Por qué la red neuronal predice mal en sus propios datos de entrenamiento?


17

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.0030resultado ( ) 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?

ingrese la descripción de la imagen aquí

Esto se debe a que el algoritmo restauró los mejores pesos de la época # 5. Okey, ahora guardemos este modelo en el .h5archivo, 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:

ingrese la descripción de la imagen aquí

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

3
Los ejemplos reproducibles son tan raros hoy en día (en contraste con miles de preguntas similares sin) que podría decirse que es una buena idea anunciar su existencia al comienzo de su publicación (agregado);)
desertnaut

77
El problema podría ser que esperas demasiada previsibilidad del mercado de valores. Si entrenó un modelo en una secuencia de 1 millón de lanzamientos de monedas y luego trató de predecir el lanzamiento de monedas, no sería sorprendente que el modelo se equivocara, incluso si los lanzamientos provienen de los datos de entrenamiento: el modelo No se espera que memorice sus datos de entrenamiento y regurgite.
user2357112 es compatible con Monica el

2
Además de lo que @ user2357112supportsMonica dijo, su modelo obtuvo la media correcta, que es realmente todo lo que esperaría que obtuviera un modelo como este (al menos con cualquier consistencia), y espera demasiado de 5 días de datos. Realmente necesita muchos más datos para poder decir con importancia cuál es el error en su modelo.
Aaron

Hay muchos más parámetros para ajustar el modelo. Intenté algunos de ellos, como parar temprano (paciencia = 20), aumentar el número de épocas, aumentar las unidades de lstm de 32 a 64, etc. Los resultados fueron mucho mejores. consulte aquí github.com/jvishnuvardhan/Stackoverflow_Questions/blob/master/… . Como mencionó @sirjay, agregar más funciones (actualmente solo 4), agregar más capas (lstm, batchnorm, dropout, etc.), ejecutar la optimización de hiperparámetros daría como resultado un rendimiento mucho mejor.
Vishnuvardhan Janapati

@VishnuvardhanJanapati gracias por revisar. Compilé su código, guardé el modelo, luego lo configuré df.drop(df.tail(10).index, inplace=True), mostró el mismo mal resultado que tenía.
sirjay

Respuestas:


5

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 EarlyStoppingse usa aquí para evitar el sobreajuste, el mejor modelo se guarda en epoch=5, val_loss=0.0030según lo mencionado por el OP. En este caso, la pérdida de entrenamiento es igual 0.0343, es decir, el RMSE del entrenamiento es 0.185. Dado que el conjunto de datos se escala utilizando MinMaxScalar, 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 2290y 3380. Por lo tanto, tener 0.185como RMSE de entrenamiento significa que, incluso para el conjunto de entrenamiento, los valores pronosticados pueden diferir de los valores de verdad del terreno en aproximadamente 0.185*(3380-2290), es decir, ~200unidades 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 0que 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 32celdas no es lo suficientemente complejo como para reconstruir los datos del entrenamiento. Por lo tanto, he agregado otra capa LSTM de la siguiente manera.

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

Y el modelo está entrenado para 1000épocas sin tener en cuenta EarlyStopping.

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

Al final de 1000la época, tenemos una pérdida de entrenamiento 0.00047que 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.

predicción

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.


Soy curioso. ¿Por qué recomendó un tamaño de lote de 64 para el sobreajuste (en comparación con 1)? ¿Y por qué barajar = Verdadero? ¿No tomaría más tiempo converger a la solución?
Daniel Scott hace

Creo que el modelo se vuelve más propenso al sobreajuste a medida que aumenta el tamaño del lote. Por favor, eche un vistazo aquí y aquí . Además, entrenar batch_size=1es muy lento. Y sí, shuffle=Falsehabría tomado un tiempo más corto para sobreajustar porque los datos son secuenciales.
Achintha Ihalage

2

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

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.


2

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.

ingrese la descripción de la imagen aquí

en el lado izquierdo podemos ver que, dado el número suficiente de etiquetas aleatorias de épocas, se obtiene una pérdida de alrededor de 0: puntaje perfecto ( comprender el aprendizaje profundo requiere repensar la generalización por zhang et al 2016 )

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:

  • parada temprana
  • abandonar
  • normalización de lotes
  • Pérdida de peso (por ejemplo, normas l1 l2)
  • aumento de datos
  • Agregar ruido aleatorio / gaussiano

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


1

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.


1

La respuesta corta:

Conjunto:

batch_size = 1
epochs = 200
shuffle = False

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.


1

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 :

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

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:

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

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

  1. Agente de comercio de tortugas
  2. Agente de media móvil
  3. Agente rodante de señal
  4. Agente de gradiente de políticas
  5. Agente de Q-learning
  6. Agente de estrategia de evolución

Encuentre un enlace muy ingenioso aquí .


Adam también tiene estas propiedades, en realidad Adam es una especie de evolución de Adagrad
ShmulikA

1

Como ya han dicho otros, no debe esperar mucho de esto.

Sin embargo, encontré lo siguiente en su código:

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

    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. Conjunto shuffle=False. Como debe mantener el orden de su conjunto de datos.

  3. Conjunto batch_size=1. Como será menos propenso al sobreajuste y el aprendizaje será más ruidoso y el error será menos promediado.

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

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí

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

ingrese la descripción de la imagen aquí


0

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


Parece que tienes los secretos para predecir el mercado. ¿Qué más debería hacer?
Daniel Scott

2
softmax es para clasificación, es un problema de regresión.
ShmulikA

2
@DanielScott no lo entiendes. En lo más profundo (miles de millones de capas debajo) es un problema de clasificación que decide entre ganancias o pérdidas. ¿Por qué preocuparse por predecir una serie temporal?
Sowmya hace

@Sowmya Me gusta tu humor. ;)
Daniel Scott hace

-1

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

Mira que haces:

  1. Construyendo un modelo con algunas capas
  2. Modelo de entrenamiento con los datos de entrenamiento
  3. Cuando entrenó al modelo, se entrenaron todos los parámetros entrenables (es decir, se guardaron los pesos del modelo)
  4. Estos pesos ahora representan la relación entre entrada y salida.
  5. Cuando predice los mismos datos de entrenamiento nuevamente, este modelo entrenado en tiempo utiliza pesos para obtener la salida.
  6. La calidad de su modelo ahora decide las predicciones y, por lo tanto, son diferentes de los resultados originales a pesar de que los datos son los mismos.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.