目录
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(蓝色正方形),所以预测新来者为蓝色正方形。