k近邻算法(KNN)

本文详细介绍k-最近邻(kNN)算法原理及其Python实现过程,并通过手写数字识别案例展示了算法的应用效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:http://blog.youkuaiyun.com/zouxy09/article/details/16955347

在原文的最终代码上稍作修改,添加了中文注释,运行环境为3.6

一、kNN算法分析

K最近邻(k-Nearest Neighbor,KNN)分类算法可以说是最简单的机器学习算法了。它采用测量不同特征值之间的距离方法进行分类。它的思想很简单:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

比如上面这个图,我们有两类数据,分别是蓝色方块和红色三角形,他们分布在一个上图的二维中间中。那么假如我们有一个绿色圆圈这个数据,需要判断这个数据是属于蓝色方块这一类,还是与红色三角形同类。怎么做呢?我们先把离这个绿色圆圈最近的几个点找到,因为我们觉得离绿色圆圈最近的才对它的类别有判断的帮助。那到底要用多少个来判断呢?这个个数就是k了。如果k=3,就表示我们选择离绿色圆圈最近的3个点来判断,由于红色三角形所占比例为2/3,所以我们认为绿色圆是和红色三角形同类。如果k=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。从这里可以看到,k的值还是很重要的。

KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合。

该算法在分类时有个主要的不足是,当样本不平衡时,如一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新样本时,该样本的K个邻居中大容量类的样本占多数。因此可以采用权值的方法(和该样本距离小的邻居权值大)来改进。该方法的另一个不足之处是计算量较大,因为对每一个待分类的文本都要计算它到全体已知样本的距离,才能求得它的K个最近邻点。目前常用的解决方法是事先对已知样本点进行剪辑,事先去除对分类作用不大的样本。该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量较小的类域采用这种算法比较容易产生误分[参考机器学习十大算法]。

总的来说就是我们已经存在了一个带标签的数据库,然后输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似(最近邻)的分类标签。一般来说,只选择样本数据库中前k个最相似的数据。最后,选择k个最相似数据中出现次数最多的分类。其算法描述如下:

1)计算已知类别数据集中的点与当前点之间的距离;

2)按照距离递增次序排序;

3)选取与当前点距离最小的k个点;

4)确定前k个点所在类别的出现频率;

5)返回前k个点出现频率最高的类别作为当前点的预测分类。

二、Python实现

对于机器学习而已,Python需要额外安装三件宝,分别是Numpy,scipy和Matplotlib。前两者用于数值计算,后者用于画图。安装很简单,直接到各自的官网下载回来安装即可。安装程序会自动搜索我们的python版本和目录,然后安装到python支持的搜索路径下。反正就python和这三个插件都默认安装就没问题了。

另外,如果我们需要添加我们的脚本目录进Python的目录(这样Python的命令行就可以直接import),可以在系统环境变量中添加:PYTHONPATH环境变量,值为我们的路径。

2.1、kNN基础实践

一般实现一个算法后,我们需要先用一个很小的数据库来测试它的正确性,否则一下子给个大数据给它,它也很难消化,而且还不利于我们分析代码的有效性。

  首先,我们新建一个kNN.py脚本文件,文件里面包含两个函数,一个用来生成小数据库,一个实现kNN分类算法。代码如下:
[python] view plain copy
#########################################  
# kNN: k Nearest Neighbors  

# Input:      newInput: vector to compare to existing dataset (1xN)  
#             dataSet:  size m data set of known vectors (NxM)  
#             labels:   data set labels (1xM vector)  
#             k:        number of neighbors to use for comparison   

# Output:     the most popular class label  
#########################################  

from numpy import *  
import operator  

# create a dataset which contains 4 samples with 2 classes  
def createDataSet():  
    # create a matrix: each row as a sample  
    group = array([[1.0, 0.9], [1.0, 1.0], [0.1, 0.2], [0.0, 0.1]])  
    labels = ['A', 'A', 'B', 'B'] # four samples and two classes  
    return group, labels  

# classify using kNN  
def kNNClassify(newInput, dataSet, labels, k):  
    numSamples = dataSet.shape[0] # shape[0] stands for the num of row  

    ## step 1: calculate Euclidean distance  
    # tile(A, reps): Construct an array by repeating A reps times  
    # the following copy numSamples rows for dataSet  
    diff = tile(newInput, (numSamples, 1)) - dataSet # Subtract element-wise  
    squaredDiff = diff ** 2 # squared for the subtract  
    squaredDist = sum(squaredDiff, axis = 1) # sum is performed by row  
    distance = squaredDist ** 0.5  

    ## step 2: sort the distance  
    # argsort() returns the indices that would sort an array in a ascending order  
    sortedDistIndices = argsort(distance)  

    classCount = {} # define a dictionary (can be append element)  
    for i in xrange(k):  
        ## step 3: choose the min k distance  
        voteLabel = labels[sortedDistIndices[i]]  

        ## step 4: count the times labels occur  
        # when the key voteLabel is not in dictionary classCount, get()  
        # will return 0  
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1  

    ## step 5: the max voted class will return  
    maxCount = 0  
    for key, value in classCount.items():  
        if value > maxCount:  
            maxCount = value  
            maxIndex = key  

    return maxIndex   

