《机器学习实战2》

2017.2.28
第二章《k-近邻算法》

思维导图:

在这里插入图片描述

1、基本算法原理

简单地说,k近邻算法采用测量不同特征值之间的距离方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度高、空间复杂度高。
适用数据范围:数值型和标称型。
工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输人没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

2、k-近邻算法的处理流程

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

3、代码部分具体实现

(1)收集数据
获取每条数据的前三个特征值构成了31000的矩阵,作为训练样本矩阵returnMat,每条数据的最后一个值存储的是所属分类的标签,则这些值构成了11000的类标签向量classLabelVector。

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())         #get the number of lines in the file
    returnMat = zeros((numberOfLines,3))        #prepare matrix0 to return
    classLabelVector = []                       #prepare labels return   
    fr = open(filename)
    index = 0
    for line in fr.readlines():
        line = line.strip()                     #去掉字符串头尾的空格
        listFromLine = line.split('\t')         #以空格作为分隔符,分隔每一行的字符串
        returnMat[index,:] = listFromLine[0:3]  #将分隔的字符串前三个赋值给之前创建的0矩阵
        classLabelVector.append(int(listFromLine[-1]))  #在labels矩阵末尾添加分隔的第4个字符串
        index += 1
    return returnMat,classLabelVector           #返回两个矩阵,分别是样本训练集和标签向量

(2)准备数据:
我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

newvalue = (oldvalue-min)/(max-min)

其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。

def autoNorm(dataSet):                                                              
    minVals = dataSet.min(0)                    #寻找样本中最小的值
    maxVals = dataSet.max(0)                    #寻找样本中最大的值
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))         #创建和样本集一样大的0矩阵
    m = dataSet.shape[0]                        #创建一维数组
    normDataSet = dataSet - tile(minVals, (m,1))    #样本中的元素和最小值之间的差值
    normDataSet = normDataSet/tile(ranges, (m,1))   #归一化后的样本集
    return normDataSet, ranges, minVals

(3)分析数据:
我们需要了解数据的真实含义。当然我们可以直接浏览文本文件,但是这种方法非常不友好,一般来说,我们会采用图形化的方式直观地展示数据。下面就用?^1!(瓜工具来图形化展示数据内容,以便辨识出一些数据模式。使用Matplotlib创建散点图k可以清楚明白的看到分类的必要。
输入一下画图代码:

import numpy
import matplotlib
import matplotlib.pyplot as plt
import kNN
reload(kNN)
datingDataMat,datingLabels=kNN.file2matrix('datingTestSet2.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*numpy.array(datingLabels),15.0*numpy.array(datingLabels))
plt.show()

下面使用后两个特征的图片
在这里插入图片描述
将scatter函数修改为:

ax.scatter(datingDataMat[:,0],datingDataMat[:,1],15.0*numpy.array(datingLabels),15.0*numpy.array(datingLabels))

使用前两个特征的图片
在这里插入图片描述

(4)分类算法:
就是使用距离公式计算特征值之间的距离,选择最邻近的k个点,通过统计这k个点的结果来得出样本的预测值。首先给出这种分类算法。

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]        #从样本集中取出数据
    diffMat = tile(inX, (dataSetSize,1)) - dataSet  #求出所有样本集和输入值之间的差值
    sqDiffMat = diffMat**2                #算差值的平方  
    sqDistances = sqDiffMat.sum(axis=1)   #计算求和,axis=1表示按行相加 , axis=0表示按列相加
    distances = sqDistances**0.5          #开方
    sortedDistIndicies = distances.argsort()   #返回数组值的从小到大的索引值  
    classCount={}          
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]   #选出距离小于k的label值
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1    #创建一个字典,存放的是label值出现的次数count
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)  #对字典遍历,按照count的顺序,从大到小排序
    return sortedClassCount[0][0]    #返回第一个就是频率最高的那个

(5)测试算法作为完整程序验证分类器
机器学习算法的一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本,而使用其余的10%数据去测试分类器,检测分类器的正确率。需要注意的是,这10%的测试数据应该是随机选择的,下面我们在kNN.py中创建函数datingClassTest()来测试分类器效果,获取错误率。

datingClassTest()代码如下:
def datingClassTest():
    hoRatio = 0.10      #hold out 10%           #预留10%作为测试样本
    datingDataMat,datingLabels = file2matrix(r'C:\Users\lz\Desktop\2017_2_28\datingTestSet2.txt')      #load data setfrom file
    normMat, ranges, minVals = 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],3)     #调用k分类器
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))   #打印测试集的结果
        if (classifierResult != datingLabels[i]): errorCount += 1.0

    print ("the total error rate is: %f" % (errorCount/float(numTestVecs)) )                  #输出错误总数
    print (errorCount)

