K近邻算法(KNN)

1. k近邻算法(K-Nearest Neighbor,KNN)

  K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路是:在特征空间中,如果一个样本附近的k个最近(即特征空间中最邻近)样本的大多数属于某一个类别,则该样本也属于这个类别。如下图所示:

2. 距离函数的定义

 

  在多维空间中,KNN使用的是欧氏距离度量周围样本距离预测样本的距离。公式如下:

3. KNN实现鸢尾花(Iris)分类

  先看一下,鸢尾花长这样子。

  鸢尾花数据集记录了三类花以及它们的四种属性。(四种属性:花萼长度,花萼宽度,花瓣长度,花瓣宽度;3种标签:Setosa,versicolor,virginica)

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

iris_dataset = load_iris()  # 载入数据集
feature = iris_dataset['data']
target = iris_dataset['target']
x_train, x_test, y_train, y_test = train_test_split(feature, target, test_size=0.2, random_state=0)
print(x_train)
print(y_train)#标签0,1,2表示三种不同的花卉(山鸢尾、变色鸢尾、维吉尼亚鸢尾)
knn = KNeighborsClassifier(n_neighbors=3)
knn = knn.fit(x_train, y_train)
y_pred = knn.predict(x_test)
print('模型的分类结果:', y_pred)
print('真实的分类结果:', y_test)
predict_result=knn.predict([[6.1, 3.1, 4.7, 2.1]])
print(predict_result)#结果是2,维吉尼亚鸢尾

4. KNN实现手写数字识别分类

  手写数字中用到的数据集为minist手写数字数据集,每张图片大小为28*28灰度图片。训练集为6万张,测试集为1万张,类别为0~9。

  一共包含四个文件:

  train-images-idx3-ubyte.gz:训练集图像(9912422 字节)55000张训练集 + 5000张验证集;

  train-labels-idx1-ubyte.gz:训练集标签(28881 字节)训练集对应的标签;

  t10k-images-idx3-ubyte.gz:测试集图像(1648877 字节)10000张测试集;

  t10k-labels-idx1-ubyte.gz:测试集标签(4542 字节)测试集对应的标签;

  比如读取其中一个数据,转换为向量形式,这里我把向量放在了txt中,如图所示,很显然代表数字0.

  由于是灰度图(一个通道),数字范围为0~255,进一步,可以将向量表示的图像进行阈值二值化,变为0与1,如下图:

  代码实现:

import gzip
import numpy as np
import matplotlib.pyplot as plt
import cv2

def readImage(path,num_images=1):
    f=gzip.open(path,'r')
    image_size = 28
    f.read(16)#前16个字节存储的是magic number,number of images,number of rows ,number of columns
    buf=f.read(image_size * image_size * num_images)
    data=np.frombuffer(buf,dtype=np.uint8).astype(np.float32)#ndarray类型
    print("data",type(data))
    data=data.reshape(num_images,image_size,image_size,1)
    image = np.asarray(data).squeeze()
    return image


def readLabel(path,num_images=1):
    f = gzip.open(path,'r')
    f.read(8)
    labels=[]
    for i in range(0,num_images):
        buf = f.read(1)
        label = np.frombuffer(buf, dtype=np.uint8).astype(np.int64)
        labels.append(label)
    return  labels

def calEuclidean(x, y): #计算两向量的欧氏距离
    dist = np.sqrt(np.sum(np.square(x-y)))   # 注意:np.array 类型的数据可以直接进行向量、矩阵加减运算。np.square 是对每个元素求平均
    return dist

def knn(test,train,trainLabel,k):   #给定任意一个图片的向量采用k近邻求得所属类别,k表示距离最近的K的元素
    distAll=[]
    for i in range(0,len(train)):
        dist=calEuclidean(test,train[i])
        distAll.append(dist)
    #对距离排序
    # print(distAll)
    distAllTuple = sorted(zip(distAll, range(len(distAll))))
    distAllTuple.sort(key=lambda t: t[0])  #
    sort_distAll_position = [x[1] for x in distAllTuple]  # 得到排序后原来的下标,方便后面找label
    # print(sort_distAll_position)
    #计算前k个哪个类别出现的最多
    labelNum={}
    for i in range(0,k):
        if str(trainLabel[sort_distAll_position[i]][0]) not in labelNum:
            labelNum[str(trainLabel[sort_distAll_position[i]][0])]=1
        else:
            labelNum[str(trainLabel[sort_distAll_position[i]][0])]+=1
    print("在最近的k个元素中,每个类别出现的频率为",labelNum)
    return max(labelNum,key=labelNum.get)#返回数字最大所对应的类别

