机器学习之KNN近邻算法

    科技2022-07-10  170

    1.KNN近邻算法简介

    图为二分类图,表示有两种类型的点,但是在图上(6.5,3.4)的地方有一个黑点,是还未分类的,从图上来看,大概觉得这是一个绿色的点,因为黑点周围的大部分也是绿点,所以黑点大概率是绿点。 KNN近邻算法就是依据周围的点的分类从而来判断自己的类别是属于什么分类。 计算过程十分的简单暴力,用距离计算公式依次计算一个点到黑点的距离: 从第一个点开始计算,得出一个距离,再计算第二个点,得出第二个距离…直到所有点与黑点的距离都被算出,然后选出距离最近的k个点作为参考,依旧少数服从多数,决定黑点是红色还是绿色,选取k=5,距离最近的5个点均为绿色,所以未分类黑点是绿色。核心是k值的选取。

    KNN近邻算法的优点是方法简单靠谱,不需要用大量数据训练模型(即函数),但缺点就是需要计算每一个的距离,然后再选取k个距离近的点作为参考,计算量较大。

    2.用KNN近邻算法做分类模型

    from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier import matplotlib.pyplot as plt iris=datasets.load_iris() X=iris.data y=iris.target #鸢尾花有0,1,2三种分类,这里选取0,1两种做二分类 X=X[y<2] y=y[y<2] X_train,x_test,y_train,y_test=train_test_split(X,y,test_size=0.2)#划分训练数据与测试数据,划分比例为8:2 #对点画散点图 plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1],color='r')#已经明确类别的点 plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1],color='b') plt.scatter(x_test[y_test==0,0],x_test[y_test==0,1],color='black')#要分类的点 plt.scatter(x_test[y_test==1,0],x_test[y_test==1,1],color='black') plt.show() #使用KNN算法 knn=KNeighborsClassifier()#默认邻居数: 5 knn.fit(X_train,y_train)#将明确分类的训练数据填入,产生红色和蓝色的分类点 predict=knn.predict(x_test)#将为分类的测试数据填入得出预测分类结果 print(predict) print('得分:',knn.score(x_test,y_test)) #分类结果集如下 [0 0 0 0 1 1 0 0 1 1 1 0 0 0 0 0 0 0 1 0] 得分: 1.0

    由于鸢尾花数据是标准数据,分类十分明显,所以得分为100%. 得分=预测成功的结果数/测试结果数。 如果没有测试结果集就没有办法算出得分,因为要将测试结果集与预测分类结果集相对比才能得出分数。

    3.用KNN近邻算法做回归模型

    KNN回归原理: 与KNN分类相比,分类是确定距离最近的k个(默认为k=5)点的类别,如何根据少数服从多数来确定未分类点的类别。 而KNN回归也类似,确定距离最近的K(默认为5个)个点的y坐标,然后(y1+y2+y3+y4+y5)/5=Ky,Ky即预测的y值,即取周围5个点的y结果集的平均数作为自己的结果y。

    from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsRegressor import matplotlib.pyplot as plt digit=datasets.load_digits() X=digit.data#x参数结果集 y=digit.target#即为y结果集 print(digit) #划分训练数据集和测试数据集 X_train,x_test,y_train,y_test=train_test_split(X,y,test_size=0.2) #使用KNN算法 knn=KNeighborsRegressor()#没有加超参数,使用默认超参数 print('默认邻居数:',knn.n_neighbors) print('默认规则:',knn.weights) print('默认距离算法:',knn.p) knn.fit(X_train,y_train)#填入已知数据 predict=knn.predict(x_test)#根据测试数据得出预测结果集y print(predict) print(knn.score(x_test,y_test))

    3.5 KNN超参数

    KNN三个超参数如下: <1> 邻居数量n_neighbors:(即k值),默认是选取距离最近的5个邻居 <2> 距离度量p:(默认p=2p)为距离计算方式,p=1表示曼哈顿距离(c=|x2-x1|+|y2-y1|),p=2表示欧氏距离(常用): 以及明氏距离,切比雪夫距离,马氏距离等,p最大为5,即有5中距离计算方式 <3> 权重计算weights:(默认为统一权重)有统一权重(weights=uniform)和距离加权权重(weights=distance)。 统一权重:即不考虑k个邻居与目标点的距离,直接计算平均数y作为目标点的结果y 距离加权重:5个邻居离目标点有近有远,这时要给更近的邻居更大的权重,更远的邻居更小的权重,用距离d的倒数幂次方来进行加权,最后得出距离加权平均数y作为目标点的结果y

    超参数的作用:不同的超参数会使目标点有不同个数的邻居和不同的邻居距离计算方法,邻居选的多少非常可能影响预测结果,要得出最佳超参数才能训练出最佳的模型。

    KNN超参数的使用:

    knn_clf=KNeighborsClassifier(weights='distance',n_neighbors=5,p=2) knn_clf.fit(x_train,y_train)

    4.用交叉验证找到最佳超参数

    线性回归简介:

    一般的线性回归即为:y=kx+b 复杂的线性回归为: y=k1x1+k2 x2+k3 x3+k4 x4…+b 如三维空间线性回归: y=k1 x+k2 z+b,每多一个x即为多一个维度 训练模型即为用已知数据(x_train数据集和y_train数据集)来得出k1,k2,k3等参数的值,形成一个线性回归方程,再将测试数据(x_test数据集)填入,得出结果y_predict,将y_predict与y_test相比,得出分数(即正确率)。

    交叉验证简介:

    将数据集划分为训练数据集和测试数据集,再将训练数据集划分为三个等大小数据集,其中两个作为训练数据集,训练出模型(即函数方程),再将剩下的一个作为验证,验证模型的准确度,如此进行三次不同的划分验证数据集,即为三折交叉验证。五折交叉验证即将训练数据就分为A,B,C,D,E,5个子数据集,依次使用一个子数据集作为验证数据集,供训练5次,得出5个具有相同超参数,但不同的模型(函数方程),也具有5个得分,得出平均得分,这即为这组超参数下的模型最终得分。 交叉验证例子: 先用训练数据集得出最佳超参数,再用最佳超参数下的KNN分类模型来训练模型,最后预测测试结果。

    from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score #使用自带的数据集 digist=datasets.load_digits() X=digist.data y=digist.target X_train,x_test,y_train,y_test=train_test_split(X,y,test_size=2)#划分数据集 # #使用KNN分类模型 # knn=KNeighborsClassifier() #设置超参数 k,p=0,0 #最终得分 score=0 #得分最高的超参数 best_k,best_p,best_score=0,0,0 #寻找最佳超参数 for k in range(2,10):#将邻居的数量最大值为10个 for p in range(1,6):#使用1到5种距离计算方法 knn=KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)#权重计算weights使用距离权重,k,p通过不断的循环来找最佳值 all_score=cross_val_score(knn,X_train,y_train,cv=3)#cv:折数,三种验证。交叉验证后会得出一组同超参数下的分数结果集 score=all_score.mean()#取平均分作为这组超参数模型的最终得分 print('邻居数K:',k) print('距离算法:',p) print('这组超参数的平均得分:',score) if score>best_score: best_k,best_p,best_score=k,p,score print('得分最高的超参数:','score=',best_score,'K=',best_k,'p=',best_p) #使用最佳超参数来预测测试集结果 best_knn=KNeighborsClassifier(weights='distance',n_neighbors=best_k,p=best_p) best_knn.fit(X_train,y_train) print(best_knn.score(x_test,y_test)) #打印结果如下: 邻居数K: 2 距离算法: 1 这组超参数的平均得分: 0.9816127957595621 邻居数K: 2 距离算法: 2 这组超参数的平均得分: 0.9827276229613459 邻居数K: 2 距离算法: 3 这组超参数的平均得分: 0.9832850365622378 ....... 这组超参数的平均得分: 0.9816146569068106 邻居数K: 9 距离算法: 5 这组超参数的平均得分: 0.9799405549568866 得分最高的超参数: score= 0.9877424842221242 K= 3 p= 2

    5.网格搜索寻找最佳超参数

    网格搜索法就是将影响结果的参数给出范围,如k =[ 2,11],p=[1,6],然后其自动将不同的k和p组合起来放入函数中运算,得出最佳结果的参数。

    from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import GridSearchCV digist=datasets.load_digits() X=digist.data y=digist.target X_train,x_test,y_train,y_test=train_test_split(X,y,test_size=0.2) #使用KNN分类函数 knn=KNeighborsClassifier() #网格搜索 param=[ #参数字典集 { 'weights':['distance'], #这些参数对应knn的超参数名 'n_neighbors':[i for i in range(2,10)], 'p':[i for i in range(1,6)] } ] grid_search=GridSearchCV(knn,param,verbose=2,cv=3) #网格搜索函数将param参数字典不断的依次填入knn函数中,不断得出结果 #verbose有0:不打印运行结果;1:偶尔打印运行结果;大于1:大于全部运行结果 #cv=3,进行3折交叉验证,共有3*5*8=120种结果集,3是折数,5是p的取值个数range(1,6),8是k的取值个数range(2,10) grid_search.fit(X_train,y_train) print(grid_search.best_score_)#得出最好的分数 print(grid_search.best_params_)#得出最好的参数集 #使用最好的分类器进行训练模型 best_knn_estimator=grid_search.best_estimator_ #即填入了最佳参数的knn函数 print(best_knn_estimator.score(x_test,y_test)) #打印结果如下: [Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers. Fitting 3 folds for each of 40 candidates, totalling 120 fits [CV] n_neighbors=2, p=1, weights=distance ............................ [CV] ............. n_neighbors=2, p=1, weights=distance, total= 0.0s [CV] n_neighbors=2, p=1, weights=distance ............................ [Parallel(n_jobs=1)]: Done 1 out of 1 | elapsed: 0.0s remaining: 0.0s [CV] ............. n_neighbors=2, p=1, weights=distance, total= 0.0s [CV] n_neighbors=2, p=1, weights=distance ............................ [CV] ............. n_neighbors=2, p=1, weights=distance, total= 0.0s [CV] n_neighbors=2, p=2, weights=distance ............................ [CV] ............. n_neighbors=2, p=2, weights=distance, total= 0.0s ...... [CV] n_neighbors=9, p=5, weights=distance ............................ [CV] ............. n_neighbors=9, p=5, weights=distance, total= 0.8s [Parallel(n_jobs=1)]: Done 120 out of 120 | elapsed: 49.7s finished 0.9860821155184412 {'n_neighbors': 2, 'p': 2, 'weights': 'distance'} #所以最好的参数为k=2,p=2

    网格搜索是将这一部分封装成了一个函数,使其具有广泛使用意义。

    #寻找最佳超参数 for k in range(2,10):#将邻居的数量最大值为10个 for p in range(1,6):#使用1到5种距离计算方法 knn=KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)#权重计算weights使用距离权重,k,p通过不断的循环来找最佳值 all_score=cross_val_score(knn,X_train,y_train,cv=3)#cv:折数,三种验证。交叉验证后会得出一组同超参数下的分数结果集 score=all_score.mean()#取平均分作为这组超参数模型的最终得分 print('邻居数K:',k) print('距离算法:',p) print('这组超参数的平均得分:',score) if score>best_score: best_k,best_p,best_score=k,p,score print('得分最高的超参数:','score=',best_score,'K=',best_k,'p=',best_p)

    网格搜索中又包含了交叉验证,第4部分和第5部分大同小异,但第4部分用了循环来模拟网格搜索,再使用交叉验证。 网格搜索实用少参数的模型,如果参数很多,那就非常多的参数组合,运算速度非常慢。且数据集多的话,运行起来也慢。 本次实例网格搜索共用时1.5min,共得出120种参数组合,速度可见一斑。

    KNN近邻算法做分类比较适合,用来做回归得分就比较一般了,不过胜在简单,不需要训练出模型(函数方程),只需要对每个点做距离运算,找出邻居点,根据邻居点的值来决定自己的值。

    Processed: 0.028, SQL: 8