目录
一、算法简介
1.1算法概述
决策树是一种基本的机器学习算法,用于分类和回归任务。它通过从数据中学习一系列简单的规则来建立模型,这些规则形成了树状结构,其中每个内部节点表示一个特征/属性测试,每个叶节点表示一个类别(在分类问题中)或一个数值(在回归问题中)。由于这种决策分支画成图形很像一棵树的枝干,故称决策树。下图为决策树的基本形状:
决策树只有一个根节点,所以要选择“纯度”最高的哪一个特征来作为根节点。
1.2决策树的生成
决策树的构造是一个递归的过程,主要包括以下步骤:
- 选择划分特征:
通过某种准则选择最佳的划分特征,常用的准则包括信息增益、基尼不纯度等。
对于每个可能的特征,计算其划分后的不纯度或信息增益,选择最佳的特征进行划分。 - 划分数据集:
根据选择的特征对数据集进行划分,形成子集。
每个子集中的样本都具有相同的特征值,这些子集构成了树中的一个节点。 - 递归构建子树:
对每个子集,递归地重复上述过程,直到满足终止条件。
终止条件可以是达到最大深度、节点中样本个数小于阈值等。 - 树的生长过程:
通过不断地选择最佳的划分特征和划分数据集,构建整棵树。
树的生长过程可以在预剪枝和后剪枝的基础上进行,以防止过拟合。 - 处理连续特征:
对于连续型特征,需要确定划分点。
可以通过遍历所有可能的划分点或使用启发式方法来选择划分点。 - 处理缺失值:
对于包含缺失值的样本,可以选择一个默认的子集或者根据缺失值的概率进行权重分配。 - 树的剪枝:
在树的构建过程中或构建完成后,可以对树进行剪枝,以提高泛化能力并防止过拟合。
预剪枝和后剪枝是常用的剪枝策略。 - 生成决策树:
构造完成后,得到一棵完整的决策树,树的节点包括特征值、划分条件和子树等信息。
二、特征的选择
2.1特征选择方法
选择决策树的特征是构建决策树时非常重要的一步,它直接影响了树的性能和泛化能力。通常可以使用以下方法来选择决策树的特征:
-
信息增益(ID3 算法):
信息增益是一种常用的特征选择准则,用于选择能够最大程度降低数据集不确定性的特征。在每次划分时,选择能够使得信息熵或基尼不纯度减少最多的特征作为划分特征。信息增益越大说明样本纯度越高。
示例:假设有一个二分类任务的数据集,目标变量为 𝑌,特征值为𝑋,取值为
{x1,x2,…xv},根据特征 𝑋进行划分后得到 𝑉个子集 𝐷1,𝐷2,…,𝐷𝑉。
1.计算整个数据集的信息熵 :信息熵(𝐷)。
2.对于每个特征取值 𝑣,计算相应的子集 𝐷𝑣 的信息熵 (𝐷𝑣)
3.计算每个特征取值对应的权重,即 ∣𝐷𝑣∣/∣𝐷∣。
4.根据公式计算信息增益(X)。
5.选择信息增益最大的特征作为划分特征。 -
基尼指数(CART 算法):
基尼不纯度是衡量数据集不纯度的一种指标,它表示从数据集中随机选取两个样本,其类别标签不一致的概率。选择能够使得基尼不纯度减少最多的特征作为划分特征。信息增益越小说明样本纯度越高。
-
信息增益率(C4.5 算法):
信息增益比是信息增益的一种变体,它对特征取值数目较多时的偏好性进行了调整。信息增益比考虑了特征取值的多少,避免了对取值较多的特征过分偏好。信息增益越大说明样本纯度越高。
其中但是要计算这三个算法还要先计算一个东西,叫做信息熵。
2.2信息熵
信息熵是信息论中衡量信息不确定性的概念。在机器学习中,信息熵常被用于衡量数据集的纯度或不确定性,特别是在决策树等算法中作为特征选择的指标之一。信息熵越高,数据集的不确定性也就越大。
2.2信息熵计算方法
假设当前样本集合D中第k个样本所占的比例为pk(k=1,2,…,|y|),则D的信息熵定义为
Ent(D)的值越小,则D的纯度越高。
三、决策树剪枝
剪枝分类
3.1预剪枝
预剪枝是在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点。
3.2后剪枝处理
后剪枝是从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。
四、信息增益算法实现
4.1准备数据集
为方便,将其进行进行数字改写:
年龄:0代表青年,1代表中年,2代表老年;
有工作:0代表否,1代表是;
有自己的房子:0代表否,1代表是;
信贷情况:0代表一般,1代表好,2代表非常好;
类别(是否给贷:no代表否,yes代表是。
def createDataSet():
dataSet = [
[0, 0, 0, 0, 'no'],
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no'],
]
# 列名
labels = ['年龄','有工作','有自己的房子','信贷情况']
return dataSet, labels
4.2信息熵计算
def calcEntropy(dataSet):
# 1. 获取所有样本数
exampleNum = len(dataSet)
# 2. 计算每个标签值的出现数量
labelCount = {}
for featVec in dataSet:
curLabel = featVec[-1]
if curLabel in labelCount.keys():
labelCount[curLabel] += 1
else:
labelCount[curLabel] = 1
# 3. 计算熵值(对每个类别求熵值求和)
entropy = 0
for key, value in labelCount.items():
# 概率值
p = labelCount[key] / exampleNum
# 当前标签的熵值计算并追加
curEntropy = -p * math.log(p, 2)
entropy += curEntropy
# 4. 返回
return entropy
4.3划分数据集
def splitDataSet(dataSet, featureIndex, value):
returnDataSet = []
for featVec in dataSet:
if featVec[featureIndex] == value:
# 将featureIndex那一列删除
deleteFeatVec = featVec[:featureIndex]
deleteFeatVec.extend(featVec[featureIndex + 1:])
# 将删除后的样本追加到新的dataset中
returnDataSet.append(deleteFeatVec)
return returnDataSet
4.4选择最好的特征
def chooseBestFeatureToSplit(dataSet):
# 1. 计算特征个数 -1 是减去最后一列标签列
featureNum = len(dataSet[0]) - 1
# 2. 计算当前(未特征划分时)熵值
curEntropy = calcEntropy(dataSet)
# 3. 找最好特征划分
bestInfoGain = 0 # 最大信息增益
bestFeatureIndex = -1 # 最好特征索引
for i in range(featureNum):
# 拿到当前列特征
featList = [example[i] for example in dataSet]
# 获取唯一值
uniqueVals = set(featList)
# 新熵值
newEntropy = 0
# 计算分支(不同特征划分)的熵值
for val in uniqueVals:
# 根据当前特征划分dataSet
subDataSet = splitDataSet(dataSet, i, val)
# 加权概率值
weight = len(subDataSet) / len(dataSet)
# 计算熵值,追加到新熵值中
newEntropy += (calcEntropy(subDataSet) * weight)
# 计算信息增益
infoGain = curEntropy - newEntropy
# 更新最大信息增益
print("第%d个特征的增益为%.3f" % (i, infoGain))
if bestInfoGain < infoGain:
bestInfoGain = infoGain
bestFeatureIndex = i
# 4. 返回
return bestFeatureIndex
4.5构建决策树
def createTreeNode(dataSet, labels, featLabels):
# 取出当前节点的样本的标签 -1 表示在最后一位
curLabelList = [example[-1] for example in dataSet]
# -------------------- 停止条件 --------------------
# 1. 判断当前节点的样本的标签是不是已经全为1个值了,如果是则直接返回其唯一类别
if len(curLabelList) == curLabelList.count(curLabelList[0]):
return curLabelList[0]
# 2. 判断当前可划分的特征数是否为1,如果为1则直接返回当前样本里最多的标签
if len(labels) == 1:
return getMaxLabelByDataSet(curLabelList)
# -------------------- 下面是正常选择特征划分的步骤 --------------------
# 1. 选择最好的特征进行划分(返回值为索引)
bestFeatIndex = chooseBestFeatureToSplit(dataSet)
# 2. 利用索引获取真实值
bestFeatLabel = labels[bestFeatIndex]
# 3. 将特征划分加入当前决策树
featLabels.append(bestFeatLabel)
# 4. 构造当前节点
myTree = {bestFeatLabel: {}}
# 5. 删除被选择的特征
del labels[bestFeatIndex]
# 6. 获取当前最佳特征的那一列
featValues = [example[bestFeatIndex] for example in dataSet]
# 7. 去重(获取唯一值)
uniqueFeaValues = set(featValues)
# 8. 对每个唯一值进行分支
for value in uniqueFeaValues:
# 递归创建树
myTree[bestFeatLabel][value] = createTreeNode(
splitDataSet(dataSet, bestFeatIndex, value), labels.copy(),
featLabels.copy())
# 9. 返回
return myTree
4.6运行结果
五、信息增益率算法实现
5.1改动计算信息增益的函数为信息增益率的函数。
def calcEntropy(dataSet):
# 返回数据集的行数
numEntires = len(dataSet)
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
# 如果标签(Label)没有放入统计次数的字典,添加进去
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
Entropy= 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntires
Entropy-= prob * log(prob, 2)
return Entropy
5.2运行结果
第0个特征的增益为0.052
第1个特征的增益为0.352
第2个特征的增益为0.433
第3个特征的增益为0.232
第0个特征的增益为0.164
第1个特征的增益为1.000
第2个特征的增益为0.340
{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}