代码中用datingTestSet2.txt中的10%的数据作为测试样本,50%的数据作为训练样本,将推测结果和实际结果进行比较,获取错误率,以下为测试结果:
在这里插入图片描述
错误率为5%,我们可以通过对训练样本和测试样本的调整,改变精度,也可以通过对k值的调整,改变精度。

(6)使用算法:完善系统
kNN.py中加入函数classifyPerson():

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent playing video games?"))
    ffMiles = float(input("frequent flier miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))
    datingDataMat, datingLabels = file2matrix(r'C:\Users\lz\Desktop\2017_2_28\datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
    print ("you will probably like this peron:", resultList[classifierResult - 1])

使用这个分类测试结果:
在这里插入图片描述

3、参考博客

http://blog.csdn.net/du_wood/article/details/52204850
http://blog.csdn.net/iboxty/article/details/44982013
http://blog.csdn.net/memray/article/details/17646197

4、源代码


kNN.py

# -*- coding: utf-8 -*-
"""
Created on Mon Feb 27 20:23:01 2017
@author: lz
"""
from numpy import *
import operator
from os import listdir

def classify0(inX, dataSet, labels, k):
   dataSetSize = dataSet.shape[0]        #从样本集中取出数据
   diffMat = tile(inX, (dataSetSize,1)) - dataSet  #求出所有样本集和输入值之间的差值
   sqDiffMat = diffMat**2                #算差值的平方 
   sqDistances = sqDiffMat.sum(axis=1)   #计算求和,axis=1表示按行相加 , axis=0表示按列相加
   distances = sqDistances**0.5          #开方
   sortedDistIndicies = distances.argsort()   #返回数组值的从小到大的索引值 
   classCount={}         
   for i in range(k):
       voteIlabel = labels[sortedDistIndicies[i]]   #选出距离小于k的label值
       classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1    #创建一个字典,存放的是label值出现的次数count
   sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)  #对字典遍历,按照count的顺序,从大到小排序
   return sortedClassCount[0][0]    #返回第一个就是频率最高的那个

def createDataSet():
   group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
   labels = ['A','A','B','B']
   return group, labels


def file2matrix(filename):
   fr = open(filename)
   numberOfLines = len(fr.readlines())         #get the number of lines in the file
   returnMat = zeros((numberOfLines,3))        #prepare matrix0 to return
   classLabelVector = []                       #prepare labels return  
   fr = open(filename)
   index = 0
   for line in fr.readlines():
       line = line.strip()                     #去掉字符串头尾的空格
       listFromLine = line.split('\t')         #以空格作为分隔符,分隔每一行的字符串
       returnMat[index,:] = listFromLine[0:3]  #将分隔的字符串前三个赋值给之前创建的0矩阵
       classLabelVector.append(int(listFromLine[-1]))  #在labels矩阵末尾添加分隔的第4个字符串
       index += 1
   return returnMat,classLabelVector           #返回两个矩阵,分别是样本训练集和标签向量


def autoNorm(dataSet):                                                             
   minVals = dataSet.min(0)                    #寻找样本中最小的值
   maxVals = dataSet.max(0)                    #寻找样本中最大的值
   ranges = maxVals - minVals
   normDataSet = zeros(shape(dataSet))         #创建和样本集一样大的0矩阵
   m = dataSet.shape[0]                        #创建一维数组
   normDataSet = dataSet - tile(minVals, (m,1))    #样本中的元素和最小值之间的差值
   normDataSet = normDataSet/tile(ranges, (m,1))   #归一化后的样本集
   return normDataSet, ranges, minVals



def datingClassTest():
   hoRatio = 0.10      #hold out 10%           #预留10%作为测试样本
   datingDataMat,datingLabels = file2matrix(r'C:\Users\lz\Desktop\2017_2_28\datingTestSet2.txt')      #load data setfrom file
   normMat, ranges, minVals = 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],3)     #调用k分类器
       print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))   #打印测试集的结果
       if (classifierResult != datingLabels[i]): errorCount += 1.0

   print ("the total error rate is: %f" % (errorCount/float(numTestVecs)) )                  #输出错误总数
   print (errorCount)

def classifyPerson():
   resultList = ['not at all', 'in small doses', 'in large doses']
   percentTats = float(input("percentage of time spent playing video games?"))
   ffMiles = float(input("frequent flier miles earned per year?"))
   iceCream = float(input("liters of ice cream consumed per year?"))
   datingDataMat, datingLabels = file2matrix(r'C:\Users\lz\Desktop\2017_2_28\datingTestSet2.txt')
   normMat, ranges, minVals = autoNorm(datingDataMat)
   inArr = array([ffMiles, percentTats, iceCream])
   classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
   print ("you will probably like this peron:", resultList[classifierResult - 1])

