一篇文彻底理解KNN算法 - 我点了一包华子,终于明白了海伦是个好女孩

大家好,我是W

这次我们要手撕KNN,同时自己实现KNN。当然KNN的思想很简单,所以重点会放在实现自己的KNN上。K-近邻(KNN,K-NearestNeighbor)算法是一种基本分类与回归算法,这一篇文中我们用来实现分类。接下来的内容顺序是:KNN算法的原理、案例1-海伦约会、案例2-手写数字识别。

KNN算法的原理

假设在你面前摆设有一堆瓜子,你只能通过观察其外表来分别瓜子的好坏,那么你能够获得的观察维度只能包括两种特征:瓜子壳的破损情况、瓜子的香味。因为瓜子最初并没有标签,所以你并不知道瓜子好坏的标准,所以你被允许记录并试吃一百颗瓜子。所以你在试吃的过程中得到了100颗瓜子的信息,接下来你需要根据学习到的信息来对剩余的瓜子进行分类,所以你选择的策略是**“在坐标轴中标出试吃过的瓜子的坐标,并且对每一颗需要判断的瓜子也标注在坐标轴中,最终通过与待判断瓜子最近的K个已知瓜子的好坏程度来判断其好坏”**。

所以,KNN的核心就是一句话“近朱者赤”。

KNN的开发流程

步骤 方法
收集数据 任何方法
准备数据 得到计算所需的格式
分析数据 任何方法
测试算法 计算准确率等指标
使用算法 输入样本数据和结构化的输出结果,运行knn判断类别

KNN的特点

优点 缺点
精度高 计算复杂度高
对异常值不敏感、无数据输入假定 空间复杂度高

适用数据范围: 数值型和标称型

案例1 - 海伦约会

案例背景

海伦在相亲网站找约会对象,经过一段时间之后,她发现曾交往过三种类型的人:

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人

并且她希望:

  • 工作日跟魅力一般的人约会
  • 周末与极具魅力的人约会
  • 排除不喜欢的人

现在,她收集到了一些约会网站未曾记录的数据信息,这更有助于匹配对象的归类。

开发流程

  • 观察数据集
  • 解析数据集
  • 分析数据集
  • 测试算法

1’ 观察数据集

点击这里下载数据集,海伦的数据集中共有三个特征,分别是:

  • 每年飞行公里数
  • 玩videoGame的时间占比
  • 每周消耗雪糕公升数

可以看到数据集并没有表头,但是还是可以清楚分辨出对应数据,并且第四列很明显表示的是海伦对该样本的喜爱程度。数据集共有1000个样本,不是很大。

2’ 解析数据集

这一步我们使用pandas库来对数据进行解析。

import pandas as pddef read_data(file_path):
"""
读取数据集
:param file_path: 文件路径
:return: dating_data
"""
dating_data = pd.read_csv(file_path, delimiter="\t", names=["飞行公里数", "游戏时间比例", "雪糕消耗", "喜爱程度"])
data = dating_data.iloc[:, :3]
target = dating_data.iloc[:, 3]
return data, target

3’ 分析数据集

在分析数据集这一步,我们使用matplotlib的pyplot从几个角度分别画出其二维散点图。

