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

《机器学习实战》第二章——K-近邻算法

程序员文章站 2022-05-02 14:03:39
...
1.K-近邻算法(kNN)

1.1K-近邻算法概述

简单的说,K-近邻算法采用测量不同特征值之间的距离方法进行分类

优点:精度高、对异常值不敏感、无数据输入假定

缺点:计算复杂度高、空间复杂度高

适用数据范围:数值型和标称型


1.2KNN算法原理

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


电影类型评估代码(本书所需的数据集可到这位大神的GitHub下载)

import numpy as np
import operator

def createDataSet():
    group=np.array([[3,104],[2,100],[101,10],[99,5]])
    #四组二维特征
    labels=['爱情片','爱情片','动作片','动作片']
    #四组二维特征对应的标签
    return group,labels

def classify0(inX,dataSet,labels,k):
    dataSetSize=dataSet.shape[0]
    #numpy函数shape[0]返回dataSet的行数
    diffMat=np.tile(inX,(dataSetSize,1))-dataSet
    #np.tile()函数,把数组沿各个方向复制,此例中是沿横向复制一倍(其实是没有增加),纵向复制dataSetSize次
    sqDiffMat=diffMat**2
    sqDistances=sqDiffMat.sum(axis=1)
    #sum(0)列相加,sum(1)行相加
    distances=sqDistances**0.5
    sortedDistIndicies=distances.argsort()
    #返回distance中元素从小到大的排列值
    classCount={}
    for i in range(k):
        votelabel=labels[sortedDistIndicies[i]]
        #取出前k个元素的类别
        classCount[votelabel]=classCount.get(votelabel,0)+1
        #dict.get(key,default=None),字典的get方法,返回指定键的值,如果不在字典中返回默认值
        #计算类别次数
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #字典函数items(),函数以列表返回可遍历的键和值
    #python3中用items()替换python2中的iteritems()
    #key=operator.itemgetter[1]根据字典的值进行排序
    #key=operator.itemgetter[0 ]根据字典的键进行排序
    #reverse降序排序字典
    return sortedClassCount[0][0]
    #返回次数最多的类别    

if __name__=='__main__':
    group,labels=createDataSet()
    test=[3,100]
    result=classify0(test,group,labels,2)
    print(result)
    

运行结果:

《机器学习实战》第二章——K-近邻算法

在约会网站上使用K—近邻算法寻找合适海伦的人

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

import numpy as np
import matplotlib
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号

def file2matrix(filename):
    fr=open(filename)
    arrayOlLines=fr.readlines()
    #读取文件所有内容
    numberOfLines=len(arrayOlLines)
    returnMat=np.zeros((numberOfLines,3))
    #np.zeros()函数生成矩阵中的值全为0
    classLabelVector=[]
    index=0
    for line in arrayOlLines:
        line=line.strip()
        #strip(),括号为空时,默认删除空白符(包括\n,\r,\t)
        listFromline=line.split('\t')
        returnMat[index,:]=listFromline[0:3]
        if listFromline[-1]=='didntLike':
            classLabelVector.append(1)
        elif listFromline[-1]=='smallDoses':
            classLabelVector.append(2)
        elif listFromline[-1]=='largeDoses':
            classLabelVector.append(3)
        index+=1
    return returnMat,classLabelVector

def showdata(data,classla):
    fig=plt.figure()
    #产生一个空窗口
    ax=fig.add_subplot(221)#221的意思是把一张图片分为两行两列,把ax要显示的放在第一张图上
    bx=fig.add_subplot(222)
    cx=fig.add_subplot(223)
    #将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)
    #当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域
    #fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8))

    #number0fLabels=len(data)
    labelsColors=[]
    for i in classla:
        if  i==1:
            labelsColors.append('black')
        if  i==2:
            labelsColors.append('orange')
        if  i==3:
            labelsColors.append('red')

    #print(type(data),type(classla))
    ax.scatter(data[:,0],data[:,1],color=labelsColors,s=15,alpha=.5)
    bx.scatter(data[:,0],data[:,2],color=labelsColors,s=15,alpha=.5)
    cx.scatter(data[:,1],data[:,2],color=labelsColors,s=15,alpha=.5)
   
    ax_xlabel_text = ax.set_xlabel(u'每年获得的飞行常客里程数')
    ax_ylabel_text = ax.set_ylabel(u'玩视频游戏所消耗时间占比')
 
    bx_xlabel_text = bx.set_xlabel(u'每年获得的飞行常客里程数')
    bx_ylabel_text = bx.set_ylabel(u'每周消费的冰激淋公升数')
   
    cx_xlabel_text = cx.set_xlabel(u'玩视频游戏所消耗时间占比')
    cx_ylabel_text = cx.set_ylabel(u'每周消费的冰激淋公升数')
    plt.setp(ax_xlabel_text, size=7, weight='bold', color='black') 
    plt.setp(bx_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(cx_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(ax_ylabel_text, size=7, weight='bold', color='black')
    plt.setp(bx_ylabel_text, size=7, weight='bold', color='black')
    plt.setp(cx_ylabel_text, size=7, weight='bold', color='black')
    plt.show()

if __name__=="__main__":
    data,classla=file2matrix('./datingTestSet.txt')
    showdata(data,classla)
    

运行结果:

《机器学习实战》第二章——K-近邻算法

准备数据,这里使用了欧式距离来计算两个点之间的距离:

《机器学习实战》第二章——K-近邻算法

然后对数据做了归一化处理

归一化代码:

#准备数据,归一化数据

# -*- coding: utf-8 -*-
"""
Created on Sun Apr  1 20:11:04 2018


@author: Administrator
"""
import operator
import numpy as np
import matplotlib
import matplotlib.pyplot as plt


def classify0(inX,dataSet,labels,k):
    dataSetSize=dataSet.shape[0]
    #numpy函数shape[0]返回dataSet的行数
    diffMat=np.tile(inX,(dataSetSize,1))-dataSet
    #np.tile()函数,把数组沿各个方向复制,此例中是沿横向复制一倍(其实是没有增加),纵向复制dataSetSize次
    sqDiffMat=diffMat**2
    sqDistances=sqDiffMat.sum(axis=1)
    #sum(0)列相加,sum(1)行相加
    distances=sqDistances**0.5
    sortedDistIndicies=distances.argsort()
    #返回distance中元素从小到大的排列值
    classCount={}
    for i in range(k):
        votelabel=labels[sortedDistIndicies[i]]
        #取出前k个元素的类别
        classCount[votelabel]=classCount.get(votelabel,0)+1
        #dict.get(key,default=None),字典的get方法,返回指定键的值,如果不在字典中返回默认值
        #计算类别次数
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #字典函数items(),函数以列表返回可遍历的键和值
     #python3中用items()替换python2中的iteritems()
     #key=operator.itemgetter[1]根据字典的值进行排序
     #key=operator.itemgetter[0 ]根据字典的键进行排序
     #reverse降序排序字典
    return sortedClassCount[0][0]
    #返回次数最多的类别

#准备数据,归一化数据

def autoNorm(dataSet):
    min=dataSet.min(0)
    max=dataSet.max(0)
    ranges=max-min
    m=dataSet.shape[0]
    normDataSet=dataSet-np.tile(min,(m,1))
    normDataSet=normDataSet/np.tile(ranges,(m,1))
    return normDataSet,ranges,min

def file2matrix(filename):
    fr=open(filename)
    arrayOlLines=fr.readlines()
    #读取文件所有内容
    numberOfLines=len(arrayOlLines)
    returnMat=np.zeros((numberOfLines,3))
    #np.zeros()函数生成矩阵中的值全为0
    classLabelVector=[]
    index=0
    for line in arrayOlLines:
        line=line.strip()
        #strip(),括号为空时,默认删除空白符(包括\n,\r,\t)
        listFromline=line.split('\t')
        returnMat[index,:]=listFromline[0:3]
        if listFromline[-1]=='didntLike':
            classLabelVector.append(1)
        elif listFromline[-1]=='smallDoses':
            classLabelVector.append(2)
        elif listFromline[-1]=='largeDoses':
            classLabelVector.append(3)
        index+=1
    return returnMat,classLabelVector



if __name__=="__main__":
    data,classla=file2matrix('./datingTestSet.txt')
    data1=autoNorm(data)
    print(data1)


    

运行结果:

《机器学习实战》第二章——K-近邻算法

用完整代码测试分类器效果

# -*- coding: utf-8 -*-
"""
Created on Sun Apr  1 20:11:04 2018

@author: Administrator
"""
import operator
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

def classify0(inX,dataSet,labels,k):
    dataSetSize=dataSet.shape[0]
    #numpy函数shape[0]返回dataSet的行数
    diffMat=np.tile(inX,(dataSetSize,1))-dataSet
    #np.tile()函数,把数组沿各个方向复制,此例中是沿横向复制一倍(其实是没有增加),纵向复制dataSetSize次
    sqDiffMat=diffMat**2
    sqDistances=sqDiffMat.sum(axis=1)
    #sum(0)列相加,sum(1)行相加
    distances=sqDistances**0.5
    sortedDistIndicies=distances.argsort()
    #返回distance中元素从小到大的排列值
    classCount={}
    for i in range(k):
        votelabel=labels[sortedDistIndicies[i]]
        #取出前k个元素的类别
        classCount[votelabel]=classCount.get(votelabel,0)+1
        #dict.get(key,default=None),字典的get方法,返回指定键的值,如果不在字典中返回默认值
        #计算类别次数
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #字典函数items(),函数以列表返回可遍历的键和值
     #python3中用items()替换python2中的iteritems()
     #key=operator.itemgetter[1]根据字典的值进行排序
     #key=operator.itemgetter[0 ]根据字典的键进行排序
     #reverse降序排序字典
    return sortedClassCount[0][0]
    #返回次数最多的类别


#准备数据,归一化数据

def autoNorm(dataSet):
    min=dataSet.min(0)
    max=dataSet.max(0)
    ranges=max-min
    m=dataSet.shape[0]
    normDataSet=dataSet-np.tile(min,(m,1))
    normDataSet=normDataSet/np.tile(ranges,(m,1))
    return normDataSet,ranges,min


def file2matrix(filename):
    fr=open(filename)
    arrayOlLines=fr.readlines()
    #读取文件所有内容
    numberOfLines=len(arrayOlLines)
    returnMat=np.zeros((numberOfLines,3))
    #np.zeros()函数生成矩阵中的值全为0
    classLabelVector=[]
    index=0
    for line in arrayOlLines:
        line=line.strip()
        #strip(),括号为空时,默认删除空白符(包括\n,\r,\t)
        listFromline=line.split('\t')
        returnMat[index,:]=listFromline[0:3]
        if listFromline[-1]=='didntLike':
            classLabelVector.append(1)
        elif listFromline[-1]=='smallDoses':
            classLabelVector.append(2)
        elif listFromline[-1]=='largeDoses':
            classLabelVector.append(3)
        index+=1
    return returnMat,classLabelVector




#测试算法,作为完整程序验证分类

def datingClassTest():
    hoRatio=0.2
    datingDataMat,datingLabels=file2matrix('datingTestSet.txt')
    normMat,ranges,min=autoNorm(datingDataMat)
    m=normMat.shape[0]
    numTestVecs=int(m*hoRatio)
    errorCount=0.0
    for i in range(numTestVecs):
        classifierResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],5)
        print('分类器返回:{},正确结果是:{}'.format(classifierResult,datingLabels[i]))
        if(classifierResult!=datingLabels[i]):
            errorCount+=1.0
    print('the total error rate is:{}'.format(errorCount/float(numTestVecs)))


