knn闽南语是什么意思_史上最全面K近邻算法/KNN算法详解+python实现

本文根据贪心学院付费课程学习笔记整理而来,参见课程网址:AI教AI。

本文github源代码网址:

https://github.com/ZhiHuDaShiJie/Fundamentals-of-Machine-Learning/blob/main/2.1%20KNN.ipynb​github.com https://github.com/ZhiHuDaShiJie/Fundamentals-of-Machine-Learning/blob/main/2.2%20KNN%20decision%20boundary%20visualization.ipynb​github.com ZhiHuDaShiJie/Fundamentals-of-Machine-Learning​github.com

本文目录:

1. KNN算法的核心思想

2. 用sklearn实现KNN代码讲解

3. KNN具体的实现步骤详解

4. 用python从零开始实现一个KNN算法

5. K近邻的决策边界以及决策边界的python可视化实现

6.用交叉验证选择超参数K

7. 用特征缩放解决KNN算法的潜在隐患

8. KNN 算法总结

以下为正文

1. KNN算法的核心思想

KNN是一个极其简单的算法,中文叫K近邻算法。

算法虽然简单,但非常有效,即便深度学习横行的今天,很多的问题其实都可以使用KNN来解决。KNN主要用于分类问题,但这不意味着不能解决回归问题,只不过回归不是它的主战场罢了

对于KNN,可以用两句话来做个总结。首先,它是最容易理解的机器学习算法。学习编程语言的第一步通常是学习编写"Hello World",那对于机器学习来说,KNN就像是一个Hello World算法。

其次,KNN是最容易实现的算法,即便从零开始实现我们完全可以通过少于五行代码来编写KNN,特别简单。

下面的图片讲述了KNN的核心思想。

1a49e59d8318b6d848ff3cead2551f3e.png

总结起来,给定一个预测目标(上图中的是否给张三offer),接下来计算预测预测目标和所有样本之间的距离或者相似度(根据已有的特征,如上图中的应聘者之前工作经验年限和之前的工资水平),然后选择距离最近的前K个样本,然后通过这些样本来投票决策。

这个好比一个人如果跟很多AI工程师有交集,即可以预测这个人也是从事AI工作的。

是否可以选择不同的K值?

  • K个数是人为定义的,可以通过一些技巧来选择合适的K值。

不同的K值会对算法有影响吗?

  • K值当然对算法是有影响的,所以也是KNN里最为重要的一个参数。下面来看一下K值的不一样对预测产生什么样的影响。

K设置为奇数的主要好处是什么吗?

  • 一般对于二分类问题来说,把K设置为奇数是容易防止平局的现象。但对于多分类来说,设置为奇数未必一定能够防平局。

2. 用sklearn实现KNN代码讲解

from sklearn import datasets    
from sklearn.model_selection import train_test_split    
from sklearn.neighbors import KNeighborsClassifier   
import numpy as np    

第一个import是用来导入一个样本数据。sklearn库本身已经提供了不少可以用来测试模型的样本数据,所以通过这个模块的导入就可以直接使用这些数据了。 第二个import是用来做数据集的分割,把数据分成训练集和测试集,这样做的目的是为了评估模型。第三个是导入了KNN的模块,是sklearn提供的现成的算法。

iris = datasets.load_iris()    
X = iris.data    
y = iris.target    
print (X, y)

这几行代码是用来导入数据集的。在这里我们导入的数据集叫做iris数据集,也是开源数据中最为重要的数据集之一。这个数据包含了3个类别,所以适合的问题是分类问题。另外,具体数据集的描述可以参考:https://archive.ics.uci.edu/ml/datasets/Iris/ 从print(x,y)结果可以看到X拥有四个特征,并且标签y拥有0,1,2三种不同的值。

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2003)

在这里X存储的是数据的特征,y存储的每一个样本的标签或者分类。我们使用 train_test_split来把数据分成了训练集和测试集。主要的目的是为了在训练过程中也可以验证模型的效果。如果没有办法验证,则无法知道模型训练的好坏。
这里的random_state就像随机生成器中的seed。通过不同的值采样的训练数据和测试数据是不一样的。设定一个固定的random_state有助于诊断程序本身,因为每次所期待的结果都会一样。

lf = KNeighborsClassifier(n_neighbors=3)    
clf.fit(X_train, y_train)

这部分是KNN算法的主要模块。首先在这里我们定义了一个KNN object,它带有一个参数叫做n_neighbors=3, 意思就是说我们选择的K值是3.

correct = np.count_nonzero((clf.predict(X_test)==y_test)==True)    
print ("Accuracy is: %.3f" %(correct/len(X_test)))

