K-近邻算法(KNN)

Posted 胡微_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了K-近邻算法(KNN)相关的知识,希望对你有一定的参考价值。

K-近邻算法(KNN)

1 KNN介绍

K Nearest Neighbor算法又叫KNN算法,是一种分类算法,这个算法是机器学习里面一个比较经典的算法, 总体来说 KNN 算法是相对比较容易理解的算法。

定义:如果一个样本在特征空间中的 k 个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

  • 通过你的“邻居”来判断你属于哪个类别
  • 如何计算你到你的"邻居"的距离:一般时候,都是使用欧氏距离

算法流程

  • 计算已知类别数据集中的点与当前点之间的距离
  • 按距离递增次序排序
  • 选取与当前点距离最小的 k 个点
  • 统计前 k 个点所在的类别出现的频率
  • 返回前 k 个点出现频率最高的类别作为当前点的预测分类

2 KNN的初步使用

K-近邻算法API

sklearn.neighbors.KNeighborsClassifier(n_neighbors=5)

  • n_neighbors:int,可选,默认= 5

案例:

"""
1.获取数据集
2.数据基本处理(该案例中省略)
3.特征工程(该案例中省略)
4.机器学习
5.模型评估(该案例中省略)
"""

# 导入模块
from sklearn.neighbors import KNeighborsClassifier

# 构造数据集
x = [[0], [1], [2], [3]]
y = [0, 0, 1, 1]

# 训练模型
# 实例化API
estimator = KNeighborsClassifier(n_neighbors=1)
# estimator = KNeighborsClassifier() 会报错,因为邻居数默认为5,而样本数只有4
# 使用fit方法进行训练
estimator.fit(x, y)
print(estimator.predict([[1]])) # 0

问:为什么x是二维,y是一维?
答:可以这样理解,x是特征值,是dataframe形式,理解为二维的[[]],y表示的目标值,可以表示为series,表示为一维数组[]

3 距离度量

3.1 基本性质

3.2 常见距离公式

(1)欧式距离

欧氏距离是最容易直观理解的距离度量方法,我们小学、初中和高中接触到的两个点在空间中的距离一般都是指欧氏距离。

(2)曼哈顿距离

在曼哈顿街区要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”。曼哈顿距离也称为“城市街区距离”(City Block distance)。


(3)切比雪夫距离

国际象棋中,国王可以直行、横行、斜行,所以国王走一步可以移动到相邻8个方格中的任意一个。国王从格子(x1,y1)走到格子(x2,y2)最少需要多少步?这个距离就叫切比雪夫距离。


(4)闵可夫斯基距离

闵氏距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。
两个n维变量a(x11,x12,…,x1n)与b(x21,x22,…,x2n)间的闵可夫斯基距离定义为:

其中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.3 距离属性

我们常将属性划分为"连续属性" (continuous attribute)和"离散属性" (categorical attribute),前者在定义域上有无穷多个可能的取值,后者在定义域上是有限个取值.

  • 若属性值之间存在序关系,则可以将其转化为连续值,例如:身高属性“高”“中等”“矮”,可转化为1, 0.5, 0。
    • 闵可夫斯基距离可以用于有序属性。
  • 若属性值之间不存在序关系,则通常将其转化为向量的形式,例如:性别属性“男”“女”,可转化为(1,0),(0,1)。

4 k值选择

  • 近似误差:
    • 对现有训练集的训练误差,关注训练集
    • 如果近似误差过小可能会出现过拟合的现象,对现有的训练集能有很好的预测,但是对未知的测试样本将会出现较大偏差的预测。
    • 模型本身不是最接近最佳模型。
  • 估计误差:
    • 可以理解为对测试集的测试误差,关注测试集
    • 估计误差小说明对未知数据的预测能力好,
    • 模型本身最接近最佳模型。
  1. 选择较小的K值,就相当于用较小的领域中的训练实例进行预测,
    “学习”近似误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是“学习”的估计误差会增大,
    换句话说,K值的减小就意味着整体模型变得复杂,容易发生过拟合;
  2. 选择较大的K值,就相当于用较大领域中的训练实例进行预测,
    其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时候,与输入实例较远(不相似的)训练实例也会对预测器作用,使预测发生错误。
    且K值的增大就意味着整体的模型变得简单。
  3. K=N(N为训练样本个数),则完全不足取,
    因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单,忽略了训练实例中大量有用信息。

