KNN实现手写字符识别

目录

 

一、环境

二、读取数据

三、基于KNN实现分类

四、总结


一、环境

python, opencv

二、读取数据

用到的数据集是mnist数据集,下载地址,数据集每个文件的格式在官网中都有介绍,训练集一共60000张图像,测试集一共10000张图像,图像大小为28*28,以下代码注意更改文件路径:

import numpy as np
from struct import unpack

# 读入图像
def ReadImgFile(filepath):
    with open(filepath, 'rb') as f:
        _, img_num, h, w = unpack('>4I', f.read(16))
        # fromfile()函数读取数据时需要用户指定文件中的元素类型
        img = np.fromfile(f, dtype = np.uint8).reshape(img_num, h * w)
        return img_num, h, w, img

# 读入图像标签
def ReadLableFIle(filepath):
    with open(filepath, 'rb') as f:
        _, img_num = unpack('>2I', f.read(8))
        label = np.fromfile(f, dtype = np.uint8).reshape(img_num, 1)
        return img_num, label

# 读取训练集和测试集的图像和标签
train_img_num, train_h, train_w, train_img = ReadImgFile('./mnistdata/train-images.idx3-ubyte')
train_label_num, train_label = ReadLableFIle('./mnistdata/train-labels.idx1-ubyte')
test_img_num, test_h, test_w, test_img = ReadImgFile('./mnistdata/t10k-images.idx3-ubyte')
test_label_num, test_label = ReadLableFIle('./mnistdata/t10k-labels.idx1-ubyte')

数据集读入完成后可以调用matplotlib显示训练集和测试集的前5张图片

import matplotlib.pyplot as plt
def Display(img, label, h, w, num):
    fig = plt.figure()    # 使用figure()命令来产生一个图
    for i in range(num):
        im = img[i].reshape([h, w])    # 将一维的像素矩阵reshape成原图像大小的二维矩阵
        # add_subplot把图分割成多个子图,三个参数分别为行数、列数、当前子图位置
        ax = fig.add_subplot(1, num, i + 1)
        ax.set_title(str(label[i]))    # 每个子图的命名为其标签
        ax.axis('off')    # 隐藏坐标
        ax.imshow(im, cmap='gray')
    plt.show()

Display(train_img, train_label, train_h, train_w, 5)    # 显示训练集前5张图像
Display(test_img, test_label, test_h, test_w, 5)    # 显示测试集前5张图像

三、基于KNN实现分类

KNN是监督学习中最简单的算法之一,这里只讨论将KNN用于分类的情况,其基本思想是将已知类别的数据映射到特征空间中,对于未知类别的数据,在特征空间中寻找与它最接近的匹配。以mnist数据集为例,KNN的输入是训练集所有图像的特征向量和图像对应的标签,这里选取图像的像素值作为特征向量,即28*28=784维,对于新来的测试图像,计算它的特征向量与训练集每个特征向量的距离,选择距离最近的k个训练数据的标签的众数作为当前测试图像的标签,即k个邻居中,哪种标签出现的频率最高,就认为测试图像属于该分类,具体的理解可以参照OpenCV-KNN的官网

基于OpenCV的实现可以参考OpenCV官网:OpenCV-KNN,调用cv2.ml.KNearest_create()创建一个KNN分类器,然后调用train方法进行训练,调用findNearest方法进行测试,findNearest的返回值result表示根据knn算法得到的测试图像对应的标签,neighbours表示测试图像的k个最近邻,dist表示相应最近邻的距离

以下代码中k = 5表示选择5个最近邻(k的取值一般小于20),用5个邻居标签的众数作为当前测试图像的标签,取众数是因为这里的分类任务一张图像对应一个标签。代码中result.shape = (10000, ),neighbours.shape = (10000, 5),dist.shape = (10000, 5),最后测试得到的准确率是96.88%,修改k的值会得到不同的准确率

import cv2

