匠心精神 - 良心品质腾讯认可的专业机构-IT人的高薪实战学院

咨询电话:4000806560

Python实现机器学习算法:决策树原理与代码实现

Python实现机器学习算法:决策树原理与代码实现

在机器学习中,决策树是一种非常重要的算法。它可以用于分类和回归问题,常用于数据挖掘和统计分析。本文将介绍决策树的原理和代码实现。

一、决策树原理

决策树是一种树形结构,其中每个节点代表一个特征,每个分支代表一个特征的取值,每个叶子节点代表一个类别。其基本原理是将训练集中的数据依据特征进行分割,使得同一类别的数据尽可能聚集在一起,不同类别的数据尽可能分开。我们通过计算每个特征的信息熵的变化,来选择最优特征进行分割。

决策树的构建遵循以下过程:

1. 根据训练集选择最优特征进行分割
2. 将不同分支的样本分别划分到不同节点
3. 对每个节点重复步骤 1 和 2,直到所有分支都是同一类别或无法继续分割为止

例如,我们有以下训练集:

| 编号 | 年龄 | 收入 | 是否有房 | 是否结婚 | 类别 |
| --- | --- | --- | --- | --- | --- |
| 1 | 青年 | 高 | 否 | 否 | 否 |
| 2 | 青年 | 高 | 否 | 否 | 否 |
| 3 | 中年 | 高 | 否 | 是 | 否 |
| 4 | 老年 | 中 | 否 | 是 | 是 |
| 5 | 老年 | 低 | 是 | 是 | 是 |
| 6 | 中年 | 低 | 是 | 是 | 是 |
| 7 | 青年 | 中 | 否 | 否 | 否 |
| 8 | 青年 | 低 | 是 | 是 | 是 |
| 9 | 老年 | 低 | 是 | 是 | 是 |
| 10 | 青年 | 中 | 是 | 否 | 是 |

我们希望通过这个训练集来构建一个决策树。首先,我们需要选择一个最优特征进行分割。我们可以通过计算信息增益来选择最优特征。信息增益表示的是分割前后样本熵的变化。

计算公式如下:

信息增益 = 父节点熵 - 权重和 * 子节点熵

其中,父节点熵表示整个训练集的熵,可以通过以下公式计算:

熵 = -p1 * log2 p1 - p2 * log2 p2 - ... - pn * log2 pn

其中,p1~pn 表示不同类别的样本占比。

子节点熵表示将样本按照某个特征值分割后,每个子节点中的样本熵加权平均值。计算公式如下:

子节点熵 = 权重1 * 熵1 + 权重2 * 熵2 + ... + 权重n * 熵n

其中,权重表示每个子节点所占的比例,熵表示子节点中不同类别的样本熵加权平均值。

对于我们的训练集,我们可以通过计算信息增益来选择最优特征进行分割。首先,我们计算整个训练集的熵。根据上面的公式,我们可以计算出:

熵 = -6/10 * log2 6/10 - 4/10 * log2 4/10 = 0.971

接下来,我们依次计算每个特征的信息增益。例如,我们可以计算年龄特征的信息增益。首先,我们将年龄分为青年、中年和老年三个取值。对于青年这个取值,我们可以将数据分为以下两组:

| 编号 | 年龄 | 收入 | 是否有房 | 是否结婚 | 类别 |
| --- | --- | --- | --- | --- | --- |
| 1 | 青年 | 高 | 否 | 否 | 否 |
| 2 | 青年 | 高 | 否 | 否 | 否 |
| 7 | 青年 | 中 | 否 | 否 | 否 |
| 8 | 青年 | 低 | 是 | 是 | 是 |
| 10 | 青年 | 中 | 是 | 否 | 是 |

我们可以计算出该分支的熵为:

熵 = -2/5 * log2 2/5 - 3/5 * log2 3/5 = 0.971

对于中年和老年这两个取值,我们可以类似地计算出它们的熵。然后,我们可以计算出年龄这个特征的信息增益:

信息增益 = 0.971 - 5/10 * 0.971 - 4/10 * 0.811 = 0.124

接下来,我们计算其他特征的信息增益,选择最大的一个作为最优特征进行分割。最终,我们得到以下决策树:

![decision tree](https://i.imgur.com/VOsH9wW.png)

二、决策树代码实现

决策树的代码实现包括以下几个步骤:

1. 定义节点类
2. 定义树类
3. 定义递归函数构建决策树
4. 定义预测函数

以下是 Python 实现决策树的代码:

```python
import math
from collections import Counter

class Node:
    def __init__(self, feature, split, left, right, label):
        self.feature = feature
        self.split = split
        self.left = left
        self.right = right
        self.label = label

class Tree:
    def __init__(self):
        self.root = None

    def build(self, X, y):
        self.root = self._build(X, y)

    def _build(self, X, y):
        if len(set(y)) == 1:
            return Node(None, None, None, None, y[0])

        n_samples, n_features = X.shape
        best_feature = None
        best_split = None
        current_score = self.score(y)

        for i in range(n_features):
            splits = set(X[:, i])
            for split in splits:
                y_left = y[X[:, i] < split]
                y_right = y[X[:, i] >= split]
                if len(y_left) == 0 or len(y_right) == 0:
                    continue
                score = current_score - len(y_left) / n_samples * self.score(y_left) - len(y_right) / n_samples * self.score(y_right)
                if score > current_score:
                    current_score = score
                    best_feature = i
                    best_split = split

        if best_feature is None:
            return Node(None, None, None, None, Counter(y).most_common(1)[0][0])

        X_left = X[X[:, best_feature] < best_split]
        y_left = y[X[:, best_feature] < best_split]
        left = self._build(X_left, y_left)

        X_right = X[X[:, best_feature] >= best_split]
        y_right = y[X[:, best_feature] >= best_split]
        right = self._build(X_right, y_right)

        return Node(best_feature, best_split, left, right, None)

    def score(self, y):
        n_samples = len(y)
        if n_samples == 0:
            return 0
        counts = Counter(y)
        p = [count / n_samples for count in counts.values()]
        return -sum([pi * math.log(pi, 2) for pi in p])

    def predict(self, X):
        return [self.predict_one(x, self.root) for x in X]

    def predict_one(self, x, node):
        if node.label is not None:
            return node.label
        if x[node.feature] < node.split:
            return self.predict_one(x, node.left)
        else:
            return self.predict_one(x, node.right)
```

以上代码定义了一个节点类和一个树类,其中节点类保存了该节点代表的特征、分割点、左子树、右子树和分类标签,树类保存了根节点。`build` 方法递归地构建决策树,`score` 方法计算熵,`predict` 方法预测多个样本的分类,`predict_one` 方法预测单个样本的分类。

我们可以使用以下代码进行测试:

```python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)

tree = Tree()
tree.build(X_train, y_train)

y_pred = tree.predict(X_test)

accuracy = accuracy_score(y_test, y_pred)

print('Accuracy:', accuracy)
```

以上代码从 sklearn 中加载 Iris 数据集,将其拆分为训练集和测试集,构建决策树,并输出准确率。