Pyhthon3《机器学习实战》学习笔记一:K-近邻算法

1 k-近邻法简介

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

k-近邻算法步骤如下:

  1. 计算已知类别数据集中的点与当前点之间的距离;
  2. 按照距离递增次序排序;
  3. 选取与当前点距离最小的k个点;
  4. 确定前k个点所在类别的出现频率;
  5. 返回前k个点所出现频率最高的类别作为当前点的预测分类。

KNN 通俗理解

给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的 k 个实例,这 k 个实例的多数属于某个类,就把该输入实例分为这个类。

2 Python3代码实现

我们已经知道了k-近邻算法的原理,那么接下来就是使用Python3实现该算法,

在构造完整的K-近邻算法之前,我们还需要编写一些基本的通用函数,在KNN.py增添下面代码:

import numpy as np
import operator

def createDataSet():
    group = np.array([[1.0,1.1],
                      [1.0,1.0],
                      [0,0],
                      [0,0.1]])
    labels = ['A','A','B','B']

    return group,labels

if __name__ == '__main__':

    group,labels = createDataSet()
    print(group)
    print(labels)

运行结果如下:

k-近邻算法

def knn_base(x,dataSet,labels,k):
    """
     X 是输入的测试样本,是一个[x, y]样式的
     dataset 是训练样本集
     labels 是训练样本标签
     k 是top k最相近的

    """
    dataSetSize = dataSet.shape[0]   # shape返回矩阵的[行数,列数]shape[0]获取数据集的行数
    """
     numpy中tile()函数用法
     >>>numpy.tile([0,0],5)#在列方向上重复[0,0]5次,默认行1次  
     array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])  
     >>> numpy.tile([0,0],(1,1))#在列方向上重复[0,0]1次,行1次  
     array([[0, 0]])  
     >>> numpy.tile([0,0],(2,1))#在列方向上重复[0,0]1次,行2次  
     array([[0, 0],  
           [0, 0]])

    """
    diff = np.tile(x,(dataSetSize,1)) - dataSet
    sqdiff = diff ** 2
    Distance_sum = sqdiff.sum(axis = 1)          # axis=1表示按照横轴,sum表示累加,即按照行进行累加。
    distance = np.sqrt(Distance_sum)
    sortedDistIndicies = np.argsort(distance)    # 按照升序进行快速排序,返回的是原数组的下标。
    # 存放最终的分类结果及相应的结果投票数
    classCount = {}


    # 投票过程,就是统计前k个最近的样本所属类别包含的样本个数
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]  #sortedDistIndicies[i]是第i个最相近的样本下标 labels是样本index对应的分类结果(‘A’ or ‘B’)
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1  # classCount.get(voteIlabel, 0)返回voteIlabel的值,如果不存在,则返回0 然后加1
        # 把分类结果进行排序,然后返回得票数最多的分类结果

        sortedclassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
        return sortedclassCount[0][0]

        # maxCount = 0
        # for key, value in classCount.items():
        #     if value > maxCount:
        #         maxCount = value
        #         classes = key
        #         print(classes)
        # return classes
if __name__ == '__main__':
    group, labels = createDataSet()
    a = knn_base([0,0],group,labels,3)
    print(a)

 结果如下:

2.1、约会网站配对案例实战:

 背景:海伦女士一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人。经过一番总结,她发现自己交往过的人可以进行如下分类:

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人

    海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。

datingTestSet.txt 数据集下载

  海伦收集的样本数据主要包含以下3种特征:

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

打开数据集如下图所示:

准备数据 

在将上述特征数据输入到分类器前,必须将待处理的数据的格式改变为分类器可以接收的格式。分类器接收的数据是什么格式的?从上小结已经知道,要将数据分类两部分,即特征矩阵和对应的分类标签向量。在kNN.py文件中创建名为file2matrix的函数,以此来处理输入格式问题。 将datingTestSet.txt放到与kNN.py相同目录下,编写代码如下:

"""
 将文本记录转换为Numpy的解析程序
"""


