K-近邻算法介绍与代码实现
声明:如需转载请先联系我。
最近学习了k近邻算法,在这里进行了总结。
KNN介绍
k近邻法(k-nearest neighbors)是由Cover和Hart于1968年提出的,它是懒惰学习(lazy learning)的著名代表。
它的工作机制比较简单:
- 给定一个测试样本
- 计算它到训练样本的距离
- 取离测试样本最近的
k
个训练样本 - “投票法”选出在这k个样本中出现最多的类别,就是预测的结果
距离衡量的标准有很多,常见的有:\(L_p\)距离、切比雪夫距离、马氏距离、巴氏距离、余弦值等。
什么意思呢?先来看这张图
我们对应上面的流程来说
- 1.给定了红色和蓝色的训练样本,绿色为测试样本
- 2.计算绿色点到其他点的距离
- 3.选取离绿点最近的k个点
- 4.选取k个点中,同种颜色最多的类。例如:k=1时,k个点全是蓝色,那预测结果就是Class 1;k=3时,k个点中两个红色一个蓝色,那预测结果就是Class 2
举例
这里用欧氏距离
作为距离的衡量标准,用鸢尾花数据集举例说明。
鸢尾花数据集有三个类别,每个类有150个样本,每个样本有4个特征。
先来回顾一下欧氏距离的定义(摘自维基百科):
在欧几里得空间中,点 x = (x1,...,xn) 和 y = (y1,...,yn) 之间的欧氏距离为
\(d(x,y):={\sqrt {(x_{1}-y_{1})^{2}+(x_{2}-y_{2})^{2}+\cdots +(x_{n}-y_{n})^{2}}}={\sqrt {\sum _{{i=1}}^{n}(x_{i}-y_{i})^{2}}}\)
向量 \({\displaystyle {\vec {x}}}\)的自然长度,即该点到原点的距离为
\(\|{\vec {x}}\|_{2}={\sqrt {|x_{1}|^{2}+\cdots +|x_{n}|^{2}}}\)
它是一个纯数值。在欧几里得度量下,两点之间线段最短。
现在给出六个训练样本,分为三类,每个样本有4个特征,编号为7的名称
是我们要预测的。
编号 | 花萼长度(cm) | 花萼宽度(cm) | 花瓣长度(cm) | 花瓣宽度(cm) | 名称 |
---|---|---|---|---|---|
1 | 4.9 | 3.1 | 1.5 | 0.1 | Iris setosa |
2 | 5.4 | 3.7 | 1.5 | 0.2 | Iris setosa |
3 | 5.2 | 2.7 | 3.9 | 1.4 | Iris versicolor |
4 | 5.0 | 2.0 | 3.5 | 1.0 | Iris versicolor |
5 | 6.3 | 2.7 | 4.9 | 1.8 | Iris virginica |
6 | 6.7 | 3.3 | 5.7 | 2.1 | Iris virginica |
7 | 5.5 | 2.5 | 4.0 | 1.3 | ? |
按照之前说的步骤,我们来计算测试样本到各个训练样本的距离。例如到第一个样本的距离:
\(d_{1}=\sqrt{(4.9 - 5.5)^2 + (3.1 - 2.5)^2 + (1.5 - 4.0)^2 + (0.1 - 1.3)^2} = 2.9\)
写一个函数来执行这个操作吧
import numpy as np
def calc_distance(iA,iB):
temp = np.subtract(iA, iB) # 对应元素相减
temp = np.power(temp, 2) # 元素分别平方
distance = np.sqrt(temp.sum()) # 先求和再开方
return distance
testSample = np.array([5.5, 2.5, 4.0, 1.3])
print("Distance to 1:", calc_distance(np.array([4.9, 3.1, 1.5, 0.1]), testSample))
print("Distance to 2:", calc_distance(np.array([5.4, 3.7, 1.5, 0.2]), testSample))
print("Distance to 3:", calc_distance(np.array([5.2, 2.7, 3.9, 1.4]), testSample))
print("Distance to 4:", calc_distance(np.array([5.0, 2.0, 3.5, 1.0]), testSample))
print("Distance to 5:", calc_distance(np.array([6.3, 2.7, 4.9, 1.8]), testSample))
print("Distance to 6:", calc_distance(np.array([6.7, 3.3, 5.7, 2.1]), testSample))
Distance to 1: 2.9
Distance to 2: 2.98496231132
Distance to 3: 0.387298334621
Distance to 4: 0.916515138991
Distance to 5: 1.31909059583
Distance to 6: 2.36854385647
如果我们把k定为3,那么离测试样本最近3个依次是:
编号 | 名称 |
---|---|
3 | Iris versicolor |
4 | Iris versicolor |
5 | Iris virginica |
显然测试样本属于Iris versicolor
类的“票数”多一点,事实上它的确属于这个类。
优/缺点
这里参考了CSDN芦金宇博客上的总结
优点
- 简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
- 可用于数值型数据和离散型数据;
- 训练时间复杂度为O(n);无数据输入假定;
- 对异常值不敏感。
缺点
- 计算复杂性高;空间复杂性高;
- 样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
- 一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易发生误分。
- 最大的缺点是无法给出数据的内在含义。
补充一点:由于它属于懒惰学习,因此需要大量的空间来存储训练实例,在预测时它还需要与已知所有实例进行比较,增大了计算量。
这里介绍一下,当样本不平衡时的影响。
从直观上可以看出X应该属于\(\omega_{1}\),这是理所应当的。对于Y看起来应该属于\(\omega_{1}\),但事实上在k范围内,更多的点属于\(\omega_{2}\),这就造成了错误分类。
一个结论
在周志华编著的《机器学习》中证明了最近邻学习器的泛化错误率不超过贝叶斯最优分类器的错误率的两倍,在原书的226页,这里就不摘抄了。
代码实现
知道KNN的原理后,应该可以很轻易的写出代码了,这里介绍一下在距离计算上的优化,在结尾给上完整代码(代码比较乱,知道思想就好)。
函数的输入:train_X
、test_X
是numpy array,假设它们的shape分别为(n, 4)、(m, 4);要求输出的是它们两点间的距离矩阵,shape为(n, m)。
不就是计算两点之间的距离,再存起来吗,直接暴力上啊︿( ̄︶ ̄)︿,于是就有了下面的
双循环暴力实现
def euclideanDistance_two_loops(train_X, test_X):
num_test = test_X.shape[0]
num_train = train_X.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
for j in range(num_train):
test_line = test_X[i]
train_line = train_X[j]
temp = np.subtract(test_line, train_line)
temp = np.power(temp, 2)
dists[i][j] = np.sqrt(temp.sum())
return dists
不知道你有没有想过,这里的样本数只有100多个,所以时间上感觉没有等太久,但是当样本量非常大的时候呢,双循环计算的弊端就显露出来了。解决方案是把它转换为两个矩阵之间的运算,这样就能避免使用循环了。
转化为矩阵运算实现
L2 Distance
此处参考CSDNfrankzd的博客
记测试集矩阵P的大小为\(M*D\),训练集矩阵C的大小为\(N*D\)(测试集中共有M个点,每个点为D维特征向量。训练集中共有N个点,每个点为D维特征向量)
记\(P_{i}\)是P的第i行\(P_i = [ P_{i1}\quad P_{i2} \cdots P_{iD}]\),记\(C_{j}\)是C的第j行\(C_j = [ C_{j1} C_{j2} \cdots \quad C_{jD}]\)
- 首先计算Pi和Cj之间的距离dist(i,j)
- 我们可以推广到距离矩阵的第i行的计算公式
- 继续将公式推广为整个距离矩阵(也就是完全平方公式)
知道距离矩阵怎么算出来的之后,在代码上只需要套公式带入就能实现了。
def euclideanDistance_no_loops(train_X, test_X):
num_test = test_X.shape[0]
num_train = train_X.shape[0]
sum_train = np.power(train_X, 2)
sum_train = sum_train.sum(axis=1)
sum_train = sum_train * np.ones((num_test, num_train))
sum_test = np.power(test_X, 2)
sum_test = sum_test.sum(axis=1)
sum_test = sum_test * np.ones((1, sum_train.shape[0]))
sum_test = sum_test.T
sum = sum_train + sum_test - 2 * np.dot(test_X, train_X.T)
dists = np.sqrt(sum)
return dists
是不是很简单,这里两种方法我们衡量两点间距离的标准是欧氏距离
。如果想用其他的标准呢,比如\(L_{1}\)距离该怎么实现呢,这里我参照上面推导公式的思想得出了计算\(L_{1}\)距离的矩阵运算。
L1 Distance
记测试集矩阵P的大小为\(M*D\),训练集矩阵C的大小为\(N*D\)(测试集中共有M个点,每个点为D维特征向量。训练集中共有N个点,每个点为D维特征向量)
记\(P_{i}\)是P的第i行\(P_i = [ P_{i1}\quad P_{i2} \cdots P_{iD}]\),记\(C_{j}\)是C的第j行\(C_j = [ C_{j1} C_{j2} \cdots \quad C_{jD}]\)
首先计算Pi和Cj之间的距离dist(i,j)
\(d(P_{i}, C_{j}) = |P_{i1} - C_{j1}| + |P_{i2} - C_{j2}| + \cdots + |P_{iD} - C_{jD}|\)我们可以推广到距离矩阵的第i行的计算公式
\(dist[i] = \left | [P_{i}\quad P_{i} \cdots P_{i}] - [C_{1}\quad C_{2}\cdots C_{N}] \right|=[|P_{i} - C_{1}|\quad |P_{i} - C_{2}| \cdots |P_{i} - C_{N}|]\)继续将公式推广为整个距离矩阵
其中\(P_i = [ P_{i1}\quad P_{i2} \cdots P_{iD}]\)、\(C_j = [ C_{j1} C_{j2} \cdots \quad C_{jD}]\)
def l1_distance_no_loops(train_X, test_X):
num_test = test_X.shape[0]
num_train = train_X.shape[0]
test = np.tile(test_X, (num_train, 1, 1))
train = np.tile(train_X, (num_test, 1, 1))
train = np.transpose(train, axes=(1, 0, 2))
sum = np.subtract(test, train)
sum = np.abs(sum)
sum = np.sum(sum, axis=2)
dists = sum.T
return dists
由于测试集样本数量有限,两种距离衡量标准下的准确率分别是0.94和0.98。
完整代码
近期文章
K-近邻算法介绍与代码实现的更多相关文章
- 从K近邻算法谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...
- 机器学习03:K近邻算法
本文来自同步博客. P.S. 不知道怎么显示数学公式以及排版文章.所以如果觉得文章下面格式乱的话请自行跳转到上述链接.后续我将不再对数学公式进行截图,毕竟行内公式截图的话排版会很乱.看原博客地址会有更 ...
- <转>从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/likika2012/article/details/39619687 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章待写:1.KD树:2.神经 ...
- 用Python从零开始实现K近邻算法
KNN算法的定义: KNN通过测量不同样本的特征值之间的距离进行分类.它的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别.K通 ...
- 从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
转载自:http://blog.csdn.net/v_july_v/article/details/8203674/ 从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 前言 前两日,在微博上说: ...
- 1.K近邻算法
(一)K近邻算法基础 K近邻(KNN)算法优点 思想极度简单 应用数学知识少(近乎为0) 效果好 可以解释机器学习算法使用过程中的很多细节问题 更完整的刻画机器学习应用的流程 图解K近邻算法 上图是以 ...
- 数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例)
数据挖掘入门系列教程(三)之scikit-learn框架基本使用(以K近邻算法为例) 简介 scikit-learn 估计器 加载数据集 进行fit训练 设置参数 预处理 流水线 结尾 数据挖掘入门系 ...
- 机器学习——KNN算法(k近邻算法)
一 KNN算法 1. KNN算法简介 KNN(K-Nearest Neighbor)工作原理:存在一个样本数据集合,也称为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分 ...
- 机器学习实战 - python3 学习笔记(一) - k近邻算法
一. 使用k近邻算法改进约会网站的配对效果 k-近邻算法的一般流程: 收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据.一般来讲,数据放在txt文本文件中,按照一定的格式进 ...
随机推荐
- C++学习笔记26,虚函数
在C++里面,虚拟功能是功能的一类重要!不同目的可以通过在不同的虚拟功能来达到同样的动作被定义. 举一个简单的例子: #include <iostream> #include <st ...
- WPF响应长按事件
原文:WPF响应长按事件 版权声明:本文为博主原创文章,转载请注明出处. https://blog.csdn.net/lwwl12/article/details/78983140 思路:MouseD ...
- Altera公司高速PCB布线指南
来至Altera公司的高速PCB布线指南,该文档言简意赅,深入浅出,对于日常高速PCB布局布线中经常碰到的一些问题进行了解析.例如:板材的选择,介电常数及损耗因子对高速高频线路的影响,传输线,阻抗控制 ...
- Swift现实
笔者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/34540623 转载请注明出处 假设认为文章对你有所帮助,请通过留言 ...
- jquery layer插件弹出弹层 结构紧凑,功能强大
/* 去官方网站下载最新的js http://sentsin.com/jquery/layer/ ①引用jquery ②引用layer.min.js */ 事件触发炸弹层可以自由绑定,例如: $('# ...
- 关于C#你应该知道的2000件事
原文 关于C#你应该知道的2000件事 下面列出了迄今为止你应该了解的关于C#博客的2000件事的所有帖子. 帖子总数= 1,219 大会 #11 -检查IL使用程序Ildasm.exe d #179 ...
- ubuntu16.04安装搜狗输入法
安装完Ubuntu 16.04后,要更换为国内的软件源: Ali-OSM Alibaba Open Source Mirror Site Home About Join Us Ubuntu 1.软件包 ...
- Hutool 3.0.8 发布,Java 工具集
Hutool 是一个Java工具包,提供了丰富的文件.日期.日志.正则.字符串.配置文件等工具方法,并封装了一套简单易用的ORM框架. 主页:http://hutool.cn/ 文档:http://h ...
- Win10 UWP版《芒果TV》v2.4.0直播超女,芒果台综艺一网打尽
Win10 UWP版<芒果TV>直播超女,芒果台综艺一网打尽 Win10版UWP<芒果TV>自2015年9月登录商店以来,一直在持续更新,积极改进,拥有芒果台视频的独家点播和直 ...
- SqlServer 禁止架构更改的复制中手动修复使发布和订阅中分别增加的字段同步
原文:SqlServer 禁止架构更改的复制中手动修复使发布和订阅中分别增加的字段同步 由于之前的需要,禁止了复制架构更改,以至在发布中添加一个字段,并不会同步到订阅中,而现在又在订阅中添加了一个同名 ...