这部分的代码主要用来做预测以及计算准确率。计算准确率的逻辑也很简单,就是判断预测和实际值有多少是相等的。如果相等则算预测正确,否则预测失败。

3. KNN具体的实现步骤详解

由于sklearn已经封装好了KNN算法,所以用起来很简单。但它的具体的实现细节又如何呢?
为了实现一个KNN算法,我们需要具备四个方面的信息。

特征工程

  • 第一、特征工程,把一个物体表示成向量、矩阵、张量等数量化的信息。任何的算法的输入一定是数量化的信息,我们把它叫做特征,需要把现实生活中的物体通过数字化的特征来进行描述。

0b2d388d0052db589857f5e4007fb1e2.png

样本标注

  • 第二、由于KNN是监督学习算法,所以需要提前标注好的样本。

7487f082136f48bb7ccfd0054e2f1da8.png

相似度计算

  • 第三、我们需要想办法来计算两个样本之间的距离或者相似度,之后才能选出最相近的样本。欧氏距离是一个最简单常用的方法,当然还有其他方法。

da6f9788e39cc21d1159a06359fde734.png

选择最合适的K值

  • 第四、需要知道如何选择最合适的K值,下面会单独讲如何选择K。

4. 用python从零开始实现一个KNN算法

from sklearn import datasets
from collections import Counter  # 为了做投票
from sklearn.model_selection import train_test_split
import numpy as np

# 导入iris数据
iris = datasets.load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2003)


def euc_dis(instance1, instance2):
    """
    计算两个样本instance1和instance2之间的欧式距离
    instance1: 第一个样本, array型
    instance2: 第二个样本, array型
    """
    # TODO
    dist = np.sqrt(np.sum((instance1-instance2)**2))
    return dist
    
    
def knn_classify(X, y, testInstance, k):
    """
    给定一个测试数据testInstance, 通过KNN算法来预测它的标签。 
    X: 训练数据的特征
    y: 训练数据的标签
    testInstance: 测试数据,这里假定一个测试数据 array型
    k: 选择多少个neighbors? 
    """
    # TODO  返回testInstance的预测标签 = {0,1,2}

    # 计算 testInstance 与 X的距离
    dists=[euc_dis(x,testInstance) for x in X]
   
    # 找出最近的K个元素的idx
    idxknn= np.argsort(dists)[:k] #将dists从小到大排序,返回排序后的元素indices

    # 找出KNN对应的n个y值
    yknn=y[idxknn]

    # 返回投票结果
    return Counter(yknn).most_common(1)[0][0]


# 预测结果。    
predictions = [knn_classify(X_train, y_train, data, 3) for data in X_test]
correct = np.count_nonzero((predictions==y_test)==True)
print ("Accuracy is: %.3f" %(correct/len(X_test)))

5. K近邻的决策边界以及决策边界的python可视化实现

决策边界

c87e406237e5138952c308a7178491a0.png
  • 当华为Mate40降价到2000元的时候购买一个。对于这个问题,我的决策边界是2000元,也就是大于2000元的时候我不会购买,但小于2000元时我会选择购买,类似的生活中的例子很多。

7b001b7d0567b9c40d3364510ae42eb4.png
  • 决策边界分成两大类,分别是线性决策边界和非线性决策边界。拥有线性决策边界的模型我们称为线性模型,反之非线性模型。
  • 线性分类器与非线性分类器的唯一分类标准是看它的决策边界是不是线性的。
  • 模型的泛化能力,可以简单理解成“它在新的环境中的适应能力”,当然这个环境需要跟已有的环境类似才行。

K=1,KNN决策边界如何去找?

  • 把平面均匀的分为很多像素点,按KNN算法依次标记每一个像素点的类别,标记完之后,决策边界就出来了。

b5f1e2dc9fa7db1d54548607c128fbce.png
  • 决策边界的特点: 决策边界上的值可以属于分类问题中的两类的任何一类。

决策边界的可视化实现

  • 导入numpy以及pyplot模块,还有KNN模块。这里有个模块叫做product,这主要是用在可视化模块。
import matplotlib.pyplot as plt    
import numpy as np    
from itertools import product    
from sklearn.neighbors import KNeighborsClassifier 
  • 在这里我们随机生成了样本。其中一半的数据来源于第一个高斯分布,另一半的数据来自于第二个高斯分布。
# 生成一些随机样本    
n_points = 100    
X1 = np.random.multivariate_normal([1,50], [[1,0],[0,10]], n_points)    
X2 = np.random.multivariate_normal([2,50], [[1,0],[0,10]], n_points)    
X = np.concatenate([X1,X2])    
y = np.array([0]*n_points + [1]*n_points)    
print (X.shape, y.shape)    
  • 训练多个KNN模型,不同的KNN模型的参数K选择不一样。这里我们训练了9个不同的KNN模型。
