引言
众所周知,考试前会刷题。但是考试大部分又不是原题,那考前刷题有什么用?我们考前做的题目的当然不是为了赌考试有一模一样的题(有可能也是。。。),我们是为了从题目中学到一般的知识,这样我们在遇到新题目的时候也可以根据知识来做出题目。其实在机器学习中,考前刷的题就是训练集,考试中的题就是我们模型之后遇到的新样本,而泛化就是我们的模型遇到新样本的表现(也就对应着考试的分数)。
交叉验证
在我们训练完模型之后,我们肯定是想看模型的泛化是怎么样的。一般的做法就是把数据集分为训练集和测试集,我们用训练集训练模型,用测试集测试模型的泛化能力。我们还会根据测试集的准确率来调整模型。
但这样又遇到一个问题,那就是如果根据测试集来调整模型,那么模型很可能就会在测试集上过拟合,或者说这样做的话,就无法体现模型的泛化能力了。
所以更多的做法,是将数据集分为训练集、验证集、测试集。通过训练集训练模型,验证集调整模型,测试集测试模型的泛化能力。
但是验证集也有随机性,很可能因为这一份验证集而产生过拟合,所以又产生了交叉验证。
我们将训练数据随机分为k份,上图中分为k=3份,将任意两种组合作为训练集,剩下的一组作为验证集,这样就得到k个模型,然后在将k个模型的均值作为结果调参。显然这种方式要比随机只用一份数据集作为验证集要靠谱的多。
下面用实际的例子,来了解一下如何使用交叉验证调参:
- 第一种情况:只使用训练集测试集测试:
1 | import numpy as np |
输出结果
1 | Best K= 3 |
- 第二种情况:使用交叉验证
1 | from sklearn.model_selection import cross_val_score |
根据输出结果,默认情况下 sklearn
包的交叉验证是分为 5
份,也可以通过 cv
参数修改。
1 | best_score, best_p, best_k = 0, 0, 0 |
输出结果
1 | Best K= 2 |
从输出结果可以看出,选择的参数和之前不一样了,虽然分数下降了点,但使用交叉验证的更可靠。当然这里不是模型的测试分数。
1 | best_knn_clf = KNeighborsClassifier(weights='distance', n_neighbors=2, p=2) |
输出结果 0.980528511821975
才是测试分数。
偏差方差权衡
偏差方差权衡(Bias Variance Trade off),当我们的模型表现不佳时,通常是出现两种问题,一种是 高偏差 问题,另一种是
高方差 问题。识别它们有助于选择正确的优化方式,所以我们先来看下 偏差 与 方差 的意义。
- 偏差: 描述模型输出结果的期望与样本真实结果的差距。
- 方差: 描述模型对于给定值的输出稳定性。 方差越大模型的泛华能力越弱。
就像打靶一样,偏差描述了我们的射击总体是否偏离了我们的目标,而方差描述了射击准不准。左一是方差跟偏差都很小,都比较靠近中心且集中,右一分散在中心附近,但比较散,因此方差较大。这样结合下面这两幅图就可以大概理解,偏差描述的是描述模型输出结果的期望与样本真实结果的差距。而方差则是对于输出结果是否集中,描述模型对于给定值的输出稳定性。
模型误差 = 偏差 + 方差 + 不可避免的误差
- 导致偏差大的原因:对问题本身的假设不正确!如非线性数据使用线性回归。或者特征对应标记高度不相关也会导致高偏差,不过这是对应着特征选择,跟算法没有关系,对于算法而言基本属于欠拟合问题
underfitting
。 - 导致方差大的原因:数据的一点点扰动都会极大地影响模型。通常原因就是使用的模型太复杂,如高阶多项式回归。这就是所说的过拟合(
overfitting
) - 总结:有些算法天生就是高方差的算法,如KNN,非参数学习的算法通常都是高方差的,因为不对数据进行任何假设。还有一些算法天生就是高偏差的,如线性回归。参数学习通常都是高偏差算法,因为对数据具有极强的假设。大多数算法具有相应的算法可以调整偏差和方差,如KNN中的k,如线性回归中使用多项式回归中的degree,偏差和方差通常是矛盾的,降低偏差,会提高方差,降低方差,会提高偏差,因此在实际应用中需要进行权衡。机器学习的主要挑战,在于方差。这句话只针对算法,并不针对实际问题。因为大多数机器学习需要解决过拟合问题。
解决手段:
- 降低模型复杂度
- 减少数据维度;降噪
- 增加样本数量
- 使用验证集
- 模型正则化
模型正则化
模型正则化(Regularization
):限制参数的大小。常常用来解决过拟合问题。
先看一下多项式回归过拟合的情况:
1 | from sklearn.linear_model import LinearRegression |
1 | lin_reg.coef_ |
通过查看多项式回归的系数可以发现,有些系数能差13个数量级,其实这就是过拟合了!而模型正则化就是为了解决这个问题。先来回顾一下多项式回归的目标。
通过加入的正则项来控制系数不要太大,从而使曲线不要那么陡峭,变化的那么剧烈。在这里有几个细节需要注意。
- 第一点:θ从1开始,只包含系数不包括截距,这是因为截距只决定曲线的高低,并不会影响曲线的陡峭和缓和。
- 第2点:就是这个
1/2
,是为了求导之后能够将2消去,为了方便计算。不过其实这个有没有都是可以的,因为在正则化前有一个系数,我们可以把这个1/2
可以考虑到ɑ
中去。 - 第3点:系数
ɑ
,它表示正则化项在整个损失函数中所占的比例。极端一下,ɑ
=0时,相当于模型没有加入正则化,但如果ɑ
= 正无穷,此时其实主要的优化任务就变成了需要所有的ɑ
都尽可能的小,最优的情况就是全为0。至于ɑ
的取值就需要尝试了。
岭回归(Ridege Regression)
测试用例:
1 | import numpy as np |
1 | from sklearn.linear_model import LinearRegression |
把画图这些操作封装成一个函数,方便后面调用:
1 | def plot_model(model): |
使用岭回归:
1 | from sklearn.linear_model import Ridge |
1 | ridege2_reg = RidgeRegression(20, alpha=1) |
1 | ridege3_reg = RidgeRegression(20, alpha=100) |
1 | ridege4_reg = RidgeRegression(20, alpha=1000000) |
这也跟之前分析,如果 ɑ
=正无穷 时,为了使损失函数最小,就需要所有的系数的平方和最小,即 θ
都趋于0。通过上面几种alpha的取值可以看出我们可以在1-100进行更加细致的搜索,找到最合适的一条相对比较平滑的曲线去拟合。这就是L2正则。
LASSO Regularization
LASSO: Least Absolute Shrinkage and Selection Operator Regression
Shrinkage:收缩,缩小,收缩量。特征缩减。重点在于Selection Operator
使用lasso回归:
1 | from sklearn.linear_model import Lasso |
1 | lasso2_reg = LassoRegression(20, 0.1) |
1 | lasso3_reg = LassoRegression(20, 1) |
解释Ridge和LASSO
通过这两幅图进行对比发现,LASSO
拟合的模型更倾向于是一条直线,而Ridge
拟合的模型更趋向与一条曲线。这是因为两个正则的本质不同,Ridge
是趋向于使所有 θ
的加和尽可能的小,而 Lasso
则是趋向于使得一部分 θ
的值变为0,因此可作为特征选择用,这也是为什么叫 Selection Operation
的原因。
下面就对上面这两句话尝试着进行一下解释:
导数中的 θ
都是有值的,顺着梯度方向下降。Ridge
是趋向于使所有 θ
的加和尽可能的小,而不是像lasso
一样直接为0。
所以,当如果从上图的一点开始进行梯度下降的话,就不能想 Ridge
一样曲线地去逼近 0
,而是只能使用这些非常规则的方式去逼近零点。在这种路径的梯度下降中,就会达到某些轴的零点,Lasso
则是趋向于使得一部分 θ
的值变为 0
。所以可以作为特征选择用。不过也正是因为这样的特性,使得 Lasso
这种方法有可能会错误将原来有用的特征的系数变为 0
,所以相对 Ridge
来说,准确率还是 Ridge
相对较好一些,但是当特征特别大时候,此时使用 Lasso
也能将模型的特征变少的作用。
弹性网
既然两者各有优势,就把他们结合起来,这就是弹性网(Elastic Net)。