本文属于知识点总结,内容属于摘抄和整理,看完本文无需再查阅其他资料

一、提出问题

神经网络训练多少轮是一个很关键的问题,为了获得性能良好的神经网络,网络定型过程中需要进行许多关于设置(超参数)的决策。超参数之一是定型周期(epoch)的数量:亦即应当完整遍历数据集多少次(一次为一个epoch)?如果epoch数量太少,网络有可能发生欠拟合(即对于定型数据的学习不够充分);如果epoch数量太多,则有可能发生过拟合(即网络对定型数据中的“噪声”而非信号拟合)。

根本原因就是因为继续训练会导致测试集上的准确率下降。那继续训练导致测试准确率下降的原因猜测可能是:

1. 过拟合

2. 学习率过大导致不收敛

3. 使用正则项的时候,Loss的减少可能不是因为准确率增加导致的,而是因为权重大小的降低

二、Early Stopping是什么

那如何选择训练轮数呢?Early stopping可以帮助我们解决这个问题,它也可以被视为一种能够避免网络发生过拟合的正则化方法。它的作用就是当模型在验证集上的性能不再增加的时候就停止训练,从而达到充分训练的作用,又避免过拟合。Early stopping旨在解决epoch数量需要手动设置的问题。

EarlyStopping是Callbacks的一种,callbacks用于指定在每个epoch开始和结束的时候进行哪种特定操作。Callbacks中有一些设置好的接口,可以直接使用,如’acc’, 'val_acc’, ’loss’ 和 ’val_loss’等等。
EarlyStopping则是用于提前停止训练的callbacks。具体地,可以达到当训练集上的loss不在减小(即减小的程度小于某个阈值)的时候停止继续训练。

官方文档:Callbacks API (keras.io)

源码:keras/callbacks.py at master · keras-team/keras · GitHub

原理

将数据分为训练集和验证集,每个epoch结束后(或每N个epoch后): 在验证集上获取测试结果,随着epoch的增加,如果在验证集上发现测试误差上升,则停止训练;将停止之后的权重作为网络的最终参数。

这种做法很符合直观感受,因为精度都不再提高了,在继续训练也是无益的,只会提高训练的时间。那么该做法的一个重点便是怎样才认为验证集精度不再提高了呢?并不是说验证集精度一降下来便认为不再提高了,因为可能经过这个Epoch后,精度降低了,但是随后的Epoch又让精度又上去了,所以不能根据一两次的连续降低就判断不再提高。一般的做法是,在训练的过程中,记录到目前为止最好的验证集精度,当连续10次Epoch(或者更多次)没达到最佳精度时,则可以认为精度不再提高了。
 

三、图示直观理解


最优模型是在垂直虚线的时间点保存下来的模型,即处理测试集时准确率最高的模型。

为什么能减小过拟合


当还未在神经网络运行太多迭代过程的时候,w参数接近于0,因为随机初始化w值的时候,它的值是较小的随机值。当你开始迭代过程,w的值会变得越来越大。到后面时,w的值已经变得十分大了。所以early stopping要做的就是在中间点停止迭代过程。我们将会得到一个中等大小的w参数,会得到与L2正则化相似的结果,选择了w参数较小的神经网络。

优缺点

虽然早停法简单易懂,也很好操做,可是也存在很大的缺陷。好比当模型剧烈波动的时候可能会停的太早,以致于不能获得想要的“好结果”。另外因为原理过于简单,对于复杂的问题每每统一用一种方式进行评判,也不能很好的获得恰当的结论。

四、EarlyStopping的使用与技巧

一般是在model.fit函数中调用callbacks,fit函数中有一个参数为callbacks。注意这里需要输入的是list类型的数据,所以通常情况只用EarlyStopping的话也要是[EarlyStopping()]

EarlyStopping的参数:

