KNN算法分类和回归

KNN(K-Nearest Neighbor)是最简单的机器学习算法之一,可以用于分类和回归,是一种监督学习算法。它的思路是这样,如果一个样本在特征空间中的K个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。也就是说,该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。

K-近邻算法是一种惰性学习模型(lazy learning),也称为基于实例学习模型,这与勤奋学习模型(eager learning)不一样。例如线性回归模型就是属于勤奋学习模型。
勤奋学习模型在训练模型的时候会很耗资源,它会根据训练数据生成一个模型,在预测阶段直接带入数据就可以生成预测的数据,所以在预测阶段几乎不消耗资源
惰性学习模型在训练模型的时候不会估计由模型生成的参数,他可以即刻预测,但是会消耗较多资源,例如KNN模型,要预测一个实例,需要求出与所有实例之间的距离。
K-近邻算法是一种非参数模型,参数模型使用固定的数量的参数或者系数去定义模型,非参数模型并不意味着不需要参数,而是参数的数量不确定,它可能会随着训练实例数量的增加而增加,当数据量大的时候,看不出解释变量和响应变量之间的关系的时候,使用非参数模型就会有很大的优势,而如果数据量少,可以观察到两者之间的关系的,使用相应的模型就会有很大的优势。

KNN算法三要素:
KNN算法我们主要要考虑三个重要的要素,对于固定的训练集,只要这三点确定了,算法的预测方式也就决定了。这三个最终的要素是k值的选取,距离度量的方式和分类决策规则。

分类决策规则,在分类模型中,主要使用多数表决法或者加权多数表决法;在回归模型中,主要使用平均值法或者加权平均值法。
 

KNN分类算法的计算过程:

1)计算待分类点与已知类别的点之间的距离

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

3)选取与待分类点距离最小的K个点

4)确定前K个点所在类别的出现次数

5)返回前K个点出现次数最高的类别作为待分类点的预测分类

KNN回归算法

要预测的点的值通过求与它距离最近的K个点的值的平均值得到,这里的“距离最近”可以是欧氏距离,也可以是其他距离,具体的效果依数据而定,思路一样。

K值的选择

作为KNN算法中唯一的一位超参数,K值的选择对最终算法的预测结果会产生直观重要的影响

如果选择较小的K值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差会减小,只有输入实例较近的训练实例才会对预测结果起作用。但缺点是“学习”的估计误差会增大,预测结果会对近邻实例点非常敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说,K值得减小就意味着整体模型非常复杂,容易发生过拟合。

如果选择较大的K值,就相当于用较大邻域中的训练实例进行预测,其实优点是减少学习的估计误差,但缺点是学习的近似误差会增大。这时与输入实例较远的训练实例也会起预测作用,使预测发生错误,k值的增大就意味着整体的模型变得简单,容易发生欠拟合。可以假定极端条件K=N,那么无论输入实例是什么,都将简单的预测它属于训练实例中最多的类。这时,模型过于简单,完全忽略训练中的大量有用信息,是不可取的。

在应用中,通常采用交叉验证法来选择最优K值。从上面的分析也可以知道,一般K值取得比较小。我们会选取K值在较小的范围,同时在验证集上准确率最高的那一个确定为最终的算法超参数K。

距离度量选择

样本空间中两个点之间的距离度量表示的是两个样本点之间的相似程度:距离越小,表示相似程度越高;相反,距离越大,相似程度越低。

在KNN算法中,常用的距离有三种,分别为曼哈顿距离、欧式距离和闵可夫斯基距离。为了方便下面的解释和举例,先设定我们要比较X个体和Y个体间的差异,它们都包含了N个维的特征。它们的数学描述如下

1. 闵可夫斯基距离(Minkowski distance)

闵可夫斯基距离不是一种距离,而是一类距离的定义。其数学定义如下:

其中p可以随意取值,可以是负数,也可以是正数,或是无穷大。当p=1时,又叫做曼哈顿距离,当p=2时,又叫做欧式距离,当p=∞时,又叫做契比雪夫距离。

2.欧式距离

欧式距离(L2范数)其实就是空间中两点间的距离,是我们最常用的一种距离计算公式。因为计算是基于各维度特征的绝对数值,所以欧氏度量需要保证各维度指标在相同的刻度级别,比如对身高(cm)和体重(kg)两个单位不同的指标使用欧式距离可能使结果失效。所以在使用欧氏距离时,应该尽量将特征向量的每个分量归一化,以减少因为特征值的刻度级别不同所带来的干扰。

3.曼哈顿距离

