【ML学习笔记】13:k-近邻算法做数值特征分类

继续跟着机器学习白皮书学习k-近邻算法,这个例子给的样本集是三个特征一个标签的。

问题描述

这个问题讲的是一个女生想给自己约会的男生分类,特征是:

ffMiles:每年获得的飞行常客里程数
percentTats:玩视频游戏消耗的时间百分比
iceCream:每周消费的冰淇淋公斤数

标签的值是:

①不怎么好
②比较一般
③较为不错

为了方便,在给出的样本集里用数字1/2/3来表示:
这里写图片描述

模块实现

#-*-coding:utf-8-*-
from numpy import * #科学计算包
import operator     #运算符模块
from matplotlib import pyplot as plt
#上一行的末尾不能打注释!

#将文本记录转化为numpy(以tab分割的[3特征,1标签]的文本)
def file2matrix(filename):
    fr=open(filename) #打开文件
    arrayOLines=fr.readlines() #读取文件
    numberOfLines=len(arrayOLines) #获得文件行数
    returnMat=zeros((numberOfLines,3)) #行数=文件行数,列数=3的零矩阵
    classLabelVector=[]
    index=0
    #对于文件读进来的每一行
    for line in arrayOLines:
        line=line.strip() #strip()去掉首尾空格
        listFromLine=line.split('\t') #按Tab分割成列表
        #把这一行的特征给特征矩阵第index行
        returnMat[index,:]=listFromLine[0:3]
        #把这一行的标签给标签向量第index行
        classLabelVector.append(int(listFromLine[-1]))
        index+=1 #指向下一行
    return returnMat,classLabelVector #返回特征矩阵,标签向量

#k-近邻算法(输入的特征向量,训练集特征向量矩阵,训练集标签向量,k值)
def KJL(inX,dataSet,labels,k):
    #用shape[0]读取训练集第一维的长度(行数即训练集实例数)
    dataSetRow=dataSet.shape[0]
    '''
    #用shape[1]读取训练集第二维的长度(列数即特征数)
    dataSetCol=dataSet.shape[1]
    #读取输入向量的列数(特征数)
    inXCol=len(inX)
    #训练集标签向量的个数
    labelsLen=len(labels)
    #对训练集的判定
    if labelsLen!=dataSetRow:
        print "训练集的特征集和标签集实例数目不等"
        return
    #对输入的判定
    if inXCol!=dataSetCol:
        print "用于测试的的实例的特征数目和训练集特征数目不等"
        return
    '''
    #用tile()将输入的特征向量重复成和训练集特征向量一样多的行
    #变成2维,里面的维度重复1次,外面一层重复dataSetRow次
    diffMat=tile(inX,(dataSetRow,1))
    #减去训练集特征向量矩阵得到存偏差的矩阵
    diffMat=diffMat-dataSet
    #将减出来的偏差矩阵每个元素平方
    sqDiffMat=diffMat**2
    #对行求和,表示这个实例和这行对应的训练集实例的L2范数的平方
    sqDistances=sqDiffMat.sum(axis=1)
    #为了方便就不开根号(**0.5)了
    #argsort()返回其从小到大排序的排序索引序列
    sortIndex=sqDistances.argsort()
    #空字典,用来存各个标签在前k邻居中出现的次数
    classCount={}
    #找前k个距离最近的,也就是排序下标从0~k-1的
    for i in range(k):
        #暂存第i近(从0计数)训练集实例的标签
        voteIlab=labels[sortIndex[i]]
        #先取字典中以这个标签为key的value值,如果没有则返回0
        #加上1作为以这个标签为key的value值
        classCount[voteIlab]=classCount.get(voteIlab,0)+1
    #把classCount用iteritems()方法变成可迭代对象传入
    #用operator.itemgetter()方法定义一个函数给参数key,这个函数按1号域排序
    #将reverse参数显示修正为True,表示降序排序(找频数最大的)
    sortedClassCount=sorted(                
            classCount.iteritems(),
            key=operator.itemgetter(1),
            reverse=True)
    #排序好后,第0个对象就是要找的那个频率最高的实例的[标签,频率]了
    #只返回标签
    return sortedClassCount[0][0]

#展示kNN算法的数据集(特征矩阵,标签向量,第i号特征为横轴,第j号特征为纵轴)
def kNNshow(dataMat,labelVec,i,j):
    fig=plt.figure()
    ax=fig.add_subplot(111) #1行1列的第1个
    #把第0号特征和1号特征作为两维度绘制点,传入它们的list以绘制点
    #scatter()的第3和第4个参数决定点的颜色和大小,把类别号乘以一个系数以放大这种差异
    ax.scatter(dataMat[:,i],dataMat[:,j],15.0*array(labelVec),15.0*array(labelVec))
    plt.show()

#不同的特征,范围可能不同,作差得到的值可能差别很大
#如果它们的重要程度一样,显然不应如此
#可以将不同取值范围的特征值数值归一化到0~1之间
def autoNorm(dataMat):
    #下面参数0表示从列中取最大最小
    minVals=dataMat.min(0) #每列的最小值存到minVals里
    maxVals=dataMat.max(0) #每列的最大值存到maxVals里
    ranges=maxVals-minVals #每列最大最小值之差,存到ranges里
    newDataMat=zeros(shape(dataMat)) #用来存归一化后的矩阵
    m=dataMat.shape[0] #取第0维即行数
    newDataMat=dataMat-tile(minVals,(m,1)) #把最小值重复m成m行,用原值减去
    newDataMat=newDataMat/tile(ranges,(m,1)) #把减完的每个偏差值除以自己列最大和最小的差值
    return newDataMat,ranges,minVals #返回归一化之后的矩阵,原范围,原最小值

