KNN算法实例


KNN算法实例

KNN算法(K近邻算法)是一种很朴实的机器学习方法,既可以做分类,也可以做回归。分类问题是机器学习非常重要的一个组成部分,它的目标是根据已知样本的某些特征,判断一个样本属于哪个类别。

一、案例

  • 案例背景

将收集的收集的一些数据存放在文本文件datingTestSet1.txt中,每个样本数据占一行,总共有1000行。(数据前三列分别表示:豆子植物的茎长,豆子的重量和豆子的宽度。最后一列则表示豆子的种类,总共有三种,分别为黑豆、绿豆和红豆)

  • datingTestSet.txt中的个别数据:
    在这里插入图片描述

二、具体实现代码实现

1.准备数据

代码如下:

# 准备数据:从文本文件中解析数据
import numpy as np
def file2matrix(filename):
    #打开文件
    fr = open(filename)
    #读取文件所有内容
    arrayOLines = fr.readlines()
    #得到文件行数
    numberOfLines = len(arrayOLines)
    #返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
    returnMat = np.zeros((numberOfLines,3))
    #返回的分类标签向量
    classLabelVector = []
    #行的索引值
    index = 0
    for line in arrayOLines:
        #s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
        line = line.strip()
        #使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。
        listFromLine = line.split('\t')
        #特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        #标签分类
        if listFromLine[-1] == 'RedBean':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'MungBean':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'BlackBean':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector
filename = 'E:\MechineLearning\KNN\KNN\datingTestSet1.txt'
datingDataMat, datingLabels = file2matrix(filename)  
print(datingDataMat)    #归一化后的数据
print(datingLabels)     #每一行数据的标签

执行结果:
在这里插入图片描述

上面的代码读取了datingTestSet1.txt里面的数据,将datingTestSet1.txt里面值经过处理后循环遍历,前三个字段的值存储在returnMat中,然后根据最后一个字段来判断豆子的分类,并将其对应的标签值(1,2,3)添加到classLableVector中,然后返回特征矩阵returnMat和分类标签向量classLableVector,然后将datingTestSet1.txt文件路径作为参数调用file2matrix()函数,将解析得到的特征矩阵存储在datingDataMat中,将分类标签存储在datingLabels中。打印归一化后的特征矩阵datingDataMat和每一行数据的标签datingLabels。

2.分析数据:使用Matplotlib创建散点图

代码如下:

import matplotlib.pyplot as plt
import matplotlib.lines as mlines
# 数据展示
def showData(datingDataMat, datingLabels):

    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))


    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
    axs0_title_text = axs[0][0].set_title('height_weight')
    axs0_xlabel_text = axs[0][0].set_xlabel('height')
    axs0_ylabel_text = axs[0][0].set_ylabel('weight')
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 设置标题,x轴label,y轴label

    axs1_title_text = axs[0][1].set_title('height_width')
    axs1_xlabel_text = axs[0][1].set_xlabel('height')
    axs1_ylabel_text = axs[0][1].set_ylabel('width')
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

  
    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 设置标题,x轴label,y轴label
    axs2_title_text = axs[1][0].set_title('weight_width')
    axs2_xlabel_text = axs[1][0].set_xlabel('weight')
    axs2_ylabel_text = axs[1][0].set_ylabel('width')
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
    # 设置图例
    BlackBean = mlines.Line2D([], [], color='black', marker='.',
                              markersize=6, label='BlackBean')
    MungBean = mlines.Line2D([], [], color='orange', marker='.',
                               markersize=6, label='MungBean')
    RedBean = mlines.Line2D([], [], color='red', marker='.',
                               markersize=6, label='RedBean')
    # 添加图例
    axs[0][0].legend(handles=[BlackBean, MungBean, RedBean])
    axs[0][1].legend(handles=[BlackBean, MungBean, RedBean])
    axs[1][0].legend(handles=[BlackBean, MungBean, RedBean])
    # 显示图片
    plt.show()
showData(datingDataMat,datingLabels)