在实际应用中,K值一般取一个比较小的数值,例如采用交叉验证法(简单来说,就是把训练数据再分成两组:训练集和验证集)来选择最优的K值。

由此可得,KNN中K值大小选择对模型的影响:

  • K值过小:
    • 容易受到异常点的影响
    • 容易过拟合
  • K值过大:
    • 受到样本均衡的问题
    • 容易欠拟合

5 KNN优化—kd树

实现k近邻算法时,主要考虑的问题是如何对训练数据进行快速k近邻搜索。这在特征空间的维数大及训练数据容量大时尤其必要。

k近邻法最简单的实现是线性扫描(穷举搜索),即要计算输入实例与每一个训练实例的距离。计算并存储好以后,再查找K近邻。当训练集很大时,计算非常耗时。

为了提高KNN搜索的效率,可以考虑使用特殊的结构存储——kd树来训练数据,以减小计算距离的次数。

5.1 kd树简介

根据KNN每次需要预测一个点时,我们都需要计算训练数据集里每个点到这个点的距离,然后选出距离最近的k个点进行投票。当数据集很大时,这个计算成本非常高,针对N个样本,D个特征的数据集,其算法复杂度为O(DN^2​​)

kd树:为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。其基本原理是,如果A和B距离很远,B和C距离很近,那么A和C的距离也很远。有了这个信息,就可以在合适的时候跳过距离远的点。这样优化后的算法复杂度可降低到O(DNlogN)

黄色的点作为根节点,上面的点归左子树,下面的点归右子树,接下来再不断地划分,分割的那条线叫做分割超平面(splitting hyperplane),在一维中是一个点,二维中是线,三维的是面。


类比“二分查找”:给出一组数据:[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维空间的划分。空间划分就是对数据点进行分类,“挨得近”的数据点就在一个空间里面。

5.2 构造方法

kd树(K-dimension tree)是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。kd树是一种二叉树,表示对k维空间的一个划分,构造kd树相当于不断地用垂直于坐标轴的超平面将K维空间切分,构成一系列的K维超矩形区域。kd树的每个结点对应于一个k维超矩形区域。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。

  1. 构造根结点,使根结点对应于K维空间中包含所有实例点的超矩形区域;
  2. 通过递归的方法,不断地对k维空间进行切分,生成子结点。在超矩形区域上选择一个坐标轴和在此坐标轴上的一个切分点,确定一个超平面,这个超平面通过选定的切分点并垂直于选定的坐标轴,将当前超矩形区域切分为左右两个子区域(子结点);这时,实例被分到两个子区域。
  3. 上述过程直到子区域内没有实例时终止(终止时的结点为叶结点)。在此过程中,将实例保存在相应的结点上。
  4. 通常,循环的选择坐标轴对空间切分,选择训练实例点在坐标轴上的中位数为切分点,这样得到的kd树是平衡的(平衡二叉树:它是一棵空树,或其左子树和右子树的深度之差的绝对值不超过1,且它的左子树和右子树都是平衡二叉树)。

KD树中每个节点是一个向量,和二叉树按照数的大小划分不同的是,KD树每层需要选定向量中的某一维,然后根据这一维按左小右大的方式划分数据。在构建KD树时,关键需要解决2个问题:

  • 选择向量的哪一维进行划分;

    • 解决方法:可以是随机选择某一维或按顺序选择,但是更好的方法应该是在数据比较分散的那一维进行划分(分散的程度可以根据方差来衡量,方差越大越分散图像越矮胖)
  • 如何划分数据;

    • 解决方法:好的划分方法可以使构建的树比较平衡,可以每次选择中位数来进行划分。

5.3 案例分析

(1)树的建立

给定一个二维空间数据集:T=(2,3),(5,4),(9,6),(4,7),(8,1),(7,2),构造一个平衡kd树。

第一维度X:2,4,5,7,8,9 中位数:(5+7)/2=6
第二维度Y:1,2,3,4,6,7
显而易见,在选取根结点时,第一维度X更分散,方差更大,因此选择X维进行划分

根结点对应包含数据集T的矩形,选择X轴,6个数据点的X坐标中位数是6,这里选最接近的(7,2)点,【选择(5,4)也可以】以平面X=7将空间分为左、右两个子矩形(子结点);接着左矩形以Y=4分为两个子矩形【左矩形中(2,3),(5,4),(4,7)点的Y坐标中位数正好为4),Y轴的方差更大,选择Y划分】,右矩形以Y=6分为两个子矩形,如此递归,最后得到如下图所示的特征空间划分和kd树。

