一、选择最优K值
### --- 选择最优K值
~~~ KNN 中的 k 是一个超参数,所谓“超参数”,就是需要人为输入,算法不能通过直接计算得出的参数。
~~~ KNN 中的 k 代表的是距离需要分类的测试点 x 最近的 k 个样本点,
~~~ 如果不输入这个值,那么算法中重要部分 “选出 k 个最近邻” 就无法实现。
~~~ 从 KNN 的原理中可见,是否能够确认合适的 k 值对算法有极大的影响。
![](https://i-blog.csdnimg.cn/blog_migrate/b1694d7322623b3d19a530dda61486a7.png)
~~~ 如果选择的 k 值较小,就相当于较小的邻域中的训练实例进行预测,
~~~ 这时候只有与输入实例较近的(相似的)训练实例才会对预测结果起作用,
~~~ 但缺点是预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰好是噪声,预测就会出错。
~~~ 相反地,如果选择的 k 值较大,就相当于较大的邻域中的训练实例进行预测。
~~~ 这时与输入实例较远的 (不相似的)训练实例也会对预测起作用,使预测发生错误。
~~~ 因此,超参数 k 的选定是 KNN 的头号问题。
### --- 学习曲线
~~~ 那我们怎样选择一个最佳的 k 呢?在这里我们要使用机器学习中的神器:参数学习曲线。
~~~ 参数学习曲线是一条以不同的参数取值为横坐标,
~~~ 不同参数取值下的模型结果为纵坐标的曲线,
~~~ 我们往往选择模型表现最佳点的参数取值作为这个参数的取值。
# 更换不同的n_neighbors参数的取值,观察结果的变化
clf = KNeighborsClassifier(n_neighbors=7)
clf = clf.fit(Xtrain,Ytrain)
score = clf.score(Xtest,Ytest)
score
~~~ # 输出参数
0.9385964912280702
~~~ # 绘制学习曲线:
score = [ ]
krange = range(1,20)
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
clf = clf.fit(Xtrain,Ytrain)
score.append(clf.score(Xtest,Ytest))
plt.plot(krange,score)
plt.show()
![](https://i-blog.csdnimg.cn/blog_migrate/c0165644cf29b99b0db64d519932fcb7.png)
### --- 究竟上图中k为多少的时候分数越高?
score.index(max(score))+1
~~~ # 输出参数
8
### --- 但是这个时候会有个问题,如果随机划分的数据集变化的的话,得分最高的k值也会发生变化:
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=421)
score = []
krange = range(1,20)
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
clf = clf.fit(Xtrain,Ytrain)
score.append(clf.score(Xtest,Ytest))
plt.plot(krange,score)
plt.show()
![](https://i-blog.csdnimg.cn/blog_migrate/4391f87ed5a0227ac62cb5acaf866134.png)
### --- 这样就无法确定最佳的k值了,就无法进行下面的建模工作,怎么办?
score.index(max(score))+1
~~~ # 输出参数
3
二、交叉验证
### --- 交叉验证
~~~ 确定了 k 之后,我们还能够发现一件事:每次运行的时候学习曲线都在变化,
~~~ 模型的效果时好时坏, 这是为什么呢?实际上,这是由于「训练集」和「测试集」的划分不同造成的。
~~~ 模型每次都使用不同的训练集进行训练, 不同的测试集进行测试,自然也就会有不同的模型结果。
~~~ 在业务当中,我们的训练数据往往是已有的历史数据,
~~~ 但我们的测试数据却是新进入系统的一系列还没有标签的未知数据。
~~~ 我们的确追求模型的效果,但我们追求的是模型在未知数据集上的效果,
~~~ 在陌生数据集上表现优秀的能力被称为泛化能力,即我们追求的是模型的泛化能力。
### --- 泛化能力
~~~ 我们在进行学习算法前, 通常会将一个样本集分成训练集(training set)和测试集(testing set),
~~~ 其中训练集用于模型的学习或训练,而后测试集通常用于评估训练好的模型对于数据的预测性能评估。
~~~ 训练误差(training error)代表模型在训练集上的错分样本比率。
~~~ 测试误差(empirical error)是模型在测试集上的错分样本比率。
~~~ 训练误差的大小,用来判断给定问题是不是一个容易学习的问题。
~~~ 测试误差则反映了模型对未知数据的
~~~ 预测能力,测试误差小的学习方法具有很好的预测能力,如果得到的训练集和测试集的数据没有交集,
~~~ 通常将此预测能力称为泛化能力(generalization ability)。
~~~ 我们认为,如果模型在一套训练集和数据集上表现优秀,
~~~ 那说明不了问题,只有在众多不同的训练集和 测试集上都表现优秀,
~~~ 模型才是一个稳定的模型,模型才具有真正意义上的泛化能力。
~~~ 为此,机器学习领域有发挥神作用的技能:「交叉验证」,来帮助我们认识模型。
### --- K 折交叉验证
~~~ 最常用的交叉验证是 k 折交叉验证。我们知道训练集和测试集的划分会干扰模型的结果,
~~~ 因此用交叉验证 n 次的结果求出的均值,是对模型效果的一个更好的度量。
![](https://i-blog.csdnimg.cn/blog_migrate/f306489229a3823e96df36fdd1e83a89.png)
### --- 带交叉验证的学习曲线
~~~ 对于带交叉验证的学习曲线,我们需要观察的就不仅仅是最高的准确率了,
~~~ 而是准确率高且方差还相对较小的点,这样的点泛化能力才是最强的。
~~~ 在交叉验证+学习曲线的作用下,我们选出的超参数能够保证更好的泛化能力。
from sklearn.model_selection import cross_val_score as CVS
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.2,random_state=420)
clf = KNeighborsClassifier(n_neighbors=8)
cvresult = CVS(clf,Xtrain,Ytrain,cv=6) #训练集对折6次,一共6个预测率输出
cvresult #每次交叉验证运行时估算器得分的数组
~~~ # 输出参数
array([0.92207792, 0.90789474, 0.97368421, 0.94736842, 0.93333333, 0.92 ])
# 均值:查看模型的平均效果
cvresult.mean()
# 方差:查看模型是否稳定
cvresult.var()
~~~ # 输出参数
0.934059770638718
0.0004622658270548926
score = []
var = []
krange=range(1,20) #设置不同的k值,从1到19都看看
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
cvresult = CVS(clf,Xtrain,Ytrain,cv=5)
score.append(cvresult.mean()) # 每次交叉验证返回的得分数组,再求数组均值
var.append(cvresult.var())
plt.plot(krange,score,color='k')
plt.plot(krange,np.array(score)+np.array(var)*2,c='red',linestyle='--')
plt.plot(krange,np.array(score)-np.array(var)*2,c='red',linestyle='--')
![](https://i-blog.csdnimg.cn/blog_migrate/6c6e51fdb47bfba9a836e4d170f751df.png)
### --- 是否需要验证集
~~~ 最标准,最严谨的交叉验证应该有三组数据:训练集、验证集和测试集。 当我们获取一组数据后:
~~~ 先将数据集分成整体的训练集和测试集。
~~~ 然后我们把训练集放入交叉验证中。
~~~ 从训练集中分割更小的训练集(k-1 份)和验证集(1 份)。
~~~ 返回的交叉验证结果其实是验证集上的结果。
~~~ 使用验证集寻找最佳参数,确认一个我们认为泛化能力最佳的模型。
~~~ 将这个模型使用在测试集上,观察模型的表现。
~~~ 通常来说,我们认为经过验证集找出最终参数后的模型的泛化能力是增强了的,
~~~ 因此模型在未知数据(测试集)上的效果会更好,但尴尬的是,
~~~ 模型经过交叉验证在验证集上的调参之后,在测试集上的结果没有变好的情况时有发生。
~~~ # 原因其实是:
~~~ 我们自己分的训练集和测试集,会影响模型的效果。
~~~ 交叉验证后的模型的泛化能力增强了,表示它在未知数据集上方差更小,平均水平更高,
~~~ 但却无法保证它在现在分出来的测试集上预测能力最强。
~~~ 如此说来,是否有测试集的存在,其实意义不大了。
~~~ 如果我们相信交叉验证的调整结果是增强了模型的泛化能力的,
~~~ 那即便测试集上的测试结果并没有变 好(甚至变坏了),我们也认为模型是成功的。
~~~ 如果我们不相信交叉验证的调整结果能够增强模型的泛化能力,而一定要依赖测试集来进行判断,
~~~ 我们完全没有进行交叉验证的必要,直接用测试集上的结果来跑学习曲线就好了。
~~~ 所以,究竟是否需要验证集,其实是存在争议的,在严谨的情况下,大家还是使用有验证集的方式。
### --- 其他交叉验证
~~~ 交叉验证的方法不止“k 折” 一种,分割训练集和测试集的方法也不止一种,
~~~ 分门别类的交叉验证占据了sklearn 中非常长的一章。
~~~ 所有的交叉验证都是在分割训练集和测试集,只不过侧重的方向不同。
~~~ “k 折"就是按顺序取训练集和测试集。
~~~ ShuffleSplit 就侧重于让测试集分布在数据的全方位之内。
~~~ StratifiedKFold 则是认为训练数据和测试数据必须在每个标签分类中占有相同的比例。
~~~ 各类交叉验证的原理繁琐,大家在机器学习道路上一定会逐渐遇到更难的交叉验证,
~~~ 但是万变不离其宗:本质上交叉验证是为了解决训练集和测试集的划分对模型带来的影响,
~~~ 同时检测模型的泛化能力的。
~~~ 交叉验证的折数不可太大,因为折数越大抽出来的数据集越小,
~~~ 训练数据所带的信息量会越小,模型会越来越不稳定。
![](https://i-blog.csdnimg.cn/blog_migrate/2391c56160575f26a96356f3d8f9e1c3.png)
### --- 避免折数太大
~~~ 如果你发现不使用交叉验证的时候模型表现很好,一使用交叉验证模型的效果就骤降
~~~ 一定要查看你的标签是否有顺序。
~~~ 然后就是查看你的数据量是否太小,折数是否太高。
~~~ 如果将上面例题的代码中将 cv 将 5 改成 100:
![](https://i-blog.csdnimg.cn/blog_migrate/9226177a509ecd3d1554bbf48124f2da.png)
~~~ # 折数过大:
~~~ 运算效率变慢。
~~~ 预测率方差变大,难以保证在新的数据集达到预期预测率。