Почему нейронная сеть предсказывает неверные данные о своих тренировках? - PullRequest
20 голосов
/ 25 апреля 2020

Я создал нейронную сеть LSTM (RNN) с контролируемым обучением для прогнозирования запасов данных. Проблема в том, почему он предсказывает неверные данные о тренировках? (примечание: воспроизводимый пример ниже)

Я создал простую модель для прогнозирования цены акций на следующие 5 дней:

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

Правильные результаты в y_test (5 значения), поэтому модель тренируется, оглядываясь назад на 90 предыдущих дней, а затем восстанавливая веса с наилучшего (val_loss=0.0030) результата с 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

Результат прогнозирования довольно удивительный, не так ли?

enter image description here

Это потому, что алгоритм восстановил лучшие веса с # 5 эпохи. Ладно, давайте теперь сохраним эту модель в файле .h5, вернемся на -10 дней и прогнозируем последние 5 дней (в первом примере мы сделали модель и проверим ее 17-23 апреля, включая выходные дни, теперь давайте проверим 2-8 апреля ). Результат:

enter image description here

Показывает абсолютно неверное направление. Как мы видим, это потому, что модель прошла обучение и заняла 5-е время лучше всего для проверки, установленной 17-23 апреля, а не 2-8. Если я стараюсь больше тренироваться, играя с какой эпохой выбрать, что бы я ни делал, в прошлом всегда будет много временных интервалов, которые имеют неправильный прогноз.

Почему модель показывает неверные результаты на своих собственных обученных данных ? Я тренировал данные, он должен помнить, как прогнозировать данные на этом фрагменте, но предсказывает неправильно. То, что я также попробовал:

  • Использование больших наборов данных с 50k + строками, 20-летними ценами на акции, добавлением более или менее функций
  • Создание различных типов моделей, таких как добавление дополнительных скрытых слоев, разные batch_sizes, разные уровни активации, выпадения, батчнормализация
  • Создание собственного обратного вызова EarlyStopping, получение среднего val_loss из многих наборов данных проверки и выбор лучшего

Может быть, я что-то пропустил? Что я могу улучшить?

Вот очень простой и воспроизводимый пример. yfinance скачивает данные о запасах 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()

Ответы [ 9 ]

7 голосов
/ 03 мая 2020

ОП постулирует интересную находку. Позвольте мне упростить исходный вопрос следующим образом.

Если модель обучается по определенному временному ряду, почему модель не может восстановить данные предыдущих временных рядов, на которых она уже была обучена?

Хорошо, Ответ заложен в самом процессе обучения. Поскольку здесь используется EarlyStopping, чтобы избежать переоснащения, лучшая модель сохраняется в epoch=5, где val_loss=0.0030, как указано в OP. В этом случае потеря обучения равна 0.0343, то есть RMSE обучения составляет 0.185. Поскольку набор данных масштабируется с использованием MinMaxScalar, нам необходимо отменить масштабирование RMSE, чтобы понять, что происходит.

Минимальные и максимальные значения временной последовательности равны 2290 и 3380 , Следовательно, наличие 0.185 в качестве СКО обучения означает, что даже для обучающего набора прогнозируемые значения могут отличаться от базовых значений истинности приблизительно на 0.185*(3380-2290), то есть в среднем ~200 единиц.

Это объясняет, почему существует большая разница при прогнозировании самих данных обучения на предыдущем шаге по времени.

Что я должен сделать, чтобы идеально имитировать данные тренировки?

I задал этот вопрос от себя. Ответ прост: доведите потерю обучения до 0, что соответствует модели.

После некоторого обучения я понял, что модель с одним слоем LSTM, имеющим ячейки 32, не является достаточно сложной. восстановить данные обучения. Поэтому я добавил еще один слой LSTM следующим образом.

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

И модель обучена для 1000 эпох без учета EarlyStopping.

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

В конце 1000 ая эпоха у нас потеря тренировок 0.00047, что намного ниже, чем потери тренировок в вашем случае. Таким образом, мы ожидаем, что модель лучше восстановит данные обучения. Ниже приводится график прогноза на 2-8 апреля.

