python实现knn房价预测_纯Python实现kNN机器学习算法

最近在写文档,突然想自己实现一下k近邻算法,随手编写的,可能不太严谨,仅供大家参考。

k近邻(k-Nearest Neighbor)算法,简称kNN,可能是最简单也最好理解的监督学习方法了。

其工作机制为:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个‘近邻’的信息来进行测试样本的预测。

这是一种很朴素的分类思维,事物属于哪一类,只要看它在种群集合中所处的位置。和它在位置上靠得越近的族类,必定是和它更亲近的同类。

从kNN的工作机制可以分析出以下几个要点:

某种距离度量方法:一般是欧氏距离。当然也可以使用其它的。

最靠近的:只看邻居,不看远亲。

k个:显而易见的超参数。也就是考察的邻居数量。

基于邻居的信息进行预测,有不同的预测方法。分类和回归又有不同。

比如,在分类任务中可以使用‘投票法’,选择邻居最多的那一类;在回归任务中使用‘平均法’,将k个邻居的标记值取平均值作为预测结果;还可以基于距离远近进行加权投票或加权平均,距离越近的邻居权重越大。

k是k近邻算法最重要的参数,不同的k值,可能导致完全不同的分类结果。当k为1的时候,被称为‘最近邻分类器’。

与其它的机器学习方法相比,k近邻没有显式的训练过程,它是‘懒惰学习’(lazy learning)的著名代表,此类学习方法在训练阶段仅仅是把样本保存起来,训练时间开销为0,待有预测任务时,再用测试样本和训练样本去逐一比较。相应的,那些在训练阶段就对训练样本进行学习处理的方法称为‘积极学习’(eager learning),线性回归、决策树、支持向量机等大多数机器学习算法都是‘积极学习’类型。

积极学习

懒惰学习

另外,根据数学推导,最近邻分类器虽然简单,但它的泛化错误率不超过贝叶斯最优分类器的错误率的两倍。

在Python的Scikit-learn库中有专门的kNN算法实现。但是,这里先给出一个用纯python实现的k近邻算法,用于展示其原理和机制。该方法只实现了二维下的kNN对单个点的分类预测,可拓展到多维、多个对象的预测分类任务。该方法未过多考虑算法效率,内存开销、参数合法性检验等其它问题。

import random

import bisect

"""

该方法使用纯Python环境,展示kNN分类算法的基本原理。

该方法实现了二维的kNN对单个点的分类预测。可拓展到多维、多个对象的预测分类任务。

该方法未过多考虑算法效率,内存开销、参数合法性检验等。

"""

def cal_distance(a, b):

"""

只计算平方和,省去开根号的计算开销。

:param a:

:param b:

:return: 欧氏距离的平方

"""

return (a[0]-b[0])**2 + (a[1]-b[1])**2

def kNNClassifier(train_set, k, i):

"""

构造三个包含k个元素的列表,分别用来保存最小距离、最小距离点、点对应的分类标记。

当数据集较大的时候,这个方法可能会快一点。数据集较小的时候,建议直接排序。

:param train_set: 训练集

:param k: 判定的邻居数量

:param i: 预测点

:return: 预测分类

"""

# 使用训练集的第一个元素初始化三个列表

distances = [cal_distance(train_set[0][0], i)]*k # 从小到大排列的k个元素

points = [train_set[0][0]]*k # 与distances对应的点的列表

labels = [train_set[0][13]]*k # 与points对应的点的分类

# 遍历训练集中的每个点

for item in train_set:

dis = cal_distance(item[0], i) # 计算它与点i的距离

if dis < distances[-1]: # 如果这个距离比当前distances列表中最后一个元素的值小

index = bisect.bisect(distances, dis) # 使用Python内置的bisect二分法,查找插入的位置

distances.insert(index, dis) # 执行插入操作

distances.pop() # 弹出最后一个元素,保证distances中始终只有k个元素

points.insert(index, item[0]) # 与上面的操作类似

points.pop()

labels.insert(index, item[1]) # 与上面的操作类似

labels.pop()

print('最小的距离值列表: ', distances)

print('对应的点的列表: ', points)

print('点的分类标记列表: ', labels)

# 统计每类标记的数量

count_dic = {labels.count(key): key for key in set(labels)}

# 返回数量最多的标记以及对应的数量

return count_dic[max(count_dic)], max(count_dic)

if __name__ == '__main__':

# random.seed(42) # 如果不执行这行代码,每次生成的数据集都是随机的,结果不可重现。如果定义相同的seed,则每次执行的结果可重现。

# 随机获取1万个,1-10000的整数点(x, y)

random_points = [(random.randint(1, 10000), random.randint(1, 10000)) for _ in range(10000)]

# print(points)

# print(len(points))

# 去掉可能产生的重复点,这可能导致实际训练集中点的数量小于10000.

points = set(random_points)

# print(points)

# print(len(points))

# 随机为每个点标记分类

train_data = {}

for item in points:

train_data[item] = random.choice(['A', 'B']) # 可以自己指定分类标记和数量

# list(train_data.items())类似

# [((4879, 4728), 'B'), ((7417, 3998), 'A'), ....,((3312, 4037), 'B'), ((1944, 7900), 'A')]

predict, num = kNNClassifier(list(train_data.items()), 10, (23, 48))

print('预测分类为:', predict, ' 有{}个最近邻属于该分类'.format(num))

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值