(2)最近领域的搜索

假设标记为星星的点是 test point, 绿色的点是找到的近似点,在回溯过程中,需要用到一个队列,存储需要回溯的点,在判断其他子节点空间中是否有可能有距离查询点更近的数据点时,做法是以查询点为圆心,以当前的最近距离为半径画圆,这个圆称为候选超球(candidate hypersphere),如果圆与回溯点的轴相交,则需要将轴另一边的节点都放到回溯队列里面来。

(3)查找点(2.1,3.1)

在(7,2)点测试到达(5,4),在(5,4)点测试到达(2,3),然后search_path中的结点为<(7,2),(5,4), (2,3)>,从search_path中取出(2,3)作为当前最佳结点nearest, dist为0.141;

然后回溯至(5,4),以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆,并不和超平面y=4相交,如上图,所以不必跳到结点(5,4)的右子空间去搜索,因为右子空间中不可能有更近样本点了。

于是再回溯至(7,2),同理,以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆并不和超平面x=7相交,所以也不用跳到结点(7,2)的右子空间去搜索。

至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2.1,3.1)的最近邻点,最近距离为0.141。

(4)查找点(2,4.5)

在(7,2)处测试到达(5,4),在(5,4)处测试到达(4,7)【优先选择在本域搜索】,然后search_path中的结点为<(7,2),(5,4), (4,7)>,从search_path中取出(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。

kd树的搜索过程

  • 二叉树搜索比较待查询节点和分裂节点的分裂维的值,(小于等于就进入左子树分支,大于就进入右子树分支直到叶子结点)
  • 顺着“搜索路径”找到最近邻的近似点
  • 回溯搜索路径,并判断搜索路径上的结点的其他子结点空间中是否可能有距离查询点更近的数据点,如果有可能,则需要跳到其他子结点空间中去搜索
  • 重复这个过程直到搜索路径为空

6 数据集

6.1 获取数据集

(1)数据集API介绍

sklearn.datasets加载获取流行数据集

  • datasets.load_*()
    • 获取小规模数据集,数据包含在datasets里,例如:加载并返回鸢尾花数据集sklearn.datasets.load_iris()
  • datasets.fetch_*(data_home=None)
    • 获取大规模数据集,需要从网络上下载,函数的第一个参数是data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/。例如:sklearn.datasets.fetch_20newsgroups(data_home=None,subset=‘train’),subset:‘train’或者’test’,‘all’,可选,选择要加载的数据集。

(2)数据集返回值介绍

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)

6.2 划分数据集

机器学习一般的数据集会划分为两个部分:

  • 训练数据:用于训练,构建模型
  • 测试数据:在模型检验时使用,用于评估模型是否有效

划分比例:

  • 训练集:70% 80% 75%
  • 测试集:30% 20% 25%

sklearn.model_selection.train_test_split(arrays, *options)

  • 参数:
    • x 数据集的特征值
    • y 数据集的标签值
    • test_size 测试集的大小,一般为float
    • random_state 随机数种子,不同的种子会造成不同的随机采样结果。相同的种子采样结果相同。类似于np.random.seed()
  • 返回值
    • x_train, x_test, y_train, y_test
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 特征工程—特征预处理

特征预处理是特征工程的内容之一,即通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程。

为什么我们要进行归一化/标准化?
特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级,容易影响(支配)目标结果,使得一些算法无法学习到其它的特征。要保证每一个特征同等重要。

特征预处理APIsklearn.preprocessing

我们需要用到一些方法进行无量纲化,使不同规格的数据转换到同一规格

  • 归一化
  • 标准化

7.1 归一化

通过对原始数据进行变换把数据映射到(默认为[0,1])之间

