决策树(DT)与随机森林(RF)是非常高效的分类算法。DT属于比较基础的弱分类器,基本原理是通过对不同维度逐一分类完成最终分类。RF属于DT的集成方式,在处理大数据量或特征较多的情况时可以很快完成训练。虽然训练效果往往不能达到SVM或者LR那么好,但是训练成本很低,适合需要快速完成的项目。
1. DT
当使用DT对目标数据进行分类时,若原始数据共有个特征,则可以通过将所有数据输入至DT的树根,然后以一个特征维度对数据进行分类。对于每个子类而言,再从剩余的个特征中选择一款,对其进行分类。循环往复,可以得到不断被细分之后的子类。对于特征而言,如果其本身就是离散的,则可以直接根据特征本身进行分类。比如性别可以按照男、女、其他分为三类。如果特征连续,则需要设定阈值进行分类。比如年龄可以按照0-18,19-25,26-40,40岁以上进行分类。
2. 各种熵
2.1 熵(entropy)
熵是用来表示系统混乱程度的物理量。其表达式为:
其中是第个事件发生的概率,当取1时,对应的熵为0。分类算法实际上是希望将不确定的事情变为确定的,因此可以通过熵来作为分类效果的表示量。如果DT每一次的分类都可以使熵下降,则最终会将不确定的事件变得确定下来。
举例来说,如果某层DT在分类前的发生概率是,假设分为3类,则对每个子类的概率,和而言,如果分类前的熵大于分类后的,即,则这一层的分类使得事件更加确定。
2.2 Conditional entropy
Conditional entropy is defined as:
which describes that outcome of a random variable Y given that the value of another random variable X is known. P(x) is the probability of X=x so that this equation indicates that the H(Y|X) is the average of the Y for every given x in X is given.
Wikipedia(https://en.wikipedia.org/wiki/Conditional_entropy) shows that:
This can be transformed as:
Thus, conditional entropy can be calculated directly as the jointentropy minus the given entropy.
2.3 相对熵
相对熵:设p(x)与q(x)为X取值的两个概率分布,则p对q的相对熵为
相对熵也叫KL散度,常用于度量两个随机变量间的距离,其实质是将p(x)与q(x)的比值的对数在p(x)上求期望。
3. 互信息
定义互信息I(X,Y)为X,Y的联合分布与独立分布乘积的交叉熵:
当X,Y独立时,p(x, y)与p(x)p(y)的比值为1,因此I(X,Y)为0。如果X,Y并不独立,则互信息的值大于0。因此互信息可以用来衡量X与Y的关联程度。
用熵减去互信息:
上式化简之后就是H(Y|X)。因此有
4. DT生成算法
4.1 ID3与信息增益
DT每一次只选择一个维度做分类,选取哪一个维度很关键。一般对于所有的候选维度,使DT在当前层熵值下降最明显的维度,将被作为这一层的分类维度,比如ID3。信息增益是ID3用来衡量熵值下降程度的量,特征A对训练集D的信息增益定义为:
ID3寻找信息增益更大的特征,然后对数据分类,但有时会有过拟合发生。比如,对于学生的各个数据进行分类,有性别,年龄,各科成绩,学号等等。以学号作为分类维度显然可以使得熵降到最低,因为每个人的学号都不相同。但该特征并不能够起到实际的分类效果。
4.2 C4.5与信息增益率
在4.1中给出的例子表明,虽然信息增益很大,但这种做法毫无意义,因为特征将数据分成太多太多类了,所以这个十分混乱的维度自身熵值很大。因此有的时候评价标准会用信息增益除以该维度自身的熵,计算信息增益率,比如C4.5。特征A对训练集D的信息增益率定义为:
基于学号维度分类熵值一定急剧下降,但是分类结果实际上很不理想。因为这相当于把每个人都分为单独的一类,导致过拟合。而如果使用信息增益率评价特征的话,学号这一维度的熵值很大,导致信息增益率实际并不高。
4.3 CART与Gini系数
首先给出Gini系数的定义:
定义中的项,实际上就是熵的计算公式中在处的一阶泰勒展开。Gini系数可以算是熵的一阶近似。
CART(Classification And Regression Tree)是可以进行分类以及回归的DT,对特征评价方式就是采用Gini系数。自身结构属于二叉树,每次对于特征只进行是、否的二分判断。即使特征内存在多种取值,CART一样会将该特征分为两个部分。比如年龄可以分为大于27和不大于27,学历可以分为硕士和非硕士。迭代结束之后,CART还要对其进行剪枝,去除掉过拟合部分。
5. 评价函数
DT可以根据叶子结点的熵值的加权和进行评价。可以将评价函数定义为:
其中t为叶子节点,N是该节点样本个数,因此评价函数是对于各个叶子节点的熵求加权和,评价函数的值越小越好。也可以把评价函数看成是loss function。评价函数可以用来后剪枝消除过拟合。在训练DT时计算出评价函数后,可以代入到测试集上观察测试效果,如果评价函数变大,则有可能DT太深导致过拟合,这时就需要剪掉一部分叶子。
6. RF
RF是指,对于大量的训练数据和特征时,将多棵DT用不同的样本及特征组合训练,最终通过集成DT的表决结果进行分类。如果将DT集成,在某一棵树上发生的过拟合,在其他的树上可能就会被避免。因此 RF算法有避免DT过拟合的能力。因为集成多棵DT对特征进行分类,每一棵DT的深度都不需要像单独的DT那样深,训练速度也是飞快。
7. Bagging
Bagging是指,对n个样本进行重采样n次。因此取到的样本虽然还是n个,但是会有原数据中的数据被重复采样的情况,同时也存在原数据中的数据没有被采样的情况。这一采样方法常常伴随DT/RF算法,可以通过bagging让RF的每棵树得到的训练数据不同。算是帮助RF消除过拟合的一种方式。不过bagging往往应用于弱分类器,并不是所有分类器都适用。比如SVM这种,模型极其依赖支持向量,假设采样时恰好没有选到支持向量,那么分割超平面的法线会发生比较大的偏移。
大约有63.2%的数据被采样到,剩下的数据被称为Out of bag(OOB)的数据。OOB数据可以用来做验证数据或测试数据。
RF不希望给每棵DT相同的样本,因此使用bagging,同时对于特征,RF也希望每个DT可以跑不同的特征。因此对于特征,RF往往也是分配给每个DT一个特征范围,不过由于特征无法重复,所以不用像bagging这样重采样。这样搭建的DT集群,样本,特征都是随机的,因此这样的一片森林,被称为随机森林。RF与其说是一种算法,不如说是一种集成思想,不仅仅针对DT的集成,对于SVM,LR之类的分类器,如果使用类似形式进行集成,也可以认为是RF的一种。
8. 投票表决与样本处理
既然RF集成了多棵DT,那么最终分类结果,就要有所有参与分类的DT进行投票表决。常见的做法是少数服从多数的投票机制,其他机制也存在,比如相对极端的一票否决制,或者给不同的DT赋予不同票数的加权投票机制。
机器学习算法往往比较依赖样本。如果样本过少,或者不均衡,那么训练效果将会很差,往往需要在取样过程中做一些处理。比如样本不均衡的时候,可以进行降采样/重采样来调节正例与负例的比例,甚至可以对较少样本进行随机插值生成新样本的方式将样本数量增大。样本过少,可以通过引入同类型数据的平均值对其进行稀释。
9. Iris数据分类
数据选用的是Iris数据,数据格式为:
5.1,3.5,1.4,0.2,Iris-setosa
前面的数据分别为该花的petal和sapel的长宽值,最后一项为label,是花的类型。总数据量为150条,三种花每种50条。将花的三种类型转为0,1,2之后,使用ID3的DT对iris数据进行分类,代码如下:
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
if __name__ == "__main__":
path = 'iris.data'
data = pd.read_csv(path, header=None)
x, y = np.split(data.values, (4,), axis=1)
# One-hot for Iris category as y
y = pd.Categorical(data[4]).codes
# print(data)
# print(x)
# print(y)
x_train, x_test, y_train, y_test = train_test_split(x, y, train_size=0.8, random_state=1)
dt = DecisionTreeClassifier(criterion='entropy')
dt.fit(x_train, y_train)
y_test_hat = dt.predict(x_test)
y_test = y_test.reshape(-1)
print(y_test_hat)
print(y_test)
result = (y_test_hat == y_test)
accuracy = np.mean(result)
print('Accuracy: ', accuracy)
# y_hat_prob = dt.predict_proba(x_test)
# print(y_hat_prob)
最终结果:Accuracy = 0.967