为啥神经网络在自己的训练数据上预测错误?

Posted

技术标签:

【中文标题】为啥神经网络在自己的训练数据上预测错误?【英文标题】:Why neural network predicts wrong on its own training data?为什么神经网络在自己的训练数据上预测错误? 【发布时间】:2020-08-09 00:36:57 【问题描述】:

我制作了一个带有监督学习的 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 天,然后使用patience=3 从最佳(val_loss=0.0030)结果中恢复权重:

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

预测结果非常棒,不是吗?

这是因为算法从 #5 epoch 恢复了最佳权重。好的,现在让我们将此模型保存到.h5 文件,向后移动 -10 天并预测过去 5 天(在第一个示例中,我们制作模型并在 4 月 17 日至 23 日进行验证,包括周末休息日,现在让我们在 4 月 2 日至 8 日进行测试)。结果:

它显示了绝对错误的方向。正如我们所看到的,这是因为模型在 4 月 17 日至 23 日进行了训练,并在 4 月 17 日至 23 日获得了 #5 epoch 的最佳验证集,而不是在 2 日至 8 日。如果我尝试进行更多训练,选择哪个 epoch,无论我做什么,过去总会有很多时间间隔预测错误。

为什么模型在自己的训练数据上显示错误的结果?我训练了数据,它一定记得如何在这块集合上预测数据,但是预测错误。我也尝试过:

使用包含 50k+ 行、20 年股票价格的大型数据集,添加或多或少的特征 创建不同类型的模型,例如添加更多隐藏层、不同的 batch_size、不同的激活层、dropout、batchnormalization 创建自定义 EarlyStopping 回调,从多个验证数据集中获取平均 val_loss 并选择最佳

也许我错过了什么?我可以改进什么?

这是一个非常简单且可重现的示例。 yfinance 下载标准普尔 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()

【问题讨论】:

如今可重复的例子是如此罕见(与没有类似问题的大量类似问题相比),这可以说是在您的帖子开头宣传其存在的好主意(添加);) 问题可能只是您对股市的可预测性期望过高。如果你在 100 万次抛硬币的序列上训练了一个模型,然后试图让它预测抛硬币,即使抛硬币来自训练数据 - 模型,模型出错也不足为奇预计不会记住它的训练数据并反刍它。 除了@user2357112supportsMonica 所说的,你的模型是正确的,这就是我期望这样的模型真正得到的(至少具有任何一致性),而且你是对 5 天的数据期望过高。你真的需要更多的数据才能说出你的模型中的错误是什么。 还有很多参数可以调整模型。我尝试了其中的几个,例如提前停止(耐心 = 20)、增加 epoch 数、将 lstm 单位从 32 增加到 64 等。结果要好得多。在这里查看github.com/jvishnuvardhan/***_Questions/blob/master/…。正如@sirjay 所说,添加更多功能(目前只有 4 个)、添加更多层(lstm、batchnorm、dropout 等),运行超参数优化会带来更好的性能。 @VishnuvardhanJanapati 感谢您的检查。我编译了你的代码,保存了模型,然后设置了df.drop(df.tail(10).index, inplace=True),结果和我一样。 【参考方案1】:

OP 假设了一个有趣的发现。让我将原始问题简化如下。

如果模型是在特定时间序列上训练的,为什么模型不能重建之前已经训练过的时间序列数据?

嗯,答案嵌入在训练进度本身中。由于此处使用EarlyStopping 以避免过度拟合,因此最佳模型保存在epoch=5,其中val_loss=0.0030 如OP所述。此时训练损失等于0.0343,即训练的RMSE为0.185。由于数据集是使用MinMaxScalar 缩放的,因此我们需要撤消 RMSE 的缩放以了解发生了什么。

发现时序的最小值和最大值分别为22903380。因此,将0.185 作为训练的 RMSE 意味着,即使对于训练集,预测值也可能与真实值相差大约 0.185*(3380-2290),即平均为 ~200 个单位。

这解释了为什么在前一个时间步预测训练数据本身时会有很大差异。

我应该怎么做才能完美模拟训练数据?

我问自己这个问题。简单的答案是,让训练损失接近0,即模型过拟合。

经过一些训练,我意识到只有 1 个 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')

并且该模型在不考虑EarlyStopping的情况下针对1000 epochs进行了训练。

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

1000th epoch 结束时,我们的训练损失为0.00047,远低于您的情况下的训练损失。所以我们希望模型能够更好地重建训练数据。以下是 4 月 2-8 日的预测图。

最后说明:

在特定数据库上进行训练并不一定意味着模型应该能够完美地重建训练数据。尤其是引入early stopping、正则化和dropout等方法避免过拟合时,模型更倾向于泛化而不是记忆训练数据。

【讨论】:

我很好奇。为什么你推荐 64 的批量大小来进行过拟合(与 1 相比)?为什么 shuffle=True?这不是需要更长的时间才能收敛到解决方案吗? 我相信随着批次大小的增加,模型变得更容易过度拟合。请查看here 和here。此外,使用batch_size=1 进行训练真的很慢。是的,shuffle=False 会花费更短的时间来过度拟合,因为数据是连续的。 无论如何,你的方法太适合模型了,你的 LSTM 绝对比训练记住的更多!还有一件事:1000 个 epoch 也很长。 @DanielScott 事实上,我们有大约 500 个样本,当你训练它们超过 1000 个 epoch 时,即使 shuffle=Truebatch_size=64。您最终将获得与shuffle=Falsebatch_size=1epoch=50 几乎完全相同的性能。因为这样的 epoch 太多,所以使用少量示例(即 500 个)将显着增加相关 64-mini-batches 的机会。我运行了他的模型并将其与下面的模型进行了比较,两者都得出了几乎完全相同的预测!除了几乎相同的训练时间!【参考方案2】:

嫌疑人 #1 - 正则化

神经网络非常擅长过度拟合训练数据,实际上有一个experiment 用训练数据集上的随机标签替换 CIFAR10(图像分类任务)标签(y 值),并且网络拟合随机标签导致几乎为零损失。

在左侧,我们可以看到给定足够的 epochs 随机标签 大约 0 损失 - 满分(来自understanding deep learning requires re-thinking generalization by zhang et al 2016)

那么为什么它没有一直发生呢? regularization

正则化(大致)试图解决比我们为模型定义的优化问题(损失)更难的问题。

神经网络中一些常见的正则化方法:

提前停止 辍学 批量标准化 权重衰减(例如 l1 l2 规范) 数据增强 添加随机/高斯噪声

这些方法有助于减少过度拟合,通常会带来更好的验证和测试性能,但会降低训练性能(实际上这并不重要,如上一段所述)。

训练数据性能通常不是那么重要,为此我们使用验证集。

嫌疑人 #2 - 模型尺寸

您正在使用具有 32 个单元的单个 LSTM 层。那是很小的。 尝试增加大小,甚至放置两个 LSTM 层(或双向层),我确信模型和优化器只要您允许它们就会过度拟合您的数据 - 即删除早期停止、restore_last_weights 和上面指定的任何其他正则化。

关于问题复杂性的说明

试图仅通过查看历史来预测未来的股票价格并不是一件容易的事,即使模型可以(过度)完美地拟合训练集,它也可能不会在测试集或现实世界中做任何有用的事情.

ML 不是黑魔法,x 样本需要以某种方式与 y 标签相关,我们通常假设 (x,y) 是从某个分布中提取的。

当您需要为狗/猫类别手动标记图像时,一种更直观的思考方式 - 这非常简单。但是您可以仅通过查看股票的历史来手动“标记”股票价格吗?

这是对这个问题有多难的一些直觉。

关于过度拟合的注意事项

不应追求更高的训练性能尝试过拟合训练数据几乎毫无用处,因为我们通常会尝试使用与训练数据具有相似属性的新的不可见数据的模型来表现良好。所有的想法都是尝试概括和学习数据的属性以及与目标的相关性,这就是学习的意义:)

【讨论】:

【参考方案3】:

正如其他人已经说过的那样,您不应对此抱有太大期望。

不过,我在您的代码中发现了以下内容:

    在训练测试期间,您每次都重新拟合缩放器。您需要保存sacler,并且只在测试期间转换数据,否则结果会略有不同:

    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)
    

    设置shuffle=False。因为您确实需要保持数据集的顺序。

    设置batch_size=1。因为它不太容易过度拟合,学习会更嘈杂,平均错误更少。

    设置epochs=50或更多。


通过上述设置,模型达到loss: 0.0037 - val_loss: 3.7329e-04

检查以下预测样本:

从 2020 年 4 月 17 日起 --> 2020 年 4 月 23 日:

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

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

【讨论】:

对于任何对此进行测试的人,sklearn.externals.joblib is deprecated in 0.21 and will be removed in 0.23. Please import this functionality directly from joblib, which can be installed with: pip install joblib. If this warning is raised when loading pickled models, you may need to re-serialize those models with scikit-learn 0.21+. 我将shuffle 保留为True,将batch_size 保留为64,但将epochs 增加到250,结果很好。【参考方案4】:

为什么模型在其自己的训练数据上显示错误的结果?我训练了数据,它一定记得如何在这块集合上预测数据,但是预测错了。

您希望模型学习输入和输出之间的关系,而不是记忆。如果一个模型记住了每个输入的正确输出,我们可以说它过度拟合了训练数据。通常,您可以通过使用一小部分数据来强制模型过度拟合,因此如果这是您希望看到的行为,您可以尝试这样做。

【讨论】:

【参考方案5】:

基本上如果你想得到更好的训练数据结果,你的训练准确率应该尽可能高。您应该针对您拥有的数据使用更好的模型。基本上,无论测试精度如何,您都应该检查您的训练精度是否用于此目的。这也称为过拟合,它在训练数据而不是测试数据中提供更好的准确性。

在采用最佳测试/验证准确度而非训练准确度的情况下,提前停止可能会受到影响。

【讨论】:

【参考方案6】:

将模型架构和优化器更改为 Adagrad 后,我能够在一定程度上改善结果。

这里使用 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()

股票预测是一项非常具有挑战性的任务,因此我们可以让多个模型协同工作以进行预测,然后根据最大投票结果进行预测,而不是坚持使用单个模型的预测,这与集成学习方法类似。此外,我们可以将几个模型堆叠在一起,例如:

    深度前馈自动编码器神经网络降维 + 深度递归神经网络 + ARIMA + Extreme Boosting Gradient Regressor

    Adaboost + Bagging + Extra Trees + Gradient Boosting + Random Forest + XGB

强化学习代理在股票预测中的表现相当不错,例如:

    海龟贸易代理 移动平均代理 信号滚动代理 策略梯度代理 Q 学习代理 进化策略代理

请找到一个资源丰富的链接here。

【讨论】:

adam 也有这些属性,实际上adam 是adagrad 的某种演变【参考方案7】:

简短的回答:

设置:

batch_size = 1
epochs = 200
shuffle = False

直觉:您正在描述训练数据中高精度的优先级。这是描述过拟合。为此,请将批量大小设置为 1,epochs 高,然后改组。

【讨论】:

【参考方案8】:

这是不合适的,为了改善这一点,你需要在隐藏层中添加神经元。!! 另一点是尝试激活函数'relu'。 Sigmoid 没有给出好的结果。您还需要在输出层中定义“softmax”。!

【讨论】:

听起来你掌握着预测市场的秘诀。他还应该怎么做? softmax 用于分类,它是一个回归问题。 @DanielScott 你不明白吗。在深处(数十亿层之下),这是一个决定盈利或亏损的分类问题。为什么还要关心预测时间序列? @Sowmya 我喜欢你的幽默。 ;)【参考方案9】:

为什么模型在自己的训练数据上显示错误的结果?我训练了数据,它必须 记住如何预测这块集合上的数据,但是预测错误。

看看你在做什么:

    使用一些层构建模型 使用 training_data 训练模型 当您训练模型时,所有可训练的参数都会得到训练(即模型的权重得到保存) 这些权重现在表示输入和输出之间的关系。 当您再次预测相同的 training_data 时,这次经过训练的模型使用权重来获取输出。 模型的质量现在决定了预测,因此即使数据相同,它们也不同于原始结果。

【讨论】:

以上是关于为啥神经网络在自己的训练数据上预测错误?的主要内容,如果未能解决你的问题,请参考以下文章

神经网络为啥需要训练多轮运动

MLP模型训练出来的数据预测结果为啥都是0.1到1之间的数呢?

在 matlab 上配置和训练后,如何使用神经网络运行预测?

手撕Resnet卷积神经网络-pytorch-详细注释版(可以直接替换自己数据集)-直接放置自己的数据集就能直接跑。跑的代码有问题的可以在评论区指出,看到了会回复。训练代码和预测代码均有。

为啥我的神经网络总是预测同一个类别?

为啥我的 MLP 神经网络的输出在 0 和 1 之间?