Keras 深度学习笔记(四)机器学习基础:模型评估与正则化

机器学习的四个分支

《Keras深度学习笔记(三):神经网络入门》的例子中,我们已经介绍了三种类型的机器学习问题:二分类问题、多分类问题和标量回归问题。这三者都是监督学习 (supervised learning) 的例子,其目标是学习训练输入与训练目标之间的关系。

机器学习算法大致可分为四大类,将在接下来的四小节中依次介绍。

监督学习

监督学习是最常见的机器学习类型。给定一组样本,它可以学会将输入数据映射到已知目标。

监督学习主要包括分类和回归,但还有许多变体,比如:

  • 序列生成 (sequence generation)。给定一张图像,预测描述图像的文字。
  • 语法树预测 (syntax tree prediction)。给定一个句子,预测其分解生成的语法树。
  • 目标检测 (object detection)。给定一张图像,在图中特定目标的周围画一个边界框。
  • 图像分割 (image segmentation)。给定一张图像,在特定物体上画一个像素级的掩模 (mask)。

无监督学习

无监督学习是指在没有目标的情况下寻找输入数据的有趣变换,其目的在于数据可视化、数据压缩、数据去噪或更好地理解数据中的相关性。降维 (dimensionality reduction) 和聚类 (clustering) 都是众所周知的无监督学习方法。

无监督学习是数据分析的必备技能,在解决监督学习问题之前,为了更好地了解数据集,它通常是一个必要步骤。

自监督学习

自监督学习是监督学习的一个特例,它是没有人工标注的标签的监督学习。标签仍然存在,但它们是从输入数据中生成的,通常是使用启发式算法生成的。

例如自编码器 (autoencoder),其生成的目标就是未经修改的输入。同样,给定视频中过去的帧来预测下一帧,或者给定文本中前面的词来预测下一个词,都是自监督学习的例子。

监督学习、自监督学习和无监督学习之间的区别有时很模糊,这三个类别更像是没有明确界限的连续体。

强化学习

在强化学习中,智能体 (agent) 接收有关其环境的信息,并学会选择使某种奖励最大化的行动。例如,神经网络会“观察”视频游戏的屏幕并输出游戏操作,目的是尽可能得高分。

分类和回归术语表

分类和回归都包含很多专业术语,这些术语在机器学习领域都有确切的定义:

  • 样本 (sample) 或输入 (input):进入模型的数据点。
  • 预测 (prediction) 或输出 (output):从模型出来的结果。
  • 目标 (target):真实值。
  • 预测误差 (prediction error) 或损失值 (loss value):模型预测与目标之间的距离。
  • 二分类 (binary classification):输入样本被划分到两个互斥的类别中。
  • 多分类 (multiclass classification):输入样本被划分到两个以上的类别中。
  • 多标签分类 (multilabel classification):每个输入样本分配多个标签。
  • 标量回归 (scalar regression):目标是连续标量值的任务。
  • 向量回归 (vector regression):目标是一组连续值(比如一个连续向量)的任务。如果对多个值进行回归,那就是向量回归。
  • 小批量 (mini-batch) 或批量 (batch):模型同时处理的一小部分样本。训练时,小批量用来为模型权重计算一次梯度下降更新。

评估机器学习模型

在前面的例子中,仅仅几轮过后模型就开始过拟合,即随着训练的进行,模型在训练数据上的性能始终在提高,但在前所未见的数据上的性能则不再变化或者开始下降。

机器学习的目的是得到可以泛化 (generalize) 的模型,即在前所未见的数据上表现很好的模型,所以能够可靠地衡量模型的泛化能力非常重要。

训练集、验证集和测试集

评估模型的重点是将数据划分为三个集合:训练集、验证集和测试集。在训练数据上训练模型,在验证数据上评估模型。一旦找到了最佳参数,就在测试数据上最后测试一次。

划分验证集是因为我们需要调节模型的超参数 (hyperparameter),比如选择的层数或每层大小。这个调节过程需要使用模型在验证数据上的性能作为反馈信号。

随着我们不断调节模型配置,模型很快就在验证集上过拟合(即使没有在验证集上直接训练模型)。但是我们关心的是模型在全新数据上的性能,而不是在验证数据上的性能,因此需要使用一个完全不同的、前所未见的数据集来评估模型,它就是测试集。

