今天梳理决策树。说起这一个模型真的也算是一个经典的模型,还好也不算太难。索性把我理解算法的精髓和编码实现都交代一下吧。
在现实生活中有些事情是可以或者方便量化的,比如上一篇逻辑回归中我们给每一道菜进行打分然后给这个菜一个评判,看看它是否好吃。然而有些事情可能量化起来不是这么容易,举一个例子,前两天Leo的同学说她被家里强迫去相亲,后来她说对方男孩还是挺好的。这个时候Leo就开始想相亲这种事,仁者见仁智者见智,每一个人评判的法则肯定不太一样,以我们世俗的眼光中,一般“高帅富”无疑是受女孩欢迎的,但是什么程度属于“高”,什么程度属于“帅”这个我们只能给出模糊的答案,那么这个模糊的答案如何将它进行计算,或者以此条件下的结果可不可信呢?决策树给了我们答案。
其实说到决策树,我比较清晰的还是用于作为分类。我们还是以女孩相亲作为一个例子,接着我们把相亲对象的条件分成“身高”,“长相”,“富有”三个部分。每一个男孩的都会被这三个指标去衡量,然后女孩根据男孩这些条件去评判这个人我见还是不见。这个结构确定下来应该是我们数据结构中的树状结构,例如下图:
PS:哈哈哈~这只是一个猜测,现实中说不定还是有真爱的~
那么言归正传,这个决策树看起来挺好的那么它是如何学习出来的?这就需要我们再细细探究一番。通过观察会其实我们会发现这个树学习的关键是找出它的各个节点之间的排列次序,既然所有的叶子节点都是判断的结果,那么哪一个特征需要我们拿来作为根节点,哪一个会成为它子节点......其实决策树的精髓也在于此,只要我们知道怎么去给特征排序,那么问题基本就解决了。
那么这个顺序我们怎么排呢?在说比较专业名词之前我们先大体猜一下这个怎么分,我们一般人想这个问题大体会想找出特征区分最鲜明的分,比如上图中“富有”的人都会被约,那么我们的根节点就安排给“富有程度”好了。
其实这个思路基本上也就是特征选择的思路。在说怎么分之前先介绍几个概念。
PS:摘抄自《统计学习方法》
第一个概念叫做熵:熵(entropy)是表示随机变量不确定性的度量。假设X是一个具有取有限个值得离散随机变量,其概率分布为:
则随机变量X的熵定义为:
若p=0 则定义0log0=0。
通常对数以2的底,这是熵的单位称作比特或者纳特,由此可知,熵只依赖于X的分布,而与X的取值无关,所以将X的熵记做H(p):
熵越大,随机变量的不确定性就越大。顺便一提:我们能发现,如果概率为0或者1时候,随机变量没有不确定性。
那我们看一下有两个随机变量(X,Y),其联合概率分布为:
知道了熵我们再看看条件熵:条件熵H(Y|X)表示在已知随机变量X的条件下的随见变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵H(Y|X),定义为X给定条件下Y的条件概率分布对X的数学期望:
最后我们需要看一下信息增益:信息增益表示特征X的信息而使得类Y的信息不确定性减少的程度,特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:
决策树学习应用信息增益准则选择特征,给定训练数据集D和特征集A,经验熵H(D)表示对数据集D分类的不准确性。而经验条件熵H(D|A)表示在特征A给定条件下对数据集D分类的不确定性。那么它的差为信息增益,就表示由于特征A而使得数据集D的分类不确定性减少的程度。可见,信息增益大的特征具有更强的分类能力~
好了,以上就是决策树的精髓了。剩下的就是完善这一套算法。我接着根据书上说的写一下ID3决策树的算法流程:
输入:训练数据集D,特征集A,阈值e;
输出:决策树T
1 :若D中所有实例属于同一类C,则T为单节点树,将类C作为该节点类标记,返回T;
2 :若A= 空集,则T为单节点树,并将D中实例最大的类C作为该节点的类标记,返回T;
3 :否则,计算每一个特征的信息增益,选择最大的特征;
4 :如果信息增益小于阈值e,则设置T为单节点树,并将D中实例数最大的类C作为该节点的类标记,返回T。
5 :否则,对A的每一个非空子集D进行分割,将最大的类标记为实例,构建子节点,由节点构成决策树T,返回T
6 :对第i个子节点,以D为训练集以A-{A(last)}为特征集递归1-5,得到子树T,返回T
下一步,咱们看一看这个东西python编码是如何实现的吧。
以下来自《知识学习实战python》
这里面比较重要的是找出经验熵和条件熵,经验熵的代码如下:
def calcShannonEnt(dataSet): numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: #the the number of unique elements and their occurance currentLabel = featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log(prob,2) #log base 2 return shannonEnt这里面用到的是python数据结构里的字典,去找其中的key值,然后统计出现的次数,最后结合咱们上面的公式(熵的定义)得出的这个系统的经验熵。PS:方法的名字里有shannon,就是香农定理的提出者,是信息学之父。
def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #the last column is used for the labels baseEntropy = calcShannonEnt(dataSet) bestInfoGain = 0.0; bestFeature = -1 for i in range(numFeatures): #iterate over all the features featList = [example[i] for example in dataSet]#create a list of all the examples of this feature uniqueVals = set(featList) #get a set of unique values newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet, i, value) prob = len(subDataSet)/float(len(dataSet)) newEntropy += prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy #calculate the info gain; ie reduction in entropy if (infoGain > bestInfoGain): #compare this to the best gain so far bestInfoGain = infoGain #if better than current best, set to best bestFeature = i return bestFeature #returns an integer这一段方法顾名思义是找出最佳的特征点,其实就是求出条件熵,并且找出最佳的增益特征的过程,它调用了之前的求经验上的方法,然后套入求条件熵的公式如下图:
ps: 看着挺麻烦,其实想想不难
返回的结果就是这一次选择出来的最佳的特征。
之后没什么可以用公式或者python知识解释的了,不过我们可能需要关注的是:决策树根据上面的流程是一个迭代的过程,那么我们把tree的迭代代码贴出来
def createTree(dataSet,labels): classList = [example[-1] for example in dataSet] if classList.count(classList[0]) == len(classList): return classList[0]#stop splitting when all of the classes are equal if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet) bestFeatLabel = labels[bestFeat] myTree = {bestFeatLabel:{}} del(labels[bestFeat]) featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) for value in uniqueVals: subLabels = labels[:] #copy all of labels, so trees don't mess up existing labels myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels) return myTree最后我们把数据的格式和准备结束的诸多过程再看一下
def splitDataSet(dataSet, axis, value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: reducedFeatVec = featVec[:axis] #chop out axis used for splitting reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet数据格式,没什么好说,是list
def majorityCnt(classList): classCount={} for vote in classList: if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0]作为分类的代码,对应的是算法流程的分类那一块
def classify(inputTree,featLabels,testVec): firstStr = inputTree.keys()[0] secondDict = inputTree[firstStr] featIndex = featLabels.index(firstStr) key = testVec[featIndex] valueOfFeat = secondDict[key] if isinstance(valueOfFeat, dict): classLabel = classify(valueOfFeat, featLabels, testVec) else: classLabel = valueOfFeat return classLabel这是分类的部分,就是把模型磊好了,下一次直接用这么模型对未知的数据进行分类。
不知道大家还不记得之前博客提到过的一个问题叫做"over_fitting",决策树的阈值e其实挺重要的,搞不好e会成为一个用处不大的over_fitting模型,所以说其实决策树这一块还没有结束。为此我们可以用随机森林,或者boosting等一些机制去把单薄的决策树逐渐完善,具体怎么做呢? 下回见啦~
原文地址:http://blog.csdn.net/leo_is_ant/article/details/43565505