欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

机器学习实战——第二章(分类):k-近邻算法与实例

程序员文章站 2022-03-22 23:01:16
...

前言

学了好几节李宏毅老师的机器学习视频,感觉脑袋瓜嗡嗡的,似懂非懂,没有代码实现过,而且听的时候我老被它的英文带跑了,英语确实不大好。。。。。李宏毅老师是把理论通过一些例子例举出来,我能听明白,但不知道干嘛用的,怎么去使用,而且一个这么长的假期搞得我基本忘得差不多了,想去补,但是又有心无力,害,没办法只能找点新的知识让自己先把学习兴趣提上来。《机器学习实战》我觉得讲的非常棒,会给出具体的实例与代码让大家一同实现,建议初学者先看书入门吧,比如像我这样的英语差的孩儿。。。。
进入正题吧。。

k-近邻算法概念

简单来说:采用测量不同特征值之间的距离的方法进行分类。

具体一丢丢:把需要测试的特征项(输入项)与训练数据集中的特征项进行欧氏距离的测算,抽取出最相似(最近邻)的数据集,并选择前k(k<20)个最近邻数据中出现次数最多的分类,作为这个输入特征项的分类。

举例案例

电影类型分类

  • 由我们自己创建数据集与结果集

  • 作用:判别电影属于爱情片还是动作片

  • 流程

    • 创建数据集,分离训练数据与训练数据的结果
    • 输入数据,进行欧氏距离测算
    • 选择距离最小的前k个点
    • 记录这k个点出现的类别的次数,并排序
    • 取序号最高位进行输出,完成分类
  • 缺陷

    • 未对算法进行测试
    • 数据集过少
  • 代码:

    from numpy import * ##导入科学计算包numpy
    import operator ##导入运算符模块
    
    #第一个参数是用于分类的输入向量inX
    #第二个参数是输入的训练样本集dataSet
    #第三个参数是训练数据的类别标签向量labels
    #第四个参数是k表示用于选择最邻近的个数k
    #其中标签向量labels的元素个数和训练数据的行数相同,采用欧式距离公式
    def classify0(inX, dataSet, labels, k):
        #1.计算欧氏距离
        dataSetSize = dataSet.shape[0]#shape查看第一维度行数
        print(dataSetSize)#4
        diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile(a,reps)函数把a按照reps的值在行列上分别重复对应次数
        sqDiffMat = diffMat**2
        sqDistances = sqDiffMat.sum(axis=1)#sum() 没有axis参数表示全部数据相加axis=0,表示按列相加axis=1,表示按行相加
        print(sqDistances)
        distances = sqDistances**0.5
        print(distances)
        sortedDistIndicies = distances.argsort()#argsort()函数,是numpy库中的函数,其返回的是从小到大排序后的索引列表,输入可以为列表
        print(sortedDistIndicies)
        classCount={}#创建一个字典
        #2.选择距离最小的k个点
        for i in range(k):#循环三次
            voteIlabel = labels[sortedDistIndicies[i]]
            classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#记录次数,用字典保存
            print('voteIlabel:'+voteIlabel)
            print(classCount[voteIlabel])
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#从大到小排序
        #sorted(iterable, /, *, key=None, reverse=False)
        #返回一个新的列表,其中包含来自iterable的所有项目,并按升序排列。
        #可以提供自定义键函数来定制排序顺序和
        #反向标志可以设置为按降序请求结果。
        return sortedClassCount[0][0]
    
    def createDataSet():
        group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])#定义四组数据,array方法创建一个两行两列的矩阵
        labels = ['A','A','B','B']
        return group,labels
    
    运行代码:
    >>>group,labels = createDataSet()
    >>>classify0([0,0], group, labels, 3)
    

