重构KNN算法和Python实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_44002167/article/details/101773623

导读:

   文章比较长,笔者尝试使用简单的语言使读者知道笔者所要描述的问题。如果有人想看代码,请直接翻到文章末尾。代码可直接运行,笔者使用的python 3.6.5+pyCharm。

----------------------------------------------------------------------------------------------------------- 本篇文章主要有以下几个方面:
   (1)KNN的原理
   (2)KNN需要解决的问题
   (3)KNN的重构
   (4)KNN关键算法
   (5)错误处理
   (6)总结
   (7)其他

一,K近邻算法的原理:

      K近邻算法,就是K个最近邻居的意思。核心思想用一句话概括就是“近朱者赤,近墨者黑”。

二,K近邻算法需要解决的问题:

   (1),计算距离:即给定测试对象,计算它与训练集中每 个对象的距离
确切的说为多维数组间距离的计算
   (2),寻找邻居:即确定距离最近的k个训练对象
对距离的集合进行排序,找出最近的k个对象
   (3),确定分类:即根据这k个近邻归属的主要类别,来对测试对象进行分类。
          确定分类时所需要解决的问题:
          (a),要有适当的训练数据集;
          (b),距离函数的选取;
          (c),k取值的决定;
          (d),最终分类决策时确定测试样本类别的方法如何选择。

三,最近邻算法的优缺点:

   优点:
       模型简单,计算简单,准确率高,容易实现,易于操作。
       避免了类不平衡问题。
   缺点:
      预测速度慢,不能处理很多特征的数据集。

四,对KNN算法的重构

(1)下载数据集:

   下载数据集,这里直接使用已有的鸢尾花数据集,它返回到是一个Bunch对象。它包含在scikit-learn的datasets模块中。通过调用load_iris函数来加载数据

(2)切分数据集为训练集和测试集:

   一般来说,训练集占75%,测试集占25%
定义函数:splitDateSets(),默认训练集的比例为0.75
返回值:X_train,Y_train,y_test和y_test,他们都是NumPy数组如书上15所说,
   注意:在对数据进行拆分之前,先利用伪随机数生成器将数据集打乱。给伪随机数设立随机种子random.seed(0),使得每次切割的数据集一致。

(3)KNN算法的实现:

   分两部分,KNN训练KNN模型精度的计算,即包括用训练数据构建模型的算法,也包括对新数据点进行预测的算法。它还包括算法从训练集中提取的信息。【构建模型只需要保存训练数据即可。想要对新数据点做出预测,算法需要在训练数据集中找到最近的数据点,就是它的最近邻】

五,关键算法

(一)欧式距离的说明:

欧几里得距离或欧几里得度量是欧几里得空间中两点间“普通”(即直线)距离。使用这个距离,欧氏空间成为度量空间。相关联的范数称为欧几里得范数。较早的文献称之为毕达哥拉斯度量。

在这里插入图片描述

(二)关键代码

 #每一个测试数据都与训练集中的数据计算距离
    for i in range(f):
        for j in range(m):
            dist = la.norm(X_train[j] - test[i])
            distance.append(dist)
        # 从小到大排序,每个数的索引位置,argsort()函数返回的是数组值从小到大的索引值
        index_sort = np.argsort(distance)
        # 获得距离样本点最近的K个点的标记值y
        near_y = [y_train[i] for i in index_sort[:k]]
        # 统计邻近K个点标记值的数量
        count_y = Counter(near_y)
        # 返回标记值最多的那个标记.most_common(),返回一个列表,包含counter中n个最大数目的元素
        y_predict = count_y.most_common(1)[0][0]
        pdt.append(y_predict)

六,错误分析及结果

      在重构KNN算法计算欧式距离的方法中,错误如下:
数组越界
   很明显,是数组角标越界,但是分析后没有发现,为每一步添加print()方法查看数据变化。
在这里插入图片描述
                     (排序输出及后面均为错误数据)
在这里插入图片描述
                                 (distance数据错误)
   利用excle发现distance在第112的数据的地方后均为错误:即一次循环后均为错误数据,发现自己对norm理解有些错误,经过改正后,distance输出正确,但是后面的数据依然没有正确输入。继续分析逻辑,整理代码。发现变量名重复了。定义了两个k值,后面在K近邻中的k和for语句中的k值重复,换为其他变量,问题解决。

模型精度结果
在这里插入图片描述
在这里插入图片描述
使用其他数据预测:
使用教材【Python机器学习基础教程】18页的数据[5,2.9,1,0.2],进行验证,得到结果如下:
在这里插入图片描述

