过拟合和低度拟合 —— 深度学习 4/6
通过提升能力或者提前停止来提升性能
简介
回忆我们上节课讲过的:Keras会在训练模型时记录每一阶段训练和验证时的损失。在这节课,我们将学习如何解释这些曲线,冰鞋学习如何让使用我们获得到的信息来指导模型提升。特别的,我们将分析学习曲线作为过拟合或低拟合的证据,再学习几种修正的策略。
解释学习曲线
- 你可以将数据分为两类:标志数据和噪声数据。标志数据是数据的可生成部分,这一部分可以帮助我们的模型从新数据中做出预测。而噪声数据是仅仅再训练集中可行。噪声数据都是一些随机的波动数据,他们是实际生活中那些随机发生的,无法形式化表示的数据,他们不能帮助模型做预测。噪声数据看起来可能很有用,但其实不是。
- 我们通过选择权重值或者参数值来尽可能缩小模型预测在训练集上的损失。你也知道,我们要想精确的评估模型的质量,需要一个新的数据集,验证集。
- 当我们训练模型时,我们将每一阶段的损失用图表表示出来。这次我们将验证的数据也加进去,这些图表统称为 学习曲线。要想有效地训练深度学习模型,我们就要能够读懂他们。
- 学习曲线中往往会得出两种问题:标志数据不够或噪声数据过多。低拟合说的就是损失并没有达到预期值,原因就在于模型没有学到足够的标志数据。过拟合 说的就是损失没达到预期值原因在于模型学习了过多的噪声数据。训练深度学习模型的诀窍就在于找到两者的平衡点。
- 我们将看到几个增加标志数据、减少噪声数据的方法。
模型的能力
- 模型的能力指的是它能够学习规律的数量和复杂度。对于神经网络来说,他极大程度上是由神经元的数量和连接方式决定的。如果你的模型呈现低拟合,那么你就要考虑尝试提升他的能力。
- 你可以通过将模型变得更宽(每层的神经元数更多)或者更深(添加更多层)来提升模型能力。更宽的模型更适合学习线性关系,而更深的模型更适合学习非线性的关系。具体怎么选,这就要看数据集了
model = keras.Sequential([
layers.Dense(16, activation='relu'),
layers.Dense(1),
])
wider = keras.Sequential([
layers.Dense(32, activation='relu'),
layers.Dense(1),
])
deeper = keras.Sequential([
layers.Dense(16, activation='relu'),
layers.Dense(16, activation='relu'),
layers.Dense(1),
])
提前终止
- 我们之前提到过,模型倾向于学习噪声数据,验证损失可能会随着训练而增加。为了避免这个现象,我们可以在损失稳定不变后,仅仅终止训练就可以了。像这样终止训练叫 提前终止
- 一旦我们发现损失有向上升的迹象,我们可以重置参数到损失最小的状态。这样可以保证模型不会继续学习,并且阻止数据过拟合。
- 带着提前终止来训练一意味着我们会过于提前停止训练,至少是在网络学习玩标记数据之前。所以除了能够避免过拟合,提前终止同样要控制来避免训练不够长。仅仅将你的训练阶段设置成一个很大的数字(比你需要的大),然后使用提前终止就会解决以上两个问题。
添加提前终止
- 在Keras中,我们通过回调(callback)加入提前终止。回调 就是一个函数,它能够在模型训练一个阶段完成后被调用。(Keras有许多定义好的好用的回调函数,但你也可以自定义)
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(
min_delta=0.001, # minimium amount of change to count as an improvement
patience=20, # how many epochs to wait before stopping
restore_best_weights=True,
)
- 这些参数表达了以下的意思:如果验证损失的提升,在20个阶段后,最小都没有达到0.001,那么就停止训练,并且存储你得到的最好的模型。
- 要判断验证损失到底是因为过拟合而增长,还是仅仅因为随机批次的验证问题,是很难的。参数中就允许我们在终止前设置一些些预留。
案例:带提前终止的模型训练
- 让我们接着用上节课的数据集。我们会提升模型的能力,并加入提前终止回调函数来避免过拟合。
- 再来一次准备数据
import pandas as pd
from IPython.display import display
red_wine = pd.read_csv('../input/dl-course-data/red-wine.csv')
# Create training and validation splits
df_train = red_wine.sample(frac=0.7, random_state=0)
df_valid = red_wine.drop(df_train.index)
display(df_train.head(4))
# Scale to [0, 1]
max_ = df_train.max(axis=0)
min_ = df_train.min(axis=0)
df_train = (df_train - min_) / (max_ - min_)
df_valid = (df_valid - min_) / (max_ - min_)
# Split features and target
X_train = df_train.drop('quality', axis=1)
X_valid = df_valid.drop('quality', axis=1)
y_train = df_train['quality']
y_valid = df_valid['quality']
- 现在我们提升模型能力,我们将获得一个较大的网络,但是依赖着回调函数,在验证损失有增长迹象时终止训练。
from tensorflow import keras
from tensorflow.keras import layers, callbacks
early_stopping = callbacks.EarlyStopping(
min_delta=0.001, # minimium amount of change to count as an improvement
patience=20, # how many epochs to wait before stopping
restore_best_weights=True,
)
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=[11]),
layers.Dense(512, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(1),
])
model.compile(
optimizer='adam',
loss='mae',
)
- 在定义完回调函数后,将它作为参数加入
fit
方法中(如果你有多个,把他们放到列表中传入)。选择一个大于你需要的阶段。
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=500,
callbacks=[early_stopping], # put your callbacks in a list
verbose=0, # turn off training log
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
print("Minimum validation loss: {}".format(history_df['val_loss'].min()))
2021-09-13 20:12:24.558514: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:116] None of the MLIR optimization passes are enabled (registered 2)
2021-09-13 20:12:24.572115: I tensorflow/core/platform/profile_utils/cpu_utils.cc:112] CPU Frequency: 2000175000 Hz
Minimum validation loss: 0.09210038185119629
- 当然,Keras在做完500个阶段之前就停止了!