Tensorflow2.1入门(基于API)实战4——过拟合的基本处理

在之前的实战中,我们对TensorFlow2.1简单的分类,回归问题都有了基本的了解,为了提升我们构建模型的泛化能力,提升在测试集中的准确率,我们需要对模型进行进一步的改进。这一次实战我们将围绕模型的过拟合(overfit)进行展开。根据官方API,这一次我们用的数据集细节并不重要,关键在于处理过拟合的操作。所以我并没有在数据导入的语句上进行细究,直接使用了官方的代码(一些函数实在是看不懂,以后要用再说吧)。
值得一提的是这次在import tensorflow_docs中遇到了一些问题,使用官方的安装语句并没有解决,我的解决方式是直接在github上下载了tensorflow documentation,解压文件,将其中的tensorflow_docs文件夹放到我的Anaconda文件夹中Lib的site-packages中,问题就解决了。接下来让我们进!入!正!题!
依然把官方的传送门留给大家!O(∩_∩)O传送门

数据集准备:(无备注,无解释)

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras import regularizers
import tensorflow_docs as tfdocs
import tensorflow_docs.modeling
import tensorflow_docs.plots
from  IPython import display
from matplotlib import pyplot as plt
import numpy as np
import pathlib
import shutil
import tempfile


logdir = pathlib.Path(tempfile.mkdtemp())/"tensorboard_logs"
shutil.rmtree(logdir, ignore_errors=True)
gz = tf.keras.utils.get_file('HIGGS.csv.gz', 'https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz')
FEATURES = 28
ds = tf.data.experimental.CsvDataset(gz,[float(),]*(FEATURES+1), compression_type="GZIP")

def pack_row(*row):
    label = row[0]
    features = tf.stack(row[1:],1)
    return features, label
packed_ds = ds.batch(10000).map(pack_row).unbatch() 

训练集准备:

在这里先补充一点epochs,batch_size,interation的知识。
比如一个数据集有10000个数据,一个batch含有200个数据,那么batch_size就是200,interation就是10000/200=50。关于batch,大致就是在神经网络在梯度下降算法中为了保证稳定性与运算的速率,将数据分为几组进行处理,这一篇讲得很不错,想了解可以看看神秘笔记。

N_VALIDATION = int(1e3)
N_TRAIN = int(1e4)
BUFFER_SIZE = int(1e4)
BATCH_SIZE = 500
STEPS_PER_EPOCH = N_TRAIN/BATCH_SIZE
validate_ds=packed_ds.take(N_VALIDATION).cache()
train_ds=packed_ds.skip(N_VALIDATION).take(N_TRAIN).cache()
validate_ds = validate_ds.batch(BATCH_SIZE)
train_ds = train_ds.shuffle(BUFFER_SIZE).repeat().batch(BATCH_SIZE)

验证集大小为1000,训练集大小为10000,batch大小为500。本次实战只是用了前10000(BAFFER_SIZE)条数据。验证集,训练集准备完毕。

学习率设定:

通常来说,在训练模型的过程中,随着训练轮数的增加,学习率逐渐下降会使模型拟合的更好:

lr_schedule = tf.keras.optimizers.schedules.InverseTimeDecay(
    0.001,
    decay_steps=STEPS_PER_EPOCH*1000,
    decay_rate=1,
    staircase=False)
def get_optimizer():
    return tf.keras.optimizers.Adam(lr_schedule)

InverseTimeDecay()
initial_learning_rate:初始学习率
decay_steps:多少个steps进行一次衰减,其中一个epochs称为一轮,数据集一轮分为steps个batch进行,每个batchs中有batch_size条记录
decay_rate:衰减率

让我们直观的看看学习率是如何衰减的:

step = np.linspace(0,100000)
lr=lr_schedule(step)#返回对应step的learning_rate
plt.figure(figsize=(8,6))
plt.plot(step/STEPS_PER_EPOCH,lr)
plt.ylim([0,max(plt.ylim())])
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')

linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
start:开始。
stop:结束。
num:返回在在start到stop之间的num个固定间隔的数据,首尾不包括在数据内,num默认值为50。

Tensorflow2.1入门(基于API)实战4——过拟合的基本处理_第1张图片

模型构建(包括回调函数):

def get_callbacks(name):
    return[
        tfdocs.modeling.EpochDots(),
        tf.keras.callbacks.EarlyStopping(monitor='val_binary_crossentropy',patience=200),
#       tf.keras.callbacks.TensorBoard(logdir/name),
    ]

这里的回调函数中有Earlystopping以及打点功能,之前使用过。

def compile_and_fit(model,name,optimizer=None,max_epochs=10000):
    if optimizer is None:
        optimizer=get_optimizer()
    
    model.compile(optimizer=optimizer,
                      loss='binary_crossentropy',
                      metrics=['accuracy','binary_crossentropy'])
    model.summary()
    history=model.fit(
        train_ds,
        steps_per_epoch=STEPS_PER_EPOCH,
        epochs=max_epochs,
        validation_data=validate_ds,
        callbacks=get_callbacks(name),
        verbose=0)
    return history

由于我们后期需要使用不同大小的模型对比过拟合处理的效果,因此我们将compile和fit都是用同一种形式,以方便对比效果。

不同模型的过拟合效果:

size_histories = {}

tiny_model=tf.keras.Sequential([
    layers.Dense(16,activation='elu',input_shape=(FEATURES,)),
    layers.Dense(1,activation='sigmoid')
])
size_histories['Tiny'] = compile_and_fit(tiny_model,'sizes/Tiny')

small_model=tf.keras.Sequential([
    layers.Dense(16,activation='elu',input_shape=(FEATURES,)),
    layers.Dense(16,activation='elu'),
    layers.Dense(1,activation='sigmoid')
])
size_histories['Small']=compile_and_fit(small_model,'size/Small')

medium_model=tf.keras.Sequential([
    layers.Dense(64,activation='elu',input_shape=(FEATURES,)),
    layers.Dense(64,activation='elu'),
    layers.Dense(64,activation='elu'),
    layers.Dense(1,activation='sigmoid')
])
size_histories['Medium']=compile_and_fit(medium_model,"size/Medium")

large_model=tf.keras.Sequential([
    layers.Dense(512,activation='elu',input_shape=(FEATURES,)),
    layers.Dense(512,activation='elu'),
    layers.Dense(512,activation='elu'),
    layers.Dense(512,activation='elu'),
    layers.Dense(1,activation='sigmoid')
])
size_histories['large']=compile_and_fit(large_model,"sizes/large")

以上代码构建了四种复杂程度的模型:Tiny(极其简单的小小的模型),Small(比较简单的小模型),Medium(正常程度的模型),Large(相当复杂的一个大模型)。
对于同样的学习数据来说,越大,越复杂的模型所能学到的东西就越多,但是也就越容易过拟合,当出现过拟合的时候,训练集的accuracy会很高,而验证集的损失会很大(姑且粗略的这么说)。我们衡量一个模型的好坏,根据的是它在预测未见过的数据时的准确率。因此,训练集中的accuracy高并没有说服力,关键是验证集中的accuracy。而EarlyStopping会让训练在损失升高或不变到一定程度的情况下停下来,有助于我们进行分析。

plotter = tfdocs.plots.HistoryPlotter(metric = 'binary_crossentropy', smoothing_std=10)
plotter.plot(size_histories)
plt.ylim([0.5, 0.7])

plotter.plot(size_histories)
plt.xscale('log')
plt.xlim([5,max(plt.xlim())])
plt.ylim([0.5,0.7])
plt.xlabel("Epochs [Log Scale]")

Tensorflow2.1入门(基于API)实战4——过拟合的基本处理_第2张图片

让我们根据这张图来进行分析:
BLUE:蓝线代表Tiny集的训练过程,显然,随着训练轮数的增加,两条线都保持着下降趋势,也就是说根据当前情况来说,只要增加训练轮数,验证集和训练集的损失都会下降,并没有出现过拟合的情况。
ORANGE:橙线代表Small集的训练过程,表现也很不错。
GREEN:绿线代表Medium集的训练过程,显然代表验证集的虚线并没有持续下降,而是在训练一定次数后出现了上升,虽然训练集的交叉熵依然随着训练次数下降(似乎也是必然)。这说明Medium集出现了过拟合。
LARGE:红线代表Large集的训练过程,趋势同绿线,而且过拟合出现的还比绿线更早一点。

