分类算法之决策树

决策树是一种基本的分类方法,当然也可以用于回归。我们一般只讨论用于分类的决策树。决策树模型呈树形结构。在分类问题中,表示基于特征对实例进行分类的过程,它可以认为是if-then规则的集合。在决策树的结构中,每一个实例都被一条路径或者一条规则所覆盖。通常决策树学习包括三个步骤:特征选择、决策树的生成和决策树的修剪

优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理逻辑回归等不能解决的非线性特征数据

缺点:可能产生过度匹配问题

适用数据类型:数值型和标称型

特征选择

特征选择在于选取对训练数据具有分类能力的特征。这样可以提高决策树学习的效率,如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的京都影响不大。通常特征选择的准则是信息增益,这是个数学概念。通过一个例子来了解特征选择的过程。

ID 年龄 有工作 有自己的房子 信贷情况 类别
1 青年 一般
2 青年
3 青年
4 青年 一般
5 青年 一般
6 中年 一般
7 中年
8 中年
9 中年 非常好
10 中年 非常好
11 老年 非常好
12 老年
13 老年
14 老年 非常好
15 老年 一般

我们希望通过所给的训练数据学习一个贷款申请的决策树,用以对文莱的贷款申请进行分类,即当新的客户提出贷款申请是,根据申请人的特征利用决策树决定是否批准贷款申请。特征选择其实是决定用那个特征来划分特征空间。下图中分别是按照年龄,还有是否有工作来划分得到不同的子节点

问题是究竟选择哪个特征更好些呢?那么直观上,如果一个特征具有更好的分类能力,是的各个自己在当前的条件下有最好的分类,那么就更应该选择这个特征。信息增益就能很好的表示这一直观的准则。这样得到的一棵决策树只用了两个特征就进行了判断:

通过信息增益生成的决策树结构,更加明显、快速的划分类别。下面介绍scikit-learn中API的使用

信息的度量和作用

我们常说信息有用,那么它的作用如何客观、定量地体现出来呢?信息用途的背后是否有理论基础呢?这个问题一直没有很好的回答,直到1948年,香农在他的论文“通信的数学原理”中提到了“信息熵”的概念,才解决了信息的度量问题,并量化出信息的作用。

一条信息的信息量与其不确定性有着直接的关系,比如我们要搞清一件非常不确定的事,就需要大量的信息。相反如果对某件事了解较多,则不需要太多的信息就能把它搞清楚 。所以从这个角度看,可以认为,信息量就等于不确定的多少。那么如何量化信息量的度量呢?2022年举行世界杯,大家很关系谁是冠军。假如我错过了看比赛,赛后我问朋友 ,“谁是冠军”?他不愿意直接告诉我,让我每猜一次给他一块钱,他告诉我是否猜对了,那么我需要掏多少钱才能知道谁是冠军?我可以把球编上号,从1到32,然后提问:冠 军在1-16号吗?依次询问,只需要五次,就可以知道结果。所以谁是世界杯冠军这条消息只值五块钱。当然香农不是用钱,而是用“比特”这个概念来度量信息量。一个比特是 一位二进制数,在计算机中一个字节是8比特。

那么如果说有一天有64支球队进行决赛阶段的比赛,那么“谁是世界杯冠军”的信息量就是6比特,因为要多猜一次,有的同学就会发现,信息量的比特数和所有可能情况的对数函数log有关,(log32=5,log64=6)

另外一方面你也会发现实际上我们不需要猜五次就能才出冠军,因为像西班牙、巴西、德国、意大利这样的球队夺得冠军的可能性比南非、尼日利亚等球队大得多,因此第一次猜测时不需要把32支球队等分成两个组,而可以把少数几支最有可能的球队分成一组,把其他球队分成一组。然后才冠军球队是否在那几支热门队中。这样,也许三次就猜出结果。因此,当每支球队夺冠的可能性不等时,“谁是世界杯冠军”的信息量比5比特少。香农指出,它的准确信息量应该是:

H = -(p1logp1 + p2logp2 + ... + p32log32)

其中,p1...p32为这三支球队夺冠的概率。H的专业术语称之为信息熵,单位为比特,当这32支球队夺冠的几率相同时,对应的信息熵等于5比特,这个可以通过计算得出。有一个特性就是,5比特是公式的最大值。那么信息熵(经验熵)的具体定义可以为如下:

信息增益

自古以来,信息和消除不确定性是相联系的。所以决策树的过程其实是在寻找某一个特征对整个分类结果的不确定减少的过程。那么这样就有一个概念叫做信息增益(information gain)。

那么信息增益表示得知特征X的信息而是的类Y的信息的不确定性减少的程度,所以我们对于选择特征进行分类的时候,当然选择信息增益较大的特征,这样具有较强的分类能力。特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即公式为:

根据信息增益的准则的特征选择方法是:对于训练数据集D,计算其每个特征的信息增益,并比较它们的阿笑,选择信息增益最大的特征

信息增益的计算

那么条件熵计算如下:

既然我们有了这两个公式,我们可以根据前面的是否通过贷款申请的例子来通过计算得出我们的决策特征顺序。那么我们首先计算总的经验熵为:

同理其他的也可以计算出来,g(D,A2)=0.324,g(D,A3)=0.420,g(D,A4)=0.363,相比较来说其中特征A3(有自己的房子)的信息增益最大,所以我们选择特征A3为最有特征

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

ubuntu:sudo apt-get install graphviz
Mac:brew install graphviz

然后我们运行这个命令

$ dot -Tps tree.dot -o tree.ps
$ dot -Tpng tree.dot -o tree.png

或者,如果我们安装了Python模块pydotplus,我们可以直接在Python中生成PDF文件,通过pip 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")

查看决策树结构图片,这个结果是经过决策树学习的三个步骤之后形成的。当作了解

扩展:所有各种决策树算法是什么,它们之间有什么不同?哪一个在scikit-learn中实现?

ID3 --- 信息增益 最大的准则

C4.5 --- 信息增益比 最大的准则

CART 回归树: 平方误差 最小 分类树: 基尼系数 最小的准则 在sklearn中可以选择划分的原则

决策树优缺点分析

决策树的一些优点是:

  • 简单的理解和解释。树木可视化。
  • 需要很少的数据准备。其他技术通常需要数据归一化,需要创建虚拟变量,并删除空值。但请注意,此模块不支持缺少值。
  • 使用树的成本(即,预测数据)在用于训练树的数据点的数量上是对数的。

决策树的缺点包括:

  • 决策树学习者可以创建不能很好地推广数据的过于复杂的树。这被称为过拟合。修剪(目前不支持)的机制,设置叶节点所需的最小采样数或设置树的最大深度是避免此问题的必要条件。
  • 决策树可能不稳定,因为数据的小变化可能会导致完全不同的树被生成。通过使用合奏中的决策树来减轻这个问题。

集成方法(分类)之随机森林

在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。利用相同的训练数搭建多个独立的分类模型,然后通过投票的方式,以少数服从多数的原则作出最终的分类决策。例如, 如果你训练了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) 返回森林中的决策路径

Titanic生还预测案例

决策树模型

步骤

  • 读取CSV
  • 挑选影响生还的特征
  • 数据缺失值处理
  • 划分测试集和训练集
  • 特征工程,特征抽取,one_host编码
  • 传入决策树模型进行预测
  • 展示树结构

数据准备与特征工程

# 导入模块
from sklearn.feature_extraction import DictVectorizer  # 特征抽取
from sklearn.tree import DecisionTreeClassifier, export_graphviz # 决策树与模型导出
from sklearn.model_selection import train_test_split #模型选择
import graphviz  # dot文件展示
import pandas as pd 
from sklearn.ensemble import RandomForestClassifier # 随机森林
from sklearn.model_selection import GridSearchCV # 网格搜索

