【从零开始的机器学习1】——KNN和手写数字识别

零.前言

又开一个坑,手写一些机器学习常用的函数的代码,以此深度了解机器学习框架背后的一些原理。

一.原理

1.简述原理

KNN的原理,简述就是:

  1. 向量化训练集和目标
  2. 预测目标与训练集中每一个成员的距离
  3. 选出训练集中与待预测的目标距离最小的K个成员
  4. 在这K个成员中,选出出现次数最多的那个成员,该成员就是我们的预测结果

2.补充说明

  1. 向量化是指:比如一张28 * 28像素的图片,我们可以给他二值化后,变成一个由0,1组成的28 * 28的矩阵,再把这个矩阵变个形,就成了一个1 * 784的单行矩阵,即可称为向量

  2. 我们在初中学过:平面直角坐标系上的距离公式为:
    d i s t a n c e = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 2 distance = \sqrt[2]{(x_1-x_2)^2+(y_1-y_2)^2} distance=2(x1x2)2+(y1y2)2
    在高中学过:空间直角坐标系上的距离公式为:
    d i s t a n c e = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 2 distance = \sqrt[2]{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} distance=2(x1x2)2+(y1y2)2+(z1z2)2
    那么到了大学,高维向量的距离公式:
    v e c t o r 1 = [ n 1   n 2   n 3   . . .   n x ] v e c t o r 2 = [ m 1   m 2   m 3   . . .   m x ] ∣ d i s t a n c e ∣ = ( n 1 − m 1 ) 2 + ( n 2 − m 2 ) 2 + . . . + ( n x − m x ) 2 2 vector_1 = [n_1\,n_2\,n_3\, ...\, n_x]\\ vector_2 = [m_1\,m_2\,m_3\, ...\, m_x]\\ \left| distance\right| = \sqrt[2]{(n_1-m_1)^2+(n_2-m_2)^2+...+(n_x-m_x)^2} vector1=[n1n2n3...nx]vector2=[m1m2m3...mx]distance=2(n1m1)2+(n2m2)2+...+(nxmx)2
    通过这个方式,我们就可以实现任意维度向量的计算

二.代码实现

1.创建数据集

目前在工程目录下,我自己写了个小软件,然后自己绘制了200张图片,命名方式如下data/{number}/{index}.jpg

然后我们通过python来将这些图像处理后,变成一组二维向量,分步来:

导入包,并创建我们的矩阵:

import cv2
import numpy as np

data = np.zeros([200, 785], dtype=int)

开始循环,并修改我们刚才创建的矩阵

for i in range(10):  # 循环确定图片标号 
    for n in range(1, 21):  # 第几张图片
        img = cv2.imread("data/"+str(i)+"/"+str(n)+".jpg", 0)  # 获取图片路径并读入
        th1, img = cv2.threshold(img, 0, 1, 0)  # 二值化白色变为1,黑色为0
        img = np.array(img).reshape(1, -1)  # 向量化
        row = i * 20 + n - 1    # 计算行
        data[row][0] = i    # 将向量第一个作为图片内容0~9
        data[row][1:785] = img  # 从1~785是784维的图片向量

保存数据

np.save("train_data", data)     # 保存数据

运行致此,我们就可以在工程目录下,得到我们的数据集,以后直接调用即可,就不需要图片预处理。

2.使用KNN预测图片算法

我们先来编写调用KNN识别的函数:

def knn_train(vector, k):	# vector使我们待预测的图片向量,第二个是KNN的参数K
    dis_list = []	# 用于存放图像值和距离
    hash_map = {}	# 用于统计前K个数的频数
    train_data = np.load('train_data.npy')	# 读入我们保存好的数据
    (row, col) = train_data.shape	# 获取下行列,以便我们以后能使用更高数量的训练集
    for i in range(row):	# 按行计算
        distance = vector - train_data[i][1:col]	# 两者作差,获得每个维度的差类似于(n1-m1)
        distance = distance**2	# 每个维度求平方和
        distance = np.sum(distance)		# 将每个维的值加起来,就不用求方差了,反正都是线性对应的,没必要浪费时间求方差
        dis_list.append((train_data[i][0], distance))	#将(图片内容。距离)存入dis_list列表里
	dis_list.sort(key=lambda x: x[1])	# 将dis_list里的值,以元组的第二个参数为key值,排序
    if k > row:		# 如果k大于了行数,那么就变成行数,虽然一般来说 k=3~30,不会那么大,但是为了怕出错,这样写好一点儿
        k = row
    for i in range(k):	# 遍历前k个元素
        (label, mark) = dis_list[i]	# 提取每个元组的信息
        label = str(label)	# 把整型的图片信息,变成字符串,以便成为字典的键
        if label in hash_map:	# 如果字典里有这个键
            value = hash_map.get(label) + 1		# 读取键,并把键值加一
            hash_map.update({label: value})		# 更新
        else:
            hash_map.update({label: 1})		# 无键,自己创一个新的
    return hash_map

此时,返回了hash_map,我们的值输出它,可能是这样的:

{'7': 5, '1': 4, '9': 1}

如果我们要其中,出现次数最高的键,就可以使用这样处理:

re_data = knn_train(img, 10)
print(max(re_data, key=lambda x: re_data[x]))	#这里的值,就是7,也就是最大键值的键名

好了,整个函数应该是这样的:

import numpy as np
import cv2


def knn_train(vector, k):
    dis_list = []
    hash_map = {}
    train_data = np.load('train_data.npy')
    (row, col) = train_data.shape
    for i in range(row):
        distance = vector - train_data[i][1:col]
        distance = distance**2
        distance = np.sum(distance)
        dis_list.append((train_data[i][0], distance))
    dis_list.sort(key=lambda x: x[1])
    if k > row:
        k = row
    for i in range(k):
        (label, mark) = dis_list[i]
        label = str(label)
        if label in hash_map:
            value = hash_map.get(label) + 1
            hash_map.update({label: value})
        else:
            hash_map.update({label: 1})
    return hash_map


img = cv2.imread("7.jpg", 0)
th1, img = cv2.threshold(img, 0, 1, 0)
img = np.array(img).reshape(1, -1)
re_data = knn_train(img, 10)
print(max(re_data, key=lambda x: re_data[x]))

代码下载地址:https://download.csdn.net/download/u011017694/12859584,免费下载。

三.总结与反思

虽然这样就实现了KNN,但还是受样本的容量影响。但如果样本容量太大,又会产生时间过长的问题。所以 我想要3090QAQ。

而且,如果单纯的只是最近距离的K个点频率最高的那个是预测值,那么万一有40%的值是特别接近距离在10左右,而60%的值离得很远在100左右的话,那么除了修改K值是别无办法的,那么为何不给每个值按距离的远近加个权后,再来求呢?我不知道,有时间试试效果。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

康娜喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值