《机器学习实战》笔记——第二章:k-近邻算法(kNN)实战

1 说明

该书主要以原理简介+项目实战为主,本人学习的主要目的是为了结合李航老师的《统计学习方法》以及周志华老师的西瓜书的理论进行学习,从而走上机器学习的“不归路”。因此,该笔记主要详细进行代码解析,从而透析在进行一项机器学习任务时候的思路,同时也积累自己的coding能力。
正文由如下几部分组成:
1、实例代码(详细注释)
2、知识要点(函数说明)
3、调试及结果展示

2 正文

(1)准备:使用python导入数据

1、将如下内容写入kNN.py文件:

from numpy import *
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

知识要点:
①operator模块:该模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。接下的代码会用到该模块中的一个非常重要的方法:itemgetter

2、打开python交互式开发环境,执行以下命令并得到结果:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>import kNN
>>>group, labels = kNN.createDataSet()
>>>group
array([[1. , 1.1],
       [1. , 1. ],
       [0. , 0. ],
       [0. , 0.1]])
>>>labels
['A', 'A', 'B', 'B']

(2)实施kNN分类算法

1、在kNN.py文件中添加如下代码,该函数用于k-近邻算法的实现,其中4个输入分别是:输入向量inX(欲进行分类的数据),输入的训练样本集dataSet,标签向量labels以及所选k值。具体实现如下代码所示:

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]#获取训练样本集的行数,即样本个数
    diffMat = tile(inX, (dataSetSize,1)) - dataSet#利用tile函数将inX向量构造成一个和dataset有相同行数列数的矩阵,并与之相减
    sqDiffMat = diffMat**2#各个元素分别平方
    sqDistances = sqDiffMat.sum(axis=1)#按列求和,即得到了每一个距离的平方
    distances = sqDistances**0.5#各个元素开平方即得到了距离矩阵
    sortedDistIndicies = distances.argsort()#把向量中每个元素进行排序,而它的结果是元素的索引形成的向量
    classCount={}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]#把按值大小顺序排列的欧氏距离索引list前k个对应的labels遍历出来
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#统计labels中各类出现的频次,以字典的形式输出
    #分解为元组列表,operator.itemgetter(1)按照第二个元素的次序对元组进行排序,reverse=True是逆序,即按照从大到小的顺序排列
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

知识要点:
①tile():tile函数位于python模块 numpy中,其功能是重复某个数组,从而形成新的数组。
②argsort():tile函数位于python模块 numpy中,其功能是将目标数组中的元素从小到大排列,提取其对应的index(索引),然后输出。
③sorted():sorted函数是python的内置函数,用来做排序任务,该函数可以对list按一定的规则进行排序。
④items():Python 字典(Dictionary) items() 函数以列表返回可遍历的(键, 值) 元组数组。返回值类型为dict_items。
⑤itemgetter():operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号(即需要获取的数据在对象中的序号),其定义的是一个函数,通过该函数作用到对象上才能获取值。

2、我们假设现在有个待测试数据(0,0),k值选择3,下面就来预测一下该数据所在分类是什么,还是接着之前的python交互式开发环境,所得结果是B分类:

******
>>>kNN.classify0([0,0], group, labels, 3)
'B'

(3)案例1-使用k-近邻算法改进约会网站的配对效果

1、书中案例给定了3个维度的特征,共计1000组,存放在工程根目录下的datingTestSet2.txt文本文件下。在将特征数据输入到分类器之前,需要将待处理数据的格式改变为分类器可以接受的格式。案例中定义了file2matrix函数,该函数的输入为文本文件名字符串,输出为训练样本矩阵和类标签向量。具体实现代码如下:

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())#获取文件的行数
    returnMat = zeros((numberOfLines,3))#构造返回的矩阵
    classLabelVector = []#构造返回的labels列表
    fr = open(filename)#此处为何还要开一次呢?
    index = 0
    for line in fr.readlines():
        line = line.strip()#按行去除头尾字符、空白符(包括\n、\r、\t、' ',即:换行、回车、制表符、空格)
        listFromLine = line.split('\t')#拆分字符串,通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(list)
        returnMat[index,:] = listFromLine[0:3]#通过切片操作抽取特征向量
        classLabelVector.append(int(listFromLine[-1]))#获取labels
        index += 1
    return returnMat,classLabelVector

知识要点:
①open():open() 函数是python内置的file对象中的一个方法,用于打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写。
②readlines():用于读取所有行(直到结束符 EOF)并返回列表,该列表可以由 Python 的 for… in … 结构进行处理。如果碰到结束符 EOF 则返回空字符串。
③strip():用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
④split():str.split(str="", num=string.count(str))。通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num+1 个子字符串。

2、在python交互开发环境中执行的命令和结果如下:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>from kNN import *
>>>datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
>>>datingDataMat
>>>array([[4.0920000e+04, 8.3269760e+00, 9.5395200e-01],
          [1.4488000e+04, 7.1534690e+00, 1.6739040e+00],
          [2.6052000e+04, 1.4418710e+00, 8.0512400e-01],
          ...,
          [2.6575000e+04, 1.0650102e+01, 8.6662700e-01],
          [4.8111000e+04, 9.1345280e+00, 7.2804500e-01],
          [4.3757000e+04, 7.8826010e+00, 1.3324460e+00]])
>>>datingLabels[0:20]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]

此处我是重新打开了交互环境,如果你是按着书上的步骤一步一步做下来的,需要reload我们创建的kNN模块。而书中例程用的是python2,reload还是属于python内置的。如果我们用的是python3,那么就要通过下面的方式进行重载:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>from importlib import reload
>>>reload(kNN)

3、分析数据
使用Matplotlib创建散点图,以便辨识出一些数据模式。在python交互开发环境下输入命令:

>>>import matplotlib
>>>import matplotlib.pyplot as plt
>>>fig = plt.figure()
>>>ax = fig.add_subplot(111)
>>>ax.scatter(datingDataMat[:,1], datingDataMat[:,2])
<matplotlib.collections.PathCollection object at 0x0000022E5294ABE0>
>>>plt.show()

知识要点:
①figure():创建一个图形实例对象。
②add_subplot(111):就是在一张figure里面生成子图,参数111的意思是:将画布分割成1行1列,图像画在从左到右从上到下的第1块。
③datingDataMat[:,1], datingDataMat[:,2]:这两句分别表示了datingDataMat矩阵的第二、第三列数据,分别表示特征值“玩视频游戏所消耗的时间百分比”和“每周所消费的冰激凌公升数”。
④scatter(): matplotlib模块中的绘制散点图的函数,功能很强大,具体参数和使用方法就不展开了。

以上命令得到散点图如下所示:
《机器学习实战》笔记——第二章:k-近邻算法(kNN)实战_第1张图片
上图是没有样本类别标签的约会数据散点图,难以辨识图中的点究竟属于哪个类别分类,同时也很难提取到有用的信息。根据书中例程,我们采用scatter函数通过色彩和点的大小对数据做个性化标记。这里我是另外创建了一个plottest.py文件:

# -*- coding: utf-8 -*-

from kNN import *
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

font_set = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=10)#设置轴标签所需的字体信息
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
fig = plt.figure()#创建一个图形对象

ax = fig.add_subplot(211)
plt.xlabel("玩视频游戏所消耗时间百分比", fontproperties=font_set)
plt.ylabel("每周消费的冰激凌公升数", fontproperties=font_set)
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))

bx = fig.add_subplot(212)
plt.xlabel("每年获取的飞行常客里程数", fontproperties=font_set)
plt.ylabel("玩视频游戏所消耗时间百分比", fontproperties=font_set)
bx.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))

plt.show()

执行后得到下图,从图中可以很明显看出,采用第一和第二列属性相较于第二和第三列可以得到更好的展示效果:
《机器学习实战》笔记——第二章:k-近邻算法(kNN)实战_第2张图片
4、对数据进行归一化处理
对多维的特征数据进行归一化处理主要由两大好处:提升模型的收敛速度、提升模型的精度。提升模型的收敛速度:比如我们在做LR的时候,特征向量中不同特征的取值相差较大,会导致目标函数变“扁”,而归一化处理能够使得目标函数变“圆”,这样的话,我们在进行梯度下降的时候,梯度的方向就不会过多地偏离最小值的方向,会少走很多弯路,使得训练时间大大缩短。
而在我们KNN算法中,对数据进行归一化处理,主要是为了消除量纲差异导致的个别特征对计算结果影响过大,从而提高最终的模型精度。
这里采用的归一化方法为min-max标准化方法:
n e w V a l u e = o l d V a l u e − m i n m a x − m i n newValue=\frac{oldValue-min}{max-min} newValue=maxminoldValuemin
通过代码实现如下:

def autoNorm(dataSet):
    minVals = dataSet.min(0)#返回矩阵中每一列的最小值
    maxVals = dataSet.max(0)#返回矩阵中每一列的最大值
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))#构造一个shape和dataSet相同的全零矩阵
    m = dataSet.shape[0]#获取dataSet行数
    normDataSet = dataSet - tile(minVals, (m,1))#利用tile函数将minVals构造成一个和dataSet有相同行数列数的矩阵,并与之相减得到min-max标准化分子部分
    normDataSet = normDataSet/tile(ranges, (m,1))#利用tile函数将ranges构造成一个和dataSet有相同行数列数的矩阵得到分母部分,并完成整个数据集的归一化处理
    return normDataSet, ranges, minVals

知识要点:
①常见的归一化方法:min-max标准化(Min-max normalization)/0-1标准化(0-1 normalization)/线性函数归一化/离差标准化。具体实现方法,此处不作展开。
②min()/max():返回给定参数的最小/大值,参数可以为序列。

打开python交互开发环境,通过下面命令输出归一化结果:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>from kNN import *
>>>datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
>>>normMat, ranges, minVals = autoNorm(datingDataMat)
>>>normMat
array([[0.44832535, 0.39805139, 0.56233353],
       [0.15873259, 0.34195467, 0.98724416],
       [0.28542943, 0.06892523, 0.47449629],
       ...,
       [0.29115949, 0.50910294, 0.51079493],
       [0.52711097, 0.43665451, 0.4290048 ],
       [0.47940793, 0.3768091 , 0.78571804]])
>>>ranges
array([9.1273000e+04, 2.0919349e+01, 1.6943610e+00])
>>>minVals
array([0.      , 0.      , 0.001156])

5、测试数据
模型基本都搞定了,现在进入最激动人心的时刻,让我们开始评估一下算法的性能如何。例程中选取的是数据集前10%的样本作为测试数据,当然你还可以采用其他的抽取比例或者抽取方式,这里我们还是按照书上的抽取方式来定义我们的datingClassTest函数:

def datingClassTest():
    hoRatio = 0.10#取10%作为测试样本
    datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')#加载数据和标签
    normMat, ranges, minVals = autoNorm(datingDataMat)#归一化处理后载入
    m = normMat.shape[0]#获取normMat矩阵行数
    numTestVecs = int(m*hoRatio)#计算测试样本个数
    errorCount = 0.0#初始化错误计数器
    for i in range(numTestVecs):
        #取前numTestVecs个样本作为测试数据,依次导入classify0函数进行分类处理
        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#如果结果不对则错误计数器+1
    print("the total error rate is: %f" % (errorCount/float(numTestVecs)))#输出错误率
    print(errorCount)

打开python交互式开发环境,输入如下命令,得到最终的错误率为5%:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>import kNN
>>>kNN.datingClassTest()
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
...,
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 1
the total error rate is: 0.050000
5.0

通过改变datingClassTest内变量hoRatio或k的值,错误率将会随着变量的变化而变化:
当hoRatio=0.2,k=3时,error rate=0.080000;
当hoRatio=0.3,k=3时,error rate=0.083333;
当hoRatio=0.4,k=3时,error rate=0.077500;
当hoRatio=0.5,k=3时,error rate=0.066000;
当hoRatio=0.5,k=10时,error rate=0.064000;

6、使用算法:构建完整可用系统
在kNN.py中定义如下函数,用来对用户输入的样本进行类别的预测:

#使用算法预测输入样本的类别
def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent playing video games?"))#输入参数1
    ffMiles = float(input("freguent flier miles earned per year?"))#输入参数2
    iceCream = float (input("liters of ice cream consumed per year?"))#输入参数3
    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])#输出分类结果

在python交互开发环境下,输入运行即可得到相应的预测结果:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>import kNN
>>>kNN.classifyPerson()
percentage of time spent playing video games?>? 10
freguent flier miles earned per year?>? 10000
liters of ice cream consumed per year?>? 0.5
You will probably like this person:  in small doses

(4)案例2-使用k-近邻算法构建手写识别系统

1、准备数据:将图像转换为测试向量
要使用classify0分类器,需要将图像统一格式化处理为一个向量,下面我们通过定义img2vector函数将32x32的二进制图像矩阵转换为1x1024的向量:

def img2vector(filename):
    returnVect = zeros((1,1024))#初始化一个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])#将文件中的字符值逐个读入Numpy数组中
    return returnVect

在python命令行中输入下列命令测试img2vector函数:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>from kNN import *
>>>testVector = img2vector('testDigits/0_13.txt')
>>>testVector[0,0:31]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1.,
       1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
>>>testVector[0,32:63]
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1.,
       1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

2、测试算法:使用k-近邻算法识别手写数字
我们将数据处理完成后,就可以来进行算法测试了。首先我们根据书中提示,将os模块中的listdir导入,以便列出给定目录的文件名。

from os import listdir

知识要点:
①listdir方法:os.listdir(path)方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。这个列表以字母顺序。 它不包括 ‘.’ 和’…’ 即使它在文件夹中。只支持在 Unix, Windows 下使用。

定义手写数字识别系统的测试代码:

def handwritingClassTest():
    hwLabels = []#初始化一个列表
    trainingFileList = listdir('trainingDigits')#通过os.listdir获取目标文件夹中的文件列表
    m = len(trainingFileList)#获取文件个数
    trainingMat = zeros((m,1024))#初始化数据集矩阵
    for i in range(m):
        fileNameStr = trainingFileList[i]#逐个获取文件名
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])#通过这两步对文件名称进行操作,逐个获取标签
        hwLabels.append(classNumStr)#将标签值逐个存入列表
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)#对每个文件逐个进行数据转换,构建数据集矩阵
    testFileList = listdir('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)#将测试数据文件逐个转换成1x1024的向量
        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#如果结果不对则错误计数器+1
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount/float(mTest)))#输出错误率

在python命令提示符中输入如下命令,测试该函数的输出结果为:

******
PyDev console: starting.
Python 3.6.7 |Anaconda, Inc.| (default, Oct 28 2018, 19:44:12) [MSC v.1915 64 bit (AMD64)] on win32
>>>from kNN import *
>>>handwritingClassTest()
the classifier came back with: 0, the real answer is: 0
the classifier came back with: 0, the real answer is: 0
the classifier came back with: 0, the real answer is: 0
...,
the classifier came back with: 6, the real answer is: 5
the classifier came back with: 5, the real answer is: 5
the classifier came back with: 5, the real answer is: 5
...,
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9

the total number of errors is: 10

the total error rate is: 0.010571

从上面的输出可以看到,我们得到的错误率为0.010571。改变变量k的值或者修改函数handwritingClassTest随机选取训练样本、改变训练样本的数目,都可以对其错误率产生影响,此处就不展开了,可以参考上一个案例。

(5)本章小结

结合书中的论述及自己的理解(如果不对,麻烦帮忙指出),k-近邻算法主要有如下优缺点:
1、优点:
①准确度高,对数据没有假设,对outlier不敏感;
②理论成熟,既可以用来做分类也可以用来做回归;
③计算时间和空间线性于训练集的规模;
④重新训练的代价较低;

2、缺点:
①计算量大,执行效率不高;
②必须保存全部数据集,需要大量的内存;
③样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
④输出的可解释性不强,无法给出任何数据的基础结构信息。

知识要点:
①k-NN是一种基本分类与回归方法,其中作为分类方法,其输出还可以取多类。
②k-NN三个基本要素:k值的选择、距离度量、分类决策规则。
③k-NN的特殊情况是当k=1时,称为最近邻算法。
④特征空间中,对每个训练实例点xi,距离该点比其他点更近的所有点组成的一个区域,叫作单元(cell)
⑤距离度量的方法有欧氏距离、Lp距离或Minkowski距离。
⑥取较小的k值,会使得近似误差减小,而估计误差增大,预测结果对实例点十分敏感,易发生过拟合;而较大的k值会使得估计误差减小,近似误差增大,如果k太大,会导致模型太过简单,忽略训练实例中的大量有用信息。在应用中,k值一般取一个比较小的数值,通常采用交叉验证法来选取最优k值。
⑦k-NN最简单的实现方式是线性扫描,但是当训练集很大的时候,计算十分耗时。为了提高k近邻搜索的效率,可以采用特殊的结构存储训练数据的方法来减少计算距离的次数,比如**kd树(kd tree)**方法。

3 完整代码

'''
Created on Sep 16, 2010
kNN: k Nearest Neighbors

Input:      inX: vector to compare to existing dataset (1xN)
            dataSet: size m data set of known vectors (NxM)
            labels: data set labels (1xM vector)
            k: number of neighbors to use for comparison (should be an odd number)
            
Output:     the most popular class label

@author: pbharrin

'''
from numpy import *
import operator
from os import listdir

