机器学习实战Ch02_k近邻算法的运行

本文主要针对于第一次运行代码中遇到的问题总结的汇总,由于自己是小白,第一次运行的时候遇到了各种各样的问题,经过多次的尝试终于解决

1 算法概述

 

 

1.1 算法特点

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

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

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

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

标称型目标变量的结果只在有限目标集中取值,如真与假、动物分类集合{ 爬行类、鱼类、哺乳类、两栖类} ;数值型目标变量则可以从无限的数值集合中取值,如0.100、42.001、1000.743 等。

 

1.2 工作原理

存在一个训练样本集,并且每个样本都存在标签(有监督学习)。输入没有标签的新样本数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取出与样本集中特征最相似的数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,而且k通常不大于20。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。(简化:给定训练数据样本和标签,对于某测试的一个样本数据,选择距离其最近的k个训练样本,这k个训练样本中所属类别最多的类即为该测试样本的预测标签。这里的距离一般是欧氏距离。

1.3 k近邻算法的一般流程

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

1.4 从文本文件中解析数据的伪代码

(1)计算样本数据集中的点与当前带分类数据的点之间的距离(计算相似程度)
(2)按照距离递增排序
(3)选取与当前距离最小的前K个点
(4)确定k个点所在类别的出现频率
(5)返回出现频率最高的类别作为预测分类
 
 
k-近邻算法是分类数据最简单有效的算法 k-近邻算法基于实例的学习,使用算法时,必须有接近实际数据的训练样本数据  
k-近邻算法必须保存全部数据集,这样训练数据集很大的话,必须使用大量的存储空间。由于必须对数据集中每个数据计算距离值,实际使用时可能非常耗时  
k-近邻算法的另一个缺陷是无法给出任何数据的基础结构信息,因此无法知晓平均实例样本和典型实例样本具有什么特征 
 

1.5 算法背景

什么是监督学习,什么又是无监督学习呢。监督学习就是我们知道目标向量的情况下所使用的算法,无监督学习就是当我们不知道具体的目标变量的情况下所使用的。而监督学习又根据目标变量的类别(离散或连续)分为分类器算法和回归算法。

k-Nearest Neighbor。k是算法中的一个约束变量,整个算法的总体思想是比较简单的,就是将数据集的特征值看作是一个个向量。我们给程序一组特征值,假设有三组特征值,就可以看做是(x1,x2,x3)。系统原有的特征值就可以看做是一组组的(y1,y2,y3)向量。通过求两向量间的距离,我们找出前k个距离最短的y的特征值对。这些y值所对应的目标变量就是这个x特征值的分类。

 

1.6 python基础之numpy

numpy是python的一个数学计算库,主要是针对一些矩阵运算,这里我们会大量用到它。 介绍一下本章代码中用到的一些功能。

array:是numpy自带的数组表示,比如本例中的4行2列数字可以这样输入
group=array([[9,400],[200,5],[100,77],[40,300]])

shape:显示(行,列)例:shape(group)=(4,2)

zeros:列出一个相同格式的空矩阵,例:zeros(group)=([[0,0],[0,0],[0,0],[0,0]])

tile函数位于python模块 numpy.lib.shape_base中,他的功能是重复某个数组。比如tile(A,n),功能是将数组A重复n次,构成一个新的数组

sum(axis=1)
矩阵每一行向量相加

 

2 代码实现

打开pycharm,创建项目,同时创建一个kNN.py文件,所有的代码都放在这里面。

代码分三部分:创建数据集(createDataSet或者导入数据file2matrix)、数据归一化(autoNorm)、分类函数(classify)

 

2.1 创建数据集和标签

 

#_*_ coding:utf-8 _*_
#上面那一行是为了能够在代码中添加中文注释
from numpy import *#导入科学计算包numpy
import operator#导入运算符模块

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
#上面那一行是为了能够在代码中添加中文注释
from numpy import *#导入科学计算包numpy
import operator#导入运算符模块

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

 

2.2 构造kNN分类器

#第一个kNN分类器  inX-测试数据的输入向量 dataSet-训练样本数据集  labels-标签向量 k-邻近的k个样本
def classify0(inX, dataSet, labels, k):
    # 使用欧氏距离来计算距离
    dataSetSize = dataSet.shape[0]#训练集大小即数组大小
    diffMat = tile(inX, (dataSetSize, 1))-dataSet#把待分类数据向量复制成与训练集等阶,对应项求差
    sqDiffMat = diffMat ** 2#各项平方,上一项是为了这一步,因为欧氏距离就是对应项间的操作
    sqDistance = sqDiffMat.sum(axis=1)#axis=1,将一个矩阵的每一行向量相加(平方和想加) 
    # print(sqdistance)
    distances = sqDistance ** 0.5#开方,求出距离 
#按照距离递增次序排列  计算完所有点之间的距离后,可以对数据按照从小到大的次序进行排序,然后确定前k个距
 
#离最小元素所在的主要分类,输入k总是正整数;最后,将classCount字典分解为元祖列表,然后使用程序第二行导
#入运算符模块的itemgetter方法,按照第二个元素的次序对元祖进行排序  
 
    sortedDistIndex = distances.argsort()#距离从小到大排序,返回数组的索引,因为索引对应标签 
    classCount = {}#字典分解为元祖列表
    # 选择距离最小的k个点 
    for i in range(k):#在字典里每次都是遍历前K个样本训练集,确定前k个距离最小的元素所对应的分类
        voteIlabel = labels[sortedDistIndex[i]]#对应分类
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1#出现过的计数,没出现的找分类
    # 排序 
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    # 此处排序为逆序,按照从大到小
    #输出在前k个里面出现频率最高的元素标签
    return sortedClassCount[0][0]#返回统计最多的分类

 

2.3 代码运行讲解

 
 
 
 
 

因为是在python开发环境下运行代码,需要打开终端,在命令提示符在完成相关操作(我的python安装路径在C:\Python27)。而我下载的pycharm把编写爱的代码默认路径为C:\Users\common\PycharmProjects。接下来需要把python运行环境C:\Python27改变到代码文件kNN.py存储路径C:\Users\common\PycharmProjects\untitled1。

扫描二维码关注公众号,回复: 13505615 查看本文章

操作如下:其中python代码路径,需要导入os文件,os.getcwd()显示当前目录,os.chdir(‘’)改变目录,listdir()显示当前目录的所有文件。

接下来所有的代码都是在上面的那个kNN.py文件中进行添加即可,每当修改一次kNN.py代码文件,运行前都需要reload(kNN)重新加载一下kNN模块。

 

2.4 准备数据(从文本文件解析数据)

文本文件直接复制放在创建的项目(unititled1)下面即可,
可以在pycharm中直接复制

也可以在C:\Users\common\PycharmProjects\untitled1路径下直接拖放

#输入为文件名字符串输出为训练样本矩阵和类标签向量
def file2matrix(filename):
    fr = open(filename)
    arrayOnLines = fr.readlines()#获取所有数据,以行为单位切割
    numberOfLines = len(arrayOnLines)#得到文件行数1000
    returnMat = zeros((numberOfLines, 3))#构建矩阵NumPy(默认二维),以0填充,我们将矩阵另一维设置成固定值3
    classLabelVector = []
    index=0
    for line in arrayOnLines:#循环处理文件每行数据
        line = line.strip()#截取掉所有的回车字符
        listFromLine = line.split('\t')#以tab分割为数组(将上一步得到的整行数据分割成一个元素列表)
        returnMat[index, :] = listFromLine[0:3]#copy数据,选取前3个元素存储到矩阵
        classLabelVector.append(int(listFromLine[-1]))#保存对应的分类(-1表示列表最后一列元素)
    return returnMat, classLabelVector
添加以上代码,然后运行
reload(kNN)
datingDataMat,datingLabels=kNN.file2matrix('datingTestSet2.txt')注意这个引号是英文的
datingDataMat
datingLabels[0:20]

 

2.5 分析数据(使用matplotlib创建散点图)

import matplotlib报错直接cmd安装,不要在python环境下
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.show()
#scatter(x,y,s=1,c="g",marker="s",linewidths=0)  
#s:散列点的大小,c:散列点的颜色,marker:形状,linewidths:边框宽度  
散点图使用datingDataMat矩阵的第2第3列数据,分别表示玩游戏时间百分比和每周冰激凌公升数

重新输入上面所有的代码(从头再来),因为由于没有使用样本分类特征值,为了在图中用不用颜色标记不同样本分类,来更好理解数据信息。scatter函数支持个性化标记散点图的点:
from numpy import array注意不加这个会报错,因为python的array和numpy的array有差别
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()

利用颜色以及尺寸标识了数据点的属性类别,带有养病呢分类标签的约会数据散点图,虽然能够比较容易的区分数据点从属类别,但依然很难根据这张图给出结论性的信息  

 

2.6 准备数据(归一化数据)

为了减少特征值之间因度量不同带来权重的偏差,需要将数据归一化。所谓的归一化,是讲数值范围处理在0~1或-1~1之间。
可以用公式:newValue = (oldValue-min)/(max-min)即当前值减去最小值再除以取值范围
max和min分别是该项数据集特征中的最大值和最小值。
据此可写出归一化函数
def autoNorm(dataSet):
    minVals = dataSet.min(0)#一维数组,值为各项特征(列)中的最小值,1*3。每列的最小值  参数0可以从列中选取最小值而不是选取当前行的最小值
    maxVals = dataSet.max(0)#每列最大值
    ranges = maxVals - minVals#1*3。函数计算可能的取值范围,并创建新的返回矩阵,为了归一化特征值,必须使用当前值减去最小值,然后除以取值范围  
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]#特征值大小为1000*3。注意事项:特征值矩阵有1000*3个值。而minVals和range的值都为1*3.为了解决这个问题使用numpy中tile函数将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet/tile(ranges, (m,1))#特征值相除,使用tile函数使得矩阵大小一致
    return normDataSet, ranges, minVals注意事项:特征值矩阵有1000*3个值。而minVals和range的值都为1*3.为了解决这个问题使用numpy中tile函数将变量内容复制成输入矩阵同样大小的矩阵
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet/tile(ranges, (m,1))#特征值相除,使用tile函数使得矩阵大小一致
    return normDataSet, ranges, minVals
 