模型一定不能读取与测试集有关的任何信息,既使间接读取也不行。如果基于测试集性能来调节模型,那么对泛化能力的衡量是不准确的。

如果可用数据很少,还有几种高级的评估方法:简单的留出验证、$K$ 折验证,以及带有打乱数据的重复 $K$ 折验证。

  1. 简单的留出验证

    在训练集上训练模型,然后在测试集上评估模型。为了防止信息泄露,我们不能基于测试集来调节模型,所以还应在训练集中保留一个验证集。

    # 简单的留出验证数据划分
    num_validation_samples = 10000
     
    np.random.shuffle(data)   # 通常需要打乱数据
     
    validation_data = data[:num_validation_samples]   # 定义验证集
    data = data[num_validation_samples:]
     
    training_data = data[:]   # 定义训练集
       
    # 在训练数据上训练模型,并在验证数据上评估模型
    model = get_model()
    model.train(training_data)
    validation_score = model.evaluate(validation_data)
     
    # 现在你可以调节模型、重新训练、评估,然后再次调节……
       
    # 一旦调节好超参数,通常就在所有非测试数据上从头开始训练最终模型
    model = get_model()
    model.train(np.concatenate([training_data,       
                                validation_data]))   
    test_score = model.evaluate(test_data)           
    

    这是最简单的评估方法,但有一个缺点:如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。即如果在划分数据前进行不同的随机打乱,最终得到的模型性能差别很大。

  2. $K$ 折验证

    $K$ 折验证 ($K$-fold validation) 将数据划分为大小相同的 $K$ 个分区。对于每个分区 $i$,在剩余的 $K-1$ 个分区上训练模型,然后在分区 $i$ 上评估模型。最终分数等于 $K$ 个分数的平均值。对于不同的训练集/测试集划分,如果模型性能的变化很大,那么这种方法很有用。

    # K 折交叉验证
    k = 4
    num_validation_samples = len(data) // k
     
    np.random.shuffle(data)
     
    validation_scores = []
    for fold in range(k):
        # 选择验证数据分区
        validation_data = data[num_validation_samples * fold:
         num_validation_samples * (fold + 1)]
        # 使用剩余数据作为训练数据
        training_data = data[:num_validation_samples * fold] +
         data[num_validation_samples * (fold + 1):]
     
    	# 创建一个全新的模型实例(未训练)
        model = get_model()
        model.train(training_data)
        validation_score = model.evaluate(validation_data)
        validation_scores.append(validation_score)
     
    # 最终验证分数:K 折验证分数的平均值
    validation_score = np.average(validation_scores)
       
    # 在所有非测试数据上训练最终模型
    model = get_model()
    model.train(data)                        
    test_score = model.evaluate(test_data)   
    
  3. 带有打乱数据的重复 $K$ 折验证

    如果可用的数据相对较少,而你又需要尽可能精确地评估模型,那么可以选择带有打乱数据的重复 $K$ 折验证 (iterated $K$-fold validation with shuffling)。具体做法是多次使用 $K$ 折验证,在每次将数据划分为 $K$ 个分区之前都先将数据打乱,最终分数是每次 $K$ 折验证分数的平均值。这种方法一共要训练和评估 $P\times K$ 个模型($P$ 是重复次数),计算代价很大。

评估模型的注意事项

选择模型评估方法时,需要注意以下几点。

  • 数据代表性 (data representativeness)。我们希望训练集和测试集都能够代表当前数据。在将数据划分为训练集和测试集之前,通常应该随机打乱数据。
  • 时间箭头 (the arrow of time)。如果想要根据过去预测未来(比如股票走势),那么在划分数据前你应该随机打乱数据,否则模型将在未来数据上得到有效训练。应该始终确保测试集中所有数据的时间都晚于训练集数据。
  • 数据冗余 (redundancy in your data)。如果数据中的某些数据点出现了两次,那么打乱数据并划分成训练集和验证集会导致训练集和验证集之间的数据冗余。一定要确保训练集和验证集之间没有交集。

数据预处理、特征工程和特征学习

