K-近邻算法
Posted PCGuo999
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了K-近邻算法相关的知识,希望对你有一定的参考价值。
一、K-近邻算法
1.介绍
K-近邻算法(K Nearest Neighbor)又叫KNN算法,指如果一个样本在特征空间中的k个最相似的样本中的大多数属于某一个类别,则该样本也属于这个类别。也就是对于新输入的实例,从数据集中找到于该实例最邻近的k个实例,那么这k个实例大多数属于某一个类,那么就把该实例放到该类中。
KNN算法不仅可以用于分类,还可以用于回归。通过找出一个样本的k个最近邻居,将这些邻居的属性的平均值赋给该样本,就可以得到该样本的属性。
举个栗子:若已经对一部分人标明皮肤白还是黑,当新加入一个人时,若要对其判定皮肤是黑还是白,那么就可以看其皮肤颜色与已判定黑或者白的人群皮肤颜色进行对比,与哪方更贴近则可以表明该人皮肤到底属于哪一类
2.K-近邻算法流程
- 计算已知类别数据集中的点与当前点之间的距离
- 按距离递增次序排序
- 选取与当前点距离最小的k个点
- 统计前k个点所在的类别出现的频率
- 返回前k个点出现频率最高的类别作为当前点的预测分类
计算距离一般使用欧氏距离公式, d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 d(x,y)=\\sqrt\\sum_i=1^n(x_i-y_i)^2 d(x,y)=∑i=1n(xi−yi)2
机器学习流程获取数据-》数据基本处理-》特征工程-》机器学习-》模型评估
3.Scikit-learn机器学习库
Scikit-learn是python学习机器学习的工具,包括很多的机器学习算法。
优点:
- 简单,易理解,精度高,既可以用来做分类也可以用来做回归
- 可用于数值型数据和离散型数据
- 无数据输入假定
- 适合对稀有事件进行分类
(1)安装
安装:pip3 install scikit-learn
注:安装scikit-learn需要Numpy, Scipy等库
(2)Scikit-learn包含的内容
- 分类、聚类、回归
- 特征工程
- 模型选择、调优
4.K-近邻算法API—sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm='auto')
- n_neighbors=5:int,可选(默认= 5),k_neighbors查询默认使用的邻居数
- algorithm:‘auto’,‘ball_tree’,‘kd_tree’,‘brute’
- 快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,
- brute是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。
- kd_tree,构造kd树存储数据以便对其进行快速检索的树形数据结构,kd树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于20时效率高。
- ball tree是为了克服kd树高维失效而发明的,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。
- 快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,
案例
3.1 步骤分析
- 1.获取数据集
- 2.数据基本处理(该案例中省略)
- 3.特征工程(该案例中省略)
- 4.机器学习
- 5.模型评估(该案例中省略)
3.2 代码过程
from sklearn.neighbors import KNeighborsClassifier
# 1.构造数据
# x为特征值,是dataframe形式理解为二维[[]]
x = [[1], [2], [3], [4]]
# y为目标值,可以表示为series,表示为一维数组[]
y = [0, 10, 20, 30]
# 表示
# x y
# 1 0
# 2 10
# 3 20
# 4 30
# 2.训练模型
# 2.1实例化API,实例化一个估计器对象,因为样本数只有4个,所以n_neighbors<=4
estimator = KNeighborsClassifier(n_neighbors=1)
# 2.2使用fit方法进行训练,x为二维,y为一维
estimator.fit(x, y)
# 3.数据预测,将测试集的特征值传入,根据先前计算出的模型,来预测所给测试机的目标值,注意参数为二维[[]]
ret1 = estimator.predict([[0]])
print(ret1)
# 0离1近,1的值为0,所以输出0
ret2 = estimator.predict([[10]])
print(ret2)
# 10离4近,4的值为30,所以输出30
# 输出结果[0] [30]
5.距离度量
(1)距离公式基本性质
- 非负性: d i s t ( X i , X j ) > = 0 dist(X_i,X_j) >= 0 dist(Xi,Xj)>=0 ;
- 同一性: d i s t ( x i , x j ) = 0 dist(x_i,x_j)=0 dist(xi,xj)=0。当且仅当 X i = X j X_i = X_j Xi=Xj;
- 对称性: d i s t ( x i , x j ) = d i s t ( x j , x i ) dist(x_i,x_j)=dist(x_j,x_i) dist(xi,xj)=dist(xj,xi);
- 直递性: d i s t ( x i , x j ) < = d i s t ( x i , x k ) + d i s t ( x k , x j ) dist(x_i,x_j) <= dist(x_i,x_k) +dist(x_k,x_j) dist(xi,xj)<=dist(xi,xk)+dist(xk,xj)
直递性常被直接称为“三角不等式”。
(2)常见距离公式
①欧氏距离—通过距离平方值
d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 d(x,y)=\\sqrt\\sum_i=1^n(x_i-y_i)^2 d(x,y)=∑i=1n(xi−yi)2
X=[[1,1],[2,2],[3,3],[4,4]];
经计算得:
d = 1.4142 2.8284 4.2426 1.4142 2.8284 1.4142
②曼哈顿距离—通过距离的绝对值
在曼哈顿街区要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”。曼哈顿距离也称为“城市街区距离”(City Block distance)。
X=[[1,1],[2,2],[3,3],[4,4]];
经计算得:
d = 2 4 6 2 4 2
③切比雪夫距离—维度的最大值
国际象棋中,国王可以直行、横行、斜行,所以国王走一步可以移动到相邻8个方格中的任意一个。国王从格子(x1,y1)走到格子(x2,y2)最少需要多少步?这个距离就叫切比雪夫距离。
X=[[1,1],[2,2],[3,3],[4,4]];
经计算得:
d = 1 2 3 1 2 1
④闵可夫斯基距离
闵氏距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。
两个n维变量a(x11,x12,…,x1n)与b(x21,x22,…,x2n)间的闵可夫斯基距离定义为:
其中p是一个变参数:
-
当p=1时,就是曼哈顿距离;
-
当p=2时,就是欧氏距离;
-
当p→∞时,就是切比雪夫距离。
当次方为无穷时,即可等效为最大值
2 闵氏距离的缺点:
包括曼哈顿距离、欧氏距离和切比雪夫距离,都存在明显的缺点
(1)将各个分量的量纲(scale),也就是“单位”相同的看待了;
(2)未考虑各个分量的分布(期望,方差等)可能是不同的。
e.g. 二维样本(身高[单位:cm],体重[单位:kg]),现有三个样本:a(180,50),b(190,50),c(180,60)。
a与b的闵氏距离(无论是曼哈顿距离、欧氏距离或切比雪夫距离)等于a与c的闵氏距离。但实际上身高的10cm并不能和体重的10kg划等号。
(3) “连续属性”和“离散属性”的距离计算
我们常将属性划分为"连续属性" 和"离散属性",前者在定义域上有无穷多个可能的取值,后者在定义域上是有限个取值.
- 若属性值之间存在序关系,则可以将其转化为连续值,例如:身高属性“高”“中等”“矮”,可转化为1, 0.5, 0。
- 闵可夫斯基距离可以用于有序属性。
- 若属性值之间不存在序关系,则通常将其转化为向量的形式,例如:性别属性“男”“女”,可转化为(1,0),(0,1)。
6.K值选择
- K值过小:
- 容易受到异常点的影响
- 容易过拟合(即在训练集上表现好,但是在测试集上不好)
- k值过大:
- 受到样本均衡的问题
- 容易欠拟合
K值选择问题,李航博士的一书「统计学习方法」上所说:
- 1)选择较小的K值,就相当于用较小的领域中的训练实例进行预测,
- “学习”近似误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是“学习”的估计误差会增大,
- 换句话说,K值的减小就意味着整体模型变得复杂,容易发生过拟合;
- 2)选择较大的K值,就相当于用较大领域中的训练实例进行预测,
- 其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时候,与输入实例较远(不相似的)训练实例也会对预测器作用,使预测发生错误。
- 且K值的增大就意味着整体的模型变得简单。
- 3)K=N(N为训练样本个数),则完全不足取,
- 因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单,忽略了训练实例中大量有用信息。
- 在实际应用中,K值一般取一个比较小的数值,例如采用交叉验证法(简单来说,就是把训练数据在分成两组:训练集和验证集)来选择最优的K值。
误差
- 近似误差
- 对现有训练集的训练误差,关注训练集,
- 如果近似误差过小可能会出现过拟合的现象,对现有的训练集能有很好的预测,但是对未知的测试样本将会出现较大偏差的预测。
- 模型本身不是最接近最佳模型。
- 估计误差
- 可以理解为对测试集的测试误差,关注测试集,
- 估计误差小说明对未知数据的预测能力好,
- 模型本身最接近最佳模型。
7.KD树
为了提高kNN搜索的效率,可以考虑使用特殊的结构存储训练数据,以减小计算距离的次数。
kd树:为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。
根据KNN每次需要预测一个点时,我们都需要计算训练数据集里每个点到这个点的距离,然后选出距离最近的k个点进行投票。
当数据集很大时,这个计算成本非常高,针对N个样本,D个特征的数据集,其算法复杂度为O(D*N^2)。
优化后的算法复杂度可降低到O(DNlog(N))。
(1)原理—平衡二叉树
①树的建立
②最近邻域搜索(Nearest-Neighbor Lookup)
kd树(K-dimension tree)是**一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。**kd树是一种二叉树,表示对k维空间的一个划分,构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。
利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
类比“二分查找”:给出一组数据:[9 1 4 7 2 5 0 3 8],要查找8。如果挨个查找(线性扫描),那么将会把数据集都遍历一遍。而如果排一下序那数据集就变成了:[0 1 2 3 4 5 6 7 8 9],按前一种方式我们进行了很多没有必要的查找,现在如果我们以5为分界点,那么数据集就被划分为了左右两个“簇” [0 1 2 3 4]和[6 7 8 9]。
因此,根本就没有必要进入第一个簇,可以直接进入第二个簇进行查找。把二分查找中的数据点换成k维数据点,这样的划分就变成了用超平面对k维空间的划分。空间划分就是对数据点进行分类,“挨得近”的数据点就在一个空间里面。
(2)构造方法
(1)构造根结点,使根结点对应于K维空间中包含所有实例点的超矩形区域;
(2)**通过递归的方法,不断地对k维空间进行切分,生成子结点。**在超矩形区域上选择一个坐标轴和在此坐标轴上的一个切分点,确定一个超平面,这个超平面通过选定的切分点并垂直于选定的坐标轴,将当前超矩形区域切分为左右两个子区域(子结点);这时,实例被分到两个子区域。
(3)上述过程直到子区域内没有实例时终止(终止时的结点为叶结点)。在此过程中,将实例保存在相应的结点上。
(4)通常,循环的选择坐标轴对空间切分,选择训练实例点在坐标轴上的中位数为切分点,这样得到的kd树是平衡二叉树
KD树中每个节点是一个向量,和二叉树按照数的大小划分不同的是,KD树每层需要选定向量中的某一维,然后根据这一维按左小右大的方式划分数据。
在构建KD树时,关键需要解决2个问题:
①选择向量的哪一维进行划分;
②如何划分数据;
第一个问题简单的解决方法可以是随机选择某一维或按顺序选择,但是更好的方法应该是在数据比较分散的那一维进行划分(分散的程度可以根据方差来衡量)。
第二个问题中,好的划分方法可以使构建的树比较平衡,可以每次选择中位数来进行划分。
(3)kd树的搜索过程
- 1**.二叉树搜索比较待查询节点和分裂节点的分裂维的值**,(小于等于就进入左子树分支,大于就进入右子树分支直到叶子结点)
- 2.顺着“搜索路径”找到最近邻的近似点
- 3.回溯搜索路径,并判断搜索路径上的结点的其他子结点空间中是否可能有距离查询点更近的数据点,如果有可能,则需要跳到其他子结点空间中去搜索
- 4.重复这个过程直到搜索路径为空
举个栗子:树的建立
给定一个二维空间数据集:T=(2,3),(5,4),(9,6),(4,7),(8,1),(7,2),构造一个平衡kd树。
①思路引导:
- 因为x维度更分散,所以使用x(1)轴进行划分
- 中位数为6,最近的为(7,2)所以以平面x(1)=7将空间分为左、右两个子矩形(子结点)
- 2,4,5对应的y轴数据是:3,4,7。中位数是4,将4放到中间,3放到左边,7放到右边
- 8,9对应的y轴数据是:1,6
- 如此递归,最后得到如下图所示的特征空间划分和kd树。
划分顺序1.紫色2.黄色3.黑色
② 最近领域的搜索
假设标记为星星的点是 test point, 绿色的点是找到的近似点,在回溯过程中,需要用到一个队列,存储需要回溯的点,在判断其他子节点空间中是否有可能有距离查询点更近的数据点时,做法是以查询点为圆心,以当前的最近距离为半径画圆,这个圆称为候选超球(candidate hypersphere),如果圆与回溯点的轴相交,则需要将轴另一边的节点都放到回溯队列里面来。
③eg:查找点(2,4.5)
- 先对比x轴,2比7小,来到左边
- 对比y轴,4.5比4小,来到右边,到达叶子结点,查找路径search_path中的结点为<(7,2),(5,4), (4,7)>
- 回溯找最近点:先拿叶子结点(4,7)当作最佳结点nearest,此时距离dist为3.202;
- 向上回溯,到(5,4),以(2,4.5)为圆心,以dist=3.202为半径画一个圆与超平面y=4相交,所以需要跳到(5,4)的左子空间去搜索。所以要将(2,3)加入到search_path中,现在search_path中的结点为<(7,2),(2, 3)>;另外,(5,4)与(2,4.5)的距离为3.04 < dist = 3.202,所以将(5,4)赋给nearest,并且dist=3.04。
- 回溯至(2,3),(2,3)是叶子节点,直接平判断(2,3)是否离(2,4.5)更近,计算得到距离为1.5,所以nearest更新为(2,3),dist更新为(1.5)
- 回溯至(7,2),同理,以(2,4.5)为圆心,以dist=1.5为半径画一个圆并不和超平面x=7相交, 所以不用跳到结点(7,2)的右子空间去搜索。
- 至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2,4.5)的最近邻点,最近距离为1.5。
二、案例1:鸢尾花种类预测–数据集介绍
1.数据集介绍
2 scikit-learn中数据集介绍
(1)scikit-learn数据集API介绍
- sklearn.datasets
- 加载获取流行数据集
- datasets.load_*()
- 获取小规模数据集,数据包含在datasets里
- datasets.fetch_*(data_home=None)
- 获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/
返回值类型是bunch–是一个字典类型
(2)sklearn小数据集
-
sklearn.datasets.load_iris()
加载并返回鸢尾花数据集
from sklearn.datasets import load_iris
iris = load_iris()
print(iris)
(3)sklearn大数据集
- sklearn.datasets.fetch_20newsgroups(data_home=None,subset=‘train’)
- subset:‘train’或者’test’,‘all’,可选,选择要加载的数据集。
- 训练集的“训练”,测试集的“测试”,两者的“全部”
from sklearn.datasets import load_iris
news = fetch_20newsgroups()
print(news)
(4)sklearn数据集返回值介绍
- load和fetch返回的数据类型datasets.base.Bunch(字典格式)
- data:特征数据数组,是 [n_samples * n_features] 的二维 numpy.ndarray 数组
- target:标签数组,是 n_samples 的一维 numpy.ndarray 数组
- DESCR:数据描述
- feature_names:特征名,新闻数据,手写数字、回归数据集没有
- target_names:标签名
from sklearn.datasets import load_iris
# 获取鸢尾花数据集
iris = load_iris()
print("鸢尾花数据集的返回值:\\n", iris)
# 返回值类型是bunch--是一个字典类型
print("鸢尾花数据集特征值是:",iris["data"])
# 既可以使用[]输出也可以使用.输出
# print("数据集特征值是:",iris.data)
print("鸢尾花数据集目标值是:",iris.target)
print("鸢尾花数据集特征值名字是:",iris.feature_names)
print("鸢尾花数据集目标值名字是:",iris.target_names)
print("鸢尾花数据集的描述是:",iris.DESCR)
(5)查看数据分布
通过创建一些图,以查看不同类别是如何通过特征来区分的。 在理想情况下,标签类将由一个或多个特征对完美分隔。 在现实世界中,这种理想情况很少会发生。
seaborn介绍
- Seaborn 是基于 Matplotlib 核心库进行了更高级的 API 封装,可以让你轻松地画出更漂亮的图形。而 Seaborn 的漂亮主要体现在配色更加舒服、以及图形元素的样式更加细腻。
- 安装 pip3 install seaborn
- seaborn.lmplot() 是一个非常有用的方法,它会在绘制二维散点图时,自动完成回归拟合
- sns.lmplot() 里的 x, y 分别代表横纵坐标的列名,
- data= 是关联到数据集,
- hue=*代表按照 species即花的类别分类显示,
- fit_reg=是否进行线性拟合。
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.datasets import load_iris
# 设置显示中文字体
plt.rcParams["font.sans-serif"] = ["SimHei"]
# 获取数据集
iris = load_iris()
# 图像可视化
# 把数据转换成dataframe的格式
iris_d = pd.DataFrame(iris['data'], columns = ['Sepal_Length', 'Sepal_Width', 'Petal_Length', 'Petal_Width'])
# # 输出二维数组表格
# print(iris_d)
# 种类
iris_d['Species'] = iris.target
def plot_iris(iris, col1, col2):
# hue表示目标值,fit_reg = False表示不线性拟合,即不要线段
sns.lmplot(x = col1, y = col2, data = iris, hue = "Species", fit_reg = False)
# x,y轴标签
plt.xlabel(col1)
plt.ylabel(col2)
# 表格标题
plt.title('鸢尾花种类分布图')
plt.show()
plot_iris(iris_d, 'Petal_Width', 'Sepal_Length')
(6)数据集的划分—train_test_split(arrays, *options)
机器学习一般的数据集会划分为两个部分:
- 训练数据:用于训练,构建模型
- 测试数据:在模型检验时使用,用于评估模型是否有效
划分比例:
- 训练集:70% 80% 75%
- 测试集:30% 20% 25%
数据集划分api
sklearn.model_selection.train_test_split(arrays, *options)
- 参数:
- x 数据集的特征值
- y 数据集的标签值
- test_size 测试集的大小,一般为float,比如测试集是0.2,则表示测试集是20%,那么训练集是0.8,即80%
- random_s
以上是关于K-近邻算法的主要内容,如果未能解决你的问题,请参考以下文章
机器学习经典算法具体解释及Python实现--K近邻(KNN)算法
- 参数: