「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。
一.决策树算法简介
决策树思想的来源非常朴素,程序设计中的条件分支结构就是if-else结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法
决策树:是一种树形结构,其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果,本质是一颗由多个判断节点组成的树。
如果对决策树这一过程进行量化,需要用到信息论中的知识:信息熵,信息增益
二.决策树分类原理
2.1熵
2.1.1概念
“熵”:混乱程度的度量
系统越有序,熵值越低;系统越混乱或者分散,熵值越高。
信息熵:
-
从信息的完整性上进行的描述
当系统的有序状态一致时,数据越集中的地方熵值越小,数据越分散的地方熵值越大。
-
从信息的有序性上进行的描述
当数据量一致时,系统越有序,熵值越低;系统越混乱或者分散,熵值越高。
"信息熵" (information entropy)是度量样本集合纯度最常用的一种指标。
假定当前样本集合D中第k类样本所占的比例为
,为样本的所有数量,为第k类样本的数量。
则D的信息熵定义为:
其中,Ent(D)的值越小,则D的纯度越高。
2.1.2案例
假设我们没有看世界杯的比赛,但是想知道哪支球队会是冠军,
我们只能猜测某支球队是或不是冠军,然后观众用对或不对来回答,
我们想要猜测次数尽可能少,你会用什么方法?
二分法:
假如有 16 支球队,分别编号,先问是否在 1-8 之间,如果是就继续问是否在 1-4 之间,
以此类推,直到最后判断出冠军球队是哪只。
如果球队数量是 16,我们需要问 4 次来得到最后的答案。那么世界冠军这条消息的信息熵就是 4。
那么信息熵等于4,是如何进行计算的呢?
Ent(D) = -(p1 * logp1 + p2 * logp2 + ... + p16 * logp16),
其中 p1, ..., p16 分别是这 16 支球队夺冠的概率。
当每支球队夺冠概率相等都是 1/16 的时:H = -(16 * 1/16 * log1/16) = 4
每个事件概率相同时,熵最大,这件事越不确定。
2.2决策树的划分依据--信息增益
2.2.1概念
信息增益:以某特征划分数据集前后的熵的差值。熵可以表示样本集合的不确定性,熵越大,样本的不确定性就越大。因此可以使用划分前后集合熵的差值来衡量使用当前特征对于样本集合D划分效果的好坏。
信息增益 = entroy(前) - entroy(后)
注:信息增益表示得知特征X的信息而使得类Y的信息熵减少的程度
定义及公式:
假定离散属性a有V个可能的取值
若使用a来对样本集D进行划分,则会产生V个分支结点
其中第v个分支结点包含了 D 中所有在属性a上取值为的样本,记为。我们可根据前面给出的信息熵公式计算出的信息熵,再考虑到不同的分支结点所包含的样本数不同,给分支结点赋予权重。
即样本数越多的分支结点的影响越大,于是可计算出用属性a对样本集 D 进行划分所获得的"信息增益"
其中:
特征a对训练集D的信息增益Gain(D,a),定义为**集合D的信息熵Ent(D)与给定特征条件a下D的信息条件熵Ent(D|a)**之差,即公式为:
公式的详细解释:
信息熵的计算:
条件熵的计算:
其中:
表示a属性中第v个分支节点所包含的样本数
表示a属性中第v个分支节点包含的样本数中,第k个类别下包含的样本数
一般而言,信息增益越大,则意味着使用属性 a 来进行划分所获得的"纯度提升"越大。因此,我们可用信息增益来进行决策树的划分属性选择,著名的 ID3 决策树学习算法就是以信息增益为准则来选择划分属性。
其中,ID3 名字中的 ID 是 Iterative Dichotomiser (迭代二分器)的简称
2.2.2案例
如下图,第一列为论坛号码,第二列为性别,第三列为活跃度,最后一列用户是否流失。
我们要解决一个问题:性别和活跃度两个特征,哪个对用户流失影响更大
通过计算信息增益可以解决这个问题,统计上右表信息
其中Positive为正样本(已流失),Negative为负样本(未流失),下面的数值为不同划分下对应的人数。
可得到三个熵:
整体熵:
性别熵:
性别信息增益:
活跃度熵:
活跃度信息增益:
活跃度的信息增益比性别的信息增益大,也就是说,活跃度对用户流失的影响比性别大。
在做特征选择或者数据分析的时候,我们应该重点考察活跃度这个指标。
2.3决策树的划分依据二--信息增益率
2.3.1概念
实际上,信息增益准则对可取值数目较多的属性有所偏好,为减少这种偏好可能带来的不利影响,著名的 C4.5 决策树算法 [Quinlan, 1993J 不直接使用信息增益,而是使用"增益率" (gain ratio) 来选择最优划分属性.
增益率:增益率是用前面的信息增益Gain(D, a)和属性a对应的"固有值"(intrinsic value) [Quinlan , 1993J的比值来共同定义的。
其中:
属性 a 的可能取值数目越多(即 v 越大),则 IV(a) 的值通常会越大。
2.3.2案例-根据天气判断活动是否进行
如下图,第一列为天气,第二列为温度,第三列为湿度,第四列为风速,最后一列该活动是否进行。
我们要解决:根据下面表格数据,判断在对应天气下,活动是否会进行?
该数据集有四个属性,属性集合A={ 天气,温度,湿度,风速}, 类别标签有两个,类别集合L={进行,取消}。 统计信息如图所示:
a.计算类别信息熵
类别信息熵表示的是所有样本中各种类别出线的不确定性之和。根据熵的概念,熵越打,不确定性越大,把事情搞清楚所需要的信息量就越多。
b.计算每个属性的信息熵
每个属性的信息熵相当于一种条件熵。它表示的是在某种属性的条件下,各种类别出线的不确定性之和。属性的信息熵越大,表示这个属性中拥有的样本类别越不“纯”。
a="天气"(5个“晴”,4个“阴”,5个“雨”,总:14)
a="温度"(4个“寒冷”,6个“适中”,4个“炎热”)
a="湿度"(7个“正常”,7个“高”)
a="风速"(8个“弱”,6个“强”)
c.计算信息增益
信息增益 = 熵 - 条件熵,在这里就是 类别信息熵 - 属性信息熵,它表示的是信息不确定性减少的程度。如果一个属性的信息增益越大,就表示用这个属性进行样本划分可以更好的减少划分后样本的不确定性,当然,选择该属性就可以更快更好地完成我们的分类目标。
信息增益就是ID3算法的特征选择指标。
Gain(D,天气)= 0.940-0.694 = 0.246
Gain(D,温度)= 0.940-0.911 = 0.029
Gain(D,湿度)= 0.940-0.789 = 0.151
Gain(D,风速)= 0.940-0.892 = 0.048
假设我们把上图1的数据前面添加一列为"编号",取值(1--14). 若把"编号"也作为一个候选划分属性,则根据前面步骤: 计算每个属性的信息熵过程中,我们发现,该属性的值为0, 也就是其信息增益为0.940. 但是很明显这么分类,最后出现的结果不具有泛化效果.此时根据信息增益就无法选择出有效分类特征。所以,C4.5选择使用信息增益率对ID3进行改进。
d.计算属性分裂信息度量
用分裂信息度量来考虑某种属性进行分裂时分支的数量信息和尺寸信息,我们把这些信息称为属性的内在信息(instrisic information)。信息增益率用信息增益 / 内在信息,会导致属性的重要性随着内在信息的增大而减小(也就是说,如果这个属性本身不确定性就很大,那我就越不倾向于选取它),这样算是对单纯用信息增益有所补偿。
e.计算信息增益
天气的信息增益率最高,选择天气为分裂属性。发现分裂了之后,天气是“阴”的条件下,类别是”纯“的,所以把它定义为叶子节点,选择不“纯”的结点继续分裂。
在子结点当中重复过程a~e,直到所有的叶子结点足够"纯"。
现在我们来总结一下C4.5的算法流程
while(当前节点"不纯"):
1.计算当前节点的类别熵(以类别取值计算)
2.计算当前阶段的属性熵(按照属性取值吓得类别取值计算)
3.计算各个属性的分类信息度量
4.计算各个属性的信息增益率
end while
当前阶段设置为叶子节点
2.3.3 C4.5的优点
-
用信息增益率来选择属性
克服了用信息增益来选择属性时偏向选择值多的属性的不足。
-
采用了一种后剪枝的方法
避免树的高度无节制的增长,避免过度拟合数据
-
对于缺失值的处理
在某些情况下,可供使用的数据可能缺少某些属性的值。假如〈x,c(x)〉是样本集S中的一个训练实例,但是其属性A的值A(x)未知。
处理缺少属性值的一种策略是赋给它结点n所对应的训练实例中该属性的最常见值;
另外一种更复杂的策略是为A的每个可能值赋予一个概率。
例如,给定一个布尔属性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就是使用这种方法处理缺少的属性值。
2.4决策树的划分依据三--基尼值和基尼指数
2.4.1概念
CART决策树使用“基尼指数”来选择划分属性
CART是Classification and Regression Tree的简写,这是一种著名的决策树学习算法,分类和回归任务都可用
基尼值Gini(D):从数据集D中随机抽取两个样本,其类别标记不一致的概率。故,Gini(D)值越小,数据集D的纯度越高。
数据集D的纯度可用基尼值来度量:
说明: |y|为分出的类别数量, k为这个类别的第几部分
基尼指数Gini_index(D):一般,选择使划分后基尼系数最小的属性作为最优化分属性。
2.4.2案例
请根据下图列表,按照基尼指数的划分依据,做出决策树。
| 序号 | 是否有房 | 婚姻状况 | 年收入 | 是否拖欠贷款 |
|---|---|---|---|---|
| 1 | yes | single | 125k | no |
| 2 | no | married | 100k | no |
| 3 | no | single | 70k | no |
| 4 | yes | married | 120k | no |
| 5 | no | divorced | 95k | yes |
| 6 | no | married | 60k | no |
| 7 | yes | divorced | 220k | no |
| 8 | no | single | 85k | yes |
| 9 | no | married | 75k | no |
| 10 | no | single | 90k | yes |
1, 为了方便计算, 我们把各个属性计算出来, 年收入是连续数值, 后面再说
2对数据集非序列标号属性{是否有房,婚姻状况,年收入}分别计算它们的Gini指数,取Gini指数最小的属性作为决策树的根节点属性。
3,当根据是否有房来进行划分时,Gini指数计算过程为:
4,若按婚姻状况属性来划分,属性婚姻状况有三个可能的取值{married,single,divorced},分别计算划分后的Gini系数增益。
{married} | {single,divorced}
{single} | {married,divorced}
{divorced} | {single,married}
分组为{married} | {single,divorced}时:
当分组为{single} | {married,divorced}时:
当分组为{divorced} | {single,married}时:
5,同理可得年收入Gini:
对于年收入属性为数值型属性,首先需要对数据按升序排序,然后从小到大依次用相邻值的中间值作为分隔将样本划分为两组。例如当面对年收入为60和70这两个值时,我们算得其中间值为65。以中间值65作为分割点求出Gini指数。
节点为65时:{年收入}=
节点为75时:{年收入}=
节点为80时:{年收入}=
节点为87.7时:{年收入}=
根据计算知道,三个属性划分根节点的指数最小的有两个:年收入属性和婚姻状况,他们的指数都为0.3。此时,选取婚姻状况。 根据婚姻状况分类后married为都没有拖欠贷款是纯的, single, divorced分支统计如下
6,接下来,采用同样的方法,分别计算剩下属性. 对于是否有房属性,可得:
7,对于年收入属性则有:
8,根据是否有房分类后, 有房的都没有拖欠, 是纯的, 在没有房的分支上, 只有年收入属性了.
经过如上流程,构建的决策树,如下图:
现在我们来总结一下CART的算法流程
while(当前节点"不纯"):
1.遍历每个变量的每一种分割方式,找到最好的分割点
2.分割成两个节点N1和N2
end while
每个节点足够“纯”为止
2.5总结
2.5.1决策树变量的两种类型
- 数字型(Numeric):变量类型是整数或浮点数,如前面例子中的“年收入”。用“>=”,“>”,“<”或“<=”作为分割条件(排序后,利用已有的分割情况,可以优化分割算法的时间复杂度)。
- 名称型(Nominal):类似编程语言中的枚举类型,变量只能从有限的选项中选取,比如前面例子中的“婚姻情况”,只能是“单身”,“已婚”或“离婚”,使用“=”来分割。
2.5.2如何评估分割点的好坏
如果一个分割点可以将当前的所有节点分为两类,使得每一类都很“纯”,也就是同一类的记录较多,那么就是一个好分割点。
比如上面的例子,“拥有房产”,可以将记录分成了两类,“是” 的节点全部都可以偿还债务,非常 “纯”;“否”的节点,可以偿还贷款和无法偿还贷款的人都有,不是很“纯”,但是两个节点加起来的纯度之和与原始节点的纯度之差最大,所以按照这种方法分割。
构建决策树采用贪心算法,只考虑当前纯度差最大的情况作为分割点。
2.5.3常见决策树的启发函数比较
信息熵:
信息增益--ID3决策树:
信息增益率--C4.5决策树:
基尼值:
基尼指数--CART决策树:
| 名称 | 提出时间 | 分支方式 | 备注 |
|---|---|---|---|
| CART | 1984 | Gini系数 | 可以进行分类和回归,可以处理离散属性,也可以处理连续属性 |
| ID3 | 1975 | 信息增益 | ID3只能对离散属性的数据集构成决策树 |
| C4.5 | 1993 | 信息增益率 | 优化后解决了ID3分支过程中总喜欢偏向选择值较多的属性 |
-
ID3算法
存在的缺点
(1) ID3算法在选择根节点和各内部节点中的分支属性时,采用信息增益作为评价标准。信息增益的缺点是倾向于选择取值较多的属性,在有些情况下这类属性可能不会提供太多有价值的信息.
(2) ID3算法只能对描述属性为离散型属性的数据集构造决策树。
-
C4.5算法
做出的改进(为什么使用C4.5要好)
(1) 用信息增益率来选择属性
(2) 可以处理连续数值型属性
(3)采用了一种后剪枝方法
(4)对于缺失值的处理
C4.5算法的优缺点
优点:
产生的分类规则易于理解,准确率较高。
缺点:
在构造树的过程中,需要对数据集进行多次的顺序扫描和排序,因而导致算法的低效。
此外,C4.5只适合于能够驻留于内存的数据集,当训练集大得无法在内存容纳时程序无法运行。
-
CART算法
CART算法相比C4.5算法的分类方法,采用了简化的二叉树模型,同时特征选择采用了近似的基尼系数来简化计算。
C4.5不一定是二叉树,但CART一定是二叉树。
同时,无论是ID3, C4.5还是CART,在做特征选择的时候都是选择最优的一个特征来做分类决策,但是大多数,分类决策不应该是由某一个特征决定的,而是应该由一组特征决定的。这样决策得到的决策树更加准确。这个决策树叫做多变量决策树(multi-variate decision tree)。在选择最优特征的时候,多变量决策树不是选择某一个最优特征,而是选择最优的一个特征线性组合来做决策。这个算法的代表是OC1,这里不多介绍。
如果样本发生一点点的改动,就会导致树结构的剧烈改变。这个可以通过集成学习里面的随机森林之类的方法解决。
三.CART剪枝
3.1为什么要剪枝
-
横轴表示在决策树创建过程中树的结点总数,纵轴表示决策树的预测精度。
-
实线显示的是决策树在训练集上的精度,虚线显示的则是在一个独立的测试集上测量出来的精度。
-
随着树的增长,在训练样集上的精度是单调上升的, 然而在独立的测试样例上测出的精度先上升后下降。
出现这种情况的原因:
原因1:噪声、样本冲突,即错误的样本数据。
原因2:特征即属性不能完全作为分类标准。
原因3:巧合的规律性,数据量不够大。
3.2常用的剪枝方法
3.2.1预剪枝
-
每一个结点所包含的最小样本数目,例如10,则该结点总样本数小于10时,则不再分;
-
指定树的高度或者深度,例如树的最大深度为4;
-
指定结点的熵小于某个值,不再划分。随着树的增长, 在训练样集上的精度是单调上升的, 然而在独立的测试样例上测出的精度先上升后下降。
3.2.2后剪枝
后剪枝,在已生成过拟合决策树上进行剪枝,可以得到简化版的剪枝决策树。
四.特征工程-特征提取
4.1特征提取
4.1.1定义
将任意数据(如文本或图像)转换为可用于机器学习的数字特征
注:特征值化是为了计算机更好的去理解数据
- 特征提取分类:
- 字典特征提取(特征离散化)
- 文本特征提取
- 图像特征提取
4.1.2特征提取API
sklearn.feature_extraction
4.2字典特征提取
作用:对字典数据进行特征值化
- sklearn.feature_extraction.DictVectorizer(sparse=True,…)
- DictVectorizer.fit_transform(X)
- X:字典或者包含字典的迭代器返回值
- 返回sparse矩阵
- DictVectorizer.get_feature_names() 返回类别名称
- DictVectorizer.fit_transform(X)
4.2.1应用
我们对以下数据进行特征提取
[{'city': '北京','temperature':100},
{'city': '上海','temperature':60},
{'city': '深圳','temperature':30}]
4.2.2流程分析
- 实例化类DictVectorizer
- 调用fit_transform方法输入数据并转换(注意返回格式)
from sklearn.feature_extraction import DictVectorizer
def dict_demo():
data = [{'city': '北京', 'temperature': 100}, {'city': '上海', 'temperature': 60}, {'city': '深圳', 'temperature': 30}]
# 1、实例化一个转换器类
transfer = DictVectorizer(sparse=False)
# 2、调用fit_transform
data = transfer.fit_transform(data)
print("返回的结果:\n", data)
# 打印特征名字
print("特征名字:\n", transfer.get_feature_names())
return None
dict_demo()
注意观察没有加上sparse=False参数的结果
返回的结果:
(0, 1) 1.0
(0, 3) 100.0
(1, 0) 1.0
(1, 3) 60.0
(2, 2) 1.0
(2, 3) 30.0
特征名字:
['city=上海', 'city=北京', 'city=深圳', 'temperature']
这个结果并不是我们想要看到的,所以加上参数,得到想要的结果:
返回的结果:
[[ 0. 1. 0. 100.]
[ 1. 0. 0. 60.]
[ 0. 0. 1. 30.]]
特征名字:
['city=上海', 'city=北京', 'city=深圳', 'temperature']
之前在学习pandas中的离散化的时候,也实现了类似的效果。
我们把这个处理数据的技巧叫做”one-hot“编码。
4.3文本特征提取
作用:对文本数据进行特征值化
- sklearn.feature_extraction.text.CountVectorizer(stop_words=[])
- 返回词频矩阵
- CountVectorizer.fit_transform(X)
- X:文本或者包含文本字符串的可迭代对象
- 返回值:返回sparse矩阵
- CountVectorizer.get_feature_names() 返回值:单词列表
- sklearn.feature_extraction.text.TfidfVectorizer
4.3.1应用
对如下数据进行特征提取
["life is short,i like python",
"life is too long,i dislike python"]
4.3.2流程分析
- 实例化类CountVectorizer
- 调用fit_transform方法输入数据并转换 (注意返回格式,利用toarray()进行sparse矩阵转换array数组)
from sklearn.feature_extraction.text import CountVectorizer
def text_count_demo():
"""
对文本进行特征抽取,countvetorizer
:return: None
"""
data = ["life is short,i like like python", "life is too long,i dislike python"]
# 1、实例化一个转换器类
# transfer = CountVectorizer(sparse=False) # 注意,没有sparse这个参数
transfer = CountVectorizer()
# 2、调用fit_transform
data = transfer.fit_transform(data)
print("文本特征抽取的结果:\n", data.toarray())
print("返回特征名字:\n", transfer.get_feature_names())
return None
返回结果:
文本特征抽取的结果:
[[0 1 1 2 0 1 1 0]
[1 1 1 0 1 1 0 1]]
返回特征名字:
['dislike', 'is', 'life', 'like', 'long', 'python', 'short', 'too']
问题:如果我们将数据替换成中文?
"人生苦短,我喜欢Python","生活太长久,我不喜欢Python"
那么最终得到的结果是
为什么会得到这样的结果呢,仔细分析之后会发现英文默认是以空格分开的。其实就达到了一个分词的效果,所以我们要对中文进行分词处理
4.3.3 jieba分词处理
- jieba.cut()
- 返回词语组成的生成器
需要安装下jieba库
pip install jieba
4.3.4案例分析
对一下三句话j进行特征值化
今天很残酷,明天更残酷,后天很美好,
但绝对大部分是死在明天晚上,所以每个人不要放弃今天。
我们看到的从很远星系来的光是在几百万年之前发出的,
这样当我们看到宇宙时,我们是在看它的过去。
如果只用一种方式了解某样事物,你就不会真正了解它。
了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。
- 分析
- 准备句子,利用jieba.cut进行分词
- 实例化CountVectorizer
- 将分词结果变成字符串当作fit_transform的输入值
from sklearn.feature_extraction.text import CountVectorizer
import jieba
def cut_word(text):
"""
对中文进行分词
"我爱北京天安门"————>"我 爱 北京 天安门"
:param text:
:return: text
"""
# 用结巴对中文字符串进行分词
text = " ".join(list(jieba.cut(text)))
return text
def text_chinese_count_demo2():
"""
对中文进行特征抽取
:return: None
"""
data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。",
"我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。",
"如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"]
# 将原始数据转换成分好词的形式
text_list = []
for sent in data:
text_list.append(cut_word(sent))
print(text_list)
# 1、实例化一个转换器类
# transfer = CountVectorizer(sparse=False)
transfer = CountVectorizer()
# 2、调用fit_transform
data = transfer.fit_transform(text_list)
print("文本特征抽取的结果:\n", data.toarray())
print("返回特征名字:\n", transfer.get_feature_names())
return None
返回结果:
Building prefix dict from the default dictionary ...
Dumping model to file cache /var/folders/mz/tzf2l3sx4rgg6qpglfb035_r0000gn/T/jieba.cache
Loading model cost 1.032 seconds.
['一种 还是 一种 今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝对 大部分 是 死 在 明天 晚上 , 所以 每个 人 不要 放弃 今天 。', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去 。', '如果 只用 一种 方式 了解 某样 事物 , 你 就 不会 真正 了解 它 。 了解 事物 真正 含义 的 秘密 取决于 如何 将 其 与 我们 所 了解 的 事物 相 联系 。']
Prefix dict has been built succesfully.
文本特征抽取的结果:
[[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]]
返回特征名字:
['一种', '不会', '不要', '之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']
但如果把这样的词语特征用于分类,会出现什么问题?
该如何处理某个词或短语在多篇文章中出现的次数高这种情况
4.3.5 Tf-idf文本特征提取
- TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的概率高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
- TF-IDF作用:用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。
-
公式
-
词频(term frequency,tf)指的是某一个给定的词语在该文件中出现的频率
-
逆向文档频率(inverse document frequency,idf)是一个词语普遍重要性的度量。某一特定词语的idf,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取以10为底的对数得到
-
最终得出结果可以理解为重要程度:
举例:
假如一篇文章的总词语数是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
-
案例
from sklearn.feature_extraction.text import TfidfVectorizer import jieba def cut_word(text): """ 对中文进行分词 "我爱北京天安门"————>"我 爱 北京 天安门" :param text: :return: text """ # 用结巴对中文字符串进行分词 text = " ".join(list(jieba.cut(text))) return text def text_chinese_tfidf_demo(): """ 对中文进行特征抽取 :return: None """ data = ["一种还是一种今天很残酷,明天更残酷,后天很美好,但绝对大部分是死在明天晚上,所以每个人不要放弃今天。", "我们看到的从很远星系来的光是在几百万年之前发出的,这样当我们看到宇宙时,我们是在看它的过去。", "如果只用一种方式了解某样事物,你就不会真正了解它。了解事物真正含义的秘密取决于如何将其与我们所了解的事物相联系。"] # 将原始数据转换成分好词的形式 text_list = [] for sent in data: text_list.append(cut_word(sent)) print(text_list) # 1、实例化一个转换器类 # transfer = CountVectorizer(sparse=False) transfer = TfidfVectorizer(stop_words=['一种', '不会', '不要']) # 2、调用fit_transform data = transfer.fit_transform(text_list) print("文本特征抽取的结果:\n", data.toarray()) print("返回特征名字:\n", transfer.get_feature_names()) return None返回结果:
Building prefix dict from the default dictionary ... Loading model from cache /var/folders/mz/tzf2l3sx4rgg6qpglfb035_r0000gn/T/jieba.cache Loading model cost 0.856 seconds. Prefix dict has been built succesfully. ['一种 还是 一种 今天 很 残酷 , 明天 更 残酷 , 后天 很 美好 , 但 绝对 大部分 是 死 在 明天 晚上 , 所以 每个 人 不要 放弃 今天 。', '我们 看到 的 从 很 远 星系 来 的 光是在 几百万年 之前 发出 的 , 这样 当 我们 看到 宇宙 时 , 我们 是 在 看 它 的 过去 。', '如果 只用 一种 方式 了解 某样 事物 , 你 就 不会 真正 了解 它 。 了解 事物 真正 含义 的 秘密 取决于 如何 将 其 与 我们 所 了解 的 事物 相 联系 。'] 文本特征抽取的结果: [[ 0. 0. 0. 0.43643578 0. 0. 0. 0. 0. 0.21821789 0. 0.21821789 0. 0. 0. 0. 0.21821789 0.21821789 0. 0.43643578 0. 0.21821789 0. 0.43643578 0.21821789 0. 0. 0. 0.21821789 0.21821789 0. 0. 0.21821789 0. ] [ 0.2410822 0. 0. 0. 0.2410822 0.2410822 0.2410822 0. 0. 0. 0. 0. 0. 0. 0.2410822 0.55004769 0. 0. 0. 0. 0.2410822 0. 0. 0. 0. 0.48216441 0. 0. 0. 0. 0. 0.2410822 0. 0.2410822 ] [ 0. 0.644003 0.48300225 0. 0. 0. 0. 0.16100075 0.16100075 0. 0.16100075 0. 0.16100075 0.16100075 0. 0.12244522 0. 0. 0.16100075 0. 0. 0. 0.16100075 0. 0. 0. 0.3220015 0.16100075 0. 0. 0.16100075 0. 0. 0. ]] 返回特征名字: ['之前', '了解', '事物', '今天', '光是在', '几百万年', '发出', '取决于', '只用', '后天', '含义', '大部分', '如何', '如果', '宇宙', '我们', '所以', '放弃', '方式', '明天', '星系', '晚上', '某样', '残酷', '每个', '看到', '真正', '秘密', '绝对', '美好', '联系', '过去', '还是', '这样']
五.决策树算法api
- class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, max_depth=None,random_state=None)
- criterion
- 特征选择标准
- "gini"或者"entropy",前者代表基尼系数,后者代表信息增益。一默认"gini",即CART算法。
- min_samples_split
- 内部节点再划分所需最小样本数
- 这个值限制了子树继续划分的条件,如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分。 默认是2.如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。我之前的一个项目例子,有大概10万样本,建立决策树时,我选择了min_samples_split=10。可以作为参考。
- min_samples_leaf
- 叶子节点最少样本数
- 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。 默认是1,可以输入最少的样本数的整数,或者最少样本数占样本总数的百分比。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。之前的10万样本项目使用min_samples_leaf的值为5,仅供参考。
- max_depth
- 决策树最大深度
- 决策树的最大深度,默认可以不输入,如果不输入的话,决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间
- random_state
- 随机数种子
- criterion
六.案例
泰坦尼克号乘客生存预测
6.1泰坦尼克号数据
在泰坦尼克号和titanic2数据帧描述泰坦尼克号上的个别乘客的生存状态。这里使用的数据集是由各种研究人员开始的。其中包括许多研究人员创建的旅客名单,由Michael A. Findlay编辑。我们提取的数据集中的特征是票的类别,存活,乘坐班,年龄,登陆,home.dest,房间,票,船和性别。
经过观察数据得到:
- 1 乘坐班是指乘客班(1,2,3),是社会经济阶层的代表。
- 2 其中age数据存在缺失。
6.2步骤分析
- 获取数据
- 数据基本处理
- 确定特征值、目标值
- 缺失值处理
- 数据集划分
- 特征工程:字典特征抽取
- 机器学习:决策树
- 模型评估
6.3代码
-
导入模块
import pandas as pd from sklearn.feature_extraction import DictVectorizer from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier, export_graphviz -
获取数据
# 获取数据 titan = pd.read_csv("http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt") -
数据基本处理
-
确定目标值、特征值
# 确定特征值、目标值 x = titan[["pclass", "age", "sex"]] y = titan["survived"] -
缺失值处理
# 缺失值需要处理,将特征当中有类别的这些特征进行字典特征抽取 x['age'].fillna(x['age'].mean(), inplace=True) -
数据集划分
# 数据集划分 x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)
-
-
特征工程:字典特征提取
特征中出现类别符号,需要进行one-hot编码处理(DictVectorizer)
x.to_dict(orient="records") 需要将数组特征转换成字典数据
# 特征工程:字典特征抽取 # 对于x转换成字典数据x.to_dict(orient="records") # [{"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")) -
决策树模型训练和评估
决策树API当中,如果没有指定max_depth那么会根据信息熵的条件直到最终结束。这里我们可以指定树的深度来进行限制树的大小
# 机器学习:决策树 estimator = DecisionTreeClassifier(criterion="entropy", max_depth=5) estimator.fit(x_train, y_train) # 模型评估 estimator.score(x_test, y_test) estimator.predict(x_test)
6.4决策树可视化
6.4.1保存树的结构到dot文件
- sklearn.tree.export_graphviz() 该函数能够导出DOT格式
- tree.export_graphviz(estimator,out_file='tree.dot’,feature_names=[‘’,’’])
export_graphviz(estimator, out_file="./data/tree.dot", feature_names=['age', 'pclass=1st', 'pclass=2nd', 'pclass=3rd', '女性', '男性'])
dot文件内容如下:
digraph Tree {
node [shape=box] ;
0 [label="男性 <= 0.5\nentropy = 0.919\nsamples = 984\nvalue = [655, 329]"] ;
1 [label="pclass=3rd <= 0.5\nentropy = 0.912\nsamples = 336\nvalue = [110, 226]"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ;
2 [label="age <= 38.5\nentropy = 0.346\nsamples = 185\nvalue = [12, 173]"] ;
1 -> 2 ;
3 [label="age <= 17.5\nentropy = 0.399\nsamples = 139\nvalue = [11, 128]"] ;
2 -> 3 ;
4 [label="entropy = 0.0\nsamples = 15\nvalue = [0, 15]"] ;
3 -> 4 ;
5 [label="age <= 37.5\nentropy = 0.432\nsamples = 124\nvalue = [11, 113]"] ;
3 -> 5 ;
6 [label="entropy = 0.409\nsamples = 122\nvalue = [10, 112]"] ;
5 -> 6 ;
7 [label="entropy = 1.0\nsamples = 2\nvalue = [1, 1]"] ;
5 -> 7 ;
8 [label="age <= 49.5\nentropy = 0.151\nsamples = 46\nvalue = [1, 45]"] ;
2 -> 8 ;
9 [label="entropy = 0.0\nsamples = 26\nvalue = [0, 26]"] ;
8 -> 9 ;
10 [label="age <= 50.5\nentropy = 0.286\nsamples = 20\nvalue = [1, 19]"] ;
8 -> 10 ;
11 [label="entropy = 0.722\nsamples = 5\nvalue = [1, 4]"] ;
10 -> 11 ;
12 [label="entropy = 0.0\nsamples = 15\nvalue = [0, 15]"] ;
10 -> 12 ;
13 [label="age <= 38.5\nentropy = 0.935\nsamples = 151\nvalue = [98, 53]"] ;
1 -> 13 ;
14 [label="age <= 32.097\nentropy = 0.945\nsamples = 146\nvalue = [93, 53]"] ;
13 -> 14 ;
15 [label="age <= 19.5\nentropy = 0.93\nsamples = 139\nvalue = [91, 48]"] ;
14 -> 15 ;
16 [label="entropy = 0.998\nsamples = 21\nvalue = [10, 11]"] ;
15 -> 16 ;
17 [label="entropy = 0.897\nsamples = 118\nvalue = [81, 37]"] ;
15 -> 17 ;
18 [label="age <= 36.5\nentropy = 0.863\nsamples = 7\nvalue = [2, 5]"] ;
14 -> 18 ;
19 [label="entropy = 0.0\nsamples = 4\nvalue = [0, 4]"] ;
18 -> 19 ;
20 [label="entropy = 0.918\nsamples = 3\nvalue = [2, 1]"] ;
18 -> 20 ;
21 [label="entropy = 0.0\nsamples = 5\nvalue = [5, 0]"] ;
13 -> 21 ;
22 [label="age <= 12.5\nentropy = 0.632\nsamples = 648\nvalue = [545, 103]"] ;
0 -> 22 [labeldistance=2.5, labelangle=-45, headlabel="False"] ;
23 [label="pclass=3rd <= 0.5\nentropy = 0.702\nsamples = 21\nvalue = [4, 17]"] ;
22 -> 23 ;
24 [label="entropy = 0.0\nsamples = 13\nvalue = [0, 13]"] ;
23 -> 24 ;
25 [label="age <= 3.5\nentropy = 1.0\nsamples = 8\nvalue = [4, 4]"] ;
23 -> 25 ;
26 [label="entropy = 0.0\nsamples = 2\nvalue = [0, 2]"] ;
25 -> 26 ;
27 [label="age <= 7.5\nentropy = 0.918\nsamples = 6\nvalue = [4, 2]"] ;
25 -> 27 ;
28 [label="entropy = 0.0\nsamples = 2\nvalue = [2, 0]"] ;
27 -> 28 ;
29 [label="entropy = 1.0\nsamples = 4\nvalue = [2, 2]"] ;
27 -> 29 ;
30 [label="pclass=1st <= 0.5\nentropy = 0.577\nsamples = 627\nvalue = [541, 86]"] ;
22 -> 30 ;
31 [label="age <= 18.5\nentropy = 0.462\nsamples = 501\nvalue = [452, 49]"] ;
30 -> 31 ;
32 [label="entropy = 0.0\nsamples = 17\nvalue = [17, 0]"] ;
31 -> 32 ;
33 [label="age <= 45.5\nentropy = 0.473\nsamples = 484\nvalue = [435, 49]"] ;
31 -> 33 ;
34 [label="entropy = 0.484\nsamples = 468\nvalue = [419, 49]"] ;
33 -> 34 ;
35 [label="entropy = 0.0\nsamples = 16\nvalue = [16, 0]"] ;
33 -> 35 ;
36 [label="age <= 60.5\nentropy = 0.873\nsamples = 126\nvalue = [89, 37]"] ;
30 -> 36 ;
37 [label="age <= 28.5\nentropy = 0.9\nsamples = 117\nvalue = [80, 37]"] ;
36 -> 37 ;
38 [label="entropy = 1.0\nsamples = 14\nvalue = [7, 7]"] ;
37 -> 38 ;
39 [label="entropy = 0.87\nsamples = 103\nvalue = [73, 30]"] ;
37 -> 39 ;
40 [label="entropy = 0.0\nsamples = 9\nvalue = [9, 0]"] ;
36 -> 40 ;
}
6.4.2网站显示决策树结构
6.5决策树总结
- 优点:
- 简单的理解和解释,树木可视化。
- 缺点:
- 决策树学习者可以创建不能很好地推广数据的过于复杂的树,容易发生过拟合。
- 改进:
- 减枝cart算法
- 随机森林(集成学习的一种)
注:企业重要决策,由于决策树很好的分析能力,在决策过程应用较多, 可以选择特征