机器学习——【3】KNN算法理论及实例

1. 从案例中说起

一个有关电影分类的例子:

Image.png

这是一个根据打斗次数和接吻次数作为特征来进行类型的分类。最后一条的记录就是待分类的数据。

KNN这个分类过程比较简单的一个原因是它不需要创建模型,也不需要进行训练,并且非常容易理解。把例子中打斗次数和接吻次数看成是x轴和y轴,那么就很容易建立一个二维坐标,每条记录都是坐标中的点。对于未知点来说,寻找其最近的几个点,哪种分类数较多,未知点就属于哪一类。

2. 算法说明

KNN(K Near Neighbor):k个最近的邻居,即每个样本都可以用它最接近的k个邻居来代表。

KNN算法的思想:
KNN是一种非参数机器学习算法(机器学习中通过模型训练而学到的是模型参数,而要人工调整的是超参数,请注意避免混淆)。使用KNN首先要有一个已知的数据集D,数据集内对于任意一个未知标签的样本数据x,可以通过计算x与D中所有样本点的距离,取出与x距离最近的前k个已知数据,用该k个已知数据的标签对x进行投票,哪一类票数最多,x就是哪一类,这是KNN的大概思想。

1.超参数:是模型运行前必须要决定的参数,例如k近邻算法中的k值和距离;
2.确定超参数一般使用的方法:领域知识;经验数值;实验探索。

注意:通常 K 的取值比较小,不会超过 20。

算法的一般步骤为:

  • 计算测试数据与各个训练数据之间的距离;
  • 按值从小到大排序;
  • 确定k值,选取前k个点;
  • 确定前k个点所在类别出现的频率;
  • 返回前k个点中出现频率最高的类别作为测试数据的预测分类。

什么情况下使用KNN算法?

KNN算法既可以用于分类也可以用于回归预测。然而,业内主要用于分类问题。在评估一个算法时,我们通常从以下三个角度出发:

  • 模型解释性;
  • 运算时间;
  • 预测能力;
3. 距离的衡量方法

KNN算法中最为关键的就是距离的计算。一般而言,定义一个距离函数d(x,y),需要满足下面几个准则:
1)同一性: d(x,x) = 0 // 到自己的距离为0
2) 非负性:d(x,y) >= 0 // 距离非负
3) 对称性:d(x,y) = d(y,x) //如果 A 到 B 距离是 a,那么 B 到 A 的距离也应该是 a
4) 直递性:d(x,k)+d(k,y) >= d(x,y) // 三角形法则: (两边之和大于第三边)

距离的计算有很多方法,大致分为离散型特征值计算方法和连续型特征值计算方法两类。下面我们介绍常用的计算连续型数据相似度的度量方法。

闵可夫斯基距离
闵氏距离有时也指时空间隔;

设n维空间中有两点坐标x, y,p为常数,数值点P和Q的坐标为 P=(x1,x2,…,xu)和Q=(y1,y2,…,yu),则闵式距离定义为:
在这里插入图片描述
注:u为x和y的下标变量,u=1,2,…n;

【1】当p=1时,该距离是曼哈顿距离(Manhattan distance);

【2】当p=2时,该距离是欧几里得距离(Euclidean distance),即常用的欧式距离;

【3】当p趋近于无穷大时,该距离转化成了切比雪夫距离(Chebyshev distance);
在这里插入图片描述

4. K 值的选择

K值的选择会影响结果,有一个经典的图如下:

在这里插入图片描述

图中的数据集是良好的数据,即都打好了 label ,一类是蓝色的正方形,一类是红色的三角形,那个绿色的圆形是待分类的数据。

  • k= 3 时,范围内红色三角形多,这个待分类点属于红色三角形。
  • k= 5 时,范围内蓝色正方形多,这个待分类点属于蓝色正方形。

归纳:
K太大:导致分类模糊;
K太小:受个例影响,波动较大;

如果选择较小的K值,就相当于用较小的邻域中的训练实例进行预测,学习的近似误差会减小,只有与输入实例较近的训练数据才会对预测结果起作用,但缺点是学习的估计误差会增大,预测结果会对近邻的实例点分成敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说,K值减小就意味着整体模型变复杂,分的不清楚,就容易发生过拟合。
如果选择较大K值,就相当于用较大邻域中的训练实例进行预测,其优点是可以减少学习的估计误差,但近似误差会增大,也就是对输入实例预测不准确,K值的增大就意味着整体模型变的简单。

了解:
近似误差:可以理解为对现有训练集的训练误差。
估计误差:可以理解为对测试集的测试误差。

近似误差关注训练集,如果k值小了会出现过拟合的现象,对现有的训练集能有很好的预测,但是对未知的测试样本将会出现较大偏差的预测。模型本身不是最接近最佳模型。
估计误差关注测试集,估计误差小了说明对未知数据的预测能力好。模型本身最接近最佳模型。

