【机器学习】通俗的k-近邻算法算法解析和应用

这把我C 2021-06-28 14:10:22 4143

【机器学习】通俗的k-近邻算法算法解析和应用

在这里插入图片描述

文章目录
1 概述
2 KNN 场景
3 KNN 原理
4 实例:改进约会网站的配对效果
5 算法总结
6 KNN算法的优缺点
7 图像分类应用

1 概述

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

一句话总结: 近朱者赤近墨者黑!

k 近邻算法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类。k 近邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其 k 个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻算法不具有显式的学习过程。

k 近邻算法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。 k值的选择、距离度量以及分类决策规则是k近邻算法的三个基本要素。

2 KNN 场景

电影可以按照题材分类,那么如何区分 动作片 和 爱情片 呢?

动作片: 打斗次数更多
爱情片: 亲吻次数更多
基于电影中的亲吻、打斗出现的次数,使用 k-近邻算法构造程序,就可以自动划分电影的题材类型。

在这里插入图片描述
现在根据上面我们得到的样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到 k 个距离最近的电影。
假定 k=3,则三个最靠近的电影依次是, He's Not Really into Dudes 、 Beautiful Woman 和 California Man。
knn 算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此我们判定未知电影是爱情片。

3 KNN 原理

KNN 工作原理
假设有一个带有标签的样本数据集(训练样本集),其中包含每条数据与所属分类的对应关系。
输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
计算新数据与样本数据集中每条数据的距离。
对求得的所有距离进行排序(从小到大,越小表示越相似)。
取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
求 k 个数据中出现次数最多的分类标签作为新数据的分类。
KNN 通俗理解
给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的 k 个实例,这 k 个实例的多数属于某个类,就把该输入实例分为这个类。

KNN 开发流程
收集数据: 任何方法
准备数据: 距离计算所需要的数值,最好是结构化的数据格式
分析数据: 任何方法
训练算法: 此步骤不适用于 k-近邻算法
测试算法: 计算错误率
使用算法: 输入样本数据和结构化的输出结果,然后运行 k-近邻算法判断输入数据分类属于哪个分类,最后对计算出的分类执行后续处理
KNN 算法特点

优点: 精度高、对异常值不敏感、无数据输入假定
缺点: 计算复杂度高、空间复杂度高
适用数据范围: 数值型和标称型

4 实例:改进约会网站的配对效果

题目描述:海伦喜欢在在线约会网站寻找适合自己的对象,但是她不是喜欢每一个人。她发现交往过三种类型的人:

1.不喜欢的人

2.魅力一般的人

3.极具魅力的人

所以需要对网站的对象归入恰当的分类。她周一到周五喜欢魅力一般的人,而周末则更喜欢极具魅力的人。所以需要根据数据来分类

准备数据:
数据在文本datingTestSet.txt中,每个样本占据一行,总共1000行,每个样本有3个特征:

   每年获得的飞行常客里程数

  玩视频游戏所消耗的时间

  每周消费的冰淇淋的公升

那么这些数据从文本读取后需要改写成分类器可以接受的格式。输出为样本矩阵和类标签向量

def file2matrix(filename):
    fr = open(filename)
    arrayOlines = fr.readlines()
    numberOfLines = len(arrayOlines)
    returnMat = zeros((numberOfLines,3))
    classLabelVector=[]
    index = 0
    for line in arrayOLines:
        line = line.strip()  #去回车
        listFromLine = line.split('\t')  #以\t分割
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index+=1
    return returnMat,classLabelVector

其实python处理文本文件很容易。
reload(kNN)

datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')

datingDataMat
array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01],
[ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00],
[ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01],
...,
[ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01],
[ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01],
[ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]])

接下来需要分析数据,使用Matplotlib创建散点图

import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2]) #颜色以及尺寸标识了数据点属性类别
散点图使用datingDataMat矩阵的第二,第三列数据代表玩视频游戏所耗时间百分比,每周所消耗的冰淇凌公升数

由于没有样本分类的特征值。很难看到任何有用的数据模型信息。一般用采用色彩或其他记号标记不同分类

而展现的数据图如下:
在这里插入图片描述
但是如果标识了三个不同的样本分类区域,采用第1.2列效果会更直观

在这里插入图片描述
对数据进行归一化处理:
如果我们对数据样本直接进行计算距离。(0-67)^2 + (20000-32000)^2 + (1.1-0.1)^2数字最大属性对结果影响很大。

也就是说飞行常客对于结果的影响远远大于其他特征。但是我们应该认为这3个属性同等重要。所i有需要数值归一化我们可以将任意值转化到