if __name__=="__main__":
    datingClassTest()

    

运行结果:

《机器学习实战》第二章——K-近邻算法

这里的数据集错误率是6.5%,有点高,可以试着改变k值和hoRatio的值来调整错误率。

使用算法

# -*- coding: utf-8 -*-
"""
Created on Sun Apr  1 20:11:04 2018

@author: Administrator
"""
import operator
import numpy as np
import matplotlib
import matplotlib.pyplot as plt

def classify0(inX,dataSet,labels,k):
    dataSetSize=dataSet.shape[0]
    #numpy函数shape[0]返回dataSet的行数
    diffMat=np.tile(inX,(dataSetSize,1))-dataSet
    #np.tile()函数,把数组沿各个方向复制,此例中是沿横向复制一倍(其实是没有增加),纵向复制dataSetSize次
    sqDiffMat=diffMat**2
    sqDistances=sqDiffMat.sum(axis=1)
    #sum(0)列相加,sum(1)行相加
    distances=sqDistances**0.5
    sortedDistIndicies=distances.argsort()
    #返回distance中元素从小到大的排列值
    classCount={}
    for i in range(k):
        votelabel=labels[sortedDistIndicies[i]]
        #取出前k个元素的类别
        classCount[votelabel]=classCount.get(votelabel,0)+1
        #dict.get(key,default=None),字典的get方法,返回指定键的值,如果不在字典中返回默认值
        #计算类别次数
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #字典函数items(),函数以列表返回可遍历的键和值
     #python3中用items()替换python2中的iteritems()
     #key=operator.itemgetter[1]根据字典的值进行排序
     #key=operator.itemgetter[0 ]根据字典的键进行排序
     #reverse降序排序字典
    return sortedClassCount[0][0]
    #返回次数最多的类别


