K-近邻

1 算法概述
1.1 算法特点

简单地说,k-近邻算法采用测量不同特征值之间的距离方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型

1.2 工作原理
存在一个训练样本集,并且每个样本都存在标签(有监督学习)。输入没有标签的新样本数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取出与样本集中特征最相似的数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,而且k通常不大于20。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

1.3 实例解释
以电影分类为例子,使用k-近邻算法分类爱情片和动作片。有人曾经统计过很多电影的打斗镜头和接吻镜头,下图显示了6部电影的打斗和接吻镜头数。 假如有一部未看过的电影,如何确定它是爱情片还是动作片呢?
这里写图片描述
①首先需要统计这个未知电影存在多少个打斗镜头和接吻镜头,下图中问号位置是该未知电影出现的镜头数
这里写图片描述
②之后计算未知电影与样本集中其他电影的距离(相似度),具体算法先忽略,结果如下表所示:
这里写图片描述
③将相似度列表排序,选出前k个最相似的样本。此处我们假设k=3,将上表中的相似度进行排序后前3分别是:He’s Not Really into Dudes,Beautiful Woman,California Man。
④统计最相似样本的分类。此处很容易知道这3个样本均为爱情片。
⑤将分类最多的类别作为未知电影的分类。那么我们就得出结论,未知电影属于爱情片。

算法流程:将已有的特征及标签存入列表中,将待分类的待测数据转换成与训练样本集等格式的矩阵并作差,将前K个距离待测数据最近的训练样本做统计,取出投票结果,将票数最多的类别作为待测样本的类别

2 代码实现
2.1 k-近邻简单分类的应用
2.1.1 算法一般流程
这里写图片描述

2.1.2 Python实现代码及注释

#coding=UTF8
from numpy import *
import operator

def createDataSet():
    """
    函数作用:构建一组训练数据(训练样本),共4个样本
    同时给出了这4个样本的标签,及labels
    """
    group = array([
        [1.0, 1.1],
        [1.0, 1.0],
        [0. , 0. ],
        [0. , 0.1]
    ])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