def file2matrix(filename):
    #打开文件
    fr=open(filename)
    #读取文件每一行到array0Lines列表
    #read():读取整个文件,通常将文件内容放到一个字符串中
    #readline():每次读取文件一行,当没有足够内存一次读取整个文件内容时,使用该方法
    #readlines():读取文件的每一行,组成一个字符串列表,内存足够时使用
    array0Lines=fr.readlines()
    #获取字符串列表行数行数
    numberOfLines=len(array0Lines)
    #返回的特征矩阵大小
    returnMat=np.zeros((numberOfLines,3))
    #list存储类标签
    classLabelVector=[]
    index=0
    for line in array0Lines:
        #去掉字符串头尾的空格,类似于Java的trim()
        line=line.strip()
        #将整行元素按照tab分割成一个元素列表
        listFromLine=line.split('\t')
        #将listFromLine的前三个元素依次存入returnmat的index行的三列
        returnMat[index,:]=listFromLine[0:3]
        #python可以使用负索引-1表示列表的最后一列元素,从而将标签存入标签向量中
        #使用append函数每次循环在list尾部添加一个标签值
        classLabelVector.append((listFromLine[-1]))
        index+=1
    return returnMat,classLabelVector

if __name__ == '__main__':
    datingDataMat , datingLabels = file2matrix('datingTestSet2.txt')
    print(datingDataMat)
    print(datingLabels)
[[  4.09200000e+04   8.32697600e+00   9.53952000e-01]
 [  1.44880000e+04   7.15346900e+00   1.67390400e+00]
 [  2.60520000e+04   1.44187100e+00   8.05124000e-01]
 ..., 
 [  2.65750000e+04   1.06501020e+01   8.66627000e-01]
 [  4.81110000e+04   9.13452800e+00   7.28045000e-01]
 [  4.37570000e+04   7.88260100e+00   1.33244600e+00]]