作用于每一列,max为一列的最大值,min为一列的最小值,那么X’’为最终结果,mx,mi分别为指定区间值默认mx为1,mi为0

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

minmax_demo()

"""
     milage     Liters  Consumtime  target
0     40920   8.326976    0.953952       3
1     14488   7.153469    1.673904       2
2     26052   1.441871    0.805124       1
3     75136  13.147394    0.428964       1
4     38344   1.669788    0.134296       1
..      ...        ...         ...     ...
995   11145   3.410627    0.631838       2
996   68846   9.974715    0.669787       1
997   26575  10.650102    0.866627       3
998   48111   9.134528    0.728045       3
999   43757   7.882601    1.332446       3

[1000 rows x 4 columns]
最小值最大值归一化处理的结果:
 [[2.44832535 2.39805139 2.56233353]
 [2.15873259 2.34195467 2.98724416]
 [2.28542943 2.06892523 2.47449629]
 ...
 [2.29115949 2.50910294 2.51079493]
 [2.52711097 2.43665451 2.4290048 ]
 [2.47940793 2.3768091  2.78571804]]
"""

dating.txt数据有三个特征值:milage,Liters,Consumtime,一个目标值target

注意:最大值最小值是变化的,另外,最大值与最小值非常容易受异常点影响,所以这种方法鲁棒性(确定性、稳定性)较差,只适合传统精确小数据场景。

7.2 标准化

通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内

作用于每一列,mean为平均值,σ为标准差

  • 对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
  • 对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。

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

stand_demo()

"""
 milage     Liters  Consumtime  target
0     40920   8.326976    0.953952       3
1     14488   7.153469    1.673904       2
2     26052   1.441871    0.805124       1
3     75136  13.147394    0.428964       1
4     38344   1.669788    0.134296       1
..      ...        ...         ...     ...
995   11145   3.410627    0.631838       2
996   68846   9.974715    0.669787       1
997   26575  10.650102    0.866627       3
998   48111   9.134528    0.728045       3
999   43757   7.882601    1.332446       3

[1000 rows x 4 columns]
标准化的结果:
 [[ 0.33193158  0.41660188  0.24523407]
 [-0.87247784  0.13992897  1.69385734]
 [-0.34554872 -1.20667094 -0.05422437]
 ...
 [-0.32171752  0.96431572  0.06952649]
 [ 0.65959911  0.60699509 -0.20931587]
 [ 0.46120328  0.31183342  1.00680598]]
每一列特征的平均值:
 [3.36354210e+04 6.55996083e+00 8.32072997e-01]
每一列特征的方差:
 [4.81628039e+08 1.79902874e+01 2.46999554e-01]

"""

在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。

7.3 案例分析

K-近邻算法API进阶使用

sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm='auto')

  • n_neighbors:
    • 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分割样本空间,每个节点是一个超球体。
"""
1.获取数据集
2.数据基本处理
3.特征工程
4.机器学习(模型训练)
5.模型评估
"""
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

# 1.获取数据集
iris = load_iris()

# 2.数据基本处理
# x_train,x_test,y_train,y_test为训练集特征值、测试集特征值、训练集目标值、测试集目标值
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=22)

# 3、特征工程:标准化
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train) # 训练集使用fit_transform
x_test = transfer.transform(x_test) # 测试集使用transform

# 4、机器学习(模型训练)
estimator = KNeighborsClassifier(n_neighbors=9)
estimator.fit(x_train, y_train)

# 5、模型评估
# 方法1:比对真实值和预测值
y_predict = estimator.predict(x_test)
print("预测结果为:\\n", y_predict)
print("比对真实值和预测值:\\n", y_predict == y_test)
# 方法2:直接计算准确率
score = estimator.score(x_test, y_test)
print("准确率为:\\n", score)

注意:

  • fit和transform没有任何关系,仅仅是数据处理的两个不同环节
    • fit():简单来说,就是求得训练集X的均值,方差,最大值,最小值等
    • transform():在fit的基础上,对数据进行标准化、降维、归一化等转换操作
  • 我们在训练集上调用fit_transform(),其实找到了均值μ和方差σ^2,即我们已经找到了转换规则,我们把这个规则利用在训练集上,同样,我们可以直接将其运用到测试集上(甚至交叉验证集),所以在测试集上的处理,我们只需要标准化数据而不需要再次拟合数据。