# monitor:监视参数,min_delta:小于此数认为不变化,mode:loss小好,acc大好,patience:n周期无提升则退出,restore_best_weights:取最优权重
earlyStop = EarlyStopping(monitor='val_accuracy', min_delta=0, patience=80, mode='max', verbose=1, restore_best_weights = True)
# 增加validation_data参数作为验证集,添加早停止机制,训练时打乱序列顺序
history = model.fit(trainx, trainy, callbacks=[earlyStop], epochs=300, batch_size=batch_size, validation_data=(testx, testy), verbose=1, shuffle=True) 
  1. monitor: 监控的数据接口,有’acc’,’val_acc’,’loss’,’val_loss’等等。正常情况下如果有验证集,就用’val_acc’或者’val_loss’。但是因为笔者用的是5折交叉验证,没有单设验证集,所以只能用’acc’了。
  2. min_delta:增大或减小的阈值,只有大于这个部分才算作improvement。这个值的大小取决于monitor,也反映了你的容忍程度。如monitor是’acc’,同时其变化范围在70%-90%之间,所以对于小于0.01%的变化不关心。加上观察到训练过程中存在抖动的情况(即先下降后上升),所以适当增大容忍程度,最终设为0.003%。
  3. patience:能够容忍多少个epoch内都没有improvement。这个设置其实是在抖动和真正的准确率下降之间做tradeoff。如果patience设的大,那么最终得到的准确率要略低于模型可以达到的最高准确率。如果patience设的小,那么模型很可能在前期抖动,还在全图搜索的阶段就停止了,准确率一般很差。patience的大小和learning rate直接相关。在learning rate设定的情况下,前期先训练几次观察抖动的epoch number,比其稍大些设置patience。在learning rate变化的情况下,建议要略小于最大的抖动epoch number。笔者在引入EarlyStopping之前就已经得到可以接受的结果了,EarlyStopping算是锦上添花,所以patience设的比较高,设为抖动epoch number的最大值。
  4. mode: 就’auto’, ‘min’, ‘,max’三个可能。如果知道是要上升还是下降,建议设置一下。笔者的monitor是’acc’,所以mode=’max’。
  5. restore_best_weights : 如果restore_best_weights默认为False,如果是False,则保留最后一次训练时的权重参数,如果设置为True,则保存训练过程中准确率最高或者误差最时的网络权重。
  6. verbose:日志显示函数,verbose = 0 为不在标准输出流输出日志信息,verbose = 1 为输出进度条记录,verbose = 2 为每一个epoch输出一行记录


min_delta和patience都和“避免模型停止在抖动过程中”有关系,所以调节的时候需要互相协调。通常情况下,min_delta降低,那么patience可以适当减少;min_delta增加,那么patience需要适当延长;反之亦然。

五、数学原理介绍

我们需要一个停止的标准来实施早停法,因此,我们希望它可以产生最低的繁华错误,同时也可以有最好的性价比,即给定泛化错误下的最小训练时间

1、停止标准简介

在这里插入图片描述

第一类停止标准

在这里插入图片描述

第二类停止标准

在这里插入图片描述

第三类停止标准

在这里插入图片描述

2、停止标准选择规则

一般情况下,“较慢”的标准会相对而言在平均水平上表现略好,可以提高泛化能力。然而,这些标准需要较长的训练时间。其实,总体而言,这些标准在系统性的区别很小。主要选择规则包括:

  1. 除非较小的提升也有很大价值,否则选择较快的停止标准
  2. 为了最大可能找到一个好的方案,使用GL标准
  3. 为了最大化平均解决方案的质量,如果网络只是过拟合了一点点,可以使用PQ标准,否则使用UP标准

六、代码示例整理

1.

small_experiment/early_stopping.py at master · tigerchen52/small_experiment (github.com)

2.

#!/usr/bin/env python
# -*- coding:utf-8 -*- 
# Author: Jia ShiLin
 
import numpy as np
import pandas as pd
 
from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.callbacks import EarlyStopping, ModelCheckpoint
from keras.optimizers import SGD, Adadelta, Adam, RMSprop, Adagrad, nadam, Adamax
 