想象在曼哈顿要从一个十字路口开车到另外一个十字路口,驾驶距离是两点间的直线距离吗?显然不是,除非你能穿越大楼。实际驾驶距离就是这个“曼哈顿距离”。而这也是曼哈顿距离名称的来源,曼哈顿距离也称为城市街区距离(City Blockdistance)。

 KNN的优缺点是什么?

优点:

1)算法简单,理论成熟,既可以用来做分类也可以用来做回归。

2)可用于非线性分类。

3)没有明显的训练过程,而是在程序开始运行时,把数据集加载到内存后,不需要进行训练,直接进行预测,所以训练时间复杂度为0。

4)由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属的类别,因此对于类域的交叉或重叠较多的待分类样本集来说,KNN方法较其他方法更为适合。

5)该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量比较小的类域采用这种算法比较容易产生误分类情况。

缺点:

1)需要算每个测试点与训练集的距离,当训练集较大时,计算量相当大,时间复杂度高,特别是特征数量比较大的时候。

2)需要大量的内存,空间复杂度高。

3)样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少),对稀有类别的预测准确度低。

4)是lazy learning方法,基本上不学习,导致预测时速度比起逻辑回归之类的算法慢。

注意,为了克服降低样本不平衡对预测准确度的影响,我们可以对类别进行加权,例如对样本数量多的类别用较小的权重,而对样本数量少的类别,我们使用较大的权重。 另外,作为KNN算法唯一的一个超参数K,它的设定也会算法产生重要影响。因此,为了降低K值设定的影响,可以对距离加权。为每个点的距离增加一个权重,使得距离近的点可以得到更大的权重。

Jupyter测试KNN分类算法

import numpy as np
import pandas as pd

#读取训练数据集,header指定标题,默认为0,如果没有标题 None
data=pd.read_csv(r'iris.csv',header=0)
#data.head()
#data.tail()
#data.sample()
data.sample(10)

#将类别文本映射成数据类型
data["Species"]=data["Species"].map({"virginica":0,"setosa":1,"versicolor":2})
data

#删除不需要的ID列, inplace=True 在原有的数据上删除数据
data.drop("Id",axis=1,inplace=True)

data.duplicated().any()#返回true,表示数据集中有重复的数据

data.drop_duplicates(inplace=True) #inplace=True 在原有的数据上删除数据
len(data)

data["Species"].value_counts()

#KNN分类算法
class KNN:
    """使用Python语言实现K个邻近算法。(分类算法)"""
    def __init__(self,k):
        """构造方法,k:邻居的个数"""
        self.k=k
    def fit(self,X,y):
        """训练方法,
        X:类数组类型,形状:【样本数量,特征数量】
                待训练的样本特征(属性)
        y:类数组类型,形状:【样本数量】
                每个样本目标值(标签)
        """
        self.X=np.asarray(X)
        self.y=np.asarray(y)
    
    def predict(self,X):
        """根据参数传递的样本,对样本数据进行预测
                X:类数组类型,形状:【样本数量,特征数量】
                    待预测的样本特征(属性)
            return:数组类型
                预测结果
        """
        X=np.asarray(X)#将待预测的样本类型转换为ndArray类型
        result=[]
        #遍历
        for x in X:
            #对于测试集(30)中的每一个测试样本,依次与训练集(120)中的所有样本计算距离
            dis=np.sqrt(np.sum((x-self.X)**2,axis=1))
            #对dis按照下标进行排序,返回dis对应的下标
            index=dis.argsort()
            #截断,只取前k个元素【取距离最近的k个元素索引】
            index=index[:self.k]
            #统计 self.y中对应位置的类型(0,1,2)分别出现的次数
            count=np.bincount(self.y[index])
            #返回count数组中,值最大元素所对应的索引,就是出现最多次数的元素
            result.append(count.argmax())
        
        return np.array(result)
    
    def predict2(self,X):  #加权
        """根据参数传递的样本,对样本数据进行预测
                X:类数组类型,形状:【样本数量,特征数量】
                    待预测的样本特征(属性)
            return:数组类型
                预测结果
        """
        X=np.asarray(X)#将待预测的样本类型转换为ndArray类型
        result=[]
        #遍历
        for x in X:
            #对于测试集(30)中的每一个测试样本,依次与训练集(120)中的所有样本计算距离
            dis=np.sqrt(np.sum((x-self.X)**2,axis=1))
            #对dis按照下标进行排序,返回dis对应的下标
            index=dis.argsort()
            #截断,只取前k个元素【取距离最近的k个元素索引】
            index=index[:self.k]
            #统计 self.y中对应位置的类型(0,1,2)分别出现加权重的次数 【权重:距离的倒数】
            count=np.bincount(self.y[index],weights=1/dis[index])
            #返回count数组中,值最大元素所对应的索引,就是出现最多次数的元素
            result.append(count.argmax())
        
        return np.array(result)