人物喜欢类型分类

  • 数据集一部分用来作为训练集,另一部分作为测试集,用文档保存

  • 作用:根据人物的三种特征项来判断是否是哪种类型的人

  • 流程

    • 从文件中取出数据集用矩阵保存
    • 对数据进行归一化
    • 测试算法
      • 加载测试集、训练集、训练数据的分类集与k测试算法
      • 加载测试数据的分类集,对错误结果次数计数
      • 错误结果次数/总测试数求出错误率
      • 可通过修改k值、测试集对算法进行优化。
    • 实际运用
      • 给定人物的三个特征,可以通过算法判断是否是哪种类型的人
  • 代码:

    from numpy import *
    import operator
    from os import listdir
    
    #第一个参数是用于分类的输入向量inX
    #第二个参数是输入的训练样本集dataSet
    #第三个参数是训练数据的类别标签向量labels
    #第四个参数是k表示用于选择最邻近的个数k
    #其中标签向量labels的元素个数和训练数据的行数相同,采用欧式距离公式
    def classify0(inX, dataSet, labels, k):
        #1.计算欧氏距离
        dataSetSize = dataSet.shape[0]#shape查看维数,行数
       # print(dataSetSize)#4
        diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile(a,reps)函数把a按照reps的值在行列上分别重复对应次数
        sqDiffMat = diffMat**2
        sqDistances = sqDiffMat.sum(axis=1)#sum() 没有axis参数表示全部数据相加axis=0,表示按列相加axis=1,表示按行相加
        #print(sqDistances)
        distances = sqDistances**0.5
        #print(distances)
        sortedDistIndicies = distances.argsort()#argsort()函数,是numpy库中的函数,其返回的是从小到大排序后的索引列表,输入可以为列表
        #print(sortedDistIndicies)
        classCount={}#创建一个字典
        #2.选择距离最小的k个点
        for i in range(k):#循环三次
            voteIlabel = labels[sortedDistIndicies[i]]
            classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#记录次数,用字典保存
            #print('voteIlabel:'+voteIlabel)
            #print(classCount[voteIlabel])
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#从大到小排序
        #sorted(iterable, /, *, key=None, reverse=False)
        #返回一个新的列表,其中包含来自iterable的所有项目,并按升序排列。
        #可以提供自定义键函数来定制排序顺序和
        #反向标志可以设置为按降序请求结果。
        return sortedClassCount[0][0]
    
    
    #分析训练数据
    def file2matrix(filename):
        fr = open(filename)
        #1.得到文件行数
        numberOfLines = len(fr.readlines())         #读取一行file,获取行数1000
        #print(numberOfLines)
        #2.创建返回的numpy矩阵
        returnMat = zeros((numberOfLines,3))        #zeros(行,列)可以构造特定的矩阵
        classLabelVector = []                       #准备返回类别
        #3.解析文件数据到列表
        fr = open(filename)
        index = 0
        for line in fr.readlines():#读取一行数据
            line = line.strip()#strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
            listFromLine = line.split('\t')#以制表符进行字符串切割,返回一个列表
            returnMat[index,:] = listFromLine[0:3]#取前三个数据,放到returnMat矩阵的第0行,下次循环就放到第1行。列表名[start位置,end位置]start位置和end位置不填默认就是最开始和最后的位置
            if listFromLine[-1] == 'didntLike':#列表名[-1]代表取列表最后一个
                classLabelVector.append(1)
            elif listFromLine[-1] == 'smallDoses':
                classLabelVector.append(2)
            elif listFromLine[-1] == 'largeDoses':
                classLabelVector.append(3)
            else:
                classLabelVector.append(int(listFromLine[-1]))
            #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
            index += 1
        return returnMat,classLabelVector
    
    #归一化数据 
    def autoNorm(dataSet):
        minVals = dataSet.min(0)#返回A每一列最小值组成的列表
        #print(minVals)
        maxVals = dataSet.max(0)#返回A每一列最大值组成的列表
        #print(maxVals)
        ranges = maxVals - minVals
        #print(ranges)
        normDataSet = zeros(shape(dataSet))#zeros构造特定矩阵,shape查看行数。也就是构造一个一行,列数为参数的矩阵
        #print(normDataSet)
        #print(shape(dataSet))#1000,3
        m = dataSet.shape[0]#读取dataSet列表第一维行数,1000
        #print(m)
        normDataSet = dataSet - tile(minVals, (m,1))#tile(a,reps)函数,把a按照reps的值在行列上分别重复对应次数复制
        #print(tile(minVals, (m,1)))
        #print(normDataSet)
        normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide
        #print(tile(ranges, (m,1)))
        #print(normDataSet)
        return normDataSet, ranges, minVals#需要取值范围和最小值进行归一化测试数据
    
    #测试分类器 
    def datingClassTest():
        hoRatio = 0.50      #选用前50%测试
        datingDataMat,datingLabels = file2matrix('datingTestSet.txt')#从文件加载数据集
        normMat, ranges, minVals = autoNorm(datingDataMat)#归一化数据集
        m = normMat.shape[0]#1000
        numTestVecs = int(m*hoRatio)#500
        errorCount = 0.0
        for i in range(numTestVecs):
            classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)#输入数据是训练数据集的前五百行作为我们将要测试的数据,训练数据是500~1000行,结果集就是最后一列的500~1000行,k取3
            print("返回的分类器是:%d,真正的答案是:%d" % (classifierResult, datingLabels[i]))
            if (classifierResult != datingLabels[i]): errorCount += 1.0#在这累加分类的错误次数
        print("错误次数为:%d" % errorCount)
        print("总错误率为: %f" % (errorCount/float(numTestVecs)))#用错误结果的次数除以测试数据的总数,就是错误率
       
    #约会网站预测函数
    def classifyPerson():
        resultList = ['不喜欢','感觉魅力一般','感觉极具魅力']
        percentTats = float(input("花在玩电子游戏上的时间占多大比例?"))
        ffMiles = float(input("飞行常客每年能获得多少里程?"))
        iceCream = float(input("每年消耗多少升冰淇淋?"))
        datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')#1.解析文件数据成矩阵返回
        normMat,ranges,minVals = autoNorm(datingDataMat)#2.归一化数据
        inArr = array([ffMiles,percentTats,iceCream])#3.(inArr-minVals)/ranges把我们输入的数据也进行归一化
        classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels,3)#4.对已归一化好的输入数据进行分类,训练数据为归一化后的数据,训练数据类别,k值
        print('你可能会对这个人: ',resultList[classifierResult-1])
    
        
    运行代码:
    >>> classifyPerson()
    随后输入人物的三个特征项即可得出结果
    

