KNN和K-means算法

一、KNN算法

1.基本概念

介绍: KNN算法也称为K-最邻近算法,是将数据挖掘分类技术的一种最简单的方法之一。K最邻近含义是K个最邻近的邻居,每个样本可以用它K个邻居来代表。KNN是一种分类算法,它基于实例的学习,属于懒惰学习,即KNN没有显示学习过程(没有训练阶段)。数据集原先已有分类和特征值,测试样本靠规定的K个最邻近邻居的特征来确定它的特征。

算法思路: 在一个特征空间中,待测试样本属性和它周围的K个邻近数据中大多数所拥有的属性一致。它周围K个数据是已知属性的数据,已经归于某一类别。K值由自己需求决定,K值不同,待测样本的类别可能会有所不同。

直观解释:
在这里插入图片描述
如图所示,待测样本数据(绿)的类别由周围K个邻居类别来确定。
当K = 1时,它最邻近的1个邻居是红色正方形,它是属于class2,因此待测样本也属于class2。
当K = 5时,它最邻近的5个邻居时2个红色正方形和3个蓝色三角形,由于蓝色三角形数目多,蓝色三角形属于class1,因此待测样本也属于class1.

2.算法步骤

(1)先确定一个k值;
(2)再求出训练数据与待测数据之间距离;
(3)从小到大依次排序,选取前k个数据;
(4)统计这k个数据中数据所属类别;
(5)找到出现次数最多的类别,作为待测数据的预测分类。

3.距离的运算方法

距离是指两个数据点之间的直线距离。
距离公式:
1.闵可夫斯基距离
2.欧几里得距离
3.曼哈顿距离
4.切比雪夫距离
5.马氏距离
6.余弦相似度
7.皮尔逊相关系数
8.汉明距离
9.杰卡德相似距离
10.编辑距离
最常用的距离公式为欧几里得距离
d = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d=\sqrt{(x_{1}-x_{2})^{2}+(y_{1}-y_{2})^{2}} d=(x1x2)2+(y1y2)2

4.K值的选取

K为邻近数,即以K个邻近点来预测待测数据

关于K值选取的一些问题:

1.K 值的选择会对算法的结果产生重大影响。

2.K 值较小意味着只有与测试数据较近的训练实例才会对预测结果起作用,容易发生过拟合。
如果 K 值较大,优点是可以减少学习的估计误差,但缺点是学习的近似误差增大,这时与测试数据较远的训练实例也会对预测起作用,使预测发生错误。

3.在实际应用中,K 值一般选择一个较小的数值,通常采用交叉验证的方法来选择最优的 K 值。随着训练实例数目趋向于无穷和 K=1 时,误差率不会超过贝叶斯误差率的 2 倍,如果 K 也趋向于无穷,则误差率趋向于贝叶斯误差率。(贝叶斯误差可以理解为最小误差)

三种交叉验证方法:

1.Hold-Out: 随机从最初的样本中选出部分,形成交叉验证数据,而剩余的就当做训练数据。 一般来说,少于原本样本三分之一的数据被选做验证数据。常识来说,Holdout 验证并非一种交叉验证,因为数据并没有交叉使用。

2.K-foldcross-validation:K 折交叉验证,初始采样分割成 K 个子样本,一个单独的子样本被保留作为验证模型的数据,其他 K-1 个样本用来训练。交叉验证重复 K 次,每个子样本验证一次,平均 K 次的结果或者使用其它结合方式,最终得到一个单一估测。这个方法的优势在于,同时重复运用随机产生的子样本进行训练和验证,每次的结果验证一次,10 折交叉验证是最常用的。

3.Leave-One-Out Cross Validation:正如名称所建议, 留一验证 (LOOCV) 意指只使用原本样本中的一项来当做验证资料, 而剩余的则留下来当做训练资料。 这个步骤一直持续到每个样本都被当做一次验证资料。 事实上,这等同于 K-fold 交叉验证是一样的,其中 K 为原本样本个数。

5.决策分类方法

明确 K 个邻居中所有数据类别的个数,将测试数据划分给个数最多的那一类。即由输入实例的 K 个最临近的训练实例中的多数类决定输入实例的类别。
最常用的两种决策规则:

1.多数表决法:多数表决法和我们日常生活中的投票表决是一样的,少数服从多数,是最常用的一种方法。
2.加权表决法:有些情况下会使用到加权表决法,比如投票的时候裁判投票的权重更大,而一般人的权重较小。所以在数据之间有权重的情况下,一般采用加权表决法

6.代码实现

K = 4

