《机器学习实战》kNN学习笔记

《机器学习实战》kNN学习笔记

概述

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

优缺点

优点:精度高、对异常值不敏感、唔数据输入假定
缺点:计算复杂度高、空间复杂度高。
适用数据范围:数值型和标称型
标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)
数值型:数值型目标变量则可以从无限的数值集合中取值,如0.100,42.001等 (数值型目标变量主要用于回归分析)

k-近邻算法的一般流程

收集数据:可以使用任何方法.

准备数据:距离计算所需要的数值,最好是结构化的数据格式.

分析数据:可以使用任何方法.

训练算法:此步骤不适用于 k-近邻算法.

测试算法:计算错误率.

使用算法:首先需要输入样本数据和结构化的输出结果,然后运行 k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理.

简单案例kNN.py

from numpy import *
import operator
from importlib import reload
# k-近邻算法
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 classify(inX,dataSet,labels,k):
    # 计算距离
    dataSetSize = dataSet.shape[0] #读取矩阵第一行的长度 4
    diffMat = tile(inX,(dataSetSize,1)) - dataSet #tile 重复inx的各个维度1次    相减
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort()
    classCount={}
    # 选择距离最小的k个点
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0)+1
    # 排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

然后在python交互式开发环境下

import kNN
kNN.classify([0,0],group,labels,3)

输出结果应该是B
可以改变输入【0,0】的值,查看运行结果

在约会网站上使用k-近邻算法

首先,把约会数据存放在文本文件datingTestSet中,样本主要包括以下三个特征

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消耗的冰激凌公升数

在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量

def file2matrix(filename):
    # 打开文件
    fr = open(filename)
    # 读取文件所有内容
    arrayOlines = fr.readlines()
    # 得到文件行数
    numberOfLines = len(arrayOlines)
    # 返回的NumPy矩阵numberOfLines行,3列
    returnMat = np.zeros((numberOfLines, 3))
    # 创建分类标签向量
    classLabelVector = []
    # 行的索引值
    index = 0
    # 读取每一行
    for line in arrayOlines:
        # 去掉每一行首尾的空白符,例如'\n','\r','\t',' '
        line = line.strip()
        # 将每一行内容根据'\t'符进行切片,本例中一共有4列
        listFromLine = line.split('\t')
        # 将数据的前3列进行提取保存在returnMat矩阵中,也就是特征矩阵
        returnMat[index,:] = listFromLine[0:3]
        # 根据文本内容进行分类1:不喜欢;2:一般;3:喜欢
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    # 返回标签列向量以及特征矩阵
    return returnMat, classLabelVector

在python命令提示符下输入下内容

>>> from numpy import *
>>> import kNN
>>> from importlib import reload
>>> reload(kNN)
<module 'kNN' from 'D:\\pythonworkspace\\Machine-Learning-in-Action-Python3-master\\kNN_Project1\\kNN.py'>
>>> datingDataMat,datingLabels = kNN.file2matrix('datingTestSet.txt')
>>> 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 0x000002B8A857A788>
>>> plt.show()

>>>

我们使用Matplotlib创建散点图
散点图使用大厅DataMat矩阵的第二第三列数据,分别表示特征值玩‘视频游戏所耗时间百分比’和’每周消耗的冰激凌公升数‘
如图所示
在这里插入图片描述
上图很难得到一些需要的数据信息,所以用色彩来标记不同的样本分类
Matplotlib库提供的scatter函数支持个性化标记散点图上的点。重新输入上面代码
调用scatter函数时使用下列参数

>>> ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15*array(datingLabels),15*array(datingLabels))
<matplotlib.collections.PathCollection object at 0x000002B8A857A788>
>>> plt.show()

结果如下图所示
在这里插入图片描述

归一化特征值

在处理不同取值范围的特征值时,我们通常采用的方法时讲数值归一化,例如将取值范围处理为0-1之间。
下面的公式可以将特征值转化为0-1区间内的值:
newValue = (oldValue - min)/(max - min)
其中min和max分别时数据集中最小特征值和最大特征值。虽然改变取值范围增加了分类器的复杂度,但能提高准确度。
在kNN.py中添加一个函数,autoNorm(),该函数可以将数字特征值转化为0-1的区间

def autoNorm(dataSet):
    # 获取数据的最小值
    minVals = dataSet.min(0)
    # 获取数据的最大值
    maxVals = dataSet.max(0)
    # 最大值和最小值的范围
    ranges = maxVals - minVals
    # shape(dataSet)返回dataSet的矩阵行列数
    normDataSet = np.zeros(np.shape(dataSet))
    # numpy函数shape[0]返回dataSet的行数
    m = dataSet.shape[0]
    # 原始值减去最小值(x-xmin)
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    # 差值处以最大值和最小值的差值(x-xmin)/(xmax-xmin)
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    # 归一化数据结果,数据范围,最小值
    return normDataSet, ranges, minVals

然后再python命令提示符下输入

