【机器学习实战学习笔记(1-1)】k-近邻算法原理及python实现

笔者本人是个初入机器学习的小白,主要是想把学习过程中的大概知识和自己的一些经验写下来跟大家分享,也可以加强自己的记忆,有不足的地方还望小伙伴们批评指正,点赞评论走起来~

1.k-近邻算法概述

k-近邻(k-nearest neighbor, k-NN)算法是一种基本分类与回归方法,这里只讨论分类问题中的k-NN。

k-NN算法:假设给定一个训练数据集,其中的实例类别已定(即有标签的训练数据集,属于有监督学习问题),分类时,对新的输入实例,在训练数据集中找到与该实例距离最近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类。

因此,k-NN不具有显式的学习过程。该算法实际上是利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。

k-NN的特点

  • 优点:精度高、对异常值不敏感、无数据输入假定;(优点的第三点不是特别理解,欢迎知道的小伙伴留言指教~)
  • 缺点:计算复杂度高、空间复杂度高;
  • 适用数据范围:数值型、标称型

k-NN使用的模型实际上对应于特征空间的划分。模型由距离度量、k值、分类决策规则这三个基本要素决定,下面对k-NN模型这三个基本要素进行简单介绍。

1.1 距离度量

特征空间中两个实例点的距离是两个实例点相似程度的反映。k-NN使用的距离是欧氏距离,也可以是其他距离,如更一般的LpL_pLp​距离或Minkowski距离。

设特征空间χ\chiχ是n维实数向量空间RnR^nRn,xi,xj∈χx_i,x_j\in\chixi​,xj​∈χ,xi=(xi(1),xi(2),...,xi(n))Tx_i = (x_{i}^{(1)},x_{i}^{(2)},...,x_{i}^{(n)})^Txi​=(xi(1)​,xi(2)​,...,xi(n)​)T,xj=(xj(1),xj(2),...,xj(n))Tx_j= (x_{j}^{(1)},x_{j}^{(2)},...,x_{j}^{(n)})^Txj​=(xj(1)​,xj(2)​,...,xj(n)​)T,xi,xjx_i,x_jxi​,xj​的LpL_pLp​距离定义为:

Lp(xi,xj)=(∑l=1n∣xi(l)−xj(l)∣p)1p(p≥1)L_p(x_i,x_j)=(\sum_{l=1}^{n}|x_{i}^{(l)}-x_{j}^{(l)}|^p)^{\frac{1}{p}}(p\ge1)Lp​(xi​,xj​)=(l=1∑n​∣xi(l)​−xj(l)​∣p)p1​(p≥1)

  • 当p=2时,为欧氏距离(Euclidean distance),即L2(xi,xj)=(∑l=1n∣xi(l)−xj(l)∣2)12L_2(x_i,x_j)=(\sum_{l=1}^{n}|x_{i}^{(l)}-x_{j}^{(l)}|^2)^{\frac{1}{2}}L2​(xi​,xj​)=(∑l=1n​∣xi(l)​−xj(l)​∣2)21​,或者我们更为熟悉的形式:L2(xi,xj)=∑l=1n(xi(l)−xj(l))2L_2(x_i,x_j)=\sqrt{\sum_{l=1}^{n}(x_{i}^{(l)}-x_{j}^{(l)})^2}L2​(xi​,xj​)=l=1∑n​(xi(l)​−xj(l)​)2​
  • 当p=1时,称为曼哈顿距离(Manhattan distance),即L1(xi,xj)=∑l=1n∣xi(l)−xj(l)∣L_1(x_i,x_j)=\sum_{l=1}^{n}|x_{i}^{(l)}-x_{j}^{(l)}|L1​(xi​,xj​)=l=1∑n​∣xi(l)​−xj(l)​∣
  • 当p=∞\infty∞时,它是各个坐标距离的最大值,即L∞(xi,xj)=maxl∣xi(l)−xj(l)∣L_\infty(x_i,x_j)=max_{l}|x_{i}^{(l)}-x_{j}^{(l)}|L∞​(xi​,xj​)=maxl​∣xi(l)​−xj(l)​∣

1.2 k值的选择

k值的选择会对k-NN法的结果产生重大影响。

若选择较小的k值,就相当于用较小的领域中的训练实例进行预测。

  • 优点:学习的近似误差(approximation error)会减小,只有与输入实例距离较近(相似)的训练实例才会对预测结果有影响;
  • 缺点:学习的估计误差(estimation error)会增大,预测结果会对近邻的实例点非常敏感。这时如果邻近的实例点恰巧是噪声,预测就会出错。

