k-NN算法

目录

1>k-NN算法

2>使用OpenCV实现k-NN算法


1>k-NN算法

理解k-NN算法:

k近邻算法是一种基本分类和回归方法,当前我们只讨论分类问题的k近邻算法。即给定一个训练数据集,对于新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,若这k个实例的多数属于某个类,就把该输入实例分类到这个类中,这类似于现实生活中少数服从多数的思想。

如上图所示,这里有两类不同的样本数据,分别用蓝色正方形和红色三角形表示,而绿色圆点所标识的数据则是待分类的数据。如何对绿色圆点进行分类,便是k-NN算法需要研究的问题。

下面我们根据k近邻算法的思想来给绿色圆点进行分类:

  • 如果k=3,则绿色圆点最邻近的3个点是2个红色三角形和1个蓝色正方形,少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于红色三角形一类。
  • 如果k=5,则绿色圆点最邻近的5个点是2个红色三角形和3个蓝色正方形,少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于蓝色正方形一类。

从上面的例子可以看出,k近邻算法的思想非常简单,只要找到离它最近的k个实例,从中选出最多的类别并进行分类即可。

2>使用OpenCV实现k-NN算法

为了方便理解,我们先设定训练数据的背景:

数据点是小镇地图中的房子,每个数据点有两个特征:

  • 在地图上的坐标(x,y)
  • 一个类别标签:其中红色三角形的类别为1,蓝色正方形的类别为0。

打开一个新的IPython会话:

ipython

引入所有必需的模块:

import numpy as np
import cv2
import matplotlib.pyplot as plt
%matplotlib
plt.style.use('ggplot')

固定随机数生成器的种子值:

np.random.seed(42)

假设小镇地图的范围是0≤x<100和0≤y<100,在地图上随机选择一个位置:

single_data_point=np.random.randint(0, 100, 2)
single_data_point
#结果:array([51, 92])

这段代码将会从0到100之间获取2个随机的整数,将第一个整数当作数据点在地图上的x坐标值,第二个整数当作数据点在地图上的y坐标值。

为该数据点选择一个标签:

single_label=np.random.randint(0, 2)
single_label
#结果:0

结果表示这个数据点的类别为0,我们把它当作一个蓝色正方形。

把这个过程包装成函数,输入是要生成的数据点个数(num_samples)和每个数据点的特征数(num_features):

def generate_data(num_samples, num_features=2):
    #我们想要创建的数据矩阵应该有num_samples行、num_features列,其中每一个元素都应该是[0,100]范围内的一个随机整数:
    data_size=(num_samples, num_features)
    train_data=np.random.randint(0, 100, size=data_size)

    #创建一个所有样本在[0,2]范围内的随机整数标签值的向量:
    labels_size=(num_samples, 1)
    labels=np.random.randint(0, 2, size=labels_size)

    #让函数返回生成的数据:
    return train_data.astype(np.float32), labels

对函数进行测试,先生成任意数量的数据点,比如说11个数据点,并随机选择它们的坐标:

train_data, labels=generate_data(11)
train_data
'''
结果:
array([[71., 60.],
       [20., 82.],
       [86., 74.],
       [74., 87.],
       [99., 23.],
       [ 2., 21.],
       [52.,  1.],
       [87., 29.],
       [37.,  1.],
       [63., 59.],
       [20., 32.]], dtype=float32)
'''

由上面的结果可知,train_data变量是一个11×2的数组,每一行表示一个单独的数据点,可以使用数组索引来获取第一个数据和它对应的标签:

train_data[0], labels[0]
#结果:(array([71., 60.], dtype=float32), array([1]))

这个结果告诉我们:第一个数据点是一个红色的三角形(因为它的类别是1),它在小镇地图的坐标位置是(x,y)=(71,60)。

可以用Matplotlib在小镇地图上画出这个数据点:

plt.plot(train_data[0, 0], train_data[0, 1], 'sb')
plt.xlabel('x coordinate')
plt.ylabel('y coordinate')

得到下方的绘图输出结果:

如果想要一次就显示所有的训练数据集,可以通过写一个函数来实现:

def plot_data(all_blue, all_red):
    plt.figure(figsize=(10, 6))

    #把所有蓝色数据点用蓝色正方形画出来(使用颜色'b'和标记's'),并把蓝色数据点当作N×2的数组传入,其中N是样本的数量。
    #all_blue[:, 0]包含了所有蓝色数据点的x坐标,all_blue[:, 1]包含了所有蓝色数据点的y坐标:
    plt.scatter(all_blue[:, 0], all_blue[:, 1], c='b', marker='s', s=180)

    #红色数据点与蓝色同理:
    plt.scatter(all_red[:, 0], all_red[:, 1], c='r', marker='^', s=180)

    #设置绘图的标签:
    plt.xlabel('x coordinate (feature 1)')
    plt.ylabel('y coordinate (feature 2)')

函数输入包括一个全部是蓝色正方形数据点的列表(all_blue)和一个全部是红色三角形数据点的列表(all_red)。

测试该函数,首先需要把所有数据点分成红色数据点和蓝色数据点,下面的命令可以快速选择labels数组中所有等于0的元素:

labels.ravel()==0
'''
结果:
array([False, False, False,  True, False,  True,  True,  True,  True,
        True, False])
'''

前面创建的train_data中对应标签为0的那些行就是蓝色数据点:

blue=train_data[labels.ravel()==0]

对于所有的红色数据点也可以同样操作:

red=train_data[labels.ravel()==1]

最后,画出所有的数据点:

plot_data(blue, red)

得到下方的绘图输出结果:

创建一个新的分类器:

knn=cv2.ml.KNearest_create()

把训练数据传入到train方法中:

knn.train(train_data, cv2.ml.ROW_SAMPLE, labels)
#结果:True

我们的数据是一个N×2的数组(即每一行都是一个数据点),这个函数会在执行成功后返回True。

knn提供的一个非常有用的方法叫作findNearest,它可以根据最近邻数据点的标签来预测新数据点的标签:​​​

newcomer, _=generate_data(1)
newcomer
#结果:array([[91., 59.]], dtype=float32)

generate_data函数会返回一个随机的类别,但我们对它不感兴趣,可以通过一个下划线让Python忽略输出值。

回到我们的小镇地图,我们要像之前一样把训练数据集画出来,并将新的数据点加入,用绿色的圆圈表示:

plot_data(blue, red)
plt.plot(newcomer[0, 0], newcomer[0, 1], 'go', markersize=14);

可以在plt.plot函数后面添加一个分号来抑制输出,与Matlab一样。

得到下方的绘图输出结果:

在k=1的情况下,分类器所预测的结果:

ret, results, neighbor, dist=knn.findNearest(newcomer, 1)
print("Predicted label:\t", results)
print("Neighbor's label:\t", neighbor)
print("Distance to neighbor:\t", dist)
'''
结果:
Predicted label:         [[1.]]
Neighbor's label:        [[1.]]
Distance to neighbor:    [[250.]]
'''

由上面的结果可知,knn报告说最近邻的点有250个单位距离远,其类别是1(红色三角形),因此新数据点的类别应该也是1。

如果非常大地扩大搜索窗口,根据k=7最近邻来对新数据点进行分类,分类器所预测的结果:

ret, results, neighbor, dist=knn.findNearest(newcomer, 7)
print("Predicted label:\t", results)
print("Neighbor's label:\t", neighbor)
print("Distance to neighbor:\t", dist)
'''
结果:
Predicted label:         [[0.]]
Neighbor's label:        [[1. 1. 0. 0. 0. 1. 0.]]
Distance to neighbor:    [[ 250.  401.  784.  916. 1073. 1360. 4885.]]
'''

由上面的结果可知,预测的标签变成了0(蓝色正方形)。这是因为当前范围内只有3个邻居的标签为1(红色三角形),而另外4个邻居的标签为0(蓝色正方形),所以预测新来者为蓝色正方形。

猜你喜欢

转载自blog.csdn.net/Kannyi/article/details/113270364