机器学习实战笔记--kNN


本文为《机器学习实战》学习笔记


1. 相关数据类型&函数介绍

SciPy 基于Python生态系统提供了数学运算、科学和工程的开源软件,主要包括基本N维数组包NumPy,科学计算基本库SciPy library, 用于2D绘图的Matplotlib,交互式控制台IPython,用于符号数学Sympy,用于数据结构和分析的pandas。

1.1 NumPy

NumPy是python科学计算的基础包。包括强大的N维数组对象;复杂的函数;集成C/C++和Fortran代码的工具;线性代数、傅里叶变换和随机数功能。
标准python库中的array是一维的,功能更少。numpy的数组是ndarray,可以是多维的,拥有更多功能和属性。ndarray的主要属性如下:
ndim – 数组维度
shape – 数组形状,使用tuple表示数组在各个维度的大小,对于一个n行m列的数组,返回值为(n, m),该元组的长度为数组维度的数量,即ndim
size – 数组中元素的总数
dtype –数组中元素的类型
ndarray的基本运算如下:
A * B表示两个数组对应位置的元素相乘
A.dot(B) / dot(A, B) 表示矩阵积
A.sum(axis = ) 对数组元素求和。A.sum()表示对数组中的所有元素求和,axis = 0表示对每一列求和,axis = 1表示对每一行求和。
不同数据类型的数据运算时,结果向上转型(upcasting)

1.2 Matplotlib

Matplotlib是Python 2D绘图库。matplotlib.pyplot集合了命令行风格的函数,使得Matplotlib能够像MATLAB一样工作。每个pyplot函数都会导致图片的一些改变。

1.3 operator

operator模块包含了Python的标准操作函数,也定义了用于通用属性和item查找的工具。

1.4 io

io模块主要提供处理多种类型I/O的工具。

1.5 os

os模块与依赖操作系统的功能有关。其中,通过open()函数读写文件,os.path模块提供了操作路径的方法,fileinput模块能够在命令行中读取所有文件的所有行,tempfile模块提供创建临时文件和目录的操作,shutil模块提供高级的文件和目录处理。

2. kNN算法

2.1 构造数据集

#构造数据集
from numpy import *
def createDataSet():
    #group为样本,labels为样本对应标签
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

numpy.array(object, dtype=None, copy=True, order=’K’, subok=False, ndmin=0)建立数组。object为数组,dtype指定数据类型,ndmin指定最小维度

2.2 kNN分类器

#kNN算法
#inX为待分类向量,dataSet为训练样本集, labels为标签,k
def kNN(inX, dataSet, labels, k):
    #计算待分类向量与所有训练样本的欧氏距离
    dataSetSize = dataSet.shape[0] #获得训练样本的个数
    difMat = tile(inX, (dataSetSize, 1)) - dataSet #待分类向量与所有训练样本的差值矩阵
    sqDifMat = difMat ** 2  #差值平方
    sqDistances = sqDifMat.sum(axis = 1) # 对每行求和
    distances = sqDistances ** 0.5 #开根号,得到待分类向量与所有训练样本的距离
    sortedDistIndicies = distances.argsort() #得到使距离有序的索引数组
    #统计k个最近邻样本的标签数
    classCounts = {} #使用字典记录不同类别的样本数
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCounts[voteIlabel] = classCounts.get(voteIlabel, 0) + 1
    #按照标签的数量对标签逆序排序
    sortedClassCount = sorted(classCounts.items(), key = operator.itemgetter(1), reverse = True)
    return sortedClassCount[0][0]

numpy.tile(A, reps)函数通过将A重复reps次,构造数组。A为输入的数组,reps为A在每个维度上的重复次数。
numpy.argsort(a, axis=-1, kind=’quicksort’, order=None)返回能够使数组有序的索引数组index_array,a[index_array]使数组a有序。a为待排序数组;axis为排序维度,默认为-1,表示最后一个维度;kind为排序算法,可为’quicksort’, ‘mergesort’,’heapsort’;order为指定的顺序,当a具有字段定义时有效。
sorted(iterable[, key][, reverse])返回可迭代对象iterable的有序列表,是稳定排序。key为函数,指定用于比较的自变量,默认为None,表示直接进行排序;reverse为bool变量,指定是否逆序。
operator.itemgetter(item)返回可调用的对象,实现operator._getitem_(a, b)函数的功能,返回a在索引b处的值,item即为b。如果指定多个对象,返回元组。operator.itemgetter函数获取的不是值,而是定义了一个函数,通过该函数作用到对象上才能获取值。
如,f = operator.itemgetter(2, 3)定义函数f,获取索引(2, 3)处的值, 调用f(r)返回(r[2], r[3])

3. 使用kNN改进约会网站的配对效果

3.1 从文本文件中解析数据

文本文件中的数据如下所示:
机器学习实战笔记--kNN_第1张图片
需要将待处理数据改为分类器可接受的格式,即每个样本的特征为一行,存入数组,所有样本的标签存入列表。

#将文本转换为特征矩阵和标签列表
def file2matrix(filename):
    fr = open(filename, 'r')
    try:
        #得到文件的行数
        arrayOfLines = fr.readlines()   #按行读入文件,存入list
        numberOfLines = len(arrayOfLines)   #得到文件的行数
        #创建返回的Numpy特征矩阵
        returnMat = zeros((numberOfLines, 3)) #全0填充指定大小的矩阵
        classLabelVector = []
        #解析文本
        index = 0
        for line in arrayOfLines:
            line = line.strip() #去掉文本前后的空白符
            listFromLine = line.split('\t') #用'\t'划分整行数据,得到元素列表
            returnMat[index, :] = listFromLine[0 : 3]  
            classLabelVector.append(listFromLine[-1])   
            index += 1
    finally:
        fr.close()
    return returnMat, classLabelVector

numpy.zeros(shape, dtype=float, order=’C’)返回给定形状和数据类型,用0填充的数组
io.readline(size=-1)从流中读并返回1行,如果指定size,最多读入size字节。
io.readlines(hint=-1)从流中读入并返回行组成的列表,可以指定hint控制读入行的数量。
str.strip([chars])拷贝并移除str首尾的chars的任意组合。chars缺失,移除首尾空格。例如,

>>> strA = '  www.helloworld.com  '
>>> strB = strA.strip()
>>> strC = strB.strip('.wcdo') #移除首尾'.wcdo的任意组合'
>>> strA
'  www.helloworld.com  '
>>> strB
'www.helloworld.com'
>>> strC
'helloworld.com'

3.2 分析数据–图形化展示数据

图形化展示数据更直观,有助于了解数据的真实含义。可使用Matplotlib制作原始数据的散点图。

#分析数据--画散点图
import matplotlib.pyplot as plt
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']    #动态设置添加中文黑体
mpl.rcParams['axes.unicode_minus'] = False  #更改字体导致显示不出负号,所以设置为true,保证负号的显示
fig = plt.figure()  #创建图
ax1 = fig.add_subplot(211)   #创建子图,ax1位于两行一列的第一个
#在子图区域绘制散点图,后两项为不同的标签设置不同的节点大小和颜色
#将list转换为array,才能给每项*15,否则为将list重复15次
ax1.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0 * array(datinglabels), 15.0 * array(datinglabels))
plt.xlabel('玩游戏所耗时间百分比')
plt.ylabel('每周消费的冰淇淋公升数')
#添加图例,将不同类型的点分开添加到散点图中
ids_1 = []
ids_2 = []
ids_3 = []
for i in range(len(datinglabels)):
    if datinglabels[i] == 1:
        ids_1.append(i)
    elif datinglabels[i] == 2:
        ids_2.append(i)
    else:
        ids_3.append(i)
ax2 = fig.add_subplot(212)  #ax2位于两行一列的第二个
ax2.scatter(datingDataMat[ids_1, 0], datingDataMat[ids_1, 1], marker = 'x', c = 'g', label = '极具魅力')
ax2.scatter(datingDataMat[ids_2, 0], datingDataMat[ids_2, 1], marker = '+', c = 'y', label = '魅力一般')
ax2.scatter(datingDataMat[ids_3, 0], datingDataMat[ids_3, 1], marker = 'o', c = 'b', label = '不喜欢')
plt.ylabel('玩游戏所耗时间百分比')
plt.xlabel('每年获取的飞行常客里程数')
ax2.legend(loc = 4) #4等价于'lower right'
plt.show()

figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True, **kwargs)创建新的图片,所有参数均可选,不指定则设为默认值。num为图片的id,可以为整数或字符串,如果不指定num,默认递增,如果指定的num已存在,将会激活对应图片,否则创建新图片;figsize指定图片宽度和长度(以英寸为单位),整数元组;dpi指定图片分辨率,整数;facecolor指定背景颜色;edgecolor指定边界颜色。
add_subplot(*args, **kwargs)添加子图。包含若干参数,数字’ABC’或’A, B, C’表示将figure划分为A行,B列,对划分后的区域按行从1编码到A*B,当前plot为编码为C的区域。
subplot()函数创建子图,但会把之前存在的重叠子图覆盖。而add_subplot()激活之前存在的子图。
scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, hold=None, data=None, **kwargs)绘制x, y的散点图,x, y的形状均为(n,);s为点的大小;c为点的颜色,当使用RGB时,c应为2维数组,行为RGB。
legend(*args, **kwargs)为axes添加图例,loc表示图例的位置,可用数字0-10和对应字符串表示:

Location String Location Code
‘best’ 0
‘upper right’ 1
‘upper left’ 2
‘lower left’ 3
‘lower right’ 4
‘right’ 5
‘center left’ 6
‘center right’ 7
‘lower center’ 8
‘upper center’ 9
‘center’ 10

还有许多参数可用来设置列数,背景色等。当图中包含不同类型的数据,添加图例时,需要为不同类型的数据设置标签分别添加到axes中,然后在legend函数中设置图例的样式。
结果如图所示:
机器学习实战笔记--kNN_第2张图片

3.3 准备数据–归一化数值

在计算距离时,特征值大的属性对结果的影响越大。如果认为所有的属性重要程度相同,可以对所有的属性进行归一化,使得它们的取值范围相同。使用最大最小值归一化,能够使结果在[0,1]区间。

newValue=(oldValuemin)maxmin

#归一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0) #从每列中取最小值
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    rows = dataSet.shape[0]     #数据集的行数
    normDataSet = dataSet - tile(minVals, (rows, 1))    #分子
    normDataSet = normDataSet / tile(ranges, (rows, 1))  #归一化后结果
    return normDataSet, ranges, minVals

3.4 测试算法

使用错误率评估分类器的性能,即分类器给出的错误结果次数除以测试数据的总数。通常从已有数据中选择90%的数据作为训练样本训练分类器,其余10%的样本测试分类器,检测分类器的正确率。
在函数中,使用errorCount对错误分类结果计数,除以预测的总数即可得到错误率。

def datingClassTest(filename, holdRatio = 0.10):
    datingDataMat, datinglabels = file2matrix(filename) #解析文本文件
    normDataSet, ranges, minVals = autoNorm(datingDataMat)
    rows = datingDataMat.shape[0]
    numTestVecs = int(rows * holdRatio)    #得到验证集的数量
    #预测并对错误结果计数
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = kNN0(datingDataMat[i, ], normDataSet[numTestVecs :,], datinglabels[numTestVecs :], 3)
        #打印预测结果和实际结果
        print('kNN 分类结果为%d,实际结果为%d' % (classifierResult, datinglabels[i]))
        if classifierResult != datinglabels[i]:
            errorCount += 1
    print('kNN分类器的错误率为%f' % (errorCount / rows))

结果如下所示:

kNN 分类结果为3,实际结果为1
kNN 分类结果为3,实际结果为2
kNN 分类结果为3,实际结果为3
kNN 分类结果为3,实际结果为3
kNN 分类结果为3,实际结果为3
...
kNN 分类结果为3,实际结果为2
kNN 分类结果为3,实际结果为3
kNN 分类结果为3,实际结果为3
kNN分类器的错误率为0.061000

3.5 使用算法–构建完整可用系统

使用kNN分类器分类。当输入某个人的信息时,给出对该人喜欢程度的预测值。

#约会网站预测函数
def classifyPerson():
    #结果列表,分类结果和标签的对应关系,根据file2matrix中的dicFromLabels构造,标签按照从前到后的顺序对应1,2,3
    resultList = ['largeDoses', 'smallDoses', 'didntLike']
    gameTime = float(input('玩游戏所占时间百分比:'))
    flyMiles = float(input('每年的飞行里程数:'))
    iceCream = float(input('每年冰淇淋公升数:'))
    filename = 'C:/Users/hp/Desktop/SH/python/MLInAction/machinelearninginaction/Ch02/datingTestSet.txt'
    datingDataMat, datinglabels = file2matrix(filename)
    normDataSet, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([flyMiles, gameTime, iceCream])   #分类器的输入数据
    classifierResult = kNN0((inArr - minVals) / ranges, normDataSet, datinglabels, 3)   #分类时对输入数据归一化处理
    print('喜欢这个人的程度为:%s' % (resultList[classifierResult -1]))

结果如下所示:

玩游戏所占时间百分比:10
每年的飞行里程数:10000
每年冰淇淋公升数:0.5
喜欢这个人的程度为:smallDoses

