ML刻意练习第1周之KNN算法

k近邻法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一种基本分类与回归方法。它的工作原理是:存在一个样本数据集合,也称作为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

KNN实战之一

1.导入算法所用的numpy和operate模块。

import numpy as np
import operator

2.创建最简单的带标签的数据集,并将其封装在函数creatDateSet()中。

def creatDateSet():
    group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])#创建一个数组
    labels = ['A','A','B','B']
    return group,labels

3.实现最简单的KNN算法,并将其封装在函数classify0()中。
①.首先定义classify0()函数,有四个参数,inx表示待分类的新数据,dataset表示已知标签的数据集,labels为数据对应的标签,k表示待分类样本的分类取决于k个最相近的数据。

def classify0(inX, dataSet, labels, k):

②.函数的实现:首先把数据集中的点放入矩阵中,然后复制n倍(等同于dateset中样本的数目),再与dateset中的数据求距离。相当于将待分类数据与所有样本的距离存储在一个数组distances中。

dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
sqDiffMat = diffMat**2#坐标差的平方
sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
distances = sqDistances**0.5#一个数组

之后返回距离从小到大排列后的索引,放入数组sortedDistIndicies中。

sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引

创建字典classCount,统计距离待分类样本最相似的k个样本的类别及每类的样本个数,最后返回最相似的那个类别。

  classCount={}#创建字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#例如{'B': 2, 'A': 1};统计了最近的k个点分别有多少属于哪类
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
    return sortedClassCount[0][0]

4.综上:完整代码如下

import numpy as np
import operator
def creatDateSet():
    group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])#创建一个数组
    labels = ['A','A','B','B']
    return group,labels

def classify0(inX, dataSet, labels, k):
    #inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
    dataSetSize = dataSet.shape[0]#第一维的个数(多少行)

    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
    sqDiffMat = diffMat**2#坐标差的平方
    sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
    distances = sqDistances**0.5#一个数组
    sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
    classCount={}#创建字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
    return sortedClassCount[0][0]
def main():
    group,labels = creatDateSet()
    result = classify0([0,0], group ,labels,3)
    print(result)
if __name__ == '__main__':
    main()

5.运行结果:

E:\anaconda\envs\tf1\python.exe "C:/Users/范淑卷/Desktop/ML实战练习代码/P19 最简单的KNN算法/knn.py"
B

Process finished with exit code 0

得知待分类的点[0,0]应该为类别‘B’。

6.学习心得:
①.KNN算法的实现;
②.numpy模块下tile函数用来形成矩阵,从而便于用矩阵对特征运算;
③.shape函数用来返回矩阵或者数组的大小;
④.numpy模块下的argsort函数用于返回数组的索引;
⑤.内置的sorted函数实现对字典类型数据的排序。

KNN实战之二:约会网站

分析:该场景的例子共有三个特征值:1.每年获得的飞行常客里程数;2.玩游戏所耗时间百分比;3.每周消费的冰淇淋公升数。但是由于特征1比另外两个特征的数值要大很多,所以直接计算时特征1势必会成为影响分类的决定性因素,然而本题的三个特征同等重要,所以首先需要对数据进行归一化处理。又因为只给了一组数据,所以我们要挑选一部分作为测试数据(本例题挑选了前百分之10的数据作为测试)。
1.首先,先将txt文件中的数据导入,并封装在函数file2matrix()中。

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())         #get the number of lines in the file
    returnMat = np.zeros((numberOfLines,3))        #prepare matrix to return
    classLabelVector = [] #prepare labels return
    fr.close()

    fr = open(filename)
    index = 0
    for line in fr.readlines():#按行读入
        line = line.strip()#去掉每一行回车键
        listFromLine = line.split('\t')#按制表符分隔
        returnMat[index,0:3] = listFromLine[0:3]#将特征存入returnMat中
        classLabelVector.append(int(listFromLine[-1]))#将标签存入classLabelVector中
        #此处的listFromLine[-1]用listFromLine[3]也可以,不过一般习惯用-1表示数组中的最后一个
        index += 1#遍历文件,index用来控制数据特征在returnMat中的位置
    fr.close()
    return returnMat,classLabelVector#返回数据特征矩阵和标签数组

程序中,下面代码的作用是去掉txt文件中所有的回车键,并根据制表符分隔读取的每一行。

line = line.strip()#去掉每一行最后的回车键
        listFromLine = line.split('\t')

2.在将数据导入之后,应当对数据进行归一化处理,归一化后的数据的各特征分布在0到1之间.

def autoNorm(dataSet):
    minVals = dataSet.min(0)#dataset中的最小值
    maxVals = dataSet.max(0)#dataset中的最大值
    ranges = maxVals - minVals#最大差(为了将结果归一化到零到壹之间)
    # normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]#dataset中的数据量
    normDataSet = dataSet - np.tile(minVals, (m,1))#将该区间规范到0开始
    normDataSet = normDataSet/ np.tile(ranges, (m,1))  #将结果归一化到零到壹之间
    return normDataSet, ranges, minVals