import matplotlib.pyplot as pltdef draw_raw_data_01(data):"""画出海伦约会数据集中游戏时间与飞行时间分布散点图:param data: [1000,4] 矩阵:return: None"""plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文plt.rcParams['axes.unicode_minus'] = False  # 显示负号plt.figure(figsize=(20, 9), dpi=100)plt.xlabel("飞行公里数")plt.ylabel("游戏时间占比")plt.title("海伦约会数据集飞行公里数与游戏时间占比喜爱程度分布图")g1 = plt.scatter(x=data[data["喜爱程度"] == "largeDoses"]["飞行公里数"], y=data[data["喜爱程度"] == "largeDoses"]["游戏时间比例"],c="red")g2 = plt.scatter(x=data[data["喜爱程度"] == "smallDoses"]["飞行公里数"], y=data[data["喜爱程度"] == "smallDoses"]["游戏时间比例"],c="blue")g3 = plt.scatter(x=data[data["喜爱程度"] == "didntLike"]["飞行公里数"], y=data[data["喜爱程度"] == "didntLike"]["游戏时间比例"],c="black")plt.legend(handles=[g1, g2, g3], labels=["largeDoses", "smallDoses", "didntLike"], prop={'size': 16})plt.savefig("./dataset/dating_pic/飞行时间与游戏占比分布图.png")def draw_raw_data_02(data):"""画出海伦约会数据集中飞行公里数与雪糕消耗分布散点图:param data: [1000,4] 矩阵:return: None"""plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文plt.rcParams['axes.unicode_minus'] = False  # 显示负号plt.figure(figsize=(20, 9), dpi=100)plt.xlabel("飞行公里数")plt.ylabel("雪糕消耗")plt.title("海伦约会数据集飞行公里数与雪糕消耗占比喜爱程度分布图")g1 = plt.scatter(x=data[data["喜爱程度"] == "largeDoses"]["飞行公里数"], y=data[data["喜爱程度"] == "largeDoses"]["雪糕消耗"],c="red")g2 = plt.scatter(x=data[data["喜爱程度"] == "smallDoses"]["飞行公里数"], y=data[data["喜爱程度"] == "smallDoses"]["雪糕消耗"],c="blue")g3 = plt.scatter(x=data[data["喜爱程度"] == "didntLike"]["飞行公里数"], y=data[data["喜爱程度"] == "didntLike"]["雪糕消耗"],c="black")plt.legend(handles=[g1, g2, g3], labels=["largeDoses", "smallDoses", "didntLike"], prop={'size': 16})plt.savefig("./dataset/dating_pic/飞行时间与雪糕消耗分布图.png")def draw_raw_data_03(data):"""画出海伦约会数据集中游戏时间比例与雪糕消耗的喜爱成图散点图:param data: [1000,4] 矩阵:return: None"""plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文plt.rcParams['axes.unicode_minus'] = False  # 显示负号plt.figure(figsize=(20, 9), dpi=100)plt.xlabel("游戏时间比例")plt.ylabel("雪糕消耗")plt.title("海伦约会数据集游戏时间比例与雪糕消耗占比喜爱程度分布图")g1 = plt.scatter(x=data[data["喜爱程度"] == "largeDoses"]["游戏时间比例"], y=data[data["喜爱程度"] == "largeDoses"]["雪糕消耗"],c="red")g2 = plt.scatter(x=data[data["喜爱程度"] == "smallDoses"]["游戏时间比例"], y=data[data["喜爱程度"] == "smallDoses"]["雪糕消耗"],c="blue")g3 = plt.scatter(x=data[data["喜爱程度"] == "didntLike"]["游戏时间比例"], y=data[data["喜爱程度"] == "didntLike"]["雪糕消耗"],c="black")plt.legend(handles=[g1, g2, g3], labels=["largeDoses", "smallDoses", "didntLike"], prop={'size': 16})plt.savefig("./dataset/dating_pic/游戏时间比例与雪糕消耗分布图.png")

因为总共就3个特征,所以只需要画出3张2维图片就可以了,当然如果会花3-D的会更好,可惜我不会=。=

最终得到的图片分别是:

在上图可以看到,蓝、红、黑三种点可以说是区分的很明显的,对于飞行公里数小于20000的男士,海伦的态度是smallDoses,即不是热爱但也不讨厌。对于飞行公里数在20000~50000的男士,海伦大多数的态度是热爱的。而对于飞行公里数大于50000的男士,海伦显然是didn’tLike的。