4. 使用kNN的手写识别系统

识别数字0-9。所有的图片被处理为宽高为32像素*32像素的黑白图像。再将图像转换为文本文件,0为空白部分,1为字迹部分。

4.1 准备数据–将图像转换为测试向量

将32*32的二进制图像矩阵转换为1*1024的向量,便于使用分类器处理分类。
给定文件,循环读出文件的32行,并将每行的前32个字符存在NumPy数组中并返回数组。

def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename, 'r')
    try:
        for i in range(32):
            lineStr = fr.readline()
            for j in range(32):
                returnVect[0, i * 32 + j] = int(lineStr[j])
        return returnVect
    finally:
        fr.close()

4.2 测试算法–使用kNN算法识别手写数字

import os
def handwritingClassTest(trainPath, testPath, k):
    #处理训练集,将所有的训练样本处理为特征矩阵和对应的类别标签
    trainFileList = os.listdir(trainPath)    #得到该路径下的文件名列表
    mTrain = len(trainFileList)   #得到文件数量
    #将文件解析到训练特征矩阵和标签
    trainDataMat = zeros((mTrain, 1024))     #训练特征矩阵
    trainLabels = []
    for i in range(mTrain):
        trainFilePath  = trainPath + '/' + trainFileList[i]
        trainDataMat[i, :] = img2vector(trainFilePath)
        trainLabel = int(trainFileList[i].split('_')[0])
        trainLabels.append(trainLabel)
    #处理测试集,计算错误率
    testFilelist = os.listdir(testPath)
    mTest = len(testFilelist)
    errorCount = 0.0
    for i in range(mTest):
        testFilePath = testPath +  '/' + testFilelist[i]
        VectorUnderTest = img2vector(testFilePath)
        testLabel = int(testFilelist[i].split('_')[0])
        classifierResult = kNN0(VectorUnderTest, trainDataMat, trainLabels, k)
        #打印预测结果和实际结果
        # print('kNN 分类结果为%d,实际结果为%d' % (classifierResult, writeLabel))
        if classifierResult != testLabel:
            errorCount += 1
            print('kNN 分类结果为%d,实际结果为%d' % (classifierResult, testLabel))
    print('手写字符识别kNN分类器的错误率为%f' % (errorCount / mTest))

os.listdir(path=’.’)返回给定path下所有条目name构成的list。list以任意的顺序排列,不包括’.’和’..’。如果path为字节,返回的文件名也为字节,否则返回的文件名为字符串,fsencode()函数将字符串文件名编码为字节。scandir()函数能够返回目录的条目和文件属性信息。
打印错误分类的结果和错误率如下:

kNN 分类结果为7,实际结果为1
kNN 分类结果为9,实际结果为3
kNN 分类结果为9,实际结果为3
kNN 分类结果为3,实际结果为5
kNN 分类结果为6,实际结果为5
kNN 分类结果为6,实际结果为8
kNN 分类结果为3,实际结果为8
kNN 分类结果为1,实际结果为8
kNN 分类结果为1,实际结果为8
kNN 分类结果为1,实际结果为9
kNN 分类结果为7,实际结果为9
手写字符识别kNN分类器的错误率为0.011628

如果要在IDLE中调用该函数,首先使用os.chdir(‘path’)函数将目录改为kNN.py所在目录,然后导入kNN,调用handwritingClassTest函数。

>>> os.chdir('D:\Python\ML\kNN')
>>> import kNN
>>> kNN.handwritingClassTest(kNN.trainPath, kNN.validPath, 3)
kNN 分类结果为7,实际结果为1
kNN 分类结果为9,实际结果为3
kNN 分类结果为9,实际结果为3
kNN 分类结果为3,实际结果为5
kNN 分类结果为6,实际结果为5
kNN 分类结果为6,实际结果为8
kNN 分类结果为3,实际结果为8
kNN 分类结果为1,实际结果为8
kNN 分类结果为1,实际结果为8
kNN 分类结果为1,实际结果为9
kNN 分类结果为7,实际结果为9
手写字符识别kNN分类器的错误率为0.011628

5. 小结

kNN算法简单有效,不用训练。可以直接根据训练数据集预测待分类样本。但必须保存全部数据集,占用大量的存储空间,需要计算每个训练样本和待分类样本的距离,执行效率低。并且无法给出数据的内在含义。
k决策树是kNN的优化版,可以节省大量计算开销。

你可能感兴趣的:(机器学习,python,机器学习,python,knn分类器)