#返回归一化后的数据集,数据极限差值和最小值(归一化前)

3.实现kNN算法,类比实战一中的实现。

def classify0(inX, dataSet, labels, k):
    #inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
    dataSetSize = dataSet.shape[0]#第一维的个数(多少行)

    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
    sqDiffMat = diffMat**2#坐标差的平方
    sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
    distances = sqDistances**0.5#一个数组
    sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
    classCount={}#创建字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
    return sortedClassCount[0][0]

4.将txt文件中的前百分之10数据用来测试,返回每个数据的分类情况以及测试数据的错误个数和错误率。

def datingClassTest():
    hoRatio = 0.10  # hold out 10%
    datingDataMat, datingLabels = file2matrix('E:\机器学习算法刻意练习\机器学习实战书电子版'
                                                 '\machinelearninginaction\Ch02\datingTestSet2.txt')# 导入数据
    normMat, ranges, minVals = autoNorm(datingDataMat)#首先先归一化
    m = normMat.shape[0]
    numTestVecs = int(m * hoRatio)#拿出百分之10的数据用于测试
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 5)
        print("the classifier came back with:{}, the real answer is: {}" .format(classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("the total error rate is: {}" .format(errorCount / float(numTestVecs)))
    print(errorCount)

5.综上,实战二之约会网站的完整代码如下(此例中取的k=5):

import numpy as np
import matplotlib.pyplot as plt
import operator

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())         #get the number of lines in the file
    returnMat = np.zeros((numberOfLines,3))        #prepare matrix to return
    classLabelVector = [] #prepare labels return
    fr.close()

    fr = open(filename)
    index = 0
    for line in fr.readlines():
        line = line.strip()#去掉所有回车键
        listFromLine = line.split('\t')
        returnMat[index,0:3] = listFromLine[0:3]#将特征存入returnMat中
        classLabelVector.append(int(listFromLine[-1]))#将标签存入classLabelVector中
        index += 1
    fr.close()
    return returnMat,classLabelVector

def autoNorm(dataSet):
    minVals = dataSet.min(0)#dataset中的最小值
    maxVals = dataSet.max(0)#dataset中的最大值
    ranges = maxVals - minVals#最大差(为了将结果归一化到零到壹之间)
    # normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]#dataset中的数据量
    normDataSet = dataSet - np.tile(minVals, (m,1))#将该区间规范到0开始
    normDataSet = normDataSet/ np.tile(ranges, (m,1))  #将结果归一化到零到壹之间
    return normDataSet, ranges, minVals
#返回归一化后的数据集,最大最小差和最小值(归一化前)

def classify0(inX, dataSet, labels, k):
    #inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
    dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
    sqDiffMat = diffMat**2#坐标差的平方
    sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
    distances = sqDistances**0.5#一个数组
    sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
    classCount={}#创建字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
    return sortedClassCount[0][0]

def datingClassTest():
    hoRatio = 0.10  # hold out 10%
    datingDataMat, datingLabels = file2matrix('E:\机器学习算法刻意练习\机器学习实战书电子版'
                                                 '\machinelearninginaction\Ch02\datingTestSet2.txt')# 导入数据
    normMat, ranges, minVals = autoNorm(datingDataMat)#首先先归一化
    m = normMat.shape[0]
    numTestVecs = int(m * hoRatio)#拿出百分之10的数据用于测试
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 5)
        print("the classifier came back with:{}, the real answer is: {}" .format(classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0

    print("the total error rate is: {}" .format(errorCount / float(numTestVecs)))
    print(errorCount)
    
def main():
    datingClassTest()
    
if __name__ == '__main__':
    main()

6.运行的部分结果如下(测试集数据个数过多,无法一一列出):

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:2, the real answer is: 1
the classifier came back with:1, the real answer is: 1
the total error rate is: 0.05
5.0

Process finished with exit code 0

可知KNN在测试集上的错误率为5%。

7.学习心得:
①.如何将单文件的数据的特征与标签存储在矩阵和数组中;
②.数据的归一化处理;
③.取数据中的一部分作为测试数据。

KNN实战之三:手写字体识别

分析:具体的预测分类实现方法与前两个实例没什么区别,最大的难点可能是如何将不同文件中的各类“图形文件”的特征和标签读入。
1.重点and难点:多文件的读入
此题与实战二不同。实战二是将三个特征与标签放入了文件的每一行中,每一行包含了三个特征与分类标签;而这个题每一个txt文件都包含了32×32个特征值,而每一个样本的类别则是文件名字的第一个数字。因此要将其分两步读入,首先完成对单文件特征的读入,再次完成多文件特征与类别标签的读入。
①单文件特征的读入:将32×32的特征转换存储到1×1024的矩阵中,每一行都是一个数据的所有特征值。

def img2vector(filename):
    returnVect = np.zeros((1,1024))#创建一个1*1024的矩阵
    fr = open(filename)
    for i in range(32):  #依次读入每个文件的32行数据
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])#读入每行的32个字符,并转化为int型存储在矩阵returnVect中
    fr.close()
    return returnVect

