基尼不纯度与信息增益
您将计算样本划分的基尼不纯度和熵,并理解树为何选择能够最大化信息增益的划分
基尼不纯度与信息增益 是 CoddyKit 上的免费 Machine Learning Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Machine Learning Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Machine Learning Academy 课程共包含 4 节课。
划分问题:应该询问哪个特征?
构建决策树时,我们必须在每个节点选择能够产生最有用划分的特征和阈值。目标是创建尽可能纯的子节点——理想情况下,每个子节点只包含一个类别。我们需要一种数学上的不纯度度量,用来表示节点中的类别混合程度。不纯度越低越好:如果一个节点中的所有样本都属于同一类别,则其不纯度为零(完全纯)。两种广泛使用的不纯度度量是基尼不纯度和 entropy。
# Impurity measures how mixed the classes are in a node
# Perfect purity: all samples belong to one class -> impurity = 0
# Maximum impurity: classes are equally distributed
import numpy as np
# Node A: all class 0 -> pure
node_a = [0, 0, 0, 0] # impurity = 0
# Node B: 50/50 mix -> maximally impure
node_b = [0, 0, 1, 1] # impurity = maximum
# Node C: mostly one class
node_c = [0, 0, 0, 1] # impurity = low
for name, node in [('A', node_a), ('B', node_b), ('C', node_c)]:
print(f'Node {name}: classes = {node}')基尼不纯度:默认标准
基尼不纯度衡量这样一种概率:从节点中随机选择一个样本,并按照该节点中的类别分布随机为其标注类别时,标注错误的概率。公式为:Gini = 1 - sum(p_i^2),其中 p_i 表示类别 i 所占的比例。基尼不纯度的范围是 0(纯节点)到 0.5(两个类别等比例划分)。对于 K 个类别,其最大值为 1 - 1/K。基尼不纯度是 scikit-learn 的 DecisionTreeClassifier 的默认标准,因为它计算效率高(不需要计算对数)。
import numpy as np
def gini_impurity(y):
classes, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
return 1 - np.sum(probabilities ** 2)
# Pure node
print('Pure [0,0,0,0]:', gini_impurity([0,0,0,0])) # 0.0
# 50/50 split
print('50/50 [0,0,1,1]:', gini_impurity([0,0,1,1])) # 0.5
# 75/25 split
print('75/25 [0,0,0,1]:', gini_impurity([0,0,0,1])) # 0.375
# Three classes equal
print('3-class equal:', gini_impurity([0,1,2,0,1,2])) # ~0.667熵与信息论
entropy源自信息论,用于衡量一个分布的不确定性或信息量。公式为:H = -sum(p_i * log2(p_i))。纯节点的熵为 0(没有不确定性)。50/50 的划分熵为 1(一个比特的不确定性——您需要提出一个问题才能确定类别)。在实际应用中,entropy 和基尼不纯度生成的树非常相似。entropy 的计算速度稍慢(需要计算 log),但当类别分布偏斜时,可能会产生更好的划分。您可以在 scikit-learn 中使用 criterion='entropy' 进行切换。
import numpy as np
def entropy(y):
classes, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
# Avoid log(0) by filtering zero probabilities
probs = probabilities[probabilities > 0]
return -np.sum(probs * np.log2(probs))
print('Pure [0,0,0,0]:', entropy([0,0,0,0])) # 0.0
print('50/50 [0,0,1,1]:', entropy([0,0,1,1])) # 1.0 (1 bit)
print('75/25 [0,0,0,1]:', entropy([0,0,0,1]).round(3)) # 0.811
print('3-class equal:', entropy([0,1,2,0,1,2]).round(3)) # 1.585信息增益:划分质量指标
信息增益衡量一次划分使不纯度降低了多少。它的计算方式是:父节点的不纯度减去子节点的不纯度加权平均值:IG = impurity(parent) - (N_left/N * impurity(left) + N_right/N * impurity(right))。最佳划分会使信息增益最大化:它会生成尽可能纯的子节点,并根据子节点的大小进行加权(因此较大的子节点权重更高)。树构建器会评估每个特征和每个阈值的信息增益,然后选择增益最高的组合。
import numpy as np
def gini_impurity(y):
_, counts = np.unique(y, return_counts=True)
p = counts / len(y)
return 1 - np.sum(p**2)
def information_gain(y_parent, y_left, y_right):
n = len(y_parent)
n_l, n_r = len(y_left), len(y_right)
parent_impurity = gini_impurity(y_parent)
weighted_child = (n_l/n)*gini_impurity(y_left) + (n_r/n)*gini_impurity(y_right)
return parent_impurity - weighted_child
y_parent = [0,0,0,1,1,1] # 50/50 parent
y_left = [0,0,0] # pure left
y_right = [1,1,1] # pure right
print('IG:', information_gain(y_parent, y_left, y_right)) # 0.5 (perfect split)评估多个划分
为了找到最佳划分,算法会评估所有候选的特征—阈值组合,并选择信息增益最高的组合。对于包含 N 个样本和 d 个特征的数据集,每个特征最多需要评估 N-1 个阈值(相邻唯一值之间的中点),因此每个节点需要评估 O(N * d) 个划分。下面是一个简化示例,展示同一特征使用不同阈值时如何产生不同的信息增益。
import numpy as np
X_feature = np.array([1, 2, 3, 4, 5, 6])
y = np.array([0, 0, 0, 1, 1, 1])
best_threshold, best_ig = None, -1
for threshold in [1.5, 2.5, 3.5, 4.5, 5.5]:
left_mask = X_feature <= threshold
right_mask = ~left_mask
y_l, y_r = y[left_mask], y[right_mask]
_, cnt_p = np.unique(y, return_counts=True)
_, cnt_l = np.unique(y_l, return_counts=True) if len(y_l) else (None, [1])
_, cnt_r = np.unique(y_r, return_counts=True) if len(y_r) else (None, [1])
ig = information_gain(y, y_l, y_r)
print(f'Threshold {threshold}: IG = {ig:.3f}')
if ig > best_ig:
best_ig, best_threshold = ig, threshold
print('Best threshold:', best_threshold, 'with IG:', best_ig)基尼不纯度与熵:实际差异
大多数情况下,基尼不纯度和 entropy 生成的树几乎完全相同。两者的关键差异比较细微:entropy 往往会生成更平衡的树(由于对数的作用,它会更严厉地惩罚不平衡的划分),而基尼不纯度倾向于在一个分支中分离出出现频率最高的类别。在计算方面,基尼不纯度更快,因为它不需要计算对数。在实际应用中,二者之间的选择是一个需要调优的超参数——请使用交叉验证分别尝试二者,并选择在您的特定数据集上表现更好的标准。
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(return_X_y=True)
for criterion in ['gini', 'entropy']:
tree = DecisionTreeClassifier(criterion=criterion, max_depth=5, random_state=42)
score = cross_val_score(tree, X, y, cv=10).mean()
print(f'criterion={criterion}: CV accuracy = {score:.3f}')
# Usually within 0.5% of each other -- not the critical choice多类别问题中的加权基尼不纯度
基尼不纯度可以自然地扩展到多类别问题,无需任何修改:Gini = 1 - sum(p_i^2) 对任意数量的类别都适用。信息增益的计算方式也不变——子节点的不纯度加权值减去父节点不纯度。对于一个包含 3 个类别的问题,完全纯的叶节点(例如全部属于类别 2)的基尼不纯度为 0。一个 3 个类别比例相等的节点,其基尼不纯度最大,为 2/3。决策树是少数几种能够原生处理多类别问题而无需任何修改的算法之一——不同于逻辑回归,后者需要一对多或 softmax 扩展。
import numpy as np
def gini_multiclass(y):
_, counts = np.unique(y, return_counts=True)
p = counts / len(y)
return 1 - np.sum(p**2)
# 3-class examples
print('Pure [0,0,0]:', gini_multiclass([0,0,0])) # 0.0
print('Equal [0,1,2]:', gini_multiclass([0,1,2]).round(3)) # 0.667
print('2 dominant [0,0,1,2]:', gini_multiclass([0,0,1,2]).round(3)) # 0.625
# Max Gini for K classes = 1 - 1/K
for K in [2, 3, 4, 5]:
print(f'Max Gini for {K} classes: {1 - 1/K:.3f}')scikit-learn 中的不纯度降低
在 scikit-learn 内部,树会在每个节点存储划分前的不纯度以及每个子节点的不纯度。二者的差值按样本数量加权后,就是不纯度降低量(信息增益)。这个值会按特征汇总所有节点上的结果,并进行归一化,从而计算出 feature_importances_——即归因于每个特征的总不纯度降低量。出现在根节点附近且处理大量样本的特征往往具有最高的重要性,因为每次划分都会影响很大一部分数据。
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import numpy as np
X, y = load_iris(return_X_y=True)
tree = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)
tree.fit(X, y)
# Impurity at root and children
print('Root impurity (Gini):', tree.tree_.impurity[0].round(4))
print('Left child impurity:', tree.tree_.impurity[1].round(4))
print('Right child impurity:', tree.tree_.impurity[2].round(4))
# Feature importances = total weighted impurity reduction per feature
print('Feature importances:', tree.feature_importances_.round(3))min_impurity_decrease 的作用
默认情况下,只要不纯度有所降低且节点包含足够的样本,树就会继续划分节点。min_impurity_decrease 参数增加了一个最低阈值:只有当一次划分至少使不纯度降低该数值时,才会创建这次划分。这可以防止树创建只记住噪声的微小划分。将 min_impurity_decrease=0.01 设置为该值,意味着只有信息增益超过 0.01 时,树才会继续划分。这是直接控制树深度之外的一种有用正则化方法。
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(return_X_y=True)
for min_ig in [0.0, 0.001, 0.005, 0.01, 0.05]:
tree = DecisionTreeClassifier(
min_impurity_decrease=min_ig,
random_state=42
)
score = cross_val_score(tree, X, y, cv=5).mean()
depth = tree.fit(X, y).get_depth()
print(f'min_impurity_decrease={min_ig}: depth={depth}, CV acc={score:.3f}')比较不同特征上的划分
这是一个演示树如何选择最佳特征和阈值的完整示例。给定两个特征,并为每个特征提供一个划分阈值,树会计算所有选项的信息增益并选出最佳选项。这说明了树为什么能够自然地进行隐式特征选择:在任何阈值下都无法产生高信息增益的特征,永远不会被选为划分特征,实际上会被忽略。因此,与会受到无关特征影响的 KNN 不同,决策树能够较好地应对无关特征。
import numpy as np
# Toy dataset: X[:,0]=income, X[:,1]=age; y=churn
X = np.array([[100, 25], [120, 30], [40, 22], [50, 28], [90, 35], [30, 40]])
y = np.array([0, 0, 1, 1, 0, 1])
# Try splitting on income at 75
left_y = y[X[:, 0] <= 75] # [1,1,1]
right_y = y[X[:, 0] > 75] # [0,0,0]
ig_income = information_gain(y, left_y, right_y)
# Try splitting on age at 30
left_y2 = y[X[:, 1] <= 30] # [0,0,1,1]
right_y2 = y[X[:, 1] > 30] # [0,1]
ig_age = information_gain(y, left_y2, right_y2)
print(f'IG(income<=75): {ig_income:.3f}')
print(f'IG(age<=30): {ig_age:.3f}')
print('Best split:', 'income' if ig_income > ig_age else 'age')在 GridSearchCV 中使用基尼不纯度
使用 GridSearchCV 调优决策树时,您可以将 criterion 参数(gini 与 entropy)加入参数网格,让交叉验证针对您的特定数据集选择更好的选项。还可以在同一次搜索中加入 max_depth、min_samples_split 和 min_samples_leaf。搜索 criterion 的额外计算成本极低——每种组合只增加两个配置;当类别分布高度偏斜或数据集中存在许多质量相近的划分时,这样做偶尔能带来显著改进。
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(return_X_y=True)
param_grid = {
'criterion': ['gini', 'entropy'],
'max_depth': [3, 5, 7, None],
'min_samples_leaf': [1, 5, 10]
}
grid = GridSearchCV(
DecisionTreeClassifier(random_state=42),
param_grid, cv=10, scoring='accuracy', n_jobs=-1
)
grid.fit(X, y)
print('Best criterion:', grid.best_params_['criterion'])
print('Best depth:', grid.best_params_['max_depth'])
print('Best CV accuracy:', grid.best_score_.round(4))快速检查
测试您对本课中 Python 机器学习概念的理解。
课程回顾
在本课中,您学习了:基尼不纯度衡量节点中的类别混合程度(公式:1 - sum(p_i^2));信息增益衡量一次划分使不纯度降低了多少(父节点不纯度减去子节点不纯度的加权值);以及树总会在所有特征和阈值中选择能够使信息增益最大化的划分。接下来,我们将探索如何控制树的深度以防止过拟合。
用 AI 导师学习 Python — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 30
- 课程
- 120
常见问题解答
「基尼不纯度与信息增益」课时是免费的吗?
是的 — 「基尼不纯度与信息增益」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Machine Learning Academy 课程的其余内容,请升级到 CoddyKit PRO。 Machine Learning Academy 课程共包含 4 节课。
「基尼不纯度与信息增益」这节课中我会学到什么?
您将计算样本划分的基尼不纯度和熵,并理解树为何选择能够最大化信息增益的划分 你通过在浏览器中直接运行的动手代码来练习 Machine Learning Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Machine Learning Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Machine Learning Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「基尼不纯度与信息增益」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Machine Learning Academy 课中编写并运行代码吗?
能。每节 Machine Learning Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 构建树:分裂、节点与叶节点
- 基尼不纯度与信息增益
- 控制树深度以防止过拟合
- 决策树的可视化与解读