# OpenCV 实现 k-NN
OpenCV 中文官方文档
# 算法描述
如果我们是红队球迷,我们不可能搬到大多数人都认为可能是蓝队球迷的社区。
k-NN 算法认为一个数据点可能与其邻居属于同一类。
没了。这不比 Prim 还水?
# 训练数据生成
前置:
import numpy as np | |
import matplotlib as mpl | |
import matplotlib.pyplot as plt | |
plt.style.use('ggplot') | |
np.random.seed(42) |
随机生成二维点及标签:
single_data_point = np.random.randint(0, 100, 2) #随机生成坐标 | |
print(single_data_point) #[51, 92] | |
single_label = np.random.randint(0, 2) #随机指定标签 | |
print(single_label) #0 |
大量数据生成:
#生成器 | |
def generate_data(num_samples, num_features=2): #数据个数,特征数 | |
data_size = (num_samples, num_features) #行,列 | |
train_data = np.random.randint(0, 100, size=data_size) | |
label_size = (num_samples, 1) | |
labels = np.random.randint(0, 2, size=label_size) | |
return train_data.astype(np.float32), labels | |
train_data, labels = generate_data(11) | |
print(train_data[0], labels[0]) #[71., 60.] [1] |
绘制:
#尝试绘制单点 | |
plt.plot(train_data[0, 0], train_data[0, 1], color='r', marker='^', markersize=10) | |
plt.xlabel('x-coordinate') #坐标轴描述 | |
plt.ylabel('y-coordinate') | |
plt.show() |
#绘制全部点 | |
def plot_data(all_blue, all_red): | |
plt.figure(figsize=(10, 6)) | |
#x 坐标列表,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) | |
#numpy 列表功能:list.ravel () 将列表压成向量 (一维数组),list == 0 生成布尔数组 bool_list,list [bool_list] 选取数组中的一部分 (true 对应下标元素)。 | |
blue = train_data[labels.ravel() == 0] | |
red = train_data[labels.ravel() == 1] | |
plot_data(blue, red) | |
plt.show() |
<img src="https://s3.ax1x.com/2021/02/15/y6JYOP.png" width="500px">
# 训练分类器
接上段:
import cv2 | |
knn = cv2.ml.KNearest_create() | |
knn.train(train_data, cv2.ml.ROW_SAMPLE, labels) #成功会返回 true |
# 预测一个新数据点的标签
生成一个新点并绘制:
newcomer, _ = generate_data(1) | |
print(newcomer) #[[91., 59.]] | |
plot_data(blue, red) | |
plt.plot(newcomer[0, 0], newcomer[0, 1], 'go', markersize=14) | |
plt.show() |
<img src="https://s3.ax1x.com/2021/02/15/y6JJyt.png" width="500px">
查看预测结果:
ret, results, neighbor, dist = knn.findNearest(newcomer, 1) #待测点,最近的 k 个邻居 | |
print("Predicted label:\t{}\nNeighbor's label:\t{}\nDistance to neighbor:\t{}\n".format(results, neighbor, dist)) | |
''' | |
k = 1 | |
Predicted label: [[1.]] | |
Neighbor's label: [[1.]] | |
Distance to neighbor: [[250.]] | |
k = 2 | |
Predicted label: [[1.]] | |
Neighbor's label: [[1. 1.]] | |
Distance to neighbor: [[250. 401.]] | |
k = 3 | |
Predicted label: [[1.]] | |
Neighbor's label: [[1. 1. 0.]] | |
Distance to neighbor: [[250. 401. 784.]] | |
''' | |
#另一种姿势 | |
knn.setDefaultK(3) | |
print(knn.predict(newcomer)) #(1.0, array([[1.]], dtype=float32)) |
# k-NN 实现手写数字识别
# 数据准备
这里采用 sklearn 的 datasets 中的 digits。
In [1]: import numpy as np | |
...: from sklearn import datasets as ds | |
...: digits = ds.load_digits() |
包含内容:
In [2]: digits.keys() | |
Out[2]: dict_keys(['data', 'target', 'frame', 'feature_names', 'target_names', 'images', 'DESCR']) |
data 和 images 和 target:
In [13]: digits.images[0] | |
Out[13]: #每个元素是 8×8 像素的二维数组 | |
array([[ 0., 0., 5., 13., 9., 1., 0., 0.], | |
[ 0., 0., 13., 15., 10., 15., 5., 0.], | |
[ 0., 3., 15., 2., 0., 11., 8., 0.], | |
[ 0., 4., 12., 0., 0., 8., 8., 0.], | |
[ 0., 5., 8., 0., 0., 9., 8., 0.], | |
[ 0., 4., 11., 0., 1., 12., 7., 0.], | |
[ 0., 2., 14., 5., 10., 12., 0., 0.], | |
[ 0., 0., 6., 13., 10., 0., 0., 0.]]) | |
In [10]: digits.data[0] | |
Out[10]: #images 展成 64 个元素的一维数组 | |
array([ 0., 0., 5., 13., 9., 1., 0., 0., 0., 0., 13., 15., 10., | |
15., 5., 0., 0., 3., 15., 2., 0., 11., 8., 0., 0., 4., | |
12., 0., 0., 8., 8., 0., 0., 5., 8., 0., 0., 9., 8., | |
0., 0., 4., 11., 0., 1., 12., 7., 0., 0., 2., 14., 5., | |
10., 12., 0., 0., 0., 0., 6., 13., 10., 0., 0., 0.]) | |
In [11]: type(digits.data[0, 0]) | |
Out[11]: numpy.float64 #每个像素是一个 float64 表示的颜色 | |
In [20]: digits.target_names | |
Out[20]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) #每个图片对应标签就是数字 | |
In [23]: len(digits.target) | |
...: len(digits.data) | |
Out[23]: 1797 #数据集包含 1797 个样本 |
# 实现思路
直接把图片展开,看成 64 维向量(或者该叫 64 维空间内的点?)。
距离表示图片相似程度,相同的数字距离较近。
套 k-NN 板子。
# 具体实现
Life is short, I use Python.
import numpy as np | |
import cv2 | |
from sklearn import datasets as ds | |
from sklearn import metrics as mts | |
#数据准备 | |
digits = ds.load_digits() | |
knn = cv2.ml.KNearest_create() | |
train_data = digits.data[:1000].astype(np.float32) #前 999 个用来训练,转为 float32 便于读取 | |
train_labels = digits.target[:1000] | |
test_data = digits.data[1000:].astype(np.float32) | |
test_labels = digits.target[1000:] | |
#模型训练 | |
knn.train(train_data, cv2.ml.ROW_SAMPLE, train_labels) | |
#测试 | |
_, res, _, _ = knn.findNearest(test_data, k=10) | |
print(mts.accuracy_score(res, test_labels)) #正确率 0.9560853199498118 |