找回密码
 立即注册
查看: 205|回复: 0

以K近邻算法为例,使用交叉验证优化模型最佳参数

[复制链接]
发表于 2022-9-20 17:17 | 显示全部楼层 |阅读模式
参数优化

K近邻算法的介绍可以参考之前的文章:K近邻分类算法的Python代码实现。 在K近邻算法中,参数是K,表示K个最近的邻居。不同的K,可能会导致不同的预测结果。 如下图所示,K=1时,红色方块被预测为蓝色圆点;K=3时,红色方块被预测为黄色上三角;当K增长至9时,红色方块又被预测为蓝色原点。


那么在K近邻算法中,哪个K才是最佳的K?以及如何得到这个最佳的K?本文介绍的解决方案是交叉验证。
交叉验证

下图以5折交叉验证为例,绘制了交叉验证的计算过程。首先是把训练集等分为5份,构造出5组“子训练集和子测试集”的组合,其中子训练集占比4/5,子测试集占比1/5,不同组的组合中,子训练集和子测试集会有一些不同。 然后对于每一组,选择一个k值后,可以分别计算得到K近邻算法的性能指标score1,score2,score3,score4和score5。 最后把这5个值取平均值后,可以得到K_score。


从以上的描述可知,交叉验证的价值包括:(1)每一个数据都作为子测试集被测试过,所以使用交叉验证后,可以有效防止过拟合;(2)对于每个K,都可以得到一个K_score,如果计算了不同K对应的K_score,通过对比K_score值的大小,就可以找到最佳的K值。
代码实现

交叉验证有3步:数据集划分、分别计算模型指标和统计平均值。 以下基于Python代码实现了这个过程。 为了方便后续与sklearn的结果进行细致比较,代码中并未直接统计平均值,而是分别输出模型的指标,此处使用的指标是f1_score,如果对指标含义不清楚,可以参考之前的文章:机器学习模型常用性能指标和Python代码实现。 需要注意的是,此处在划分数据集时,直接使用了自带的函数StratifiedKFold。 对于分类问题来说,此处不能使用KFold,否则会和sklearn无法对齐。
from sklearn import neighbors
from sklearn.metrics import f1_score
from sklearn.model_selection import StratifiedKFold


def k_folder_by_self(X, y, cross_num, k):
   
    # 不能使用KFold,否则结果无法对齐
    kf = StratifiedKFold(cross_num)
    i = 0
    for train_index, test_index in kf.split(X, y):
        X_train = X[train_index]
        y_train = y[train_index]

        X_test = X[test_index]
        y_test = y[test_index]

        clf = neighbors.KNeighborsClassifier(k)
        clf.fit(X_train, y_train)
        print('i: {}, f1_score: {}'.format(i, f1_score(y_test, clf.predict(X_test))))
        i += 1
在sklearn中,已有交叉验证的函数cross_val_score,所以此处直接调用即可。
from sklearn import neighbors
from sklearn.model_selection import cross_val_score


def k_folder_by_sklearn(X, y, cross_num, k):
    scores = cross_val_score(neighbors.KNeighborsClassifier(n_neighbors=k), X, y, cv=cross_num, scoring='f1')
    for i in range(0, cross_num):
        print('i: {}, f1_score: {}'.format(i, scores))
代码测试

本节通过一个实例来测试自编代码和skearn代码的计算结果。 此处使用了cancer数据集,并设置K=3(最近邻数量为3),cv=5(5折交叉验证)。
if __name__ == '__main__':
    _, X, y = breast_cancer()

    k = 3
    cv = 5

    print('=======f1_score by self=======')
    k_folder.k_folder_by_self(X, y, cv, k)
    print('=======f1_score by sklearn=======')
    k_folder.k_folder_by_sklearn(X, y, cv, k)
计算结果如下,显然对于5次计算的结果,两个程序都是完全一致的,由此验证了逻辑的正确性。


实际应用

在遇到实际问题时,使用交叉验证的方法如下图所示。右边是机器学习的常规流程,左边为交叉验证的流程,其插入在训练集的后面。



从上图可以看出,一共包含4个步骤:数据集拆分为训练集和测试集;训练集使用交叉验证,得到最佳参数;使用最佳参数针对训练集进行重新训练;使用测试集计算模型的性能指标。以下为一个实例的具体代码。
from sklearn import neighbors
from sklearn.metrics import f1_score
from sklearn.model_selection import validation_curve, train_test_split
import numpy as np


def cal_validation_curve(X, y, cv):

    # 数据集拆分
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
   
    # 交叉验证
    param_name = 'n_neighbors'
    param_range = range(1, 20)
    train_scores, test_scores = validation_curve(neighbors.KNeighborsClassifier(), X_train, y_train, cv=cv,
                                                 param_name=param_name, param_range=param_range, scoring='f1')
    test_scores_mean = np.mean(test_scores, axis=1)
    best_k = np.argmax(test_scores_mean) + 1
   
    # 使用最佳参数重新训练
    classifier = neighbors.KNeighborsClassifier(n_neighbors=best_k)
    classifier.fit(X_train, y_train)
   
    # 计算最终性能指标
    f1 = f1_score(y_test, classifier.predict(X_test))

    return best_k, f1


if __name__ == '__main__':
   
    _, X, y = breast_cancer()
    cv = 5
    best_k, f1 = cal_validation_curve(X, y, cv)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-7-2 09:01 , Processed in 0.119402 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表