plot_datingTestSet.py
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 28 11:26:03 2017
 
@author: lz
"""
 
import numpy
import matplotlib
import matplotlib.pyplot as plt
import kNN
reload(kNN)
datingDataMat,datingLabels=kNN.file2matrix(r'C:\Users\lz\Desktop\2017_2_28\datingTestSet2.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,0],datingDataMat[:,1],15.0*numpy.array(datingLabels),15.0*numpy.array(datingLabels))
plt.show()

run.py
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 28 11:26:03 2017
 
@author: lz
"""
 
import sys
sys.path.append(r'C:\Users\lz\Desktop\2017_2_28')
import kNN
reload (kNN)
#group,labels = kNN.createDataSet()
#kNN.datingClassTest()
kNN.classifyPerson()

利用Knn近邻算法识别手写字体

背景:利用k-近邻算法构造可以识别数字0到9的系统,需要处理的数字已经使用图形处理软件,处理成32*32像素的黑白图像,为了方便理解,数据以文本文件存储(虽然这不是一个好办法),数据格式如下:

00000000000001100000000000000000
00000000000011111100000000000000
00000000000111111111000000000000
00000000011111111111000000000000
00000001111111111111100000000000
00000000111111100011110000000000
00000001111110000001110000000000
00000001111110000001110000000000
00000011111100000001110000000000
00000011111100000001111000000000
00000011111100000000011100000000
00000011111100000000011100000000
00000011111000000000001110000000
00000011111000000000001110000000
00000001111100000000000111000000
00000001111100000000000111000000
00000001111100000000000111000000
00000011111000000000000111000000
00000011111000000000000111000000
00000000111100000000000011100000
00000000111100000000000111100000
00000000111100000000000111100000
00000000111100000000001111100000
00000000011110000000000111110000
00000000011111000000001111100000
00000000011111000000011111100000
00000000011111000000111111000000
00000000011111100011111111000000
00000000000111111111111110000000
00000000000111111111111100000000
00000000000011111111110000000000
00000000000000111110000000000000

以上为数字‘0’的一个训练样本。

步骤
● 收集数据:提供文本文件。
● 准备数据:编写函数classify0(),将数据转换为分类器使用的list格式。
● 分析数据:在python命令中检查数据,确保它符合要求。
● 训练算法:此步骤不适用于k-近邻算法。
● 测试算法:编写函数,以部分样本作为测试样本,计算精度。
● 使用算法:构建完整的应用程序。

准备数据:将图像转换为测试向量
目录trainingDigits中包含了大约2000个例子,每个数字大约有200个样本,目录testDigits中包含大约900个测试数据。我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果。

首先我们在上节的kNN模块中新增函数img2vector(),将图像转换为向量:该函数创建11024的numpy数组,然后打开文件,循环读出文件的前32行,将每行的前32个字符存储在numpy数组中,将3232的矩阵转换为1*1024的向量:

代码如下

 def img2vector(filename):                                         #创建一个1*1024的数组 
    returnVect = zeros((1,1024))                                   
    fr = open(filename)
    for i in range(32):                                           #每32行的数据循环读出来                  
        lineStr = fr.readline()                                  
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])                #把每32行的数据读出来放置在创建的1*1024的数组中
    return returnVect

测试算法:使用k-近邻算法识别手写数字
kNN模块中建立handwritingClassTest()函数:

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')                  #输出目录下所有的文件名
    m = len(trainingFileList)                                     #计算有多少个文件
    trainingMat = zeros((m,1024))                                 #按照1*1024的格式创建训练集
    for i in range(m):
        fileNameStr = trainingFileList[i]                         #获取文件名的名字
        fileStr = fileNameStr.split('.')[0]     #take off .txt    #去掉文件的后缀名
        classNumStr = int(fileStr.split('_')[0])                  #取出文件的数字名
        hwLabels.append(classNumStr)                              #把每个文件对应的分类数字名当做labels存起来
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)   #将所有文本的内容按照格式化存在样本集中
    testFileList = listdir('testDigits')        #iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]     #take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)        #把所有测试文件的内容按照格式化存在测试集中
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)    #调用分类算法,对算法进行测试
        print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))   #打印分类算法预估出来的labels和实际labels
        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)))

kNN中要先加入 from os import listdir 来调用函数listdir()列出给定目录的文件名,这段代码将训练样本的文件名的第一个数字作为样本的类标签,设训练样本的个数为m,首先构建了一个m*1024的训练矩阵,每一行存储一个样本数据向量。利用classify0()函数对测试样本的每一个数据进行分类,并计算错误率.
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/u012414189/article/details/84954077