【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用

目录

KNN算法简介

算法原理

基本流程

KNN算法的三要素

距离度量

K值选择-交叉验证

KNN算法的优缺点以及改进方法

KNN改进算法介绍

KD树

Ball树

Annoy

HNSW

KNN算法手动实现并完成鸢尾花分类

主体部分

交叉验证选择最适K值

导入数据和预处理

完整代码

利用Sklearn实现KNN完成鸢尾花分类

利用Sklearn的KNN完成手写数字识别

导入数据并查看数字图像

数据预处理

选择最佳K值

训练模型,测试模型得分

应用手写数字识别

图片预处理

将彩色图片以灰度图片导入

黑白颠倒以及二值化

使用模型识别手写数字 

完整代码

对模型的总结和评价 


KNN算法简介

k近邻法(k-nearest neighbor,k-NN)属于监督学习中的分类算法,是机器学习中最简单的算法之一,同时也是最常用的分类算法之一。这篇文章将会基于numpy手动实现KNN,在此之后,将会利用sklearn实现的KNN完成数字识别。

KNN需要数据标准化。

算法原理

KNN不同于其他机器学习算法,它不需要训练过程,当使用该算法预测一个新的值时,它会根据距离最近的K个点的类型来判断自己的类型:K个点中哪个类型多就属于哪个类型。

例:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第1张图片

当K等于3时,根据上述规则可得预测为红类;

当K等于5时,根据上述规则可得预测为蓝类。

基本流程

  1. 计算新数据与每一个已知样本的距离

  2. 按距离从小到大排序

  3. 选出距离最小的K个点

  4. 通过这K个点的分类决策新数据所属类别

KNN算法的三要素

根据上面的例子,可以看出不同K的选择对预测结果有较大影响。实验表明,K值越小,越容易发生过拟合;K值越大,越容易发生欠拟合。除K值的选择外,影响KNN算法的还有距离度量分类决策规则

分类决策规则一般采用多数表决,即最近的K个样本中哪个种类多就属于哪一类。

距离度量

设有同维度特征空间向量\vec A = (a_1,a_2,...,a_n),\vec B = (b_1,b_2,...,b_n) ,则\vec A,\vec BL_P距离为:

L_p(\vec A,\vec B) = (\sum_{i=1}^n|a_i-b_i|^p)^{\frac 1 p}

曼哈顿距离(P=1)L_1 = \sum_{i=1}^n|a_i-b_i|

欧式距离(P=2):L_2 = \sqrt{\sum_{i=1}^n|a_i-b_i|^2}

切比雪夫距离(P=\infty)L_{\infty} = max(|a_1-b_1|,|a_2-b_2|,...,|a_n-b_n|)

一般采用欧式距离作为距离度量。

K值选择-交叉验证

交叉验证简介:将原始数据集可以分为训练集、验证集、测试集。利用训练集和验证集测试模型好坏的方法叫做交叉验证。注:交叉验证是没有用到测试集的!如果用测试集进行交叉验证,会导致预测结果乐观化。

通过交叉验证方法可以得到最合适的K值。

KNN算法的优缺点以及改进方法

优点:

  1. 原理简单,便于实现。

  2. KNN是惰性模型,不需要训练。

  3. 预测效果好。

  4. 对异常值不敏感。

缺点:通过KNN算法原理可以得知,如果要预测一个新数据,需要将它和每一个样本都计算一次距离,然后排序,选出最小的K个样本进行决策。那么预测一次的时间复杂度为O(n),时间复杂度较高。并且需要将所有样本都储存在内存,空间复杂度较高,即对内存要求较高。所以KNN算法的缺点主要是:

  1. 效率比较低,运行时间可能比较长。

  2. 对内存要求较高。

KNN适用于数据集较小的分类。对于数据集较大的可使用神经网络进行分类。

KNN改进算法介绍

这里不会说明各算法的原理与实现,只会简单介绍,参考文章:盘点高效的KNN实现算法-技术圈 (proginn.com)

