k-近邻算法

一、算法原理
算法原理是什么?允许我不严谨的说一下:首先有一堆有标签的样本,比如有一堆各种各样的鸟(样本集),我知道各种鸟的不同外貌(特征),比如羽毛颜色有无脚蹼身体重量身体长度以及最重要的它属于哪一种鸟类别/标签);然后给我一只不是这堆鸟中的一只鸟(测试样本),让我观察了它的羽毛颜色等后,让我说出它属于哪一种鸟?我的做法是:遍历之前的一堆鸟,分别比较每一只鸟的羽毛颜色、身体重量等特征与给定鸟的相应特征,并给出这两只鸟的相似度。最终,从那一堆鸟中找出相似度最大的前k只,然后统计这k只鸟的分类,最后把分类数量最多的那只鸟的类别作为给定的类别。虽然结果不一定准确,但是是有理论支持的,那就是概率论,哈哈。
下面来看一下书上对这个算法的原理介绍:存在一个训练样本集,并且每个样本都存在标签(有监督学习)。输入没有标签的新样本数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取出与样本集中特征最相似的数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,而且k通常不大于20。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
二、如何解决问题
没接触过的同学应该能懂了吧。书中的举例是对电影的题材进行分类:爱情片or动作片。依据电影中打斗镜头和接吻镜头的数量。下面来看一下如何用kNN来解决这个问题。


要解决给定一部电影,判断其属于哪一种电影这个问题,就需要知道这个未知电影存在多少个打斗镜头和接吻镜头,如上图所示,问号位置所代表的两种镜头次数分别是多少?
下面我们来看一下图中电影的特征值,如下表:

    相信看过数据以后,即使不知道未知电影(?)属于哪一种类型,但是可以通过某个计算方法计算出来。

第一步:首先计算未知电影与已知电影的相似度(抽象距离--相似度越小,距离越远)。具体如何计算暂且不考虑。下面看一下相似度列表:

第二步:再将相似度列表排序,选出前k个最相似的样本。此处我们假设k=3,将上表中的相似度进行排序后前3分别是:He’s Not Really into DudesBeautiful WomanCalifornia Man
第三步:统计最相似样本的分类。此处很容易知道这3个样本均为爱情片。
第四步:将分类最多的类别作为未知电影的分类。那么我们就得出结论,未知电影属于爱情片。

    下面贴一下书上总结的k近邻算法的一般流程:
#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  
      
    # 把分类结果进行排序,然后返回得票数最多的分类结果  
    # classCount.items():迭代器,获得每一个对象
    # operator.itemgetter(1):表示获取函数一维数据进行排序,即按照其投票数进行排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)  
    return sortedClassCount[0][0] 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容