飞行公里数在实际场景中可能代表着男人的事业,所以说,海伦对于男性的要求是希望对方有自己的事业(红区),但是又能够有时间陪陪自己(蓝区),而对于只顾着工作的工作狂,海伦是没有好感的(黑区)

在这张图可以看到,横轴依然是飞行公里数,纵轴则变成了游戏时间占比。与上一张图一样,我们可以在2万公里和5万公里处画一条线。那么上图就会分为左中右三个区域,对于左边区域可以看到大部分的态度是smallDoses,而对于中间区域又可以分为上下两部分,上部分是largeDoses,下部分是didn’tLike,而右边区域则基本上都是didn’tLike。

这就很有意思了,在实际场景中,游戏时间可能代表着男生的幽默程度。所以说,海伦可以接受事业不繁忙,同时也没那么有趣的人(可能这就是女生的安全感吧,你可以陪着我,哪怕不是那么优秀=。=)。海伦热爱的是有自己的事业,同时相当有趣的男生(这是谁都会热爱吧,又有事业又幽默),海伦讨厌的是一心只顾着事业,没有幽默感或者虽然有幽默感但是相当繁忙的男生

所以,可以看出海伦更希望男朋友能够多陪陪自己,给自己带来快乐,有自己的事业是好事,但是没有也没关系啊!

这张图可以看到三色点散乱分布在坐标轴上,并没有像上两张图那么清晰的划分,这就表明雪糕消耗对于海伦的喜好并没有良好的区分。但是可以看到:

  • 黑色均匀分布在各个角落
  • 蓝色和红色依然有一条较为清晰的分界线,即游戏占比为7%左右

首先,雪糕消耗在实际场景中可以理解为男生的身材,虽然我们没有把数据拉到3维来看,但是依然可以想象到,在上两张图中海伦厌恶的往往是过于繁忙和无趣的人,并且在这张图中并没有因雪糕消耗而产生的明显分界,可以看出海伦并不是那么看脸的人!

综上,海伦希望寻找的是有自己的事业、幽默风趣的男生,她对自己的另一半身材相貌并没有明显的要求。她希望另一半能够多陪陪自己(哪怕对方没那么有趣),而不是每天顾着工作不着家(哪怕对方很幽默)。当我从新看到这个数据我深深的陷入了沉思,抽了一包中华我才逐渐醒悟过来,原来海伦是个好女孩,她并不虚荣,并不看颜,她需要的是真真实实的,能够跟她脚踏实地地过日子的男孩,陪伴对她来说胜过一切。

4’ 测试算法

归一化

因为数据量纲的不同可以考虑归一化,但是对于KNN分类来说这个并不是问题。但是归一化的确可以带来一些好处:

  • 可以把数据限制在一定的范围内
  • 方便后期数据的处理
  • 保证程序运行时收敛加快

归一化的方法有很多,我们采用最常见的0-1归一化:

import numpydef to_normalize(data):"""给数据做(0,1)归一化:param data: [1000,4]:return: data_normed"""# minVal_0 = min(data["飞行公里数"])# maxVal_0 = max(data["飞行公里数"])# print(minVal_0, maxVal_0)# minVal_1 = min(data["游戏时间比例"])# maxVal_1 = max(data["游戏时间比例"])# print(minVal_1, maxVal_1)# minVal_2 = min(data["雪糕消耗"])# maxVal_2 = max(data["雪糕消耗"])# print(minVal_2, maxVal_2)# 取得data里的每个特征的最小值、最大值minVal = data.min(0)maxVal = data.max(0)# 各个特征的极差ranges = maxVal - minVal# print(ranges)# 使用numpy生成新矩阵new_matrix = numpy.zeros(shape=numpy.shape(data))  # [1000, 3]# 做(0,1)归一化for i in range(numpy.shape(data)[0]):new_matrix[i][0] = (data["飞行公里数"][i] - minVal[0]) / ranges[0]new_matrix[i][1] = (data["游戏时间比例"][i] - minVal[1]) / ranges[1]new_matrix[i][2] = (data["雪糕消耗"][i] - minVal[2]) / ranges[2]return new_matrix

