机器学习-k近邻算法(kNN算法)及简单实现

一、k-近邻算法

1、简述k-近邻算法

k-近邻(k-Nearest Neighbor,简称kNN)是一种常用的监督学习方法,其工作机制非常简单:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个“邻居”的信息来进行预测,选择这k个样本中出现最多的类别标记作为预测结果。当k取不同值时,分类结果会有显著不同。另一方面,若采用不同的距离计算方式,则找出的“近邻”可能有显著差别,从而也会导致分类结果有显著不同。

如果概念太过于抽象,我们可以举个关于集美大学的例子来理解k-近邻算法:

集大建筑 午餐时间人数 工作时间人数 建筑类型
万人 1000 50 餐厅
陆大 30 800 教学楼
西苑 600 30 餐厅
建发 20 2000 教学楼

ps:这里容我简单介绍一下,万人食堂西苑食堂顾名思义是集美大学本部两个主要的食堂,陆大楼是计算机工程学院的楼,与建发楼一样,是学生上课的地方,不同的是陆大五楼是学院领导、辅导员的基地,下面还有各种实验室,而建发楼就可以说是完完全全的教学楼了,为了方便,我们将陆大楼也看作是教学楼。

我们可以使用k-近邻算法分类集美大学的某一个建筑是餐厅还是教学楼。

具体怎么分类呢?

上表是我们已有的数据集,也就是训练样本。这个数据集有两个特征,即午餐时间人数工作时间人数,我们也知道每个建筑的所属类型,即分类标签。

人是铁饭是钢,一顿不吃饿的慌,作为一名集大的学生,用肉眼可以很明确地观察到,饭点大家都跑去万人、西苑(当然也有某些卷王不干饭...),上课时间大家都跑去陆大、建发(当然也有某些卷王没课跑去食堂开卷...可能图书馆没位置了...怎么这么多卷王...)。

如果现在给我一个建筑,告诉我午餐时间人数工作时间人数,我可以根据这些判断这个建筑属于食堂还是教学楼,午餐时间人数多而工作时间人数少的是食堂,反之则是教学楼。k-近邻算法也可以像我们人一样做到这一点。

ps:这里会一个问题,假如我拿一个新的建筑来做测试,午餐时间人数工作时间人数都是1000,这个时候我知道这个建筑可能是陈延奎图书馆也可能是嘉庚图书馆,其类型属于图书馆,而k-近邻算法不会,因为在它眼里,建筑类型只有食堂和教学楼,它会提取样本集中特征最相似数据(最邻近)的分类标签,得到的结果可能是食堂,也可能是教学楼,但绝不会是图书馆。当然,这些取决于数据集的大小以及最近邻的判断标准等因素。

 2、k值的选择

一般而言,从k = 1开始,随着k的逐渐增大,k近邻算法的分类效果会逐渐提升;在增大到某个值后,随着的进一步增大,k近邻算法的分类效果会逐渐下降。

k值较小,相当于用较小的邻域中的训练实例进行预测,只有距离近的(相似的)起作用

  • 单个样本影响大
  • “学习”的近似误差(approximation error)会减小,但估计误差(estimation error)会增大
  • 噪声敏感
  • 整体模型变得复杂,容易发生过拟合

k值较大,这时距离远的(不相似的)也会起作用

  • 近似误差会增大,但估计误差会减小
  • 整体的模型变得简单

k值选择

  • 一般k值较小
  • k通常取奇数,避免产生相等占比的情况
  • 往往需要通过交叉验证(Cross Validation)等方法评估模型在不同取值下的性能,进而确定具体问题的K值

3、距离度量

下面给出两种常见的距离度量方式

3-1、欧氏距离

欧式距离也称欧几里得距离,是最常见的距离度量,衡量的是多维空间中两个点之间的绝对距离。

n维空间点间的欧式距离(两个n维向量):

d_{12}=\sqrt{\sum_{k=1}^{n}(x_{1k}-x_{2k})^{2}}

3.2、曼哈顿距离

在曼哈顿街区要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”。曼哈顿距离也称为“城市街区距离”(City Block distance)。

机器学习-k近邻算法(kNN算法)及简单实现_第1张图片

n维空间点间的曼哈顿距离(两个n维向量):

d_{12}=\sum_{k=1}^{n}\left | x_{1k}-x_{2k} \right |

二、简单测验机器学习-k近邻算法(kNN算法)及简单实现_第2张图片

如何判断黄点标记的禹洲楼所属的类别呢?

可以从散点图大致推断,这个黄点标记的建筑可能属于教学楼,因为距离已知的两个蓝点更近。k-近邻算法同样是这个原理,专业点的话叫距离度量。这个例子是简单的二维样例,我们可以采用欧氏距离公式计算距离。

通过计算,我们可以得到如下结果:

  • 禹洲楼(980,150)->教学楼(1000,100)的距离约为53.85
  • 禹洲楼(980,150)->教学楼(850,20)的距离约为183.85
  • 禹洲楼(980,150)->食堂(30,800)的距离约为1151.09
  • 禹洲楼(980,150)->食堂(50,1000)的距离约为1259.92

现在k值取3,那么按距离依次排序的三个点分别是教学楼(1000,100)、教学楼(850,20)、食堂(30,800)、食堂(50,1000)。在这三个点中,教学楼出现的频率为2/3,食堂出现的频率为1/3,所以该黄点标记的建筑为教学楼。这个判别过程就是k-近邻算法。

三、Python实现

我们对上面的例子进行Python的实现

1、准备数据集

创建数据集并打印查看创建结果

# -*- coding = utf-8 -*-
# @Time : 2022/10/26 14:20
# @Author : Nch
# @File : kNN.py
# @Software : PyCharm
import numpy as np
def createDataSet():
    #四组二维特征
    features = np.array([[1000,100],[850,20],[30,800],[50,1000]])
    #四组特征的标签
    labels = ['教学楼','教学楼','食堂','食堂']
    return features, labels
#创建数据集
features,labels = createDataSet()
#打印数据集
print(features,'\n',labels)

运行结果如下,创建数据集成功

机器学习-k近邻算法(kNN算法)及简单实现_第3张图片

2、k-近邻算法

根据欧氏公式计算距离,选择距离最小的前k个点,并返回分类结果

def classify0(inX, dataSet, labels, k):
    # numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 二维特征相减后平方
    sqDiffMat = diffMat ** 2
    # sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 开方,计算出距离
    distances = sqDistances ** 0.5
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    # 定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素的类别
        voteIlabel = labels[sortedDistIndices[i]]
        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        # 计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # python3中用items()替换python2中的iteritems()
    # key=operator.itemgetter(1)根据字典的值进行排序
    # key=operator.itemgetter(0)根据字典的键进行排序
    # reverse降序排序字典
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回次数最多的类别,即所要分类的类别
    return sortedClassCount[0][0]

3、预测禹洲楼(980,150)

# -*- coding = utf-8 -*-
# @Time : 2022/10/26 14:20
# @Author : Nch
# @File : kNN.py
# @Software : PyCharm
import numpy as np
import operator
def createDataSet():
    #四组二维特征
    features = np.array([[1000,100],[850,20],[30,800],[50,1000]])
    #四组特征的标签
    labels = ['教学楼','教学楼','食堂','食堂']
    return features, labels
#创建数据集
# features,labels = createDataSet()
#打印数据集
# print(features,'\n',labels)
def classify0(inX, dataSet, labels, k):
    # numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 二维特征相减后平方
    sqDiffMat = diffMat ** 2
    # sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 开方,计算出距离
    distances = sqDistances ** 0.5
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    # 定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素的类别
        voteIlabel = labels[sortedDistIndices[i]]
        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        # 计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # python3中用items()替换python2中的iteritems()
    # key=operator.itemgetter(1)根据字典的值进行排序
    # key=operator.itemgetter(0)根据字典的键进行排序
    # reverse降序排序字典
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回次数最多的类别,即所要分类的类别
    return sortedClassCount[0][0]

features,labels = createDataSet()
test = [980,150]
test_class = classify0(test,features,labels,3)
print(test_class)

运行结果如下,根据我们给出的测试集和算法,可以判断禹洲楼是教学楼。

希望以上内容对读者有帮助,大家一起进步! 对集美大学感兴趣的也欢迎来咨询我!

你可能感兴趣的:(机器学习,近邻算法,人工智能)