七,总结

      重构KNN,不仅需要对KNN的思路非常清楚,而且需要对其中的数据计算过程比较清楚,尤其在计算欧式距离的过程中,涉及到的多维矩阵的计算,尽管使用简单的函数就能够实现,但是如果要调用NumPy文档中的数学方法,需要对线性代数中矩阵的知识有所了解,比如说,三角矩阵,否则不能很好的理解多维数组欧式距离的计算。
      在重构这个算法时候,主要有以下问题/麻烦及认识:
      (1) 第一次,万事开头难,任何事情的第一次都是比较麻烦的。重构算法的难,难就在于使用python话的代码叙述流程,相比起其他语言python确实是有有时的,简单易读,但是从其他编程语言将这种思维转变过来,得稍微锻炼一下;
      (2) Python基础不够扎实,虽然也可以一边查,一边写,但这样对于这个作业来说太耗费时间,且极易让人产生挫败感。这样显得就比较困难。
      (3) 编写Python代码的经验还不够,代码出错只能简单的分析错误。不能借用编译器很好的分析,虽然可以借用其他方法,但是,不可避免的走了许多弯路。
      (4) 对于split()函数,课本及其他书籍中均调用sciket-learn中的函数时使用过一个种子数,使得它能够每次生成相同的序列。而使用permutation()每次的执行结果都是不同的。这里应当改进下,可以使用Python中的集合或者列表及推导式,集合的内部实现保证了元素不重复,并做了大量的优化。

八,说明

      在本次重构KNN算法的过程中,即是对机器学习的学习,同时也很考验Python基础能力。在编写该算法中,参考了大量文档和书籍,部分如下:
   (1) Python机器学习基础教程 Andreas C Muller,Sarah Guido
   (2) 大话Python机器学习 张居营
   (3)计算机视觉学习笔记(2.1)-KNN算法中距离矩阵的计算
   (4)机器学习之k-近邻(kNN)算法与Python实现
   (5)索引与切片
   (6)欧几里得度量
   (7)python collections模块详解
   (8)np.linalg.norm(求范数)
   (9)Python-Counter-计数函数
   (10)Numpy中文文档
   (11)【Python】Numpy 中的 shuffle VS permutation
   (12)numpy中argsort函数用法

九,代码

from sklearn.datasets import load_iris
import numpy as np
import numpy.linalg as la
from collections import Counter
import random

#加载数据集
def load_datasets():
    iris_dataset = load_iris()
    return iris_dataset

# 生成不重复随机数列的函数,number为个数
def RandomNumbers(number, start=0, end=0):
    data = []
    n = 0
    random.seed(0)
    while True:
        element = random.randint(start, end)
        if element not in data:
            data.append(element)
            n += 1
        if n == number:
            break
    return data


# 分割数据集,默认为75%的训练集,25%的测试集合
def splitDataSets(dataset, rate=0.75):
    # 分多少数据
    trainSize = int(rate * len(dataset.data))
    # 打乱数组[0,len(dataset.data))的排序后返回
    index = RandomNumbers(len(dataset.data), 0, len(dataset.data) - 1)
    # index = np.random.permutation(len(dataset.data))
    # 分割数据集(X表示数据,y表示标签),以返回的index为下标
    X_train = dataset.data[index[:trainSize]]
    y_train = dataset.target[index[:trainSize]]
    X_test = dataset.data[index[trainSize:]]
    y_test = dataset.target[index[trainSize:]]
    return X_train, y_train, X_test, y_test

# 预测函数
def predict(X_train, y_train, test, k=6):
    pdt = []
    distance = []
    # 计算距离
    m, n = X_train.shape
    f, g = test.shape

    # 每一个测试数据都与训练集中的数据计算距离
    for i in range(f):
        for j in range(m):
            dist = la.norm(X_train[j] - test[i])
            distance.append(dist)
        # 从小到大排序,每个数的索引位置,argsort()函数返回的是数组值从小到大的索引值
        index_sort = np.argsort(distance)
        # 获得距离样本点最近的K个点的标记值y
        near_y = [y_train[i] for i in index_sort[:k]]
        # 统计邻近K个点标记值的数量
        count_y = Counter(near_y)
        # 返回标记值最多的那个标记.most_common(),返回一个列表,包含counter中n个最大数目的元素
        y_predict = count_y.most_common(1)[0][0]
        pdt.append(y_predict)
        distance.clear()
    return pdt


# 评估KNN模型
def evaluationKNN(Y_pre, Y_test):
    # cnt = np.sum(preY == testY)
    # acc = np.divide(cnt, len(testX))
    # print("the accurate of knn:", round(acc, 4))
    print("Test set score:{:.2f}".format(np.mean(Y_pre == Y_test)))

# 主函数
def main():
    # 调用函数加载和切分数据集
    X_train, y_train, X_test, y_test = splitDataSets(load_datasets())
    # 打印出KNN对测试集的预测
    print(predict(X_train, y_train, X_test))
    # 打印出测试集的标签
    print("{}".format(y_test))
    # 对模型精度进行计算
    evaluationKNN(predict(X_train, y_train, X_test), y_test)
    # 对其他数据的预测test = [5, 2.9, 1, 0.2]
    test = np.array([[5, 2.9, 1, 0.2]])
    print("该数据的预测结果为:{}".format(predict(X_train, y_train, test)))


if __name__ == '__main__':
    main()

猜你喜欢

转载自blog.csdn.net/qq_44002167/article/details/101773623