knn测试

import operatordef my_knn(data_normed, sample, labels, k=3):"""实现KNN算法:param data_normed: 归一化后的样本集:param sample: 需要predict的样本:param k: 最近的k个人:return: final_label"""# 通过sample数组构建[1000,3]矩阵,然后实现矩阵相减得到new_data_normednew_data_normed = tile(sample, (data_normed.shape[0], 1)) - data_normedprint(tile(sample, (data_normed.shape[0], 1)))# 计算欧氏距离double_matrix = new_data_normed ** 2double_distance = double_matrix.sum(axis=1)sqrt_distance = double_distance ** 0.5new_matrix = pd.DataFrame()new_matrix["distance"] = sqrt_distancenew_matrix["label"] = labels# 排序new_matrix = new_matrix.sort_values(by=["distance"], ascending=True)# 取前k个final_matrix = new_matrix.iloc[:k, :]label_dict = {"didntLike": 0, "smallDoses": 0, "largeDoses": 0}for i in range(k):label_dict[final_matrix.iloc[i]["label"]] += 1print(label_dict)sorted_label = sorted(label_dict.items(), key=operator.itemgetter(1), reverse=True)return sorted_label[0][0]

在这一个环节涉及一些numpy库中的矩阵运算,大家若是由看不懂的地方我推荐可以打印出来看一看数据类型和实际数据,并不难理解。

主调函数

if __name__ == '__main__':file_path = "./dataset/datingTestSet.txt"data, target = read_data(file_path)# print(data)# draw_raw_data_01(data)# draw_raw_data_02(data)# draw_raw_data_03(data)data_normed = to_normalize(data)print(data_normed)# 由于懒得对测试数据做归一化所以直接写入sample = [0.29115949, 0.50910294, 0.51079493]label = my_knn(data_normed, sample, target, k=3)print("KNN结果是:", label)

案例2-手写数字识别

案例背景

手写数字识别也是tensorflow的一个经典案例,但是这个案例一样可以使用KNN算法来做。

开发流程

  • 观察数据集
  • 解析数据集
  • 测试算法

1’ 观察数据集

点击这里可以下载数据集,提取码:xrm6。打开数据集可以看到两个文件夹,一个是trainingDigits一个是testDigits,里面的文件都是txt格式的,并且文件命名规则都是标签_编号.txt格式,所以可以预测到我们需要对文件名做处理得到标签。

点开txt文件,可以看到是一个32*32的[0,1]矩阵,1的位置表示手写数字的位置。

2’ 分析数据集

数据集标签需要通过python的os模块和字符串split拿到,我们需要做成一个np.array类型,也可以考虑与数据部分合并成一个DataFrame。

因为本次使用的是KNN,并且矩阵是[0,1]矩阵,显然不需要做归一化,我们需要做的就是把矩阵从[32,32]转变为[1,1024]形状

3’ 测试算法

这里直接把整个代码贴上来了,但是不建议大家去读。我建议大家可以自己想想如何去做,这里的难点主要是数据读取和处理的过程,KNN的过程其实和海伦约会一样。

