机器学习经典算法之KNN

1、前言
KNN 的英文叫 K-Nearest Neighbor,应该算是数据挖掘算法中最简单的一种。
先用一个例子体会下。

/*请尊重做者劳动成果,转载请标明原文连接:*/html

/* https://www.cnblogs.com/jpcflyer/p/11111817.html * /git

假设,咱们想对电影的类型进行分类,统计了电影中打斗次数、接吻次数,固然还有其余的指标也能够被统计到,以下表所示。
咱们很容易理解《战狼》《红海行动》《碟中谍 6》是动做片,《前任 3》《春娇救志明》《泰坦尼克号》是爱情片,可是有没有一种方法让机器也能够掌握这个分类的规则,当有一部新电影的时候,也能够对它的类型自动分类呢?
咱们能够把打斗次数当作 X 轴,接吻次数当作 Y 轴,而后在二维的坐标轴上,对这几部电影进行标记,以下图所示。对于未知的电影 A,坐标为 (x,y),咱们须要看下离电影 A 最近的都有哪些电影,这些电影中的大多数属于哪一个分类,那么电影 A 就属于哪一个分类。实际操做中,咱们还须要肯定一个 K 值,也就是咱们要观察离电影 A 最近的电影有多少个。
 
 
2、KNN 的工做原理
“近朱者赤,近墨者黑”能够说是 KNN 的工做原理。整个计算过程分为三步:
1. 计算待分类物体与其余物体之间的距离;
2. 统计距离最近的 K 个邻居;
3.对于 K 个最近的邻居,它们属于哪一个分类最多,待分类物体就属于哪一类。
 
K 值如何选择
你能看出整个 KNN 的分类过程,K 值的选择仍是很重要的。那么问题来了,K 值选择多少是适合的呢?
若是 K 值比较小,就至关于未分类物体与它的邻居很是接近才行。这样产生的一个问题就是,若是邻居点是个噪声点,那么未分类物体的分类也会产生偏差,这样 KNN 分类就会产生过拟合。
若是 K 值比较大,至关于距离过远的点也会对未知物体的分类产生影响,虽然这种状况的好处是鲁棒性强,可是不足也很明显,会产生欠拟合状况,也就是没有把未分类物体真正分类出来。
因此 K 值应该是个实践出来的结果,并非咱们事先而定的。在工程上,咱们通常采用交叉验证的方式选取 K 值。
交叉验证的思路就是,把样本集中的大部分样本做为训练集,剩余的小部分样本用于预测,来验证分类模型的准确性。因此在 KNN 算法中,咱们通常会把 K 值选取在较小的范围内,同时在验证集上准确率最高的那一个最终肯定做为 K 值。
 
距离如何计算
在 KNN 算法中,还有一个重要的计算就是关于距离的度量。两个样本点之间的距离表明了这两个样本之间的类似度。距离越大,差别性越大;距离越小,类似度越大。
关于距离的计算方式有下面五种方式:
欧氏距离;
曼哈顿距离;
闵可夫斯基距离;
切比雪夫距离;
余弦距离。
其中前三种距离是 KNN 中最经常使用的距离,如今分别讲解下。
 
欧氏距离是咱们最经常使用的距离公式,也叫作欧几里得距离。在二维空间中,两点的欧式距离就是:
同理,咱们也能够求得两点在 n 维空间中的距离:
曼哈顿距离
在几何空间中用的比较多。曼哈顿距离等于两个点在坐标系上绝对轴距总和。用公式表示就是:
 
闵可夫斯基距离
不是一个距离,而是一组距离的定义。对于 n 维空间中的两个点 x(x1,x2,…,xn) 和 y(y1,y2,…,yn) , x 和 y 两点之间的闵可夫斯基距离为:
其中 p 表明空间的维数,当 p=1 时,就是曼哈顿距离;当 p=2 时,就是欧氏距离;当 p→∞时,就是切比雪夫距离。
那么切比雪夫距离 怎么计算呢?二个点之间的切比雪夫距离就是这两个点坐标数值差的绝对值的最大值,用数学表示就是:max(|x1-y1|,|x2-y2|)。
余弦距离 实际上计算的是两个向量的夹角,是在方向上计算二者之间的差别,对绝对数值不敏感。在兴趣相关性比较上,角度关系比距离的绝对值更重要,所以余弦距离能够用于衡量用户对内容兴趣的区分度。好比咱们用搜索引擎搜索某个关键词,它还会给你推荐其余的相关搜索,这些推荐的关键词就是采用余弦距离计算得出的。
 
KD 树
其实从上文你也能看出来,KNN 的计算过程是大量计算样本点之间的距离。为了减小计算距离次数,提高 KNN 的搜索效率,人们提出了 KD 树(K-Dimensional 的缩写)。KD 树是对数据点在 K 维空间中划分的一种数据结构。在 KD 树的构造中,每一个节点都是 k 维数值点的二叉树。既然是二叉树,就能够采用二叉树的增删改查操做,这样就大大提高了搜索效率。
在这里,咱们不须要对 KD 树的数学原理了解太多,你只须要知道它是一个二叉树的数据结构,方便存储 K 维空间的数据就能够了。并且在 sklearn 中,咱们直接能够调用 KD 树,很方便。
 
用 KNN 作回归
KNN 不只能够作分类,还能够作回归。首先讲下什么是回归。在开头电影这个案例中,若是想要对未知电影进行类型划分,这是一个分类问题。首先看一下要分类的未知电影,离它最近的 K 部电影大多数属于哪一个分类,这部电影就属于哪一个分类。
若是是一部新电影,已知它是爱情片,想要知道它的打斗次数、接吻次数多是多少,这就是一个回归问题。
那么 KNN 如何作回归呢?
对于一个新点,咱们须要找出这个点的 K 个最近邻居,而后将这些邻居的属性的平均值赋给该点,就能够获得该点的属性。固然不一样邻居的影响力权重能够设置成不一样的。举个例子,好比一部电影 A,已知它是动做片,当 K=3 时,最近的 3 部电影是《战狼》,《红海行动》和《碟中谍 6》,那么它的打斗次数和接吻次数的预估值分别为 (100+95+105)/3=100 次、(5+3+31)/3=13 次。
 
3、 如何在 sklearn 中使用 KNN
接下来,咱们先看下如何在 sklearn 中使用 KNN 算法,而后经过 sklearn 中自带的手写数字数据集来进行实战。
在 Python 的 sklearn 工具包中有 KNN 算法。KNN 既能够作分类器,也能够作回归。若是是作分类,你须要引用:
1 from sklearn.neighbors import KNeighborsClassifier
若是是作回归,你须要引用:
1 from sklearn.neighbors import KNeighborsRegressor
从名字上你也能看出来 Classifier 对应的是分类,Regressor 对应的是回归。通常来讲若是一个算法有 Classifier 类,都能找到相应的 Regressor 类。好比在决策树分类中,你可使用 DecisionTreeClassifier,也可使用决策树来作回归 DecisionTreeRegressor。
好了,咱们看下如何在 sklearn 中建立 KNN 分类器。
这里,咱们使用构造函数 KNeighborsClassifier(n_neighbors=5, weights=‘uniform’, algorithm=‘auto’, leaf_size=30),这里有几个比较主要的参数,我分别来说解下:
1.n_neighbors:即 KNN 中的 K 值,表明的是邻居的数量。K 值若是比较小,会形成过拟合。若是 K 值比较大,没法将未知物体分类出来。通常咱们使用默认值 5。
2.weights:是用来肯定邻居的权重,有三种方式:
weights=uniform,表明全部邻居的权重相同;
weights=distance,表明权重是距离的倒数,即与距离成反比;
自定义函数,你能够自定义不一样距离所对应的权重。大部分状况下不须要本身定义函数。
3.algorithm:用来规定计算邻居的方法,它有四种方式:
algorithm=auto,根据数据的状况自动选择适合的算法,默认状况选择 auto;
algorithm=kd_tree,也叫做 KD 树,是多维空间的数据结构,方便对关键数据进行检索,不过 KD 树适用于维度少的状况,通常维数不超过 20,若是维数大于 20 以后,效率反而会降低;
algorithm=ball_tree,也叫做球树,它和 KD 树同样都是多维空间的数据结果,不一样于 KD 树,球树更适用于维度大的状况;
algorithm=brute,也叫做暴力搜索,它和 KD 树不一样的地方是在于采用的是线性扫描,而不是经过构造树结构进行快速检索。当训练集大的时候,效率很低。
4.leaf_size:表明构造 KD 树或球树时的叶子数,默认是 30,调整 leaf_size 会影响到树的构造和搜索速度。
建立完 KNN 分类器以后,咱们就能够输入训练集对它进行训练,这里咱们使用 fit() 函数,传入训练集中的样本特征矩阵和分类标识,会自动获得训练好的 KNN 分类器。而后可使用 predict() 函数来对结果进行预测,这里传入测试集的特征矩阵,能够获得测试集的预测分类结果。
 
4、 如何用 KNN 对手写数字进行识别分类
手写数字数据集是个很是有名的用于图像识别的数据集。数字识别的过程就是将这些图片与分类结果 0-9 一一对应起来。完整的手写数字数据集 MNIST 里面包括了 60000 个训练样本,以及 10000 个测试样本。若是你学习深度学习的话,MNIST 基本上是你接触的第一个数据集。
今天咱们用 sklearn 自带的手写数字数据集作 KNN 分类,你能够把这个数据集理解成一个简版的 MNIST 数据集,它只包括了 1797 幅数字图像,每幅图像大小是 8*8 像素。
 
咱们先来规划下整个 KNN 分类的流程:
数据加载:咱们能够直接从 sklearn 中加载自带的手写数字数据集;
准备阶段:在这个阶段中,咱们须要对数据集有个初步的了解,好比样本的个数、图像长什么样、识别结果是怎样的。你能够经过可视化的方式来查看图像的呈现。经过数据规范化可让数据都在同一个数量级的维度。另外,由于训练集是图像,每幅图像是个 8*8 的矩阵,咱们不须要对它进行特征选择,将所有的图像数据做为特征值矩阵便可;
分类阶段:经过训练能够获得分类器,而后用测试集进行准确率的计算。
 
好了,按照上面的步骤,咱们一块儿来实现下这个项目。
首先是加载数据和对数据的探索:
 1 # 加载数据
 2 digits = load_digits()
 3 data = digits.data
 4 # 数据探索
 5 print(data.shape)
 6 # 查看第一幅图像
 7 print(digits.images[0])
 8 # 第一幅图像表明的数字含义
 9 print(digits.target[0])
10 # 将第一幅图像显示出来
11 plt.gray()
12 plt.imshow(digits.images[0])
13 plt.show()
运行结果:
 1 (1797, 64)
 2 [[ 0.  0.  5. 13.  9.  1.  0.  0.]
 3 [ 0.  0. 13. 15. 10. 15.  5.  0.]
 4 [ 0.  3. 15.  2.  0. 11.  8.  0.]
 5 [ 0.  4. 12.  0.  0.  8.  8.  0.]
 6 [ 0.  5.  8.  0.  0.  9.  8.  0.]
 7 [ 0.  4. 11.  0.  1. 12.  7.  0.]
 8 [ 0.  2. 14.  5. 10. 12.  0.  0.]
 9 [ 0.  0.  6. 13. 10.  0.  0.  0.]]
10 0
咱们对原始数据集中的第一幅进行数据可视化,能够看到图像是个 8*8 的像素矩阵,上面这幅图像是一个“0”,从训练集的分类标注中咱们也能够看到分类标注为“0”。
sklearn 自带的手写数字数据集一共包括了 1797 个样本,每幅图像都是 8*8 像素的矩阵。由于并无专门的测试集,因此咱们须要对数据集作划分,划分红训练集和测试集。由于 KNN 算法和距离定义相关,咱们须要对数据进行规范化处理,采用 Z-Score 规范化,代码以下:
 1 # 分割数据,将 25% 的数据做为测试集,其他做为训练集(你也能够指定其余比例的数据做为训练集)
 2 train_x, test_x, train_y, test_y = train_test_split(data, digits.target, test_size=0.25, random_state=33)
 3 # 采用 Z-Score 规范化
 4 ss = preprocessing.StandardScaler()
 5 train_ss_x = ss.fit_transform(train_x)
 6 test_ss_x = ss.transform(test_x)
 7 而后咱们构造一个 KNN 分类器 knn,把训练集的数据传入构造好的 knn,并经过测试集进行结果预测,与测试集的结果进行对比,获得 KNN 分类器准确率,代码以下:
 8 # 建立 KNN 分类器
 9 knn = KNeighborsClassifier()
10 knn.fit(train_ss_x, train_y)
11 predict_y = knn.predict(test_ss_x)
12 print("KNN 准确率: %.4lf" % accuracy_score(predict_y, test_y))
运行结果:
1 KNN 准确率: 0.975
好了,这样咱们就构造好了一个 KNN 分类器。以前咱们还讲过 SVM、朴素贝叶斯和决策树分类。咱们用手写数字数据集一块儿来训练下这些分类器,而后对比下哪一个分类器的效果更好。代码以下:
 1 # 建立 SVM 分类器
 2 svm = SVC()
 3 svm.fit(train_ss_x, train_y)
 4 predict_y=svm.predict(test_ss_x)
 5 print('SVM 准确率: %0.4lf' % accuracy_score(predict_y, test_y))
 6 # 采用 Min-Max 规范化
 7 mm = preprocessing.MinMaxScaler()
 8 train_mm_x = mm.fit_transform(train_x)
 9 test_mm_x = mm.transform(test_x)
10 # 建立 Naive Bayes 分类器
11 mnb = MultinomialNB()
12 mnb.fit(train_mm_x, train_y)
13 predict_y = mnb.predict(test_mm_x)
14 print(" 多项式朴素贝叶斯准确率: %.4lf" % accuracy_score(predict_y, test_y))
15 # 建立 CART 决策树分类器
16 dtc = DecisionTreeClassifier()
17 dtc.fit(train_mm_x, train_y)
18 predict_y = dtc.predict(test_mm_x)
19 print("CART 决策树准确率: %.4lf" % accuracy_score(predict_y, test_y))
运行结果以下:
1 SVM 准确率: 0.9867
2 多项式朴素贝叶斯准确率: 0.8844
3 CART 决策树准确率: 0.8556
这里须要注意的是,咱们在作多项式朴素贝叶斯分类的时候,传入的数据不能有负数。由于 Z-Score 会将数值规范化为一个标准的正态分布,即均值为 0,方差为 1,数值会包含负数。所以咱们须要采用 Min-Max 规范化,将数据规范化到 [0,1] 范围内。
好了,咱们整理下这 4 个分类器的结果。
你能看出来 KNN 的准确率仍是不错的,和 SVM 不相上下。
你能够本身跑一遍整个代码,在运行前还须要 import 相关的工具包(下面的这些工具包你都会用到,因此都须要引用):
1 from sklearn.model_selection import train_test_split
2 from sklearn import preprocessing
3 from sklearn.metrics import accuracy_score
4 from sklearn.datasets import load_digits
5 from sklearn.neighbors import KNeighborsClassifier
6 from sklearn.svm import SVC
7 from sklearn.naive_bayes import MultinomialNB
8 from sklearn.tree import DecisionTreeClassifier
9 import matplotlib.pyplot as plt
代码中,我使用了 train_test_split 作数据集的拆分,使用 matplotlib.pyplot 工具包显示图像,使用 accuracy_score 进行分类器准确率的计算,使用 preprocessing 中的 StandardScaler 和 MinMaxScaler 作数据的规范化。
相关文章
相关标签/搜索