prediction

Заключительное примечание:

Обучение конкретная база данных не обязательно означает, что модель должна быть в состоянии идеально реконструировать данные обучения. Особенно, когда вводятся такие методы, как ранняя остановка, регуляризация и отсев, чтобы избежать переобучения, модель имеет тенденцию быть более обобщенной, нежели запоминание данных тренировки.

3 голосов
/ 03 мая 2020

Подозреваемый # 1 - Регуляризация

Нейронные сети отлично подходят для переобучения обучающих данных, на самом деле есть эксперимент , заменяющий метки CIFAR10 (задача классификации изображений) (значения y) случайными метками на обучающем наборе данных и в сети помещаются случайные метки, приводящие к почти нулевым потерям.

enter image description here

на левой стороне мы видим, что дано случайных меток на многие эпохи получают около 0 потерь - отличный результат (из понимание глубокого обучения требует переосмысления обобщений от zhang et al 2016 )

Так почему это не происходит постоянно ? регуляризация .

регуляризация (примерно) пытается решить более сложную задачу, чем задача оптимизации (потери), которую мы определили для модели.

некоторые распространенные методы регуляризации в нейронных сетях:

  • ранняя остановка
  • выпадение
  • пакетная нормализация
  • снижение веса (например, l1 l2 норм)
  • увеличение данных
  • добавление случайного / гауссовского шума

эти методы помогают уменьшить переоснащение и обычно приводят к лучшей проверке и производительности тестирования, но приводят к снижению производительности поезда (что не на самом деле имеет значение, как объяснено в последнем абзаце).

производительность данных поезда, как правило, не так важна, и для этого мы используем набор проверки.

Подозреваемый # 2 - Размер модели

вы используете один слой LSTM с 32 единицами. это довольно мало. попробуйте увеличить размер и даже поместить два слоя LSTM (или двунаправленный), и я уверен, что модель и оптимизатор будут соответствовать вашим данным до тех пор, пока вы позволите им - т.е. удалите раннюю остановку, restore_last_weights и любую другую регуляризацию, указанную выше.

Замечание о сложности задачи

Попытка предсказать будущие цены на акции, просто взглянув на историю, - задача не из легких, и даже если модель может (пере) соответствовать идеально обучающему набору, она будет вероятно, не будет ничего полезного на тестовом наборе или в реальном мире.

ML - это не черная магия c, образцы x должны каким-то образом соотноситься с тегами y, мы обычно предполагаем, что (x , y) взяты из некоторого дистрибутива вместе.

Более интуитивный способ думать об этом, когда вам нужно пометить изображение вручную для класса собака / кошка - это довольно просто. но можете ли вы вручную «пометить» цену акций, взглянув на историю этих акций в одиночку?

Это дает некоторую интуицию о том, насколько серьезна эта проблема.

Примечание по перегонке

Не следует гоняться за более высокими показателями тренировок почти бесполезно пытаться переписать данные тренировок, так как мы обычно стараемся хорошо работать с моделью на новых невидимых данных, схожих с данными поезда. вся идея состоит в том, чтобы попытаться обобщить и изучить свойства данных и корреляцию с целью, вот что такое обучение:)

2 голосов
/ 04 мая 2020

Как уже говорили другие, от этого многого ожидать не стоит.

Тем не менее, я нашел в вашем коде следующее:

  1. Вы перемонтируете скейлер каждый раз во время обучение и тестирование. Вам нужно сохранить сэмплер и преобразовывать данные только во время тестирования, иначе результаты будут немного отличаться:

    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. Так как вам нужно сохранить порядок вашего набора данных.

  3. Set batch_size=1. Так как это будет менее склонно к переобучению, и обучение будет более шумным, а ошибка будет менее усредненной.

  4. Set epochs=50 или более.


При вышеупомянутых настройках модель достигла loss: 0.0037 - val_loss: 3.7329e-04.

Проверьте следующие образцы прогноза:

С 17/04/2020 -> 23/04/2020:

enter image description here

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

enter image description here

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

enter image description here

2 голосов
/ 03 мая 2020