import os
import operator
import pandas as pd
import numpy as npdef read_data(file_path):"""读取指定目录下的所有txt文件并且[32,32] -> [1024,1] 的data转换并且获取每一个文件的target 做成一个向量:param file_path: 需要读取的文件夹路径:return: DataFrame[data,target]"""dir_list = os.listdir(file_path)data = pd.DataFrame(columns=("data", "target"))for file_name in dir_list:# 获得目标值file_target = file_name.split(sep="_")[0]# 读取txt文件,转为[1024,1]data_32 = pd.read_table(filepath_or_buffer=file_path + file_name, header=None)# df格式转为np.arraydata_32 = np.array(data_32)new_str = ""for i in data_32:new_str += i[0]# 向dataframe中加一行data = data.append({"data": new_str, "target": file_target}, ignore_index=True)return datadef df_to_np(data):"""将df对象矩阵转为np.array矩阵:param data: df类型 ["data","target"]:return: np.array"""data = data["data"]# 需要先做一个0向量才能生成np_matrix = np.array(np.zeros(shape=(1, 1024)))# 先构造一个np矩阵 因为最小0 最大1 所以不需要归一化了for item in data:item_list = []for index in item:item_list.append(int(index))item_nparray = np.array(item_list)np_matrix = np.row_stack((np_matrix, item_nparray))np_matrix = np_matrix[1:]# 把第一行全0除去return np_matrixdef my_knn(line, train_matrix, train_label, k):np_matrix = np.tile(line, (train_matrix.shape[0], 1)) - train_matrix# 计算欧式距离double_matrix = np_matrix ** 2double_distance = double_matrix.sum(axis=1)distance = double_distance ** 0.5new_matrix = pd.DataFrame()new_matrix["distance"] = distancenew_matrix["label"] = train_label# 排序new_matrix = new_matrix.sort_values(by=["distance"], ascending=True)# 取前k个final_matrix = new_matrix.iloc[:k, :]label_dict = {}for i in range(10):label_dict[str(i)] = 0for i in range(k):label_dict[final_matrix.iloc[i]["label"]] += 1sorted_label = sorted(label_dict.items(), key=operator.itemgetter(1), reverse=True)print(sorted_label[0][0])return sorted_label[0][0]def cal_acc_recall(train_data, test_data, k=3):"""通过train_data来实现knn,然后遍历test_data来算acc recall:param train_data: 训练集:param test_data: 测试集:param k: k个最临近值:return: None"""train_matrix = df_to_np(train_data)train_label = train_data["target"]test_matrix = df_to_np(test_data)test_label = test_data["target"]print(test_label[0])# 计数器iter = 0# acc_numacc_num = 0for line in test_matrix:predict_label = my_knn(line, train_matrix, train_label, k)acc_num = acc_num + 1 if predict_label == test_label[iter] else acc_numiter += 1# 准确率print("准确率是%f" % (acc_num / (iter + 1)))# 召回率 懒得做了return Noneif __name__ == '__main__':# 1' 读取数据集、返回data,targettrainingDigits_path = "./dataset/knn-handwriting_num/trainingDigits/"testDigits_path = "./dataset/knn-handwriting_num/testDigits/"train_data = read_data(trainingDigits_path)# 2' 读取测试数据集test_data = read_data(testDigits_path)# 3' 使用KNN对test_data一一测试cal_acc_recall(train_data, test_data, 3)

项目地址

点击进入github