在代码中设定一个计数器变量,每次分类器错误的分类数据,计数器就+1,程序执行完成后计算器的结果除以数据点总数即为错误率  
运行代码
reload(kNN)
normMat,ranges,minVals=kNN.autoNorm(datingDataMat)
normMat
ranges
minVals

 

2.7 测试算法(完整程序验证分类器)

通常只用90%的数据来训练分类器,剩余数据去测试分类器,获取正确/错误率。
def datingClassTest():
    hoRatio = 0.50  # hold out 10%
    datingDataMat, datingLabels = file2matrix('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)
        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)
我们可以改变hoRatio和k的值来检测错误率有无变化。
运行代码:
reload(kNN)
kNN.datingClassTest()

 

2.8 使用算法(约会网站预测)

def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(raw_input(\
        "percentage of time spent playing video games?"))
    ffMiles = float(raw_input("frequent flier miles earned per year?"))#raw_input允许用户输入文本行命令并返回用户输入的命令
    iceCream = float(raw_input("liters of ice cream consumed per year?"))
    datingDataMat, datingLabels = file2matrix('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 person: ", \
        resultList[classifierResult - 1]

运行代码
reload(kNN)
kNN.classifyPerson()

 

3 手写识别系统

构造的系统只能识别数字0~9,需要是别的数字已经使用图像处理软件,处理成具有相同的色彩和大小:  
宽高是32*32的黑白图像  
 

1、收集数据 提供文本文件  
2、准备数据 编写函数classify0(),将图像格式转换成分类器使用的list格式  
3、分析数据 在Python命令提示符中检查数据,确保它符合要求  
4、训练算法 此步骤不适合k-近邻算法  
5、测试算法 测试样本和非测试样本区别在于:测试样本已经完成分类的数据,如果预测分类与实际类别不同,则标为error  
6、使用算法 

 

3.1 准备数据

之家将训练数据和测试数据复制到代码路径下
#将图像转换为向量,以使用前面的分类器来处理数字图像信息
def img2vector(filename):
    returnVect = zeros((1, 1024))#创建1*1024的numpy数组
    fr = open(filename)
    for i in range(32):#循环读出文件的前32行
        lineStr = fr.readline()
        for j in range(32):#将每行的头32个字符值存储在numpy数组中,最后返还给数组
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVect
运行代码

reload(kNN)

testVector=kNN.img2vector('testDigits/0_13.txt')

testVector[0,0:31]

testVector[0,32:63]

 

3.2 测试算法

首先在代码最前面加上 from os import listdir(他可以列出给定目录的文件名)
# 手写识别系统测试代码
def handwritingClassTest():
    hwLabels = []
    trainingFileList = dir('trainingDigits')#获取testDigits目录中的文件内容存储在列表中
    m = len(trainingFileList)#得到目录中有多少文件,便将其存储到变量m中
    trainingMat = zeros((m, 1024))#创建一个m*1024的训练矩阵,该矩阵的每行数据存储一个图像,可以从文件名中解析出分类数字
    for i in range(m):
        fileNameStr = trainingFileList[i] #分割得到标签  从文件名解析得到分类数据
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr) #测试样例标签,该目录下的文件按照规则命名,如文件9_45.txt的分类是9,它是数字9的第45个实例
#将类代码存储在hwLabels向量中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr )#使用img2vector载入图像 
    #对testDigits目录中的文件执行相似的操作,不同之处在于我们并不将这个目录下的文件载入矩阵中  
    #而是利用classify0()函数测试该目录下每个文件,由于文件中的值已在0~1之间,所以不需要autoNorm()函数 
    testFileList = dir('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('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print ("the classifier came back with: %d, the real answer is: %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)))
运行代码

reload(kNN)

kNN.handwritingClassTest()

该算法执行效率不高,因为算法需要为每个测试向量做2000词距离计算,每个距离计算包括了1024个维度浮点计算,总计执行900次,此外还需要为向量准备2M的存储空间 k决策树是k-近邻算法的改进版  

所有的代码操作:

cmd
cd c:\python27
python
import os
os.getcwd()
os.chdir("C:\\Users\\common\\PycharmProjects\\1kNN")注意不同的代码这个位置不同
os.getcwd()
import kNN
group, labels = kNN.createDataSet()
group
labels
reload(kNN)
kNN.classify0([0,0],group,labels,3)
datingDataMat,datingLabels=kNN.file2matrix('datingTestSet2.txt')注意这个引号是英文的,否则报错
datingDataMat
datingLabels[0:20]

import matplotlib报错的时候,直接重新打开cmd安装,不要在python环境下
import matplotlib.pyplot as plt
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.show()

from numpy import array注意不加这个会报错
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()

normMat,ranges,minVals=kNN.autoNorm(datingDataMat)
normMat
ranges
minVals
reload(kNN)
kNN.datingClassTest()
reload(kNN)
kNN.classifyPerson()
testVector=kNN.img2vector('testDigits/0_13.txt')
testVector[0,0:31]
testVector[0,32:63]
reload(kNN)

kNN.handwritingClassTest()

 


 
 
 

猜你喜欢

转载自blog.csdn.net/lj2048/article/details/78530632
今日推荐