首先本章节解决这3个问题:
q1:训练集和测试集到底是什么关系?
q2:训练集愈来愈好,是不是代表模型越来越好?
q3:如何选取最优模型,如何调整模型中最优参数?
准备知识:
回答这3个问题,先了解泛化,或者说学习一个能够在前所未见的新数据上表现良好的模型。这就引出了欠拟合和过拟合的概念,前者是指一个模型无法获取训练数据中的所有变化,后者是指模型过分关注训练数据,但对新数据的泛化性能不好。以下要讨论几个经典模型在泛化问题上的优缺点:knn,线性,贝叶斯,决策树,支持向量机,随机森林(开始组合了和随机森林(RF)的变种梯度提升决策树(GBDT))、神经网络。
下面开始分述,从knn开始。第一次鸢尾花用的knn的k只用了k=1的情况,也就是只考虑了一个邻居,有时候我们调参会多考虑几个k。k考虑少了容易出错,就好像我们写一个论文,参考文献如果只有一份无法对问题理解清楚,k太多,也把握不了方向,耗时也比较高,计算复杂度比较高。所以我们比较一下不同的k对knn的准度影响。
(敲黑白!)比较前,要装一个分析库mglearn(有时候会出状况,多装几次)mglearn库,该库集成了sklearn和数据的许多操作方法,很便捷,获取对应数据。这个库中的plots模块内有许多已经做好的数据视图。方便查看,帮助你省略了构建的过程。
当我们利用mglearn调用k不同取值时(就是看旁边有多少点,他们是什么,你就是什么,他们是朱,你就是朱,他们是赤,你就是赤),可以看到k=1,圆圈判断为五角星,而k=3,圆圈判断为三角。
一般都以为KNN只能用于分类,但是KNN也可以用于回归。
KNN的优点、缺点和参数
一般来说,KNeighbors 分类器有 2 个重要参数:邻居个数与数据点之间距离的度量方法。在实践中,使用较小的邻居个数(比如 3 个或 5 个)往往可以得到比较好的结果,但你应该调节这个参数。选择合适的距离度量方法超出了本书的范围。默认使用欧式距离,它在许多情况下的效果都很好。
KNN 的优点之一就是模型很容易理解,通常不需要过多调节就可以得到不错的性能。在考虑使用更高级的技术之前,尝试此算法是一种很好的基准方法。构建最近邻模型的速度通常很快,但如果训练集很大(特征数很多或者样本数很大),预测速度可能会比较慢。使用 KNN 算法时,对数据进行预处理是很重要的。这一算法对于有很多特征(几百或更多)的数据集往往效果不好,对于大多数特征的大多数取值都为 0 的数据集(所谓的稀疏数据集)来说,这一算法的效果尤其不好。
虽然 k 近邻算法很容易理解,但由于预测速度慢且不能处理具有很多特征的数据集,所以在实践中往往不会用到。下面介绍的这种方法就没有这两个缺点。
线性回归:又名普通最小二乘法(ordinary least squares,OLS),是回归问题最简单也最经典的线性方法。线性回归寻找参数 w 和 b,使得对训练集的预测值与真实的回归目标值 y 之间的均方误差最小。均方误差(mean squared error)是预测值与真实值之差的平方和除以样本数。线性回归没有参数,这是一个优点,但也因此无法控制模型的复杂度。
L2优化:机器学习中,如果参数过多,模型过于复杂,容易造成过拟合(overfit)。即模型在训练样本数据上表现的很好,但在实际测试样本上表现的较差,不具备良好的泛化能力。为了避免过拟合,最常用的一种方法是使用使用正则化,例如 L1 和 L2 正则化。对OLS上做了L2优化的叫Ridge regression。
L1 正则化公式也很简单,直接在原来的损失函数基础上加上权重参数的绝对值:
L2正则化公式直接在原来的损失函数基础上加上权重参数的平方和:
Ridge regression调整的参数,很明显看到测试集的准度上来了。
简单性和训练集性能二者对于模型的重要程度可以由用户通过设置 alpha 参数来指定。在前面的例子中,我们用的是默认参数 alpha=1.0。但没有理由认为这会给出最佳权衡。alpha 的最佳设定值取决于用到的具体数据集。增大 alpha 会使得系数更加趋向于 0,从而降低训练集性能,但可能会提高泛化性能。两个模型进行评估(将模型性能作为数据集大小的函数进行绘图,这样的图像叫作学习曲线):
下面我们在看看另外一种线性回归Lasso,只用到了 105 个特征中的 4 个在训练集与测试集上的表现都很差。这表示存在欠拟合,Lasso 也有一个正则化参数 alpha,可以控制系数趋向于 0 的强度。在上一个例子中,我们用的是默认值 alpha=1.0。为了降低欠拟合,我们尝试减小 alpha。这么做的同时,我们还需要增加 max_iter 的值(运行迭代的最大次数,这个特重要)
中间的结果最好,但如果把 alpha 设得太小,那么就会消除正则化的效果,并出现过拟合,最下的结果次之。
好,现在来回答第一个问题:训练集和测试集到底是什么关系?
为了了解一个模型对新样本的泛化能力,唯一的办法是:让已经训练好的模型真正的处理新的样本。
解决方法: 将原始数据划分成两个部分:训练集 和测试集。可以使用训练集来训练模型,然后用测试集来测试模型。通过测试集来评估模型,可以了解模型的泛化误差。
如果训练误差很低(模型在训练集上的错分样本比率,就是训练集结果效果很好),但是泛化误差很高(西瓜书上的解释,学习器在训练集上的误差成为“训练误差”或“经验误差”。在新样本上的误差称为“泛化误差”,就是测试集效果不好),说明模型对于训练数据已经过拟合了。例如上图Lasso中第二个和第三个测试,训练误差很都不错,都有9成以上的准度。虽然版本2准度不如版本3高。但是版本2的泛化误差比版本3的泛化误差要低,在评价模型的时候,版本2更好一些。
现在来回答第二个问题:训练集越来越好,是不是代表模型越来越好?
我们在看一下mglearn中默认乳腺癌数据集 在LR逻辑回归上的不同参数表现。
敲黑板!那么现在有这3个值,到底C到底是哪一个更好呢?第一个trainning是0.953测试集是0.958好像很接近,初学者一定会觉得很好。在没有其他结果时候,也许很好,但由于训练集和测试集的性能非常接近,所以模型很可能是欠拟合的。我们尝试增大 C 来拟合一个更灵活的模型,发现调第二次和第三次的时候,第二次提高了,第三次开始降低了。特别是第三次,测试集开始比第二次降低了。对比下面这张图。说明第二次应该是最佳模型。
现在回答第三个问题:如何选取最优模型,如何调整模型中最优参数?
先说选模型的结论:
最近邻:适用于小型数据集,是很好的基准模型,很容易解释。(我看RecSys推荐系统顶会经常用kNN做benchmark)
线性模型:非常可靠的首选算法,适用于非常大的数据集,也适用于高维数据。
朴素贝叶斯:只适用于分类问题。比线性模型速度还快,适用于非常大的数据集和高维数据。精度通常要低于线性模型。
决策树:速度很快,不需要数据缩放,可以可视化,很容易解释。
随机森林:几乎总是比单棵决策树的表现要好,鲁棒性很好,非常强大。不需要数据缩放。不适用于高维稀疏数据。
梯度提升决策树:精度通常比随机森林略高。与随机森林相比,训练速度更慢,但预测速度更快,需要的内存也更少。比随机森林需要更多的参数调节。
支持向量机:对于特征含义相似的中等大小的数据集很强大。需要数据缩放,对参数敏感。
神经网络:可以构建非常复杂的模型,特别是对于大型数据集而言。对数据缩放敏感,对参数选取敏感。大型网络需要很长的训练时间。
面对新数据集,通常最好先从简单模型开始,比如线性模型、朴素贝叶斯或最近邻分类器,看能得到什么样的结果。对数据有了进一步了解之后,你可以考虑用于构建更复杂模型的算法,比如随机森林、梯度提升决策树、SVM 或神经网络。
下面结合本章主题“泛化”,来继续看看乳腺癌数据在上述几个算法中的提醒效果,是否如结论描述一致。
3.1 首先看看决策树,从下图看,还是之前的乳腺癌数据。训练集上的精度是 100%,这是因为叶结点都是纯的,树的深度很大,足以完美地记住训练数据的所有标签。测试集精度比之前讲过的线性模型略低,线性模型的精度约为 95%。肯定有人会惊奇,为什么会有100%的情况?这好比训练集是我们的模拟试卷,测试集是我们的高考。如果我把所有训练集都做了,准度是有可能到100%,因为不可能再错了。这就过拟合了,为什么?因为训练集做的很好,这个模型转移到测试集上不代表很好,如图,第一个测试集准度是0.937,第二个就到了0.951.
如果我们不限制决策树的深度,它的深度和复杂度都可以变得特别大。因此,未剪枝的树容易过拟合,对新数据的泛化性能不佳。现在我们将预剪枝应用在决策树上,这可以在完美拟合训练数据之前阻止树的展开。一种选择是在到达一定深度后停止树的展开。这里我们设置 max_depth=4,这意味着只可以连续问 4 个问题。结论:限制树的深度可以减少过拟合。这会降低训练集的精度,但可以提高测试集的精度。
很少有人单独用决策树,用的是集成决策树,那当然是最出门的GBDT,即梯度提升决策树(gradient boosted decision tree),随机森林(random forest)也不错,也是一个集成道理,但我们重点放在GBDT。
决策树的一个主要缺点在于经常对训练数据过拟合。随机森林是解决这个问题的一种方法。随机森林本质上是许多决策树的集合,其中每棵树都和其他树略有不同。随机森林背后的思想是,每棵树的预测可能都相对较好,但可能对部分数据过拟合。如果构造很多树,并且每棵树的预测都很好,但都以不同的方式过拟合,那么我们可以对这些树的结果取平均值来降低过拟合。既能减少过拟合又能保持树的预测能力,这可以在数学上严格证明。
GBDT,通过合并多个决策树来构建一个更为强大的模型。虽然名字中含有“回归”,但这个模型既可以用于回归也可以用于分类。与随机森林方法不同,梯度提升采用连续的方式构造树,每棵树都试图纠正前一棵树的错误。默认情况下,梯度提升回归树中没有随机化,而是用到了强预剪枝。梯度提升树通常使用深度很小(1 到 5 之间)的树,这样模型占用的内存更少,预测速度也更快。
梯度提升背后的主要思想是合并许多简单的模型(在这个语境中叫作弱学习器,adboost也是一个道理),比如深度较小的树。每棵树只能对部分数据做出好的预测,因此,添加的树越来越多,可以不断迭代提高性能。
梯度提升树经常是机器学习竞赛的优胜者,并且广泛应用于业界。与随机森林相比,它通常对参数设置更为敏感,但如果参数设置正确的话,模型精度更高。
上面是在乳腺癌数据集上应用 GradientBoostingClassifier 的示例。默认使用 100 棵树,最大深度是 3,学习率为 0.1:由于训练集精度达到 100%,所以很可能存在过拟合。为了降低过拟合,我们可以限制最大深度来加强预剪枝(红框),也可以降低学习率(红框)。降低模型复杂度的两种方法都降低了训练集精度,这和预期相同。在这个例子中,减小树的最大深度显著提升了模型性能,而降低学习率仅稍稍提高了泛化性能。
(敲黑板!)我们还可以利用一些有用的属性来总结树的工作原理。其中最常用的是特征重要性(feature importance),它为每个特征对树的决策的重要性进行排序。对于每个特征来说,它都是一个介于 0 和 1 之间的数字,其中 0 表示“根本没用到”,1 表示“完美预测目标值”。注意,梯度提升树的特征重要性与其他算法(随机森林、线性、knn)的特征重要性有些类似(可以自己测试一下),不过其他算法完全忽略了某些特征。
优点、缺点和参数。GBDT是监督学习中最强大也最常用的模型之一。其主要缺点是需要仔细调参,而且训练时间可能会比较长。与其他基于树的模型类似,这一算法不需要对数据进行缩放就可以表现得很好,而且也适用于二元特征与连续特征同时存在的数据集。与其他基于树的模型相同,它也通常不适用于高维稀疏数据。
梯度提升树模型的主要参数包括树的数量 n_estimators 和学习率 learning_rate,后者用于控制每棵树对前一棵树的错误的纠正强度。这两个参数高度相关,因为 learning_rate 越低,就需要更多的树来构建具有相似复杂度的模型。随机森林的 n_estimators 值总是越大越好,但梯度提升不同,增大 n_estimators 会导致模型更加复杂,进而可能导致过拟合。通常的做法是根据时间和内存的预算选择合适的 n_estimators,然后对不同的 learning_rate 进行遍历。
另一个重要参数是 max_depth(或 max_leaf_nodes),用于降低每棵树的复杂度。梯度提升模型的 max_depth 通常都设置得很小,一般不超过 5。
3.2 核支持向量机(kernelized support vector machine)核支持向量机(通常简称为 SVM)是可以推广到更复杂模型的扩展,这些模型无法被输入空间的超平面定义。虽然支持向量机可以同时用于分类和回归,但我们只会介绍用于分类的情况,它在 SVC 中实现。类似的概念也适用于支持向量回归,后者在 SVR 中实现。
SVM如何定义,这就不展开细说,很多网站和数据都有提及。这主要说说技巧。
(敲黑板!)向数据表示中添加非线性特征,可以让线性模型变得更强大。但是,通常来说我们并不知道要添加哪些特征,而且添加许多特征(比如 100 维特征空间所有可能的交互项)的计算开销可能会很大。幸运的是,有一种巧妙的数学技巧,让我们可以在更高维空间中学习分类器,而不用实际计算可能非常大的新的数据表示。这种技巧叫作核技巧(kernel trick),它的原理是直接计算扩展特征表示中数据点之间的距离(更准确地说是内积),而不用实际对扩展进行计算。
对于支持向量机,将数据映射到更高维空间中有两种常用的方法:一种是多项式核,在一定阶数内计算原始特征所有可能的多项式(比如 feature1 ** 2 * feature2 ** 5);另一种是径向基函数(radial basis function,RBF)核,也叫高斯核。高斯核有点难以解释,因为它对应无限维的特征空间。一种对高斯核的解释是它考虑所有阶数的所有可能的多项式,但阶数越高,特征的重要性越小。12
不过在实践中,核 SVM 背后的数学细节并不是很重要,可以简单地总结出使用 RBF 核 SVM 进行预测的方法。重要的调参,SVM有两个参数要调整:gamma 参数是用于控制高斯核的宽度。它决定了点与点之间“靠近”是指多大的距离。C 参数是正则化参数,与线性模型中用到的类似。它限制每个点的重要性。
这个模型在训练集上的分数十分完美,但在测试集上的精度只有 63%,存在相当严重的过拟合。虽然 SVM 的表现通常都很好,但它对参数的设定和数据的缩放非常敏感。特别地,它要求所有特征有相似的变化范围。
从这张图中,我们可以确定乳腺癌数据集的特征具有完全不同的数量级。这对其他模型来说(比如线性模型)可能是小问题,但对核 SVM 却有极大影响。解决这个问题的一种方法就是对每个特征进行缩放,使其大致都位于同一范围。核 SVM 常用的缩放方法就是将所有特征缩放到 0 和 1 之间。网上有很多自动“归一化的”处理的方法,这里先“手工”操作一下。
第一段,利用训练集的最小值和范围对测试集做相同的变换。第二段,数据缩放的作用很大!实际上模型现在处于欠拟合的状态,因为训练集和测试集的性能非常接近,但还没有接近 100% 的精度。从这里开始,我们可以尝试增大 C 或 gamma 来拟合更为复杂的模型。第三段,在这个例子中,增大 C 可以显著改进模型,得到 97.2% 的精度。
核支持向量机是非常强大的模型,在各种数据集上的表现都很好。SVM 允许决策边界很复杂,即使数据只有几个特征。它在低维数据和高维数据(即很少特征和很多特征)上的表现都很好,但对样本个数的缩放表现不好。在有多达 10 000 个样本的数据上运行 SVM 可能表现良好,但如果数据量达到 100 000 甚至更大,在运行时间和内存使用方面可能会面临挑战。
SVM 的另一个缺点是,预处理数据和调参都需要非常小心。这也是为什么如今很多应用中用的都是基于树的模型,比如随机森林或梯度提升(需要很少的预处理,甚至不需要预处理)。此外,SVM 模型很难检查,可能很难理解为什么会这么预测,而且也难以将模型向非专家进行解释。
不过 SVM 仍然是值得尝试的,特别是所有特征的测量单位相似(比如都是像素密度)而且范围也差不多时。
核 SVM 的重要参数是正则化参数 C、核的选择以及与核相关的参数。
3.3神经网络
隐层的多层感知(即神经网络)与Logistic 回归相比需要学习更多的系数(也叫作权重):在每个输入与每个隐单元(隐单元组成了隐层)之间有一个系数,在每个隐单元与输出之间也有一个系数。从数学的角度看,计算一系列加权求和与只计算一个加权求和是完全相同的,因此,为了让这个模型真正比线性模型更为强大,我们还需要一个技巧。在计算完每个隐单元的加权求和之后,对结果再应用一个非线性函数——通常是校正非线性(rectifying nonlinearity,也叫校正线性单元或 relu)。然后将这个函数的结果用于加权求和,有了这非线性函数,神经网络可以学习比线性模型复杂得多的函数。
我们用了另外一个sklearn的数据集moons,很明显神经网络学到的决策边界完全是非线性的,但相对平滑。我们用到了 solver='lbfgs'。
默认情况下,MLP 使用 100 个隐结点,这对于这个小型数据集来说已经相当多了。我们可以减少其数量(从而降低了模型复杂度),但仍然得到很好的结果
最后,我们还可以利用 L2 惩罚使权重趋向于 0,从而控制神经网络的复杂度,正如我们在岭回归和线性分类器中所做的那样。MLPClassifier 中调节 L2 惩罚的参数是 alpha(与线性回归模型中的相同),它的默认值很小(弱正则化)。图 2-52 显示了不同 alpha 值对 two_moons 数据集的影响,用的是 2 个隐层的神经网络,每层包含 10 个或 100 个单元,只有 10 个隐单元时,决策边界看起来更加参差不齐。结论是:参数用的越好,分类越平滑。但是杀鸡不用牛刀,根据具体数据具体选取。
最后,来看看神经网络在乳腺癌上的效果。
MLP 的精度相当好,但没有其他模型好。与较早的 SVC 例子相同,原因可能在于数据的缩放。神经网络也要求所有输入特征的变化范围相似,最理想的情况是均值为 0、方差为 1。我们必须对数据进行缩放以满足这些要求(第一段)。同样,我们这里将人工完成,但在第 3 章将会介绍用 StandardScaler 自动完成。缩放之后的结果要好得多,而且也相当有竞争力。不过模型给出了一个警告(第二段),告诉我们已经达到最大迭代次数。这是用于学习模型的 adam 算法的一部分,告诉我们应该增加迭代次数。
(第三段)增加迭代次数加到1000了,但仅提高了训练集性能,但没有提高泛化性能。不过模型的表现相当不错。由于训练性能和测试性能之间仍有一些差距,所以我们可以尝试降低模型复杂度来得到更好的泛化性能。这里我们选择增大 alpha 参数(变化范围相当大,从 0.0001 到 1),以此向权重添加更强的正则化(第四段):这得到了与我们目前最好的模型相同的性能!
在机器学习的许多应用中,神经网络再次成为最先进的模型。它的主要优点之一是能够获取大量数据中包含的信息,并构建无比复杂的模型。给定足够的计算时间和数据,并且仔细调节参数,神经网络通常可以打败其他机器学习算法(无论是分类任务还是回归任务)。
这就引出了下面要说的缺点。神经网络——特别是功能强大的大型神经网络——通常需要很长的训练时间。它还需要仔细地预处理数据,正如我们这里所看到的。与 SVM 类似,神经网络在“均匀”数据上的性能最好,其中“均匀”是指所有特征都具有相似的含义。如果数据包含不同种类的特征,那么基于树的模型可能表现得更好。神经网络调参本身也是一门艺术。调节神经网络模型和训练模型的方法有很多种,我们只是蜻蜓点水地尝试了几种而已。
估计神经网络的复杂度。最重要的参数是层数和每层的隐单元个数。你应该首先设置 1 个或 2 个隐层,然后可以逐步增加。每个隐层的结点个数通常与输入特征个数接近,但在几千个结点时很少会多于特征个数。
在考虑神经网络的模型复杂度时,一个有用的度量是学到的权重(或系数)的个数。如果你有一个包含 100 个特征的二分类数据集,模型有 100 个隐单元,那么输入层和第一个隐层之间就有 100 * 100 = 10 000 个权重。在隐层和输出层之间还有 100 * 1 = 100 个权重,总共约 10 100 个权重。如果添加含有 100 个隐单元的第二个隐层,那么在第一个隐层和第二个隐层之间又有 100 * 100 = 10 000 个权重,总数变为约 20 100 个权重。如果你使用包含 1000 个隐单元的单隐层,那么在输入层和隐层之间需要学习 100 * 1000 = 100 000 个权重,隐层到输出层之间需要学习 1000 * 1 = 1000 个权重,总共 101 000 个权重。如果再添加第二个隐层,就会增加 1000 * 1000 = 1 000 000 个权重,总数变为巨大的 1 101 000 个权重,这比含有 2 个隐层、每层 100 个单元的模型要大 50 倍。
神经网络调参的常用方法是,首先创建一个大到足以过拟合的网络,确保这个网络可以对任务进行学习。知道训练数据可以被学习之后,要么缩小网络,要么增大 alpha 来增强正则化,这可以提高泛化性能。
在我们的实验中,主要关注模型的定义:层数、每层的结点个数、正则化和非线性。这些内容定义了我们想要学习的模型。还有一个问题是,如何学习模型或用来学习参数的算法,这一点由 solver 参数设定。solver 有两个好用的选项。默认选项是 'adam',在大多数情况下效果都很好,但对数据的缩放相当敏感(因此,始终将数据缩放为均值为 0、方差为 1 是很重要的)。另一个选项是 'lbfgs',其鲁棒性相当好,但在大型模型或大型数据集上的时间会比较长。还有更高级的 'sgd' 选项,许多深度学习研究人员都会用到。'sgd' 选项还有许多其他参数需要调节,以便获得最佳结果。你可以在用户指南中找到所有这些参数及其定义。当你开始使用 MLP 时,我们建议使用 'adam' 和 'lbfgs'。
(本章完)
参考文献:
网上介绍mglearn如何安装:https://blog.csdn.net/qq_41185868/article/details/80941207
网上介绍mglearn如何使用:https://blog.csdn.net/az9996/article/details/86490496
L1和L2的优化:https://blog.csdn.net/red_stone1/article/details/80755144
训练集 验证集 测试集的关系:https://blog.csdn.net/JohnsonSmile/article/details/88905456