一篇文彻底理解KNN算法 - 我点了一包华子,终于明白了海伦是个好女孩相关推荐

  1. knn算法python理解与预测_理解KNN算法

    KNN主要包括训练过程和分类过程.在训练过程上,需要将训练集存储起来.在分类过程中,将测试集和训练集中的每一张图片去比较,选取差别最小的那张图片. 如果数据集多,就把训练集分成两部分,一小部分作为验证 ...

  2. 教你用OpenCV实现机器学习最简单的k-NN算法

    前言:OpenCV 的构建是为了提供计算机视觉的通用基础接口,现在已经成为经典和最优秀的计算机视觉和机器学习的综合算法工具集.作为一个开源项目,研究者.商业用户和政府部门都可以轻松利用和修改现成的代码 ...

  3. KNN算法的机器学习基础

    KNN算法的机器学习基础 https://mp.weixin.qq.com/s/985Ym3LjFLdkmqbytIqpJQ 本文原标题 : Machine Learning Basics with ...

  4. 什么是k-NN算法?怎样实现?终于有人讲明白了

    导读:使用分类模型预测类标签. 作者:阿迪蒂亚·夏尔马(Aditya Sharma).维什韦什·拉维·什里马利(Vishwesh Ravi Shrimali).迈克尔·贝耶勒(Michael Beye ...

  5. Python+OpenCV:理解k近邻(kNN)算法(k-Nearest Neighbour (kNN) algorithm)

    Python+OpenCV:理解k近邻(kNN)算法(k-Nearest Neighbour (kNN) algorithm) 理论 kNN is one of the simplest classi ...

  6. knn算法python理解与预测_K近邻算法用作回归的使用介绍(使用Python代码)

    介绍 在我遇到的所有机器学习算法中,KNN是最容易上手的.尽管它很简单,但事实上它其实在某些任务中非常有效(正如你将在本文中看到的那样). 甚至它可以做的更好?它可以用于分类和回归问题!然而,它其实更 ...

  7. 小白のKNN算法理解及代码实现

    KNN算法 一.概述 二.代码实现 2.1 准备数据 2.2数据可视化 2.3数据归一化 2.4 划分训练集和测试集 2.5 KNN分类器的实现 三.总结 一.概述 k-近邻算法(k-Nearest ...

  8. KNN算法原理通俗理解

    1.什么是KNN算法? KNN全称是k-Nearest Neighbors,意思是K个最近的邻居. KNN算法从名字上我们就可以很直观地看出它的原理:从所有的训练样本中找出和未知最近的K个样本,将k个 ...

  9. knn算法中k值的理解

    knn算法是指对预测集中的每一个图像与训练集中的所有图像比较,寻找出在训练集中与这一张预测图片最接近的图像,将该图像的标签给这张预测图片.实施的方法为图像矩阵相减并取绝对值,然后将得到的像素矩阵各元素 ...

最新文章

  1. 启示录:打造用户喜爱的产品【PDF清晰版】,产品经理必看书籍之一
  2. [译]Professional ASP.NET MVC3(01)-Chapter 1:Getting Started(上)
  3. hibernate它 11.many2many双向
  4. 我的node+express小例子
  5. 软件测试用python一般用来做什么-月薪20K的软件测试岗,为什么要求我会Python?...
  6. getpeername函数与getsockname函数的介绍
  7. git安装 perl ubuntu_Ubuntu系统上安装Git
  8. python连接access 参数太少_paip. 解决php 以及 python 连接access无效的参数量。参数不足,期待是 1”的错误...
  9. css flex 之 flex-grow | flex-direction
  10. 部分Excel函数的使用
  11. python使用正则验证电子邮件_在Python中使用正则表达式提取电子邮件地址
  12. DenseNet解析
  13. [转载]斐讯K2 A2版免TTL刷BREED不死Bootloader
  14. 中文版Cooledit2.1安装程序+插件
  15. ROS机器人更换新雷达需要重新配置carto和navigation的哪些参数
  16. 看电脑头痛计算机专业,看电脑头疼怎么回事
  17. 3.Trie树(题:最大异或对)
  18. Camels and Bridge[ARC105C][二分+Dp]
  19. EXCEL 2010添加趋势线
  20. 成都市2021年高考三诊成绩查询,2020年成都各校高三“三诊”成绩一览表

热门文章

  1. OPEX推出新一代货到人仓库自动化技术——Infinity ASRS
  2. Redhat/Selinux上mysql启动报错Operating system error number 13的解决方法
  3. 关于mikefile 萌新用法
  4. 信息安全管理 读书笔记
  5. skywalking agent 导致内存泄漏的一些问题
  6. K8S太火了!花10分钟玩转它不香么?
  7. 选股绝招[如何抓住狂涨的黑马]
  8. winedt103系统找不到指定文件_win10专业版提示系统找不到指定文件的解决教程
  9. Apache Lucene - Building and Installing the Basic Demo
  10. 微博小尾巴自定义名字中的Android,新浪微博自定义来自XX小尾巴怎么改 新浪微博显示来自XX小尾巴设置教程...