data = pd.read_csv('winequality-red.csv', sep=';')
y = data['quality']
x = data.drop(['quality'], axis=1)
 
# 数据集划分
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=2019)
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.2, random_state=2019)
 
 
# model_function
def create_model(opt):
    '''
    creat a  model
    :param opt:
    :return:model
    '''
    model = Sequential()
    model.add(Dense(100, input_dim=x_train.shape[1], activation='relu'))
    model.add(Dense(50, activation='relu'))
    model.add(Dense(25, activation='relu'))
    model.add(Dense(1, activation='linear'))
    return model
 
 
# create_function,用来定义在训练期间将使用的回调函数
def create_callbacks(opt):
    '''
    回调函数
    :return:callbacks,类型list
    '''
 
    # 一般是在model.fit函数中调用callbacks,fit函数中有一个参数为callbacks。
    # 注意这里需要输入的是list类型的数据,所以通常情况只用EarlyStopping的话也要是[EarlyStopping()]
    callbacks = [
        EarlyStopping(monitor='val_acc', patience=200, verbose=2),
        ModelCheckpoint('best_model_' + opt + '.h5', monitor='val_acc', save_best_only=True, verbose=0)
    ]
    return callbacks
 
 
# 创建一个想要尝试的优化器字典
opts = dict({'sgd': SGD(),
             'adam': Adam(),
             })
 
# train_and_save
results = []
# 遍历优化器
for opt in opts:  # 依次取到每个键
    model = create_model(opt)
    callbacks = create_callbacks(opt)
    model.compile(loss='mse', optimizer=opts[opt], metrics=['accuracy'])
    hist = model.fit(x_train, y_train, batch_size=128, epochs=5000,
                     validation_data=(x_train.values,y_train),
                     batch_size=128,
                     callbacks=callbacks,
                     verbose=0
                     )
    best_epoch = np.argmax(hist.history['val_acc'])#返回最大下标
    best_acc = hist.history['val_acc']['best_epoch']
 
    #加载具有最高验证精度的模型
    best_model = create_model(opt)
    best_model.load_weights('best_model_'+opt+'.h5')
    best_model.compile(loss='mse',optimizer=opts[opt],metrics='accuracy')
    score = best_model.evaluate(x_train.values,y_test,verbose=0)
    test_accuracy = score[1]
    results.append([opt,best_epoch,best_acc,test_accuracy])
 
    #比较结果
    res = pd.DataFrame(results)
    res.columns =['optimizer','epoch','val_accuracy','test_accuracy']
    print(res)#或者控制台输入:res

3. 

class RocAucMetricCallback(keras.callbacks.Callback):
    def __init__(self, predict_batch_size=1024):
        super(RocAucMetricCallback, self).__init__()
        self.predict_batch_size = predict_batch_size
 
    def on_batch_begin(self, batch, logs={}):
        pass
 
    def on_batch_end(self, batch, logs={}):
        pass
 
    def on_train_begin(self, logs={}):
        if not ('val_roc_auc' in self.params['metrics']):
            self.params['metrics'].append('val_roc_auc')
 
    def on_train_end(self, logs={}):
        pass
 
    def on_epoch_begin(self, epoch, logs={}):
        pass
 
    def on_epoch_end(self, epoch, logs={}):
        logs['roc_auc'] = float('-inf')
        if (self.validation_data):
            logs['roc_auc'] = roc_auc_score(self.validation_data[1],
                                            self.model.predict(self.validation_data[0],
                                                               batch_size=self.predict_batch_size))
            print('ROC_AUC - epoch:%d - score:%.6f' % (epoch + 1, logs['roc_auc']))
    my_callbacks = [
        RocAucMetricCallback(),  # include it before EarlyStopping!
        EarlyStopping(monitor='roc_auc', patience=20, verbose=2, mode='max')
    ]
 
    mlp.fit(X_train_pre, y_train_pre,
            batch_size=512,
            epochs=500,
            class_weight="auto",
            callbacks=my_callbacks,
            validation_data=(X_train_pre_val, y_train_pre_val))


Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