上述提到的均是KNN的线性扫描实现方法,即暴力法。通过分析,可知线性扫描不适用于数据规模较大的数据集。

实际上,KNN的实现方法高大10种,下面将介绍几种改进方法。

KD树

KD树是一种树形结构存储算法。

适用范围:

KD树在特征维度小于20时效率最高,一般适用于训练样本数远大于空间维数时的k近邻搜索。当空间维数接近训练实例数时,效率会迅速下降,几乎接近线形扫描。

Ball树

ball树是KD树的一种改良算法。

Annoy

Annoy,全称“Approximate Nearest Neighbors OYeah”,是一种适合实际应用的快速相似查找算法。

Annoy 同样通过建立一个二叉树,使得每个点查找时间复杂度是O(log n)。和KD树不同的是,Annoy没有对k维特征进行切分。

HNSW

HNSW(Hierarchcal Navigable Small World graphs)是基于图存储的数据结构。

上述四种算法中,Annoy和HNSW是可以在实际业务中落地的算法。


KNN算法手动实现并完成鸢尾花分类

主体部分

详细解释都写代码里了,因为主体部分实在太短了没必要分开写,就直接写在一个模块了:

import numpy as np
import operator

# 使用KNN预测数据类别
def run_KNN(X,X_train,y_train,K):
    '''
    :param X: 需要预测的数据集
    :param X_train: X训练集
    :param y_train: Y训练集
    :param K: K个最近
    :return: 预测集
    '''
    dataSize = X_train.shape[0]
    y_predict = []
    for x in X:
        # 计算未知数据到每一个样本的欧氏距离
        diff = np.tile(x,(dataSize,1)) - X_train # 把X扩大然后矩阵相减
        squaredDist = np.sum(diff**2,axis=1) # axis = 1计算每一行的和
        distance = squaredDist ** 0.5

        # 对距离递增排序获取最前面K个样本的种类并统计各种类出现次数
        nearIds = distance.argsort() # 按值排序,得到对应下标数组
        classesCount = {}
        for i in range(K):
            y = y_train[nearIds[i]] # 得到对应的种类
            classesCount[y] = classesCount.get(y,0)+1 # 0为设置默认值
        # print(classesCount)
        # 对字典按值进行递减排序
        sortClassesCount = sorted(classesCount.items(),key=operator.itemgetter(1),reverse=True)
                                                        # 获取对象第二个元素           逆序
        y_predict.append(sortClassesCount[0][0])  # 预测种类为出现次数最多的那一类
    return y_predict

到这里,就已经手动实现了KNN算法,可以拿来预测类别了,确实很简单。

但我们还需要利用交叉验证集选择最好的K,本来想借助sklearn的交叉验证,但是对我们自己手写的KNN好像不太好用,所以还是自己写一个简易的吧。

不过像拆分训练集和计算Score这种事就交给sklearn吧。

交叉验证选择最适K值

注意,交叉验证集不能涉及到测试集!

这里用多次交叉验证计算score取均值:

import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score

# 交叉验证集
def chooseK(X_train_old,y_train_old,K = 10,C = 5):
    '''
    :param X_train_old: 原训练特征集
    :param y_train_old: 原训练目标集
    :param K: 最大K
    :param C: 计算次数
    :return: 最好的K值
    '''

    # K从1取到maxK,分别计算Score
    scores = [0]
    for k in range(1,K+1):
        score = 0
        for j in range(1,C+1):
            # 拆分训练集为训练集和验证集
            X_train, X_check, y_train, y_check = train_test_split(X_train_old, y_train_old, train_size=0.6)
            y_predict = run_KNN(X_check,X_train,y_train,k)
            # 计算Score
            score = score + f1_score(y_check, y_predict, average='weighted')
        score = score/(C)
        # print(f"k = {k},score = {score}")
        scores.append(score)

    scores = np.array(scores)
    bestK = scores.argmax()
    plt_KScore(scores,K,bestK)
    return bestK

