Python机器学习实战之K-近邻算法概述

一. k-近邻算法的概述

       简单的说,k-近邻算法采用测量不同特征值之间的距离方法进行分类,其工作原理:存在一个样本数据集,样本集中每个数据均有标签,即知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中的数据对应的特征进行比较,然后算法提取样本集中特征最相似数据的分类标签。通产只选择前k个最相似的数据。

k-近邻算法的一般流程:

  • 收集数据:可以使用任何方法
  • 准备数据:距离计算所需要的数值,最好是结构化的数据格式
  • 分析数据:可以使用任何方法
  • 训练算法:此步骤不适用与k-近邻算法
  • 测试算法:计算错误率
  • 使用算法:首先需要输入样本数据和结构化的输出成果,然后运行k-近邻算法判定输入数据属于哪个分类,最后应用对计算出的分类执行后续的处理

二. k-近邻算法的简单分类算法实现

2.1.1

from numpy import *
import operator

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 #py允许返回多个不同类型的值

在上面的代码中,我们导入了两个模块:科学计算包和运算符模块,此外creatDataSet函数创建了数据集和标签,两者一一对应,即A(1.0,1.1) A(1,1) B(0,0) B(0,0.1)

2.1.2 实施kNN分类算法

该分类算法的伪代码如下,对未知类别属性的数据集中每个点依次执行以下操作:

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

#描述k近邻算法
def classify0(inX,dataSet,labels,k):  #用于分类的输入向量是inX(需要判别的坐标),输入的训练样本集是dataset,标签向量为labels k为近邻算法的k
    dataSetSize = dataSet.shape[0]  #返回矩阵的行数
    diffMat = tile(inX,(dataSetSize,1)) - dataSet
    #tile构造的结果是[[0,0],[0,0],[0,0],[0,0]]  diffmat为坐标差的矩阵
    sqDiffMat = diffMat**2  #将列表中各元素二次方
    sqDistance = sqDiffMat.sum(axis=1) #将矩阵各行元素相加
    distances = sqDistance**0.5
    sortedDis = distances.argsort()  #将对应的索引排序
    classCount = {}
    for i in range(k):   #统计距离最近的k个点的类别
        label = labels[sortedDis[i]]
        classCount[label] = classCount.get(label,0) + 1
    sortedClass = sorted(classCount.items(),key = lambda item:item[1],reverse = True)
    return sortedClass[0][0]

测试数据[0,0]得到结果B,测试数据[1,1.1]得到结果A


2.2  示例:使用k-近邻算法改进约会网站的配对效果

     单身女士一直使用在线约会网站寻找适合自己的约会对象,尽管网站会推荐不同的人选,但她并不是喜欢每一个人。经过总结,她发现她曾经交往过的三种类型的人:不喜欢的人,魅力一般的人,极具魅力的人。因而网站需要收集各男士信息以划分类别,从而有助于女士们筛选。

2.2.1 准备数据:从文本文件中解析数据

40920	8.326976	0.953952	3
14488	7.153469	1.673904	2
26052	1.441871	0.805124	1
75136	13.147394	0.428964	1
38344	1.669788	0.134296	1
72993	10.141740	1.032955	1
35948	6.830792	1.213192	3
每个样本数据占据一行,样本数据包含以下三种特征:每年飞行里程数,玩视频游戏所耗时间百分比,每周消费的冰淇淋公升数,下面的代码将文本文档中的数据变为可分析的格式
#文本文件保留了样本的特征值
def file2Matrix(filename):
    file = open(filename)
    fileLines = file.readlines()
    numberOfLine = len(fileLines)
    returnMat = zeros((numberOfLine,3))  #用0填充矩阵 指定行号与列号
    classLabelVector = []
    index = 0
    for line in fileLines:
        line = line.strip() #截取掉所有回车字符
        listFormLine = line.split('\t')  #分割成一个元素列表‘
        returnMat[index,:] = listFormLine[0:3] #将元素列表赋值给矩阵
        classLabelVector.append(int(listFormLine[-1]))  #提取类别
        index = index + 1
    return returnMat,classLabelVector

2.2.2   分析数据:使用Matplotlib创建散点图

import kNN
import matplotlib
import matplotlib.pyplot as plt
from numpy import *


if __name__ == '__main__':
    datingDataMat,datingLabels = kNN.file2Matrix('C:/Users/lpw007/Desktop/datingTestSet2.txt')
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
    plt.show()

结果如下:



2.2.3  准备数据:归一化数值

从样本数据的特征值中可以发现,数值差值最大的属性对计算结果的影响最大,如飞行里程数对于计算结果的影响远远大于其他两个特征值。但女士们认为这三个特征同等重要,因此我们在处理这种不同取值范围的特征值时,通常采用的方法是将数值归一化,即newValue = (oldValue - minVal)/(maxVal - minVal)

