knn算法及实现

什么是KNN

在解释KNN之前,我先给大家举个例子:假如现在院子里分别在不同的栅栏里饲养了10只鸡,8条狗,5只猫,2头猪,这时候从外面又新买了一只鸭子,可是没多余的栅栏了,这时候从这几个家禽家畜的体型外观来分析,是不是应该把鸭子和鸡放在一起饲养呢?我们接下来要说的KNN就是这个思想。
KNN(K-Nearest Neighbor)算法(又叫K近邻)是机器学习算法中最基础、最简单的算法之一。说的直白一点,所谓K最近邻,就是k个最近的邻居的意思,就是是每个样本都可以用它最接近的k个邻居来代表,它既能用于分类,也能用于回归,通过测量不同特征值之间的距离来进行分类。因此KNN核心思想就是对于任意n维输入向量,分别对应于特征空间中的某个点,输出为该特征向量所对应的类别标签或预测值。

KNN的工作原理

KNN和其他机器学习算法不太一样,KNN没有学习的过程,他的工作原理依赖于极限定理,KNN需要的训练数据都是已经确定分类的,利用训练数据对特征向量空间进行划分,并将划分结果作为最终算法模型。然后输入没有标签的数据后,将这个没有标签的数据的每个特征与样本集中的数据对应的特征进行比较,然后提取样本中特征最相近的数据(最近邻)的分类标签作为输入数据的分类标签。
但是在实际操作过程中经常会先找到最相近的前K个数据集,这便是KNN中的K的由来,然后在这K个数据集中再做个统计,统计每个数据的类别,哪个类别的数据集多,就把输入的数据分到哪一类

KNN算法流程

数据准备

我们现在随机生成20名男生的身高体重数据和20名女生的身高体重,然后再随机造一个没有标签的数据(176,55)

        #生成20个男生身高体重数据
        ht_man = np.random.randint(165,190,20)
        man_data = []
        for one in ht_man:
            onedate = np.array((one,one-105+random.randint(-5,10),"男"))
            man_data = np.concatenate((man_data,onedate))
        man_data=man_data.reshape(20,3)
        print(man_data)
        #生成20个女生身高体重数据
        ht_woman = np.random.randint(155, 170, 20)
        woman_data = []
        for one in ht_woman:
            onedate = np.array((one,one-110+random.randint(-5,5),"女"))
            woman_data = np.concatenate((woman_data,onedate))
        woman_data=woman_data.reshape(20,3)
        print(woman_data)

我们看一下我们生成的数据及其分布情况

[['175' '65' '男']
 ['189' '83' '男']
 ['168' '72' '男']
 ['189' '85' '男']
 ['173' '73' '男']
 ['165' '59' '男']
 ['186' '76' '男']
 ['184' '89' '男']
 ['175' '73' '男']
 ['176' '76' '男']
 ['174' '71' '男']
 ['175' '78' '男']
 ['186' '81' '男']
 ['188' '83' '男']
 ['171' '64' '男']
 ['165' '63' '男']
 ['185' '84' '男']
 ['177' '71' '男']
 ['172' '67' '男']
 ['179' '78' '男']]
 
[['165' '59' '女']
 ['163' '51' '女']
 ['164' '51' '女']
 ['158' '47' '女']
 ['163' '53' '女']
 ['163' '52' '女']
 ['155' '49' '女']
 ['160' '52' '女']
 ['168' '53' '女']
 ['158' '47' '女']
 ['165' '59' '女']
 ['166' '55' '女']
 ['164' '49' '女']
 ['164' '59' '女']
 ['165' '52' '女']
 ['160' '45' '女']
 ['162' '53' '女']
 ['161' '49' '女']
 ['155' '43' '女']
 ['159' '44' '女']]