将数据输入神经网络之前,如何准备输入数据和目标?许多数据预处理方法和特征工程技术都是和特定领域相关的(比如只和文本数据或图像数据相关),下面我们要介绍所有数据领域通用的基本方法。

神经网络的数据预处理

数据预处理的目的是使原始数据更适于用神经网络处理,包括向量化、标准化、处理缺失值和特征提取。

  1. 向量化

    神经网络的所有输入和目标都必须是浮点数张量(在特定情况下可以是整数张量)。无论处理什么数据(声音、图像还是文本),都必须首先将其转换为张量,这一步叫作数据向量化 (data vectorization)。

  2. 值标准化

    一般来说,将取值相对较大的数据(比如多位整数,比网络权重的初始值大很多)或异质数据 (heterogeneous data,比如数据的一个特征在 0~1 范围内,另一个特征在 100~200 范围内) 输入到神经网络中是不安全的。这么做可能导致较大的梯度更新,进而导致网络无法收敛。

    为了让网络的学习变得更容易,输入数据应该具有以下特征:

    • 取值较小:大部分值都应该在 0~1 范围内。
    • 同质性 (homogenous):所有特征的取值都应该在大致相同的范围内。

    此外,下面这种更严格的标准化方法也很常见,而且很有用,虽然不一定总是必需的。

    • 将每个特征分别标准化,使其平均值为 0。
    • 将每个特征分别标准化,使其标准差为 1。

    这对于 Numpy 数组很容易实现。

    # 假设x是一个形状为(samples,features) 的二维矩阵
    x -= x.mean(axis=0)
    x /= x.std(axis=0)
    
  3. 处理缺失值

    数据中有时可能会有缺失值。一般来说,对于神经网络,将缺失值设置为 0 是安全的,只要 0 不是一个有意义的值。网络能够从数据中学到 0 意味着缺失数据,并且会忽略这个值。

    注意,如果测试数据中可能有缺失值,而网络是在没有缺失值的数据上训练的,那么网络不可能学会忽略缺失值。可以人为生成一些有缺失项的训练样本:多次复制一些训练样本,然后删除测试数据中可能缺失的某些特征。

特征工程

特征工程 (feature engineering) 是指将数据输入模型之前,利用我们对于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果。

我们来看一个直观的例子。假设你想开发一个模型,输入一个时钟图像,模型能够输出对应的时间:

如果我们直接用图像的原始像素作为输入数据,那么这个问题将非常困难(需要使用卷积网络,花费大量的计算资源)。但如果我们从更高的层次理解了这个问题(知道怎么看时钟),那么可以找到更好的输入特征,比如每个指针尖的 $(x, y)$ 坐标,然后通过简单的机器学习算法就可以学会坐标与时间的对应关系。还可以进一步进行坐标换为相对于图像中心的极坐标,这样的特征使问题变得非常简单,即使不通过机器学习,简单的舍入运算和字典查找就足以给出大致的时间。

这就是特征工程的本质:用更简单的方式表述问题,从而使问题变得更容易。它通常需要深入理解问题。

特征工程曾经非常重要,因为经典的浅层算法没有足够大的假设空间来自己学习有用的表示。将数据呈现给算法的方式对解决问题至关重要。

对于现代深度学习,大部分特征工程都是不需要的,因为神经网络能够从原始数据中自动提取有用的特征。但是特征工程依然非常重要:

  • 良好的特征仍然可以让你用更少的资源更优雅地解决问题。
  • 良好的特征可以让你用更少的数据解决问题。深度学习模型自主学习特征的能力依赖于大量的训练数据。如果只有很少的样本,那么特征的信息价值就变得非常重要。

过拟合与欠拟合

机器学习的根本问题是优化和泛化之间的对立。优化 (optimization) 是指调节模型以在训练数据上得到最佳性能(即机器学习中的学习),而泛化 (generalization) 是指训练好的模型在前所未见的数据上的性能好坏。机器学习的目的当然是得到良好的泛化,但你无法控制泛化,只能基于训练数据调节模型。

训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合 (underfit) 的,即仍有改进的空间。但在训练数据上迭代一定次数之后,泛化不再提高,验证指标先是不变,然后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误的或无关紧要的。

为了防止模型从训练数据中学到错误或无关紧要的模式,最优解决方法是获取更多的训练数据。模型的训练数据越多,泛化能力自然也越好。如果无法获取更多数据,次优解决方法是调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。

