决策树思想的来源非常朴素,程序设计中的条件分支结构就是 if-else 结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法。
(1)信息熵的概念
物理学上,熵( Entropy) 是“混乱”程度的量度。
系统越有序,熵值越低;系统越混乱或者分散,熵值越高。
信息理论:
“信息熵” (information entropy)是度量样本集合纯度最常用的一种指标。
(2)案例
假设我们没有看世界杯的比赛,但是想知道哪支球队会是冠军, 我们只能猜测某支球队是或不是冠军,然后观众用对或不对来回答,
我们想要猜测次数尽可能少,你会用什么方法?
二分法:假如有 16 支球队,分别编号,先问是否在 1-8 之间,如果是就继续问是否在 1-4 之间,以此类推,直到最后判断出冠军球队是哪支。如果球队数量是 16,我们需要问 4 次来得到最后的答案。那么世界冠军这条消息的信息熵就是 4。
那么信息熵等于4,是如何进行计算的呢?
Ent(D) = -(p1 * logp1 + p2 * logp2 + … + p16 * logp16),
其中 p1, …, p16 分别是这 16 支球队夺冠的概率。
当每支球队夺冠概率相等都是 1/16 的时:Ent(D) = -(16 * 1/16 * log1/16) = 4
每个事件概率相同时,熵越大,这件事越不确定。
篮球比赛里,有4个球队 {A,B,C,D} ,获胜概率分别为{1/2, 1/4, 1/8, 1/8}
求Ent(D)
(1)概念
信息增益:以某特征划分数据集前后的熵的差值。熵可以表示样本集合的不确定性,熵越大,样本的不确定性就越大。因此可以使用划分前后集合熵的差值来衡量使用当前特征对于样本集合D划分效果的好坏
信息增益表示得知特征X的信息而使得类Y的信息熵减少的程度
如下图,第一列为论坛号码,第二列为性别,第三列为活跃度,最后一列用户是否流失。
我们要解决一个问题:性别和活跃度两个特征,哪个对用户流失影响更大?
其中Positive为正样本(已流失),Negative为负样本(未流失),下面的数值为不同划分下对应的人数。
可得到三个熵:
a.计算类别信息熵(整体熵)
b.计算性别属性的信息熵(a=“性别”)
c.计算性别的信息增益(a=“性别”)
b.计算活跃度属性的信息熵(a=“活跃度”)
c.计算活跃度的信息增益(a=“活跃度”)
综上,活跃度的信息增益比性别的信息增益大,也就是说,活跃度对用户流失的影响比性别大。在做特征选择或者数据分析的时候,我们应该重点考察活跃度这个指标。
在上面的介绍中,我们有意忽略了"编号"这一列.若把"编号"也作为一个候选划分属性,则根据信息增益公式可计算出它的信息增益为 0.9182(整体熵),远大于其他候选划分属性。
计算每个属性的信息熵过程中,我们发现,该属性的值为0, 也就是其信息增益为0.9182. 但是很明显这么分类,最后出现的结果不具有泛化效果.无法对新样本进行有效预测。
实际上,信息增益准则对可取值数目较多的属性有所偏好(偏向选择值类别多的属性),为减少这种偏好可能带来的不利影响,著名的 C4.5 决策树算法 [Quinlan, 1993J 不直接使用信息增益,而是使用"增益率" (gain ratio) 来选择最优划分属性。
(1)概念
增益率:增益率是用前面的信息增益Gain(D, a)和属性a对应的"固有值"(intrinsic value) [Quinlan , 1993J的比值来共同定义的。
a.计算类别信息熵
b.计算性别属性的信息熵(性别、活跃度)
c.计算活跃度的信息增益(性别、活跃度)
d.计算属性分裂信息度量
用分裂信息度量来考虑某种属性进行分裂时分支的数量信息和尺寸信息,我们把这些信息称为属性的内在信息(instrisic information)。信息增益率用信息增益/内在信息,会导致属性的重要性随着内在信息的增大而减小(也就是说,如果这个属性本身不确定性就很大,那我就越不倾向于选取它),这样算是对单纯用信息增益有所补偿。
e.计算信息增益率
活跃度的信息增益率更高一些,所以在构建决策树的时候,优先选择
通过这种方式,在选取节点的过程中,我们可以降低取值较多的属性的选取偏好。
在本例中IV(编号)为log15的值近似4,其作为分母较大,信息增益率较小,这样降低取值较多的属性的选取偏好(这里的log以2为底)
(3)案例二
如下图,第一列为天气,第二列为温度,第三列为湿度,第四列为风速,最后一列该活动是否进行。
我们要解决:根据下面表格数据,判断在对应天气下,活动是否会进行?
该数据集有四个属性,属性集合A={ 天气,温度,湿度,风速}, 类别标签有两个,类别集合L={进行,取消}。
a.计算类别信息熵
类别信息熵表示的是所有样本中各种类别出现的不确定性之和。根据熵的概念,熵越大,不确定性就越大,把事情搞清楚所需要的信息量就越多。
信息增益的 = 熵 - 条件熵,在这里就是 类别信息熵 - 属性信息熵,它表示的是信息不确定性减少的程度。如果一个属性的信息增益越大,就表示用这个属性进行样本划分可以更好的减少划分后样本的不确定性,当然,选择该属性就可以更快更好地完成我们的分类目标。
假设我们把上面表格1的数据前面添加一列为"编号",取值(1–14). 若把"编号"也作为一个候选划分属性,则根据前面步骤: 计算每个属性的信息熵过程中,我们发现,该属性的值为0, 也就是其信息增益为0.940. 但是很明显这么分类,最后出现的结果不具有泛化效果.此时根据信息增益就无法选择出有效分类特征。所以,C4.5选择使用信息增益率对ID3进行改进。
d.计算属性分裂信息度量
用分裂信息度量来考虑某种属性进行分裂时分支的数量信息和尺寸信息,我们把这些信息称为属性的内在信息(instrisic information)。信息增益率用信息增益/内在信息,会导致属性的重要性随着内在信息的增大而减小(也就是说,如果这个属性本身不确定性就很大,那我就越不倾向于选取它),这样算是对单纯用信息增益有所补偿。
e.计算信息增益率
天气的信息增益率最高,选择天气为分裂属性。发现分裂了之后,天气是“阴”的条件下,类别是”纯“的(天气是“阴”时,活动都会“进行”),所以把它定义为叶子节点,选择不“纯”的结点继续分裂。
信息增益率越大,代表纯度提升越大,表示这个特征越重要
在子结点当中重复过程1~5,直到所有的叶子结点足够"纯"。
C4.5的算法流程总结:
while(当前节点"不纯"):
1.计算当前节点的类别熵(以类别取值计算)
2.计算当前阶段的属性熵(按照属性取值吓得类别取值计算)
3.计算信息增益
4.计算各个属性的分裂信息度量
5.计算各个属性的信息增益率
end while
当前阶段设置为叶子节点
为什么使用C4.5要好?
克服了用信息增益来选择属性时偏向选择值多的属性的不足。
避免树的高度无节制的增长,避免过度拟合数据
在某些情况下,可供使用的数据可能缺少某些属性的值。假如〈x,c(x)〉是样本集S中的一个训练实例,但是其属性A的值A(x)未知。
处理缺少属性值的策略:
例如,给定一个布尔属性A,如果结点n包含6个已知A=1和4个A=0的实例,那么A(x)=1的概率是0.6,而A(x)=0的概率是0.4。于是,实例x的60%被分配到A=1的分支,40%被分配到另一个分支。
C4.5就是使用这种方法处理缺少的属性值。
CART 决策树 [Breiman et al., 1984] 使用"基尼指数" (Gini index)来选择划分属性。
CART 是Classification and Regression Tree的简称,这是一种著名的决策树学习算法,分类和回归任务都可用
(1)概念
请根据下图列表,按照基尼指数的划分依据,做出决策树。
对数据集非序列标号属性{是否有房,婚姻状况,年收入}
分别计算它们的Gini指数,取Gini指数最小的属性作为决策树的根节点属性。
① 当根据是否有房来进行划分时,Gini指数计算过程为:
② 若按婚姻状况属性来划分,属性婚姻状况有三个可能的取值{married,single,divorced},(CART一定是二叉树)分别计算划分后的Gini指数:
对比计算结果,根据婚姻状况属性来划分根节点时取Gini指数最小的分组作为划分结果,即:{married} | {single,divorced}
③ 年收入Gini指数:
对于年收入属性为 数值型 属性,首先需要对数据按升序排序,然后从小到大依次用相邻值的中间值作为分隔将样本划分为两组。例如:当面对年收入为60和70这两个值时,我们算得其中间值为65。以中间值65作为分割点求出Gini指数
根据计算知道,三个属性划分根节点的指数最小的有两个:年收入和婚姻状况,他们的指数都为0.3。此时,选取首先出现的属性【婚姻状况】作为第一次划分。
接下来,采用同样的方法,分别计算剩下属性
② 对于年收入属性则有:
经过如上流程,构建的决策树,如下图:
(1) ID3 决策树算法的缺点
(2) C4.5 决策树算法
做出的改进(为什么使用C4.5要好)
优点:
缺点:
(3)CART 决策树算法
出现这种情况的原因:
剪枝 (pruning)是决策树学习算法对付"过拟合"的主要手段。
在决策树学习中,为了尽可能正确分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,这时就可能因训练样本学得"太好"了,以致于把训练集自身的一些特点当作所有数据都具有的一般性质而导致过拟合。因此,可通过主动去掉一些分支来降低过拟合的风险。
决策树剪枝的基本策略有"预剪枝" (pre-pruning)和"后剪枝"(post- pruning) 。
(1)预剪枝
预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点;
(2)后剪枝
后剪枝则是先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。
对比两种剪枝方法:
将任意数据(如文本或图像)转换为可用于机器学习的数字特征(特征值化是为了计算机更好的去理解数据)
除此之外,我们还可以在数据预处理过程中将离散的特征数据进行OneHot独热编码。
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder()
enc.fit([[0,0,3],[1,1,0],[0,2,1],[1,0,2]]) # 训练。这里共有4个数据,3种特征
array = enc.transform([[0,1,3]]).toarray() # 测试。这里使用1个新数据来测试
print(array) # [[ 1 0 0 1 0 0 0 0 1]] # 独热编码结果
特征提取分类:
特征提取API:sklearn.feature_extraction
作用:对字典数据进行特征值化
sklearn.feature_extraction.DictVectorizer(sparse=True,…)
案例:
我们对以下特征进行提取
[{'city': '北京','temperature':100},
{'city': '上海','temperature':60},
{'city': '深圳','temperature':30}]
from sklearn.feature_extraction import DictVectorizer
data = [{'city': '北京', 'temperature': 100},
{'city': '上海', 'temperature': 60},
{'city': '深圳', 'temperature': 30}]
# 1. 实例化一个转换器类
transfer = DictVectorizer() # 默认sparse=True
# 2. 调用fit_transform方法输入数据并转换(注意返回格式)
data = transfer.fit_transform(data)
print("返回的结果:\n", data)
# 打印每一列特征名字
names = transfer.get_feature_names()
print(names)
若为transfer = DictVectorizer(sparse=False)
的结果(非sparse矩阵)
sparse=True
的作用
- 提高读取效率
- 节省内存空间
注意:对于特征当中存在类别信息的我们都会做 one-hot 编码处理
作用:对文本数据进行特征值化
sklearn.feature_extraction.text.CountVectorizer(stop_words=[])
stop_words
:停用词CountVectorizer.fit_transform(X)
CountVectorizer.get_feature_names()
返回值:单词列表sklearn.feature_extraction.text.TfidfVectorizer
(1)英文文本特征提取
我们对以下数据进行特征提取
["life is short,i like python",
"life is too long,i dislike python"]
from sklearn.feature_extraction.text import CountVectorizer
data = ["life is short,i like like python", "life is too long,i dislike python"]
# 1. 实例化类CountVectorizer
transfer = CountVectorizer() # 要注意CountVectorizer()没有sparse这个参数
# 2. 调用fit_transform方法输入数据并转换
# (注意返回格式,利用toarray()将sparse矩阵转换array数组)
data = transfer.fit_transform(data)
print(data)
print(data.toarray())
print("特征名称为:\n",transfer.get_feature_names())
注意:
- 单个字符的单词以及标点符号不做统计
- 英文默认是以空格分开的,其实就达到了一个分词的效果
(2)中文文本特征提取
我们要对中文进行分词处理,需要利用 jieba 分词处理
jieba库的简单用法见:https://blog.csdn.net/hu_wei123/article/details/127121301
对以下三句话进行特征值化
今天很残酷,明天更残酷,后天很美好,
但绝对大部分是死在明天晚上,所以每个人不要放弃今天。
我们看到的从很远星系来的光是在几百万年之前发出的,
这样当我们看到宇宙时,我们是在看它的过去。
如果只用一种方式了解某样事物,你就不会真正了解它。
了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。
from sklearn.feature_extraction.text import CountVectorizer
import jieba
data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。",
"我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。",
"如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"]
text_list = []
for sentence in data:
text = " ".join(jieba.lcut(sentence))
# print(text)
text_list.append(text)
print(text_list)
# 1. 实例化一个转化器类
transfer = CountVectorizer()
# 2. 调用fit_transform
data = transfer.fit_transform(text_list)
print("文本特征抽取的结果:\n", data.toarray())
print("返回特征名字:\n", transfer.get_feature_names())
文本特征抽取的结果:
[[2 0 1 0 0 0 2 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 2 0 1 0 2 1 0 0 0 1 1 0 0 1 0]
[0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 1 3 0 0 0 0 1 0 0 0 0 2 0 0 0 0 0 1 0 1]
[1 1 0 0 4 3 0 0 0 0 1 1 0 1 0 1 1 0 1 0 0 1 0 0 0 1 0 0 0 2 1 0 0 1 0 0 0]]
返回特征名字:
['一种', '不会', '不要', '之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']
(3)Tf-idf文本特征提取
TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的概率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
TF-IDF的作用:用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度
TF-IDF的重要性:分类机器学习算法进行 文章分类 中前期数据处理方式
最终得出结果 tfidf 可以理解为重要程度
举例:
假如一篇文章的总词语数是100个,而词语"非常"出现了5次,那么"非常"一词在该文件中的词频就是5/100=0.05。
而计算文件频率(IDF)的方法是以文件集的文件总数,除以出现"非常"一词的文件数。
所以,如果"非常"一词在1,0000份文件出现过,而文件总数是10,000,000份的话,
其逆向文件频率就是lg(10,000,000 / 1,0000)=3。
最后"非常"对于这篇文档的tf-idf的分数为0.05 * 3=0.15
其 api 用法同上述中文特征提取
sklearn.tree.DecisionTreeClassifier(criterion=’gini’,max_depth=None,random_state=None)
参数:
criterion
:特征选择标准
max_depth
:决策树最大深度
random_state
:随机数种子
min_samples_split
:内部节点再划分所需最小样本数
min_samples_leaf
:叶子节点最少样本数
sklearn.tree.export_graphviz()
tree.export_graphviz(estimator,out_file='tree.dot’,feature_names=[‘’,’’])
泰坦尼克号沉没是历史上最臭名昭着的沉船之一。1912年4月15日,在她的处女航中,泰坦尼克号在与冰山相撞后沉没,在2224名乘客和机组人员中造成1502人死亡。这场耸人听闻的悲剧震惊了国际社会,并为船舶制定了更好的安全规定。 造成海难失事的原因之一是乘客和机组人员没有足够的救生艇。尽管幸存下沉有一些运气因素,但有些人比其他人更容易生存,例如 妇女,儿童和上流社会 (也就是与性别 Sex、年龄 Age、社会阶层 Pclass有关)。 在这个案例中,我们要求您完成对哪些人可能存活的分析。特别是,我们要求您运用机器学习工具来预测哪些乘客幸免于悲剧。
泰坦尼克号数据集获取:https://pan.baidu.com/s/13L6UxNxd2XzgA0rERsXHrg?pwd=blhx
本数据集是从kaggle平台获取,为了实验方便并将train.csv
重命名为titanic.csv
,读者也可直接从kaggle平台:https://www.kaggle.com/c/titanic/overview,自行下载实验
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
from sklearn.tree import DecisionTreeClassifier,export_graphviz
# 1.获取数据
titanic = pd.read_csv("./titanic.csv")
# 2.数据基本处理
# 2.1 确定特征值,目标值
x = titanic[["Sex","Age","Pclass"]]
y = titanic["Survived"]
# 2.2 缺失值处理
# nan_num = titanic["Age"].isnull().sum()
# print(num) # Age列有177个空缺值
# 用Age列的平均值填充空缺值
x['Age'].fillna(x['Age'].mean(), inplace=True)
# 2.3 数据集划分
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=1)# 默认测试集0.25的划分比例
# 3.特征工程(字典特征抽取)
# 对于x转换成字典数据,转换后list形式
# [{"pclass": "1st", "age": 29.00, "sex": "female"},{},{}……]
transfer = DictVectorizer(sparse=False)
x_train = transfer.fit_transform(x_train.to_dict(orient="records"))
x_test = transfer.fit_transform(x_test.to_dict(orient="records"))
# 4.机器学习(决策树)
estimator = DecisionTreeClassifier(max_depth=5)
estimator.fit(x_train,y_train)
# 5.模型评估
y_pre = estimator.predict(x_test)
score = estimator.score(x_test,y_test)
# 6.决策树可视化
export_graphviz(estimator, out_file="./tree.dot", feature_names=['Age', 'Pclass','male', 'female'])
pandas关于to_dict的使用:https://blog.csdn.net/qq_38060702/article/details/109843385
前面已经讲到,关于数据类型,我们主要可以把其分为两类,连续型数据和离散型数据。在面对不同数据时,决策树也可以分为两大类型:
不管是回归决策树还是分类决策树,都会存在两个核心问题:
输入:训练数据集 D
输出:回归树 f(x)
流程:在训练数据集所在的输入空间中,递归的将每个区域划分为两个子区域并决定每个子区域上的输出值
为了易于理解,接下来通过一个简单实例加深对回归决策树的理解。
训练数据见下表,目标是得到一棵最小二乘回归树。
(1)选择最优的切分特征 j 与最优切分点 s
a. 计算子区域输出值:
b. 计算损失函数值,找到最优切分点:
(2)用选定的(j,s)划分区域,并决定输出值
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor
from sklearn import linear_model
# 生成数据
x = np.array(list(range(1, 11))).reshape(-1, 1)
y = np.array([5.56, 5.70, 5.91, 6.40, 6.80, 7.05, 8.90, 8.70, 9.00, 9.05])
# 训练模型
model1 = DecisionTreeRegressor(max_depth=1)
model2 = DecisionTreeRegressor(max_depth=3)
model3 = linear_model.LinearRegression()
model1.fit(x, y)
model2.fit(x, y)
model3.fit(x, y)
# 模型预测
# reshape(行数,列数)常用来更改数据的行列数目,这里-1是指未设定行数,程序随机分配,所以这里-1表示任一正整数
X_test = np.arange(0.0, 10.0, 0.01).reshape(-1, 1) # 生成1000个数,用于预测模型
y_1 = model1.predict(X_test)
y_2 = model2.predict(X_test)
y_3 = model3.predict(X_test)
# 结果可视化
plt.figure(figsize=(10, 6), dpi=100)
plt.scatter(x, y, label="data")
plt.plot(X_test, y_1,label="max_depth=1")
plt.plot(X_test, y_2, label="max_depth=3")
plt.plot(X_test, y_3, label='liner regression')
plt.xlabel("data")
plt.ylabel("target")
plt.title("Decision Tree Regression")
plt.legend()
plt.show()
由此可见,决策树的深度越大,越容易出现过拟合,其泛化能力并不是越好。
模型的 泛化能力 是指模型对新鲜样本的适应能力。通俗易懂的说就是模型在测试集(其中的数据模型以前没有见过)中的表现,也就是模型举一反三的能力。
优点:
缺点:
改进:
企业重要决策,由于决策树很好的分析能力,在决策过程应用较多, 可以选择特征