You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

决策树训练报错:空序列argmax与递归深度超限问题排查

自定义决策树模型错误修复方案

错误原因分析

1. ValueError: attempt to get argmax of an empty sequence

递归构建树时,某个分支的样本标签数组y为空,调用np.bincount(y)会返回空数组,再执行np.argmax()就触发这个错误。这种情况通常发生在分割后某一边没有样本,却依然递归进入该分支。

2. RecursionError: maximum recursion depth exceeded

你修改停止条件时添加了np.size(y) != 0的判断,导致当y为空时,停止条件不满足,代码继续执行后续的分割逻辑。但空样本无法产生有效分割,代码会反复进入无效递归,最终触发递归深度超限。

修复步骤

  1. 移除错误的停止条件限制:恢复原有的if self._is_finished(depth)判断,不要添加np.size(y) != 0,避免绕过正常停止逻辑。
  2. 提前处理空样本分支:在递归创建子节点前,检查分割后的左右样本索引是否为空。如果为空,直接创建叶子节点(使用当前节点样本的多数类),不继续递归空分支。
  3. 添加最佳分割的兜底处理:当所有可能的分割都无法获得有效信息增益时,直接返回叶子节点,避免无效递归。
  4. 空样本的停止条件兜底:在_build_tree开头添加空y的判断,提前返回叶子节点,避免后续代码报错。

修复后的完整代码

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split

class Node:
    def __init__(self, feature=None, threshold=None, left=None, right=None, *, value=None):
        self.feature = feature
        self.threshold = threshold
        self.left = left
        self.right = right
        self.value = value        
        
class decisionTree:
    def __init__(self, max_depth=100, min_samples_per_split=2):
        self.max_depth = max_depth
        self.min_samples_per_split = min_samples_per_split
        self.root = None
        
    def _is_finished(self, depth):
        if (depth >= self.max_depth
            or self.n_class_labels == 1
            or self.n_samples < self.min_samples_per_split):
            return True
        return False
    
    def _create_split(self, X, thresh):
        left_idx = np.argwhere(X <= thresh).flatten()
        right_idx = np.argwhere(X > thresh).flatten()
        return left_idx, right_idx
    
    def _information_gain(self, X, y, thresh):
        parent_loss = self._entropy(y)
        left_idx, right_idx = self._create_split(X, thresh)
        n, n_left, n_right = len(y), len(left_idx), len(right_idx)
        
        if n_left == 0 or n_right == 0:
            return 0
        
        child_loss = (n_left / n) * self._entropy(y[left_idx]) + (n_right / n) * self._entropy(y[right_idx])
        return parent_loss - child_loss
    
    def _best_split(self, X, y, features):
        split = {'score':-1, 'feat': None, 'thresh': None}
        for feat in features:
            X_feat = X[:, feat]
            thresholds = np.unique(X_feat)
            for thresh in thresholds:
                score = self._information_gain(X_feat, y, thresh)
                if score > split['score']:
                    split['score'] = score
                    split['feat'] = feat
                    split['thresh'] = thresh
                    
        return split['feat'], split['thresh']
    
    def _entropy(self, y):
        if len(y) == 0:
            return 0
        proportions = np.bincount(y) / len(y)
        entropy = -np.sum([p * np.log2(p) for p in proportions if p > 0])
        return entropy
    
    def _build_tree(self, X, y, depth=0):
        self.n_samples, self.n_features = X.shape
        self.n_class_labels = len(np.unique(y)) if len(y) > 0 else 0

        # 兜底处理空样本
        if len(y) == 0:
            return Node(value=0)  # 可根据实际训练集多数类调整,这里默认用0
        
        # 正常停止条件
        if self._is_finished(depth):
            most_common_Label = np.argmax(np.bincount(y))
            return Node(value=most_common_Label)

        # 获取最佳分割
        rnd_feats = np.random.choice(self.n_features, self.n_features, replace=False)
        best_feat, best_thresh = self._best_split(X, y, rnd_feats)

        # 兜底:无有效分割时直接返回叶子节点
        if best_feat is None:
            most_common_Label = np.argmax(np.bincount(y))
            return Node(value=most_common_Label)

        # 分割样本并递归构建子树
        left_idx, right_idx = self._create_split(X[:, best_feat], best_thresh)
        
        # 处理空左分支
        if len(left_idx) == 0:
            left_child = Node(value=np.argmax(np.bincount(y)))
        else:
            left_child = self._build_tree(X[left_idx, :], y[left_idx], depth + 1)
        
        # 处理空右分支
        if len(right_idx) == 0:
            right_child = Node(value=np.argmax(np.bincount(y)))
        else:
            right_child = self._build_tree(X[right_idx, :], y[right_idx], depth + 1)
        
        return Node(best_feat, best_thresh, left_child, right_child)

    def fit(self, x, y):
        self.root = self._build_tree(x, y)
        
def main():
    data = datasets.load_breast_cancer()
    x, y = data.data, data.target

    x_train, x_test, y_train, y_test = train_test_split(
        x, y, test_size=0.2, random_state=1
    )
    clf = decisionTree(max_depth=10)
    clf.fit(x_train, y_train)
    print("模型训练完成")
    
if __name__ == "__main__":
    main()

验证说明

运行修复后的代码,会输出模型训练完成,说明递归正常终止,空样本分支也被正确处理,两个错误都已解决。

内容的提问来源于stack exchange,提问作者a certain wanderer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:54:50