# 绘制K对应的Score图像
def plt_KScore(scores,K,bestK):
    k_range = range(1,K+1)
    plt.plot(k_range,scores[1:])
    plt.scatter(bestK,scores[bestK],marker='o',c='red')
    plt.xlabel('Value of K in KNN')
    plt.ylabel('Score')
    plt.show()'Score')
    plt.show()

下面将测试交叉验证集选择K值。

这里使用sklearn自带的鸢尾花数据集,数据标准化之前的文章已经手动实现过,这里直接用sklearn进行数据标准化。

导入数据和预处理

import time
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

if __name__ == '__main__':
    # 导入鸢尾花数据集
    iris = datasets.load_iris()
    dataSet = iris.data
    target = iris.target

    # 拆分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(dataSet,target, train_size=0.7, random_state=10)
    # 数据标准化
    scaler = StandardScaler()
    X_train = scaler.fit_transform(X_train)  # 标准化训练集X
    X_test = scaler.transform(X_test)

 现在开始测试交叉验证集选择K:

    # 接主函数
    
    start = time.time()
    K = chooseK(X_train,y_train,50)
    print(f"bestK is {K}")

    end = time.time()
    print(f"run time is {end-start}")

 结果:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第2张图片

bestK is 7
run time is 2.0105364322662354

实际上会发现每次计算出来的“Best K”都不一样,不过都在那几个值跳动,这是因为在分割测试集和验证集时没有设置随机种子的原因。

现在,让我们用最好的K值来测试分类效果吧!

    # 接主函数
    
    start = time.time()
    best_K = chooseK(X_train,y_train,50)
    print(f"bestK is {best_K}")
    y_predict = run_KNN(X_test,X_train,y_train,best_K)
    score = f1_score(y_test, y_predict, average='weighted')
    print(f"predict score is {score}")
    end = time.time()
    print(f"run time is {end-start}")

结果:

bestK is 4
predict score is 0.9778242192035296
run time is 0.400089502334594704

可以看出bestK和上一次算的不一样,不过最后分类的结果还是非常好的。 

完整代码

import time
import numpy as np
import operator
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

# 使用KNN预测数据类别
def run_KNN(X,X_train,y_train,K):
    '''
    :param X: 需要预测的数据集
    :param X_train: X训练集
    :param y_train: Y训练集
    :param K: K个最近
    :return: 预测集
    '''
    dataSize = X_train.shape[0]
    y_predict = []
    for x in X:
        # 计算未知数据到每一个样本的欧氏距离
        diff = np.tile(x,(dataSize,1)) - X_train # 把X扩大然后矩阵相减
        squaredDist = np.sum(diff**2,axis=1) # axis = 1计算每一行的和
        distance = squaredDist ** 0.5

        # 对距离递增排序获取最前面K个样本的种类并统计各种类出现次数
        nearIds = distance.argsort() # 按值排序,得到对应下标数组
        classesCount = {}
        for i in range(K):
            y = y_train[nearIds[i]] # 得到对应的种类
            classesCount[y] = classesCount.get(y,0)+1 # 0为设置默认值
        # print(classesCount)
        # 对字典按值进行递减排序
        sortClassesCount = sorted(classesCount.items(),key=operator.itemgetter(1),reverse=True)
                                                        # 获取对象第二个元素           逆序
        y_predict.append(sortClassesCount[0][0])  # 预测种类为出现次数最多的那一类
    return y_predict

# 交叉验证集
def chooseK(X_train_old,y_train_old,K = 10,C = 5):
    '''
    :param X_train_old: 原训练特征集
    :param y_train_old: 原训练目标集
    :param K: 最大K
    :param C: 计算次数
    :return: 最好的K值
    '''

    # K从1取到maxK,分别计算Score
    scores = [0]
    for k in range(1,K+1):
        score = 0
        for j in range(1,C+1):
            # 拆分训练集为训练集和验证集
            X_train, X_check, y_train, y_check = train_test_split(X_train_old, y_train_old, train_size=0.6)
            y_predict = run_KNN(X_check,X_train,y_train,k)
            # 计算Score
            score = score + f1_score(y_check, y_predict, average='weighted')
        score = score/(C)
        # print(f"k = {k},score = {score}")
        scores.append(score)

    scores = np.array(scores)
    bestK = scores.argmax()
    # plt_KScore(scores,K,bestK)
    return bestK