['3', '2', '1', '1', '1', '1', '3', '3', '1', '3', '1', '1', '2', '1', '1', '1', '1', '1', '2', '3', '2', '1', '2', '3', '2', '3', '2', '3', '2', '1', '3', '1', '3', '1', '2', '1', '1', '2', '3', '3', '1', '2', '3', '3', '3', '1', '1', '1', '1', '2', '2', '1', '3', '2', '2', '2', '2', '3', '1', '2', '1', '2', '2', '2', '2', '2', '3', '2', '3', '1', '2', '3', '2', '2', '1', '3', '1', '1', '3', '3', '1', '2', '3', '1', '3', '1', '2', '2', '1', '1', '3', '3', '1', '2', '1', '3', '3', '2', '1', '1', '3', '1', '2', '3', '3', '2', '3', '3', '1', '2', '3', '2', '1', '3', '1', '2', '1', '1', '2', '3', '2', '3', '2', '3', '2', '1', '3', '3', '3', '1', '3', '2', '2', '3', '1', '3', '3', '3', '1', '3', '1', '1', '3', '3', '2', '3', '3', '1', '2', '3', '2', '2', '3', '3', '3', '1', '2', '2', '1', '1', '3', '2', '3', '3', '1', '2', '1', '3', '1', '2', '3', '2', '3', '1', '1', '1', '3', '2', '3', '1', '3', '2', '1', '3', '2', '2', '3', '2', '3', '2', '1', '1', '3', '1', '3', '2', '2', '2', '3', '2', '2', '1', '2', '2', '3', '1', '3', '3', '2', '1', '1', '1', '2', '1', '3', '3', '3', '3', '2', '1', '1', '1', '2', '3', '2', '1', '3', '1', '3', '2', '2', '3', '1', '3', '1', '1', '2', '1', '2', '2', '1', '3', '1', '3', '2', '3', '1', '2', '3', '1', '1', '1', '1', '2', '3', '2', '2', '3', '1', '2', '1', '1', '1', '3', '3', '2', '1', '1', '1', '2', '2', '3', '1', '1', '1', '2', '1', '1', '2', '1', '1', '1', '2', '2', '3', '2', '3', '3', '3', '3', '1', '2', '3', '1', '1', '1', '3', '1', '3', '2', '2', '1', '3', '1', '3', '2', '2', '1', '2', '2', '3', '1', '3', '2', '1', '1', '3', '3', '2', '3', '3', '2', '3', '1', '3', '1', '3', '3', '1', '3', '2', '1', '3', '1', '3', '2', '1', '2', '2', '1', '3', '1', '1', '3', '3', '2', '2', '3', '1', '2', '3', '3', '2', '2', '1', '1', '1', '1', '3', '2', '1', '1', '3', '2', '1', '1', '3', '3', '3', '2', '3', '2', '1', '1', '1', '1', '1', '3', '2', '2', '1', '2', '1', '3', '2', '1', '3', '2', '1', '3', '1', '1', '3', '3', '3', '3', '2', '1', '1', '2', '1', '3', '3', '2', '1', '2', '3', '2', '1', '2', '2', '2', '1', '1', '3', '1', '1', '2', '3', '1', '1', '2', '3', '1', '3', '1', '1', '2', '2', '1', '2', '2', '2', '3', '1', '1', '1', '3', '1', '3', '1', '3', '3', '1', '1', '1', '3', '2', '3', '3', '2', '2', '1', '1', '1', '2', '1', '2', '2', '3', '3', '3', '1', '1', '3', '3', '2', '3', '3', '2', '3', '3', '3', '2', '3', '3', '1', '2', '3', '2', '1', '1', '1', '1', '3', '3', '3', '3', '2', '1', '1', '1', '1', '3', '1', '1', '2', '1', '1', '2', '3', '2', '1', '2', '2', '2', '3', '2', '1', '3', '2', '3', '2', '3', '2', '1', '1', '2', '3', '1', '3', '3', '3', '1', '2', '1', '2', '2', '1', '2', '2', '2', '2', '2', '3', '2', '1', '3', '3', '2', '2', '2', '3', '1', '2', '1', '1', '3', '2', '3', '2', '3', '2', '3', '3', '2', '2', '1', '3', '1', '2', '1', '3', '1', '1', '1', '3', '1', '1', '3', '3', '2', '2', '1', '3', '1', '1', '3', '2', '3', '1', '1', '3', '1', '3', '3', '1', '2', '3', '1', '3', '1', '1', '2', '1', '3', '1', '1', '1', '1', '2', '1', '3', '1', '2', '1', '3', '1', '3', '1', '1', '2', '2', '2', '3', '2', '2', '1', '2', '3', '3', '2', '3', '3', '3', '2', '3', '3', '1', '3', '2', '3', '2', '1', '2', '1', '1', '1', '2', '3', '2', '2', '1', '2', '2', '1', '3', '1', '3', '3', '3', '2', '2', '3', '3', '1', '2', '2', '2', '3', '1', '2', '1', '3', '1', '2', '3', '1', '1', '1', '2', '2', '3', '1', '3', '1', '1', '3', '1', '2', '3', '1', '2', '3', '1', '2', '3', '2', '2', '2', '3', '1', '3', '1', '2', '3', '2', '2', '3', '1', '2', '3', '2', '3', '1', '2', '2', '3', '1', '1', '1', '2', '2', '1', '1', '2', '1', '2', '1', '2', '3', '2', '1', '3', '3', '3', '1', '1', '3', '1', '2', '3', '3', '2', '2', '2', '1', '2', '3', '2', '2', '3', '2', '2', '2', '3', '3', '2', '1', '3', '2', '1', '3', '3', '1', '2', '3', '2', '1', '3', '3', '3', '1', '2', '2', '2', '3', '2', '3', '3', '1', '2', '1', '1', '2', '1', '3', '1', '2', '2', '1', '3', '2', '1', '3', '3', '2', '2', '2', '1', '2', '2', '1', '3', '1', '3', '1', '3', '3', '1', '1', '2', '3', '2', '2', '3', '1', '1', '1', '1', '3', '2', '2', '1', '3', '1', '2', '3', '1', '3', '1', '3', '1', '1', '3', '2', '3', '1', '1', '3', '3', '3', '3', '1', '3', '2', '2', '1', '1', '3', '3', '2', '2', '2', '1', '2', '1', '2', '1', '3', '2', '1', '2', '2', '3', '1', '2', '2', '2', '3', '2', '1', '2', '1', '2', '3', '3', '2', '3', '1', '1', '3', '3', '1', '2', '2', '2', '2', '2', '2', '1', '3', '3', '3', '3', '3', '1', '1', '3', '2', '1', '2', '1', '2', '2', '3', '2', '2', '2', '3', '1', '2', '1', '2', '2', '1', '1', '2', '3', '3', '1', '1', '1', '1', '3', '3', '3', '3', '3', '3', '1', '3', '3', '2', '3', '2', '3', '3', '2', '2', '1', '1', '1', '3', '3', '1', '1', '1', '3', '3', '2', '1', '2', '1', '1', '2', '2', '1', '1', '1', '3', '1', '1', '2', '3', '2', '2', '1', '3', '1', '2', '3', '1', '2', '2', '2', '2', '3', '2', '3', '3', '1', '2', '1', '2', '3', '1', '3', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '2', '2', '2', '2', '2', '1', '3', '3', '3']

可以看到,我们已经顺利导入数据,并对数据进行解析,格式化为分类器需要的数据格式。接着我们需要了解数据的真正含义。可以通过友好、直观的图形化的方式观察数据。

