图片作者
许多高级机器学习模型,例如随机森林或梯度提升算法,例如 XGBoost、CatBoost 或 LightGBM(甚至 自动编码器!) 依赖于一个关键的共同成分: 决策树!
如果不了解决策树,就不可能理解上述任何高级装袋或梯度提升算法,这对任何数据科学家来说都是一种耻辱! 因此,让我们通过在 Python 中实现决策树来揭开决策树的内部工作原理的神秘面纱。
在本文中,您将学习
- 决策树为什么以及如何拆分数据,
- 信息增益,和
- 如何使用 NumPy 在 Python 中实现决策树。
你可以在上面找到代码 我的Github.
为了做出预测,决策树依赖于 分裂 以递归方式将数据集分成更小的部分。
图片作者
在上图中,您可以看到一个拆分示例——原始数据集被分成两部分。 在下一步中,这两个部分将再次拆分,依此类推。 这一直持续到满足某种停止标准为止,例如,
- 如果拆分导致部分为空
- 如果达到某个递归深度
- 如果(在之前的拆分之后)数据集仅包含少数元素,则无需进一步拆分。
我们如何找到这些分裂? 为什么我们甚至关心? 让我们找出来。
动机
让我们假设我们要解决一个 二进制 分类问题 我们现在创造自己:
import numpy as np
np.random.seed(0) X = np.random.randn(100, 2) # features
y = ((X[:, 0] > 0) * (X[:, 1] < 0)) # labels (0 and 1)
二维数据如下所示:
图片作者
我们可以看到有两个不同的类别——紫色占大约 75%,黄色占大约 25%。 如果您将此数据提供给决策树 分类,这棵树最初有以下想法:
“有两个不同的标签,这对我来说太乱了。 我想通过将数据分成两部分来清理这个烂摊子——这些部分应该比之前的完整数据更干净。” ——获得意识的树
树也是如此。
图片作者
树决定大致沿着 x-轴。 这样的效果是数据的顶部现在是完美的 清洁, 意味着你只能找到 一个班级 (在这种情况下为紫色)那里。
但是,底部仍然是 凌乱,在某种意义上甚至比以前更混乱。 在完整的数据集中,类别比例过去约为 75:25,但在这个较小的部分中约为 50:50,这是尽可能混合的
请注意: 在这里,紫色和黄色在图片中很好地分开并不重要。 只是 不同标签的原始数量 在两部分算。
图片作者
尽管如此,作为树的第一步,这已经足够好了,因此它继续进行。 虽然它不会在顶部造成另一个分裂, 清洁 部分不再存在,它可以在底部创建另一个拆分来清理它。
图片作者
Et voilà,这三个独立的部分中的每一个都是完全干净的,因为我们只在每个部分找到一个单一的颜色(标签)。
现在做预测真的很容易:如果有新的数据点进来,你只需检查其中的哪一个 三个部分 它撒谎并给它相应的颜色。 现在效果很好,因为每个部分都是 清洁. 容易,对吧?
图片作者
好吧,我们在谈论 清洁 和 凌乱 数据,但到目前为止,这些词只代表一些模糊的想法。 为了实现任何东西,我们必须找到一种方法来定义 清洁.
清洁措施
让我们假设我们有一些标签,例如
y_1 = [0, 0, 0, 0, 0, 0, 0, 0] y_2 = [1, 0, 0, 0, 0, 0, 1, 0]
y_3 = [1, 0, 1, 1, 0, 0, 1, 0]
凭直觉 y₁ 是最干净的标签集,其次是 yXNUMX 然后 y₃. 到目前为止一切顺利,但我们如何才能对这种行为进行统计呢? 也许最容易想到的是以下内容:
只需计算零的数量和一的数量。 计算它们的绝对差。 为了让它更好,通过除以数组的长度来规范化它。
例如, yXNUMX 总共有 8 个条目——6 个零和 2 个一。 因此,我们的自定义 清洁度评分 将是 |6 – 2| / 8 = 0.5。 很容易计算出清洁度得分为 y₁ 和 y₃ 分别为 1.0 和 0.0。 在这里,我们可以看到通用公式:
图片作者
在这里, n₀ 和 n₁ 分别是零和一的数量, n = n₀ + n₁ 是数组的长度和 p₁ = n₁ / n 是 1 个标签的份额。
这个公式的问题在于它是 专门针对两个类的情况,但我们通常对多类分类感兴趣。 一个非常有效的公式是 基尼杂质度量:
图片作者
或一般情况:
图片作者
它工作得很好,scikit-learn 采用它作为默认措施 其 DecisionTreeClassifier
类。
图片作者
请注意: 基尼系数 混乱 而不是清洁度。 示例:如果列表仅包含一个类(=非常干净的数据!),则总和中的所有项均为零,因此总和为零。 最坏的情况是如果所有类都出现了准确的次数,在这种情况下基尼系数是 1–1/C 哪里 C 是类的数量。
现在我们有了清洁/混乱的衡量标准,让我们看看如何使用它来找到好的拆分。
寻找分裂
我们有很多拆分可供选择,但哪种拆分方式好呢? 让我们再次使用我们的初始数据集,以及基尼不纯度度量。
图片作者
我们现在不计算点数,但让我们假设 75% 是紫色的,25% 是黄色的。 使用 Gini 的定义,完整数据集的不纯度为
图片作者
如果我们沿着 x-轴,如前所述:
图片作者
顶部的基尼杂质为 0.0 和底部
图片作者
平均而言,这两个部分的基尼不纯度为 (0.0 + 0.5) / 2 = 0.25,优于之前整个数据集的 0.375。 我们也可以用所谓的 信息获取:
这种分裂的信息增益是 0.375 – 0.25 = 0.125。
就这么简单。 信息增益越高(即基尼杂质越低)越好。
请注意: 另一个同样好的初始分割是沿着 y 轴。
要记住的一件重要事情是,它对 通过零件的大小衡量零件的基尼杂质. 例如,让我们假设
- 第 1 部分由 50 个数据点组成,基尼不纯度为 0.0 和
- 第 2 部分由 450 个数据点组成,基尼不纯度为 0.5,
那么平均基尼杂质不应该是 (0.0 + 0.5) / 2 = 0.25 而是 50 / (50 + 450) * 0.0 + 450 / (50 + 450) * 0.5 = 0.45.
好的,我们如何找到最佳拆分? 简单但发人深省的答案是:
只需尝试所有拆分并选择信息增益最高的拆分即可。 这基本上是一种蛮力方法。
更准确地说,标准决策树使用 沿坐标轴拆分,即 xᵢ = c 对于某些功能 i 和门槛 c. 这意味着
- 分割数据的一部分由所有数据点组成 x xᵢ c和
- 所有点的另一部分 x xᵢ ≥ c.
这些简单的拆分规则已在实践中证明足够好,但您当然也可以扩展此逻辑以创建其他拆分(即对角线,如 xᵢ + 2xⱼ = 3, 例如)。
太好了,这些都是我们现在需要的所有成分!
我们现在将实施决策树。 由于它由节点组成,让我们定义一个 Node
先上课。
from dataclasses import dataclass @dataclass
class Node: feature: int = None # feature for the split value: float = None # split threshold OR final prediction left: np.array = None # store one part of the data right: np.array = None # store the other part of the data
一个节点知道它用于分裂的特征(feature
) 以及分裂值 (value
). value
也作为决策树最终预测的存储。 由于我们将构建一棵二叉树,因此每个节点都需要知道其左右子节点,如存储在 left
和 right
.
现在,让我们进行实际的决策树实施。 我正在让它与 scikit-learn 兼容,因此我使用了一些来自 sklearn.base
. 如果您对此不熟悉,请查看我关于 如何构建 scikit-learn 兼容模型.
让我们实施吧!
import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin class DecisionTreeClassifier(BaseEstimator, ClassifierMixin): def __init__(self): self.root = Node() @staticmethod def _gini(y): """Gini impurity.""" counts = np.bincount(y) p = counts / counts.sum() return (p * (1 - p)).sum() def _split(self, X, y): """Bruteforce search over all features and splitting points.""" best_information_gain = float("-inf") best_feature = None best_split = None for feature in range(X.shape[1]): split_candidates = np.unique(X[:, feature]) for split in split_candidates: left_mask = X[:, feature] split X_left, y_left = X[left_mask], y[left_mask] X_right, y_right = X[~left_mask], y[~left_mask] information_gain = self._gini(y) - ( len(X_left) / len(X) * self._gini(y_left) + len(X_right) / len(X) * self._gini(y_right) ) if information_gain > best_information_gain: best_information_gain = information_gain best_feature = feature best_split = split return best_feature, best_split def _build_tree(self, X, y): """The heavy lifting.""" feature, split = self._split(X, y) left_mask = X[:, feature] split X_left, y_left = X[left_mask], y[left_mask] X_right, y_right = X[~left_mask], y[~left_mask] if len(X_left) == 0 or len(X_right) == 0: return Node(value=np.argmax(np.bincount(y))) else: return Node( feature, split, self._build_tree(X_left, y_left), self._build_tree(X_right, y_right), ) def _find_path(self, x, node): """Given a data point x, walk from the root to the corresponding leaf node. Output its value.""" if node.feature == None: return node.value else: if x[node.feature] node.value: return self._find_path(x, node.left) else: return self._find_path(x, node.right) def fit(self, X, y): self.root = self._build_tree(X, y) return self def predict(self, X): return np.array([self._find_path(x, self.root) for x in X])
就是这样! 你现在可以做所有你喜欢的关于 scikit-learn 的事情:
dt = DecisionTreeClassifier().fit(X, y)
print(dt.score(X, y)) # accuracy # Output
# 1.0
由于这棵树是非正则化的,它过度拟合了很多,因此得到了完美的训练分数。 看不见的数据的准确性会更差。 我们还可以通过以下方式检查树的外观
print(dt.root) # Output (prettified manually):
# Node(
# feature=1,
# value=-0.14963454032767076,
# left=Node(
# feature=0,
# value=0.04575851730144607,
# left=Node(
# feature=None,
# value=0,
# left=None,
# right=None
# ),
# right=Node(
# feature=None,
# value=1,
# left=None,
# right=None
# )
# ),
# right=Node(
# feature=None,
# value=0,
# left=None,
# right=None
# )
# )
作为图片,它会是这样的:
图片作者
在本文中,我们详细了解了决策树的工作原理。 我们从一些模糊但直观的想法开始,并将它们转化为公式和算法。 最后,我们能够从头开始实现决策树。
不过请注意:我们的决策树还不能正则化。 通常,我们想指定参数,如
- 最大深度
- 叶子大小
- 和最小的信息增益
在许多其他人中。 幸运的是,这些东西并不难实现,这对你来说是一个完美的家庭作业。 例如,如果您指定 leaf_size=10
作为参数,则包含超过 10 个样本的节点不应再拆分。 另外,这个实现是 效率不高. 通常,您不想将部分数据集存储在节点中,而只希望存储索引。 因此,您的(可能很大的)数据集仅在内存中出现一次。
好消息是您现在可以使用此决策树模板发疯了。 你可以:
- 实施对角线分割,即 xᵢ + 2xⱼ = 3 而不只是 xᵢ = 3,
- 改变叶子内部发生的逻辑,即你可以在每个叶子中运行逻辑回归,而不是仅仅进行多数表决,这会给你一个 线性树
- 更改拆分程序,即不要使用蛮力,而是尝试一些随机组合并选择最好的一个,这会给你一个 树外分类器
- 還有更多
罗伯特·库伯勒博士 是 Publicis Media 的数据科学家和 Towards Data Science 的作者。
原版。 经许可重新发布。
- SEO 支持的内容和 PR 分发。 今天得到放大。
- 柏拉图区块链。 Web3 元宇宙智能。 知识放大。 访问这里。
- Sumber: https://www.kdnuggets.com/2023/02/understanding-implementing-decision-tree.html?utm_source=rss&utm_medium=rss&utm_campaign=understanding-by-implementing-decision-tree
- 1
- 10
- 100
- 11
- 28
- 7
- 9
- a
- Able
- 关于
- 以上
- 绝对
- 高级
- 后
- 算法
- 所有类型
- 量
- 和
- 另一个
- 回答
- 出现
- 的途径
- 约
- 围绕
- 排列
- 刊文
- 作者
- 基地
- 基本上
- 因为
- before
- 作为
- 最佳
- 更好
- 提高
- 半身裙/裤
- 蛮力
- 建立
- 不能
- 关心
- 案件
- 例
- 一定
- 查
- 儿童
- 程
- 类
- 分类
- 码
- 颜色
- 组合
- 相当常见
- 兼容
- 完成
- 完全
- 计算
- 意识
- 继续
- 协调
- 相应
- 课程
- 创建信息图
- 关键
- data
- 数据科学
- 数据科学家
- 数据集
- 决定
- 决策树
- 默认
- 深度
- 细节
- 差异
- 不同
- 难
- 不会
- 做
- 每
- 最简单的
- 效果
- 分子
- 更多
- 整个
- 一样
- 醚(ETH)
- 甚至
- 例子
- 特快
- 延长
- 熟悉
- 时尚
- 专栏
- 特征
- 少数
- 最后
- 找到最适合您的地方
- 姓氏:
- 浮动
- 其次
- 以下
- 力
- 公式
- 止
- 进一步
- Gain增益
- 其他咨询
- 得到
- 基尼
- 给
- 特定
- 给
- Go
- 去
- 非常好
- 发生
- 此处
- 更高
- 最高
- 功课
- 创新中心
- HTML
- HTTPS
- 主意
- 思路
- 实施
- 履行
- 实施
- 进口
- 重要
- 不可能
- in
- 指数
- 信息
- 初始
- 原来
- 代替
- 有兴趣
- 直观的
- IT
- 掘金队
- 保持
- 类
- 知道
- 标签
- 标签
- 大
- 学习
- 长度
- 翻新
- 线
- 清单
- LOOKS
- 占地
- 爱
- 机
- 机器学习
- 多数
- 使
- 制作
- 制作
- 手动
- 许多
- 问题
- 手段
- 衡量
- 媒体
- 内存
- 介意
- 最小
- 杂
- 模型
- 更多
- 需求
- 需要
- 全新
- 下页
- 节点
- 节点
- 数
- 数字
- 麻木
- 一
- 秩序
- 原版的
- 其他名称
- 其它
- 参数
- 参数
- 部分
- 部分
- 允许
- 挑
- 图片
- 柏拉图
- 柏拉图数据智能
- 柏拉图数据
- 点
- 点
- 可能
- 在练习上
- 预测
- 预测
- 以前
- 市场问题
- 成熟
- 放
- 蟒蛇
- 随机
- 比
- 达到
- 递归
- 回归
- 代表
- 分别
- 成果
- 回报
- ROBERT
- 根
- 定位、竞价/采购和分析/优化数字媒体采购,但算法只不过是解决问题的操作和规则。
- 运行
- 科学
- 科学家
- scikit学习
- 搜索
- 自
- 感
- 分开
- 集
- Share
- 应该
- 简易
- 自
- 单
- 尺寸
- 小
- So
- 至今
- 解决
- 一些
- 分裂
- 拆分
- 标准
- 开始
- 步
- 停车
- 存储
- 商店
- 存储
- 这样
- 量身定制
- 说
- 模板
- 条款
- 信息
- 其
- 事
- 事
- 三
- 门槛
- 通过
- 时
- 至
- 一起
- 也有
- 最佳
- 合计
- 向
- 培训
- 树
- 转身
- 理解
- 理解
- us
- 使用
- 平时
- 折扣值
- 投票
- 这
- 而
- 将
- 中
- Word
- 话
- 工作
- 运作
- 合作
- 最差
- 将
- X
- XGBoost
- 您一站式解决方案
- 和风网
- 零