②类别标签的读入:用listdir函数遍历多个txt文件存储的文件夹,获得各个txt文件的名字,然后通过split函数获得文件名的第一个数字(即该数据的类别),存储在列表hwLabels中。

    hwLabels = []
    trainingFileList = listdir('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits')    #样本文件的文件名
    m = len(trainingFileList)   #m表示有多少训练文件(数据样本)
    trainingMat = np.zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]     #去掉文件名后的 .txt
        classNumStr = int(fileStr.split('_')[0])#得到该文件数据的类别
        hwLabels.append(classNumStr)#存储所有文件的类别

数据集与测试集读取方法想同,在此不再赘述。

多文件数据特征的读入:路径前半部分相同,可以固定为’E:\机器学习算法刻意练习\算法所用数据\2.KNN\trainingDigits\’,最后的即txt文件的文件名,可以通过遍历由listdir函数获得的列表获得。

    for i in range(m):
        fileNameStr = trainingFileList[i]
        trainingMat[i,:] = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr)
        #将所有训练数据文件依次读入并存储在矩阵trainingMat中

2.KNN算法同上,不再赘述。
3.最后通过比较测试数据的真实分类与预测分类给出KNN算法预测错误的个数以及预测错误率。(此题取K=5)

mTest = len(testFileList)#表示有多少个测试文件(数据)
    for i in range(mTest):
        fileNameStr2 = testFileList[i]
        fileStr2 = fileNameStr2.split('.')[0]     #take off .txt
        classNumStr2 = int(fileStr2.split('_')[0])
        vectorUnderTest = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr2)
        # 将所有训练测试文件依次读入并存储在矩阵trainingMat中
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 5)
        print("the classifier came back with:{}, the real answer is:{}".format(classifierResult, classNumStr2))
        if classifierResult != classNumStr2: #如果分类与KNN预测的结果不一样,则错误个数++
            errorCount += 1.0
    print("the total number of errors is: {}" .format(errorCount))
    print("the total error rate is:{}" .format(errorCount/float(mTest)))

4.综上:完整的程序代码如下:

import numpy as np
import matplotlib.pyplot as plt
import operator
from os import listdir

def img2vector(filename):
    returnVect = np.zeros((1,1024))#创建一个1024维度的数组
    fr = open(filename)
    for i in range(32):  #依次读入每个文件的32行数据
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])#读入每行的32个字符,并转化为int型存储在数组returnVect中
    fr.close()
    return returnVect

def classify0(inX, dataSet, labels, k):
    #inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
    dataSetSize = dataSet.shape[0]#第一维的个数(多少行)

    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
    sqDiffMat = diffMat**2#坐标差的平方
    sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
    distances = sqDistances**0.5#一个数组
    sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
    classCount={}#创建字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
    return sortedClassCount[0][0]

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits')    #样本文件的文件名
    m = len(trainingFileList)   #m表示有多少训练文件(数据样本)
    trainingMat = np.zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]     #去掉文件名后的 .txt
        classNumStr = int(fileStr.split('_')[0])#得到该文件数据的类别
        hwLabels.append(classNumStr)#存储所有文件的类别
        trainingMat[i,:] = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr)
        #将所有训练数据文件依次读入并存储在矩阵trainingMat中
    testFileList = listdir('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\testDigits')        #iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)#表示有多少个测试文件(数据)
    for i in range(mTest):
        fileNameStr2 = testFileList[i]
        fileStr2 = fileNameStr2.split('.')[0]     #take off .txt
        classNumStr2 = int(fileStr2.split('_')[0])
        vectorUnderTest = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr2)
        # 将所有训练测试文件依次读入并存储在矩阵trainingMat中
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 5)
        print("the classifier came back with:{}, the real answer is:{}".format(classifierResult, classNumStr2))
        if classifierResult != classNumStr2: #如果分类与KNN预测的结果不一样,则错误个数++
            errorCount += 1.0
    print("the total number of errors is: {}" .format(errorCount))
    print("the total error rate is:{}" .format(errorCount/float(mTest)))

def main():
    handwritingClassTest()

if __name__ == '__main__':
    main()

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: 17.0
the total error rate is:0.017970401691331923

Process finished with exit code 0

可知共有17个数据预测错误,错误率为1.797%。
6.学习心得:
①.多文件的遍历与特征读取;
②.多文件的遍历与分类标签的读取。

你可能感兴趣的:(ML刻意练习第1周之KNN算法)