分析数据:数据可视化

绘制数据散点图:

if __name__ =='__main__':
    import numpy as np
    import matplotlib
    import matplotlib.pyplot as plt

    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*np.array(datingLabels),15.0*np.array(datingLabels))
    plt.xlabel("玩游戏所耗时间百分比")
    plt.ylabel("每周消费的冰淇淋公斤数")
    plt.show()

三类数据按照第2-3列属性聚类,不同颜色:

稍微修改代码位按照数据1-2列属性聚类:

ax.scatter(datingDataMat[:,0], datingDataMat[:, 1], 15.0*array(datingLabels), 15.0*array(datingLabels))

 

准备数据:数据归一化

    下表给出了四组样本,如果想要计算样本3和样本4之间的距离,可以使用欧拉公式计算

样本 玩游戏所耗时间百分比 每年获得的飞行常用里程数 每周消费的冰淇淋公升数 样本分类
1 0.8 400 0.5 1
2 12 134000 0.9 3
3 0 20000 1.1 2
4 67 32000 0.1 2

 

       我们很容易发现,上面方程中数字差值最大的属性对计算结果的影响最大,也就是说,每年获取的飞行常客里程数对于计算结果的影响将远远大于表中其他两个特征-玩视频游戏所耗时间占比和每周消费冰淇淋公斤数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。但海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。 

       在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

newValue = (oldValue - min) / (max - min)

       其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。在kNN.py文件中编写名为autoNorm的函数,用该函数自动将数据归一化。代码如下:

"""
数据归一化
"""
def autoNorm(dataSet):
    minVals = dataSet.min(0)                            #找出每一列的最小值
    maxVals = dataSet.max(0)                            #找出每一列的最大值
    range = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))           #创建与dataSet等大小的归一化矩阵,shape()获取矩阵的大小

    m = dataSet.shape[0]                                #获取dataSet第一维度的大小

    normDataSet = dataSet - np.tile(minVals,(m,1))      #将dataSet的每一行的对应列减去minVals中对应列的最小值
    normDataSet = normDataSet / np.tile(range,(m,1))    #归一化,公式newValue=(value-minvalue)/(maxVal-minVal)

    return normDataSet, range, minVals

 运行上述代码

if __name__ == '__main__':
    datingDataMat , datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    print(normMat)
    print(ranges)
    print(minVals)

       从运行结果可以看到,我们已经顺利将数据归一化了,并且求出了数据的取值范围和数据的最小值,这两个值是在分类的时候需要用到的,直接先求解出来,也算是对数据预处理了 。

测试算法:验证分类器

       机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。需要注意的是,10%的测试数据应该是随机选择的,由于海伦提供的数据并没有按照特定目的来排序,所以我么你可以随意选择10%数据而不影响其随机性。

    为了测试分类器效果,在kNN.py文件中创建函数datingClassTest,编写代码如下

"""
分类器针对约会网站的测试
"""