# 绘制K对应的Score图像
def plt_KScore(scores,K,bestK):
    k_range = range(1,K+1)
    plt.plot(k_range,scores[1:])
    plt.scatter(bestK,scores[bestK],marker='o',c='red')
    plt.xlabel('Value of K in KNN')
    plt.ylabel('Score')
    plt.show()

if __name__ == '__main__':
    # 导入鸢尾花数据集
    iris = datasets.load_iris()
    dataSet = iris.data
    target = iris.target

    # 拆分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(dataSet,target, train_size=0.7, random_state=10)
    # 数据标准化
    scaler = StandardScaler()
    X_train = scaler.fit_transform(X_train)  # 标准化训练集X
    X_test = scaler.transform(X_test)

    start = time.time()
    best_K = chooseK(X_train,y_train,50)
    print(f"bestK is {best_K}")
    y_predict = run_KNN(X_test,X_train,y_train,best_K)
    score = f1_score(y_test, y_predict, average='weighted')
    print(f"predict score is {score}")
    end = time.time()
    print(f"run time is {end-start}")

利用Sklearn实现KNN完成鸢尾花分类

关于sklearn封装的KNN的参数说明详细请参考文章: KNN算法详解及实现__dingzhen的博客-CSDN博客_knn

读懂每一个需要的参数很很很重要!!一定要去看参数说明

sklearn的KNN实现方式包括了之前提到的线性扫描KDTreeballTree,没有规定特定参数时它会自己选则实现方法。详细说明都在链接里了。

这次我们用Sklearn封装的KNN来完成上面提到的例子,即鸢尾花分类。同样也用了交叉验证寻找最佳K值。

详见代码:

import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import f1_score
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn import datasets
from sklearn.preprocessing import StandardScaler

# 交叉验证找最好K
def findBestK(X_train,y_train,max_k = 50):
    k_scores = [0]
    for k in range(1,max_k+1):
        Knn = KNeighborsClassifier(n_neighbors=k)
        # 交叉验证,scoring='f1_weighted' 使用的与之前手写的交叉验证使用同一个F1-score规则
        scores = cross_val_score(Knn,X_train,y_train,cv=5,scoring='f1_weighted')
        k_scores.append(scores.mean())
    k_scores = np.array(k_scores)
    bestK = k_scores.argmax()
    plt_KScore(k_scores,max_k,bestK)
    return bestK

# 绘制K对应的Score图像
def plt_KScore(scores,K,bestK):
    k_range = range(1,K+1)
    plt.plot(k_range,scores[1:])
    plt.scatter(bestK,scores[bestK],marker='o',c='red')
    plt.xlabel('Value of K in KNN')
    plt.ylabel('Score')
    plt.show()


if __name__ == '__main__':
    # 导入鸢尾花数据集
    iris = datasets.load_iris()
    dataSet = iris.data
    target = iris.target

    # 拆分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(dataSet,target, train_size=0.7, random_state=10)
    # 数据标准化
    scaler = StandardScaler()
    X_train = scaler.fit_transform(X_train)  # 标准化训练集X
    X_test = scaler.transform(X_test)

    # 寻找最好K值
    start = time.time()
    best_K = findBestK(X_train,y_train)
    print(f"best_k is {best_K}")
    model = KNeighborsClassifier(n_neighbors=best_K)
    model.fit(X_train,y_train)
    y_predict = model.predict(X_test)
    score = f1_score(y_test, y_predict, average='weighted')
    print(f"predict score is {score}")
    end = time.time()
    print(f"run time is {end - start}")

交叉验证集选择K:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第3张图片

 最终结果:

best_k is 11
predict score is 0.9778242192035296
run time is 0.7521629333496094

通过自己实现的KNN与sklearn实现的KNN对比,发现:

  1. 自己实现的KNN寻找到的最佳K值不固定,而sklearn每次寻找的最佳K值都一样。导致自己实现的KNN最佳K值不固定的原因是拆分数据集和验证集时每次的随机种子不固定。偶尔有几次自己算出来的结果和sklearn算出来的结果完全一致。

  2. 使用同种实现方式(即线性扫描实现KNN)时,sklearn用的时间比自己实现的要长那么一点点,暂时不清楚原因。


利用Sklearn的KNN完成手写数字识别

到此为止,你已经学会了如何自己实现KNN算法,并且取得了不错的分类效果。现在我们用sklrean封装好的KNN来完成一些有趣的事吧!

手写数据识别是计算机视觉方面的入门级图像识别,一般都是用神经网络实现的,不过我们可以使用KNN来完成数据规模比较小的手写数字识别。

在此之前,请确保你已经安装tensorflow和keras,我们的数据集将来自keras。

当然也可以自己找数据。

需要的包:

import numpy as np
import matplotlib.pyplot as plt
from keras.datasets import mnist
from sklearn.neighbors import KNeighborsClassifier

导入数据并查看数字图像

if __name__ == '__main__':
    # 加载数据
    (X_train,y_train),(X_test,y_test) = mnist.load_data()
    # 绘制训练集的部分数字图像
    for i in range(6):
        plt.figure()
        plt.imshow(X_train[i],cmap='gray')
    plt.show()

 【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第4张图片

 查看X_train训练集的形状:

    print(X_train.shape)

 输出:

(60000, 28, 28)

看得出是个三维的,大概意思是60000张图片,28*28像素,且是灰色(因为如果是彩色,应该是28*28*3)

要用KNN算法,需要先将三维转化为二维矩阵(60000,28*28) ,每一行代表一张图片,共60000张。

数据预处理

    # 将训练集和测试集转化为二维数组
    X_train = X_train.reshape([-1,28*28])
    X_test = X_test.reshape([-1,28*28])
    # 归一化
    X_train /= 255
    X_test /= 255

选择最佳K值

# 交叉验证法求最好K值
def findBestK(X_train,y_train,max_k = 50):
    k_scores = [0]
    for k in range(1,max_k+1):
        print(f"{k} time")
        Knn = KNeighborsClassifier(n_neighbors=k)
        # 交叉验证,scoring='f1_weighted' 使用的与之前手写的交叉验证使用同一个F1-score规则
        scores = cross_val_score(Knn,X_train,y_train,cv=5,scoring='f1_weighted')
        k_scores.append(scores.mean())
    k_scores = np.array(k_scores)
    bestK = k_scores.argmax()
    # plt_KScore(k_scores,max_k,bestK)
    return bestK

# 绘制K对应的Score图像
def plt_KScore(scores,K,bestK):
    k_range = range(1,K+1)
    plt.plot(k_range,scores[1:])
    plt.scatter(bestK,scores[bestK],marker='o',c='red')
    plt.xlabel('Value of K in KNN')
    plt.ylabel('Score')
    plt.show()
    # 接主函数内

    start = time.time()
    # 选择最佳K值,这一步如果觉得耗时太长,其实可以去掉
    # 为了缩短时间,我们只取10000个数据用于选择最佳K值
    best_K = findBestK(X_train[:10000], y_train[:10000])
    print(f"best_k is {best_K}")


    end = time.time()
    print(f"run time is {end - start}")

 结果:

best_k is 4
run time is 54.95526051521301

 这一步太慢了!!整整耗时55秒。强烈建议不要进行,如果需要最佳K值,上面已经算出来了。