# 将所有数据转成np.float32类型
train_img = train_img.astype(np.float32)
train_label = train_label.astype(np.float32)
test_img = test_img.astype(np.float32)
test_label = test_label.astype(np.float32)

# 调用OpenCV的knn实现分类
knn = cv2.ml.KNearest_create()
knn.train(train_img, cv2.ml.ROW_SAMPLE, train_label)
ret, result, neighbours, dist = knn.findNearest(test_img, k = 5)

# 计算预测准确率
matches = result == test_label
correct = np.count_nonzero(matches)
accuracy = float(correct)/float(test_img_num)
print("accuracy:", accuracy)

测试得到result后可以调用之前的Display函数显示前10张图像,看预测的标签是否和真实标签一致,从下面图片中可以看到前10张图像预测的标签和真实标签是一致的

Display(test_img, test_lable, test_h, test_w, 10)    # 显示前10张图像及其真实标签
Display(test_img, result, test_h, test_w, 10)    # 显示前10张图像及其预测标签

也可以自己实现KNN算法,距离度量可以选择L1距离,L2距离(欧氏距离),余弦距离等,下面的代码选取了1000张图像作为训练集,200张图像作为测试集,当k = 5时,选择L1距离的准确率为82.5%,选择L2距离的准确率为86%,选择余弦距离的准确率为82.5%,调整训练图像和测试图像的数量可以得到不同的结果

def MyKNN(train, test, train_num, test_num, train_label, test_label, k):
    test_predict = np.zeros([test_num, 1])
    for test_id in range(test_num):
        t_img = test[test_id]
        diff = np.sum(np.abs(train - t_img), axis = 1)    # L1距离
#         diff = np.sqrt(np.sum(np.square(train - t_img), axis = 1))    # L2距离
#         diff = 1 - np.sum(train * t_img, axis = 1) / \
#                 (np.sqrt(np.sum(train**2, axis = 1)) + np.sqrt(np.sum(t_img**2)))    # 余弦距离
        index = np.argsort(diff)[:k]    # 找出k个最近邻的下标
        k_label = train_label[index].reshape([1,-1])[0].astype(np.int64)    # 找出对应的标签
        test_predict[test_id] = np.argmax(np.bincount(k_label))    # 计算k个最近邻标签的众数作为测试图像的标签
    return test_predict

# 将所有数据转成np.float32类型
train_img = train_img.astype(np.float32)
train_label = train_label.astype(np.float32)
test_img = test_img.astype(np.float32)
test_label = test_label.astype(np.float32)

train_num = 1000
test_num = 200
result = MyKNN(train_img[:train_num,:], test_img[:test_num,:], train_num, test_num, \
                 train_label[:train_num,:], test_label[:test_num,:], 1)

# 计算准确率
matches = result == test_label[:test_num,:]
correct = np.count_nonzero(matches)
accuracy = float(correct)/float(test_num)
print("accuracy:", accuracy)

 调用Display函数输出前10张图像的真实标签和用预测标签(L2距离),结果如图,可以看到画红线的几个测试图像预测错误

以上实现需注意在计算之前先转换数据类型,把np.uint8转成np.float32,否则会导致计算错误,在调用OpenCV的KNN时如果不转换数据类型会报错:error: (-215:Assertion failed) samples.type() == CV_32F || samples.type() == CV_32S in function 'setData',但自己实现时如果不转换会直接导致计算错误,原因是numpy的加减运算会自动返回输入数据类型,np.uint8在内存中占8位,只能表示0~255之间的数,两个像素相减如果等于负数就会导致结果出错,比如3 - 5 = -2,而在计算机中的运算为0000 0101 (3) + 1111 0111 (-5,用补码表示) = 1111 1100 (254)

四、总结

KNN算法的优点在于简单、易于理解,对异常值不敏感,不需要参数估计,也不需要预先训练;缺点是计算量大,且训练数据必须存储在本地,内存开销大。

  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值