def datingClassTest():
    h = 0.1
    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat,datingLabels = file2matrix(r"D:\PythonWenjian\mlshizhan\knn\datingTestSet.txt")
    # print(datingDataMat)
    normMat , ranges,minVals = autoNorm(datingDataMat)   #数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    m =normMat.shape[0]                                  #获得normMat的行数

    numTestVecs = int(m*h)                               #百分之十的测试数据的个数

    errcount = 0.0                                       #分类错误计数
    for i in range (numTestVecs):
        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        classifierResult = knn_base(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with : %s,the real answer is: %s" %(classifierResult,datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errcount += 1.0
    print("the total error rate is: %f"%(errcount/float(numTestVecs)))


if __name__ == '__main__':
    datingClassTest()

运行上述代码,得到结果如图所示

      从结果中可以看出,错误率是8%,这是一个想当不错的结果。我们可以改变函数datingClassTest内变量hoRatio和分类器k的值,检测错误率是否随着变量值的变化而增加。依赖于分类算法、数据集和程序设置,分类器的输出结果可能有很大的不同。 

使用算法:构建完整可用系统

    我们可以给海伦一个小段程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息。程序会给出她对男方喜欢程度的预测值。

    在kNN.py文件中创建函数classifyPerson,代码如下:

# -*- coding: utf-8 -*-
# @Time    : 2018/3/23 0023 22:11
# @Author  : Administrator
# @FileName: knn.py

import numpy as np
import operator

def createDataSet():
    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 knn_base(x,dataSet,labels,k):
    """
     X 是输入的测试样本,是一个[x, y]样式的
     dataset 是训练样本集
     labels 是训练样本标签
     k 是top k最相近的

    """
    dataSetSize = dataSet.shape[0]   # shape返回矩阵的[行数,列数]shape[0]获取数据集的行数
    """
     numpy中tile()函数用法
     >>>numpy.tile([0,0],5)#在列方向上重复[0,0]5次,默认行1次
     array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
     >>> numpy.tile([0,0],(1,1))#在列方向上重复[0,0]1次,行1次
     array([[0, 0]])
     >>> numpy.tile([0,0],(2,1))#在列方向上重复[0,0]1次,行2次
     array([[0, 0],
           [0, 0]])

    """
    diff = np.tile(x,(dataSetSize,1)) - dataSet
    sqdiff = diff ** 2
    sqDistances = sqdiff.sum(axis = 1)          # axis=1表示按照横轴,sum表示累加,即按照行进行累加。
    #开方,计算出距离
    distances = sqDistances**0.5
    #返回distances中元素从小到大排序后的索引值
    sortedDistIndicies = distances.argsort()
    # 存放最终的分类结果及相应的结果投票数
    classCount = {}


    # 投票过程,就是统计前k个最近的样本所属类别包含的样本个数
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]  #sortedDistIndicies[i]是第i个最相近的样本下标 labels是样本index对应的分类结果(‘A’ or ‘B’)
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1  # classCount.get(voteIlabel, 0)返回voteIlabel的值,如果不存在,则返回0 然后加1
        # 把分类结果进行排序,然后返回得票数最多的分类结果

    sortedclassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedclassCount[0][0]

        # maxCount = 0
        # for key, value in classCount.items():
        #     if value > maxCount:
        #         maxCount = value
        #         classes = key
        #         print(classes)
        # return classes

"""
 将文本记录转换为Numpy的解析程序
"""


def file2matrix(filename):
    #打开文件
    fr=open(filename)
    #读取文件每一行到array0Lines列表
    #read():读取整个文件,通常将文件内容放到一个字符串中
    #readline():每次读取文件一行,当没有足够内存一次读取整个文件内容时,使用该方法
    #readlines():读取文件的每一行,组成一个字符串列表,内存足够时使用
    array0Lines=fr.readlines()
    #获取字符串列表行数行数
    numberOfLines=len(array0Lines)
    #返回的特征矩阵大小
    returnMat=np.zeros((numberOfLines,3))
    #list存储类标签
    classLabelVector=[]
    index=0
    for line in array0Lines:
        #去掉字符串头尾的空格,类似于Java的trim()
        line=line.strip()
        #将整行元素按照tab分割成一个元素列表
        listFromLine=line.split('\t')
        #将listFromLine的前三个元素依次存入returnmat的index行的三列
        returnMat[index,:]=listFromLine[0:3]
        #python可以使用负索引-1表示列表的最后一列元素,从而将标签存入标签向量中
        #使用append函数每次循环在list尾部添加一个标签值
        classLabelVector.append(int((listFromLine[-1])))
        # 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


"""
数据归一化
"""
def autoNorm(dataSet):
    minVals = dataSet.min(0)                            #找出每一列的最小值
    maxVals = dataSet.max(0)                            #找出每一列的最大值
    range = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))           #创建与dataSet等大小的归一化矩阵,shape()获取矩阵的大小

    m = dataSet.shape[0]                                #获取dataSet第一维度的大小

    normDataSet = dataSet - np.tile(minVals,(m,1))      #将dataSet的每一行的对应列减去minVals中对应列的最小值
    normDataSet = normDataSet / np.tile(range,(m,1))    #归一化,公式newValue=(value-minvalue)/(maxVal-minVal)

    return normDataSet, range, minVals


"""
分类器针对约会网站的测试
"""