После изменения архитектуры модели и оптимизатора на Адаград, я смог улучшить результаты в некоторой степени.

Причина использования оптимизатора Adagrad здесь:

Он адаптирует скорость обучения к параметрам, выполняя меньшие обновления (т.е. низкие скорости обучения) для параметров, связанных с часто встречающимися функции и большие обновления (например, высокая скорость обучения) для параметров, связанных с нечастыми функциями. По этой причине он хорошо подходит для работы с разреженными данными.

Пожалуйста, обратитесь к коду ниже:

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

Прогнозирование запасов является очень сложной задачей поэтому вместо того, чтобы придерживаться прогноза одной модели, у нас может быть несколько моделей, работающих вместе, чтобы сделать прогноз, а затем, основываясь на максимальном результате голосования, принять вызов, аналогично ансамблевому подходу к обучению. Кроме того, мы можем объединить несколько моделей, таких как:

  1. Глубокая прямая нейронная сеть с автоматическим кодированием для уменьшения размеров + Глубокая рекуррентная нейронная сеть + ARIMA + Экстремальный регрессор градиента ускорения

  2. Adaboost + Bagging + Дополнительные деревья + Повышение градиента + Случайный лес + XGB

Агенты по обучению подкреплению довольно хорошо справляются с прогнозом запаса, например:

  1. Торговый агент Turtle
  2. Агент скользящей средней
  3. Агент скользящего сигнала
  4. Агент-градиент политики
  5. Агент Q-обучения
  6. Агент Evolution-стратегии

Пожалуйста, найдите очень изобретательную ссылку здесь .

2 голосов
/ 30 апреля 2020

Почему модель показывает неверные результаты на своих собственных обученных данных? Я обучил данные, он должен помнить, как прогнозировать данные на этом фрагменте, но предсказывает неправильно.

Вы хотите, чтобы модель изучала взаимосвязь между вводом и выводом вместо запоминания. Если модель запоминает правильные выходные данные для каждого входа, мы можем сказать, что это перебор данных тренировки. Зачастую вы можете принудительно изменить модель, используя небольшое подмножество данных, поэтому, если вы хотите увидеть такое поведение, вы можете попробовать это.

1 голос
/ 02 мая 2020

Краткий ответ:

Набор:

batch_size = 1
epochs = 200
shuffle = False

Интуиция: Вы описываете приоритет высокой точности в данных обучения. Это описывает переоснащение. Для этого установите размер партии равным 1, максимальные эпохи и перемешайте.

1 голос
/ 02 мая 2020

В основном, если вы хотите получить лучший результат для тренировочных данных, ваша точность обучения должна быть максимально высокой. Вы должны использовать лучшую модель в отношении данных, которые у вас есть. В основном вы должны проверить, соответствует ли ваша точность обучения для этой цели независимо от точности теста. Это также называется переоснащением, которое обеспечивает более высокую точность данных обучения, а не данных испытаний.

Ранняя остановка может повлиять на этот сценарий, когда принимается лучшая точность тестирования / проверки, а не точность обучения.

0 голосов
/ 03 мая 2020

Это не совсем подходит, и чтобы улучшить это, я думаю, вам нужно добавить нейроны в ваши скрытые слои. !! Еще один момент - попробуйте активировать функцию «relu». Сигмоид не дает хороших результатов. Также вам нужно определить «softmax» в вашем выходном слое.!

0 голосов
/ 02 мая 2020

Почему модель показывает неверные результаты на своих собственных обученных данных? Я тренировал данные, он должен помнить, как прогнозировать данные на этом фрагменте, но предсказывает неправильно.

Посмотрите, что вы делаете:

  1. Построение модели с некоторыми Слои
  2. Модель обучения с training_data
  3. Когда вы обучали модель, все обучаемые параметры обучаются (т. е. веса модели сохраняются)
  4. Эти веса теперь представляют отношения между вводом и выводом.
  5. Когда вы снова прогнозируете те же данные training_data, на этот раз обученная модель использует веса для получения выходных данных.
  6. Качество вашей модели теперь определяет прогнозы и, следовательно, они различаются Исходные результаты, хотя данные одинаковы.
...