决策树
决策树是一种基本的分类方法,当然也可以用于回归。我们一般只讨论用于分类的决策树。决策树模型呈树形结构。在分类问题中,表示基于特征对实例进行分类的过程,它可以认为是if-then规则的集合。在决策树的结构中,每一个实例都被一条路径或者一条规则所覆盖。通常决策树学习包括三个步骤:特征选择、决策树的生成和决策树的修剪
优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理逻辑回归等不能解决的非线性特征数据
缺点:可能产生过度匹配问题
适用数据类型:数值型和标称型
特征选择
特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率,如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的京都影响不大。通常特征选择的准则是信息增益,这是个数学概念。通过信息增益生成的决策树结构,更加明显、快速的划分类别。
信息的度量和作用
1948年,香农在他的论文“通信的数学原理”中提到了“信息熵”的概念,解决了信息的度量问题,并量化出信息的作用。
一条信息的信息量与其不确定性有着直接的关系,比如我们要搞清一件非常不确定的事,就需要大量的信息。相反如果对某件事了解较多,则不需要太多的信息就能把它搞清楚 。所以从这个角度看,可以认为,信息量就等于不确定的多少。
信息增益
自古以来,信息和消除不确定性是相联系的。所以决策树的过程其实是在寻找某一个特征对整个分类结果的不确定减少的过程。那么这样就有一个概念叫做信息增益(information gain)。
那么信息增益表示得知特征X的信息而是的类Y的信息的不确定性减少的程度,所以我们对于选择特征进行分类的时候,当然选择信息增益较大的特征,这样具有较强的分类能力。特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即公式为:
g ( D , A ) = H ( D ) − H ( D ∣ A ) g\left({D,A}\right){=}H\left(D\right) {-} H\left(D|A\right) g(D,A)=H(D)−H(D∣A)
根据信息增益的准则的特征选择方法是:对于训练数据集D,计算其每个特征的信息增益,并比较它们的阿笑,选择信息增益最大的特征
信息增益的计算
假设训练数据集为D,|D|表示样本个数。设有K个类 C k C_k Ck,k=1,2,3,4…k, ∣ C k ∣ |C_k| ∣Ck∣为属于类 C k C_k Ck的样本个数, ∑ k = 1 K = ∣ D ∣ \sum_{k=1}^{K}{=}{|D|} ∑k=1K=∣D∣.设特征A有n个不同的取值{a1,a2,…,an},根据特征A的取值将D划分为n个子集D1,D2,…,Dn, ∣ D i ∣ |Di| ∣Di∣为样本个数,其中Di中属于Ck类的样本的集合为 D i k D_ik Dik
经验熵计算:
条件熵计算:
sklearn.tree.DecisionTreeClassifier
sklearn.tree.DecisionTreeClassifier是一个能对数据集进行多分类的类
class sklearn.tree.DecisionTreeClassifier(criterion='gini', splitter='best', max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_split=1e-07, class_weight=None, presort=False)
"""
:param max_depth:int或None,可选(默认=无)树的最大深度。如果没有,那么节点将被扩展,直到所有的叶子都是纯类,或者直到所有的叶子都包含少于min_samples_split样本
:param random_state:random_state是随机数生成器使用的种子
"""
首先我们导入类,以及数据集,还有将数据分成训练数据集和测试数据集两部分
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
iris = load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
estimator = DecisionTreeClassifier(max_leaf_nodes=3, random_state=0)
estimator.fit(X_train, y_train)
method
apply 返回每个样本被预测的叶子的索引
estimator.apply(X)
array([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 15, 5, 5, 5, 5, 5, 5, 10, 5, 5, 5, 5, 5, 10, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 16, 16,
16, 16, 16, 16, 6, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
8, 16, 16, 16, 16, 16, 16, 14, 16, 16, 11, 16, 16, 16, 8, 8, 16,
16, 16, 14, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16])
decision_path 返回树中的决策路径
dp = estimator.decision_path(X_test)
**fit_transform(X,y=None,fit_params) 输入数据,然后转换
predict(X) 预测输入数据的类型,完整代码
estimator.predict(X_test)
array([2, 1, 0, 2, 0, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0,
0, 1, 0, 0, 1, 1, 0, 2, 1, 0, 1, 2, 1, 0, 2])
print y_test
array([2, 1, 0, 2, 0, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0,
0, 2, 0, 0, 1, 1, 0, 2, 1, 0, 2, 2, 1, 0, 1])
score(X,y,sample_weight=None) 返回给定测试数据的准确精度
estimator.score(X_test,y_test)
0.89473684210526316
决策树保存
sklearn.tree.export_graphviz() 该函数能够导出DOT格式
from sklearn.datasets import load_iris
from sklearn import tree
clf = tree.DecisionTreeClassifier()
iris = load_iris()
clf = clf.fit(iris.data, iris.target)
tree.export_graphviz(clf,out_file='tree.dot')
那么有了tree.dot文件之后,我们可以通过命令转换为png或者pdf格式,首先得安装graphviz
centos7.5:sudo pip3 install graphviz
然后我们运行这个命令
$ dot -Tps tree.dot -o tree.ps
$ dot -Tpng tree.dot -o tree.png
如果我们安装了Python模块pydotplus,我们可以直接在Python中生成PDF文件,通过pip3 install pydotplus,然后运行
import pydotplus
dot_data = tree.export_graphviz(clf, out_file=None)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_pdf("iris.pdf")
查看决策树结构图片,这个结果是经过决策树学习的三个步骤之后形成的。当作了解
决策树优缺点
决策树优点:
- 简单的理解和解释。树木可视化。
- 需要很少的数据准备。其他技术通常需要数据归一化,需要创建虚拟变量,并删除空值。但请注意,此模块不支持缺少值。
- 使用树的成本(即,预测数据)在用于训练树的数据点的数量上是对数的。
决策树缺点:
- 决策树学习者可以创建不能很好地推广数据的过于复杂的树。这被称为过拟合。修剪(目前不支持)的机制,设置叶节点所需的最小采样数或设置树的最大深度是避免此问题的必要条件。
- 决策树可能不稳定,因为数据的小变化可能会导致完全不同的树被生成。通过使用合奏中的决策树来减轻这个问题。
随机森林
在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。利用相同的训练数搭建多个独立的分类模型,然后通过投票的方式,以少数服从多数的原则作出最终的分类决策。例如, 如果你训练了5个树, 其中有4个树的结果是True, 1个数的结果是False, 那么最终结果会是True.
在前面的决策当中我们提到,一个标准的决策树会根据每维特征对预测结果的影响程度进行排序,进而决定不同的特征从上至下构建分裂节点的顺序,如此以来,所有在随机森林中的决策树都会受这一策略影响而构建的完全一致,从而丧失的多样性。所以在随机森林分类器的构建过程中,每一棵决策树都会放弃这一固定的排序算法,转而随机选取特征。
学习算法
根据下列算法而建造每棵树:
- 用N来表示训练用例(样本)的个数,M表示特征数目。
- 输入特征数目m,用于确定决策树上一个节点的决策结果;其中m应远小于M。
- 从N个训练用例(样本)中以有放回抽样的方式,取样N次,形成一个训练集(即bootstrap取样),并用未抽到的用例(样本)作预测,评估其误差。
- 对于每一个节点,随机选择m个特征,决策树上每个节点的决定都是基于这些特征确定的。根据这m个特征,计算其最佳的分裂方式。
sklearn.ensemble,集成方法模块
sklearn.ensemble提供了准确性更加好的集成方法,里面包含了主要的RandomForestClassifier(随机森林)方法。
class sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, bootstrap=True, oob_score=False, n_jobs=1, random_state=None)
"""
:param n_estimators:integer,optional(default = 10) 森林里的树木数量。
:param criteria:string,可选(default =“gini”)分割特征的测量方法
:param max_depth:integer或None,可选(默认=无)树的最大深度
:param bootstrap:boolean,optional(default = True)是否在构建树时使用自举样本。
"""
属性
-
classes_:shape = [n_classes]的数组或这样的数组的列表,类标签(单输出问题)或类标签数组列表(多输出问题)。
-
featureimportances:array = [n_features]的数组, 特征重要性(越高,功能越重要)。
方法 -
fit(X,y [,sample_weight]) 从训练集(X,Y)构建一棵树林。
-
predict(X) 预测X的类
-
score(X,y [,sample_weight]) 返回给定测试数据和标签的平均精度。
-
decision_path(X) 返回森林中的决策路径
案例——波士顿房屋租赁价格预测
这里我通过决策树和随机森林对这个数据进行一个分类,进行波士顿房屋租赁价格预测
# 导入相应的包
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import warnings # 警告处理
from sklearn.linear_model.coordinate_descent import ConvergenceWarning # 警告处理
from sklearn.linear_model import LinearRegression, LassoCV, RidgeCV # 回归模型
from sklearn.tree import DecisionTreeRegressor # 回归决策树模型
from sklearn.model_selection import train_test_split # 数据分割
from sklearn.preprocessing import MinMaxScaler # 数据归一化
from matplotlib.font_manager import FontProperties
# 设置字符集,防止中文乱码
my_font = FontProperties(fname="/usr/share/fonts/chinese/simsun.ttc")
mpl.rcParams['axes.unicode_minus'] = False
# 拦截异常
warnings.filterwarnings(action = 'ignore', category=ConvergenceWarning)
# 1.读取数据
names = ['CRIM','ZN', 'INDUS','CHAS','NOX','RM','AGE','DIS','RAD','TAX','PTRATIO','B','LSTAT']
path = "/root/zhj/python3/code/data/boston_housing.data"
# 由于每条数据的格式不统一,所以可以先按一行一条记录的方式来读取,然后再进行数据预处理
fd = pd.read_csv(path, header = None) # header = None表示没有数据对应的名称,可以给数据加上
# 2.数据处理
data = np.empty((len(fd), 14)) # 生成形状为[len(fd), 14]的空数组
# 用于预处理数据
def notEmpty(s):
return s != ''
# 对每条记录依次处理
for i, d in enumerate(fd.values): # enumerate生成一列索引i(表示fd中的每一条记录), d为其元素(此处d就是fd的一条记录内容)
d = map(float, filter(notEmpty, d[0].split(' '))) # filter一个函数,一个list
"""
d[0].split(' '):将每条记录按空格切分,生成list,可迭代
notEmpty:调用前面的自定义的函数,将空格表示为False,非空格表示为True
filter(function,iterable):将迭代器传入函数中
map(function,iterable):对迭代器进行function操作,这里表示根据filter结果是否为真,来过滤list中的空格项
"""
# map操作后的类型为map类型,转为list类型,并将该条记录存在之前定义的空数组中
data[i] = list(d)
# 遍历完所有数据,数据也就处理好了
# 3.划分数据
X, Y = np.split(data, (13,), axis=1) # 前13个数据划为X,最后一个划为Y
# 将Y拉直为一个扁平的数组
Y = Y.ravel()
# 查看下数据
# print(y.shape)
# print ("样本数据量:%d, 特征个数:%d" % x.shape)
# print ("target样本数据量:%d" % y.shape[0])
# 4.数据分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=14)
print ("训练数据集样本数目:%d, 测试数据集样本数目:%d" % (X_train.shape[0], X_test.shape[0]))
# 5.数据归一化
ss = MinMaxScaler() # 创建归一化模型
X_train = ss.fit_transform(X_train, Y_train) # 训练模型并转换数据
X_test = ss.transform(X_test) # 转换数据
print ("原始数据各个特征属性的调整最小值:",ss.min_)
print ("原始数据各个特征属性的缩放数据值:",ss.scale_)
# 6.模型构建,并训练,预测,评估
## a.决策树模型
dtr = DecisionTreeRegressor(criterion='mae',max_depth=7) # 构建回归决策树模型,使用平均绝对误差
dtr.fit(X_train, Y_train) # 模型训练
dtr_y_hat = dtr.predict(X_test) # 模型预测
dtr_score = dtr.score(X_test, Y_test) # 模型评估
print("回归决策树正确率:%.2f%%" % (dtr_score * 100))
## b.线性回归模型
lr = LinearRegression() # 构建线性回归模型
lr.fit(X_train, Y_train) # 模型训练
lr_y_hat = lr.predict(X_test) # 模型预测
lr_score = lr.score(X_test, Y_test) # 模型评估
print("线性回归正确率:%.2f%%" % (lr_score * 100))
## c.Lasso回归模型
ls = LassoCV(alphas=np.logspace(-3,1,20)) # 构建LASSO模型
ls.fit(X_train, Y_train) # 模型训练
ls_y_hat = ls.predict(X_test) # 模型预测
ls_score = ls.score(X_test, Y_test) # 模型评估
print("Lasso回归正确率:%.2f%%" % (ls_score * 100))
## d.Ridge回归模型
rg = RidgeCV(alphas=np.logspace(-3,1,20)) # 构建LASSO模型
rg.fit(X_train, Y_train) # 模型训练
rg_y_hat = rg.predict(X_test) # 模型预测
rg_score = rg.score(X_test, Y_test) # 模型评估
print("Ridge回归正确率:%.2f%%" % (rg_score * 100))
# 7.画图
plt.figure(figsize=(12,6), facecolor='w') # 大小为(12,6)的白画板
ln_x_test = range(len(X_test))
plt.plot(ln_x_test, Y_test, 'r-', lw=2, label='真实值')
plt.plot(ln_x_test, lr_y_hat, 'b-', lw=2, label='Linear回归,$R^2$=%.3f' % lr_score)
plt.plot(ln_x_test, ls_y_hat, 'y-', lw=2, label='Lasso回归,$R^2$=%.3f' % ls_score)
plt.plot(ln_x_test, rg_y_hat, 'c-', lw=2, label='Ridge回归,$R^2$=%.3f' % rg_score)
plt.plot(ln_x_test, dtr_y_hat, 'g-', lw=4, label='回归决策树预测值,$R^2$=%.3f' % dtr_score)
plt.xlabel(u'数据编码',fontproperties=my_font)
plt.ylabel(u'租赁价格',fontproperties=my_font)
plt.legend(loc = 'lower right',prop=my_font)
plt.grid(True)
plt.title(u'波士顿房屋租赁数据预测',fontproperties=my_font)
plt.show()
训练数据集样本数目:404, 测试数据集样本数目:102
原始数据各个特征属性的调整最小值: [ -7.10352762e-05 0.00000000e+00 -1.68621701e-02 0.00000000e+00
-7.92181070e-01 -6.82314620e-01 -2.98661174e-02 -1.02719857e-01
-4.34782609e-02 -3.56870229e-01 -1.34042553e+00 -6.38977636e-03
-4.90780142e-02]
原始数据各个特征属性的缩放数据值: [ 1.12397589e-02 1.00000000e-02 3.66568915e-02 1.00000000e+00
2.05761317e+00 1.91607588e-01 1.02986612e-02 9.09347180e-02
4.34782609e-02 1.90839695e-03 1.06382979e-01 2.53562554e-03
2.83687943e-02]
回归决策树正确率:80.30%
线性回归正确率:61.77%
Lasso回归正确率:61.79%
Ridge回归正确率:62.09%