#准备数据,归一化数据

def autoNorm(dataSet):
    min=dataSet.min(0)
    max=dataSet.max(0)
    ranges=max-min
    m=dataSet.shape[0]
    normDataSet=dataSet-np.tile(min,(m,1))
    normDataSet=normDataSet/np.tile(ranges,(m,1))
    return normDataSet,ranges,min


def file2matrix(filename):
    fr=open(filename)
    arrayOlLines=fr.readlines()
    #读取文件所有内容
    numberOfLines=len(arrayOlLines)
    returnMat=np.zeros((numberOfLines,3))
    #np.zeros()函数生成矩阵中的值全为0
    classLabelVector=[]
    index=0
    for line in arrayOlLines:
        line=line.strip()
        #strip(),括号为空时,默认删除空白符(包括\n,\r,\t)
        listFromline=line.split('\t')
        returnMat[index,:]=listFromline[0:3]
        if listFromline[-1]=='didntLike':
            classLabelVector.append(1)
        elif listFromline[-1]=='smallDoses':
            classLabelVector.append(2)
        elif listFromline[-1]=='largeDoses':
            classLabelVector.append(3)
        index+=1
    return returnMat,classLabelVector




#测试算法,作为完整程序验证分类

def datingClassTest():
    hoRatio=0.2
    datingDataMat,datingLabels=file2matrix('datingTestSet.txt')
    normMat,ranges,min=autoNorm(datingDataMat)
    m=normMat.shape[0]
    numTestVecs=int(m*hoRatio)
    errorCount=0.0
    for i in range(numTestVecs):
        classifierResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],5)
        print('分类器返回:{},正确结果是:{}'.format(classifierResult,datingLabels[i]))
        if(classifierResult!=datingLabels[i]):
            errorCount+=1.0
    print('the total error rate is:{}'.format(errorCount/float(numTestVecs)))

def classifPreson():
    resultList=['nor at all','in small does','in large doses']
    percentTate=float(input('话费在玩游戏上的时间百分比:'))
    ffMile=float(input('每年飞行公里数:'))
    iceCream=float(input('每周消耗的冰激凌公升数:'))
    dataset,dataLabels=file2matrix('datingTestSet.txt')
    normMat,ranges,min=autoNorm(dataset)
    inArr=np.array([ffMile,percentTate,iceCream])
    classifierresult=classify0((inArr-min)/ranges,normMat,dataLabels,3)
    print('你将要可能喜欢这个喜欢人:',resultList[classifierresult-1])
    
if __name__=="__main__":
    #datingClassTest()
    classifPreson()

    

运行结果:

《机器学习实战》第二章——K-近邻算法

sklearn实战——手写识别系统

sklearn有提供KNN算法的API,直接调用即可。

import numpy as np
import operator
from sklearn.neighbors import KNeighborsClassifier as kNN
from os import listdir

def img2vector(filename):
    returnvect=np.zeros((1,1024))
    fr=open(filename)
    for i in range(32):
        linestr=fr.readline()
        for j in range(32):
            returnvect[0,32*i+j]=int(linestr[j])
    return returnvect
    

def handwritingClassTest():
    hwLabels=[]
    trainingFileList=listdir('trainingDigits')
    m=len(trainingFileList)
    trainingMat=np.zeros((m,1024))
    for i in range(m):
        fileNameStr=trainingFileList[i]
        classNumber=int(fileNameStr.split('_')[0])
        hwLabels.append(classNumber)
        trainingMat[i,:]=img2vector('trainingDigits/{}'.format(fileNameStr))
    neigh=kNN(n_neighbors=3,algorithm='auto')
    neigh.fit(trainingMat,hwLabels)
    testFileList=listdir('testDigits')
    errorCount=0.0
    mtest=len(testFileList)
    for i in range(mtest):
        fileNameStr=testFileList[i]
        classNumber=int(fileNameStr.split('_')[0])
        vectoUndertest=img2vector('testDigits/{}'.format(fileNameStr))   
        classifierResult=neigh.predict(vectoUndertest)
        print('分类返回结果为:{} 真实结果为:{}'.format(classifierResult,classNumber))
        if (classifierResult!=classNumber):
            errorCount+=1
    print('总共错了{}个数据,错误率为{}'.format(errorCount,errorCount/mtest*100))
        


if __name__=='__main__':
     handwritingClassTest()
 
        

运行结果:

《机器学习实战》第二章——K-近邻算法