机器学习入门(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 = i = 1 n ( x i y i ) 2 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)
发布了21 篇原创文章 · 获赞 28 · 访问量 3713

猜你喜欢

转载自blog.csdn.net/LPJCSY/article/details/103767046