分布情况

    # 绘制分类点
    def drawScatterbyLabel(self,Input):
        m, n = np.shape(Input)
        target = Input[:, -1]
        plt.xlim((150,200))
        plt.ylim((40, 100))

        # 设置坐标轴名称
        plt.xlabel('high')
        plt.ylabel('weight')
        for i in range(m):
            if target[i] == '男':
                plt.scatter(int(Input[i, 0]), int(Input[i, 1]), c='blue', marker='o',label="man")
            else:
                plt.scatter(int(Input[i, 0]), int(Input[i, 1]), c='red', marker='s',label="woman")
        plt.scatter(176, 55, c='green', marker='^')

        plt.show()
在这里插入图片描述

从图上来看这个输入数据的应该是男生的身高体重,下面我们就用knn来预测一下

计算距离

计算测试数据与各个训练数据之间的距离,然后按照递增的顺序进行排序
距离的计算公式有:欧氏距离,曼哈顿距离,夹角余弦距离,切比雪夫距离,汉明距离,闵可夫斯基距离,马氏距离等,这里我使用欧式距离
我们先假设在n维空间有两个点A(x1,x2...xn),B(y1,y2...yn)

欧氏距离 (EuclideanDistance)

欧式距离:也称欧几里得距离,在一个N维度的空间里,求这两个点的距离,这个距离肯定是一个大于等于零的数字,那么这个距离需要用两个点在各自维度上的坐标相减,平方后加和再开方。
d=\sqrt{\displaystyle\sum_{i=1}^n (x_i-y_i)^2}

    def EuclideanDistance(self,x,y):
        #distance= np.sqrt(np.sum(np.square(x-y)))
        dist = np.linalg.norm(x-y,axis=1)
        return dist

这里我是调用numpy里面的norm范数函数的计算方法,默认就是L2范数,我们来看一下输入数据与我们的数据的距离

    inputdata = ((176,55)-mins)/(maxs-mins)
    dist = EuclideanDistance(inputdata,standdata)
    print(dist)
#output:
[ 0.25700763  0.69007779  0.43149942  0.87517496  0.4972528   0.3759514
  0.55281901  0.66036246  0.49024476  0.55319149  0.2003207   0.40532384
  0.76124138  0.88218871  0.24144248  0.35616466  0.55636686  0.55397281
  0.18979677  0.62329826  0.32422827  0.38471359  0.36305726  0.53999459
  0.38294447  0.38471359  0.64664988  0.50042549  0.23911105  0.53324578
  0.32422827  0.30618342  0.36305726  0.35549718  0.34057102  0.47106898
  0.41668263  0.45382157  0.64664988  0.50045249]

接下来我们就需要对这些距离进行排序,然后设定k值确定范围

设定参数k

这个参数k就是我们对输入数据的预定的邻居范围,通俗的讲就是我只允许你在我画的这个圈子里找与你最近的,然后加入他们。

如何取k值

常用的方法是从k=1开始,使用检验集估计分类器的误差率。重复该过程,每次K增值1,允许增加一个近邻。选取产生最小误差率的K。
一般k的取值不超过20,上限是n的开方,随着数据集的增大,K的值也要增大。
另外K的取值最好是奇数,这样可以一定程度上避免出现不同类别的个数相等的情况不利于预测

在前k的范围之内进行统计

选定了K值后,我们还要统计这个范围之内的数据类别,然后找出类别数量最多的数据标签,就是给输入数据打的标签,我们先看一下代码怎么实现的

    def classify(self,inputdata,data,labels):
        dist = self.EuclideanDistance(inputdata,data)
        #对距离进行排序,
        distindex = dist.argsort()
        #统计K最近邻得label
        labelcount=dict()
        for i in range(self.k):
            label = labels[distindex[i]]
            labelcount.setdefault(label,0)
            labelcount[label]+=1
        sortlabelcount=sorted(labelcount.items(),key=lambda  x:x[1],reverse=True)
        print(sortlabelcount)
        return sortlabelcount[0][0]
res =classify(inputdata,standdata,data[:,-1])
    print(res)
#output
[('男', 4), ('女', 3)]
男

前面的分布图我们也可以看出来输入数据是更偏向于男生的数据

总结