#归一化特征值   new = (old - min)/(max - min)
def autoNorm(dataSet):
    minvals = dataSet.min(0)  #参数0使函数从列中寻找最小值
    maxvals = dataSet.max(0)
    ranges = maxvals - minvals
    normDataSet = zeros(shape(dataSet))  #创建0填充的矩阵
    m = dataSet.shape[0]  #矩阵的行数
    normDataSet = dataSet - tile(minvals,(m,1))    #计算old - min
    normDataSet = normDataSet/tile(ranges,(m,1))   #得出(old - min)/(max - min)
    return normDataSet,ranges,minvals   #返回归一化处理的特征值矩阵 差值 最小值

在函数autoNorm()中,我们将每一列的最小值放在minvals中,将最大值放在maxvals中,其中min(0)的参数0使得函数可以从列中选取最小值,而不是当前行的最小值。为了归一化特征值,我们必须使用当前值减去最小值,然后除以取值范围。需要注意的是,特征值矩阵有1000*3个值,而minVals和range的值都为1*3。为了解决这个问题,使用numpy库中tile( )函数将变量内容复制成输入矩阵同样大小的矩阵。

下面给出分类器的测试代码,测试该算法的错误率

datingDataMat,datingLabels = kNN.file2Matrix('C:/Users/lpw007/Desktop/datingTestSet2.txt')
    normMat,ranges,minvals = kNN.autoNorm(datingDataMat)
    hoRatio = 0.50
    m = normMat.shape[0]
    numTest = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTest):
        classifierResult = kNN.classify0(normMat[i,:],normMat[numTest:m,:],datingLabels[numTest:m],20)
        print("result:%d,the real:%d" %(classifierResult,datingLabels[i]))
        if(classifierResult != datingLabels[i]): errorCount += 1.0
    print("error rate:%f" % (errorCount/float(numTest)))

通过调整hoRatio和k的值来检测错误率是否随着变量值的增加而变化。通过测试之后,若错误率在可期范围内,则可以正式投入使用。下面代码对输入的男士信息进行分类,从而判断是否为喜欢的类型

#输入某个人的参数  判断类别
def classfiPerson():
    resultList = ['not at all','small doge','large doge']
    gamePercent = float(input("percentage of playing game:"))
    flightMiles = float(input('fly miles:'))
    iceCream = float(input('icecream:'))
    datingDataMat, datingLabels = file2Matrix('C:/Users/lpw007/Desktop/datingTestSet2.txt')
    normMat, ranges, minvals = kNN.autoNorm(datingDataMat)
    inArr = array([flightMiles,gamePercent,iceCream])
    classifyResult = classify0((inArr-minvals)/ranges,normMat,datingLabels,3)
    print('you will like this person:',resultList[classifyResult - 1])

2.3  示例:手写识别系统


如上两图所示,图2的txt文件0_1.txt表示描述数字0的图像一,图一为txt文件的内容,其通过32*32的矩阵表示数字图像,为了使用之前写好的分类算法classify0函数,这里将32*32转化为1*1024的NumPy数组,如下函数做到这点

#将图像转化成测试向量  我们将32*32的0和1组成的图像转化成1*1024的向量(为了使用之前定义好的分词器)
def imgVerctor(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        line = fr.readline()
        for j in range(32):
            returnVect[0,i*32+j] = int(line[j])
    return returnVect

上面我们已经将数据转化成分类器可处理的格式,接下来我们将trainingDigits文件夹下对应的数据转化成数据集,将testDigits文件夹下的txt文件中的数据转化为测试数据,下面代码实现这些功能

#测试算法
def handWritingTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList) #返回目录数
    trainingMat = zeros((m,1024))  #将所有训练数据用trainingMat存储
    for i in range(m):
        fileNameStr = trainingFileList[i] #txt文件名
        fileStr = fileNameStr.split('.')[0]
        classNum = int(fileStr.split('_')[0])
        hwLabels.append(classNum)  #记录对应的数字标签列表
        trainingMat[i,:] = imgVerctor('trainingDigits/%s' %fileNameStr)
    testFileList = listdir('testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNum = int(fileStr.split('_')[0])
        verctorTest = imgVerctor('testDigits/%s' %fileNameStr)
        classResult = classify0(verctorTest,trainingMat,hwLabels,3)
        print('result:%d,real:%d'%(classResult,classNum))
        if(classResult != classNum):errorCount += 1.0
    print('errorRate:%f' %(errorCount/float(mTest)))
得出的错误率为1.0%,修改k值,修改训练样本等会影响错误率。值得注意的是,使用该算法执行时间过长,执行效率并不高。因为算法需要对每个测试向量进行2000次距离计算,每次距离计算包括1024个维度,总共有900条测试数据。是否存在一种算法能减少存储空间和计算时间的开销呢?k-决策树就是k-近邻算法的改进版,可以节省大量的计算开销。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值