#KNN分类算法完成后,开始提取数据
# versicolor  virginica  setosa
t0=data[data["Species"]==0]
t1=data[data["Species"]==1]
t2=data[data["Species"]==2]

len(t0)
len(t1)
len(t2)

t0=t0.sample(len(t0),random_state=0)
t1=t1.sample(len(t1),random_state=1)
t2=t2.sample(len(t2),random_state=2)

#训练数据集
train_X=pd.concat( [t0.iloc[:40,:-1], t1.iloc[:40,:-1], t2.iloc[:40,:-1]],axis=0)
train_y=pd.concat( [t0.iloc[:40,-1], t1.iloc[:40,-1], t2.iloc[:40,-1]],axis=0)
#测试数据集
test_X=pd.concat( [t0.iloc[40:,:-1], t1.iloc[40:,:-1], t2.iloc[40:,:-1]],axis=0)
test_y=pd.concat( [t0.iloc[40:,-1], t1.iloc[40:,-1], t2.iloc[40:,-1]],axis=0)

#创建KNN对象,进行训练和测试
knn=KNN(k=5)
#训练数据集
knn.fit(train_X,train_y)
#测试数据集
result=knn.predict(test_X)
# result=knn.predict2(test_X)

test_y
result

display(np.sum(result==test_y))
display(np.sum(result==test_y)/len(test_y))

#绘图
import matplotlib as mpl
import matplotlib.pyplot as plt

#设置画布大小
plt.figure(figsize=(10,10))
#绘制数据集
plt.scatter(x=t0["SepalLengthCm"][:40],y=t0["PetalLengthCm"][:40],color="r",label="virginica")
plt.scatter(x=t1["SepalLengthCm"][:40],y=t1["PetalLengthCm"][:40],color="g",label="setosa")
plt.scatter(x=t2["SepalLengthCm"][:40],y=t2["PetalLengthCm"][:40],color="b",label="versicolor")
#绘制测试数据集

right=test_X[result==test_y]
wrong=test_X[result!=test_y]
plt.scatter(x=right["SepalLengthCm"],y=right["PetalLengthCm"],color="c",marker="x",label="right")
plt.scatter(x=wrong["SepalLengthCm"],y=wrong["PetalLengthCm"],color="m",marker="X",label="right")


Jupyter测试KNN回归算法

import numpy as np
import pandas as pd

data=pd.read_csv(r'iris.csv')
data.drop(["Id","Species"],axis=1,inplace=True)
data

data.drop_duplicates(inplace=True)
len(data)

#KNN回归算法
class KNN:
    """使用python实现K邻近算法回归预测
        该算法用于回归预测,根据前三个特征属性,找出最近的K个邻居,然后根据K个邻居第四个
        特征属性来预测当前样本的第四个特征值
    
    """
    def __init__(self,k):
        """初始化方法
                K:邻近数量
        """
        self.k=k
    def fit(self,X,y):
        """训练方法
            X:类数组类型(特征矩阵),形状为【样本数量,特征数量】
                待训练数据集
            y:类数组类型(目标标签),形状为【样本数量】
                每个样本的目标值(标签)
        """
        self.X=np.asarray(X)
        self.y=np.asarray(y)
    def predict(self,X):
        """根据参数传递的X,对样本数据进行预测
        X:类数组类型(特征矩阵),形状为【样本数量,特征数量】
                待测试的样本特征(属性)
        return:数组类型
            预测的结果值
        """
        #转换成数组类型
        X=np.asarray(X)
        #保存预测结果值数组
        result=[]
        for x in X:
            #计算距离,(计算测试数据集中的每一个样本与训练数据集中的每一个样本的距离)
            dis=np.sqrt(np.sum((x-self.X)**2,axis=1))
            index=dis.argsort()
            index=index[:self.k]
            result.append(np.mean(self.y[index]))#将预测的结果值存入到result数组中
            
        return np.array(result)

t=data.sample(len(data),random_state=1)

#训练集
train_X=t.iloc[:120,:-1]
train_y=t.iloc[:120,-1]

#测试集
test_X=t.iloc[120:,:-1]
test_y=t.iloc[120:,-1]

knn=KNN(k=3)
knn.fit(train_X,train_y)
result=knn.predict(test_X)
display(result)
display(test_y.values)
display(np.mean((result-test_y)**2))

#绘图
import matplotlib as mpl
import matplotlib.pyplot as plt


plt.figure(figsize=(10,10))
plt.plot(result,"ro-",label="yuce")
plt.plot(test_y.values,"go--",label="really")
plt.legend()
plt.show()

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值