# KNN模型的训练过程    
clfs = []    
neighbors = [1,3,5,9,11,13,15,17,19]    
for i in range(len(neighbors)):    
    clfs.append(KNeighborsClassifier(n_neighbors=neighbors[i]).fit(X,y))     
  • 下面这部分的主要代码是用来展示结果。
# 可视化结果    
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1    
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1    
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1), 
                     np.arange(y_min, y_max, 0.1)) 
f, axarr = plt.subplots(3,3, sharex='col', sharey='row', figsize=(15, 12))   
for idx, clf, tt in zip(product([0, 1, 2], [0, 1, 2]), clfs,['KNN (k=%d)'%k for k in neighbors]):   
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) 
    Z = Z.reshape(xx.shape)    
    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.4)    
    axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k') 
    axarr[idx[0], idx[1]].set_title(tt)    
plt.show()    
  • 从这个图片里是否可以看出一些规律呢?

b8de75545e24fd1506aa3e709e54892d.png

很容易看出K值的增加会导致决策边界越来越平。

  • 问题:是否决策边界越平滑越好呢? 并不是。那如何选择K呢?
  • 答案就是交叉验证(cross validation)

6.用交叉验证选择超参数K

  • 交叉验证的第一步是把训练数据进一步分成训练集和验证集。我们需要一种评估的机制来选出最好的K值。那具体用什么数据来评估呢? 就是验证集! 因为测试数据是用来一次性测试的。比如上线前来测试是否满足上线的条件,但测试数据不能用于指导模型的训练。
  • 常用的交叉验证技术叫做K折交叉验证(K-fold Cross Validation)。 我们先把训练数据再分成训练集和验证集,之后使用训练集来训练模型,然后再验证集上评估模型的准确率。举个例子,比如一个模型有个参数叫α,我们一开始不清楚要选择0.1还是1,所以这时候我们进行了交叉验证:把所有训练集分成K块,依次选其中一块作为验证集,然后求在K个验证集上的性能平均值作为评估α值的性能。

例如:

d2e3a1651bb1f6b175d0cf1e323e199c.png

超参数

针对不同的取值,逐一尝试从而选择最好的,这种参数我们称作超参数(Hyperparameter)。

在交叉验证中经常使用K折交叉验证,就是在已有的数据上重复做多次的验证。

为什么需要这么做?而不是只做一次?如果只测试一次,有可能结果中包含偶然性,但做多次就可以得到比较稳定的结果。

K的取值

其实K可以取任意的数。问题:什么时候需要取较大的K值?

  • 一般情况下数据量较少的时候我们取的K值会更大,为什么呢? 因为数据量较少的时候如果每次留出比较多的验证数据,对于训练模型本身来说是比较吃亏的,所以这时候我们尽可能使用更多的数据来训练模型。由于每次选择的验证数据量较少,这时候K折中的K值也会随之而增大,但到最后可以发现,无论K值如何选择,用来验证的样本个数都是等于总样本个数。
  • 最极端的情况下,我们可以采用leave_one_out交叉验证,也就是每次只把一个样本当做验证数据,剩下的其他数据都当做是训练样本。(留一法)

从零开始实现KNN的交叉验证代码

import numpy as np
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import KFold  #  主要用于K折交叉验证

# 以下是导入iris数据集
iris = datasets.load_iris()
X = iris.data
y = iris.target
print (X.shape, y.shape)

# 定义我们想要搜索的K值(候选集),这里定义8不同的值
ks = [1,3,5,7,9,11,13,15]

# 进行5折交叉验证, KFold返回的是每一折中训练数据和验证数据的index
# 假设数据样本为: [1,3,5,6,11,12,43,12,44,2], 总共10个样本
# 则返回的kf的格式为(前面的是训练数据,后面的是验证集):
# [0,1,3,5,6,7,8,9], [2,4]
# [0,1,2,4,6,7,8,9], [3,5]
# [1,2,3,4,5,6,7,8], [0,9]
# [0,1,2,3,4,5,7,9], [6,8]
# [0,2,3,4,5,6,8,9], [1,7]
kf = KFold(n_splits = 5, random_state=2001, shuffle=True)

# 保存当前最好的K值和对应的准确率值
best_k = ks[0]
best_score = 0

# 循环每一个K值

for k in ks:
    curr_score = 0
    for train_index, valid_index in kf.split(X):    
        # 每一折的训练以及计算准确率
        clf = KNeighborsClassifier(n_neighbors=k)
        clf.fit(X[train_index], y[train_index])
        curr_score = curr_score + clf.score(X[valid_index], y[valid_index])
    # 求一下5折的平均准确率
    avg_score = curr_score/5 
    if avg_score > best_score:
        best_k = k
        best_score = avg_score
    print ("current best score is: %.2f"%best_score, "best k: %d"%best_k)