import matplotlib.pyplot as plt
import matplotlib
from math import sqrt
##### 初始化数据集 #####
data_A = [[1,2],[3.2,4],[4,7],[5.2,3],[7,4.1]]#数据集 A
data_B = [[2.2,5.5],[4.2,2],[5,5],[6.3,7]]#数据集 B
test_data = [[4.5,4.5]]#测试集
len_A = len(data_A)
len_B = len(data_B)
##### 计算距离并排序 #####
distance_A = []#与 A 类数据之间的距离
distance_B = []#与 B 类数据之间的距离
distance = []#全部距离
#计算距离(使用欧氏距离)
for i in range(len_A):
    d = sqrt((test_data[0][0]-data_A[i][0])**2+(test_data[0][1]-data_A[i][1])**2)
    distance_A.append(d)
for i in range(len_B):
    d = sqrt((test_data[0][0]-data_B[i][0])**2+(test_data[0][1]-data_B[i][1])**2)
    distance_B.append(d)
#由小到大排序(此处使用冒泡排序)
distance = distance_A + distance_B
for i in range(len(distance)-1):
    for j in range(len(distance)-i-1):
        if distance[j] > distance[j+1]:
            distance[j],distance[j+1]=distance[j+1],distance[j]
print("距离所有A类数据的距离为:")
print(distance_A)
print("距离所有B类数据的距离为:")
print(distance_B)
print()
print("对所有的距离升序排序:")
print(distance)
print()
##### 按 K 最近领对测试集进行分类 #####
K = 5#这里默认 K 值为 5,也可以自行更改
number_A = 0
number_B = 0
#定义删除函数,避免对同一个数据重复计算
def delete(a,b,ls):
    for i in range(b):
        if ls[i]==a:
            ls.pop(i)
            break
#找出与测试数据最接近的 K 个点
for i in range(K):
    if distance[i] in distance_A:
        number_A += 1
        delete(distance[i],len(distance_A),distance_A)
        continue
    if distance[i] in distance_B:
        number_B += 1
        delete(distance[i],len(distance_B),distance_B)
        continue
print("最终结果:")
print("距离待测数据最近的K={:}个数据中,A类数据有{:}个,B类数据有{:}个".format(K,number_A,number_B))
if number_A > number_B:
    print("所以K={:}时,待测数据划分为A类".format(K))
else:
    print("所以K={:}时,待测数据划分为B类".format(K))
##### 画图 #####
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
for i in range(len_A):#A 类,用红色三角形表示
    if i!=len_A-1:
        plt.plot(data_A[i][0],data_A[i][1],'bo',marker='^',color='red')
    else:
        plt.plot(data_A[i][0],data_A[i][1],'bo',marker='^',label='A',color='r')
    #使用 if..else... 是为了避免在图形中重复出现多个标签
for i in range(len_B):#B 类,用蓝色正方形表示
    if i!=len_B-1:
        plt.plot(data_B[i][0],data_B[i][1],'bo',marker='s',color='blue')
    else:
        plt.plot(data_B[i][0],data_B[i][1],'bo',marker='s',label='B',color='b')
plt.plot(test_data[0][0],test_data[0][1],'bo',label='待测数据',color='g')#测试集
plt.xlim(0,10)
plt.ylim(0,10)
plt.legend()
plt.show()

输出结果:距离所有 A 类数据的距离为:[4.301162633521313, 1.3928388277184118, 2.5495097567963922, 1.6552945357246849, 2.5317977802344327]距离所有 B 类数据的距离为:[2.5079872407968904, 2.5179356624028344, 0.7071067811865476, 3.080584360149872]对所有的距离升序排序:[0.7071067811865476, 1.3928388277184118, 1.6552945357246849, 2.5079872407968904, 2.5179356624028344, 2.5317977802344327, 2.5495097567963922, 3.080584360149872, 4.301162633521313]​最终结果:距离待测数据最近的 K = 5 K=5 K=5 个数据中,A 类数据有 2 个,B 类数据有 3 个所以 K = 5 K=5 K=5 时,待测数据划分为 B 类

二、K-means算法

1.基本概念

介绍: K-means算法也称为K-均值算法,它是一种聚类算法,而KNN则属于一种分类算法。聚类算法和分类算法不同之处是,分类算法的目标类别已知(属于监督学习),而聚类算法的目标类别未知(属于无监督学习)。K-means算法作为无监督算法之一,主要用于样本的聚类。
算法思想: 目的:使每一个簇尽可能聚拢,达到聚类的效果。
方法: 对于给定的训练样本集,根据聚类中心与训练样本之间的距离划分为K个簇(K由自己需求决定,簇是指一个数据团,包含n个相同类别样本),再将聚类中心算作所属簇中的一个样本,利用平均值求出新的簇聚类中心,直到让聚类中心与样本距离的误差小于理想值为止。

2.算法步骤

