想学机器学习?先看这一篇——K邻近算法(含实际应用 + 代码)

基于作业要求,接触了K邻近算法,很快完成相关的作业,感觉有趣,再进行一些深入的研究。代码量不大,操作简单,大家都可以试一试。

目录

算法简单介绍

引入


  1. 算法简单介绍

    1. 引入

      相信大家都看过电影,也都了解一般的电影都会有一个简单的分类。例如:爱情片,动作片,悬疑片等等。这样我们可以提出一些小小的疑问,这些电影的类型的评价标准又会是什么呢。以爱情片与动作片为例,我们不难想到,以亲吻和打戏作为一种在动作片与爱情片之间区分的评价标准或许是可行的。下面是一个简单的例子。

      电影名称

      打斗镜头

      接吻镜头

      电影类型

      California Man

      3

      104

      爱情片

      He’s Not Really into Dudes

      2

      100

      爱情片

      Beautiful Woman

      1

      81

      爱情片

      Kevin Longblade

      101

      10

      动作片

      Robo Slayer 3000

      99

      5

      动作片

      Amped II

      98

      2

      动作片

      ?

      18

      90

      未知

      我们很容易的发现,动作片中的打斗镜头数量较多,爱情片中的接吻镜头较多,而?组我们不难发现,打斗镜头不算多,接吻镜头也不少,所以,我们如何推测这部电影的类型呢?
    2. 算法原理

      枯燥的要来力——先看定义:
      1. 定义

        存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前 k 个最相似的数据,这就是 K 近邻算法中 k的出处,通常 k是不大于 20 的整数。最后,选择 k个最相似数据中出现次数最多的分类,作为新数据的分类。

      2. 说人话

        yysy,这个概念还是比较好理解和应用的,我稍稍用一句话归纳一下就是:测量不同特征值之间的距离方法进行分类。所以回到刚才的问题,我们怎么判定最后一个电影的类型呢?很简单,我们需要——计算。

        电影名称

        与未知电影的距离

        California Man

        20.5

        He’s Not Really into Dudes

        18.7

        Beautiful Woman

        19.2

        Kevin Longblade

        115.3

        Robo Slayer 3000

        117.4

        Amped II

        118.9

        通过距离的比较,我们能够发现,?电影与He电影在距离上相聚最近,与Ca和Be两电影也相差不大,所以,我们取前三位,很幸运,这三部电影都是爱情片,那么,我们能够比较自信的得出结论。?大概率是一部爱情片。
    3. 流程归纳

      1. 收集数据:可以使用任何方法。
      2. 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
      3. 分析数据:可以使用任何方法。
      4. 训练算法:此步骤不适用于 K 近邻算法。
      5. 测试算法:计算错误率。
      6. 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行K 近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
    4. 代码实现——手写数字识别

      1. 使用python库载入数据

        import numpy as np
        
        def createDataSet():
            group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
            labels = ['A', 'A', 'B', 'B']
            return group, labels
        print('group:', group)
        print('labels:', labels)  # 输出数值
      2. 测试分类器

        在上文中我们提到使用K近邻算法能够判断出一个电影是动作片还是爱情片,即我们使用 K 近邻 算法能够实现一个分类器。我们需要检验分类器给出的答案是否符合我们的预期。大家可能会问:「分类器何种情况下会出错?」或者「答案是否总是正确的?」

        答案是否定的,分类器并不会得到百分百正确的结果,我们可以使用多种方法检测分类器的正确率。此外分类器的性能也会受到多种因素的影响,如分类器设置和数据集等。不同的算法在不同数据集上的表现可能完全不同。

        为了测试分类器的效果,我们可以使用已知答案的数据,当然答案不能告诉分类器,检验分类器给出的结果是否符合预期结果。通过大量的测试数据,我们可以得到分类器的错误率:分类器给出错误结果的次数除以测试执行的总数。

      3. 开发准备

        1. http://labfile.oss.aliyuncs.com/courses/777/digits.zip​。利用该链接进行文件的下载。
        2. trainingDigits:训练数据,1934 个文件,每个数字大约 200 个文件。

          testDigits:测试数据,946 个文件,每个数字大约 100 个文件。

        3. 我们使用目录 trainingDigits 中的数据训练分类器,使用目录 testDigits 中的数据测试分类器的效果。两组数据没有重叠,你可以检查一下这些文件夹的文件是否符合要求。根据这些数据我们开始实现 K 近邻算法。

      4. 准备数据:将图像转换为测试向量

        我们首先编写一段函数 img2vector,将图像转换为向量:该函数创建 1x1024 的 NumPy 数组,然后打开给定的文件,循环读出文件的前 32 行,并将每行的头 32 个字符值存储在 NumPy 数组中,最后返回数组。
        def img2vector(filename):
            # 创建向量
            returnVect = np.zeros((1, 1024))
            # 打开数据文件,读取每行内容
            fr = open(filename)
            for i in range(32):
                # 读取每一行
                lineStr = fr.readline()
                # 将每行前 32 字符转成 int 存入向量
                for j in range(32):
                    returnVect[0, 32*i+j] = int(lineStr[j])
                    
            return returnVect

        测试代码

        img2vector('digits/testDigits/0_1.txt')
      5. 分析数据

        算法过程介绍:
        1. 计算已知类别数据集中的点与当前点之间的距离;
        2. 按照距离递增次序排序;
        3. 选取与当前点距离最小的k个点;
        4. 确定前k个点所在类别的出现频率;
        5. 返回前k个点出现频率最高的类别作为当前点的预测分类。
      6. 计算距离(最为重要)

        1. 这里,使用欧氏距离公式,计算两个向量点 Xa 和Xb 之间的距离:(可推广至多维)

        2. 计算完所有点之间的距离后,可以对数据按照从小到大的次序排序。然后,确定前 k个距离最小元素所在的主要分类,输入 k总是正整数;最后,将 classCount 字典分解为元组列表,然后使用程序第二行导入运算符模块的 itemgetter 方法,按照第二个元素的次序对元组进行排序。

        3. 代码:(输出结果应该是B)

          import operator
          def classify0(inX, dataSet, labels, k):
              
              """
              参数: 
              - inX: 用于分类的输入向量
              - dataSet: 输入的训练样本集
              - labels: 样本数据的类标签向量
              - k: 用于选择最近邻居的数目
              """
              
              # 获取样本数据数量
              dataSetSize = dataSet.shape[0]
          
              # 矩阵运算,计算测试数据与每个样本数据对应数据项的差值
              diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
          
              # sqDistances 上一步骤结果平方和
              sqDiffMat = diffMat**2
              sqDistances = sqDiffMat.sum(axis=1)
          
              # 取平方根,得到距离向量
              distances = sqDistances**0.5
          
              # 按照距离从低到高排序
              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)
          
              # 返回出现频次最高的类别
              return sortedClassCount[0][0]
          
          group, labels = createDataSet()
          classify0([0, 0], group, labels, 3)
      7. 测试算法

        测试的步骤:
        1. 读取训练数据到向量(手写图片数据),从数据文件名中提取类别标签列表(每个向量对应的真实的数字)
        2. 读取测试数据到向量,从数据文件名中提取类别标签
        3. 执行K 近邻算法对测试数据进行测试,得到分类结果
        4. 与实际的类别标签进行对比,记录分类错误率
        5. 打印每个数据文件的分类数据及错误率作为最终的结果
      8. 最终代码

        from os import listdir
        
        
        def handwritingClassTest():
            # 样本数据的类标签列表
            hwLabels = []
        
            # 样本数据文件列表
            trainingFileList = listdir('digits/trainingDigits')
            m = len(trainingFileList)
        
            # 初始化样本数据矩阵(M*1024)
            trainingMat = np.zeros((m, 1024))
        
            # 依次读取所有样本数据到数据矩阵
            for i in range(m):
                # 提取文件名中的数字
                fileNameStr = trainingFileList[i]
                fileStr = fileNameStr.split('.')[0]
                classNumStr = int(fileStr.split('_')[0])
                hwLabels.append(classNumStr)
        
                # 将样本数据存入矩阵
                trainingMat[i, :] = img2vector(
                    'digits/trainingDigits/%s' % fileNameStr)
        
            # 循环读取测试数据
            testFileList = listdir('digits/testDigits')
        
            # 初始化错误率
            errorCount = 0.0
            mTest = len(testFileList)
        
            # 循环测试每个测试数据文件
            for i in range(mTest):
                # 提取文件名中的数字
                fileNameStr = testFileList[i]
                fileStr = fileNameStr.split('.')[0]
                classNumStr = int(fileStr.split('_')[0])
        
                # 提取数据向量
                vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
        
                # 对数据文件进行分类
                classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        
                # 打印 K 近邻算法分类结果和真实的分类
                print("测试样本 %d, 分类器预测: %d, 真实类别: %d" %
                      (i+1, classifierResult, classNumStr))
        
                # 判断K 近邻算法结果是否准确
                if (classifierResult != classNumStr):
                    errorCount += 1.0
        
            # 打印错误率
            print("\n错误分类计数: %d" % errorCount)
            print("\n错误分类比例: %f" % (errorCount/float(mTest)))
        
        handwritingClassTest()

        上面的代码中,将 trainingDigits目录中的文件内容存储在列表中,然后可以得到目录中有多少文件,并将其存储在变量 m 中。接着,代码创建一个 m 行 1024 列的训练矩阵,该矩阵的每行数据存储一个图像。

        我们可以从文件名中解析出分类数字。该目录下的文件按照规则命名,如文件 9_45.txt 的分类是 9,它是数字 9 的第 45 个实例。然后我们可以将类代码存储在 hwLabels 向量中,使用前面讨论的 img2vector 函数载入图像。

        在下一步中,我们对 testDigits 目录中的文件执行相似的操作,不同之处是我们并不将这个目录下的文件载入矩阵中,而是使用 classify0() 函数测试该目录下的每个文件。

        最后,我们输入 handwritingClassTest(),测试该函数的输出结果。

    5. 代码实现——约会配对系统

      这一部分是在完成作业之后自行搜索的,代码难度较大,笔者也不是很能看得懂,放上来,主要也是给大家体验一下。taozeze/kNN--Improving-dating-sites: 《机器学习实战》中的使用k-近邻算法改进约会网站的配对效果 (github.com)
    6. 结语

      K邻近算法可以算是一个机器学习最初始的一种算法,不能说难度有多大,但是以距离为标准的思想在后续的更加深刻的机器算法中也是有所体现的。看到这里,望读者通过这样一个小小的实验,对机器学习产生一些兴趣,那也就够了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值