机器学习实战(1)k-近邻算法(kNN)和决策树


把吴恩达的深度学习课程以及tensorflow等过了一遍,开始过一下机器学习的几个核心算法。

k-近邻算法

kNN算法不难,这里附上伪代码和手写数字的例子。

伪代码

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

代码实例

分类函数:

#简单分类
def classify0(inx,dataset,labels,k):
    datasize = dataset.shape[0]
    diff = tile(inx,(datasize,1)) - dataset
    sum = np.sum(np.square(diff),axis=1)
    distances = np.sqrt(sum)
    sort_distance = distances.argsort()
    count = {}
    for i in range(k):
        votelabels = labels[sort_distance[i]]
        count[votelabels] = count.get(votelabels,0) + 1
    print(count.items())
    sortcount = sorted(count.items(),key=operator.itemgetter(1),reverse=True)
    return sortcount[0][0]
#classify0([0,0],group,labels,3)

定义一个img2vector函数,将图片转化为(1,10254)的向量,这里为了方便读取的是一个文本的文件夹,每一个文本表示了一张图片,文本的命名例如0_72,下划线前的数字表示图片的label。

#这里无法用split()分开,所以采用一个for循环
def img2vector(filename):
    vect = np.zeros((1,1024))
    with open(filename) as fr:
        for i in range(32):
            linestr = fr.readline()
            for j in range(32):
                vect[0,i*32+j] = int(linestr[j])
    return vect

手写识别的测试代码:

#测试算法
import os
def classwriting():
    labels = []
    path = '/Users/enjlife/machine-learning/machinelearninginaction/ch02/digits/trainingDigits/'
    path1 = '/Users/enjlife/machine-learning/machinelearninginaction/ch02/digits/testDigits/'
    filelist = os.listdir(path)# 获取文件目录
    m = len(filelist)
    trainmat = np.zeros((m,1024))
    for i in range(m):
        fstr = filelist[i]
        filestr = fstr.split('.')[0]
        classnum = int(filestr.split('_')[0])
        labels.append(classnum)
        trainmat[i,:] = img2vector(path+fstr)
    testlist = os.listdir(path1)
    count = 0
    m1 = len(testlist)
    for i in range(m1):
        tstr = testlist[i]
        tfilestr = tstr.split('.')[0]
        tnum = int(tfilestr.split('_')[0])
        testvector = img2vector(path1+tstr)
        result = classify0(testvector,trainmat,labels,3)
        print('class result:%d,real result:%d'%(result,tnum))
        if (tnum != result): count +=1
    print('\nthe total num of error:%d'%count)
    print('\nthe total ratio of error:%f'%(count/m1))
output:
the total num of error:11
the total ratio of error:0.011628

kNN算法是分类数据中最简单有效的算法,但是算法的执行效率并不高,另外,它无法给出任何数据的基础结构信息,而决策树算法可以解决这个问题。
以上是kNN算法的代码,如果有需要数据集可以评论找我要哈~

决策树

决策树算法,计算复杂度不高,输出结果易于理解,缺点是可能产生过度匹配问题。
首先计算香农熵 H = i = 1 n p ( x i ) l o g 2 p ( x i ) H=-\sum_{i=1}^n{p(x_i){log_2{p({x_i})}}} ,得到熵之后,我们可以按照最大信息增益的方法划分数据集。另一个度量集合无序程度的方法是基尼不纯度。

import math
def non(dataset):
    num = len(dataset)
    label = {}
    for data in dataset:
        cur = data[-1]# 结果标签
        if cur not in label.keys():
            label[cur] = 0
        label[cur] += 1
    nonent=0
    for key in label.keys():
        prob = float(label[key])/num
        nonent -= prob*math.log(prob,2)
    return nonent

定义一个测试数据集

def dataset():
    dataset = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']]
    labels = ['no surfacing','flippers']
    return dataset,labels

给定一个特征和值,计算应该得出的数据集。

#按给定特征划分数据集
def splitdata(dataset,axis,value):
    ret = []
    for data in dataset:
        if data[axis]==value:
            reduceddata = data[:axis]
            reduceddata.extend(data[axis+1:])
            ret.append(reduceddata)
    return ret

在写区分数据集的函数的时候,不小心运行了一个很神奇的代码,建议尝试下下面的代码,哈哈~

#很神奇的一个列表处理方式
def splitdata(dataset,axis,value):
    for data in dataset:
        if data[axis]==value:
            reduceddata = data[:axis]
            reduceddata.extend(data[axis+1:])
            dataset.append(reduceddata)
    return dataset

最后,定义选择特征的函数,此函数会评估各个特征值,挑选出能够使新数据集熵最小的特征。

def cfeature(dataset):
    numf = len(dataset[0])-1# 最后一个元素是当前实例的类别标签
    baseentropy = non(dataset)
    bestgain = 0.0
    feature = -1
    for i in range(numf):
        featlist = [example[i] for example in dataset]
        va = set(featlist) # 提取特征
        newentropy = 0.0
        for value in va:
            subdata = splitdata(dataset,i,value)
            prob = len(subdata)/float(len(dataset))# 每个特征的value求概率
            newentropy += prob*non(subdata)
            infogain = baseentropy - newentropy
            if (infogain > bestgain): # 感觉这里代码有点冗余
                bestgain = infogain
                feature = i
    return feature

运行结果为0,则表明第一个特征是能是熵减小最大的特征。
创建树的函数代码,当所有属性都选择完毕后,类标签依然不唯一,则采取多数表决的方法。第二个函数则是树的创建代码,当数据集的只剩类标签而类标签不唯一,则采用majorcnt函数来决定类标签的值。