# 导入数据
data = pd.read_csv("./titanic/tancan.csv")
data.head(10)
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0.0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1.0 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1.0 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1.0 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0.0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
5 6 0.0 3 Moran, Mr. James male NaN 0 0 330877 8.4583 NaN Q
6 7 0.0 1 McCarthy, Mr. Timothy J male 54.0 0 0 17463 51.8625 E46 S
7 8 0.0 3 Palsson, Master. Gosta Leonard male 2.0 3 1 349909 21.0750 NaN S
8 9 1.0 3 Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg) female 27.0 0 2 347742 11.1333 NaN S
9 10 1.0 2 Nasser, Mrs. Nicholas (Adele Achem) female 14.0 1 0 237736 30.0708 NaN C
# target: Survived
# features: Pclass, Sex, Age, Embarked, Fare
data = data[["Survived", "Pclass", "Sex", "Age", "Embarked", "Fare"]]
# 缺失值处理
data["Survived"].fillna(data["Survived"].mean(), inplace=True)
data["Age"].fillna(data["Age"].mean(), inplace=True)
data = data.dropna()
data["Survived"] = data["Survived"].astype("int")
data.head()

Survived Pclass Sex Age Embarked Fare
0 0 3 male 22.0 S 7.2500
1 1 1 female 38.0 C 71.2833
2 1 3 female 26.0 S 7.9250
3 1 1 female 35.0 S 53.1000
4 0 3 male 35.0 S 8.0500
# 划分测试集和训练集
x_train, x_test, y_train, y_test = train_test_split(data[["Pclass", "Sex", "Age", "Embarked", "Fare"]], data["Survived"], test_size=0.25)
# 特征工程
dict = DictVectorizer(sparse=False)
x_train = dict.fit_transform(x_train.to_dict(orient="records"))
x_test = dict.transform(x_test.to_dict(orient="records"))
# 获取特征的名字
featue_names = dict.get_feature_names_out()
featue_names
array(['Age', 'Embarked=C', 'Embarked=Q', 'Embarked=S', 'Fare', 'Pclass',
       'Sex=female', 'Sex=male'], dtype=object)

决策树预测

# 决策树预测
dec = DecisionTreeClassifier(criterion="gini", max_depth=5)
dec.fit(x_train,y_train)
# 决策树预测准确率
score1 = dec.score(x_test, y_test)
score1
0.7951070336391437
# 画出这棵树
data_dot = export_graphviz(decision_tree=dec
                       ,out_file=None
                       ,feature_names=featue_names
                       ,filled=True
                       ,rounded=True)
graph = graphviz.Source(data_dot)
graph


# 每个特征的重要性
feature_im = [*zip(featue_names, dec.feature_importances_)]
feature_im
[('Age', 0.13714911127584667),
 ('Embarked=C', 0.004435844635553067),
 ('Embarked=Q', 0.0),
 ('Embarked=S', 0.0),
 ('Fare', 0.19830610974944632),
 ('Pclass', 0.10776727432626258),
 ('Sex=female', 0.5523416600128915),
 ('Sex=male', 0.0)]

随机森林+网格搜索+交叉验证重新预测

# 实例化随机森林
rf = RandomForestClassifier()
# 创建参数字典
param = {
        "n_estimators": [120, 200, 300, 500,800, 1200], 
         "max_depth": [5, 8, 15, 25, 30],
         "n_jobs":[-1]
         }
# 网格搜索与交叉验证
gv = GridSearchCV(estimator=rf, param_grid=param, cv=10)
gv.fit(x_train, y_train)
# 随机森林准确率
score2 = gv.score(x_test, y_test)
score2
0.8103975535168195
# 查看选择的参数模型
print(gv.best_estimator_)
RandomForestClassifier(max_depth=5, n_estimators=1200, n_jobs=-1)