第一部分:使用交叉验证的背景和必要性
一般来说,我们将数据集分为训练集和测试集,其中训练集用于训练模型,测试集用来评估模型。
通常我们会用测试集的效果来反映训练过程中模型是否产生了“过拟合”。
但是有很小的概率,就是我们训练的这个模型本身就是“过拟合”,但是很碰巧的是(尽管概率很低),我们拿出来的测试集进行验证的过程中,这些测试集跟原本数据非常相似,得出的训练效果很好,因此就导致这次训练效果产生了错误的假象,本身应该是“过拟合”,却得出“正常”的效果。
解决思路:
为了防止这种非常小概率的极端情况的出现,我们能够想到,如果出现这种情况的概率为1/10,那我们完全可以多训练几回,这样回回都出错的概率将会更低,比如我们训练3回,全部都碰巧出现这种“极端情况”的概率将会是1/1000.
而实现这种训练多回,我们就需要有一个新的数据集来实现,也就是将数据集多分了一个部分,叫做“验证集”
第二部分:训练集+测试集+验证集
训练集:训练模型
测试集:评价最终效果
验证集:搜索最优超参数
以生活中的考试例子:
第三部分:K-fold交叉验证
将这K次的验证结果取平均,作为模型在该数据集上的最终表现。
第四部分:K-fold代码实现
我们以:“机器学习中的超参数(以鸢尾花数据集为例,knn算法,使用网格搜索最优化超参数)”为例:详细内容可以见我的博客:
机器学习中的超参数(以鸢尾花数据集为例,knn算法,使用网格搜索最优化超参数)!-CSDN博客
主要包含以下两种代码:
(1)手写-使用网格搜索实现最优化超参数
#第一步:导包
import numpy as np
from sklearn import datasets
#第二步:获取数据集
iris=datasets.load_iris()
#iris可以详细查看这个数据集里面的具体内容
#2.1获取特征数据X
X=iris.data
#iris.feature_names获取特征的名字
'''
结果为:
['sepal length (cm)',
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']
'''
#2.2获取标签值
Y=iris.target
#iris.target_names#获取特征的名字
'''
结果为:
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')
'''
#第三步:划分数据集(训练集+测试集)
from sklearn.model_selection import train_test_split#这个是新增的
X_train,X_test,Y_train,Y_test=train_test_split(X,Y,train_size=0.7,random_state=233,stratify=Y)#train_size=0.7是测试集比例,233是随机数种子
#stratify=y作用是让这划分之后的训练集和测试集中三个样本的数量能够平均,不会出现某种类别多,某种类别少的情况
#第四步:设置超参数
#第一个超参数k,第二个超参数同等权重还是按距离递减权重
from sklearn.neighbors import KNeighborsClassifier
knn_classifer=KNeighborsClassifier(
)
#第四步:新修改的超参数-网格搜索
#初始化参数
best_score=-1;
best_n=-1;
best_weight='';
best_p=-1;
for k in range(1,20):#代表的是k值,邻居的数量
for weight in ['uniform','distance']:
for p in range(1,4):#代表三种距离
knn_classifer=KNeighborsClassifier(
n_neighbors=k, # 设置初始的k=3
weights=weight, # distance这个代表按距离递减权重,#uniform是同等权重
p=p # 1代表是曼哈顿距离,2代表是欧式距离,3代表是明氏距离
)
# 调用fit进行训练
knn_classifer.fit(X_train, Y_train)
from sklearn.metrics import accuracy_score
# 第五步:使用训练集开始进行预测
Y_predict = knn_classifer.predict(X_test)
#score=accuracy_score(Y_test, Y_predict)
score=knn_classifer.score(X_test,Y_test)
#上面这两个的结果是一样的
if(score>best_score):
best_score=score
best_weight=weight
best_p=p
best_n=k
print("best_score=",best_score)
print("best_weight=",best_weight)
print("best_p=",best_p)
print("best_n=",best_n)
(2)sklearn实现超参数网格搜索(快捷版本)
#第一步:导包
import numpy as np
from sklearn import datasets
#第二步:获取数据集
iris=datasets.load_iris()
#iris可以详细查看这个数据集里面的具体内容
#2.1获取特征数据X
X=iris.data
#iris.feature_names获取特征的名字
'''
结果为:
['sepal length (cm)',
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']
'''
#2.2获取标签值
Y=iris.target
#iris.target_names#获取特征的名字
'''
结果为:
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')
'''
#第三步:划分数据集(训练集+测试集)
from sklearn.model_selection import train_test_split#这个是新增的
X_train,X_test,Y_train,Y_test=train_test_split(X,Y,train_size=0.7,random_state=233,stratify=Y)#train_size=0.7是测试集比例,233是随机数种子
#stratify=y作用是让这划分之后的训练集和测试集中三个样本的数量能够平均,不会出现某种类别多,某种类别少的情况
#第四部分:KNN超参数搜索
from sklearn.model_selection import GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
params = {
'n_neighbors': [n for n in range(1, 20)],
'weights': ['uniform', 'distance'],
'p': [p for p in range(1, 7)]
}
grid = GridSearchCV(
estimator=KNeighborsClassifier(),
param_grid=params,
n_jobs=-1
)
grid.fit(X_train, Y_train)
# 输出最优超参数
print(grid.best_params_)
print(grid.best_score_)
(3)两种计算结果的差异性对比
这个计算的结果不同,第一种计算的neighbors=5,第二回用内置函数计算得到的结果是neighbors=9,主要是因为 这个内置函数和我们实际计算的方法略有区别(sklearn里面用的有交叉验证,弥补了极端情况下无法检测到过拟合的情况)。
(4)再次手动代码验证上述交叉验证的结果
将“(1)手写-使用网格搜索实现最优化超参数”代码中:
在初始化参数部分加上best_cv_score=None:
#初始化参数
best_score=-1;
best_n=-1;
best_weight='';
best_p=-1;
best_cv_score=None#新加的
删掉下面这段代码:
# 调用fit进行训练
knn_classifer.fit(X_train, Y_train)
from sklearn.metrics import accuracy_score
# 第五步:使用训练集开始进行预测
Y_predict = knn_classifer.predict(X_test)
#score=accuracy_score(Y_test, Y_predict)
#score=knn_classifer.score(X_test,Y_test)
替换成:
from sklearn.model_selection import cross_val_score
cv_scores=cross_val_score(knn_classifer,X_train,Y_train,cv=5)
score=np.mean(cv_scores)
if语句里面加上 best_cv_score=cv_scores:
if(score>best_score):
best_score=score
best_weight=weight
best_p=p
best_n=k
best_cv_score=cv_scores#新加的
输出部分加上:
print("best_cv_score=",best_cv_score)
修改后的完整代码为:
#第一步:导包
import numpy as np
from sklearn import datasets
#第二步:获取数据集
iris=datasets.load_iris()
#iris可以详细查看这个数据集里面的具体内容
#2.1获取特征数据X
X=iris.data
#iris.feature_names获取特征的名字
'''
结果为:
['sepal length (cm)',
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']
'''
#2.2获取标签值
Y=iris.target
#iris.target_names#获取特征的名字
'''
结果为:
array(['setosa', 'versicolor', 'virginica'], dtype='<U10')
'''
#第三步:划分数据集(训练集+测试集)
from sklearn.model_selection import train_test_split#这个是新增的
X_train,X_test,Y_train,Y_test=train_test_split(X,Y,train_size=0.7,random_state=233,stratify=Y)#train_size=0.7是测试集比例,233是随机数种子
#stratify=y作用是让这划分之后的训练集和测试集中三个样本的数量能够平均,不会出现某种类别多,某种类别少的情况
#第四步:设置超参数
#第一个超参数k,第二个超参数同等权重还是按距离递减权重
from sklearn.neighbors import KNeighborsClassifier
knn_classifer=KNeighborsClassifier(
)
#第四步:新修改的超参数-网格搜索
#初始化参数
best_score=-1;
best_n=-1;
best_weight='';
best_p=-1;
best_cv_score=None
for k in range(1,20):#代表的是k值,邻居的数量
for weight in ['uniform','distance']:
for p in range(1,4):#代表三种距离
knn_classifer=KNeighborsClassifier(
n_neighbors=k, # 设置初始的k=3
weights=weight, # distance这个代表按距离递减权重,#uniform是同等权重
p=p # 1代表是曼哈顿距离,2代表是欧式距离,3代表是明氏距离
)
from sklearn.model_selection import cross_val_score
cv_scores=cross_val_score(knn_classifer,X_train,Y_train,cv=5)
score=np.mean(cv_scores)
#上面这两个的结果是一样的
if(score>best_score):
best_score=score
best_weight=weight
best_p=p
best_n=k
best_cv_score=cv_scores#新加的
print("best_score=",best_score)
print("best_weight=",best_weight)
print("best_p=",best_p)
print("best_n=",best_n)
print("best_cv_score=",best_cv_score)
最终结果为:
可以看到,上述的结果已经和“(2)sklearn实现超参数网格搜索(快捷版本)结果一样了”,同时也证明了交叉验证的效果。