- 验证数据集(Validation )与交叉验证(Cross Validation)
- 模型正则化-Regularization
- 岭回归 Ridge Regression
- LASSO回归
- 总结Ridge和Lasso
6. 验证数据集(Validation )与交叉验证(Cross Validation)
6.1 验证数据集
使用分割训练数据集
和测试数据
集来判断我们的机器学习性能的好坏,虽然是一个非常好的方案,但是会产生一个问题:针对特定测试数据集过拟合。
我们每次使用测试数据
来分析性能的好坏。一旦发现结果不好,我们就换一个参数(可能是degree也可能是其他超参数)重新进行训练。这种情况下,我们的模型在一定程度上围绕着测试数据集
打转。也就是说我们在寻找一组参数,使得这组参数训练出来的模型在测试结果集上表现的最好。但是由于这组测试数据集
是已知的,我们相当于在针对这组测试数据集
进行调参,那么他也有可能产生过拟合的情况,也就是我们得到的模型针对测试数据集过拟合了。
那么怎么解决这个问题呢?
解决的方式其实就是:我们需要将我们的问题分为三部分,这三部分分别是训练数据集,验证数据集,测试数据集
。 我们使用训练数据集
训练好模型之后,将验证数据集
送给这个模型,看看这个训练数据集
训练的效果是怎么样的,如果效果不好的话,我们重新换参数,重新训练模型。直到我们的模型针对验证数据
来说已经达到最优了。 这样我们的模型达到最优以后,再讲测试数据集
送给模型,这样才能作为衡量模型最终的性能。换句话说,我们的测试数据集
是不参与模型的创建的,而其他两个数据集都参与了训练。但是我们的测试数据集
对于模型是完全不可知的,相当于我们在模型这个模型完全不知道的数据。
这种方法还会有一个问题。由于我们的模型可能会针对验证数据集
过拟合,而我们只有一份验证数据集
,一旦我们的数据集里有比较极端的情况,那么模型的性能就会下降很多,那么为了解决这个问题,就有了交叉验证。
6.2 交叉验证 Cross Validation
交叉验证相对来说是比较正规的、比较标准的在我们调整我们的模型参数的时候看我们的性能的方式
交叉验证:在训练模型的时候,通常把数据分成k
份,例如分成3份(ABC)(分成k分,k属于超参数),这三份分别作为验证数据集
和训练数据集
。这样随机组合后可以分别产生三个模型,这三个模型,每个模型在测试数据集上都会产生一个性能的指标,这三个指标的平均值
作为当前这个算法训练处的模型衡量的标准是怎样的。 由于我们有一个求平均的过程,所以不会由于一份验证数据集中有比较极端的数据而导致模型有过大的偏差,这比我们只分成训练、验证、测试
数据集要更加准确。
6.3 验证&交叉验证代码示例
1.生成数据
import numpy as np
from sklearn import datasets
'''1. 生成数据(手写数字)'''
digits = datasets.load_digits()
X = digits.data
y = digits.target
print(X.shape)
# (1797, 64)
2.分割数据,并使用KNN算法寻找最佳超参数k,p
'''2. 分割数据'''
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y)
'''3. 使用KNN算法寻找最佳k,p'''
from sklearn.neighbors import KNeighborsClassifier
best_k,best_p,best_score = 0,0,0
# k为knn中寻找k个最近元素
for k in range(2,11):
# p为举例计算公式(明科夫斯基距离)
for p in range(1,6):
knn = KNeighborsClassifier(n_neighbors=k,p=p,weights='distance')
knn.fit(X_train,y_train)
score = knn.score(X_test,y_test)
# 如果该score的值大于之前中的最好的score,那么重新赋值k,p,score
if score >best_score:
best_k, best_p, best_score = k, p, score
print(best_k)
print(best_p)
print(best_score)
# 5
# 2
# 0.9977777777777778
3.使用交叉验证
cross_val_score()
:将数据分为若干份对算法模型进行随机训练。
'''4. 使用交叉验证'''
# 使用sklearn提供的交叉验证
from sklearn.model_selection import cross_val_score
# 实例化算法
knn_clf = KNeighborsClassifier()
# 返回的是一个数组,有三个元素,说明cross_val_score方法默认将我们的训练数据集分成了三份
# 这三份数据集进行交叉验证后产生了这三个结果
# cv默认为3,可以修改改参数
re = cross_val_score(knn_clf,X_train,y_train,cv=4)
print(re)
# [0.99109792 0.97626113 0.97922849 0.97619048]
4.使用交叉验证寻找最佳超参数k,p
best_k, best_p, best_score = 0, 0, 0
for k in range(2, 11):
for p in range(1, 6):
knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=k, p=p)
# 计算所有的score,
scores = cross_val_score(knn_clf, X_train, y_train,cv=3)
# 求平均值
score = np.mean(scores)
if score > best_score:
best_k, best_p, best_score = k, p, score
print("Best K =", best_k)
print("Best P =", best_p)
print("Best Score =", best_score)
# Best K = 4
# Best P = 3
# Best Score = 0.985894580549369
- 其与普通方式寻找
k,p
的区别就是在寻找best_score
时使用交叉验证数据集,随机组合后的平均结果。其测试集数据从来没有参与训练模型。
通过观察两组调参过程的结果可以发现
- 两组调参得出的参数结果是不同的,通常这时候我们更愿意详细使用交叉验证的方式得出的结果。 因为使用
train_test_split
很有可能只是过拟合了测试数据集得出的结果
2.使用交叉验证得出的最好分数0.982是小于使用分割训练测试数据集得出的0.986,因为在交叉验证的 过程中,通常不会过拟合某一组的测试数据,所以平均来讲这个分数会稍微低一些
但是使用交叉验证得到的最好参数best_score
并不是真正的最好的结果,我们使用这种方式只是为了拿到 一组超参数而已,拿到这组超参数后我们就可以训练处我们的最佳模型。
knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=4,p=3)
# 用我们找到的k和p,来对X_train,y_train整体fit一下,来看他对X_test,y_test的测试结果
knn_clf.fit(X_train,y_train)
# 注意这个X_test,y_test在交叉验证过程中是完全没有用过的,也就是说我们这样得出的结果是可信的
score = knn_clf.score(X_test,y_test)
print(score)
# 0.9822222222222222
X_test,y_test
从来没有参与过模型的训练,所以这个结果是很好的。
6.4 回顾网格搜索
我们上面的操作,实际上在网格搜索的过程中已经进行了,只不过这个过程是sklean的网格搜索自带的一个过程。
from sklearn.model_selection import GridSearchCV
param_grid = [
{
'weights': ['distance'],
'n_neighbors': [i for i in range(2, 11)],
'p': [i for i in range(1, 6)]
}
]
grid_search = GridSearchCV(knn_clf, param_grid, verbose=1)
grid_search.fit(X_train, y_train)
Fitting 3 folds for each of 45 candidates, totalling 135 fits
[Parallel(n_jobs=1)]: Done 135 out of 135 | elapsed: 1.9min finished
意思是:对于k,p
,有45
种组合(9*6),每次组合都要将数据分为是3
份进随机进行训练模型,这样的话要训练135
次模型。
score = grid_search.best_score_
print(score)
# 0.985894580549369
param = grid_search.best_params_
print(param)
# {'n_neighbors': 3, 'p': 4, 'weights': 'distance'}
best_knn_clf = grid_search.best_estimator_
score = best_knn_clf.score(X_test, y_test)
print(score)
# 0.98
6.5 cv参数
cv
:将数据分为若干份,随机组合训练模型。默认为3
。
cross_val_score(knn_clf, X_train, y_train, cv=5)
# array([ 0.99543379, 0.96803653, 0.98148148, 0.96261682, 0.97619048])
grid_search = GridSearchCV(knn_clf, param_grid, verbose=1, cv=5)
6.6 总结
虽然整体速度慢了,但是这个结果却是可信赖的。
7.模型正则化-Regularization
7.1 什么是模型正则化
下图是我们之前使用多项式回归过拟合一个样本的例子,可以看到这条模型曲线非常的弯曲,而且非常的陡峭,可以想象这条曲线的一些θ系数会非常的大。 模型正则化需要做的事情就是限制这些系数的大小。
7.2 模型正则化基本原理
也就是在损失函数的基础上加上关于theta
值的式子,使J(theta)
既要保证误差最小,还要保证theta
值不会变的很大。
注意:
对于
θ
的求和i是从1到n
,没有将θ0
加进去,因为他不是任意一项的系数,他只是一个截距,决定了整个曲线的高低,但是不决定曲线每一部分的陡峭和缓和。θ
求和的系数1/2
是一个惯例,加不加都可以,加上的原因是因为,将来对θ2>求导的时候可以抵消系数2,方便计算。不要也是可以的。α
实际上是一个超参数,代表在我们模型正则化下新的损失函数中,我们要让每一个θ
尽可能的小,小的程度占我们整个损失函数的多少。如果α
等于0,相当于没有正则化;如果α
是正无穷的话,那么我们主要的优化任务就是让每一个θ
尽可能的 小。