回想一下上一课中的例子,Keras将在其训练模型的各个时期保留训练和验证损失的历史记录。在本课中,我们将学习如何解释这些学习曲线,以及如何使用它们来指导模型开发。特别是,我们将检查学习曲线,寻找拟合不足和拟合过度的证据,并查看几种纠正策略。
你可能认为训练数据中的信息有两种:信号和噪声。信号是概括的部分,可以帮助我们的模型根据新数据进行预测。噪声是只适用于训练数据的部分;噪声是所有来自真实世界数据的随机波动,或者所有偶然的、非信息性的模式,这些模式实际上无法帮助模型做出预测。噪音是一个可能看起来有用但实际上无用的部分。
我们通过在训练集中选择使损失最小化的权重或参数来训练模型。然而,您可能知道,为了准确评估模型的性能,我们需要在一组新的数据(验证数据)上对其进行评估。(你可以在《机器学习导论》中看到我们关于模型验证的课程,以供复习。)
当我们训练一个模型时,我们已经在训练集上一代一代(epoch by epoch)地绘制损失曲线。此外,我们还将添加验证数据的图。这些曲线我们称之为学习曲线。为了有效地训练深度学习模型,我们需要能够解释它们。
训练和验证损失的图
验证损失给出了对看不见数据的预期误差的估计。
现在,当模型学习信号或噪声时,训练损失将下降。但只有当模型学习到信号时,验证损失才会降低。(无论模型从训练集中学习到什么噪声,都不会推广到新数据。)因此,当模型学习信号时,两条曲线都会下降,但当它学习噪声时,曲线中会产生一个波动(gap)。波动的大小告诉您模型已经学习了多少噪声。
理想情况下,我们将创建能够学习所有信号而无噪声的模型。这几乎永远不会发生。相反,我们做了一笔交易。我们可以让模型学习更多的信号,但代价是学习更多的噪声。只要交易对我们有利,验证损失就会继续减少。然而,在某一点之后,交易可能会对我们不利,成本超过收益,验证损失开始上升。
两张图。左图,通过几个数据点的一条直线与真抛物线拟合。右图,一条曲线穿过每个数据点,其真实拟合为抛物线。
过拟合和欠拟合
这种权衡表明,在训练模型时可能会出现两个问题:信号不足或噪声过大。未充分拟合训练集是指由于模型没有学习到足够的信号,导致损失没有尽可能低。过度拟合训练集是指由于模型学习了太多的噪声,导致损失没有尽可能低。训练深度学习模型的诀窍是在两者之间找到最佳平衡。
我们将研究几种从训练数据中获取更多信号的方法,同时减少噪音。
模型的容量是指它能够学习的模式的大小和复杂性。对于神经网络来说,这在很大程度上取决于它有多少神经元以及它们如何连接在一起。如果您的网络似乎不适合数据,您应该尝试增加其容量。
可以通过使网络更宽(将更多单元添加到现有层)或使其更深(添加更多层)来增加网络的容量。更广泛的网络更容易学习更多的线性关系,而更深的网络更喜欢非线性关系。哪个更好取决于数据集。
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中,我们通过回调在训练中包括提前终止。回调函数只是一个在网络运行时需要经常运行的函数。提前终止回调将在每个epoch之后运行。(Keras预先定义了各种有用的回调函数,但您也可以自定义回调函数。)
来自tensorflow。克拉斯。回调导入早期终止
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 个epoch中,验证损失中没有哪怕是 0.001 的改进,那么停止训练并保留找到的最佳模型。”有时很难判断验证损失是由于过度拟合还是仅仅由于随机批次变化而增加。这些参数允许我们设置一些关于何时停止的余量。
正如我们将在示例中看到的,我们将把这个回调与loss和optimizer一起传递给fit方法。
让我们继续根据上一个教程中的示例训练模型。我们将增加该网络的容量,但也会添加一个提前终止的回调函数,以防止过度拟合。
以下是数据准备。
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']
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1109 | 10.8 | 0.470 | 0.43 | 2.10 | 0.171 | 27.0 | 66.0 | 0.99820 | 3.17 | 0.76 | 10.8 | 6 |
1032 | 8.1 | 0.820 | 0.00 | 4.10 | 0.095 | 5.0 | 14.0 | 0.99854 | 3.36 | 0.53 | 9.6 | 5 |
1002 | 9.1 | 0.290 | 0.33 | 2.05 | 0.063 | 13.0 | 27.0 | 0.99516 | 3.26 | 0.84 | 11.7 | 7 |
487 | 10.2 | 0.645 | 0.36 | 1.80 | 0.053 | 5.0 | 14.0 | 0.99820 | 3.17 | 0.42 | 10.0 | 6 |
现在让我们增加网络的容量。我们将选择一个相当大的网络,但一旦验证丢失显示出增加的迹象,就依靠回调来停止训练。
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()))
Minimum validation loss: 0.09168479591608047
果不其然,Keras 在整整 500个epoch之前就停止了训练!
现在用Spotify数据集预测一首歌的流行程度。
在本练习中,您将学习如何通过提前停止回调来提高训练效果,以防止过度拟合。
准备好后,运行下一个单元格来设置一切!
# Setup plotting
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
# Set Matplotlib defaults
plt.rc('figure', autolayout=True)
plt.rc('axes', labelweight='bold', labelsize='large',
titleweight='bold', titlesize=18, titlepad=10)
plt.rc('animation', html='html5')
# Setup feedback system
from learntools.core import binder
binder.bind(globals())
from learntools.deep_learning_intro.ex4 import *
首先加载Spotify数据集。你的任务是根据不同的音频特征预测歌曲的流行程度,比如“节奏”、“舞蹈性”和“模式”。
import pandas as pd
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import make_column_transformer
from sklearn.model_selection import GroupShuffleSplit
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import callbacks
spotify = pd.read_csv('../input/dl-course-data/spotify.csv')
X = spotify.copy().dropna()
y = X.pop('track_popularity')
artists = X['track_artist']
features_num = ['danceability', 'energy', 'key', 'loudness', 'mode',
'speechiness', 'acousticness', 'instrumentalness',
'liveness', 'valence', 'tempo', 'duration_ms']
features_cat = ['playlist_genre']
preprocessor = make_column_transformer(
(StandardScaler(), features_num),
(OneHotEncoder(), features_cat),
)
# We'll do a "grouped" split to keep all of an artist's songs in one
# split or the other. This is to help prevent signal leakage.
def group_split(X, y, group, train_size=0.75):
splitter = GroupShuffleSplit(train_size=train_size)
train, test = next(splitter.split(X, y, groups=group))
return (X.iloc[train], X.iloc[test], y.iloc[train], y.iloc[test])
X_train, X_valid, y_train, y_valid = group_split(X, y, artists)
X_train = preprocessor.fit_transform(X_train)
X_valid = preprocessor.transform(X_valid)
y_train = y_train / 100 # popularity is on a scale 0-100, so this rescales to 0-1.
y_valid = y_valid / 100
input_shape = [X_train.shape[1]]
print("Input shape: {}".format(input_shape))
Input shape: [18]
让我们从最简单的网络开始,一个线性模型。这种型号的容量很小。
在不做任何更改的情况下运行下一个单元格,在Spotify数据集上训练线性模型。
model = keras.Sequential([
layers.Dense(1, input_shape=input_shape),
])
model.compile(
optimizer='adam',
loss='mae',
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=512,
epochs=50,
verbose=0, # suppress output since we'll plot the curves
)
history_df = pd.DataFrame(history.history)
history_df.loc[0:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));
Minimum Validation Loss: 0.1909
像你在这里看到的那样,曲线遵循“曲棍球棒”模式并不罕见。这使得训练的最后一部分很难看到,所以让我们从第10 eopch元开始:
你觉得这个模型怎么样,是欠拟合,过拟合,刚刚好?
这些曲线之间的差距非常小,验证损失永远不会增加,因此,网络更可能是欠拟合而不是过拟合。如果是这样的话,就值得尝试更大的容量。
现在让我们为我们的网络增加一些容量。我们将添加三个隐藏层,每个层128个单元。运行下一个单元格来训练网络,并查看学习曲线。
model = keras.Sequential([
layers.Dense(128, activation='relu', input_shape=input_shape),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
model.compile(
optimizer='adam',
loss='mae',
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=512,
epochs=50,
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));
Epoch 1/50
49/49 [==============================] - 1s 8ms/step - loss: 0.2213 - val_loss: 0.1991
Epoch 2/50
49/49 [==============================] - 0s 5ms/step - loss: 0.1999 - val_loss: 0.1941
Epoch 3/50
49/49 [==============================] - 0s 5ms/step - loss: 0.1956 - val_loss: 0.1939
Epoch 4/50
49/49 [==============================] - 0s 4ms/step - loss: 0.1939 - val_loss: 0.1920
Epoch 5/50
49/49 [==============================] - 0s 3ms/step - loss: 0.1919 - val_loss: 0.1911
...
Epoch 50/50
49/49 [==============================] - 0s 2ms/step - loss: 0.1657 - val_loss: 0.1977
Minimum Validation Loss: 0.1904
你觉得这个模型怎么样,是欠拟合,过拟合,刚刚好?
现在,验证损失很早就开始上升,而训练损失继续减少。这表明网络已经开始过拟合。在这一点上,我们需要尝试一些方法来防止它,要么通过减少单位数量,要么通过像提前停止这样的方法。(我们将在下一课中看到另一个!)
现在定义一个提前终止回调,等待5个 epochs(耐心),等待验证损失至少为0.001(min_delta)的变化,并保持损失最佳的权重(restore_best_weights)。
from tensorflow.keras import callbacks
# YOUR CODE HERE: define an early stopping callback
early_stopping = ____
# Check your answer
q_3.check()
现在运行这个单元来训练模型并得到学习曲线。请注意模型在fit中的callbacks参数。
model = keras.Sequential([
layers.Dense(128, activation='relu', input_shape=input_shape),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
model.compile(
optimizer='adam',
loss='mae',
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=512,
epochs=50,
callbacks=[early_stopping]
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot()
print("Minimum Validation Loss: {:0.4f}".format(history_df['val_loss'].min()));
Epoch 1/50
49/49 [==============================] - 0s 4ms/step - loss: 0.2242 - val_loss: 0.1994
Epoch 2/50
49/49 [==============================] - 0s 3ms/step - loss: 0.2007 - val_loss: 0.1986
Epoch 3/50
49/49 [==============================] - 0s 3ms/step - loss: 0.1959 - val_loss: 0.1931
Epoch 4/50
49/49 [==============================] - 0s 3ms/step - loss: 0.1931 - val_loss: 0.1929
Epoch 5/50
49/49 [==============================] - 0s 3ms/step - loss: 0.1913 - val_loss: 0.1928
...
49/49 [==============================] - 0s 3ms/step - loss: 0.1857 - val_loss: 0.1914
Epoch 11/50
49/49 [==============================] - 0s 2ms/step - loss: 0.1856 - val_loss: 0.1921
Minimum Validation Loss: 0.1914
与不提前停止的训练相比,这是一种进步吗?
一旦网络开始过拟合,早期停止回调确实停止了训练。此外,通过包含restore_best_weight,我们仍然可以保持验证损失最低的模型。
如果你愿意,试着用patience
和min_delta
做实验,看看这会有什么不同。
继续学习几个特殊的层:批处理规范化和退出。
# 3)定义提前终止回调
early_stopping = callbacks.EarlyStopping(
patience=5,
min_delta=0.001,
restore_best_weights=True,
)