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

来源:互联网 发布:巫师3新手必知 编辑:程序博客网 时间:2024/06/05 19:40

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

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 Neighbork是算法中的一个约束变量,整个算法的总体思想是比较简单的,就是将数据集的特征值看作是一个个向量。我们给程序一组特征值,假设有三组特征值,就可以看做是(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 *#导入科学计算包numpyimport 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



操作如下:其中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

在代码中设定一个计数器变量,每次分类器错误的分类数据,计数器就+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()









原创粉丝点击