训练模型,测试模型得分

    start = time.time()
    # 选择最佳K值,这一步如果觉得耗时太长,其实可以去掉
    # 为了缩短时间,我们只取10000个数据用于选择最佳K值
    # best_K = findBestK(X_train[:10000], y_train[:10000])
    # print(f"best_k is {best_K}")

    # 训练模型
    model = KNeighborsClassifier(n_neighbors=4)
    model.fit(X_train,y_train)
    # 计算得分
    y_predict = model.predict(X_test)
    score = f1_score(y_test, y_predict, average='weighted')
    print(f"predict score is {score}")
    end = time.time()
    print(f"run time is {end - start}")

 结果:

predict score is 0.9681286795453637
run time is 7.25964617729187

可以看出模型很不错!到此为止,使用KNN进行手写数字识别可以说完成了。

但是,作为一个合格的玩具,它还没完。

接下来是可选内容:把它变成可以玩的玩具!


应用手写数字识别

现在假设你拿到了这样一张图片:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第5张图片

 很显然,它是彩色的,但是我们之前训练用的图片是灰色的,而且,中间是白色的,像这样:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第6张图片

另外,两张像素可能不一样,前者像素不确定,后者为28*28所以我们需要先把图片做一些预处理:

  1. 将彩色图片变为灰色图片(即将三图层变为单图层)

  2. 将图片像素压缩为28*28

  3. 可以将图片二值化(即0和255),可能识别效果会更好。

图片预处理

接下来会用到opencv-python包,但是注意高版本的tensorflow和opencv-python可能出现不兼容的问题。最开始我用的tensorflow2.9.0,对应的numpy为1.23。但是安装opencv后会自动把numpy降为1.16,导致tensorflow用不了。所以这里可能需要切换一下环境。

我用的环境:tensorflow2.6.0+opencv4.5.5+numpy1.19.5

tensorflow版本降低后,数据集的导入可能会发生变化。

改变导入形式:

import cv2
import time
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
from sklearn.metrics import f1_score
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier

将彩色图片以灰度图片导入

查看原图片:

    path = 'number1.png' # path为图片储存位置
    # 原图像
    img1 = plt.imread(path) 
    plt.imshow(img1) 
    plt.show()

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第7张图片

 以灰度图片导入并查看:

    imgOriginal = cv2.imread(path,0)
    img = np.asarray(imgOriginal)
    print(f"before resize shape is:{img.shape}")
    img = cv2.resize(img,(28,28))
    print(f"after resize shape is:{img.shape}")
    plt.imshow(img,cmap='Greys_r') # cmap='Greys_r' 使plt显示灰度图像
    plt.show()

如果不加cmap='Greys_r',matplotlib会把这张灰色图片由低维(1维)映射到高维(3维),然后以彩色图片的形式展出,这显然不符合我们这里的需求。

输出:

before resize shape is:(188, 186)
after resize shape is:(28, 28)

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第8张图片

黑白颠倒以及二值化

我们的样本数据集为黑底白字,所以需要将这张图片也要变为黑底白字。同时我们可以将其二值化,变为黑白图像:

这里的二值化只是简单实现了下,还有更好的二值化方法,以后再学了。

# 二值化
def Binarization(img,Reverse = False):
    '''
    :param img:
    :param Reverse: 是否黑白颠倒
    :return:
    '''
    if Reverse == False:
        img = np.where(img > 160,255,0)
    else:
        img = np.where(img > 160,0,255)
    return img

 现在将图片二值化:

    img = Binarization(img,Reverse = True)
    plt.imshow(img, cmap='Greys_r')  # cmap='Greys_r' 使plt显示灰度图像
    plt.show()

 【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第9张图片

 最后不要忘记了归一化和转化矩阵:

    X = img/255.0
    X = X.reshape(-1,28*28)

使用模型识别手写数字 

    predict = model.predict(X)
    print(f"手写数字识别为:{predict[0]}")

结果:

手写数字识别为:2

完整代码

import cv2
import time
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
from sklearn.metrics import f1_score
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier


# 交叉验证法求最好K值
def findBestK(X_train,y_train,max_k = 50):
    k_scores = [0]
    for k in range(1,max_k+1):
        print(f"{k} time")
        Knn = KNeighborsClassifier(n_neighbors=k)
        # 交叉验证,scoring='f1_weighted' 使用的与之前手写的交叉验证使用同一个F1-score规则
        scores = cross_val_score(Knn,X_train,y_train,cv=5,scoring='f1_weighted')
        k_scores.append(scores.mean())
    k_scores = np.array(k_scores)
    bestK = k_scores.argmax()
    # plt_KScore(k_scores,max_k,bestK)
    return bestK

# 绘制K对应的Score图像
def plt_KScore(scores,K,bestK):
    k_range = range(1,K+1)
    plt.plot(k_range,scores[1:])
    plt.scatter(bestK,scores[bestK],marker='o',c='red')
    plt.xlabel('Value of K in KNN')
    plt.ylabel('Score')
    plt.show()

# 计算模型得分
def cal_score(model,X_test,y_test):
    y_predict = model.predict(X_test)
    score = f1_score(y_test, y_predict, average='weighted')
    print(f"predict score is {score}")

# 二值化
def Binarization(img,Reverse):
    '''
    :param img:
    :param Reverse: 是否黑白颠倒
    :return:
    '''
    if Reverse == False:
        img = np.where(img > 160,255,0)
    else:
        img = np.where(img > 160,0,255)
    return img

# 识别数字
def identification_number(path,model = None,Reverse = False):
    # 原图像
    # img1 = plt.imread(path)
    # plt.imshow(img1)
    # plt.show()

    imgOriginal = cv2.imread(path,0)
    img = np.asarray(imgOriginal)
    img = cv2.resize(img,(28,28))
    # plt.imshow(img,cmap='Greys_r') # cmap='Greys_r' 使plt显示灰度图像
    # plt.show()

    img = Binarization(img,Reverse)
    # plt.imshow(img, cmap='Greys_r')  # cmap='Greys_r' 使plt显示灰度图像
    # plt.show()

    X = img/255.0
    X = X.reshape(-1,28*28)
    predict = model.predict(X)
    print(f"手写数字识别为:{predict[0]}")

if __name__ == '__main__':
    # 加载数据
    (X_train,y_train),(X_test,y_test) = mnist.load_data()
    # 归一化
    X_train = X_train /255.0
    X_test = X_test /255.0
    # 将训练集和测试集转化为二维数组
    X_train = X_train.reshape([-1,28*28])
    X_test = X_test.reshape([-1,28*28])

    # start = time.time()

    # 选择最佳K值,这一步如果觉得耗时太长,其实可以去掉
    # 为了缩短时间,我们只取10000个数据用于选择最佳K值
    # best_K = findBestK(X_train[:10000], y_train[:10000])
    # print(f"best_k is {best_K}")

    # 构建模型,喂数据
    model = KNeighborsClassifier(n_neighbors=4)
    model.fit(X_train,y_train)
    #
    # # 计算得分
    # cal_score(model,X_test,y_test)

    # 识别图片
    img_path = 'number1.png'
    identification_number(img_path,model,Reverse=True) # 在这里修改是否需要颠倒图像
    # end = time.time()

对模型的总结和评价 

实际上因为没有用神经网络来做这个手写数字识别模型,加上二值化写的比较草率,它对于真实手写数字识别效果并没有预料的好。待会儿会举两个识别自己手写数字的例子。

在二值化时阈值的选择影响比较大,最开始阈值设置的200,结果自己手写的数字二值化后变成了一片白或黑色。后来调整到160-170左右,感觉比较合适。

咱也是第一次做手写数字识别玩具,整个过程还是很有意思的。

自己写的数字:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第10张图片

 二值化和预测结果:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第11张图片

手写数字识别为:5

 识别失败:

【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第12张图片【机器学习笔记9】K邻近(KNN)原理、手动实现与具体应用_第13张图片

手写数字识别为:1

你可能感兴趣的:(机器学习,分类算法,机器学习,算法,python,分类算法)