print ("after cross validation, the final best k is: %d"%best_k)

使用sklearn内置函数实现K折交叉验证

from sklearn.model_selection import GridSearchCV # 通过网格方式来搜索参数
from sklearn import datasets
from sklearn.neighbors import KNeighborsClassifier

# 导入iris是数据
iris = datasets.load_iris()
X = iris.data
y = iris.target

# 设置需要搜索的K值, 'n_neighbors'是sklearn中KNN的参数
parameters = {'n_neighbors':[1,3,5,7,9,11,13,15]}
knn = KNeighborsClassifier()  # 注意:在这里不用指定参数

# 通过GridSearchCV来搜索最好的K值。 这个模块的内部其实
# 就是对于每一个K值做了评估
clf = GridSearchCV(knn, parameters, cv=5)   
clf.fit(X, y)

# 输出最好的参数以及对应的准确率
print ("best score is: %.2f"%clf.best_score_, "  best param: ",clf.best_params_)

K折交叉验证中的其他注意事项

那K值是如何提前设定好的呢? 答案是人为的方式提前都列好的。

那如何列出比较靠谱的候选K值呢?对于KNN来讲,我们一般从K=1开始尝试,但不会选择太大的K值。而且这也取决于计算硬件,因为交叉验证是特别花时间的过程,因为逐个都要去尝试。

那有没有办法去提供交叉验证的效率呢? 最简单的方法就是通过并行化、分布式的处理。针对于不同值的交叉验证之间是相互独立的,完全可以并行化处理。

最后,对于交叉验证,必须要记住下面的一句话:绝对不能把测试数据用在交叉验证的过程中,测试数据的作用永远是做最后一步的测试,看是否模型满足上线的标准,但绝对不能参与到模型的训练。这个一定要记住,这一点非常重要!

好了,交叉验证已经理解了。但有一些技术其实还没有讲到。例如,当一个模型拥有很多参数需要调节的时候怎么办? 因为KNN里一般只要调节K值即可,但对于很多其他模型来说,需要调节的参数可能有多个,而且所有排列组合又特别多,怎么办?这个问题我们将在之后的章节里会做详细的讨论。

7. 用特征缩放解决KNN算法的潜在隐患

在KNN算法中我们使用了经典的欧式距离。其中的潜在隐患是什么呢?

529ff3d12daa277b636b44424ab0036e.png

来看一下上面的这幅图。跟之前的例子相比,唯一的区别是我们把X轴的单位从千元改成了元。这使得原来Y=13变成了Y=13000。

仔细观察一下,这将会对KNN带来什么样的问题呢?

  • 这样的改变其实对KNN的影响是非常大的,因为Y轴的单位变化了,数值也变大了。这样的改变使得距离计算过程中Y轴的影响倍增了很多,甚至工作经验基本上起不到什么作用了。

所以,这也给了我们一个提示,在使用KNN算法的时候,特征值上的范围的差异对算法影响非常大。

其实不仅仅是KNN算法,只要依赖于距离计算的算法都有这个问题,比如之后要讲到的K-means聚类算法等等。

为了解决这个问题,在应用KNN之前我们经常使用标准化的操作,也就是把特征映射到类似的量纲空间,目的是不让某些特征的影响变得太大。

特征缩放的方法

总体来讲,有两种常见的特征标准化的方法。它们分别是线性归一化和标准差归一化。其中,线性归一化指的是把特征值的范围映射到[0,1]区间,标准差标准化的方法使得把特征值映射到均值为0,标准差为1的正态分布。接下来我分别对这两个方法做详细的讲解。

ead21c5b6a3976fcccac34f1a454fa32.png

线性归一化 (min-max normalization)

d242829f8d82567e4ec04eb947c24a1e.png

标准差标准化 (Z-score normalization / standernazation)

5d450ecf2bfac5a29eaca22f5568a709.png

哪一个方法更好呢?

这其实很难说,一般情况下还是需要尝试,通过效果来得到结论。对于有些问题第一种方案可能比较好,在其他问题上第二种方案或许更好,所以不一定的。

8. KNN 算法总结

  1. KNN 非常简单
  2. 适合低维空间问题(a.高维特征中不相干的特征会降低重要特征对距离的影响;b.维度越高,算法时间复杂度越高。)
  3. KNN算法本质上没有训练过程
  4. KNN预测过程时间复杂度与样本个数线性相关。

face26f4a7aef54882301b4a953faa8d.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值