《Machine Learning In Action》学习笔记(1)-KNN(k-近邻算法)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kabuto_hui/article/details/85109858

  knn算法我在之前的博客从零开始-Machine Learning学习笔记(20)-kNN(k-Nearset Neignbor)学习笔记中也已经提到了,大家如果感兴趣可以回过头去看看,knn原理非常简单。不需要训练,当有待分类样本时,只需要从数据集中选取k个与这个样本距离最近的样本,将k个样本中最多的label作为该待分类样本的label。
  我将书中所给的代码使用Python3编译,添加了注释以便于快速理解。完整代码在我的github上,有兴趣的朋友可以自行下载。

'''
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

---------------------------
@modified: Kabuto_hui
@date: 2018/12/19
---------------------------
'''
from numpy import *
import operator
from os import listdir


def classify0(inX, dataSet, labels, k):
    '''
    knn分类器
    :param inX:         待分类向量
    :param dataSet:     数据集
    :param labels:      数据集对应的标签
    :param k:           邻居个数
    :return:            返回分类的类别
    '''
    # 获取训练集的样本数量
    dataSetSize = dataSet.shape[0]
    # 先在列方向上重复待分类向量dataSetSize次,再减去训练集;其实就是待分类向量与训练集中的每个向量相减
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    # 差值的平方
    sqDiffMat = diffMat ** 2
    # 差值的平方和
    sqDistances = sqDiffMat.sum(axis=1)
    # 差值平方和再开方
    distances = sqDistances ** 0.5
    # 将距离从小到大排序并返回index
    sortedDistIndicies = distances.argsort()
    classCount = {}
    # 遍历与待测样本距离最近的k个训练集样本,选择数量最多的label作为返回值
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # 统计最近的k个训练集样本中各个label的数量
    # sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 对dict按value值由大到小排序
    return sortedClassCount[0][0]   # 返回value值最大key作为返回,即为label


def file2matrix(filename):
    '''
    读取文件中的数据,并返回数据集及其对应的label
    :param filename:    文件名称
    :return:            返回样本集合及其label
    '''
    fr = open(filename)
    # 获取行数【样本个数】
    numberOfLines = len(fr.readlines())  # get the number of lines in the file
    returnMat = zeros((numberOfLines, 3))  # prepare matrix to return
    classLabelVector = []  # prepare labels return
    index = 0
    # 按行读取
    fr = open(filename)
    for line in fr.readlines():
        line = line.strip()    # 去掉空格
        listFromLine = line.split('\t')         # 以\t作为分割
        returnMat[index, :] = listFromLine[0:3] # 取前三列作为特征
        classLabelVector.append(int(listFromLine[-1]))  # 取最后一列作为label
        index += 1
    return returnMat, classLabelVector


def autoNorm(dataSet):
    '''
    归一化特征值x' = (x - x_min) / (x_max - x_min)
    :param dataSet: 数据集
    :return:        归一化后的数据集, 最大减最小值, 最小值
    '''
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    # 获取样本的个数
    m = dataSet.shape[0]
    # 对于数据集中的每个数据进行处理: x' = (x - x_min) / (x_max - x_min)
    normDataSet = dataSet - tile(minVals, (m, 1))
    normDataSet = normDataSet / tile(ranges, (m, 1))  # element wise divide
    return normDataSet, ranges, minVals


def datingClassTest():
    '''
    针对于约会网站的测试代码
    '''
    # 测试集的比例
    hoRatio = 0.10  # 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):
        # 对测试集中的每一个样本进行分类,k=3
        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]))
        # 如果分类错误,错误的个数+1
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print("the total error rate is: %f" % (errorCount / float(numTestVecs)))
    print(errorCount)


def img2vector(filename):
    '''
    图片转向量
    Note: 书中所给的图片都转化成只有01的32*32矩阵,这个函数将矩阵转化为向量
    :param filename:    文件名
    :return:            返回向量
    '''
    returnVect = zeros((1, 1024))
    fr = open(filename)
    for i in range(32):
        # 读取一行
        lineStr = fr.readline()
        for j in range(32):
            # 对一行中的每个数据转化为int型再存入向量中
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVect


def handwritingClassTest():
    '''
    针对于手写识别实验的测试代码
    '''
    hwLabels = []
    # 读取文件夹中的文件列表
    trainingFileList = listdir('trainingDigits')  # load the training set
    # 获取文件的个数
    m = len(trainingFileList)
    # 初始化训练集
    trainingMat = zeros((m, 1024))
    # 对每个文件进行处理
    for i in range(m):
        # 获取文件名称
        fileNameStr = trainingFileList[i]
        # 将文件名切割以获取label: 如0_0.txt
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        # 存储该图片的label
        hwLabels.append(classNumStr)
        # 图片矩阵转化为向量存入训练集中
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)

    # 获取测试集的文件列表
    testFileList = listdir('testDigits')  # iterate through the test set
    # 初始化错分的个数
    errorCount = 0.0
    # 获取测试集的数量
    mTest = len(testFileList)
    for i in range(mTest):
        # 获取该测试样本的label
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        # 将该测试样本转化为向量
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        # 调用knn进行分类
        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)))

if __name__ == '__main__':
    print('-'*15, '约会网站实验的测试代码', '-'*15)
    datingClassTest()
    print('-' * 15, '手写识别实验的测试代码', '-' * 15)
    handwritingClassTest()

相关文件及完整代码:kabutohui/Machine_Learning_In_Action

总结与思考

  在这一章中,使用了大量的矩阵加减操作。尤其使用了未曾用过的tile()函数,这个函数可以将一个向量重复n次变成一个矩阵,因此在求待分类向量的与所有训练集样本的距离的时候,使用此函数可以代替循环求解。

  另外值得可取的部分是文件读取的时候,大量的使用了str的分割,从而获取相关的label。

猜你喜欢

转载自blog.csdn.net/kabuto_hui/article/details/85109858