统计学习方法中参数选择一般是要在偏差(Bias)与方差(Variance)之间取得一个平衡(Tradeoff)。对kNN而言,k值的选择也要在偏差与方差之间取得平衡。若k取值很小,如k=1,则分类结果容易因为噪声点的干扰而出现错误,此时方差较大;若k取值很大,如k=N(N为训练集的样本数),则对所有测试样本而言,结果都一样,分类的结果都是样本最多的类别,这样稳定是稳定了,但预测结果与真实值相差太远,偏差过大。因此,k值既不能取太大也不能取太小,通常的做法是,利用交叉验证( Cross Validation)评估一系列不同的k值,选取结果最好的k值作为训练参数。

在应用中,K值一般取一个比较小的数值(K通常小于20),通常采用交叉验证法来选取最优的K值。

5. KNN 的优缺点

优点:

1.简单,易于理解,易于实现;

2.只需保存训练样本和标记,无需估计参数,无需训练;

3.不易受小错误概率的影响。经理论证明,最近邻的渐进错误率最坏时不超过两倍的贝叶斯错误率,最好时接近或达到贝叶斯错误率;

4.由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属的类别,因此对于类域的交叉或重叠较多的待分类样本集来说,KNN方法较其他方法更为适合;

5.该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量比较小的类域采用这种算法比较容易产生误分类情况。

缺点:

1.K的选择不固定;

2.样本不平衡的时候,对稀有类别的预测准确率低;

2.预测结果容易受含噪声数据的影响;

3.是慵懒散学习方法,基本上不学习,导致预测时速度比起逻辑回归之类的算法慢;

4.特征数非常多的时候,具有较高的计算复杂度和内存消耗,因为对每一个待分类的文本,都要计算它到全体已知样本的距离,才能求得它的K个最近邻。

6.KNN算法的改进策略

6.1 从降低计算复杂度的角度

当样本容量较大以及特征属性较多的时候,KNN算法分类的效率就将大大地降低。可以采用的改进方法如下。

(1)进行特征选择。使用KNN算法之前对特征属性进行约简,删除那些对分类结果影响较小(或不重要)的特征,则可以加快KNN算法的分类速度。

(2)缩小训练样本集的大小。在原有训练集中删除与分类相关性不大的样本。

(3)通过聚类,将聚类所产生的中心点作为新的训练样本。

6.2 从优化相似性度量方法的角度

很多KNN算法基于欧几里得距离来计算样本的相似度,但这种方法对噪声特征非常敏感,为了改变传统KNN算法中特征作用相同的缺点,可以在度量相似度距离公式中给特征赋予不同权重,特征的权重一般根据各个特征在分类中的作用而设定,计算权重的方法有很多,例如信息增益的方法。另外,还可以针对不同的特征类型,采用不同的相似度度量公式,更好地反映样本间的相似性。

6.3 从优化判决策略的角度

传统的KNN算法的决策规则存在的缺点是,当样本分布不均匀(训练样本各类别之间数目不均衡,或者即使基本数目接近,但其所占区域大小不同)时,只按照前k个近邻顺序而不考虑它们的距离会造成分类不准确,采取的方法很多,例如可以采用均匀化样本分布密度的方法加以改进。

6.4 从选取恰当K值的角度

由于KNN算法中的大部分计算都发生在分类阶段,而且分类效果很大程度上依赖于K值的选取,到目前为止,没有成熟的方法和理论指导k值的选择,大多数情况下需要通过反复试验来调整K值的选择。

7. 实例演示

随便创建的例子,大家参考一下,感受一下KNN算法的流程。

# coding=utf-8
"""
@Project: 课堂小练习
@Author: 王瑞
@File: KNN1.py
@IDE: PyCharm
@Time: 2020-10-21 20:39:20
"""
import numpy as np
# 给出训练数据以及对应的类别
def createDataSet():
    group = np.array([[1.0, 2.0], [1.2, 0.1], [0.1, 1.4], [0.3, 3.5]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

# 通过KNN进行分类
def classify(input,dataSet,labels,k):
    datasize = dataSet.shape[0]
    # 计算欧式距离
    dis = np.zeros(datasize, dtype=float)
    for i in range(datasize):
        dis[i] = np.linalg.norm((input - dataSet[i])*(input - dataSet[i]).T)

    # 对距离排序
    sortedindex = np.argsort(dis)

    # 累计label次数
    classcount = {
    
    }
    for i in range(k):
        vote = labels[sortedindex[i]]
        classcount[vote] = classcount.get(vote, 0)+1

    # 对map的value排序
    sortedclass = sorted(classcount.items(), key=lambda x: (x[1]), reverse=True)
    return sortedclass[0][0]


dataSet, labels = createDataSet()
input = np.array([1.1, 0.3])
k = 3
output = classify(input, dataSet, labels, k)
print(f"测试数据为:{input},分类结果为:{output}")

output:

D:\Python\Python37\python.exe E:/PyDataFile/课堂小练习/KNN1.py
测试数据为:[1.1 0.3],分类结果为:A
Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/qq_46009608/article/details/109189728
今日推荐