k值的减小意味着整体模型变得复杂,容易发生过拟合。

若选择较大的k值,就相当于用较大邻域中的训练实例进行预测。

  • 优点:学习的估计误差会减小
  • 缺点:学习的近似误差会增大。这时与输入实例距离较远(不相似)的训练实例也会对预测结果有影响,使预测发生错误。

k值的增大意味着整体模型变得简单,容易欠拟合。

在实际应用中,k值一般取一个比较小的数值。通常采用交叉验证法来选取最优的k值。

1.3 分类决策规则

k-NN中的分类决策规则通常是多数表决,即由与输入实例距离最近的k个训练实例中的多数类决定输入实例的类。

2.k-近邻算法实现

2.1 实现方法

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

下面主要介绍k-近邻法的两种实现方法:

线性搜索(linear scan)法:

  • 优点:这是k近邻法最简单的实现方法
  • 缺点:要计算输入实例与每一个训练实例的距离。当训练集很大时,计算非常耗时,这种方法不太可行。

kd树(也称k决策树):

为了提高k近邻搜索的效率,可以考虑使用特殊的结构存储训练数据,以减少计算距离的次数。具体方法有很多,其中方法之一就是kd树方法。可以节省大量的计算开销。

2.2 k-近邻法python3.6实现

本节主要介绍基于欧式距离、多数表决规则,实现方法采用线性搜索法,实现k-近邻法。开发环境为python3.6。

对于kd树的实现,现在暂时没有了解代码实现,后期再补上。

2.2.1 k-近邻法实现程序

from numpy import *
import operator ## k-近邻法实现
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
# 计算距离
diffMat = tile(inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
# 选择距离最小的k个点
sortedDistIndicies = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 对字典排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# Python3中不再支持iteritems(),将iteritems()改成items()
return sortedClassCount[0][0]

2.2.2 classify0(inX, dataSet, labels, k)中部分方法注释

  • classify0函数中参数说明

     inX:测试实例,n维行向量(n为实例的特征数)
    dataSet:训练实例数据集,m*n的NumPy矩阵(m为训练实例数,n为实例的特征数)
    labels:m维行向量(m为训练实例总个数)
  • 数组的维度

     例子:x = array([[[1,2,3],[4,5,6]],[[7,8,9],[0,1,2]],[[3,4,5],[6,7,8]]])
    print(x.shape) #(3,2,3)
    可以看到x是一个包含了3个两行三列的二维数组的三维数组:
    x.shape[0]代表包含二维数组的个数,x.shape[1]表示二维数组的行数,x.shape[2]表示二维数组的列数。
  • tile(A, res)

     功能:重复A的各个维度
    参数类型:
    A: Array类的都可以
    rep:A沿着各个维度重复的次数
    例子:
    print(tile([1,2],(2,2,3)))
    重复顺序为: [1,2] => [[1,2] , [1,2]] => [[[1,2],[1,2]] , [[1,2],[1,2]]] => [[[1,2,1,2,1,2],[1,2,1,2,1,2]] , [[1,2,1,2,1,2],[1,2,1,2,1,2]]]
  • sum()

     在numpy中数组都有着[]标记,则axis=0对应着最外层的[],axis=1对应第二外层的[],以此类推。
    例子:
    a= array([[1,2],[3,4]])
    a.sum(axis = 0)
    a有两层[],最外层[]里的最大单位块分别为[1,2],[3,4],对这两个单位块做块与块之间的运算,[1,2]+[3,4] = [4, 6];
    做完加法后本应是[[4, 6]],但是移除最外层[]后,原来的两层[]变成一层[],所以返回结果为 [4, 6]。
  • argsort()函数

     x.argsort():将x中的元素从小到大排列,提取其对应的index(索引)
  • dict.items()

     作用:可以将字典中的所有项,以列表方式返回。因为字典是无序的,所以用items方法返回字典的所有项,也是没有顺序的。
  • operator.itemgetter()

     operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号。

2.2.3 如何测试分类器

这里用错误率来评估分类器在某个数据集上的执行效果:

错误率=分类器给出错误结果的次数测试执行的总数错误率=\frac{分类器给出错误结果的次数}{测试执行的总数}错误率=测试执行的总数分类器给出错误结果的次数​

参考资料:

  • 《机器学习实战》第二章
  • 《统计学习方法》第三章
上一篇:高并发和多线程——Java内存模型


下一篇: ---- position