8 交叉验证

8.1 什么是交叉验证

交叉验证:将拿到的训练数据,分为训练和验证集。以下图为例:将数据分成10份,其中一份作为验证集。然后经过10次(组)的测试,每次都更换不同的验证集。即得到10组模型的结果,取平均值作为最终结果。又称10折交叉验证。

交叉验证目的:为了让被评估的模型更加可信

我们之前知道数据分为训练集和测试集,但是为了让从训练得到模型结果更加准确。做以下处理

  • 训练集:训练集+验证集
  • 测试集:测试集


问题:这个只是让被评估的模型更加准确可信,那么怎么选择或者调优参数呢?

8.2 模型选择与调优

网格搜索:通常情况下,有很多参数是需要手动指定的(如k-近邻算法中的K值),这种叫超参数。但是手动过程繁杂,所以需要对模型预设几种超参数组合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建立模型。简单来说,网格搜索就是把超参数的值,通过字典的形式传递进去,然后进行选择最优值

8.3 增加K值调优案例

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:每次交叉验证后的验证集准确率结果和训练集准确率结果
# 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_transform
x_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_)

9 综合案例

9.1 案例描述

本次比赛的目的是预测一个人将要签到的地方。 为了本次比赛,Facebook创建了一个虚拟世界,其中包括10公里*10公里共100平方公里的约10万个地方。 对于给定的坐标集,您的任务将根据用户的位置,准确性和时间戳等预测用户下一次的签到位置。 数据被制作成类似于来自移动设备的位置数据。

9.2 数据集介绍

数据来源:https://www.kaggle.com/c/facebook-v-predicting-check-ins


文件说明 train.csv,test.csv

  • row id:签入事件的id
  • x y:坐标
  • accuracy: 准确度,定位精度
  • time: 时间戳
  • place_id: 签到的位置,这也是你需要预测的内容

9.3 步骤分析

  • 对于数据做一些基本处理(这里所做的一些处理不一定达到很好的效果,我们只是简单尝试,有些特征我们可以根据一些特征选择的方式去做处理)
    • 缩小数据集范围 DataFrame.query()
    • 选取有用的时间特征
    • 将签到位置少于n个用户的删除
  • 分割数据集
  • 标准化处理
  • k-近邻预测

9.4 代码实现

# 1、获取数据集
facebook = pd.read_csv("./data/FBlocation/train.csv")

# 2.基本数据处理
# 2.1 缩小数据范围
facebook_data = facebook.query("x>2.0 & x<2.5 & y>2.0 & y<2.5")
# 2.2 选择时间特征
time = pd.to_datetime(facebook_data["time"], unit="s")
time = pd.DatetimeIndex(time)
facebook_data["day"] = time.day
facebook_data["hour"] = time.hour
facebook_data["weekday"] = time.weekday
# 2.3 去掉签到较少的地方
place_count = facebook_data.groupby("place_id").count()
place_count = place_count[place_count["row_id"]>3]
facebook_data = facebook_data[facebook_data["place_id"].isin(place_count.index)]
# 2.4 确定特征值和目标值
x = facebook_data[["x", "y", "accuracy", "day", "hour", "weekday"]]
y = facebook_data["place_id"]
# 2.5 分割数据集
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)

# 3.特征工程--特征预处理(标准化)
# 3.1 实例化一个转换器
transfer = StandardScaler()
# 3.2 调用fit_transform
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)

# 4.机器学习--knn+cv
# 4.1 实例化一个估计器
estimator = KNeighborsClassifier()
# 4.2 调用gridsearchCV
param_grid = "n_neighbors": [1, 3, 5, 7, 9]
estimator = GridSearchCV(estimator, param_grid=param_grid, cv=5)
# 4.3 模型训练
estimator.fit(x_train, y_train以上是关于K-近邻算法(KNN)的主要内容,如果未能解决你的问题,请参考以下文章

数据挖掘-K-近邻算法

KNN算法,k近邻

K-近邻算法(KNN)

K-近邻(KNN)算法

机器学习实战☛k-近邻算法(K-Nearest Neighbor, KNN)

K近邻算法-KNN