0到1之间 newValue = (oldValue-min)/(max-min)

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals,(m,1))
    normDataSet = normDataSet/tile(ranges,(m,1))
    return normDataSet,ranges,minVals

原来的数据为
array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01],
[ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00],
[ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01],
...,
[ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01],
[ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01],
[ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]])
进过处理后的数据为

array([[ 0.44832535, 0.39805139, 0.56233353],
[ 0.15873259, 0.34195467, 0.98724416],
[ 0.28542943, 0.06892523, 0.47449629],
...,
[ 0.29115949, 0.50910294, 0.51079493],
[ 0.52711097, 0.43665451, 0.4290048 ],
[ 0.47940793, 0.3768091 , 0.78571804]])
使用完成程序测试分类
机器学习的重要工作就是评估算法的正确率,通常按照上述的算法如果正确分类,那么可以使用这个软件来处理约会网站提供的约会名单了。

但是还需要测试当前分类器的性能,我们以测试错误率为主对这个程序测试程序如下:

def datingClassTest():
    hoRation = 0.10
    datingDataMat,datingLabels = file2matrix('E:\pythonProject\ML\datingTestSet2.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRation)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print ("the classifier came back with %d,the real answer is %d"
               %(classifierResult,datingLabels[i]))
        if(classifierResult!=datingLabels[i]):errorCount+=1.0
    print "the total error rate is %f" % (errorCount/float(numTestVecs))

5 算法总结

简单来说: 通过距离度量来计算查询点(query point)与每个训练数据点的距离,然后选出与查询点(query point)相近的K个最邻点(K nearest neighbors),使用分类决策来选出对应的标签来作为该查询点的标签。

NN 三要素

K, K的取值
对查询点标签影响显著(效果拔群)。k值小的时候 近似误差小,估计误差大。 k值大 近似误差大,估计误差小。
如果选择较小的 k 值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差(approximation error)会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。但缺点是“学习”的估计误差(estimation error)会增大,预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说,k 值的减小就意味着整体模型变得复杂,容易发生过拟合。
如果选择较大的 k 值,就相当于用较大的邻域中的训练实例进行预测。其优点是可以减少学习的估计误差。但缺点是学习的近似误差会增大。这时与输入实例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误。 k 值的增大就意味着整体的模型变得简单。
太大太小都不太好,可以用交叉验证(cross validation)来选取适合的k值。
近似误差和估计误差,请看这里: https://www.zhihu.com/question/60793482
距离度量 Metric/Distance Measure
距离度量 通常为 欧式距离(Euclidean distance),还可以是 Minkowski 距离 或者 曼哈顿距离。也可以是 地理空间中的一些距离公式。(更多细节可以参看 sklearn 中 valid_metric 部分)
分类决策 (decision rule)
分类决策 在 分类问题中 通常为通过少数服从多数 来选取票数最多的标签,在回归问题中通常为 K个最邻点的标签的平均值。
算法: (sklearn 上有三种)