KNN算法没有很难理解的数学理论,实现也比较容易,而且不需要学习训练,是最简单有效的分类算法,但是KNN的缺点却很明显:
KNN算法因为要对每个数据都进行计算距离,因此他的复杂度取决于训练数据集的数量,当训练数据集很大时,不但需要很大存储空间还比较耗时
KNN对于随机分布的数据集分类效果较差,对于类内间距小,类间间距大的数据集分类效果好,而且对于边界不规则的数据效果好于线性分类器。
KNN对于样本不均衡的数据效果不好,比如说样本数据本来和输入数据类似的就少,然后这时候统计前k个数据集类别时,就会出现最接近输入数据的类别比较少,这就需要改进,比如说常用的按距离排序后再加上权重,距离最小的权重值给大一点,这样可以一定程度上有效的减少数据样本不均衡的影响

完整代码

import numpy as np
import random
import matplotlib.pyplot as plt
class KNN:
    def __init__(self,k=None):
        self.k=k
    #利用随机函数分别生成20个男生和女生的身高,体重
    def createdata(self):
        #随机种子
        np.random.seed(3)
        #生成20个男生身高体重数据
        ht_man = np.random.randint(165,190,20)
        man_data = []
        for one in ht_man:
            onedate = np.array((one,one-105+random.randint(-5,10),"男"))
            man_data = np.concatenate((man_data,onedate))
        man_data=man_data.reshape(20,3)
        #生成20个女生身高体重数据
        np.random.seed(3)
        ht_woman = np.random.randint(155, 180, 20)
        woman_data = []
        for one in ht_woman:
            onedate = np.array((one,one-110+random.randint(-5,5),"女"))
            woman_data = np.concatenate((woman_data,onedate))
        woman_data=woman_data.reshape(20,3)
        #将男生和女生的数据合并到一个数组里
        data = np.concatenate((man_data,woman_data))
        return data

    def normalization(self,data):
        #将数据转换为数组
        data = np.array(data[:, :-1], dtype=np.int32)
        maxs = np.max(data, axis=0)
        mins = np.min(data, axis=0)
        newdata = (data - mins) / (maxs - mins)
        return maxs,mins,newdata

    def EuclideanDistance(self,x,y):
        #distance= np.sqrt(np.sum(np.square(x-y)))
        dist = np.linalg.norm(x-y,axis=1)
        return dist

    def classify(self,inputdata,data,labels):
        dist = self.EuclideanDistance(inputdata,data)
        distindex = dist.argsort()
        #统计K最近邻得label
        labelcount=dict()
        for i in range(self.k):
            label = labels[distindex[i]]
            labelcount.setdefault(label,0)
            labelcount[label]+=1
        sortlabelcount=sorted(labelcount.items(),key=lambda  x:x[1],reverse=True)
        print(sortlabelcount)
        return sortlabelcount[0][0]

    # 绘制分类点
    def drawScatterbyLabel(self,Input):
        m, n = np.shape(Input)
        target = Input[:, -1]
        plt.xlim((150,200))
        plt.ylim((40, 100))

        # 设置坐标轴名称
        plt.xlabel('high')
        plt.ylabel('weight')
        for i in range(m):
            if target[i] == '男':
                plt.scatter(int(Input[i, 0]), int(Input[i, 1]), c='blue', marker='o',label="man")
            else:
                plt.scatter(int(Input[i, 0]), int(Input[i, 1]), c='red', marker='s',label="woman")
        plt.scatter(176, 55, c='green', marker='^')

        plt.show()

if __name__=="__main__":
    knn=KNN(7)
    data = knn.createdata()
    print(data)
    #knn.drawScatterbyLabel(data)
    maxs,mins,standdata = knn.normalization(data)
    print(maxs,mins)
    #print(standdata)
    inputdata = np.array([170,55])

    inputdata = (inputdata-mins)/(maxs-mins)
    dist = knn.EuclideanDistance(inputdata,standdata)
    print(dist)
    res = knn.classify(inputdata,standdata,data[:,-1])
    print(res)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容