机器学习-k近邻算法kNN实战

153 阅读9分钟

什么是KNN近邻算法

为什么先学习KNN近邻算法呢? 对于机器学习,开发者需要一定的数学知识,而对于一些入门开发者来说,数学可能并不是那么擅长,而KNN算法对数学要求几乎为0,并且KNN是一个非常简单的算法,所以非常适合大家入门学习的一个算法

现在我们使用一个二维平面来展示我们的数据

  • X: 代表的是肿瘤的大小
  • Y:代表发现的天数
  • 绿色:代表是良性的
  • 红色:代表是恶性的

image.png

这时候来了一个新的病人,他的肿瘤大小和发现时间就构成了平面的一个点,也就是灰色的点,那么我们如何判断这个肿瘤是良性的还是恶性的呢?

image.png

kNN算法就是这样的一个算法,首先先根据实验或者经验值定义一个K的值,比如这里的K=3,至于这个K值取多少,后面会解释,那么,如何k=3的话,对于每一个新的点,kNN会计算新的点与每个点之间的距离,并取距离最近的三个点,然后以这三个点进行投票

我们可以看到距离最近的三个点都是红色的,所以红色:绿色 = 3:0,所以这个新来的点大概率就是恶性的 image.png

如果继续来了一个新的数据,如下图,这时候红色:绿色 = 2:1,那么这个新来的点大概率还是恶性的

image.png

scikit-learn对KNN算法的封装案例

from sklearn.neighbors import KNeighborsClassifier
import numpy as np
from matplotlib import pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

#加载数据,
iris = datasets.load_iris()

#赋值,x是属性,y是结果,根据x来预测出y的值
x = iris.data
y = iris.target

#划分训练集和测试数据集,测试数据集占总数据量的30%
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3)

#定义KNeighborsClassifier对象,n_neighbors的值就是上文提到的 **k** 的值
kncf = KNeighborsClassifier(n_neighbors=3)

#训练
kncf.fit(x_train,y_train)

#通过测试数据集来计算模型的准确度
y_test_predict = accuracy_score(x_test)

#打印出结果
print(y_test_accuracy)

可以看到该模型的准确度达到了 97.77%,说明我们训练的这个模型准确度还是很不错的

image.png

训练数据集,测试数据集

在上面的案例中,我们把原数据分为了训练数据集和测试数据集,这是为什么呢?

假设我们拿所有的数据来训练我们的模型,这时候我们是直接拿这个模型上真实环境使用吗? 如果我们这个模型很差怎么办? 使用这种模式的话我们根本没机会去调整我们的模型,上线之后也只能是听天由命了

有些人会说,那重新找一份测试数据不就行了吗?

我们是通过喂给机器大量的数据来进行训练,然后去预测,那么前提是你喂给机器的数据要真实有效的,不能是瞎编的数据,不然训练出来的模型误判率会很高,举个例子,像我们的征信,一般来说,银行会根据用户最近几年的信用来做一个信用度的评分,那么这时候你的测试数据集从哪来呢? 你把现在所有的数据都用来训练了,那么测试数据你就要再等很长的时间才会有

一般来说我们会把原数据分为训练数据集测试数据集

训练数据集用来训练模型,测试数据集用来校验模型的好坏以及调整我们的模型

sklearn已经提供了对应的方法来划分了

from sklearn.model_selection import train_test_split

#对数据进行划分
#test_size=0.3 表示的是测试数据集占总的数据的30%
x_train,x_test,y_train,y_train = train_test_split(x,y,test_size=0.3)

分类的准确度

分类准确度直接决定了我们训练的模型的好坏,分类准确度越高说明我们模型训练的越好 sklearn中也提供计算模型准确度的方法

from sklearn.metrics import accuracy_score

#通过测试数据集来计算模型的准确度
y_test_predict = accuracy_score(x_test)

#打印出模型准确度
print(y_test_accuracy)

超参数

在上文我们提到过kNN中的k值,上面的案例我们也是直接给设置成了3,但是在真实环境中,我们这个值到底该设置成多少呢?是传3呢还是传5呢,或者是其它的值??? 这就涉及到机器学习中非常重要的一个概念了,也就是所谓的 超参数, 与之相对应的还有一个是 模型参数

  • 超参数:就是在模型运行前决定的参数
  • 模型参数:是在算法过程中学习的,是属于这个模型的

对于kNN算法来说,是没有模型参数的,后续线性回归中会有涉及到

#定义KNeighborsClassifier对象,n_neighbors的值就是上文提到的 **k** 的值
kncf = KNeighborsClassifier(n_neighbors=3)

image.png

可以看到我们k的值是对应的参数:n_neighbors,超参数是在模型运行前决定的,那有哪几种方式可以来确定这个值呢?

  • 1:领域知识

    在不同的领域中,领域的知识是有意义的。通常在不同的领域中,面对不同的问题,有可能那个最好的超参数是不一样的,而这个最好的超参数是可以通过这个领域的知识所得到的

  • 2:经验

    根据以往的经验来决定这个值,就比如官网给的默认值是5,那么这个值在经验上就是比较好的值

  • 3:实验搜索

    通过给定不同的参数值,来计算出最终的结果