过拟合(Overfit)的处理:

根据API,官网提出了两种处理过拟合的基本方法:使用正则化或Dropout。

正则化(Regularization):

正则化分为两种:第一种是将一些权重通过惩罚的方式减小到零(L1 regularization),另一种方式也是通过惩罚的方式减小到接近于零(L2 regularization)。这里被减小的参数具体是什么,还没有深入的了解,以前好像听课,说是对拟合中的高次幂项的系数进行惩罚,以防止过拟合。。(说话声音越来越小。。。。。(⊙﹏⊙))。我们使用第二种方式。

regularizer_histories={}
regularizer_histories['Tiny']=size_histories['Tiny']

l2_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001),
                 input_shape=(FEATURES,)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(512, activation='elu',
                 kernel_regularizer=regularizers.l2(0.001)),
    layers.Dense(1, activation='sigmoid')
])
regularizer_histories['l2'] = compile_and_fit(l2_model, "regularizers/l2")
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])

由于训练过程会存在一些偏差,我的效果并不是特别好,这里用官网的图:
Tensorflow2.1入门(基于API)实战4——过拟合的基本处理_第3张图片
这里可以明显的看到,训练集与验证集的交叉熵都在下降(不过中间那个峰。。是为什么)。

Dropout:

Dropout层会将一些输出置零。官网是这么说的:Dropout, applied to a layer, consists of randomly “dropping out” (i.e. set to zero) a number of output features of the layer during training. Let’s say a given layer would normally have returned a vector [0.2, 0.5, 1.3, 0.8, 1.1] for a given input sample during training; after applying dropout, this vector will have a few zero entries distributed at random, e.g. [0, 0.5,
1.3, 0, 1.1].
就是会把输出的特征(feature)中随机的一些值置为零,比率通常在0.2~0.5之间。

dropout_model = tf.keras.Sequential([
    layers.Dense(512, activation='elu', input_shape=(FEATURES,)),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(1, activation='sigmoid')
])
regularizer_histories['dropout'] = compile_and_fit(dropout_model, "regularizers/dropout")
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])

吐槽一下这个模型好复杂,jupyter notebook跑了蛮久的。。
Tensorflow2.1入门(基于API)实战4——过拟合的基本处理_第4张图片这个效果确实不怎么明显,官方吐槽:It’s clear from this plot that both of these regularization approaches improve the behavior of the “Large” model. But this still doesn’t beat even the “Tiny” baseline.

Dropout&Regularization:

于是官方提议要不然两个方法试试吧,虽然奶茶不好喝,但是加上芋圆,珍珠,补丁,仙草。。。依然可以是一锅好喝的粥。

combined_model = tf.keras.Sequential([
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu', input_shape=(FEATURES,)),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(512, kernel_regularizer=regularizers.l2(0.0001),
                 activation='elu'),
    layers.Dropout(0.5),
    layers.Dense(1, activation='sigmoid')
])

regularizer_histories['combined'] = compile_and_fit(combined_model, "regularizers/combined")
plotter.plot(regularizer_histories)
plt.ylim([0.5, 0.7])

Tensorflow2.1入门(基于API)实战4——过拟合的基本处理_第5张图片

好了,这是一个令人高兴的消息,在我们把两个方法结合在一起以后,交叉熵明显表现不错,甚至超过了Tinymodel的baseline。
官方喜悦的说:This model with the “Combined” regularization is obviously the best one so far.

总结:

这次实战中,我们从模型的过拟合问题出发, 研究了不同复杂度的模型的过拟合问题,并使用Regularization和Dropout的方法,对过拟合的问题进行了研究(敷衍)。并发现两者共同使用的情况下,可以较好的解决复杂模型的过拟合问题。但是教程中说,这并不能说明这就是过拟合的最好解决方式,在真正实践的过程中可能还需要进行多种尝试。
这次实战的重点在于:
1:Dropout的使用。
2:Regularization的使用。
3:如何判断模型是否出现过拟合的问题(作出训练集与损失集的accuracy,交叉熵或其他特征值的图像进行判断)。

好了,这次的实战就到这里,如果在复现的时候有发现错误,记得留言提醒下我哈。谢谢~!

你可能感兴趣的:(Tensorflow实战入门)