机器学习入门 12-4 基尼系数
Posted AI机器学习与深度学习算法
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机器学习入门 12-4 基尼系数相关的知识,希望对你有一定的参考价值。
前言
前几个小节介绍了以信息熵为指标对节点中的数据进行划分,从而构建决策树。作为指标的不只有信息熵还有本小节要介绍的基尼系数(Gini coefficient)。
什么是基尼系数?
基尼系数比信息熵要简单很多,基尼系数的计算公式如下所示。
对于一个系统来说一共有 类的信息,第 类信息所占比例为 。比如三个类别的鸢尾花数据集一共有 150 个样本,其中每个类别的样本个数都是 50,因此对于鸢尾花数据集来说, :
-
第一种鸢尾花所占比例为 ,即 ; -
第二种鸢尾花所占比例为 ,即 ; -
第三种鸢尾花所占比例为 ,即 ;
下面来举几个具体的小例子。
-
有一个三个类别的数据集 ,这三个类别所占的比例分别是 ,此时这个系统的基尼系数为:
-
有一个三个类别的数据集 ,这三个类别所占的比例分别是 ,此时这个系统的基尼系数为:
现在可以比较两组数据的基尼系数,数据集 的基尼系数要比数据集 的基尼系数大。「基尼系数越大说明系统不确定性越高(或越随机),基尼系数越小说明系统的不确定性越高(或越确定)。」
-
考虑更极端的情况,有一个三个类别的数据集 ,这三个类别所占的比例分别是 ,此时这个系统的基尼系数为:
此时的基尼系数为 0,通过计算基尼系数的公式了解了基尼系数为正数,因此 0 是基尼系数所能取到的最小值。基尼系数最小,说明不确定性越低,换句话说,系统是最确定的,因为所有数据都在第一个类别中,没有任何的不确定性。
「通过上面的例子可以看出,基尼系数和信息熵一样,都可以用来做数据不确定性度量的指标。」
两个类别的基尼系数
类比于信息熵,接下来使用两个类别的特例来看一下计算基尼系数公式的函数图像。对于两个类别来说,如果其中一个类别的比例为 的话,另外一个类别的比例一定为 ,此时基尼系数 为:
二次曲线 的函数图像如下所示。
最终绘制出来的曲线呈现抛物线的样子,并且曲线整体以 这个位置左右对称。换句话说,当 的时候,曲线取得最大值。对于两个类别的数据集,其中一个类别所占的比例是 ,另外一个类别所占的比例为 ,此时整个数据的基尼系数最大。因此整个数据集不确定性最高,因为此时的数据到底属于第一个类别还是属于第二个类别,各有 50% 的可能性。
「根据绘制出来的曲线可以看出无论 的值更小还是更大,整个数据集的基尼系数都在下降,这是因为无论 变小还是变大,数据都更加偏向某一类别,数据整体的不确定性变低 (确定性更高了),所以相对应的基尼系数变的更低了。」
此时绘制的基尼系数曲线假设系统中只有两个类别,如果系统中有三个类别的话,绘制出来的基尼系数函数就是一个立体的曲面。通过绘制两个类别的基尼系数曲线可以进一步理解为什么基尼系数可以替换信息熵做另一个不确定性的度量指标。
-
当系统中每一个类别都是等概率的时候,不确定性最高,此时计算出来的基尼系数值最大; -
当系统偏向于某一个类别,相当于有了一定程度的确定性,基尼系数会逐渐降低,直到系统整体都在某一个类别中 ,此时的基尼系数值最低为 0;
当然这个结论不仅适用于两个类别,同样可以拓展到多类别。
sklearn中基尼系数的决策树
回顾使用 sklearn 中封装好的决策树对鸢尾花数据集进行训练,通过绘制训练好的决策树的决策边界来更加直观的可视化在各个节点上划分的维度以及对应的阈值。
使用 sklearn.datasets 模块中的 load_iris 方法加载鸢尾花数据集,为了方便可视化,只选取鸢尾花数据集的最后两个特征。
In[1]: from sklearn import datasets
iris = datasets.load_iris()
# 只选取petal length和petal width两个特征
X = iris.data[:, 2:]
y = iris.target
有了数据集,可以在 sklearn.tree 中导入 DecisionTreeClassifier 决策树分类器。在实例化决策树分类器时,指定三个参数:
-
max_depth: 决策树的最大深度,深度越大,模型越容易过拟合; -
criterion: 决策树的划分标准,其中 entropy
为信息熵,gini
为基尼系数; -
random_state: 随机数种子,保证每次实验结果一致;
In[2]: from sklearn.tree import DecisionTreeClassifier
dt_clf = DecisionTreeClassifier(max_depth = 2, criterion = 'gini', random_state=42)
dt_clf.fit(X, y)
Out[2]: DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=2,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False, random_state=None,
splitter='best')
训练好了决策树,接下来可以使用 plot_decision_boundary 方法来绘制决策边界,与此同时将只包含后两个特征的原始数据集也绘制出来。
In[4]: import numpy as np
import matplotlib.pyplot as plt
# 绘制不规则决策边界
def plot_decision_boundary(model, axis):
'''
model: 训练好的模型
axis: 横纵坐标轴的范围,[x_start, x_end, y_start, y_end ]
'''
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1],
int((axis[1] - axis[0]) * 100)).reshape(-1, 1),
np.linspace(axis[2], axis[3],
int((axis[3] - axis[2]) * 100)).reshape(-1, 1)
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
In[5]: plot_decision_boundary(dt_clf, axis=[0.5, 7.5, 0, 3])
plt.scatter(X[y==0,0], X[y==0,1])
plt.scatter(X[y==1,0], X[y==1,1])
plt.scatter(X[y==2,0], X[y==2,1])
plt.show()
对于相同的两个特征的鸢尾花数据集,不论是使用信息熵还是使用基尼系数,两种不同的决策树划分指标最终绘制出来的决策边界是相同的。「基尼系数和信息熵最终得到的结果,通常情况下区分不大。」
模拟使用基尼系数划分
接下来将模拟使用基尼系数对鸢尾花数据集进行划分,并将划分结果与前面使用 sklearn 封装好的决策树的划分结果进行比对,看看两种划分结果是否一致?
-
创建 split(X, y, d, value) 函数,作用:按照特征维度 d 上的 value 值对数据集 (X, y) 进行划分
split 函数有四个参数:X 表示数据集的特征,y 表示数据集的类别标签,d 表示进行划分的特征维度,value 表示在特征维度 d 上进行划分的阈值。
In[6]: def split(X, y, d, value):
index_a = (X[:, d] <= value)
index_b = (X[:, d] > value)
return X[index_a], X[index_b], y[index_a], y[index_b]
「split 函数的任务并不是寻找特征维度 d 以及在特征维度 d 上的阈值 value,而是假设已经知道了划分的 d 和 value 值的情况下对数据集进行划分。」 划分的方式非常简单,将数据集中的每个样本点的第 d 个特征维度的值与阈值 value 进行比较,其中 index_a 变量是条件小于等于 value 值的布尔数组,而 index_b 变量是条件大于 value 值的布尔数组,最后通过 index_a 和 index_b 两个布尔数组将传入的数据集划分成两个部分,对应左右两个节点分支。
-
创建 gini(y) 函数,作用:根据类别标签计算基尼系数(在介绍使用信息熵寻找最优划分中此处为计算信息熵)
gini 函数有一个参数:y 表示数据集的类别标签。类别标签 y 中包含了数据集的具体类别,通过类别标签 y 可以看出数据集中一共有多少个类别以及每个类别所包含的样本数,知道了这些已知条件就可以计算基尼系数。
In[7]: from collections import Counter
from math import log
def gini(y):
counter = Counter(y)
res = 1.0
for num in counter.values():
p = num / len(y)
res -= p**2 # 计算基尼系数的公式
return res
在 gini 函数中使用 collections 包下的 Counter 模块可以将类别标签 y 转换为包含键值信息的数据对,其中键为具体的类别,而对应的值为具体类别的个数(比如1:50,类别1样本为50个)。接下来只需要迭代计算每一个类别所占的比例,就可以套用计算基尼系数的公式得到基尼系数的值。
-
创建 try_split(X, y) 函数,作用:根据数据集 (X, y) 寻找最优的基尼系数以及对应的特征维度 d 以及在特征维度 d 上的阈值 value
try_split 函数有一个参数:X 表示数据集的特征,y 表示数据集的类别标签。
In[7]: def try_split(X, y):
# 初始化最优基尼系数为正无穷
best_g = float('inf')
# 初始化最优维度以及对应阈值为-1
best_d, best_v = -1, -1
# 遍历样本中每一个特征维度
for d in range(X.shape[1]):
# 将所有样本点按照特征维度d的值进行排序
sorted_index = np.argsort(X[:,d])
# 遍历样本点
for i in range(1, len(X)):
# 相邻的两个数据在第d个特征维度上的特征值不相等
if X[sorted_index[i], d] != X[sorted_index[i-1], d]:
# 取两个数据在第d个维度上特征值的平均值作为候选阈值
value = (X[sorted_index[i], d] + X[sorted_index[i-1], d])/2
# 使用split函数按照特征维度d上的value值对数据集进行划分
X_l, X_r, y_l, y_r = split(X, y, d, value)
# 计算当前节点的基尼系数
p_l, p_r = len(X_l) / len(X), len(X_r) / len(X)
e = p_l * gini(y_l) + p_r * gini(y_r)
if e < best_g:
best_g, best_d, best_v = e, d, value
return best_g, best_d, best_v
try_split 函数是最为核心的部分。每一次尝试都是再找使得基尼系数最小的划分方式,因此定义 best_g 变量并将其初始化为正无穷。每次找到最小的基尼系数都会有一个对应的特征维度以及特征维度上的阈值,因此定义了最优的特征维度 best_d 以及特征维度上的阈值 best_v,并都将其初始化为 -1。
接下来的事情比较简单,本质就是遍历样本中的所有特征维度 (X.shape[1]),对于每一个特征维度都需要确定划分的阈值。可选的阈值为每两个样本点在 d 这个特征维度上的平均值,所以对所有样本点在 d 这个特征维度上进行排序。使用 argsort 对样本点进行排序,argsort 函数返回排序后的索引。
遍历每一个样本点,此时开始遍历从 1 开始而不是从 0 开始,因为每次找的都是 i-1 和 i 这两个样本点在特征维度 d 上的平均值 (当然也可以从0开始,则改为range(0, len(X) - 1))。「如果 X[sorted_index[i], d] 和 X[sorted_index[i-1], d] 这两个值不相等的话,就可以将候选阈值 value 定义为相邻的两个样本点在特征维度 d 上的平均值。」
使用 split 函数按照特征维度 d 以及候选的阈值 value 对数据集进行划分,返回 (X_l, y_l) 和 (X_r, y_r) 两个部分。分别计算两个部分的基尼系数,并进行累加作为当前节点的基尼系数。
经过双重循环之后,找到了让基尼系数减低的划分方式。最优的基尼系数为 best_g,对应划分的特征维度为 best_d,特征维度下的阈值为 best_v。
完成了 try_split 这个核心函数,接下来可以将整个鸢尾花数据集传入到 try_split 函数中,try_split 函数会返回结果存入 best_g、best_d 和 best_v 三个变量中。
In[8]: best_g, best_d, best_v = try_split(X, y)
print("best_g =", best_g)
print("best_d =", best_d)
print("best_v =", best_v)
Out[8]: best_g = 0.3333333333333333
best_d = 0
best_v = 2.45
「对整个鸢尾花数据集的最佳划分位置在第 0 个特征维度上且阈值为 2.45,划分后的基尼系数变成了 0.3 左右,与前面使用 sklearn 封装好的决策树的划分结果一致。」
有了基尼系数最小时的特征维度 best_d 以及对应特征维度上的阈值 best_v,接下来就可以调用 split 函数将全部鸢尾花数据集按照 best_d 以及 best_v 划分为两个部分。
In[9]: X1_l, X1_r, y1_l, y1_r = split(X, y, best_d, best_v)
(X1_l, y1_l) 为在 best_d 特征维度上小于等于 best_v 的数据集合,为了方便将其称为左分支。(X1_r, y1_r) 为在 best_d 特征维度上大于 best_v 的数据集合,为了方便将其称为右分支。
调用 gini 函数计算左右两个分支上数据的基尼系数。
In[10]: print("划分为左部分的数据的基尼系数:",
entropy(y1_l))
Out[10]: 划分为左部分的数据的基尼系数: 0.0
In[11]: print("划分为右部分的数据的基尼系数:",
entropy(y1_r))
Out[11]: 划分为右部分的数据的基尼系数: 0.5
左分支的基尼系数为 0.0,这是因为划分后的左分支中包含同一类别的全部数据(sklearn中绘制决策树的决策边界中蓝色的样本点),因此不需要继续进行划分。右分支的基尼系数为 0.5,基尼系数的值比较大,并且还没有达到划分最大的深度,可以继续进行划分。
由于右分支的基尼系数为 0.5,因此可以继续进行划分,将右分支的全部数据传入 try_split 函数中,try_split 函数返回结果存入 best_g2、best_d2 和 best_v2 三个变量中。
In[12]: best_g2, best_d2, best_v2 = try_split(X1_r, y1_r)
print("best_g =", best_g2)
print("best_d =", best_d2)
print("best_v =", best_v2)
Out[12]: best_g = 0.1103059581320451
best_d = 1
best_v = 1.75
「对右分支的所有数据的最佳划分位置在第 1 个特征维度上且阈值为 1.75,划分后的基尼系数变成了 0.11 左右,与前面使用 sklearn 封装好的决策树的划分结果一致。」
有了基尼系数最小时的特征维度 best_d2 以及对应特征维度上的阈值 best_v2,接下来就可以调用 split 函数将右分支的全部数据按照 best_d2 以及 best_v2 划分为两个部分。
In[13]: X2_l, X2_r, y2_l, y2_r = split(X1_r, y1_r, best_d2, best_v2)
(X2_l, y2_l) 为在 best_d2 特征维度上小于等于 best_v2 的数据集合,为了方便将其称为右-左分支。(X2_r, y2_r) 为在 best_d2 特征维度上大于 best_v2 的数据集合,为了方便将其称为右-右分支。
调用 gini 函数计算左右两个分支上数据的基尼系数。
In[14]: print("划分为左部分的数据的基尼系数:",
gini(y2_l))
Out[14]: 划分为左部分的数据的基尼系数:0.1680384087791495
In[15]: print("划分为右部分的数据的基尼系数:",
gini(y2_r))
Out[15]: 划分为右部分的数据的基尼系数:0.04253308128544431
右-左分支的基尼系数大概为 0.16,右-右分支的基尼系数大概为 0.04。第二次对右分支的全部数据集划分得到的右-左分支和右-右分支上的基尼系数都没有降到 0,因此还可以继续进行划分,但是为了和前面 sklearn 中使用决策树时指定的 max_depth 最大深度为 2 一致,这里不再继续进行划分。
信息熵 VS 基尼系数
在决策树的构建过程中,对于每个节点中数据的划分指标不仅有信息熵 (entropy) 还有基尼系数 (gini)。接下来对这两种指标进行对比。
-
信息熵的计算比基尼系数稍慢。
-
这是因为计算信息熵需要计算非线性的 log 函数,而计算基尼系数只需要计算一个数值的平方; -
这也是为什么在 scikit-learn 中默认使用基尼系数的原因; -
大多数时候二者没有特别的效果优劣。
-
虽然信息熵的计算比基尼系数稍慢,但是在大多数情况下两种方式在效果上没有特别大的优劣之分。换句话说,信息熵得到的效果特别好而基尼系数得到的效果特别差这种情况很少出现; -
通过两种方式绘制的函数图像也可以看出,两个函数图像非常相似,最大的区别在于函数图像的高度不同。严格意义上来说,信息熵的函数图像不是一个抛物线,但是整体上它和基尼系数的二次抛物线的函数图像拥有一模一样的性质;
「大多数情况下,使用信息熵还是使用基尼系数对改进模型的贡献非常小的。」 下一小节会介绍对于决策树来说更重要的参数,这些参数更能够决定模型效果的优劣。
References:
Python3入门机器学习 经典算法与应用: https://coding.imooc.com/class/chapter/169.html#Anchor
以上是关于机器学习入门 12-4 基尼系数的主要内容,如果未能解决你的问题,请参考以下文章
《自然语言处理实战入门》 ---- 笔试面试题:机器学习基础(41-60)
Spark学习10_1 sparkMllib入门与相关资料索引