#KNN分类器
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]#获取训练样本集的行数,即样本个数
    diffMat = tile(inX, (dataSetSize,1)) - dataSet#利用tile函数将inX向量构造成一个和dataset有相同行数列数的矩阵,并与之相减
    sqDiffMat = diffMat**2#各个元素分别平方
    sqDistances = sqDiffMat.sum(axis=1)#按列求和,即得到了每一个距离的平方
    distances = sqDistances**0.5#各个元素开平方即得到了距离矩阵
    sortedDistIndicies = distances.argsort()#把向量中每个元素进行排序,而它的结果是元素的索引形成的向量
    classCount={}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]#把按值大小顺序排列的欧氏距离索引list前k个对应的labels遍历出来
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#统计labels中各类出现的频次,以字典的形式输出
    #分解为元组列表,operator.itemgetter(1)按照第二个元素的次序对元组进行排序,reverse=True是逆序,即按照从大到小的顺序排列
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    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())#获取文件的行数
    returnMat = zeros((numberOfLines,3))#构造返回的矩阵
    classLabelVector = []#构造返回的labels列表
    fr = open(filename)
    index = 0
    for line in fr.readlines():
        line = line.strip()#按行去除头尾字符、空白符(包括\n、\r、\t、' ',即:换行、回车、制表符、空格)
        listFromLine = line.split('\t')#拆分字符串,通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(list)
        returnMat[index,:] = listFromLine[0:3]#通过切片操作抽取特征向量
        classLabelVector.append(int(listFromLine[-1]))#获取labels
        index += 1
    return returnMat,classLabelVector

#归一化处理
def autoNorm(dataSet):
    minVals = dataSet.min(0)#返回矩阵中每一列的最小值
    maxVals = dataSet.max(0)#返回矩阵中每一列的最大值
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))#构造一个shape和dataSet相同的全零矩阵
    m = dataSet.shape[0]#获取dataSet行数
    normDataSet = dataSet - tile(minVals, (m,1))#利用tile函数将minVals构造成一个和dataSet有相同行数列数的矩阵,并与之相减得到min-max标准化分子部分
    normDataSet = normDataSet/tile(ranges, (m,1))#利用tile函数将ranges构造成一个和dataSet有相同行数列数的矩阵得到分母部分,并完成整个数据集的归一化处理
    return normDataSet, ranges, minVals

#测试算法,得出错误率
def datingClassTest():
    hoRatio = 0.10#取10%作为测试样本
    datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')#加载数据和标签
    normMat, ranges, minVals = autoNorm(datingDataMat)#归一化处理后载入
    m = normMat.shape[0]#获取normMat矩阵行数
    numTestVecs = int(m*hoRatio)#计算测试样本个数
    errorCount = 0.0#初始化错误计数器
    for i in range(numTestVecs):
        #取前numTestVecs个样本作为测试数据,依次导入classify0函数进行分类处理
        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#如果结果不对则错误计数器+1
    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?"))#输入参数1
    ffMiles = float(input("freguent flier miles earned per year?"))#输入参数2
    iceCream = float (input("liters of ice cream consumed per year?"))#输入参数3
    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])#输出分类结果

#手写识别系统:将图像转换为测试向量
def img2vector(filename):
    returnVect = zeros((1,1024))#初始化一个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])#将文件中的字符值逐个读入Numpy数组中
    return returnVect

#测试算法:使用KNN算法识别手写数字
def handwritingClassTest():
    hwLabels = []#初始化一个列表
    trainingFileList = listdir('trainingDigits')#通过os.listdir获取目标文件夹中的文件列表
    m = len(trainingFileList)#获取文件个数
    trainingMat = zeros((m,1024))#初始化数据集矩阵
    for i in range(m):
        fileNameStr = trainingFileList[i]#逐个获取文件名
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])#通过这两步对文件名称进行操作,逐个获取标签
        hwLabels.append(classNumStr)#将标签值逐个存入列表
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)#对每个文件逐个进行数据转换,构建数据集矩阵
    testFileList = listdir('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)#将测试数据文件逐个转换成1x1024的向量
        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#如果结果不对则错误计数器+1
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount/float(mTest)))#输出错误率

你可能感兴趣的:(机器学习)