def classify0(inX, dataset, labels, k):
    """
    inX 是输入的测试样本,是一个[x, y]样式的
    dataset 是训练样本集
    labels 是训练样本标签
    k 是top k最相近的
    """
    # shape返回矩阵的[行数,列数],
    # 那么shape[0]获取数据集的行数,
    # 行数就是样本的数量
    dataSetSize = dataset.shape[0] 

    """
    下面的求距离过程就是按照欧氏距离的公式计算的。
    即 根号(x^2+y^2)
    """
    # tile属于numpy模块下边的函数
    # tile(A, reps)返回一个shape=reps的矩阵,矩阵的每个元素是A
    # 比如 A=[0,1,2] 那么,tile(A, 2)= [0, 1, 2, 0, 1, 2]
    # tile(A,(2,2)) = [[0, 1, 2, 0, 1, 2],
    #                  [0, 1, 2, 0, 1, 2]]
    # tile(A,(2,1,2)) = [[[0, 1, 2, 0, 1, 2]],
    #                    [[0, 1, 2, 0, 1, 2]]] 
    # 上边那个结果的分开理解就是:
    # 最外层是2个元素,即最外边的[]中包含2个元素,类似于[C,D],而此处的C=D,因为是复制出来的
    # 然后C包含1个元素,即C=[E],同理D=[E]
    # 最后E包含2个元素,即E=[F,G],此处F=G,因为是复制出来的
    # F就是A了,基础元素
    # 综合起来就是(2,1,2)= [C, C] = [[E], [E]] = [[[F, F]], [[F, F]]] = [[[A, A]], [[A, A]]]
    # 这个地方就是为了把输入的测试样本扩展为和dataset的shape一样,然后就可以直接做矩阵减法了。
    # 比如,dataset有4个样本,就是4*2的矩阵,输入测试样本肯定是一个了,就是1*2,为了计算输入样本与训练样本的距离
    # 那么,需要对这个数据进行作差。这是一次比较,因为训练样本有n个,那么就要进行n次比较;
    # 为了方便计算,把输入样本复制n次,然后直接与训练样本作矩阵差运算,就可以一次性比较了n个样本。
    # 比如inX = [0,1],dataset就用函数返回的结果,那么
    # tile(inX, (4,1))= [[ 0.0, 1.0],
    #                    [ 0.0, 1.0],
    #                    [ 0.0, 1.0],
    #                    [ 0.0, 1.0]]
    # 作差之后
    # diffMat = [[-1.0,-0.1],
    #            [-1.0, 0.0],
    #            [ 0.0, 1.0],
    #            [ 0.0, 0.9]]
    diffMat = tile(inX, (dataSetSize, 1)) - dataset

    # diffMat就是输入样本与每个训练样本的差值,然后对其每个x和y的差值进行平方运算。
    # diffMat是一个矩阵,矩阵**2表示对矩阵中的每个元素进行**2操作,即平方。
    # sqDiffMat = [[1.0, 0.01],
    #              [1.0, 0.0 ],
    #              [0.0, 1.0 ],
    #              [0.0, 0.81]]
    sqDiffMat = diffMat ** 2

    # axis=1表示按照横轴,sum表示累加,即按照行进行累加。
    # sqDistance = [[1.01],
    #               [1.0 ],
    #               [1.0 ],
    #               [0.81]]
    sqDistance = sqDiffMat.sum(axis=1)

    # 对平方和进行开根号
    distance = sqDistance ** 0.5

    # 按照升序进行快速排序,返回的是原数组的下标。
    # 比如,x = [30, 10, 20, 40]
    # 升序排序后应该是[10,20,30,40],他们的原下标是[1,2,0,3]
    # 那么,numpy.argsort(x) = [1, 2, 0, 3]
    sortedDistIndicies = distance.argsort()

    # 存放最终的分类结果及相应的结果投票数
    classCount = {}

    # 投票过程,就是统计前k个最近的样本所属类别包含的样本个数
    for i in range(k):
        # index = sortedDistIndicies[i]是第i个最相近的样本下标
        # voteIlabel = labels[index]是样本index对应的分类结果('A' or 'B')
        voteIlabel = labels[sortedDistIndicies[i]]
        # classCount.get(voteIlabel, 0)返回voteIlabel的值,如果不存在,则返回0
        # 然后将票数增1
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1

    # 把分类结果进行排序,然后返回得票数最多的分类结果
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

if __name__== "__main__":
    # 导入数据
    dataset, labels = createDataSet()
    inX = [0.1, 0.1]
    # 简单分类
    className = classify0(inX, dataset, labels, 3)
    print('the class of test sample is %s' %className)

示例:在约会网站上使用K-近邻算法
这里写图片描述
2.2.2 Python实现代码
datingTestSet.txt 文件中有1000行的约会数据,样本主要包括以下3种特征:
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰淇淋公升数
将上述特征数据输人到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式 。在kNN.py中创建名为 file2matrix 的函数,以此来处理输人格式问题。该函数的输人为文件名字符串,输出为训练样本矩阵和类标签向量。autoNorm 为数值归一化函数,将任意取值范围的特征值转化为0到1区间内的值。最后,datingClassTest 函数是测试代码。
将下面的代码增加到 kNN.py 中。

def file2matrix(filename):
    """
    从文件中读入训练数据,并存储为矩阵
    """
    fr = open(filename)
    arrayOlines = fr.readlines()
    numberOfLines = len(arrayOlines)   #获取 n=样本的行数
    returnMat = zeros((numberOfLines,3))   #创建一个2维矩阵用于存放训练样本数据,一共有n行,每一行存放3个数据
    classLabelVector = []    #创建一个1维数组用于存放训练样本标签。
    index = 0
    for line in arrayOlines:
        # 把回车符号给去掉
        line = line.strip()
        # 把每一行数据用\t分割
        listFromLine = line.split('\t')
        # 把分割好的数据放至数据集,其中index是该样本数据的下标,就是放到第几行
        returnMat[index,:] = listFromLine[0:3]
        # 把该样本对应的标签放至标签集,顺序与样本对应。
        classLabelVector.append(listFromLine[-1])
        index += 1
    return returnMat,classLabelVector

