2019 年第 29 篇,总 53 篇文章 本文大约 9700 字,建议收藏阅读
机器学习入门系列(2)--如何构建一个完整的机器学习项目,
第十一篇!
该系列的前 10 篇文章:
上一篇文章介绍了性能评估标准,但如何进行模型评估呢,如何对数据集进行划分出训练集、验证集和测试集呢?如何应对可能的过拟合和欠拟合问题,还有超参数的调优,如何更好更快找到最优的参数呢?
本文会一一介绍上述的问题和解决方法。
1.泛化能力:指模型对未知的、新鲜的数据的预测能力,通常是根据测试误差来衡量模型的泛化能力,测试误差越小,模型能力越强;
2.统计理论表明:如果训练集和测试集中的样本都是独立同分布产生的,则有 模型的训练误差的期望等于模型的测试误差的期望 。
3.机器学习的“没有免费的午餐定理”表明:在所有可能的数据生成分布上,没有一个机器学习算法总是比其他的要好。
4.正则化是对学习算法做的一个修改,这种修改趋向于降低泛化误差(而不是降低训练误差)。
常用的对模型泛化能力的评估方法有以下几种,主要区别就是如何划分测试集。
留出法是最简单也是最直接的验证方法,它就是将数据集随机划分为两个互斥的集合,即训练集和测试集,比如按照 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
交叉验证 的工作流程:
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
的训练集。
在这个过程中将会有一部分数据是没有被采样得到的,一个样本始终没有被采样出来的概率是
,根据极限可以计算得到:
也就是采用自助法,会有 36.8% 的样本不会出现在训练集中,使用这部分样本作为测试集。这种方法也被称为包外估计。
自助法的优点有:
但也存在如下缺点:
简单介绍下训练集、验证集和测试集各自的作用:
验证集和测试集的对比:
那么一般如何选择划分训练、验证和测试集的比例呢?通常可以按照如下做法:
1.对于小批量数据,数据的拆分的常见比例为:
2.对于大批量数据,验证集和测试集占总数据的比例会更小。
3.在 k-fold
交叉验证中:先将所有数据拆分成 k
份,然后其中 1
份作为测试集,其他 k-1
份作为训练集。
深度学习时代,经常会发生:训练集和验证集、测试集的数据分布不同。
如:训练集的数据可能是从网上下载的高清图片,测试集的数据可能是用户上传的、低像素的手机照片。
如果发生了数据不匹配问题,则可以想办法让训练集的分布更接近验证集。
当训练集和验证集、测试集的数据分布不同时,有以下经验原则:
当训练集和验证集、测试集的数据分布不同时,分析偏差和方差的方式有所不同。
训练-训练集
、训练-验证集
。这时候,训练-训练集
、训练-验证集
是同一分布的。机器学习的两个主要挑战是过拟合和欠拟合。
过拟合(overfitting):指算法模型在训练集上的性能非常好,但是泛化能力很差,泛化误差很大,即在测试集上的效果却很糟糕的情况。
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
值:随机搜索可以采用 sklearn.model_selection
中的 RandomizedSearchCV
方法。
贝叶斯优化方法是基于模型的参数搜索算法的一种比较常见的算法。它相比于前面的网格搜索和随机搜索,最大的不同就是利用历史的搜索结果进行优化搜索。主要是由四部分组成的:
贝叶斯优化算法的步骤如下:
需要特别注意的是,贝叶斯优化算法容易陷入局部最优值:它在找到一个局部最优值后,会不断在该区域进行采样。
因此,贝叶斯优化算法会在探索和利用之间找到一个平衡点,探索是在还未取样的区域获取采样点,利用则是根据后验分布在最可能出现全局最优的区域进行采样。
1.通常先对超参数进行粗调,然后在粗调中表现良好的超参数区域进行精调。
2.超参数随机搜索,并不意味着是在有效范围内随机均匀取值。需要选择合适的缩放来进行随机选取。
3.通常情况下,建议至少每隔几个月重新评估或者修改超参数。因为随着时间的变化,真实场景的数据会逐渐发生改变:
4.有两种超参数调整策略:
关于模型评估方面的内容就介绍这么多,文章有些长,而且内容也比较多。
关于如何构建一个机器学习项目的内容,基本到本文就介绍完毕了,从开始的评估问题,获取数据,到数据预处理、特征工程,然后就是各种常见机器学习算法的评估,最后就是模型评估部分的内容了。
当然了,本系列的文章还是偏向于理论,代码比较少,主要也是整理和总结书本以及网上文章的知识点。
所以下一篇文章会是介绍一篇手把手教你运用机器学习算法来做分类的文章,来自国外一个大神的博客文章,主要是面向机器学习的初学者。
参考: