关键词:机器学习、决策桩、Decision Stump、弱分类器、AdaBoost、手写代码、Python 决策桩、Java 决策桩、集成学习、基学习器
一句话答案:决策桩是仅含一个根节点和两个叶节点的决策树——它是最简单的弱分类器,却是 AdaBoost、GBDT 等强大集成算法的核心构建单元!
如果你在搜索:
那么,这篇文章就是为你写的——从单桩到强模型,一步不跳。
决策桩(Decision Stump) 是深度为 1 的决策树:
if (特征_i <= 阈值_t):
预测 = A
else:
预测 = B💡 虽然单个决策桩性能弱(甚至不如随机猜测),但多个桩的加权组合可逼近任意函数——这是 Boosting 的核心思想。
特性 | 决策桩 | 完整决策树(如CART) |
|---|---|---|
深度 | 1 | ≥2(通常更深) |
特征使用 | 仅1个特征 | 多个特征组合 |
规则复杂度 | 单条 if-else | 多层嵌套规则 |
拟合能力 | 弱(高偏差) | 强(可能过拟合) |
训练速度 | ⚡ 极快 | 较慢 |
主要用途 | 集成学习的基学习器 | 独立模型或集成组件 |
✅ 决策桩的价值不在单打独斗,而在“团结协作”。
x₁ | x₂ | y(真实标签) | 样本权重 w |
|---|---|---|---|
1 | 2 | +1 | 0.2 |
2 | 3 | -1 | 0.2 |
3 | 1 | -1 | 0.2 |
4 | 4 | +1 | 0.2 |
5 | 2 | +1 | 0.2 |
目标:找到最佳特征 + 最佳阈值,使加权分类误差最小。
排序 x₁: [1, 2, 3, 4, 5] → 候选阈值: 1.5, 2.5, 3.5, 4.5
排序 x₂: [1,2,2,3,4] → 候选阈值: 1.5, 2.5, 3.5
✅ 最优决策桩:
💡 在 AdaBoost 中,该桩将被赋予较高权重,因其表现优于随机。
import numpy as np
class DecisionStump:
def __init__(self):
self.feature_idx = None
self.threshold = None
self.polarity = 1 # 1 表示 <= 为正类,-1 表示 > 为正类
self.alpha = None # AdaBoost 权重
def fit(self, X, y, sample_weights):
n_samples, n_features = X.shape
min_error = float('inf')
# 尝试每个特征
for feat in range(n_features):
feature_vals = X[:, feat]
thresholds = np.unique(feature_vals)
# 尝试每个阈值
for t in thresholds:
# 两种极性:<= 为正 或 > 为正
for polarity in [1, -1]:
pred = np.ones(n_samples)
if polarity == 1:
pred[feature_vals <= t] = -1
else:
pred[feature_vals > t] = -1
error = np.sum(sample_weights[pred != y])
if error < min_error:
min_error = error
self.feature_idx = feat
self.threshold = t
self.polarity = polarity
# 避免除零
if min_error == 0:
min_error = 1e-10
elif min_error == 1:
min_error = 1 - 1e-10
# 计算桩的权重(用于AdaBoost)
self.alpha = 0.5 * np.log((1 - min_error) / min_error)
def predict(self, X):
n_samples = X.shape[0]
pred = np.ones(n_samples)
if self.polarity == 1:
pred[X[:, self.feature_idx] <= self.threshold] = -1
else:
pred[X[:, self.feature_idx] > self.threshold] = -1
return pred
# === AdaBoost with Decision Stumps ===
class AdaBoost:
def __init__(self, n_stumps=10):
self.n_stumps = n_stumps
self.stumps = []
def fit(self, X, y):
n_samples = X.shape[0]
weights = np.full(n_samples, 1 / n_samples)
for _ in range(self.n_stumps):
stump = DecisionStump()
stump.fit(X, y, weights)
pred = stump.predict(X)
# 更新样本权重
correctness = (pred == y)
weights *= np.exp(-stump.alpha * correctness.astype(int))
weights /= np.sum(weights) # 归一化
self.stumps.append(stump)
def predict(self, X):
stump_preds = np.array([stump.alpha * stump.predict(X) for stump in self.stumps])
return np.sign(np.sum(stump_preds, axis=0))
# 测试
X = np.array([[1,2], [2,3], [3,1], [4,4], [5,2]])
y = np.array([1, -1, -1, 1, 1]) # +1/-1
ada = AdaBoost(n_stumps=3)
ada.fit(X, y)
print("预测:", ada.predict(X)) # 应接近 [1, -1, -1, 1, 1]public class DecisionStump {
private int featureIndex;
private double threshold;
private int polarity; // 1 or -1
private double alpha;
public void fit(double[][] X, int[] y, double[] weights) {
int nSamples = X.length;
int nFeatures = X[0].length;
double minError = Double.MAX_VALUE;
for (int f = 0; f < nFeatures; f++) {
// 获取特征列并去重
java.util.Set<Double> uniqueVals = new java.util.HashSet<>();
for (double[] row : X) uniqueVals.add(row[f]);
Double[] thresholds = uniqueVals.toArray(new Double[0]);
for (double t : thresholds) {
for (int pol : new int[]{1, -1}) {
int[] pred = new int[nSamples];
java.util.Arrays.fill(pred, 1);
for (int i = 0; i < nSamples; i++) {
if ((pol == 1 && X[i][f] <= t) || (pol == -1 && X[i][f] > t)) {
pred[i] = -1;
}
}
double error = 0.0;
for (int i = 0; i < nSamples; i++) {
if (pred[i] != y[i]) error += weights[i];
}
if (error < minError) {
minError = error;
featureIndex = f;
threshold = t;
polarity = pol;
}
}
}
}
// 防止数值问题
if (minError == 0) minError = 1e-10;
if (minError == 1) minError = 1 - 1e-10;
alpha = 0.5 * Math.log((1 - minError) / minError);
}
public int[] predict(double[][] X) {
int n = X.length;
int[] pred = new int[n];
java.util.Arrays.fill(pred, 1);
for (int i = 0; i < n; i++) {
if ((polarity == 1 && X[i][featureIndex] <= threshold) ||
(polarity == -1 && X[i][featureIndex] > threshold)) {
pred[i] = -1;
}
}
return pred;
}
public double getAlpha() { return alpha; }
}💡 完整 AdaBoost 需额外封装样本权重更新逻辑。
🌰 实际中,100~1000 个决策桩组成的 AdaBoost 模型常胜过单棵深度树。
场景 | 是否推荐 |
|---|---|
作为 AdaBoost / SAMME 的基学习器 | ✅ 强烈推荐 |
需要极快训练速度的在线学习 | ✅ |
高维稀疏数据(如文本) | ✅(每个桩选一个词) |
独立使用做预测 | ❌(性能太弱) |
需要可解释规则 | ⚠️ 单桩可解释,但集成后不可解释 |
决策桩是大道至简的典范——它用最朴素的 if-else 规则,支撑起了 Boosting 革命。理解决策桩,是理解 AdaBoost、Gradient Boosting 乃至 XGBoost 的第一步。
记住:在机器学习中,弱学习器的智慧,在于它们懂得如何协作。
现在,你已经能:
相关链接
无论你是想写代码调用 API 的开发者,设计 AI 产品的 PM,评估技术路线的管理者,还是单纯好奇智能本质的思考者,这里都有值得你驻足的内容。 不追 hype,只讲逻辑;不谈玄学,专注可复现的认知。 让我们一起,在这场百年一遇的智能革命中,看得更清,走得更稳 https://cloud.tencent.com/developer/column/107314
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。