Brute Force 暴力计算/线性扫描
KD Tree 使用二叉树根据数据维度来平分参数空间。
Ball Tree 使用一系列的超球体来平分训练数据集。
树结构的算法都有建树和查询两个过程。Brute Force 没有建树的过程。
算法特点:
优点: High Accuracy, No Assumption on data, not sensitive to outliers
缺点: 时间和空间复杂度 高
适用范围: continuous values and nominal values
相似同源产物:
radius neighbors 根据制定的半径来找寻邻点
影响算法因素:
N 数据集样本数量(number of samples), D 数据维度 (number of features)
总消耗:
Brute Force: O[DN^2]
此处考虑的是最蠢的方法: 把所有训练的点之间的距离都算一遍。当然有更快的实现方式, 比如 O(ND + kN) 和 O(NDK) , 最快的是 O[DN] 。感兴趣的可以阅读这个链接: k-NN computational complexity
KD Tree: O[DN log(N)]
Ball Tree: O[DN log(N)] 跟 KD Tree 处于相同的数量级,虽然建树时间会比 KD Tree 久一点,但是在高结构的数据,甚至是高纬度的数据中,查询速度有很大的提升。
查询所需消耗:
Brute Force: O[DN]
KD Tree: 当维度比较小的时候, 比如 D<20, O[Dlog(N)] 。相反,将会趋向于 O[DN]
Ball Tree: O[Dlog(N)]
当数据集比较小的时候,比如 N<30的时候,Brute Force 更有优势。
Intrinsic Dimensionality(本征维数) 和 Sparsity(稀疏度)
数据的 intrinsic dimensionality 是指数据所在的流形的维数 d < D , 在参数空间可以是线性或非线性的。稀疏度指的是数据填充参数空间的程度(这与“稀疏”矩阵中使用的概念不同, 数据矩阵可能没有零项, 但是从这个意义上来讲,它的结构 仍然是 "稀疏" 的)。
Brute Force 的查询时间不受影响。
对于 KD Tree 和 Ball Tree的查询时间, 较小本征维数且更稀疏的数据集的查询时间更快。KD Tree 的改善由于通过坐标轴来平分参数空间的自身特性 没有Ball Tree 显著。
k的取值 (k 个邻点)
Brute Force 的查询时间基本不受影响。
但是对于 KD Tree 和 Ball Tree , k越大,查询时间越慢。
k 在N的占比较大的时候,使用 Brute Force 比较好。
Number of Query Points (查询点数量, 即测试数据的数量)
查询点较少的时候用Brute Force。查询点较多的时候可以使用树结构算法。
关于 sklearn 中模型的一些额外干货:
如果KD Tree,Ball Tree 和Brute Force 应用场景傻傻分不清楚,可以直接使用 含有algorithm='auto'的模组。 algorithm='auto' 自动为您选择最优算法。 有 regressor 和 classifier 可以来选择。
metric/distance measure 可以选择。 另外距离 可以通过weight 来加权。
leaf size 对KD Tree 和 Ball Tree 的影响
建树时间: leaf size 比较大的时候,建树时间也就快点。
查询时间: leaf size 太大太小都不太好。如果leaf size 趋向于 N(训练数据的样本数量),算法其实就是 brute force了。如果leaf size 太小了,趋向于1,那查询的时候 遍历树的时间就会大大增加。leaf size 建议的数值是 30,也就是默认值。
内存: leaf size 变大,存树结构的内存变小。
Nearest Centroid Classifier
分类决策是哪个标签的质心与测试点最近,就选哪个标签。
该模型假设在所有维度中方差相同。 是一个很好的base line。
进阶版: Nearest Shrunken Centroid
可以通过shrink_threshold来设置。
作用: 可以移除某些影响分类的特征,例如移除噪音特征的影响。

6 KNN算法的优缺点

KNN算法的优点:算法简单易于实现,而且通过对K的选择可具备丢噪音数据的健壮性,我们来举一个例子,下图中鼠标未知是什么类型的样本?其实我们一看就应该认为它是红三角的,因为它距离红三角比较近,但是这有一个问题就是,假如我们设置K=1,那么此时的未知样本就被认为是蓝方块。这就是蓝色方块噪音对我们的影响。所以我们可以通过增加K,来达到去除噪音的作用,我们可以设置K=4,那么算法就会认为未知样本是红三角了,这就是增加K达到去噪的作用,这是KNN算法的优点。

在这里插入图片描述

未知未知样本是什么?KNN算法的缺点:首先算法需要大量的空间存储,算法复杂性高,需要将未知实例和所有的已知实例进行比较,当一类样本数量过大占主导地位的时候,K也设为很大的时候,新的未知实例容易被数量大的样本主导,但有可能这个未知实例并不接近这个最多的样本类别,它可能更接近其它类别,但这个问题也是可以解决的,为了解决这个问题,根据样本设置权重,比如权重为1/d(d为距离),距离越近权重越大。

7 图像分类应用

在这里插入图片描述
如何计算:
在这里插入图片描述
结果:利用K-近邻(距离为矩阵差异值累加和),不理想。
在这里插入图片描述

import numpy as np

class NearestNeighbor(object):
  def __init__(self):
    pass

  def train(self, X, y):
    #X是NXD的数组,其中每一行代表一个样本,Y是N行的一维数组,对应X的标签
    # 最近邻分类器就是简单的记住所有的数据
    self.Xtr = X
    self.ytr = y

  def predict(self, X):
    #X是NXD的数组,其中每一行代表一个图片样本
    #看一下测试数据有多少行
    num_test = X.shape[0]
    # 确认输出的结果类型符合输入的类型
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # 循环每一行,也就是每一个样本
    for i in xrange(num_test):
      # 找到和第i个测试图片距离最近的训练图片
      # 计算他们的L1距离
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
      min_index = np.argmin(distances) # 拿到最小那个距离的索引
      Ypred[i] = self.ytr[min_index] # 预测样本的标签,其实就是跟他最近的训练数据样本的标签
    return Ypred

上一个是用L1:曼哈顿距离,绝对值里两个样本相减, L2:计算欧氏距离
在这里插入图片描述

distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 92 5 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
这把我C
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区