集成学习 - 决策树-随机森林
认识
我觉得决策树+ 随机森林 应该是 ML 中最为重要的算法之一了吧, 反正我是很喜欢用的.
- 算法难度低, 可解释性很强, 能可视化
- 能处理非线性, 可扩展为随机森林(集成学习)
建立决策树的判别依据有很多, 比较主流的有经典的 ID3 算法(熵), C4.5 , 基尼系数等. 我是这种基于熵的理解了, 上学时学过熵的概念, 在<<管理信息系统>> 和 <<系统工程概论>> 有讲到. 其余的也没仔细看, 暂时能深入理解一个就可以了.
信息熵
-
衡量信息的不确定性 或 混乱程度的指标
-
不确定性越大, 则熵值越大
直观认知
举几个栗子:
a. 大海捞针: 几乎不可能, 熵值很大
b. 扔一次硬币: 不确定性也很大, 熵值很大
c. 詹姆斯强攻篮下: 进球概率大, 不确定性很小, 因此熵值小
d. 斗地主时我已经推出对方剩下的牌: 这个信息量就很大了, 对于局势不确定性减少, 则熵变小.
通过直观, 我们用 信息熵 这个概念来 衡量信息的不确定性大小, 如何对这个概念直观定性呢?
- 信息量,感觉上跟概率有关系, 概率很多, 信息量就很多
- 信息量, 应该是可以相加的
- 信息量, 应该是一个单调的
- 信息量, 应该不能为负数, 最多只能有, 最差情况是没有信息含量, 不能说负吧
理性认知
也可以从系统论的观点来认识 (系统 = 多个元素的 相互联系).
可以肯定一点, 既然跟概率有关, 那 概率取值是 [0,1], 多个事件的发生, 如是独立事件 则可以概率相加 , 但如果是条件概率, 涉及概率相乘, 那这就麻烦了.
- 信息熵 = p(x1) + p(x2) ... ?
- 信息熵 = p(x1)p(x2).....?
因为计算机是有精度的嘛, 如果多个 [0, 1] 之间的数相乘, 计算机就 overflow 了, 同时, 乘法也算起来也比较麻烦, 怎么处理呢, 当然是用对数呀, 乘法变加法, 同时还单调的 (log 默认底数为2哈)
\(log[p(x1) * p2(x2) * p(x3)] = log \ p(x1) + log \ p(x2) + log \ p(x3)\)
同时呢, 再加上概率自身作为权重, 那这样就能 想到信息熵的公式了
某事件x的信息熵 \(= -P(x)log(P(x))\)
为啥要有 - 负号? 因为 \(log_2 x\) 在 [0,1] 是小于0 的, 为了保证 信息量非负
结论: 信息熵 = 概率 * log(概率)
对于一个事件而言, 多个随机变量的值组成
\(H(X) = - \sum \limits _{i=1}^n P(X=x_i)log(P(X=x_i))\)
case
扔硬币的结果看作一个随机事件 X, 则 X 的分布有两种结果, 即正面, 反面
硬币1 (质地均匀):
估算出: 正面的概率是 0.5, 反面的概率是 0.5
则 熵 (Entropy) = \(0.5 * log(0.5) + 0.5 * log(0.5) = 1\)
说明, 质地均匀的的硬币, 熵还是很大的, 也就是说不确定性很大, 也可以说机会均等(公平竞争嘛)
硬币2(质地不均匀)
估算出: 正面的概率是 0.99, 反面的概率是 0.01
则 熵(Entropy) = \(0.99* log(0.99) + 0.01 *log(0.01) = 0.08\)
说明, 质地不均匀的硬币, 熵是较小的, 也就是说不确定性很小, 也可以说赌场出老千(不平等竞争)
条件熵
条件熵是通过,获得更多信息 来减少不确定性(熵). 即我们知道到信息越多, 信息的不确定性越小, 则熵越小.
就跟平时做促销激励是一样的. 我之前在一家房地产公司做数据分析师, 是跟着运营线走吗, 每周开会,就会结合市场来主打哪个楼盘. 在没有说要主打前, 我这的报表数据, 带看,认筹量基本是平缓的, 环比上月也无显著变化. 那这时候的熵值其实很很大的, 因为不确定也很大,对于公司. 于是跟着市场趋势, 主打某个楼盘, 大力宣传和促销激励, 这时的数据就会明细陡峭, 销量也会上升, 通过促销提高了房子的销量, 从而减少了销售的 熵值(不确定性).
从代数上来表示, 假设 购房事件是 X, 促销事件是 Y, 则要求的是 P(X|Y), 即 Y 事件发生的情况下, X 发生的条件概率的熵
\(H(X|Y) = -\sum \limits_{v=1}^n P(Y=i) H(X|Y = v)\)
跟条件概率的写法是一样的.
\(H(X|Y=v) = -\sum \limits _{i}^n P(X=i|Y=v)log_2 P(X=i|Y=v)\)
信息增益
\(I(XY) = H(X) - H(X|Y) = H(Y) - H(X|Y)\)
-
\(H(X) = -\sum \limits _{i=1}^n P(X=i) log_2 P(X=i)\)
-
\(H(X|Y=v) = -\sum \limits _{i=1}^n P(X|Y=v) log_2 P(X|Y=v)\)
-
\(H(X|Y) = -\sum \limits_{v=1}^n P(Y=i) H(X|Y = v)\)
case : 计算信息增益
构造一棵树. 假设数据有 两个类别, 共有 30个点
父节点: A有16个点, B有14个点
按某种条件再划分两个子节点
-
左子节点: A有12个, B有1个
-
有子节点: A有4个, B有13个
则:
父节点熵 =\(\ [-(\frac {16}{30} log_2 \frac {16}{30})] +[ -(\frac {14}{30} log_2 \frac {14}{30})] = 0.996\)
左子节点 = \(\ [-(\frac {12}{13} log_2 \frac {12}{13})] +[ -(\frac {1}{13} log_2 \frac {1}{13})] = 0.391\)
右子节点 = \(\ [-(\frac {4}{17} log_2 \frac {4}{17})] +[ -(\frac {13}{17} log_2 \frac {13}{17})] = 0.787\)
则条件熵 = \((\frac {13}{30} * 0.391 + \frac {17}{30} * 0.787 = 0.615\)
则 信息增益 = 0.996 - 0.615
为啥 信息增益非常重要呢?
信息增益越大, 则说明,刚加入的这条信息的信息量很大, 则, 对不确定性的减少越大, 则越有信心
由上, 父节点按不同的方式划分节点会得到不同的信息增益, 选择信息增益最大的, 作为子节点的划分方式
案例 -根据天气判断打高尔夫
这是网上的一个案例.
Outlook | Temp | Humidity | Wind | Play Golf |
---|---|---|---|---|
rainy | hot | high | false | no |
rainy | hot | high | true | no |
overcast | hot | high | false | yes |
sunny | mild | high | false | yes |
sunny | cool | normal | false | yes |
sunny | cool | normal | false | no |
overcast | cool | normal | true | yes |
rainy | mild | high | true | no |
rainy | cool | normal | false | yes |
sunny | mild | normal | false | yes |
rainy | mild | normal | true | yes |
overcast | mild | high | true | yes |
overcast | hot | normal | false | yes |
sunny | mild | high | true | no |
需求: 根据天气情况, 判断这位兄弟, 会不会来打高尔夫
\(H(X) = -\sum \limits _{i=1}^n p(x_i)log \ p(x_i)\)
首先, 计算一波父节点的熵, 即根据 Play Golf 来计算:
Play Golf | 频次 | 频率 | 熵 |
---|---|---|---|
yes | 9 | 0.64 | 0.41 |
no | 5 | 0.36 | 0.53 |
合计 | 14 | 1 | 0.94 |
即通过 历史数据, 这个兄弟, 打球的概率为 64%, 不打为 36%, 熵值达到了 0.94, 挺高的, 不确定很大呀.
然后, 构造第一个子节点
有4个特征可以作为划分依据, 随便取, 算了,就按第一个 outlook 吧
ps: 可以用 excel 对数据进行透视呀
outlook | yes | no | 频次 | 概率 |
---|---|---|---|---|
rainy | 2 | 3 | 5 | 0.36 |
overcast | 4 | 0 | 4 | 0.29 |
sunny | 3 | 2 | 5 | 0.36 |
合计 | 9 | 5 | 14 |
outlook | yes | no | 熵 |
---|---|---|---|
rainy | 0.4 | 0.6 | 0.97 |
overcast | 1 | 0 | 0 |
suny | 0.6 | 0.4 | 0.97 |
Outlook 的条件熵 = 0.36* 0.97 + 0.29*0 + 0.36 * 0.97 = 0.69
信息增益-Outlook = 0.94 - 0.69 = 0.25
接着对 temp 作为划分
Temp | yes | no | 频次 | 频率 |
---|---|---|---|---|
hot | 2 | 2 | 4 | 0.29 |
mild | 4 | 2 | 6 | 0.43 |
cool | 3 | 1 | 4 | 0.29 |
14 |
Temp | yes | no | 熵 |
---|---|---|---|
hot | 0.5 | 0.5 | 1 |
mild | 0.67 | 0.33 | 0.92 |
cool | 0.75 | 0.25 | 0.81 |
Temp 的条件熵 = 0.2 * 9 * 1 + 0.43 * 0.92 + 0.29 * 0.81 = 0.92
信息增益-Temp = 0.94 - 0.92 = 0.02
.... 重复上
信息增益-Wind = 0.94 - 0.89 = 0.05
信息增益-Humidity = 0.94 - 0.79 = 0.15
这样遍历一轮后, 发现, 发现信息增益最大的是 按Outlook 来分的情况, 达到 0.25, 即为当前最佳分类特征
因此, 决策树的第一层就构建好了
Outlook
- sunny
- overcast
- yes (叶子节点)
- rainny
然后 再继续往下对 sunny 和 overcast 作为同样操作
...
直到找到所有的叶子节点, 这样就构建好了一颗决策树如下:
Outlook
- sunny
- wind
- false
- Y
- true
- N
- false
- wind
- overcast
- Y
- rainy
- humidtiy
- high
- N
- normal
- Y
- high
- humidtiy
树建好了, 其实也就是预测的过程.
对于非类别情况
就是对于 连续型的变量, 解决的方案是:
将实数值等分为 n 个区间, 然后将一个区间内的值, 看作是一个 类别
if temp < 15 -> cool
if 15 < temp < 28 -> mild
....
就不就是再平时常说的 数据离散化 嘛
决策树小结
优势:
- 可解释性高
- 能处理非线性数据
- 不需要对数据做归一化
- 可用于做特征选择
- 对数据分布不考虑
- 容易软件实现和可视化
劣势:
-
容易过拟合
-
微小的数据改变,会改变整个数的形状
-
对类别不平衡 的数据不太好
-
不是最优解
我个人是非常推荐了, 主要是可以可视化+解释性高, 然后可以做成 PPT 跟老板展示, 另外也比较好写代码实现, 另外一点, 之前在写推荐系统 的代码时, 在特征处理部分, 有特征值缺失, 我当时就用了 随机森林 来预测特征的缺失值
决策树, 用处多多, 真的非常棒!
随机森林
= Bagging w. Trees + random feature
直观上理解就是, 采用 bootsrap 方法, 训练 多个决策树, 最后对所有的结果(Bagging) 进行 voting (投票) 而已啦.
我个人常用的一种分法如下:
- N表是训练样本的个数, P表示特征数目(p维矩阵)
- 一次随机去除一个样本(一行), 重复N次
- 随机去选出p个特征, p<P, 建立决策树
- 采取bootstrap(有放回抽样)
如和随机构造树
即讨论, 是按照 每一棵树 来随机选择特征, 还是 按照每一个树的节点来选择特征. 从现在的应用来看, 还是 Breiman , Lee 在 2001年的paper 观点, 会有更多认同
"....Random forest with random features is formed by selecting at random, at each node, a small group of input variables to split on"
随机森林小结
-
当前所有算法中, 具有极高的准确率 (属于集成学习)
-
能有效地运行在大数据集上, 和处理高纬度的样本, 不需要降维
-
是近乎完美的分类器
反正, 我是特别推崇 决策树/随机森林的
调参侠sklearn 一次
好久都没有贴代码了, 也没有自己去实现用numpy, 重点是先放在了理解数学原理上了. 不过工作中还是要以代码为主, 吓得我赶紧 copy 一段来压压惊,
调API 真的没啥技术含量, 如果不理解原理的话.
kaggle 的一个经典数据集 泰坦尼克号生存预测
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction import DictVectorizer
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
data = pd.read_csv("titanic.txt")
y = data['survived']
# 特征选择, 这里是反复测试的过程
X = data[['pclass', 'age', 'sex']]
# age字段有缺失值, 用均值去填充
X['age'].fillna(X['age'].mean(), inplace=True)
# 特征工程-对类别数据要进行OneHot编码
# 先将df转为dict, [{},{}..]再编码
X.to_dict(orient='records')
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.25)
# 特征提取
d = DictVectorizer()
X_train = d.fit_transform(X_train)
X_test = d.transform(X_test)
print(X_train.toarray())
# 模型训练
tree = DecisionTreeClassifier(max_depth=5)
tree.fit(X_train)
# 查看
print("真实结果:", y_test.values)
print('预测结果:', tree.predict(X_test))
print('准确分数:', tree.score(X_test, y_test))
# 剪枝算法: 通过设置depth来达到减少树的深度的效果
# 树可视化: 将树导出到dot文件中, 可视化观察,前提win下要安装一个exe
exprot_graphviz(tree, "test.doc",
feture_names=['age', 'pclass=lst', 'pclass=2nd', 'pclass=3rd',
'female', 'male'])
改为调用 随机森林的 API
# 随机森林去完成, 泰坦尼克号的预测
rf = RandomForestClassifier()
param = {'n_estimators:':[200, 500, 800, 1200, 1500, 2000],
'max_depth':[5, 8, 15, 24, 28, 32]}
# 超参数调优
gs = GridSearchCV(rf, param_grid=param, cv=5)
gs.fit(X_train, y_train)
print("随机森林预测的准确率为:", gs.score(x_test, y_test))
确实是, 数学理论推导,证明真的很麻烦,
but
调用API 的话, 真的非常容易, C V + 调参 就可以了
IF 从算法的根源上去理解了
API 就是浮云, 认知深刻了, 才能 做更好的 调参侠.