1. 决策树
1.1 概念
决策树是一种树形结构,为人们提供决策依据,决策树可以用来回答yes和no问题,它通过树形结构将各种情况组合都表示出来,每个分支表示一次选择(选择yes还是no),直到所有选择都进行完毕,最终给出正确答案。决策树是一种贪心算法,它要在给定时间内做出最佳选择,但 并不关心能否达到全局最优 。
决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。在实际构造决策树时,通常要进行剪枝,这是为了处理由于数据中的噪声和离群点导致的过分拟合问题。剪枝有两种:
先剪枝——在构造过程中,当某个节点满足剪枝条件,则直接停止此分支的构造。
后剪枝——先构造完成完整的决策树,再通过某些条件遍历树进行剪枝。
1.2 划分准则
决策树学习的关键:如何选择最优划分属性
划分数据集的大原则是:将无序的数据变得更加有序
划分数据集,构建决策树时将对每个特征划分数据集的结果计算一次信息增益/基尼指数/增益率,然后判断按照哪个特征划分数据集是最好的划分方式。
(1)信息增益
信息增益越大,则意味着使用属性 α 来进行划分所获得的"纯度提升"越大。信息增益准则对可取值数目较多的属性有所偏好。
(2)增益率
属性 α 的可能取值数目越多(即 V 越大),则 IV(α) 的值通常会越大,增益率越小。增益率准则对可取值数目较少的属性有所偏好。
(3)基尼指数
基尼值:
基尼指数:
Gini(D) 反映了从数据集 D 中随机抽取两个样本,其类别标记不一致的概率。 因此, Gini(D) 越小,两个样本的类别越一致,则数据集 D 的纯度越高。
1.3 决策树算法
(1)ID3
以信息增益为准则来选择划分属性,用于划分离散型数据集。
做法:
每次选取当前最佳的特征来分割数据,并按照该特征的所有可能取值来切分。一旦按某特征切分后,该特征在之后的算法执行过程中将不会再起作用,所以有观点认为这种切分方式过于迅速。
缺点:
- 切分方式过于迅速;
- 不能直接处理连续型特征。只有事先将连续型特征转换成离散型,才能使用。这种转换过程会破坏连续型变量的内在性质。
ID3算法无法直接处理数值型数据,尽管我们可以通过量化的方法将数值型数据转化为离散型数值,但是如果存在太多的特征划分, ID3算法仍然会面临其他问题。
(2)C4.5
以增益率为准则来选择划分属性,核心算法ID3的改进算法。
C4.5比ID3改进的地方:
(3)CART
CART决策树(分类回归决策树):使用"基尼指数" 来选择划分属性。
CART是十分著名且广泛记载的树构建算法,它使用二元切分来处理连续型变量:
二元切分法:每次把数据集切成两份
做法:如果特征值大于给定值就走左子树, 否则就走右子树。
优点:易于对树构建过程进行调整以处理连续型特征; 二元切分法也节省了树的构建时间。
1.4 代码实现
- ID3选择属性用的是子树的信息增益,即熵的变化值;而C4.5用的是信息增益率。一般来说率就是用来取平衡用的,比如有两个跑步的人,一个起点是10m/s的人、其1s后为20m/s;另一个人起速是1m/s、其1s后为2m/s。如果紧紧算差值那么两个差距就很大了,如果使用速度增加率(加速度)来衡量,2个人就是一样了。在这里,其克服了用信息增益选择属性时偏向选择取值多的属性的不足。
- 在树构造过程中进行剪枝。有些节点只挂着几个元素,对于这种节点,干脆不考虑最好,不然很容易导致overfitting。
- 对非离散数据都能处理,也就是把连续性的数据转化为离散的值进行处理。这个其实就是一个个式,看对于连续型的值在哪里分裂好。
- 能够对不完整数据进行处理。这个重要也重要,其实也没那么重要,缺失数据采用一些方法补上去就是了。
决策树主要是调用sklearn里面函数,这个里面包含了DecisionTreeClassifier,不需要我们自己去实现。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor
if __name__ == "__main__":
n = 500
x = np.random.rand(n) * 8 - 3
x.sort()
y = np.cos(x) + np.sin(x) + np.random.randn(n) * 0.4
x = x.reshape(-1, 1)
reg = DecisionTreeRegressor(criterion='mse')
# reg1 = RandomForestRegressor(criterion='mse')
dt = reg.fit(x, y)
# dt1 = reg1.fit(x, y)
x_test = np.linspace(-3, 5, 100).reshape(-1, 1)
y_hat = dt.predict(x_test)
plt.figure(facecolor="w")
plt.plot(x, y, 'ro', label="actual")
plt.plot(x_test, y_hat, 'k*', label="predict")
plt.legend(loc="best")
plt.title(u'Decision Tree', fontsize=17)
plt.tight_layout()
plt.grid()
plt.show()
2. 随机森林
2.1 Bagging策略
Bagging( bootstrap aggregation)的策略:从样本集中进行有放回地选出n个样本;在样本的所有特征上,对这n个样本建立分类器;重复上述两步m次,获得m个样本分类器;最后将测试数据都放在这m个样本分类器上,最终得到m个分类结果,再从这m个分类结果中决定数据属于哪一类(多数投票制)。
Bootstrap:一种有放回的抽样方法。
随机森林采用了Bagging策略,且在其基础上进行了一些修改,采用了两个随机:
- 从训练样本集中使用Bootstrap采样(随机有放回)选出n个样本。
- 设样本共有b个特征,从这b个特征中只随机选择k个特征来分割样本,通过计算选择最优划分特征作为节点来划分样本集合来建立决策树。(与Bagging的不同之处:没有使用全部的特征,这样可以避免一些过拟合的特征,不再对决策树进行任何剪枝)
- 重复以上两步m次,可建立m棵决策树
- 这m棵决策树形成了森林,可通过简单多数投票法(或其他投票机制)来决定森林的输出,决定属于哪一类型。(针对解决回归问题,可以采用单棵树输出结果总和的平均值)
随机森林在一定程序上提高了泛化能力,而且可以并行地生成单棵树。
2.2 代码示例
使用决策树和随机森林进行手写数字(sklearn中的digits数据)的预测
from sklearn import datasets
from sklearn.model_selection import cross_val_score
import datetime
from sklearn import tree
from sklearn.ensemble import RandomForestClassifier
digits = datasets.load_digits();
X = digits.data # 特征矩阵
y = digits.target # 标签矩阵
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=1/3., random_state=8) # 分割训练集和测试集
estimators = {}
# criterion: 分支的标准(gini/entropy)
# 1.决策树
estimators['tree'] = tree.DecisionTreeClassifier(criterion='gini',random_state=8)
# 2.随机森林
# n_estimators: 树的数量
# bootstrap: 是否随机有放回
# n_jobs: 可并行运行的数量
estimators['forest'] = RandomForestClassifier(n_estimators=20,criterion='gini',bootstrap=True,n_jobs=2,random_state=8)
for k in estimators.keys():
start_time = datetime.datetime.now()
# print '----%s----' % k
estimators[k] = estimators[k].fit(X_train, y_train)
pred = estimators[k].predict(X_test)
# print pred[:10]
print("%s Score: %0.2f" % (k, estimators[k].score(X_test, y_test)))
scores = cross_val_score(estimators[k], X_train, y_train,scoring='accuracy' ,cv=10)
print("%s Cross Avg. Score: %0.2f (+/- %0.2f)" % (k, scores.mean(), scores.std() * 2))
end_time = datetime.datetime.now()
time_spend = end_time - start_time
print("%s Time: %0.2f" % (k, time_spend.total_seconds()))
未完待续。。。