#测试分类器效果(不用传参的函数称为"自包含的")
def datingClassTest():
    hoRatio=0.10 #表示拿出10%的数据作为测试集
    dataMat,labelVec=file2matrix(r'datingTestSet2.txt') #调用成员函数入读入文件中的数据,形成特征矩阵和标签向量
    newMat,ranges,minVals=autoNorm(dataMat) #调用成员函数对特征矩阵归一化
    m=newMat.shape[0] #获得样本特征集的行数m
    numTestVecs=int(m*hoRatio) #用这个比例计算测试集的样本数目numTestVecs
    errorCount=0.0 #已经分类错误的样本数目,用小数是为了后面作除法得错误率方便
    #对测试集的每个条目(样本集中前numTestVecs个样本)而言
    for i in range(numTestVecs):
        #分类结果标签=KJL成员函数返回值
        #(前面的特征作测试集,后面的样本作训练集,k=3)
        classifierResult=KJL(newMat[i,:],\
                newMat[numTestVecs:m,:],\
                labelVec[numTestVecs:m],3)
        #每次测试说明测试集给出的标签和真实标签分别是什么
        print "分类器给出的标签: %d,真实标签: %d"\
                %(classifierResult,labelVec[i])
        #如果不同表示学习机器给出的分类结果错误
        if(classifierResult!=labelVec[i]):
            errorCount+=1.0 #那么分类错误数目加上1
    #最后打印出测试错误率是多少
    print "测试错误率是: %f"%(errorCount/float(numTestVecs))

#真正拿新样本做出预测的函数
def classifyPerson():
    #结果集,训练集标签向量里存的标签是1,2,3,这里是其内在含义
    resultList=['不怎么好','比较一般','较为不错']
    #提示输入0号特征
    ffMiles=float(raw_input("每年获得的飞行常客里程数?"))
    #提示输入1号特征
    percentTats=float(raw_input("玩视频游戏所消耗时间百分比?"))
    #提示输入2号特征
    iceCream=float(raw_input("每周消费的冰淇淋公斤数?"))
    dM,lV=file2matrix(r'datingTestSet2.txt') #读入训练集(特征矩阵,标签向量)
    newMat,ranges,minVals=autoNorm(dM) #对训练集特征矩阵做归一化
    inArr=array([ffMiles,percentTats,iceCream]) #把输入的特征组合到一行
    #分类结果标签=KJL成员函数返回值
    #注意传入的特征向量要用各特征最小值minVals和各特征范围ranges归一化
    #这就是它们也要被autoNorm训练集归一化函数返回的原因
    classifierResult=KJL((inArr-minVals)/ranges,\
            newMat,lV,3) #把训练集全部传入,做k=3的近邻算法
    print "分类结果:",resultList[classifierResult-1] #KJL返回的标签号和list下标有减1的关系

运行结果

还是把写好的模块和数据集放到一个目录下,然后cmd进入那个目录打开python,把模块import进来,直接调用里面的成员方法就可以用了。

①获得样本集数据

数据存放在txt文件中,读进矩阵以后,用切片操作对每一行的记录进行切片,从而得到特征矩阵标签向量,也就是得到了可使用的样本集。
这里写图片描述

②数据归一化

因为在k-近邻算法中,Lp距离总是要用到值之间的差来做计算,比如欧氏距离,对于不同类型的特征,差值总是有大有小的,这样做k-近邻算法时对距离的贡献是不一样的,如果我们认定这些特征同等重要,应该尽量保证它们对距离的贡献一致,归一化可以缓解这个问题(我个人认为不能完全解决这个问题,因为即便归一化以后,差值出现的概率密度还是不一样的)。

这里将各个特征都归一化到0~1之间,返回归一化以后的特征矩阵,以及各个特征的最小值向量各个特征的范围向量,这两个向量看起来是冗余的,实际上对于外来样本,也要做归一化,这个时候就需要用到它们了!
这里写图片描述

③画样本集的散点图

关于画散点图,书上给出了一个非常巧妙的方法。因为scatter的第3和第4个参数指定点的颜色和大小,而我们要把相同标签的点画成同一个颜色去观察,并且样本集里的标签完全是123这样的数字,所以可以把这个数值乘以一个放大的倍数,从而把不同类之间的差异放大,之间传给scatter的这两个参数上,从而画出不同样式的点,又能保证同类的点样式一致!

我把这个过程封装成了一个函数放进了这个模块里,因为特征有很多,在二维图上每次只能展示两个特征的关系,所以设定了两个参数i和j,传入它们以决定要以哪个特征为横轴,哪个特征为纵轴去观察数据集。

这里,我用刚刚归一化后的特征矩阵来画散点图观察:
这里写图片描述
这里写图片描述

这里写图片描述
这里写图片描述

这里写图片描述
这里写图片描述

④对外来样本作分类预测

这个是整个算法要实现的目标功能,已经封装好了,不用去像前面那样手动读样本集/归一化等,不过没有封装画图。
对于输入的外来样本的三个特征,能够使用k(里面设置成k=3)近邻算法得到预测的标签。
这里写图片描述
这里写图片描述
这里写图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值