def accuracy(predictResult,testlabels):
    all = len(predictResult)
    TP = 0
    for i in range(0,len(predictResult)):
        if str(testlabels[i][0])== predictResult[i]:
            TP+=1
    return TP/all


if __name__=="__main__":
    path1='MNIST_data/train-images-idx3-ubyte.gz'
    # num_images=60000
    num_images = 1000
    imageArray=readImage(path1,num_images)#得到的是num个图像的向量
    # retval, dst = cv2.threshold(imageArray[1], 50, 1, cv2.THRESH_BINARY)#可以对图像阈值处理,处理第2个图片为二值图像
    retval, dst = cv2.threshold(imageArray, 50, 1, cv2.THRESH_BINARY)  # 可以对所有图像阈值处理,处理为二值图像
    # plt.imshow(imageArray,cmap='Greys_r')
    # plt.imshow(dst, cmap='Greys_r')
    # print(dst)
    # plt.show()
    labels=readLabel('MNIST_data/train-labels-idx1-ubyte.gz',1000)
    # print(labels[1])#类别为0
    imageArraytest = readImage('MNIST_data/t10k-images-idx3-ubyte.gz', num_images=100)#读取100个测试图片
    testlabels=readLabel('MNIST_data/t10k-labels-idx1-ubyte.gz',100)#读取100个测试图片的标签
    retval, dsttest = cv2.threshold(imageArraytest, 50, 1, cv2.THRESH_BINARY)  # 可以对图像阈值处理,处理为二值图像
    # plt.imshow(imageArray,cmap='Greys_r')
    plt.imshow(dsttest[0], cmap='Greys_r')#显示7的图像
    print(dsttest[0])
    plt.show()
    #随便拿第一张图片测试
    labelResult=knn(dsttest[0], dst, labels, 100)
    print("预测的结果为",labelResult)#输出为7
    print("实际结果为",testlabels[0])
    #以下测试100个图片,并计算准确率
    predictResult=[]
    for i in range(0,100):
        labelResult = knn(dsttest[i], dst, labels, 100)
        predictResult.append(labelResult)
    print(predictResult)
    Pre_accuracy=accuracy(predictResult, testlabels)
    print("准确率",Pre_accuracy)

  运行结果:

 

  最后可以根据取不同的K值,找到最优K,另外使用交叉验证法能够找到更好的K值。

5.  KNN分类算法的优点与缺点

  优点:

    1.简单,易于理解,易于实现,无需估计参数,无需训练;

    2.适合对稀有事件进行分类;

    3.特别适合于多分类问题(multi-modal,对象具备多个类别标签), KNN比SVM的表现要好。

  缺点:

    1.样本不平衡时,会使结果出现误差

    2.计算量庞大

    3.因为不须要训练,因此算法的可控性比较差

 

  附上数据与代码:  链接:https://pan.baidu.com/s/1_3tUaXqSsq3uKlx1o-FbPA     提取码:mqff

 

  若存在不足或错误之处,欢迎指正与评论!

 

参考资料

             https://blog.csdn.net/qq_45603919/article/details/120478822

             https://blog.csdn.net/qq_42302831/article/details/102553007

             https://blog.csdn.net/asialee_bird/article/details/81051281

             http://www.javashuo.com/article/p-owdkjgnl-mc.html

             https://blog.csdn.net/asialee_bird/article/details/81051281

             https://www.cnblogs.com/zhangzhenw/p/14583195.html

 

posted @ 2022-12-03 20:07  wancy  阅读(413)  评论(0编辑  收藏  举报