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文本文件中,按照一定的格式进 ...
随机推荐
- hbase结合hive和sqoop实现数据指导mysql
hive综合hbase两个优势表中的: 1.实现数据导入到MYSQL. 2.实现hbase表转换为另外一张hbase表. 三个操作环节: 1.hbase关联hive作为外部 ...
- respondsToSelector的作用
1.respondsToSelector 用来推断某一个方法时候实现(以下的代码意思:假设baseAPIdidStartRequest这种方法实现了,那么就去调用,防止出现异常) if ([self. ...
- 一款天气app的温度曲线图的实现
原文:一款天气app的温度曲线图的实现 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/tyhzsd/article/details/50544639 ...
- 逻辑回归原理介绍及Matlab实现
原文:逻辑回归原理介绍及Matlab实现 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/laobai1015/article/details/781 ...
- MSRA专访摘要
前段时间有幸参加微软亚洲研究院之旅,顺便投简历,没想到在两次访谈迎来,并且是连续 的两次被拒绝.严重的刺激到了我.导致我疯狂的复习刷Offer.如今最终算是告于段落.如今也最终有空沉下心来总结 总结近 ...
- MVC基架生成的Create视图
@model MyMusicStore.Models.Album @{ ViewBag.Title = "Create"; } <h2>Create</h ...
- WPF 数据模板DataType属性的使用,不用指定ItemTemplate
<Window x:Class="CollectionBinding.MainWindow" xmlns="http://schemas.micros ...
- Win10 如何以管理员身份设置开机自启程序(1)
原文:Win10 如何以管理员身份设置开机自启程序(1) 最近一个项目在win10上部署时遇到问题,即如何设置以管理员身份开机自启.现把解决方法整理如下: 首先,为了进行验证,我编了一个简单的程序te ...
- .Net Core中使用NodeJs加解密DES,MD5,AES,REA
鉴于使用.net core我们的加解密也同时迁移到了跨平台上,我使用的是NodeJs加解密的.废话不多说了,还是来干活吧. 1.创建Node项目 2.添加package.json { "n ...
- Dynamic proxy (good-原创)
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflec ...