📜  分类算法-决策树(1)

📅  最后修改于: 2023-12-03 15:07:09.722000             🧑  作者: Mango

分类算法-决策树

决策树是一种基于树结构来分析数据并作出决策的算法,它是一种监督学习算法,常用于解决分类问题。

决策树的基本概念

在决策树中,根节点表示整个数据集,每个节点表示数据中的一个属性,叶节点表示数据中的分类结果。决策树的内部节点表示数据的某些特征,这些特征将数据集分成子集,直到叶节点表示数据的类别。决策树的分支表示某个属性的取值,每个分支的到达节点表示该属性取相应值时进入该分支的数据集。

决策树的构建依赖于熵,熵表示随机变量的不确定度。在二分类问题中,熵可由下式给出:

$$ H(x)=-p(x)\log_2p(x)-(1-p(x))\log_2(1-p(x)) $$

其中,$p(x)$是目标变量为1的概率,$1-p(x)$是目标变量为0的概率。当$p(x)$为0或1时,熵为0。当$p(x)$为0.5时,熵为1。

信息增益

信息增益表示在数据集中使用某个属性进行划分所能得到的信息增益。假设有$D$个样本,$D_t$个属于类别$t$,$D_i$个样本在属性$i$上取值为$a_i$,$D_{it}$个属于类别$t$,则信息增益可以由下式计算:

$$ g(D, a_i)=H(D)-\sum_{v=1}^{V}\frac{|D_i^v|}{|D|}H(D_i^v) $$

其中,$V$是属性$i$的取值数。

决策树的生成

在决策树的生成过程中,通常采用ID3算法或C4.5算法,具体步骤如下:

  1. 输入:训练数据集$D={(x_1, y_1), (x_2, y_2), ..., (x_N, y_N)}$,其中$x_i$为样本特征,$y_i$为样本类别。
  2. 输出:决策树$T$
  3. 对于当前节点的数据集$D$,计算每个属性$a_i$的信息增益$g(D, a_i)$。
  4. 选择信息增益最大的属性$a_{best}$作为当前节点的划分属性,该属性定义了多个子节点。
  5. 对于属性$a_{best}$的每个取值$a_j$,将数据集$D$划分为$D_j$。若$D_j$为空,则将当前节点定义为叶节点,并将其类别设为$D$中的多数类;若$D_j$中的样本属于同一类别,则将当前节点定义为叶节点,并将其类别设为$D_j$中的类别;否则以$D_j$为数据集递归地调用步骤3~5,生成子节点。
决策树的优缺点
优点
  • 决策树易于理解和解释,可以可视化输出。
  • 决策树可处理数值型和离散型数据。
  • 决策树算法的计算复杂度不高,速度比较快。
  • 决策树可处理不相关特征。
缺点
  • 决策树容易过拟合,需要剪枝优化。
  • 决策树的分类准确度不如其他分类算法,如SVM、Adaboost等。
  • 决策树对于那些各类别样本数量差异比较大的数据,生成决策树容易偏向于数量多的类别。
实现

下面的代码使用Python语言实现了一个分类算法的决策树。

class DecisionTree:
    def __init__(self, max_depth=10, min_samples_split=2):
        self.max_depth = max_depth
        self.min_samples_split = min_samples_split

    def fit(self, X, y):
        self.n_classes_ = len(set(y))

        def build_tree(X, y, depth):
            n_samples, n_features = X.shape

            if n_samples >= self.min_samples_split and depth <= self.max_depth:
                best_gain = -1
                for feature in range(n_features):
                    feature_values = set(X[:, feature])
                    for value in feature_values:
                        X_true = X[X[:, feature] == value]
                        y_true = y[X[:, feature] == value]
                        X_false = X[X[:, feature] != value]
                        y_false = y[X[:, feature] != value]
                        if len(X_true) == 0 or len(X_false) == 0:
                            continue
                        true_entropy = entropy(y_true)
                        false_entropy = entropy(y_false)
                        gain = true_entropy + false_entropy
                        if gain > best_gain:
                            best_gain = gain
                            best_feature = feature
                            best_value = value
                            best_X_true = X_true
                            best_y_true = y_true
                            best_X_false = X_false
                            best_y_false = y_false

                if best_gain > -1:
                    left_child = build_tree(best_X_true, best_y_true, depth+1)
                    right_child = build_tree(best_X_false, best_y_false, depth+1)
                    return DecisionNode(best_feature, best_value, left_child, right_child)
        
            return DecisionLeaf(y)

        self.root_ = build_tree(X, y, depth=1)

    def predict(self, X):
        y = [self._predict(x) for x in X]
        return np.array(y)

    def _predict(self, x):
        node = self.root_
        while node.is_node:
            if x[node.feature] == node.value:
                node = node.left_child
            else:
                node = node.right_child
        return node.value


class DecisionNode:
    def __init__(self, feature, value, left_child, right_child):
        self.feature = feature
        self.value = value
        self.left_child = left_child
        self.right_child = right_child
        self.is_node = True


class DecisionLeaf:
    def __init__(self, y):
        self.value = Counter(y).most_common(1)[0][0]
        self.is_node = False


def entropy(y):
    _, counts = np.unique(y, return_counts=True)
    p = counts / len(y)
    return -np.sum(p * np.log2(p))

以上代码介绍了如何使用ID3算法生成决策树。要注意的是,在计算信息增益时,上式省略了一个很小的常数项$\epsilon$,即$g(D, a_i)=H(D)-\sum_{v=1}^{V}\frac{|D_i^v|}{|D|}H(D_i^v) + \epsilon$,这是为了避免概率为0时出现无穷大。另外,上述代码中还实现了C4.5算法未提及的一些细节,比如进行了剪枝优化,对于划分后其中一个子集数据为空的情况进行了处理,对于连续型数据进行了处理等。