这种降低过拟合的方法叫作正则化 (regularization)。

减小网络大小

防止过拟合的最简单的方法就是减小模型大小,即减少模型中可学习参数的个数。在深度学习中,模型中可学习参数的个数通常被称为模型的容量 (capacity)。直观上来看,参数更多的模型拥有更大的记忆容量 (memorization capacity),因此能够在训练样本和目标之间轻松地学会完美的字典式映射,这种映射没有任何泛化能力。

深度学习模型通常都很擅长拟合训练数据,但真正的挑战在于泛化,而不是拟合

而如果网络的记忆资源有限,为了让损失最小化,网络必须学会对目标具有很强预测能力的压缩表示,这也正是我们感兴趣的数据表示。但是要注意模型的参数也不能太少,否则会导致欠拟合,即模型记忆资源不足。我们需要在容量过大容量不足之间要找到一个折中。

目前没有方法能够确定最佳层数或每层的最佳大小,我们必须在验证集上评估一系列不同的网络架构(不是在测试集上),以便为数据找到最佳的模型大小。

我们在电影评论分类的网络上试一下。原始网络如下所示。

from keras import models
from keras import layers

# 原始模型
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

现在我们尝试用下面这个更小的网络来替换它。

# 容量更小的模型
model = models.Sequential()
model.add(layers.Dense(4, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

下图比较了原始网络与更小网络的验证损失:

可以看到,更小的网络开始过拟合的时间要晚于参考网络,而且开始过拟合之后,它的性能变差的速度也更慢。

下面,我们再向这个基准中添加一个容量更大的网络(容量远大于问题所需)。

# 容量更大的模型
model = models.Sequential()
model.add(layers.Dense(512, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

下图显示了更大的网络与参考网络的性能对比:

更大的网络只过了一轮就开始过拟合,过拟合也更严重。其验证损失的波动也更大。

下图同时给出了这两个网络的训练损失。

可以看到,更大网络的训练损失很快就接近于零。网络的容量越大,它拟合训练数据的速度就越快,但也更容易过拟合。

添加权重正则化

奥卡姆剃刀 (Occam’s razor) 原理:如果一件事情有两种解释,那么最可能正确的解释就是最简单的那个。这个原理也适用于神经网络学到的模型:给定一些训练数据和一种网络架构,很多模型都可以解释这些数据,简单模型比复杂模型更不容易过拟合。

这里简单模型 (simple model) 是指参数值分布的熵更小的模型(或参数更少的模型)。因此,一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则 (regular)。这种方法叫作权重正则化 (weight regularization),其实现方法是向网络损失函数中添加与较大权重值相关的成本 (cost),有两种形式:

  • L1 正则化 (L1 regularization):添加的成本与权重系数的绝对值(权重的 L1 范数)成正比,更容易得到稀疏权重。
  • L2 正则化 (L2 regularization):添加的成本与权重系数的平方(权重的 L2 范数)成正比,更容易得到平滑权重。

在 Keras 中,添加权重正则化的方法是向层传递权重正则化项实例 (weight regularizer instance) 作为关键字参数。下列代码将向电影评论分类网络中添加 L2 权重正则化。

# 向模型添加 L2 权重正则化
from keras import regularizers

model = models.Sequential()
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
                       activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
                       activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

l2(0.001) 的意思是该层权重矩阵的每个系数都会使网络总损失增加 0.001 * weight_coefficient_value。注意,由于这个惩罚项只在训练时添加,所以这个网络的训练损失会比测试损失大很多。

下图显示了 L2 正则化惩罚的影响。如图所示,即使两个模型的参数个数相同,具有 L2 正则化的模型更不容易过拟合。

我们还可以用 Keras 中以下这些权重正则化项来代替 L2 正则化。

from keras import regularizers

# L1正则化
regularizers.l1(0.001)

# 同时做L1和L2正则化
regularizers.l1_l2(l1=0.001, l2=0.001)

添加 dropout 正则化

dropout 是神经网络最有效也最常用的正则化方法之一。对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为 0)。dropout 比率 (dropout rate) 是被设为 0 的特征所占的比例,通常在 0.2~0.5 范围内。测试时没有单元被舍弃,而该层的输出值需要按 dropout 比率缩小,因为这时比训练时有更多的单元被激活,需要加以平衡。

假设有一个包含某层输出的 Numpy 矩阵 layer_output,其形状为 (batch_size, features)。训练时,我们随机将矩阵中一部分值设为 0。

# 训练时,舍弃 50%的输出单元
layer_output *= np.random.randint(0, high=2, size=layer_output.shape) 

测试时,我们将输出按 dropout 比率缩小。这里我们乘以 0.5(因为前面舍弃了一半的单元)。

# 测试时
layer_output *= 0.5

为了实现这一过程,还可以让两个运算都在训练时进行,而测试时输出保持不变。这通常也是实践中的实现方式:

# 训练时
layer_output *= np.random.randint(0, high=2, size=layer_output.shape)
layer_output /= 0.5   # 注意,是成比例放大而不是成比例缩小

dropout 的核心思想是在层的输出值中引入噪声,打破不显著的偶然模式,从而降低过拟合。如果没有噪声的话,网络将会记住这些偶然模式。

在 Keras 中,可以通过 Dropout 层向网络中引入 dropout,dropout 将被应用于前面一层的输出。

model.add(layers.Dropout(0.5))

我们向 IMDB 网络中添加两个 Dropout 层,来看一下它们降低过拟合的效果。

# 向IMDB 网络中添加 dropout
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

下图给出了结果的图示,可以看到,这种方法的性能相比参考网络有明显提高。

总结

防止神经网络过拟合的常用方法包括:

  • 获取更多的训练数据
  • 减小网络容量
  • 添加权重正则化
  • 添加 dropout

机器学习的通用工作流程

下面我们介绍一种可用于解决任何机器学习问题的通用模板。

定义问题,收集数据集

首先,我们必须定义所面对的问题。

  • 输入数据是什么?你要预测什么?只有拥有可用的训练数据,才能学习预测某件事情。
  • 面对的是什么类型的问题?是二分类问题、多分类问题、标量回归问题、向量回归问题,还是多分类、多标签问题?或者是其他问题,比如聚类、生成或强化学习?确定问题类型有助于选择模型架构、损失函数等。

注意我们在这一阶段所做的假设。

  • 假设输出是可以根据输入进行预测的。
  • 假设可用数据包含足够多的信息,足以学习输入和输出之间的关系。

在开发出工作模型之前,这些只是假设。我们收集了包含输入 $X$ 和目标 $Y$ 的很多样例,并不意味着 $X$ 包含足够多的信息来预测 $Y$。例如,想根据某支股票最近的历史价格来预测其股价走势,那么成功的可能性不大,因为历史价格并没有包含很多可用于预测的信息。

还有一类无法解决的问题,被称为非平稳问题 (nonstationary problem),即建模的对象会随着时间推移而改变。在这种情况下需要不断地利用最新数据重新训练模型,或者在一个问题是平稳的时间尺度上收集数据。

机器学习只能用来记忆训练数据中存在的模式。在过去的数据上训练机器学习来预测未来,这里存在一个假设,就是未来的规律与过去相同。但事实往往并非如此。

选择衡量成功的指标

要取得成功,就必须给出成功的定义:精度?准确率 (precision) 和召回率 (recall)?客户保留率?衡量成功的指标将指引你选择损失函数,即模型要优化什么。它应该直接与你的目标(如业务成功)保持一致。

对于平衡分类问题(每个类别的可能性相同),精度和接收者操作特征曲线下面积 (ROC AUC)是常用的指标。对于类别不平衡的问题,可以使用准确率和召回率。对于排序问题或多标签分类,可以使用平均准确率均值 (mean average precision)。自定义衡量成功的指标也很常见。

确定评估方法

一旦明确了目标,还必须确定如何衡量当前的进展。前面介绍了三种常见的评估方法。

  • 留出验证集。数据量很大时可以采用这种方法。
  • $K$ 折交叉验证。如果留出验证的样本量太少,无法保证可靠性,那么应该选择这种方法。
  • 重复的 $K$ 折验证。如果可用的数据很少,同时模型评估又需要非常准确,那么应该使用这种方法。

只需选择三者之一。大多数情况下,第一种方法足以满足要求。

准备数据

一旦知道了要训练什么、要优化什么以及评估方法,那么就几乎已经准备好训练模型了。但首先我们应该将数据格式化,使其可以输入到机器学习模型中(这里假设模型为深度神经网络)。

  • 如前所述,应该将数据格式化为张量。
  • 这些张量的取值通常应该缩放为较小的值,比如在 $[-1, 1]$ 区间或 $[0, 1]$ 区间。
  • 如果不同的特征具有不同的取值范围(异质数据),那么应该做数据标准化。
  • 可能需要做特征工程,尤其是对于小数据问题。

准备好输入数据和目标数据的张量后,我们就可以开始训练模型了。

开发比基准更好的模型

这一阶段的目标是获得统计功效 (statistical power),即开发一个小型模型,它能够打败纯随机的基准 (dumb baseline)。

如果你尝试了多种合理架构之后仍然无法打败随机基准,那么原因可能是问题的答案并不在输入数据中。要记住我们所做的两个假设:

  • 假设输出是可以根据输入进行预测的。
  • 假设可用的数据包含足够多的信息,足以学习输入和输出之间的关系。

这些假设很可能是错误的,这样的话就需要从头重新开始。

如果一切顺利,我们还需要选择三个关键参数来构建第一个工作模型:

  • 最后一层的激活。它对网络输出进行有效的限制。
  • 损失函数。它应该匹配你要解决的问题的类型。
  • 优化配置。使用哪种优化器?学习率是多少?大多数情况下,使用 rmsprop 及其默认的学习率是稳妥的。

注意,有时难以将衡量问题成功的指标转化为损失函数,因为损失函数需要在只有小批量数据时即可计算(即使只有一个数据点时,损失函数应该也是可计算的),而且还必须是可微的(否则无法用反向传播来训练网络)。例如,广泛使用的分类指标 ROC AUC 就不能被直接优化,常见的做法是优化替代指标,比如交叉熵。

下表列出了常见问题类型的最后一层激活和损失函数:

问题类型 最后一层激活 损失函数
二分类问题 sigmoid binary_crossentropy
多分类、单标签问题 softmax categorical_crossentropy
多分类、多标签问题 sigmoid binary_crossentropy
回归到任意值 mse
回归到 0~1 范围内的值 sigmoid mse 或 binary_crossentropy

扩大模型规模:开发过拟合的模型

一旦得到了具有统计功效的模型,问题就变成了:模型是否足够强大?它是否具有足够多的层和参数来对问题进行建模?机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上。为了找到这条界线,我们必须穿过它。

要搞清楚我们需要多大的模型,就必须开发一个过拟合的模型,这很简单。

  1. 添加更多的层。
  2. 让每一层变得更大。
  3. 训练更多的轮次。

要始终监控训练损失和验证损失,如果模型在验证数据上的性能开始下降,那么就出现了过拟合。

下一阶段将开始正则化和调节模型,以便尽可能地接近理想模型,既不过拟合也不欠拟合。

模型正则化与调节超参数

这一步是最费时间的:我们需要不断地调节模型、训练、在验证数据上评估(不是测试数据)、再次调节模型,然后重复这一过程,直到模型达到最佳性能。

  • 添加 dropout。
  • 尝试不同的架构:增加或减少层数。
  • 添加 L1 和 / 或 L2 正则化。
  • 尝试不同的超参数,以找到最佳配置。
  • (可选)反复做特征工程:添加新特征或删除没有信息量的特征。

请注意:每次使用验证集的反馈来调节模型,都会将有关验证集的信息泄露到模型中。如果只重复几次,那么无关紧要;但如果系统性地迭代许多次,最终会导致模型对验证集过拟合(即使模型并没有直接在验证数据上训练)。这会降低验证过程的可靠性。

一旦调节出满意的模型配置,就可以在所有可用数据(训练数据 + 验证数据)上训练最终的生产模型,然后在测试集上最后评估一次。

如果测试集上的性能比验证集上差很多,可能意味着验证流程不可靠,或者在调节模型参数时在验证数据上出现了过拟合。这时可能需要换用更加可靠的评估方法,比如重复的 $K$ 折验证。

该文是《Python深度学习》的阅读笔记,内容摘自原书,部分内容有修改。