K-近邻算法(KNN)
目标:
- 掌握K-近邻算法实现过程
- 知道K-近邻算法的距离公式
- 知道K-近邻算法的超参数K值以及取值问题
- 知道KD树实现搜索的过程
- 应用KNeighborsClassifier实现分类
- 知道K-近邻算法的优缺点
- 知道交叉验证实现过程
- 知道超参数搜索过程
- 应用GridSearchCV实现算法参数的调优
1 K-近邻算法简介
1.1 什么是KNN
-
概念
如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
-
距离公式
-
欧式距离
-
2 K-近邻算法API初步使用
2.1 Scikit-learn
-
安装
安装scikit-learn前需要Numpy, Scipy等库
pip3 install scikit-learn
-
介绍
scikit-learn包含的内容:
- 分类、聚类、回归
- 特征工程
- 模型选择、调优
2.2 K-近邻算法API
-
API
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5)
- n_neighbors:int,可选(默认= 5),k_neighbors查询默认使用的邻居数
-
实现流程
- 计算已知类别数据集中的点与当前点之间的距离
- 按距离递增次序排序
- 选取与当前点距离最小的k个点
- 统计前k个点所在的类别出现的频率
- 返回前k个点出现频率最高的类别作为当前点的预测分类
-
案例
from sklearn.neighbors import KNeighborsClassifier # 构造数据集 x = [[0], [1], [2], [3]] y = [0, 0, 1, 1] # 实例化一个训练模型 estimator = KNeighborsClassifier(n_neighbors=2) # 使用fit方法进行训练 estimator.fit(x, y) # 预测 estimator.predict([[1]])
3 距离变量
3.1 欧式距离
欧氏距离是最容易直观理解的距离度量方法,我们小学、初中和高中接触到的两个点在空间中的距离一般都是指欧氏距离。
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923143642.png)
3.2 曼哈顿距离
在曼哈顿街区要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”。曼哈顿距离也称为“城市街区距离”(City Block distance)。
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923143744.png)
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923143816.png)
3.3 切比雪夫距离
国际象棋中,国王可以直行、横行、斜行,所以国王走一步可以移动到相邻8个方格中的任意一个。国王从格子(x1,y1)走到格子(x2,y2)最少需要多少步?这个距离就叫切比雪夫距离。
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923143949.png)
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923144019.png)
3.4 闵可夫斯基距离
闵氏距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。
两个n维变量a(x11,x12,…,x1n)与b(x21,x22,…,x2n)间的闵可夫斯基距离定义为:
![image-20190225182628694](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923144118.png)
其中p是一个变参数:
当p=1时,就是曼哈顿距离;
当p=2时,就是欧氏距离;
当p→∞时,就是切比雪夫距离。
根据p的不同,闵氏距离可以表示某一类/种的距离。
-
缺点
举例:二维样本(身高[单位:cm],体重[单位:kg]),现有三个样本:a(180,50),b(190,50),c(180,60)。a与b的闵氏距离(无论是曼哈顿距离、欧氏距离或切比雪夫距离)等于a与c的闵氏距离。但实际上身高的10cm并不能和体重的10kg划等号。
- 将各个分量的量纲(scale),也就是“单位”相同的看待了;
- 未考虑各个分量的分布(期望,方差等)可能是不同的。
3.5 标准化欧式距离
标准化欧氏距离是针对欧氏距离的缺点而作的一种改进。
思路:既然数据各维分量的分布不一样,那先将各个分量都“标准化”到均值、方差相等。假设样本集X的均值(mean)为m,标准差(standard deviation)为s,X的“标准化变量”表示为:
![image-20190213184012294](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923144435.png)
3.6 余弦距离
几何中,夹角余弦可用来衡量两个向量方向的差异;机器学习中,借用这一概念来衡量样本向量之间的差异。
夹角余弦取值范围为[-1,1]。余弦越大表示两个向量的夹角越小,余弦越小表示两向量的夹角越大。
当两个向量的方向重合时余弦取最大值1,当两个向量的方向完全相反余弦取最小值-1。
两个n维样本点a(x11,x12,…,x1n)和b(x21,x22,…,x2n)的夹角余弦为:
![余弦距离](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923144640.png)
即为:
![余弦距离](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923144656.png)
3.7 杰卡德距离
-
杰卡德系数
两个集合A和B的交集元素在A,B的并集中所占的比例,称为两个集合的杰卡德相似系数,用符号J(A,B)表示
-
杰卡德距离
与杰卡德相似系数相反,用两个集合中不同元素占所有元素的比例来衡量两个集合的区分度
3.8 汉明距离(略)
3.9 马氏距离(略)
4 K值的选择
-
K值的影响
K值过小:容易受到异常点的影响,意味着整体模型变得复杂,容易发生过拟合
K值过大:受到样本均衡的影响,与输入实例较远(不相似的)训练实例也会对预测起作用,使预测发生错误,且K值的增大就意味着整体的模型变得简单。
在实际应用中,K值一般取一个比较小的数值,例如采用交叉验证法(训练集和验证集)来选择最优的K值。对这个简单的分类器进行泛化,用核方法把这个线性模型扩展到非线性的情况,具体方法是把低维数据集映射到高维特征空间。
-
近似误差
对现有训练集的训练误差,关注训练集,如果近似误差过小可能会出现过拟合的现象,对现有的训练集能有很好的预测,但是对未知的测试样本将会出现较大偏差的预测。模型本身不是最接近最佳模型。
-
估计误差
可以理解为对测试集的测试误差,关注测试集,估计误差小说明对未知数据的预测能力好,模型本身最接近最佳模型。
5 KD树
**k近邻法最简单的实现是线性扫描(穷举搜索),即要计算输入实例与每一个训练实例的距离。计算并存储好以后,再查找K近邻。**当训练集很大时,计算非常耗时。为了提高kNN搜索的效率,可以考虑使用特殊的结构存储训练数据,以减小计算距离的次数。
5.1 KD树简介
当数据集很大时,这个计算成本非常高,针对N个样本,D个特征的数据集,其算法复杂度为O(DN^2)。
-
什么是KD树
kd树(K-dimension tree)是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。
为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。
如果A和B距离很远,B和C距离很近,那么A和C的距离也很远。这样优化后的算法复杂度可降低到O(DNlog(N))
-
原理
黄色的点作为根节点,上面的点归左子树,下面的点归右子树,接下来再不断地划分,分割的那条线叫做分割超平面(splitting hyperplane),在一维中是一个点,二维中是线,三维的是面。
黄色节点就是Root节点,下一层是红色,再下一层是绿色,再下一层是蓝色。
-
树的建立
-
最近邻域搜索(Nearest-Neighbor Lookup)
kd树是一种二叉树,表示对k维空间的一个划分,构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
-
5.2 构造方法
-
构造根结点,使根结点对应于K维空间中包含所有实例点的超矩形区域
-
通过递归的方法,不断地对k维空间进行切分,生成子结点
-
上述过程直到子区域内没有实例时终止(终止时的结点为叶结点)。在此过程中,将实例保存在相应的结点上。
-
通常,循环的选择坐标轴对空间切分,选择训练实例点在坐标轴上的中位数为切分点,这样得到的kd树是平衡的
在构建KD树时,关键需要解决2个问题:
-
选择向量的哪一维进行划分;
可以是随机选择某一维或按顺序选择,但是更好的方法应该是在数据比较分散的那一维进行划分(分散的程度可以根据方差来衡量)
-
如何划分数据;
-
6 案例分析
6.1 鸢尾花种类预测
Iris数据集是常用的分类实验数据集,由Fisher, 1936收集整理。
6.2 scikit-learn数据集介绍
-
scikit-learn数据集API
-
sklearn.datasets
datasets.load_*() :获取小规模数据集,数据包含在datasets里
datasets.fetch_*(data_home=None):获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/
-
-
scikit-learn数据集返回值
-
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) # 返回值是一个继承自字典的Bench print("鸢尾花的特征值:\n", iris["data"]) print("鸢尾花的目标值:\n", iris.target) print("鸢尾花特征的名字:\n", iris.feature_names) print("鸢尾花目标值的名字:\n", iris.target_names) print("鸢尾花的描述:\n", iris.DESCR)
-
-
查看数据分布
通过创建一些图,以查看不同类别是如何通过特征来区分的。 在理想情况下,标签类将由一个或多个特征对完美分隔。 在现实世界中,这种理想情况很少会发生。
-
seaborn
Seaborn 是基于 Matplotlib 核心库进行了更高级的 API 封装,可以让你轻松地画出更漂亮的图形。参考:http://seaborn.pydata.org/
seaborn.lmplot() 是一个非常有用的方法,它会在绘制二维散点图时,自动完成回归拟合
- sns.lmplot() 里的 x, y 分别代表横纵坐标的列名,
- data= 是关联到数据集,
- hue=*代表按照 species即花的类别分类显示,
- fit_reg=是否进行线性拟合。
# 内嵌绘图 import seaborn as sns import matplotlib.pyplot as plt import pandas as pd # 把数据转换成dataframe的格式 iris_d = pd.DataFrame(iris['data'], columns = ['Sepal_Length', 'Sepal_Width', 'Petal_Length', 'Petal_Width']) iris_d['Species'] = iris.target def plot_iris(iris, col1, col2): sns.lmplot(x = col1, y = col2, data = iris, hue = "Species", fit_reg = False) plt.xlabel(col1) plt.ylabel(col2) plt.title('鸢尾花种类分布图') plt.show() plot_iris(iris_d, 'Petal_Width', 'Sepal_Length')
-
-
数据集的划分
-
分类
- 训练集:用于训练,构建模型,70% 80% 75%。
- 测试集:用于评估模型是否有效,30% 20% 25%。
-
api
sklearn.model_selection.train_test_split(arrays, *options)
- x 数据集的特征值
- y 数据集的目标值
- test_size 测试集的大小,一般为float
- random_state 随机数种子,不同的种子会造成不同的随机采样结果。相同的种子采样结果相同。
- return 测试集特征训练集特征值值,训练标签,测试标签(默认随机取)
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split # 1、获取鸢尾花数据集 iris = load_iris() # 对鸢尾花数据集进行分割 # 训练集的特征值x_train 测试集的特征值x_test 训练集的目标值y_train 测试集的目标值y_test x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22) print("x_train:\n", x_train.shape) # 随机数种子 x_train1, x_test1, y_train1, y_test1 = train_test_split(iris.data, iris.target, random_state=6) x_train2, x_test2, y_train2, y_test2 = train_test_split(iris.data, iris.target, random_state=6) print("如果随机数种子不一致:\n", x_train == x_train1) print("如果随机数种子一致:\n", x_train1 == x_train2)
-
7 特征工程
7.1 什么是特征预处理
-
定义
通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程
-
为何要进行归一化/标准化?
特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级,容易影响(支配)目标结果,因此我们需要用到一些方法进行无量纲化,使不同规格的数据转换到同一规格
-
-
API
sklearn.preprocessing
7.2 归一化
通过对原始数据进行变换把数据映射到(默认为[0,1])之间
作用于每一列,max为一列的最大值,min为一列的最小值,那么X’’为最终结果,mx,mi分别为指定区间值默认mx为1,mi为0
![归一化公å¼](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923174324.png)
-
API
sklearn.preprocessing.MinMaxScaler (feature_range=(0,1)… )
- MinMaxScalar.fit_transform(X)
- X:numpy array格式的数据[n_samples,n_features]
- 返回值:转换后的形状相同的array
import pandas as pd from sklearn.preprocessing import MinMaxScaler def minmax_demo(): """ 归一化演示 :return: None """ data = pd.read_csv("dating.txt") print(data) # 1、实例化一个转换器类 transfer = MinMaxScaler(feature_range=(2, 3)) # 2、调用fit_transform data = transfer.fit_transform(data[['milage','Liters','Consumtime']]) print("最小值最大值归一化处理的结果:\n", data) return None
- MinMaxScalar.fit_transform(X)
-
总结
注意最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法鲁棒性较差,只适合传统精确小数据场景。
7.3 标准化
通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内
作用于每一列,mean为平均值,σ为标准差
注:在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。
![æ ‡å‡†åŒ–å…¬å¼](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923174750.png)
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923175020.png)
-
区别
- 对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
- 对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。
-
API
sklearn.preprocessing.StandardScaler( )
- 处理之后每列来说所有数据都聚集在均值0附近标准差差为1
- StandardScaler.fit_transform(X)
- X:numpy array格式的数据[n_samples,n_features]
- 返回值:转换后的形状相同的array
import pandas as pd from sklearn.preprocessing import StandardScaler def stand_demo(): """ 标准化演示 :return: None """ data = pd.read_csv("dating.txt") print(data) # 1、实例化一个转换器类 transfer = StandardScaler() # 2、调用fit_transform data = transfer.fit_transform(data[['milage','Liters','Consumtime']]) print("标准化的结果:\n", data) print("每一列特征的平均值:\n", transfer.mean_) print("每一列特征的方差:\n", transfer.var_) return None
8 交叉验证&网格搜索
8.1 什么是交叉验证
-
定义
交叉验证:将拿到的训练数据,分为训练和验证集。
目的:为了让被评估的模型更加准确可信
- 训练集:训练集+验证集
- 测试集:测试集
注意:交叉验证不能提高模型的准确率
![img](https://gitee.com/hpu120623/noteImage/raw/master/images/20200923181541.png)
8.2 什么是网格搜索(Grid Search)
交叉验证只是对于参数得出更好的结果,那么怎么选择或者调优参数呢?
-
定义
通常情况下,有很多参数是需要手动指定的(如k-近邻算法中的K值),这种叫超参数。但是手动过程繁杂,所以需要对模型预设几种超参数组合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。
8.3 API
-
sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
- 对估计器的指定参数值进行详尽搜索
- estimator:估计器对象
- param_grid:估计器参数(dict){“n_neighbors”:[1,3,5]}
- cv:指定几折交叉验证
- fit:输入训练数据
- score:准确率
- 结果分析:
- bestscore__:在交叉验证中验证的最好结果
- bestestimator:最好的参数模型
- cvresults:每次交叉验证后的验证集准确率结果和训练集准确率结果
8.4 案例优化
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
# 1、获取数据集
iris = load_iris()
# 2、数据基本处理 -- 划分数据集
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=22)
# 3、特征工程:标准化# 实例化一个转换器类
transfer = StandardScaler()
# 调用
fit_transformx_train = transfer.fit_transform(x_train)x_test = transfer.transform(x_test)
# 4、KNN预估器流程
# 4.1 实例化预估器类
estimator = KNeighborsClassifier()
# 4.2 模型选择与调优——网格搜索和交叉验证# 准备要调的超参数
param_dict = {"n_neighbors": [1, 3, 5]}estimator = GridSearchCV(estimator, param_grid=param_dict, cv=3)
# 4.3 fit数据进行训练
estimator.fit(x_train, y_train)
# 5、评估模型效果
# 方法a:比对预测结果和真实值
y_predict = estimator.predict(x_test)print("比对预测结果和真实值:\n", y_predict == y_test)
# 方法b:直接计算准确率
score = estimator.score(x_test, y_test)print("直接计算准确率:\n", score)
# 其他评价指标
print("在交叉验证中验证的最好结果:\n", estimator.best_score_)
print("最好的参数模型:\n", estimator.best_estimator_)
print("每次交叉验证后的准确率结果:\n", estimator.cv_results_)