手写数字识别

  • 准备数据集:数据集分两个文件夹,trainingDigits为训练集,testDigits为测试集,其文件名包含答案。

  • 作用:可对手写数字进行识别

  • 流程

    • 加载文件,处理文件,获取数据集与类别集
    • 加载分类器进行算法测试
    • 实践(还没做,只知道怎么做)
      • 给出手写数字的图片
      • 进行二进制码转换成文本
      • 输入到分类器中进行判别
  • 代码:

    from numpy import *
    import operator
    from os import listdir
    
    #第一个参数是用于分类的输入向量inX
    #第二个参数是输入的训练样本集dataSet
    #第三个参数是训练数据的类别标签向量labels
    #第四个参数是k表示用于选择最邻近的个数k
    #其中标签向量labels的元素个数和训练数据的行数相同,采用欧式距离公式
    def classify0(inX, dataSet, labels, k):
        #1.计算欧氏距离
        dataSetSize = dataSet.shape[0]#shape查看维数,行数
       # print(dataSetSize)#4
        diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile(a,reps)函数把a按照reps的值在行列上分别重复对应次数
        sqDiffMat = diffMat**2
        sqDistances = sqDiffMat.sum(axis=1)#sum() 没有axis参数表示全部数据相加axis=0,表示按列相加axis=1,表示按行相加
        #print(sqDistances)
        distances = sqDistances**0.5
        #print(distances)
        sortedDistIndicies = distances.argsort()#argsort()函数,是numpy库中的函数,其返回的是从小到大排序后的索引列表,输入可以为列表
        #print(sortedDistIndicies)
        classCount={}#创建一个字典
        #2.选择距离最小的k个点
        for i in range(k):#循环三次
            voteIlabel = labels[sortedDistIndicies[i]]
            classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#记录次数,用字典保存
            #print('voteIlabel:'+voteIlabel)
            #print(classCount[voteIlabel])
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#从大到小排序
        #sorted(iterable, /, *, key=None, reverse=False)
        #返回一个新的列表,其中包含来自iterable的所有项目,并按升序排列。
        #可以提供自定义键函数来定制排序顺序和
        #反向标志可以设置为按降序请求结果。
        return sortedClassCount[0][0]
    
    
    #分析训练数据
    def file2matrix(filename):
        fr = open(filename)
        #1.得到文件行数
        numberOfLines = len(fr.readlines())         #读取一行file,获取行数1000
        #print(numberOfLines)
        #2.创建返回的numpy矩阵
        returnMat = zeros((numberOfLines,3))        #zeros(行,列)可以构造特定的矩阵
        classLabelVector = []                       #准备返回类别
        #3.解析文件数据到列表
        fr = open(filename)
        index = 0
        for line in fr.readlines():#读取一行数据
            line = line.strip()#strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
            listFromLine = line.split('\t')#以制表符进行字符串切割,返回一个列表
            returnMat[index,:] = listFromLine[0:3]#取前三个数据,放到returnMat矩阵的第0行,下次循环就放到第1行。列表名[start位置,end位置]start位置和end位置不填默认就是最开始和最后的位置
            if listFromLine[-1] == 'didntLike':#列表名[-1]代表取列表最后一个
                classLabelVector.append(1)
            elif listFromLine[-1] == 'smallDoses':
                classLabelVector.append(2)
            elif listFromLine[-1] == 'largeDoses':
                classLabelVector.append(3)
            else:
                classLabelVector.append(int(listFromLine[-1]))
            #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
            index += 1
        return returnMat,classLabelVector
    
    #归一化数据 
    def autoNorm(dataSet):
        minVals = dataSet.min(0)#返回A每一列最小值组成的列表
        #print(minVals)
        maxVals = dataSet.max(0)#返回A每一列最大值组成的列表
        #print(maxVals)
        ranges = maxVals - minVals
        #print(ranges)
        normDataSet = zeros(shape(dataSet))#zeros构造特定矩阵,shape查看行数。也就是构造一个一行,列数为参数的矩阵
        #print(normDataSet)
        #print(shape(dataSet))#1000,3
        m = dataSet.shape[0]#读取dataSet列表第一维行数,1000
        #print(m)
        normDataSet = dataSet - tile(minVals, (m,1))#tile(a,reps)函数,把a按照reps的值在行列上分别重复对应次数复制
        #print(tile(minVals, (m,1)))
        #print(normDataSet)
        normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide
        #print(tile(ranges, (m,1)))
        #print(normDataSet)
        return normDataSet, ranges, minVals#需要取值范围和最小值进行归一化测试数据
    
    #测试分类器 
    def datingClassTest():
        hoRatio = 0.50      #选用前50%测试
        datingDataMat,datingLabels = file2matrix('datingTestSet.txt')#从文件加载数据集
        normMat, ranges, minVals = autoNorm(datingDataMat)#归一化数据集
        m = normMat.shape[0]#1000
        numTestVecs = int(m*hoRatio)#500
        errorCount = 0.0
        for i in range(numTestVecs):
            classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)#输入数据是训练数据集的前五百行作为我们将要测试的数据,训练数据是500~1000行,结果集就是最后一列的500~1000行,k取3
            print("返回的分类器是:%d,真正的答案是:%d" % (classifierResult, datingLabels[i]))
            if (classifierResult != datingLabels[i]): errorCount += 1.0#在这累加分类的错误次数
        print("错误次数为:%d" % errorCount)
        print("总错误率为: %f" % (errorCount/float(numTestVecs)))#用错误结果的次数除以测试数据的总数,就是错误率
       
    #约会网站预测函数
    def classifyPerson():
        resultList = ['不喜欢','感觉魅力一般','感觉极具魅力']
        percentTats = float(input("花在玩电子游戏上的时间占多大比例?"))
        ffMiles = float(input("飞行常客每年能获得多少里程?"))
        iceCream = float(input("每年消耗多少升冰淇淋?"))
        datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')#1.解析文件数据成矩阵返回
        normMat,ranges,minVals = autoNorm(datingDataMat)#2.归一化数据
        inArr = array([ffMiles,percentTats,iceCream])#3.(inArr-minVals)/ranges把我们输入的数据也进行归一化
        classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels,3)#4.对已归一化好的输入数据进行分类,训练数据为归一化后的数据,训练数据类别,k值
        print('你可能会对这个人: ',resultList[classifierResult-1])
    
    
    #矩阵转换
    def img2vector(filename):
        returnVect = zeros((1,1024))#创建一个1X1024的零矩阵
        fr = open(filename)#打开文件
        for i in range(32):
            lineStr = fr.readline()#读取一行数据
            for j in range(32):
                returnVect[0,32*i+j] = int(lineStr[j])#把这一行中的每一个数据都放到零矩阵的第一行的每一列中,一共1024列
        return returnVect
    
    
    #识别手写数字的测试算法
    def handwritingClassTest():
        hwLabels = []
        trainingFileList = listdir('trainingDigits')           #获取训练数据目录内容
        m = len(trainingFileList)#获取目录内文件个数:1934
        trainingMat = zeros((m,1024))#构建一个1934行1024列的零矩阵
        #进入训练数据文件夹中的每个文件并对文件内容进行转换保存到矩阵中
        for i in range(m):
            fileNameStr = trainingFileList[i]
            fileStr = fileNameStr.split('.')[0]     #舍弃文件名的 .txt,取前面的内容
            classNumStr = int(fileStr.split('_')[0])#舍弃后面的内容,取前面的类别的名称
            hwLabels.append(classNumStr)#添加到列表中
            trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)#读取文件内数据并转换成1X1024的矩阵保存
        testFileList = listdir('testDigits')        #遍历测试集,获取测试数据集
        errorCount = 0.0#记录错误次数
        mTest = len(testFileList)#获取测试数据文件夹大小,准备了946个测试集
        #遍历测试集中每个文件
        for i in range(mTest):
            fileNameStr = testFileList[i]#获取本次循环文件的文件名
            fileStr = fileNameStr.split('.')[0]     #舍弃 .txt
            classNumStr = int(fileStr.split('_')[0])#舍弃_后面的数字,获取正确类别并保存
            vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)#获取测试集文件内容转换成1X1024的矩阵并保存
            classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)#输入数据为测试集中的数据,训练数据是训练集中的数据矩阵,训练数据的结果集,k值为3
            print("返回的分类器是:%d,真正的答案是:%d" % (classifierResult, classNumStr))
            if (classifierResult != classNumStr): errorCount += 1.0#记录错误次数
        print("\nthe total number of errors is: %d" % errorCount)
        print("\nthe total error rate is: %f" % (errorCount/float(mTest)))#算出错误率
    
        
    运行代码:
    >>>handwritingClassTest()
    

注意事项

上面所提及的资料都可以去我的博客资源里下载https://download.csdn.net/download/qq_42263553/12584975,代码和数据集都有,是这本书配套的。但是代码的化要注意一点的就是这本书当时还是基于Python2.6来写的代码,我的Python版本是3.8,大家直接参照我的代码就好了。

常见函数

plt.figure函数

figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True),figure的作用就是用来创建窗口

    1. num:图像编号或名称,数字为编号 ,字符串为名称
    2. figsize:指定figure的宽和高,单位为英寸;
    3. dpi参数指定绘图对象的分辨率,即每英寸多少个像素,缺省值为80 1英寸等于2.5cm,A4纸是 21*30cm的纸张
    4. facecolor:背景颜色
    5. edgecolor:边框颜色
    6. frameon:是否显示边框

add_subplot函数

是plt.figure函数的返回值所调用的函数,

add_subplot(self, *args, **kwargs)添加子图
说明、参数、返回值
Add an Axes to the figure as part of a subplot arrangement.

作为子图布置的一部分,将坐标轴添加到图中。

使用:add_subplot(111),表示绘制1行1列的1X1的坐标轴图一张,从左上角开始依次向右增加。

返回值:子图的坐标轴。

scatter函数

机器学习实战——第二章(分类):k-近邻算法与实例

机器学习实战——第二章(分类):k-近邻算法与实例

shape函数

shape函数用来查看矩阵大小,shape(矩阵):返回的是这个矩阵的行数和列数。

shape[0]用来:*shape[0]*就是读取矩阵第一维度的长度

tile函数

tile(a,reps)函数

把a按照reps的值在行列上分别重复对应次数复制