然后我们在命令行中这样测试即可:

import kNN  
from numpy import *   

dataSet, labels = kNN.createDataSet()  

testX = array([1.2, 1.0])  
k = 3  
outputLabel = kNN.kNNClassify(testX, dataSet, labels, 3)  
print "Your input is:", testX, "and classified to class: ", outputLabel  

testX = array([0.1, 0.3])  
outputLabel = kNN.kNNClassify(testX, dataSet, labels, 3)  
print "Your input is:", testX, "and classified to class: ", outputLabel  
   这时候会输出:
Your input is: [ 1.2  1.0] and classified to class:  A  
Your input is: [ 0.1  0.3] and classified to class:  B  

2.2、kNN进阶

这里我们用kNN来分类一个大点的数据库,包括数据维度比较大和样本数比较多的数据库。这里我们用到一个手写数字的数据库,可以到这里下载。这个数据库包括数字0-9的手写体。每个数字大约有200个样本。每个样本保持在一个txt文件中。手写体图像本身的大小是32x32的二值图,转换到txt文件保存后,内容也是32x32个数字,0或者1,如下:


这里写图片描述

数据库解压后有两个目录:目录trainingDigits存放的是大约2000个训练数据,testDigits存放大约900个测试数据。

这里我们还是新建一个kNN.py脚本文件,文件里面包含四个函数,一个用来生成将每个样本的txt文件转换为对应的一个向量,一个用来加载整个数据库,一个实现kNN分类算法。最后就是实现这个加载,测试的函数。

import numpy
import os


# 用kNN分类
def kNNClassify(newInput, dataSet, labels, k):
    # 计算行数
    rowCount = dataSet.shape[0]
    # 得到所有的差向量
    vectorDiff = numpy.tile(newInput, (rowCount, 1)) - dataSet
    # 平方
    squareDiff = vectorDiff ** 2
    # 求和
    sumDiff = numpy.sum(squareDiff, axis=1)
    # 开根号得到差向量的模,即距离
    distance = sumDiff ** 0.5

    # 排序
    sortedDistIndexs = numpy.argsort(distance)

    classCount = {}
    # 遍历最近的K个样本,将结果和出现次数加入classCount
    for i in range(k):
        voteLabel = labels[sortedDistIndexs[i]]
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1

    maxCount = 0
    # 遍历classCount,得到出现频率最高的类别
    for key, value in classCount.items():
        if value > maxCount:
            maxCount = value
            result = key

    return result

#读取文件中的内容(32*32个数字,0或者1)返回一个向量,大小为[1,1024]
def getImgVectorFromFile(filename):
    rows = 32
    cols = 32
    imgVector = numpy.zeros((1, rows * cols))
    with open(filename) as fileIn:
        for row in range(rows):
            lineStr = fileIn.readline()
            for col in range(cols):
                imgVector[0, row * 32 + col] = int(lineStr[col])

    return imgVector


def loadDataSet():
    print("获取训练集。。。。。。")
    dataSetDir = "E:/6Studay/机器学习/digits/"
    trainingFileList = os.listdir(dataSetDir + "trainingDigits")

    trainingNum = len(trainingFileList)
    train_x = numpy.zeros((trainingNum, 1024))
    train_y = []
    for i in range(trainingNum):
        filename = trainingFileList[i]

        train_x[i, :] = getImgVectorFromFile(dataSetDir + "trainingDigits/%s" % filename)
        #文件名第一位是正确结果
        label = int(filename.split('_')[0])
        train_y.append(label)

    print("获取测试集。。。。。。")
    testingFileList = os.listdir(dataSetDir + "testDigits")

    testNum = len(testingFileList)
    test_x = numpy.zeros((testNum, 1024))
    test_y = []
    for i in range(testNum):
        filename = testingFileList[i]

        test_x[i, :] = getImgVectorFromFile(dataSetDir + "testDigits/%s" % filename)

        label = int(filename.split('_')[0])
        test_y.append(label)

    return train_x, train_y, test_x, test_y

测试如下:

import kNN

print("加载数据:")
train_x, train_y, test_x, text_y =kNN. loadDataSet()

print("进行测试。。。。。。")
numTestSamples = test_x.shape[0]
matchCount = 0

for i in range(numTestSamples):
    predict =kNN. kNNClassify(test_x[i], train_x, train_y, 3)
    if predict == text_y[i]:
        matchCount += 1

accuracy = float(matchCount) / numTestSamples

print("准确率:%.2f%%" % (accuracy * 100))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值