执行结果:
在这里插入图片描述
这段代码实现了使用Matplotlib创建散点图。具体功能:创建一个包含2行2列的子图,即总共4个子图。根据传入的数据和标签,在第一个子图中绘制豆子植物的茎长和豆子的重量的散点图。在第二个子图中绘制豆子植物的茎长和豆子的宽度的散点图。在第三个子图中绘制豆子的重量和豆子的宽度的散点图。设置每个子图的标题、x轴标签和y轴标签。根据标签的不同,将散点的颜色分别设置为黑色、橙色和红色,分别表示不同标签的散点颜色。最后显示绘制好的散点图。

3. 对数据进行归一化

归一化:在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。
在此示例中的具体代码:

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    #shape(dataSet)返回dataSet的矩阵行列数
    normDataSet = np.zeros(np.shape(dataSet))
    #返回dataSet的行数
    m = dataSet.shape[0]
    #用归一化公示进行处理
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    #返回归一化数据结果,数据范围,最小值
    return normDataSet, ranges, minVals
normDataSet, ranges, minVals = autoNorm(datingDataMat)
print(normDataSet)
print(ranges)
print(minVals)

执行结果:
在这里插入图片描述

归一化的具体实现原理可以看前面的博客https://blog.csdn.net/m0_63047033/article/details/133522587,对数据进行归一化处理后,返回归一化数据结果,数据范围,最小值。

4.测试算法:分类器的实现

import operator
# 输入:inX - 用于分类的数据(测试集);dataSet - 训练集;labes - 分类标签;K - KNN算法参数,选择距离最小的K个点
# 输出:sortedClassCount[0][0] - 分类结果
def classify0(inX, dataSet, labels, k):
    #numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #计算欧式距离
    #二维特征相减后平方
    sqDiffMat = diffMat**2
    #sum()所有元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #开方,计算出距离
    distances = sqDistances**0.5
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndices = distances.argsort()
    #定一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        #取出前k个元素的类别
        voteIlabel = labels[sortedDistIndices[i]]
        #dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
        #计算类别次数
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #返回次数最多的类别,即所要分类的类别
    return sortedClassCount[0][0]
m = normDataSet.shape[0]#返回数据集中样本的数量
numTestVecs = int(m*0.1)
errorCount = 0.0
for i in range(numTestVecs):
    classifyResult = classify0(normDataSet[i,:],normDataSet[numTestVecs:m,:],
                              datingLabels[numTestVecs:m],4)
    print("分类结果:%d,真实类别:%d" % (classifyResult,datingLabels[i]))
    if(classifyResult!=datingLabels[i]):
        errorCount += 1.0
print("错误率:%f%%" %(errorCount/float(numTestVecs)*100)) 

运行结果:
在这里插入图片描述

normDataSet.shape[0]返回数据集中样本的数量,numTestVecs = int(m*0.1)计算测试集的样本数量,因此这里采用了90%的训练集和10%的测试集的划分方式。在测试样本中找到训练集中距离该样本最近的前K个样本,统计这K个样本所属不同类别的出现次数,将出现次数最多的类别作为该测试样本的类别。在代码中我距离度量方法采用的是欧氏距离公式。距离度量方法同样可以参考之前的博客https://blog.csdn.net/m0_63047033/article/details/133522587

5.使用算法:输入数据进行测试

# 通过输入豆子的三维特征,进行分类输出
def classifyPerson():
    #输出结果
    resultList = ['黑豆','绿豆','红豆']
    #三维特征用户输入
    ffMiles = float(input("豆子植物的茎长:"))
    precentTats = float(input("豆子的重量:"))
    iceCream = float(input("豆子的宽度:"))
    #打开的文件名
    filename = "datingTestSet1.txt"
    #打开并处理数据
    datingDataMat, datingLabels = file2matrix(filename)
    #训练集归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #生成NumPy数组,测试集
    #inArr = np.array([precentTats, ffMiles, iceCream])
    inArr = np.array([ffMiles, precentTats, iceCream])
    #测试集归一化
    norminArr = (inArr - minVals) / ranges
    #返回分类结果
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)   #直接指定k为3
    #打印结果
    print("这个豆子的种类是:%s" % (resultList[classifierResult-1]))
classifyPerson()

用户输入豆子的三维特征,包括豆子植物的茎长、豆子的重量和豆子的宽度。程序会根据输入的特征值,对训练集中的豆子进行分类,并输出豆子属于哪一种类型(黑豆、绿豆和红豆),辞职我们指定的k值为3?当我输入的三个数据分别为(1600,56,2)时,执行的结果如下图:
在这里插入图片描述

三、数据的分析

在上面的案例中我们设置的k=3,错误率为4%,可以看出模型效果还不错。当我们改变k值,或者改变

  • 改变k值
  • 当我们把k值增大10倍,当k=30时,此时我们得到的错误率仍为4%,这是因为当k增大时,模型会变得过于宽松,整体模型变得简单,甚至忽略了数据中的重要信息,当k增大到数据样本个数时,因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类
  • 当我们把k减小为1或2时,此时得到的错误率仍然为4%,但是时输入不变时,输出结果变成了‘红豆’,此时可能发生了过拟合或者欠拟合问题,导致错误率一直不变。但是预测的结果却发生了变化。
  • 改变测试集和训练集的划分方式
  • 当把90%的训练集和10%的测试集的划分方式改为80%的训练集和20%的测试集的划分方式时,当输入的数据不变时,错误率增大了,变成了8%,通过不断调整**,当改变改变测试机和训练集的划分方式时,错误率也会发生改变。**

四、模型优化

  • K值的选择:KNN算法需要手动选定K值,较小的K值会容易出现过拟合问题;而较大的K值则会容易出现欠拟合问题。一般使用交叉验证等技术来确定最优的K值
  • 距离度量:KNN算法实现的关键在于距离的计算,不同的距离度量方式会影响KNN的分类效果。除了案例中的欧式距离、我们还应用曼哈顿距离(Manhanttan Distance)和切比雪夫距离(Chebyshev Distance)。在有些具体的问题中,也可以采用自定义的距离度量来优化模型

五、knn算法的优点与缺点

  • 优点
    • 简单,易于理解,易于实现,无需估计参数。
    • 对数据没有假设,准确度高,对异常点不敏感。
    • 适合对稀有事件进行分类。
    • 适合于多分类问题。
  • 缺点
    • 当特征非常多的时候,计算量太大。
    • 样本不平衡问题(有些类别的样本数量很多,而其它样本的数量很少)。
    • 对训练数据依赖度特别大,对训练数据的容错性太差。对训练数据依赖度特别大,对训练数据的容错性太差。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个简单的KNN算法实例,包括一个数据集。请注意,这个数据集只是一个示例,您可以根据自己的需求修改它。 ```python import math # 定义KNN算法 def knn(data, query, k, distance_fn, choice_fn): """ data: 训练数据,是一个列表,每个元素都是一个元组,元组的第一个元素是数据点的特征,第二个元素是数据点的标签 query: 查询数据,是一个元组,元组的第一个元素是查询点的特征 k: 选择最近邻居的数量 distance_fn: 距离函数,用于计算数据点之间的距离 choice_fn: 投票函数,用于选择最终的标签 """ # 计算查询点与每个数据点之间的距离 distances = [] for index, point in enumerate(data): distance = distance_fn(point[0], query) distances.append((distance, index)) # 按照距离排序 distances = sorted(distances) # 选择最近的k个邻居 neighbors = distances[:k] # 对邻居进行投票,选择最终的标签 labels = [data[i][1] for distance, i in neighbors] return choice_fn(labels) # 定义欧几里得距离函数 def euclidean_distance(point1, point2): """ point1: 特征向量1 point2: 特征向量2 """ distance = 0 for i in range(len(point1)): distance += pow(point1[i] - point2[i], 2) return math.sqrt(distance) # 定义投票函数,选择标签出现最多的那个 def mode(labels): """ labels: 标签列表 """ return max(set(labels), key=labels.count) # 定义一个数据集 data = [ ([1, 2, 3], 'A'), ([4, 5, 6], 'B'), ([7, 8, 9], 'C'), ([10, 11, 12], 'D') ] # 测试KNN算法 print(knn(data, [1, 2, 1], 3, euclidean_distance, mode)) # 输出 A ``` 带有数据集的KNN算法实现到这里就结束了,您可以根据自己的需求修改数据集和特征向量的维度。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值