(1)先确定一个数据集;
(2)将数据集分为K个簇,每个簇的中心记作x1,x2,x3,…,xn;
(3)求出各个样本与K个簇中心距离,并且将样本归为距离最近簇中心所在的簇;
(4)利用方差公式求出簇中心与簇中样本距离的误差,若小于理想误差,则结束循环,找到理想簇中心;若大于理想误差,则利用平均值求出新的簇中心坐标,返回执行步骤(2),直至小于理想误差为止。

3.图像展示

假设 K=2,即有两个簇,绿色为最初的样本数据集(图 a),红色标记和蓝色标记分别为两个质心(图 b)。通过计算样本到红色质心和蓝色质心的距离,实现对样本的分类,然后再不断地更新质心的位置,最终得到了一个比较理想的聚类结果(图 f)。
在这里插入图片描述

顺序为:a→b→c→d→e→f
可以看到,整个算法是一个不断更新质心和簇的过程。

4.代码实现

# 本文作者: hang shun @航 順
# 本文链接: https://jiang-hs.gitee.io/posts/2afaae3d/
# 版权声明: 本站所有文章除特别声明外,均采用 (CC)BY-NC-SA 许可协议。转载请注明出处!

import matplotlib.pyplot as plt
from random import uniform
from math import sqrt
#创建一个数据集。
#注意:本方法创建的数据集每次运行结果都不相同
m = 60 #数据个数
data = [[],[]] #[[存储 x 轴数据],[存储 y 轴数据]]
for i in range(m):
    if i < m/3: 
        data[0].append(uniform(1,5))#随机设定
        data[1].append(uniform(1,5))
    elif i < 2*m/3:
        data[0].append(uniform(6,10))
        data[1].append(uniform(1,5))
    else:
        data[0].append(uniform(3,8))
        data[1].append(uniform(5,10))
#将创建的数据集画成散点图
plt.scatter(data[0],data[1])
plt.xlim(0,11)
plt.ylim(0,11)
plt.show()
#定义欧几里得距离
def distEuclid(x1,y1,x2,y2):
    d = sqrt((x1-x2)**2+(y1-y2)**2)
    return d
cent0 = [uniform(2,9),uniform(2,9)] #定义 K=3 个质心,随机赋值
cent1 = [uniform(2,9),uniform(2,9)] #[x,y]
cent2 = [uniform(2,9),uniform(2,9)]
mark = [] #标记列表
dist = [[],[],[]]#各质心到所有点的距离列表
#核心
for n in range(50):
    #计算各质心到所有点的距离
    for i in range(m):
        dist[0].append(distEuclid(cent0[0],cent0[1],data[0][i],data[1][i]))
        dist[1].append(distEuclid(cent1[0],cent1[1],data[0][i],data[1][i]))
        dist[2].append(distEuclid(cent2[0],cent2[1],data[0][i],data[1][i]))
    #对数据进行整理
    sum0_x = sum0_y = sum1_x = sum1_y = sum2_x = sum2_y = 0
    number0 = number1 = number2 = 0
    for i in range(m):
        if dist[0][i]<dist[1][i] and dist[0][i]<dist[2][i]:
            mark.append(0)
            sum0_x += data[0][i]
            sum0_y += data[1][i]
            number0 += 1
        elif dist[1][i]<dist[0][i] and dist[1][i]<dist[2][i]:
            mark.append(1)
            sum1_x += data[0][i]
            sum1_y += data[1][i]
            number1 += 1
        elif dist[2][i]<dist[0][i] and dist[2][i]<dist[1][i]:
            mark.append(2)
            sum2_x += data[0][i]
            sum2_y += data[1][i]
            number2 += 1    
    #更新质心
    cent0 = [sum0_x/number0,sum0_y/number0]
    cent1 = [sum1_x/number1,sum1_y/number1]
    cent2 = [sum2_x/number2,sum2_y/number2]
#画图
for i in range(m):
    if mark[i] == 0:
        plt.scatter(data[0][i],data[1][i],color='red')
    if mark[i] == 1:
        plt.scatter(data[0][i],data[1][i],color='blue')
    if mark[i] == 2:
        plt.scatter(data[0][i],data[1][i],color='green')     
plt.scatter(cent0[0],cent0[1],marker='*',color='red')
plt.scatter(cent1[0],cent1[1],marker='*',color='blue')
plt.scatter(cent2[0],cent2[1],marker='*',color='green')
plt.xlim(0,11)
plt.ylim(0,11)
plt.show()

5.K-means算法存在问题

由于 K-means 算法简单且易于实现,因此 K-means 算法得到了很多的应用,但是从 K-means 算法的过程中可以发现两个问题:

  1. 簇中心的个数 K 是需要事先给定的,对事先比较了解的数据集可以很好地进行分类,但在处理未知数据时无法确定 K 的值为多少时更合适,就无从下手或者只能盲目尝试。
    2.K-means 算法在聚类之前,需要随机初始化 K 个质心,如果质心选择不好,如上面的图形所示,最后的聚类结果可能会比较差。
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值