import operator
def majorcnt(classlist):
    count={}
    for vote in classlist:
        if vote not in count.keys():
            count[vote]=0
    sort = sorted(count.items(),key=operator.itemgetter(1),reverse=True)
    return sort
#创建树的函数
def createtree(dataset,labels):
    classlist = [example[-1] for example in dataset]
    if classlist.count(classlist[0]) == len(classlist):
        return classlist[0]
    if len(dataset[0]) == 1:
        return majorcnt(classlist)
    feature = cfeature(dataset)
    print(feature)
    print(labels)
    featlabel = labels[feature]
    mytree = {featlabel:{}}
    del(labels[feature])
    value = [example[feature] for example in dataset]
    va = set(value)
    for val in va:
        sublabels = labels[:]
        mytree[featlabel][val] = createtree(splitdata(dataset,feature,val),sublabels)
    return mytree

绘制决策树

接下来绘制决策树,利用matplotlib提供的注解工具annotation。
定义一个节点绘制函数

#绘制树
import matplotlib.pyplot as plt

decision = dict(boxstyle='sawtooth',fc='0.8')# 描绘边框等的属性
leaf = dict(boxstyle='round4',fc='0.8')
arrow_args = dict(arrowstyle='<-')
def plotnode(txt,cpt,ppt,ntype):
    createPlot.ax1.annotate(txt,xy=ppt,xycoords='axes fraction',
                            xytext=cpt,textcoords='axes fraction',
                            va='center',ha='center',bbox=ntype,arrowprops=arrow_args)
def createPlot():
    fig = plt.figure(1,facecolor='white')
    fig.clf()
    createPlot.ax1 = plt.subplot(111,frameon=False)#ax1相当于赋予一个属性
    plotnode('decision',(0.5,0.1),(0.1,0.5),decision)
    plotnode('leaf',(0.8,0.1),(0.3,0.8),leaf)
    plt.show()

下面来构建注解树,注解树比较抽象,我们来看一下。
在构建注解树前我们需要确认下树的节点数和树的层数。

#获取叶节点的数目和树的层数
def getnum(mytree):
    num = 0
    first = list(mytree.keys())[0]
    seconddict = mytree[first]
    for key in seconddict.keys():
        if type(seconddict[key]).__name__=='dict':
            num += getnum(seconddict[key])
        else: num +=1
    return num
def getdepth(mytree):
    maxdepth = 0
    first = list(mytree.keys())[0]
    seconddict = mytree[first]
    for key in seconddict.keys():
        if type(seconddict[key]).__name__=='dict':
            thisdepth = 1 + getdepth(seconddict[key])
        else: thisdepth = 1
        if thisdepth > maxdepth: maxdepth = thisdepth
    return maxdepth

下面来构建注解树函数,这里首先构建了一个箭头文字的注解,采用的是两点平均。后面包含两个函数,createPlot是主函数,定义了绘制的图和初始的树的节点数和层数以及最近一次绘制节点的初始位置(xOff,yOff),然后调用plottree函数。首先计算传入的树的节点数和层数(这是个递归函数),获取第一个key值,计算子节点的位置,注意这里计算的子节点还是传入的树,所以与传入的树的父节点是相同的,因而绘制的plottext其实为空,而在计算plotnode时,cpt和ppt相同,所以并没有箭头。这里最关键的是cpt的计算方式,xOff在初始时为-0.5*(1/totalW),这里其实为了方便计算,先不考虑。xOff下一个节点的x位置应该加(1/totalW)(1/2),之所以除2是因为每个节点都位于所占位置的中间,而又因为我们在初始化时是-0.5(1/totalW),所以第一个点我们这里重新加回来。

#plottree
def plottext(cpt,ppt,txt):
    xmid = (ppt[0]-cpt[0])/2.0 + cpt[0] #箭头上文字位置=子节点+1/2距离
    ymid = (ppt[1]-cpt[1])/2.0 + cpt[1]
    createPlot.ax1.text(xmid,ymid,txt)
def plottree(mytree,ppt,nodetxt):
    numl = getnum(mytree)
    numd = getdepth(mytree)
    first = list(mytree.keys())[0]
    cpt = (plottree.xOff + (1.0+float(numl))/2.0/plottree.totalW,plottree.yOff)
    plottext(cpt,ppt,nodetxt)
    plotnode(first,cpt,ppt,decision)
    seconddict = mytree[first]
    plottree.yOff = plottree.yOff-1.0/plottree.totalD
    for key in seconddict.keys():
        if type(seconddict[key]).__name__=='dict':
            plottree(seconddict[key],cpt,str(key))
        else:
            plottree.xOff = plottree.xOff+1.0/plottree.totalW
            plotnode(seconddict[key],(plottree.xOff,plottree.yOff),cpt,leaf)
            plottext((plottree.xOff,plottree.yOff),cpt,str(key))
    plottree.yOff = plottree.yOff + 1.0/plottree.totalD
def createPlot(intree):
    fig = plt.figure(1,facecolor='white')
    fig.clf()
    axprops = dict(xticks=[],yticks=[])
    createPlot.ax1 = plt.subplot(111,frameon=False,**axprops)#ax1相当于赋予一个属性
    plottree.totalW = float(getnum(intree))
    plottree.totalD = float(getdepth(intree))
    plottree.xOff = -0.5/plottree.totalW #初始化x
    plottree.yOff = 1.0
    plottree(intree,(0.5,1.0),'')
    plt.show()

本文参考书籍《机器学习实战》Peter Harrington

猜你喜欢

转载自blog.csdn.net/weixin_40548136/article/details/86603569