>>> reload(kNN)
<module 'kNN' from 'D:\\pythonworkspace\\Machine-Learning-in-Action-Python3-master\\kNN_Project1\\kNN.py'>
>>> normMat,ranges,minVals = kNN.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])
>>>

测试代码

def datingClassTest():
    # 打开文件名
    filename = "datingTestSet.txt"
    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix(filename)
    # 取所有数据的10% hoRatio越小,错误率越低
    hoRatio = 0.10
    # 数据归一化,返回归一化数据结果,数据范围,最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 获取normMat的行数
    m = normMat.shape[0]
    # 10%的测试数据的个数
    numTestVecs = int(m * hoRatio)
    # 分类错误计数
    errorCount = 0.0
    for i in range(numTestVecs):
        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        # k选择label数+1(结果比较好)
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],\
                                     datingLabels[numTestVecs:m], 4)
        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率:%f%%" % (errorCount/float(numTestVecs)*100))
        

它首先使用了file2matrix和autoNorm()函数从文件读取数据并把它转换为归一化特征值。接着计算测试向量的数量,决定normMat向量中哪些为测试集,哪些为训练集,然后将这两部分数据输入到classify()函数中,最后计算错误率并输出结果

预测函数

def classifyPerson():
    # 输出结果
    resultList = ['讨厌', '有些喜欢', '非常喜欢']
    # 三维特征用户输入
    percentTats = float(input("玩视频游戏所消耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰淇淋公升数:"))
    # 打开的文件名
    filename = "datingTestSet.txt"
    # 打开并处理数据
    datingDataMat, datingLabels = file2matrix(filename)
    # 训练集归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 生成NumPy数组,测试集
    inArr = np.array([percentTats, ffMiles, iceCream])
    # 测试集归一化
    norminArr = (inArr - minVals) / ranges
    # 返回分类结果
    classifierResult = classify0(norminArr, normMat, datingLabels, 4)
    # 打印结果
    print("你可能%s这个人" % (resultList[classifierResult - 1]))

新加入函数raw_input(),该函数允许用户输入文本行命令并返回用户所输入的命令

使用k-近邻算法识别手写数字

首先,我们必须把图像格式化处理为一个向量。我们把一个3232的二进制图像矩阵转化为11024的向量,这样之前的分类棋就可以处理数字图像信息了

import numpy as np
import operator
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN

"""
函数说明:将32*32的二进制图像转换为1*1024向量

Parameters:
    filename - 文件名
    
Returns:
    returnVect - 返回二进制图像的1*1024向量

def img2vector(filename):
    # 创建1*1024零向量
    returnVect = np.zeros((1, 1024))
    # 打开文件
    fr = open(filename)
    # 按行读取
    for i in range(32):
        # 读取一行数据
        lineStr = fr.readline()
        # 每一行的前32个数据依次存储到returnVect中
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])
    # 返回转换后的1*1024向量
    return returnVect

该函数创建一个1*1024的numpy数组,然后打开指定的文件,读出文件的前32行,并将每行的前32个字符储存在numpy数组中,然后返回数组

"""
函数说明:手写数字分类测试

def handwritingClassTest():
    # 测试集的Labels
    hwLabels = []
    # 返回trainingDigits目录下的文件名
    trainingFilesList = listdir('trainingDigits')
    # 返回文件夹下文件的个数
    m = len(trainingFilesList)
    # 初始化训练的Mat矩阵(全零阵),测试集
    trainingMat = np.zeros((m, 1024))
    # 从文件名中解析出训练集的类别
    for i in range(m):
        # 获得文件的名字
        fileNameStr = trainingFilesList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 将获得的类别添加到hwLabels中
        hwLabels.append(classNumber)
        # 将每一个文件的1x1024数据存储到trainingMat矩阵中
        trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr))
    # 构造kNN分类器
    neigh = kNN(n_neighbors=3, algorithm='auto')
    # 拟合模型,trainingMat为测试矩阵,hwLabels为对应标签
    neigh.fit(trainingMat, hwLabels)
    # 返回testDigits目录下的文件列表
    testFileList = listdir('testDigits')
    # 错误检测计数
    errorCount = 0.0
    # 测试数据的数量
    mTest = len(testFileList)
    # 从文件中解析出测试集的类别并进行分类测试
    for i in range(mTest):
        # 获得文件名字
        fileNameStr = testFileList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 获得测试集的1*1024向量,用于训练
        vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr))
        # 获得预测结果
        classifierResult = neigh.predict(vectorUnderTest)
        print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
        if(classifierResult != classNumber):
            errorCount += 1.0
    print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount/mTest * 100))
        
def main():
    handwritingClassTest()
    
    
if __name__ == '__main__':
    main()
    

在python输入kNN.handwritingClassTest()

分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
总共错了12个数据
错误率为1.268499%

通过改变k的值,修改函数handwritingClassTest随机选取训练样本,改变训练样本的数目,都会影响错误率

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章