那么如何通过实验搜索来找到最优的k值呢??


#定义最优分数初始值
best_score = 0.0

#初始化最优的k
index = 0

#我们测试k的值从110的最优解
for i in range(1,10):
    kncf = KNeighborsClassifier(n_neighbors=i)
    kncf.fit(x_train,y_train)
    y_test_predict = kncf.predict(x_test)
    y_accuracy_score = accuracy_score(y_test,y_test_predict)
    if y_accuracy_score > best_score:
        best_score = y_accuracy_score
        index = i

#打印出结果
print("best_score=",best_score)
print("index=" ,index)

image.png

我们可以看到,计算出来的最优k的值就是3

但是这里有个注意点我们这里是从1-10,如果计算出来的最优k值是10的话,那么我们还需要再将k的范围从 8~20再测试一遍

网格搜索与kNN更多超参数

对于之前的k这个超参数来说,我们是通过写的for循环来搜索最优的k值,但是kNN不止一个超参数,比如还有距离权重weights,有些参数之间还有关联,就比如p这个参数只有在wrights=distance的时候才有效,

  • weights="dintance"的时候 image.png

  • wights="uniform"的时候

image.png

为了方便我们查找最优的超参数,为我们封装了一个专门的网格,搜索的方式叫 grid search

我们如何使用这个网格搜索呢?

首先我们要定义我们搜索的参数


# n_neighbors从1~10,p从1~5
param_grid = [
    {
        'weights':['uniform'],
        'n_neighbors': [i for i in range(1,11)]
    },
    {
        'weights':['distance'],
        'p': [i for i in range(1,6)],
        'n_neighbors': [i for i in range(1,11)]
    }
]


knn_clf = KNeighborsClassifier()
from sklearn.model_selection import GridSearchCV

grid_search = GridSearchCV(knn_clf,param_grid)

#训练
grid_search.fit(x_train,y_train)

#获取最优参数
grid_search.best_params_

#获取分类结果
grid_search.best_score_

#获取最优的参数值,不展示的参数值就是使用默认值
grid_search.best_estimator_

image.png

数据归一化

在之前我们实现的kNN算法案例中,缺少了很重要的一步,那就是 数据归一化 ,首先我们来看看为什么要实现数据归一化

就比如我们的数据是员工的工资,可能其中80%的员工工资都在7000左右,但是有少部分的人工资却可以达到10万左右

在kNN算法的思想是计算这个点与每个点的距离,但是其中有一部分的数据特征范围远大于其它特征,如果直接进行计算的话,则距离计算时尺度较大的特征可能会使得整体距离的计算不准确。因此,归一化后每个特征都在相同的尺度上进行计算,从而能更准确地评估数据点之间的相似性。

最值归一化

image.png

把所有的值都映射在0~1之间,适用于分布有明显边界的情况,就比如学生的考试成绩,最低分是0,最高分就是150

   #初始化一个502列的随机矩阵
   X = np.random.randint(0,100,(50,2))
   X = np.array(X,dtype=float)
   
   ### 最终归一化
   X[:,0] = (X[:,0] - np.min(X[:,0])) / (np.max(X[:,0]) - np.min(X[:,0])) 
   X[:,1] = (X[:,1] - np.min(X[:,1])) / (np.max(X[:,1]) - np.min(X[:,1]))
   
   #结果可视化
   plt.scatter(X[:,0],X[:,1])
   plt.show()

image.png

均值方差归一化

image.png

适用于分布没有明显边界的情况


Y = np.random.randint(0,100,(50,2))
Y = np.array(Y,dtype=float)

#均值方差归1化
Y[:,0] = (Y[:,0] - np.mean(Y[:,0])) / np.std(Y[:,0])
Y[:,1] = (Y[:,1] - np.mean(Y[:,1])) / np.std(Y[:,1])

plt.scatter(Y[:,0],Y[:,1])
plt.show()

image.png

sklearn中封装的Scaler

sklearn提供了一个StandardScaler,官方地址:scikit-learn.org/stable/modu…

    #加载数据
    iris = datasets.load_iris()
    
    #初始化值
    x = iris.data
    y = iris.target
    
    #划分训练数据集,测试数据集
    x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3)
    
    #对训练集进行归一化
    x_train = standardScaler.transform(x_train)

    #对测试数据集归一化
    x_test = standardScaler.transform(x_test)
    
    #初始化模型
    kncf = KNeighborsClassifier(n_neighbors=3,weights="uniform")
    
    #训练
    kncf.fit(x_train,y_train)
    
    #模型预测
    y_test_predict = kncf.predict(x_test)
    
    #计算
    y_predict_score = accuracy_score(y_test,y_test_predict)
    print(y_predict_score)

image.png

总结

我们现在来总结一下机器学习的总体流程

image.png

  • 1:对数据进行训练集和测试集的数据划分
  • 2:对数据进行归一化
  • 3:使用网格搜索寻找最好的超参数
  • 4:使用训练集对模型进行训练
  • 5:使用测试集来检验模型的分类准确度