机器学习入门系列(2)–如何构建一个完整的机器学习项目,第十一篇!
该系列的前 10 篇文章:
上一篇文章介绍了性能评估标准,但如何进行模型评估呢,如何对数据集进行划分出训练集、验证集和测试集呢?如何应对可能的过拟合和欠拟合问题,还有超参数的调优,如何更好更快找到最优的参数呢?
本文会一一介绍上述的问题和解决方法。
常用的对模型泛化能力的评估方法有以下几种,主要区别就是如何划分测试集。
k-fold
交叉验证(Cross Validation)留出法是最简单也是最直接的验证方法,它就是将数据集随机划分为两个互斥的集合,即训练集和测试集,比如按照 7:3 的比例划分,70% 的数据作为训练集,30% 的数据作为测试集。也可以划分为三个互斥的集合,此时就增加一个验证集,用于调试参数和选择模型。
直接采用 sklearn
库的 train_test_split
函数即可实现,一个简单的示例代码如下,这里简单调用 knn
算法,采用 Iris
数据集。
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
# 加载 Iris 数据集
dataset = load_iris()
# 划分训练集和测试集
(trainX, testX, trainY, testY) = train_test_split(dataset.data, dataset.target, random_state=3, test_size=0.3)
# 建立模型
knn = KNeighborsClassifier()
# 训练模型
knn.fit(trainX, trainY)
# 将准确率打印
print('hold_out, score:', knn.score(testX, testY))
留出法的使用需要注意:
数据集的划分要尽可能保持数据分布的一致性,避免因为数据划分过程引入额外的偏差而对最终结果产生影响。比如训练、验证和测试集的类别比例差别很大,则误差估计将由于三个集合数据分布的差异而产生偏差。
因此,分类任务中必须保持每个集合中的类别比例相似。从采样的角度看数据集的划分过程,这种保留类别比例的采样方式称为“分层采样”。
即便确定了训练、验证、测试集的比例,还是有多种划分方式,比如排序后划分、随机划分等等,这些不同的划分方式导致单次留出法得到的估计结果往往不够稳定可靠。因此,使用留出法的时候,往往采用若干次随机划分、重复进行实验后,取平均值作为最终评估结果。
分层采样的简单代码实现如下所示,主要是调用了 sklearn.model_selection
中的 StratifiedKFold
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
def StratifiedKFold_method(n_splits=3):
'''
分层采样
:return:
'''
# 加载 Iris 数据集
dataset = load_iris()
data = dataset.data
label = dataset.target
# 建立模型
knn = KNeighborsClassifier()
print('use StratifiedKFold')
skfolds = StratifiedKFold(n_splits=n_splits, random_state=42)
scores = 0.
for train_index, test_index in skfolds.split(data, label):
clone_clf = clone(knn)
X_train_folds = data[train_index]
y_train_folds = (label[train_index])
X_test_fold = data[test_index]
y_test_fold = (label[test_index])
clone_clf.fit(X_train_folds, y_train_folds)
y_pred = clone_clf.predict(X_test_fold)
n_correct = sum(y_pred == y_test_fold)
print(n_correct / len(y_pred))
scores += n_correct / len(y_pred)
print('mean scores:', scores / n_splits)
留出法也存在以下的缺点:
2/3 ~ 4/5
的样本作为训练集,剩余的作为验证集和测试集。k-fold
交叉验证(Cross Validation)k-fold
交叉验证 的工作流程:
k
个大小相等且互斥的子集;k-1
个子集作为训练集,剩余作为验证集进行模型的训练和评估,重复 k
次(每次采用不同子集作为验证集);k
次实验评估指标的平均值作为最终的评估结果。通常,k
取 10。
但和留出法类似,同样存在多种划分 k
个子集的方法,所以依然需要随时使用不同方式划分 p
次,每次得到 k
个子集。
同样,采用 sklearn.cross_validation
的 cross_val_score
库可以快速实现 k-fold
交叉验证法,示例如下:
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cross_validation import cross_val_score
# 加载 Iris 数据集
dataset = load_iris()
data = dataset.data
label = dataset.target
# 建立模型
knn = KNeighborsClassifier()
# 使用K折交叉验证模块
scores = cross_val_score(knn, data, label, cv=10, scoring='accuracy')
# 将每次的预测准确率打印出
print(scores)
# 将预测准确平均率打印出
print(scores.mean())
留一法是 k-fold
交叉验证的一个特例情况,即让 k=N
, 其中 N
是原始数据集的样本数量,这样每个子集就只有一个样本,这就是留一法。
留一法的优点就是训练数据更接近原始数据集了,仅仅相差一个样本而已,通过这种方法训练的模型,几乎可以认为就是在原始数据集上训练得到的模型 。
但缺点也比较明显,计算速度会大大降低,特别是原始数据集非常大的时候,训练 N
个模型的计算量和计算时间都很大,因此一般实际应用中很少采用这种方法。
在留出法和 k-fold
交叉验证法中,由于保留了一部分样本用于测试,因此实际训练模型使用的训练集比初始数据集小,这必然会引入一些因为训练样本规模不同而导致的估计偏差。
留一法受训练样本规模变化的影响较小,但是计算复杂度太高。
自助法是一个以自助采样法(bootstrap sampling
)为基础的比较好的解决方案。同时,它也是随机森林算法中用到的方法。
它的做法就是对样本数量为 N
的数据集进行 N
次有放回的随机采样,得到一个大小是 N
的训练集。
在这个过程中将会有一部分数据是没有被采样得到的,一个样本始终没有被采样出来的概率是 ( 1 − 1 N ) N (1-\frac{1}{N})^N (1−N1)N,根据极限可以计算得到:
l i m N → + ∞ ( 1 − 1 N ) N = 1 e ≈ 0.368 lim_{N\rightarrow +\infty}(1-\frac{1}{N})^N=\frac{1}{e}\approx 0.368 limN→+∞(1−N1)N=e1≈0.368
也就是采用自助法,会有 36.8% 的样本不会出现在训练集中,使用这部分样本作为测试集。这种方法也被称为包外估计。
自助法的优点有:
在数据集比较小、难以有效划分训练/测试集时很有用:
能从初始数据集中产生多个不同的训练集,这对集成学习等方法而言有很大好处。
但也存在如下缺点:
简单介绍下训练集、验证集和测试集各自的作用:
验证集和测试集的对比:
那么一般如何选择划分训练、验证和测试集的比例呢?通常可以按照如下做法:
k-fold
交叉验证中:先将所有数据拆分成 k
份,然后其中 1
份作为测试集,其他 k-1
份作为训练集。
k-fold
交叉的原因是:样本集太小。如果选择一部分数据来训练,则有两个问题:
k-fold
交叉让所有的数据参与训练,会使得这种偏离得到一定程度的修正。k
-fold 交叉让所有数据参与训练,会一定程度上缓解过拟合。深度学习时代,经常会发生:训练集和验证集、测试集的数据分布不同。
如:训练集的数据可能是从网上下载的高清图片,测试集的数据可能是用户上传的、低像素的手机照片。
如果发生了数据不匹配问题,则可以想办法让训练集的分布更接近验证集。
当训练集和验证集、测试集的数据分布不同时,有以下经验原则:
确保验证集和测试集的数据来自同一分布。
因为需要使用验证集来优化超参数,而优化的最终目标是希望模型在测试集上表现更好。
确保验证集和测试集能够反映未来得到的数据,或者最关注的数据。
确保数据被随机分配到验证集和测试集上。
当训练集和验证集、测试集的数据分布不同时,分析偏差和方差的方式有所不同。
如果训练集和验证集的分布一致,那么当训练误差和验证误差相差较大时,我们认为存在很大的方差问题。
如果训练集和验证集的分布不一致,那么当训练误差和验证误差相差较大时,有两种原因:
为了弄清楚原因,需要将训练集再随机划分为:训练-训练集
、训练-验证集
。这时候,训练-训练集
、训练-验证集
是同一分布的。
训练-训练集
和 训练-验证集
上的误差的差距代表了模型的方差。训练-验证集
和 验证集上的误差的差距代表了数据不匹配问题的程度。机器学习的两个主要挑战是过拟合和欠拟合。
过拟合(overfitting):指算法模型在训练集上的性能非常好,但是泛化能力很差,泛化误差很大,即在测试集上的效果却很糟糕的情况。
NP
难甚至更难的,而有效的学习算法必然是在多项式时间内运行完成。如果可以避免过拟合,这就意味着构造性的证明了 P=NP
。欠拟合(underfitting):模型的性能非常差,在训练数据和测试数据上的性能都不好,训练误差和泛化误差都很大。其原因就是模型的学习能力比较差。
一般可以通过挑战模型的容量来缓解过拟合和欠拟合问题。模型的容量是指其拟合各种函数的能力。
一般解决过拟合的方法有:
bagging
、boosting
、dropout
(深度学习中的方法)等;解决欠拟合的方法有:
超参数调优是一件非常头疼的事情,很多时候都需要一些先验知识来选择合理的参数值,但如果没有这部分先验知识,要找到最优的参数值是很困难,非常耗费时间和精力。但超参数调优确实又可以让模型性能变得更加的好。
在选择超参数调优算法前,需要明确以下几个要素:
常用的几种超参数搜索策略如下:
手动选择超参数需要了解超参数做了些什么,以及机器学习模型如何才能取得良好的泛化。
手动搜索超参数的任务是:在给定运行时间和内存预算范围的条件下,最小化泛化误差。
手动调整超参数时不要忘记最终目标:提升测试集性能。
加入正则化只是实现这个目标的一种方法。
如果训练误差很低,也可以通过收集更多的训练数据来减少泛化误差。
如果训练误差太大,则收集更多的训练数据就没有意义。
实践中的一种暴力方法是:不断提高模型容量和训练集的大小。
这种方法增加了计算代价,只有在拥有充足的计算资源时才可行。
网格搜索可能是最简单也是应用最广泛的超参数搜索算法了。它的几种做法如下:
网格搜索也可以借助 sklearn
实现,简单的示例代码如下:
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
param_grid = [
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error')
grid_search.fit(data, labels)
随机搜索是一种可以替代网格搜索的方法,它编程简单、使用方便、能更快收敛到超参数的良好取值。
随机搜索的优点如下:
随机搜索比网格搜索更快的找到良好超参数的原因是:没有浪费的实验。
m
)的值,而其他超参数的值保持不变。如果这个超参数 m
的值对于验证集误差没有明显区别,那么网格搜索相当于进行了两个重复的实验。m
超参数与泛化误差无关,那么不同的 m
值:
m
值、相同的其他超参数值,会导致大量的重复实验。随机搜索可以采用 sklearn.model_selection
中的 RandomizedSearchCV
方法。
贝叶斯优化方法是基于模型的参数搜索算法的一种比较常见的算法。它相比于前面的网格搜索和随机搜索,最大的不同就是利用历史的搜索结果进行优化搜索。主要是由四部分组成的:
贝叶斯优化算法的步骤如下:
需要特别注意的是,贝叶斯优化算法容易陷入局部最优值:它在找到一个局部最优值后,会不断在该区域进行采样。
因此,贝叶斯优化算法会在探索和利用之间找到一个平衡点,探索是在还未取样的区域获取采样点,利用则是根据后验分布在最可能出现全局最优的区域进行采样。
通常先对超参数进行粗调,然后在粗调中表现良好的超参数区域进行精调。
超参数随机搜索,并不意味着是在有效范围内随机均匀取值。需要选择合适的缩放来进行随机选取。
通常情况下,建议至少每隔几个月重新评估或者修改超参数。因为随着时间的变化,真实场景的数据会逐渐发生改变:
由于这些变化,原来设定的超参数可能不再适用。
有两种超参数调整策略:
如果数据足够大且没有足够的计算资源,此时只能一次完成一个试验。
则可以每天观察模型的表现,实时的、动态的调整超参数。
如果数据不大,有足够的计算资源可以同一时间完成大量的试验,则可以设置多组超参数设定,然后选择其中表现最好的那个。
关于模型评估方面的内容就介绍这么多,文章有些长,而且内容也比较多。
关于如何构建一个机器学习项目的内容,基本到本文就介绍完毕了,从开始的评估问题,获取数据,到数据预处理、特征工程,然后就是各种常见机器学习算法的评估,最后就是模型评估部分的内容了。
当然了,本系列的文章还是偏向于理论,代码比较少,主要也是整理和总结书本以及网上文章的知识点。
所以下一篇文章会是介绍一篇手把手教你运用机器学习算法来做分类的文章,来自国外一个大神的博客文章,主要是面向机器学习的初学者。
参考:
欢迎关注我的微信公众号–机器学习与计算机视觉,或者扫描下方的二维码,大家一起交流,学习和进步!