def autoNorm(dataSet):
    """"
    训练数据归一化,将数字特征值转化为 0 到 1 的区间。
    公式为:newValues=(oldValue-min)/(max-min)
    """
    #获取数据集中每一列的最小数值
    minVals=dataSet.min(0)
    maxvals=dataSet.max(0)
    ranges=maxvals-minVals
    normDataSet=zeros(shape(dataSet))
    m=dataSet.shape[0]
    normDataSet=dataSet - tile(minVals,(m,1))
    normDataSet=normDataSet/tile(ranges,(m,1))
    return normDataSet,ranges,minVals

def datingClassTest():
    hoRatio=0.10    #将前10%的数据作为测试数据
    datingDataMat,datingLabels=file2matrix("E:\\机器学习实战源代码\\机器学习实战源代码\\machinelearninginaction\Ch02\\datingTestSet2.txt")
    normMat,ranges,minBals=autoNorm(datingDataMat)
    m=normMat.shape[0]
    numTestVecs=int(m*hoRatio)
    errorCount=0.0
    for i in range(numTestVecs):
        classifierResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        # print("the classifier came back with: , the real answer is: %d, result is :%s"
        #      % (classifierResult, datingLabels[i],classifierResult==datingLabels[i]))
        print("the classifier came back with: ",classifierResult,"the real answer is: ",datingLabels[i],"result is : ",classifierResult==datingLabels[i])
        if(classifierResult!=datingLabels[i]):
            errorCount+=1
    print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
    print(errorCount)

2.3 手写识别系统实例
2.3.1 实例数据
为了简单起见,这里构造的系统只能识别数字0到9。需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小 : 宽髙是32像素x 32像素的黑白图像。尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便理解,我们还是将图像转换为文本格式。
trainingDigits是2000个训练样本,testDigits是900个测试样本。
2.3.2 算法的流程
这里写图片描述

from os import listdir
def img2vector(filename):
    """"
    将图片数据转换为 0 1 矩阵。
    每张图片是32*32像素,也就是一共0024个字节
    转换的时候,每行表示一个样本,每个样本含有1024个字节
    """
    returnVect=zeros((1,1024))
    fr=open(filename)
    for i in range(32):
        lineStr=fr.readline()
        for j in range(32):
            #计算出矩阵的第 i 行,第 j 列的值在转换成数组后的位置,将值放入该位置
            returnVect[0,32*i+j]=int(lineStr[j])
    return returnVect
def handwritingClassTest():
    hwLabels=[]
    #将文件夹 trainingDigits 中所有文件的文件名存为一个列表
    trainingFileList=listdir('trainingDigits')
    # m 为总文件数
    m=len(trainingFileList)
    #创建一个有 m 行 1024 列 的矩阵,用来存储转换后的矩阵
    trainingMat=zeros((m,1024))
    for i in range(m):
        fileNameStr=trainingFileList[i]
        #去除文件名中的 .txt 例如9_45.txt 去除之后为 9_45
        fileStr=fileNameStr.split('.')[0]
        #取出 9_45 中的 9
        classNumStr=int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        trainingMat[i,:]=img2vector('trainingDigits/%s' % fileNameStr)
        testFileList=listdir('testDigits')
        errorCount=0.0
        mTest=len(testFileList)
    for i in range(mTest):
        fileNameStr=testFileList[i]
        fileStr=fileNameStr.split('.')[0]
        classNumStr=int(fileStr.split('_')[0])
        vectorUnderTest=img2vector('testDigits/%s'%fileNameStr)
        classifierResult=classify0(vectorUnderTest,trainingMat,hwLabels,3)
        print("the classifier came back with: %d, the real answer is: %d, The predict result is: %s" % (
            classifierResult, classNumStr, classifierResult == classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is: %d / %d" % (errorCount, mTest))
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值