机器学习模型评估和超参数调优实践
在这篇文章中,我们将详细探讨如何评估机器学习模型结果,以及优化超参数的最佳实践。
为此,我们将构建一个图像识别模型。我们将展示如何计算度量以评估机器学习模型的质量和一些超参数的优化技术。
模型评估
机器学习模型在准备完成预期任务之前必须经过一个学习和训练的过程。这种训练将使它能够从实际数据中归纳和派生模式,但是我们如何评估我们的模型是否提供了数据的良好表示呢?我们如何验证模型,并预测它将如何处理以前没有见过的数据呢?
在评估数据模型时,准确度是最常用的度量标准。它可以大致了解有多少数据样本被错误分类,但是这些信息可能具有欺骗性,并且可能会给我们带来虚假的安全感。
通常,您将数据拆分为训练集和测试集。您将在训练集上训练模型,并在测试集上测试模型时测量准确度。这是评估模型性能的最快方法,但它并不是最好的方法。
偏差 - 方差权衡
尽管可以使用许多不同的度量标准来度量模型的性能,但是将偏差和方差保持在低水平始终是至关重要的。我们将偏差定义为模型输出与“真实”值之间的任何系统性差异。方差是指模型的统计极限(见图)。
如果模型过度训练,或者对于给定的训练数据集过于复杂,它将记住模式,甚至输入噪声。在这种情况下,我们会有很高的方差(过度拟合),模型在不可见数据的情况下会表现得很差。
在相反的场景中,机器学习模型将执行得很差,并且在训练和测试数据上产生类似的错误。在这种情况下,我们将看到高偏差(欠拟合)。模型将过于死板,并且没有足够的特征来完全表示数据。
理想情况下,我们希望实现低偏差和低方差。我们的目标是模型预测与训练数据中看到的值非常接近或相同。不幸的是,减少一项的努力往往会增加另一项。我们必须找到一个折衷方案。偏差和方差之间的这种平衡称为偏差-方差权衡。
虽然可能不总是能够找到足够的数据来防止过度拟合,或者确切地知道模型应该有多复杂,但是将训练和测试准确度绘制成训练样本数量的函数可能会有所帮助。
K-folod交叉验证
在本节中,我们将使用Keras包装一个神经网络,并利用sklearn运行一个K-fold交叉验证。对于神经网络,我们将使用LeNet结构。它是最早著名的深度卷积架构之一,它的代码编写相当简单,而且计算成本也不高。该架构由两组卷积和subsampling (也称为池化)层组成,然后是一个flattening 卷积层,然后是两个dense (全连接)层。
K-fold交叉验证是一种最常用的方法,用于确认对数据的估计假设,并评估模型执行的准确性及其泛化能力。在k-fold交叉验证中,您可以将训练数据随机分割成“k”个大小相同的folds。在每个迭代中,其中一个fold用于性能评估,其余的用于训练。这个过程执行“k”次,以便我们获得“k”个模型和性能估计。
统计上,通过K-fold交叉验证测量的平均性能可以正确估计模型在一般情况下完成任务的程度。
交叉验证编码
首先,我们将从Keras导入数据。我们将使用Fashion-MNIST数据集作为训练集。Fashion-MNIST是一个由Zalando的产品图片组成的数据集。拥有6万个样本的训练集和1万个样本的测试集。这些集合中的每个图像都是28×28像素的。
在将数据传递给我们的机器学习模型之前,我们必须声明通道的数量(也称为图像的深度)并将样本reshape 为60,000 x [1,28,28]以满足卷积要求。请注意,数据集由黑白图像组成。因此,我们只有一个通道。
from keras.datasets import fashion_mnist import numpy as np (x_train, y_train), (x_test, y_test) = fashion_mnist.load_data() x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train = x_train/255.0 x_test = x_test/255.0 x_train = x_train[:, np.newaxis, :, :] x_test = x_test[:, np.newaxis, :, :]
在接下来的步骤中,我们为卷积神经网络(LeNet)定义一个类:
from keras.layers.convolutional import Conv2D from keras.layers.convolutional import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Flatten from keras.layers.core import Dense from keras import optimizers from keras.wrappers.scikit_learn import KerasClassifier class LeNet: @staticmethod def build(input_shape, classes, learning_rate=0.0006): optimizer = optimizers.Adam(lr=learning_rate) model = Sequential() # CONV -> RELU -> POOL model.add(Conv2D(20, kernel_size=5, padding='same', input_shape=input_shape)) model.add(Activation('relu')) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), dim_ordering="th")) # CONV -> RELU -> POOL model.add(Conv2D(50, kernel_size=5, border_mode='same')) model.add(Activation('relu')) model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2), dim_ordering="th")) # Flatten -> RELU layers model.add(Flatten()) model.add(Dense(500)) model.add(Activation('relu')) # a softmax classifier model.add(Dense(classes)) model.add(Activation('softmax')) model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["accuracy"]) return model classifier = KerasClassifier(build_fn=LeNet.build, epochs=20, batch_size=25, input_shape=(1, 28, 28), classes=np.unique(y_train, axis=0).shape[0])
要执行交叉验证,我们将从Sklearn导入函数'cross_val_score'。此函数将分类器,样本(X),标签(y)和folds数(cv)作为输入:
from sklearn.model_selection import cross_val_score accuracies = cross_val_score(estimator=classifier, X=x_train, y=y_train, cv=5) print('Test Accuracies : {0}'.format([round(accuracy, 8) for accuracy in accuracies])) print('Mean Accuracy : {0:.8f}'.format(accuracies.mean())) print('Standard Deviation : {0:.8f}'.format(accuracies.std()))
运行交叉验证后,该函数返回五个folds的准确度列表。为了了解它的平均表现,我们看一下均值和标准差:
附注:此度量衡量的是正确分类的数据样本的百分比,或者更准确地说,是正确预测样本的比例。在背后,Keras对每个样本进行分类,产生具有每个类概率的向量,并选择最高值作为模型预测。最后,Keras将预测与真实值进行比较。
经过5次交叉验证,提取平均准确度和标准差,我们对模型的性能和平均鲁棒性有了更准确的评估。可以看出,该分类器的准确率平均达到90%。该值在迭代之间波动,标准差约为0.4%。我们可以得出这样的结论,我们的方差很低,偏差也相对较小。
超参数优化
在创建神经网络时,我们有两种类型的参数。在训练期间学习的参数,例如权重,以及将分别进行硬编码和优化的参数。第二类参数称为超参数。例如dropout rate,学习率,epochs数,批量大小,优化器,层数,节点数等。
微调超参数可能会提高预测,但没有规则可以告诉您要在神经网络上使用多少层、多少个epochs或批大小。
寻找最优解(甚至是次最优解)通常需要反复训练模型的不同版本以适应不同的超参数集。然而,仍然有一些超参数优化技术。最简单的方法之一是网格搜索。
网格搜索
网格搜索是一种用于识别模型的最佳超参数集的流行方法。这是一种强力搜索方法,它为我们想要调整的每个超参数获取一组可能的值。机器评估每种组合的性能。最后,它将返回具有最佳性能的选择。
这是一种耗时的方法,但由于我们可以为超参数提供多个局部极小值和可接受的解决方案(而且很容易用一组次优的组合结束工作),网格搜索可能会提高模型的性能。在实际场景中,我们首先为每个超参数设置一组广泛的值。在确定要搜索的值和区域之后,可以缩小网格搜索的范围。
网格搜索示例
我们将继续使用之前使用的示例代码,但经过修改,我们可以为学习率,epochs和批量大小传递一组值。
尽管交叉验证(cv参数)使用的folds 数较低,而3个超参数使用的值也不多,但运行以下配置仍然需要一个多小时。我们建议将n_jobs设置为-1,以便在所有处理器上并行运行网格搜索:
from sklearn.model_selection import GridSearchCV parameters = {'batch_size': [25, 50], 'epochs': [10, 20], 'learning_rate': [0.0006, 0.0003]} grid_search = GridSearchCV(estimator=classifier, param_grid=parameters, scoring='accuracy', cv=3, verbose=2, n_jobs=-1) grid_search = grid_search.fit(x_train, y_train) mean_test_scores = grid_search.cv_results_['mean_test_score'] std_test_scores = grid_search.cv_results_['std_test_score'] mean_train_scores = grid_search.cv_results_['mean_train_score'] std_train_scores = grid_search.cv_results_['std_train_score'] parameters = grid_search.cv_results_['params'] for mean_test, std_test, mean_train, std_train, params in zip(mean_test_scores, std_test_scores, mean_train_scores, std_train_scores, parameters): print('Parameters : {}'.format(params)) print('Mean test score: {:.8f}'.format(mean_test)) print('Standard Deviation (test) : {:.8f}'.format(std_test)) print('Mean train score: {:.8f}'.format(mean_train)) print('Standard Deviation (train) : {:.8f}'.format(std_train)) print() print('Best parameters : {}'.format(grid_search.best_params_)) print('Best accuracy score : {:.8f}'.format(grid_search.best_score_))
运行网格搜索后,我们可以得到参数的最佳组合,最佳得分,以及每个组合结果的字典:
最后,最好的组合结果是我们以前用于运行交叉验证的组合。
结论
在本文中,我们介绍了如何评估机器学习模型以及如何微调其超参数的一些基本思路。
我们探索了一种非常常见的模型评估策略,即k折叠交叉验证,通过平均单个模型性能,可以确定模型是否存在高偏差或高方差。
我们还探索了如何通过应用网格搜索来搜索正确的超参数集。
关键要点
- 在训练和测试数据集中测量的准确度是评估模型时最常用的最快度量标准,但它并不是最好的。
- 无论您使用何种衡量性能的指标,都需要确保尽可能降低偏差和差异。
- 如果模型过度训练或过于复杂,您将看到高方差。如果模型太不灵活并且在训练和测试数据中产生类似的错误,您将看到高偏差。
- 尝试减少一个通常会增加另一个。需要折衷,称为偏差 - 方差权衡。
- 通过k-folds交叉验证测量的平均性能可以正确估计模型一般完成任务的程度。
- 神经网络有两种类型的参数; 在训练期间学到的,以及那些经过硬编码和单独优化的。后者是超参数。
- 优化超参数通常需要反复训练不同版本的模型到不同的超参数集。
- 网格搜索是超参数优化的最简单技术之一。