def datingClassTest():
    h = 0.1
    #打开的文件名
    filename = "datingTestSet.txt"
    #将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix(filename)
    # print(datingDataMat)
    normMat , ranges,minVals = autoNorm(datingDataMat)   #数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    m =normMat.shape[0]                                  #获得normMat的行数

    numTestVecs = int(m*h)                               #百分之十的测试数据的个数

    errcount = 0.0                                       #分类错误计数
    for i in range (numTestVecs):
        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        classifierResult = knn_base(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with : %s,the real answer is: %s" %(classifierResult,datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errcount += 1.0
    print("the total error rate is: %f"%(errcount/float(numTestVecs)))



# 约会网站也测函数

def classifyPerson():
    resultList = ['not at all','in small doses','in large doses']
    percentTats = float(input("percentage of time spent playing video games?"))
    ffMiles = float(input("frequest filer miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))
    datingDataMat,datingLabels = file2matrix("datingTestSet2.txt")
    normMat ,ranges,minVals = autoNorm(datingDataMat)

    inArr = np.array([ffMiles,percentTats,iceCream])
    classifierResult = knn_base((inArr - minVals) / ranges,normMat,datingLabels,3)
    print("You will probaby like this person: " ,resultList[int(classifierResult) - 1])

if __name__ == '__main__':
   classifyPerson()

输入数据(10,10000,0.5),预测结果是”你可能有些喜欢这个人”,也就是这个人魅力一般。一共有三个档次:讨厌、有些喜欢、非常喜欢,对应着不喜欢的人、魅力一般的人、极具魅力的人。结果如图所示。

2.2 手写数字识别系统

构造一个能识别数字 0 到 9 的基于 KNN 分类器的手写数字识别系统。

需要识别的数字是存储在文本文件中的具有相同的色彩和大小:宽高是 32 像素 * 32 像素的黑白图像。

目录 trainingDigits 中包含了大约 2000 个例子,每个例子内容如下图所示,每个数字大约有 200 个样本;目录 testDigits 中包含了大约 900 个测试数据。

编写函数 img2vector(), 将图像文本数据转换为分类器使用的向量。

"""
将图像转化为测试向量
"""
from os import listdir
def img2Vector(filename):
    returnVect = np.zeros((1,1024))
    fr = open(filename)
    for i in range (32):
        linStr = fr.readline()
        for j  in range(32):
            returnVect[0,32*i+j] = int(linStr[j])       # 就是根据首地址(位置)的偏移量计算出当前数据存放的地址(位置)
    return returnVect

if __name__ == '__main__':
    testVector = img2Vector('testDigits/0_13.txt')
    print(testVector[0,0:31])

结果为:

 测试算法:编写函数使用提供的部分数据集作为测试样本,如果预测分类与实际类别不同,则标记为一个错误

def handwritingClassTest():
    hwLabels =[]
    trainingFileList = listdir('trainingDigits')       #列出给定目录的文件名列表,使用前需导入from os import listdir
    m = len(trainingFileList)                          #获取列表的长度
    trainingMat = np.zeros((m,1024))                   #创建一个m*1024的矩阵用于存储训练数据
    for i in range(m):
        fileNameStr = trainingFileList[i]              #获取当前行的字符串
        fileStr = fileNameStr.split('.')[0]            #将字符串按照'.'分开,并将前一部分放于fileStr
        classNumStr = int(fileStr.split('_')[0])       #将字符串按照'_'分开,并将前一部分放于classNumStr
        hwLabels.append(classNumStr)                   #将每个标签值全部存入一个列表中
        trainingMat[i:] = img2Vector(r'D:\PythonWenjian\mlshizhan\knn\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])
        vectornderTest = img2Vector('D:/PythonWenjian/mlshizhan/knn/testDigits/'+ fileNameStr)

        classfilerResult = knn_base(vectornderTest,trainingMat,hwLabels,3)        #分类预测
        print("the classifier came back with: %d, the real answer is: %d " %(classfilerResult,classNumStr))

        if classfilerResult != classNumStr:
            errorCount += 1.0
    # 打印错误数和错误率
    print("the total number of error is : %d" % errorCount)
    print("the total error rate is: %f" % (errorCount / float(mTest)))


if __name__ == '__main__':
    handwritingClassTest()

测试该函数的输出结果,依赖于机器速度,加载数据集可能需要花费很长时间,输出结果如下所示:

3  总结

KNN 三要素

     K的取值、距离度量 Metric/Distance Measure、分类决策 (decision rule)

kNN算法的优缺点

优点

  • 简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
  • 可用于数值型数据和离散型数据;
  • 训练时间复杂度为O(n);无数据输入假定;
  • 对异常值不敏感。

缺点:

  • 计算复杂性高;空间复杂性高;
  • 样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
  • 一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易发生误分。
  • 最大的缺点是无法给出数据的内在含义。

 参考文献:

1 https://blog.csdn.net/c406495762/article/details/75172850

2 https://blog.csdn.net/sinat_17196995/article/details/55052174

3https://github.com/apachecn/MachineLearning/blob/master/docs/2.k-%E8%BF%91%E9%82%BB%E7%AE%97%E6%B3%95.md

猜你喜欢

转载自blog.csdn.net/abc_138/article/details/81360373
今日推荐