有关公式、基本理论等大量内容摘自《动手学深度学习》(TF2.0版))
什么是过拟合和欠拟合?
在我们训练模型过程中经常会遇见两个问题:我们的模型训练过程中准确率很高,但是实际应用或者使用验证集的时候效果比较差;我们模型训练过程准确率比较低,一直无法得到高的准确率。我们通常把前者称为过拟合,后者称为欠拟合。
通俗的说就是:以考研为例,我们希望考生模拟题做的好,考场上也能发挥出对应水平来。
过拟合就是一个人天天做考研模拟试卷(训练集),这些试卷基本上都是满分(准确率很高),但是真正上考场了考的分数很低(实际应用/验证集效果很差)。
欠拟合就更容易理解了,模拟卷都一直做不对...考试的时候也一样。、
总是上述模型都没有学习好,一个仅仅只适用于训练集,另一个则训练集都没学会。
那么上述两个情况为什么会发生呐?一方面有数据集决定,另一方面由模型决定。
从经验上来说:数据集越大,模型能学习的训练集越多就更容易发现其特征,更容易学好;模型越复杂也往往越容易学习更多的特征(等等会以三次多项式项和一次项来举例)
演示过拟合、欠拟合图像的代码如下:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
#生成数据集
n_train, n_test, true_w, true_b = 100, 100, [1.2, -3.4, 5.6], 5
#随机一维x值
features = tf.random.normal(shape=(n_train + n_test, 1))
#映射的三维x值
poly_features = tf.concat([features, tf.pow(features, 2), tf.pow(features, 3)],1)
print(poly_features.shape)
labels = (true_w[0] * poly_features[:, 0] + true_w[1] * poly_features[:, 1]+ true_w[2] * poly_features[:, 2] + true_b)
print(tf.shape(labels))
labels += tf.random.normal(labels.shape,0,0.1)
#绘图相关函数
from IPython import display
def use_svg_display():
display.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
"""Set matplotlib figure size."""
use_svg_display()
plt.rcParams['figure.figsize'] = figsize
def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=None,
legend=None, figsize=(3.5, 2.5)):
set_figsize(figsize)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.semilogy(x_vals, y_vals)
if x2_vals and y2_vals:
plt.semilogy(x2_vals, y2_vals, linestyle=':')
plt.legend(legend)
plt.show()
#训练+绘制
num_epochs, loss = 100, tf.losses.MeanSquaredError()
def fit_and_plot(train_features, test_features, train_labels, test_labels):
net = tf.keras.Sequential()
net.add(tf.keras.layers.Dense(1))
batch_size = min(10, train_labels.shape[0])
train_iter = tf.data.Dataset.from_tensor_slices(
(train_features, train_labels)).batch(batch_size)
test_iter = tf.data.Dataset.from_tensor_slices(
(test_features, test_labels)).batch(batch_size)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
with tf.GradientTape() as tape:
l = loss(y, net(X))
grads = tape.gradient(l, net.trainable_variables)
optimizer.apply_gradients(zip(grads, net.trainable_variables))
train_ls.append(loss(train_labels, net(train_features)).numpy().mean())
test_ls.append(loss(test_labels, net(test_features)).numpy().mean())
print('final epoch: train loss', train_ls[-1], 'test loss', test_ls[-1])
semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('weight:', net.get_weights()[0],
'\nbias:', net.get_weights()[1])
#三阶多项式函数拟合
fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :],
labels[:n_train], labels[n_train:])
#线性函数拟合
fit_and_plot(features[:n_train, :], features[n_train:, :], labels[:n_train],
labels[n_train:])
#少量数据训练
fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2],
labels[n_train:])
上述代码进行了做了三次多项式回归正常样例、一次线性回归正常样例、三次多项式回归极少样例的对比实验。
实验结果如下:
三次多项式回归正常样例:(正常)
一次线性回归正常样例:(过拟合)
三次多项式回归极少样例:(欠拟合)
为什么会出现这种现象呐?
样例二的原因是,模型过于简单,即使有丰富的样例,但是他是个线性模型只认为有一个W,无法学会三次多项式的三个W,所以出现了过拟合;样例三的原因是,通俗点说,就是给的太少,模型学不到太多东西。
世界上也没有绝对准确的函数,往往在过拟合和欠拟合之间寻找一个最佳的。
一些防止过拟合和欠拟合的方法
K折交叉验证
通过上述我们可以知道,训练样本越多越好,如果我们的数据样本很少的时候,我们划分训练集和训练集的时候(例如8:2),会感觉给数据集中只有8成用来训练数据感觉有点可惜(我们肯定想把全部数据都给训练~),那么人们提出了一种划分方案,即K折交叉验证。其实现方法即:把数据集划分为10分,第一轮训练的时候把前九份当成训练集,第十份当成验证集,第二轮训练的时候选择第九份数据当验证集,其他的当训练集,第三轮训练选择第八份当成验证集...通过上述思想,所有的数据都会进行训练,也都会进行验证。
权重衰减/ L2范数正则化
虽然增大训练数据集可能会减轻过拟合,但是获取额外的训练数据往往代价高昂。所以我们学习一个新的防治过拟合的算法。
下面介绍下L2犯数正则化:
线性回归的优化算法:
综上所述:L2范式正则化即增加了惩罚项,该惩罚项与w有关,所以优化过程中求导也会产生新的求导项,最终化简为图中公式。
丢弃法
除了前面介绍的权重衰减,深度学习模型常常使用丢弃法来缓解过拟合问题。丢弃法有一些不同的变体。本节中提到的丢弃法特指倒置丢弃法
说白了就是,训练的时候随机丢弃某些神经元,但是预测的时候不丢弃。
丢弃法从零实现
import tensorflow as tf
import numpy as np
from tensorflow import keras, nn, losses
from tensorflow.keras.layers import Dropout, Flatten, Dense
def dropout(X, drop_prob):
assert 0 <= drop_prob <= 1
keep_prob = 1 - drop_prob
# 这种情况下把全部元素都丢弃
if keep_prob == 0:
return tf.zeros_like(X)
#初始mask为一个bool型数组,故需要强制类型转换
#mask为一个随机矩阵,tf.random.uniform生成shape形状介于minval和maxval之间均匀分布随机数
#<keep_prob的意思为分布在的值小于keep_prob则返回1,否则返回0,举例说keep_prod为0.6,则均匀的随机数里面百分之60的数据小于9.6
#这样生成的矩阵就是非0即1的矩阵,且比例为keep_prob
mask = tf.random.uniform(shape=X.shape, minval=0, maxval=1) < keep_prob
# / keep_prob是为了保持期望
return tf.cast(mask, dtype=tf.float32) * tf.cast(X, dtype=tf.float32) / keep_prob
#随便定义一个矩阵进行测试
X = tf.reshape(tf.range(0, 16), shape=(2, 8))
print(X)
print(dropout(X, 0))
print(dropout(X, 0.5))
print(dropout(X, 1))
#定义模型参数
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
W1 = tf.Variable(tf.random.normal(stddev=0.01, shape=(num_inputs, num_hiddens1)))
b1 = tf.Variable(tf.zeros(num_hiddens1))
W2 = tf.Variable(tf.random.normal(stddev=0.1, shape=(num_hiddens1, num_hiddens2)))
b2 = tf.Variable(tf.zeros(num_hiddens2))
W3 = tf.Variable(tf.random.truncated_normal(stddev=0.01, shape=(num_hiddens2, num_outputs)))
b3 = tf.Variable(tf.zeros(num_outputs))
params = [W1, b1, W2, b2, W3, b3]
#定义模型
drop_prob1, drop_prob2 = 0.2, 0.5
def net(X, is_training=False):
X = tf.reshape(X, shape=(-1,num_inputs))
H1 = tf.nn.relu(tf.matmul(X, W1) + b1)
if is_training:# 只在训练模型时使用丢弃法
H1 = dropout(H1, drop_prob1) # 在第一层全连接后添加丢弃层
H2 = nn.relu(tf.matmul(H1, W2) + b2)
if is_training:
H2 = dropout(H2, drop_prob2) # 在第二层全连接后添加丢弃层
return tf.math.softmax(tf.matmul(H2, W3) + b3)
#读取数据
# 本函数已保存在d2lzh_pytorch
from tensorflow.keras.datasets import fashion_mnist
batch_size=256
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
x_train = tf.cast(x_train, tf.float32) / 255 #在进行矩阵相乘时需要float型,故强制类型转换为float型
x_test = tf.cast(x_test,tf.float32) / 255 #在进行矩阵相乘时需要float型,故强制类型转换为float型
train_iter = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(batch_size)
test_iter = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(batch_size)
#评估函数
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for _, (X, y) in enumerate(data_iter):
y = tf.cast(y,dtype=tf.int64)
acc_sum += np.sum(tf.cast(tf.argmax(net(X), axis=1), dtype=tf.int64) == y)
n += y.shape[0]
return acc_sum / n
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
params=None, lr=None, trainer=None):
global sample_grads
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
with tf.GradientTape() as tape:
y_hat = net(X, is_training=True)
l = tf.reduce_sum(loss(y_hat, tf.one_hot(y, depth=10, axis=-1, dtype=tf.float32)))
grads = tape.gradient(l, params)
if trainer is None:
sample_grads = grads
params[0].assign_sub(grads[0] * lr)
params[1].assign_sub(grads[1] * lr)
else:
trainer.apply_gradients(zip(grads, params))
y = tf.cast(y, dtype=tf.float32)
train_l_sum += l.numpy()
train_acc_sum += tf.reduce_sum(tf.cast(tf.argmax(y_hat, axis=1) == tf.cast(y, dtype=tf.int64), dtype=tf.int64)).numpy()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
#训练模型
num_epochs, lr, batch_size = 5, 0.5, 256
loss = tf.losses.CategoricalCrossentropy()
train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
params, lr)
丢弃法简单实现
import tensorflow as tf
from tensorflow import keras, nn, losses
from tensorflow.keras.layers import Dropout, Flatten, Dense
from tensorflow.keras.datasets import fashion_mnist
#导入数据集
batch_size=256
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
x_train = tf.cast(x_train, tf.float32) / 255 #在进行矩阵相乘时需要float型,故强制类型转换为float型
x_test = tf.cast(x_test,tf.float32) / 255 #在进行矩阵相乘时需要float型,故强制类型转换为float型
train_iter = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(batch_size)
test_iter = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(batch_size)
#定义模型
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(256,activation='relu'),
Dropout(0.2),
keras.layers.Dense(256,activation='relu'),
Dropout(0.5),
keras.layers.Dense(10,activation=tf.nn.softmax)
])
#配置模型参数
model.compile(optimizer=tf.keras.optimizers.Adam(),
loss = 'sparse_categorical_crossentropy',
metrics=['accuracy'])
#模型训练
model.fit(x_train,y_train,epochs=5,batch_size=256,validation_data=(x_test, y_test),
validation_freq=1)