目录
1 连续值处理
1.1 处理方法
1.2 实例分析
1.3 Python代码实现
2 剪枝处理
2.1 为什么要剪枝
2.2 预剪枝
2.2.1 基本思想
2.2.2 实例分析
2.2.3预剪枝的优缺点
2.3 后剪枝
2.3.1基本思想
2.3.2代码实现
2.3.3后剪枝的优缺点
在上一篇博客(http://t.csdn.cn/DmFiz)是否能找到工作的例子中,使用 ID3决策树算法实现了基于离散属性的决策树构造 。在上述例子中,对绩点的处理是将其划分成了三个等级,实际上绩点是连续值。应该如何实现连续属性的离散化呢?
C4.5算法中策略是采用二分法将连续属性离散化处理 ,具体步骤如下:
第一步:将连续属性 a 在样本集 D 上出现 n 个不同的取值从小到大排列,记为。基于划分点t,可将D分为子集和,其中包含那些在属性a上取值不大于t的样本,包含那些在属性a上取值大于t的样本。考虑包含n-1个元素的候选划分点集合
即把区间的中位点作为候选划分点
第二步:采用离散属性值方法,计算这些划分点的增益,选取最优的划分点进行样本集合的划分:
对于每个划分点t,按如下公式计算其信息增益值,然后选择使信息增益值最大的划分点进行样本集合的划分
数据集如图所示时:
对属性平均成绩进行离散化处理:
首先对平均成绩从小到大排序:
{56,58,64,65,66,68,72,75,76,78,82,85,86,87,92,93,95}
我们可以取任意相邻取值的中位点,作为划分点,例如:我们可以使用56和58的中位点进行划分,则
当t = 57时:
,
按上述方法依次计算t=58,64,...时的信息增益率,当t = 67时,Gain(D,a,t)最大,因此选择该划分点
再由前面介绍的方法得到其他属性的信息增益值,选择信息增益值最大的属性为根结点划分属性。
需要注意的是:与离散属性不同,若当前结点划分属性为连续属性,该属性还可作为其后代结点的划分属性
1.划分数据集
# 划分数据集, axis:按第几个特征划分, value:划分特征的值, LorR: value值左侧(小于)或右侧(大于)的数据集
def splitDataSet_c(dataSet, axis, value, LorR='L'):
retDataSet = []
featVec = []
if LorR == 'L':
for featVec in dataSet:
if float(featVec[axis]) < value:
retDataSet.append(featVec)
else:
for featVec in dataSet:
if float(featVec[axis]) > value:
retDataSet.append(featVec)
return retDataSet
2.选择最好的数据集划分方式
# 选择最好的数据集划分方式
def chooseBestFeatureToSplit_c(dataSet, labelProperty):
numFeatures = len(labelProperty) # 特征数
baseEntropy = calcShannonEnt(dataSet) # 计算根节点的信息熵
bestInfoGain = 0.0
bestFeature = -1
bestPartValue = None # 连续的特征值,最佳划分值
for i in range(numFeatures): # 对每个特征循环
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) # 该特征包含的所有值
newEntropy = 0.0
bestPartValuei = None
if labelProperty[i] == 0: # 对离散的特征
for value in uniqueVals: # 对每个特征值,划分数据集, 计算各子集的信息熵
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
else: # 对连续的特征
sortedUniqueVals = list(uniqueVals) # 对特征值排序
sortedUniqueVals.sort()
listPartition = []
minEntropy = inf
for j in range(len(sortedUniqueVals) - 1): # 计算划分点
partValue = (float(sortedUniqueVals[j]) + float(
sortedUniqueVals[j + 1])) / 2
# 对每个划分点,计算信息熵
dataSetLeft = splitDataSet_c(dataSet, i, partValue, 'L')
dataSetRight = splitDataSet_c(dataSet, i, partValue, 'R')
probLeft = len(dataSetLeft) / float(len(dataSet))
probRight = len(dataSetRight) / float(len(dataSet))
Entropy = probLeft * calcShannonEnt(
dataSetLeft) + probRight * calcShannonEnt(dataSetRight)
# print(Entropy)
if Entropy < minEntropy: # 取最小的信息熵
minEntropy = Entropy
bestPartValuei = partValue
newEntropy = minEntropy
infoGain = baseEntropy - newEntropy # 计算信息增益
if infoGain > bestInfoGain: # 取最大的信息增益对应的特征
bestInfoGain = infoGain
bestFeature = i
bestPartValue = bestPartValuei
return bestFeature, bestPartValue
3.创建树
# 创建树
# dataSet:样本集
# labels:特征
# labelProperty:特征属性(0 离散, 1 连续)
def createTree_c(dataSet, labels, labelProperty):
# print dataSet, labels, labelProperty
classList = [example[-1] for example in dataSet] # 类别向量
if classList.count(classList[0]) == len(classList): # 如果只有一个类别,返回
return classList[0]
if len(dataSet[0]) == 1: # 如果所有特征都被遍历完了,返回出现次数最多的类别
return majorityCnt(classList)
bestFeat, bestPartValue = chooseBestFeatureToSplit_c(dataSet,
labelProperty) # 最优分类特征的索引
if bestFeat == -1: # 如果无法选出最优分类特征,返回出现次数最多的类别
return majorityCnt(classList)
if labelProperty[bestFeat] == 0: # 对离散的特征
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel: {}}
labelsNew = copy.copy(labels)
labelPropertyNew = copy.copy(labelProperty)
del (labelsNew[bestFeat]) # 已经选择的特征不再参与分类
del (labelPropertyNew[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueValue = set(featValues) # 该特征包含的所有值
for value in uniqueValue: # 对每个特征值,递归构建树
subLabels = labelsNew[:]
subLabelProperty = labelPropertyNew[:]
myTree[bestFeatLabel][value] = createTree_c(
splitDataSet(dataSet, bestFeat, value), subLabels,
subLabelProperty)
else: # 对连续的特征,不删除该特征,分别构建左子树和右子树
bestFeatLabel = labels[bestFeat] + '<' + str(bestPartValue)
myTree = {bestFeatLabel: {}}
subLabels = labels[:]
subLabelProperty = labelProperty[:]
# 构建左子树
valueLeft = '是'
myTree[bestFeatLabel][valueLeft] = createTree_c(
splitDataSet_c(dataSet, bestFeat, bestPartValue, 'L'), subLabels,
subLabelProperty)
# 构建右子树
valueRight = '否'
myTree[bestFeatLabel][valueRight] = createTree_c(
splitDataSet_c(dataSet, bestFeat, bestPartValue, 'R'), subLabels,
subLabelProperty)
return myTree
4. 读取数据集,打印创建的树
data = pd.read_csv("D:/syy/MachineLearning/data/data_td.csv")
dataSet = data.values.tolist()
labels = ['平均绩点', '竞赛等级', '实习经历']
labelProperties = [1, 0, 0] # 属性的类型,0表示离散,1表示连续
Trees = createTree_c(dataSet, labels, labelProperties)
print(Trees)
5.决策树的可视化
剪枝的基本策略:
- 预剪枝
- 后剪枝
判断决策树泛化性能是否提升的方法:
留出法:预留一部分数据用作“验证集”以进行性能评估
决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点记为叶结点,其类别标记为该节点对应训练样例数最多的类别。
通过提前停止树的构建而对树剪枝,主要方法有:
上述不足:阈值属于超参数,很难找到过拟合--欠拟合的平衡
如图所示的数据集(黄色部分为测试集,其他为验证集)
C4.5算法生成的未剪枝决策树如图所示:
以work_experience结点为例说明预剪枝的过程:
从上述例子可以看出预剪枝有欠拟合的风险。
先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行分析计算,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。
def postPruningTree(inputTree, dataSet, data_test, labels, labelProperties):
"""
type: (dict, list, list, list, list) -> dict
inputTree: 已构造的树
dataSet: 训练集
data_test: 验证集
labels: 属性标签
labelProperties: 属性类别
"""
firstStr = list(inputTree.keys())[0]
secondDict = inputTree[firstStr]
classList = [example[-1] for example in dataSet]
featkey = copy.deepcopy(firstStr)
if '<' in firstStr: # 对连续的特征值,使用正则表达式获得特征标签和value
featkey = re.compile("(.+<)").search(firstStr).group()[:-1]
featvalue = float(re.compile("(<.+)").search(firstStr).group()[1:])
labelIndex = labels.index(featkey)
temp_labels = copy.deepcopy(labels)
temp_labelProperties = copy.deepcopy(labelProperties)
if labelProperties[labelIndex] == 0: # 离散特征
del (labels[labelIndex])
del (labelProperties[labelIndex])
for key in secondDict.keys(): # 对每个分支
if type(secondDict[key]).__name__ == 'dict': # 如果不是叶子节点
if temp_labelProperties[labelIndex] == 0: # 离散的
subDataSet = splitDataSet_c(dataSet, labelIndex, key)
subDataTest = splitDataSet_c(data_test, labelIndex, key)
else:
if key == 'Y':
subDataSet = splitDataSet_c(dataSet, labelIndex, featvalue,
'L')
subDataTest = splitDataSet_c(data_test, labelIndex,
featvalue, 'L')
else:
subDataSet = splitDataSet_c(dataSet, labelIndex, featvalue,
'R')
subDataTest = splitDataSet_c(data_test, labelIndex,
featvalue, 'R')
if len(subDataTest) > 0:
inputTree[firstStr][key] = postPruningTree(secondDict[key],
subDataSet, subDataTest,
copy.deepcopy(labels),
copy.deepcopy(
labelProperties))
if testing(inputTree, data_test, temp_labels,
temp_labelProperties) <= testingMajor(majorityCnt(classList),
data_test):
return inputTree
return majorityCnt(classList)
data = pd.read_csv("D:/syy/MachineLearning/data/data_td.csv")
dataSet = data.values.tolist()
labels = ['grade', 'match', 'work_experience']
labelProperties = [1, 0, 0] # 属性的类型,0表示离散,1表示连续
Trees = createTree_c(dataSet, labels, labelProperties)
data_test = [[70, 1, 0, 'yes'],[72, 1, 0, 'yes']]
classSet = ["yes","no"]
# Trees = createTree_c(dataSet, labels, labelProperties)
# 构建决策树
trees = createTree_c(dataSet, labels, labelProperties)
# 绘制决策树
createPlot(trees)
# 利用验证集对决策树剪枝
postPruningTree(trees, dataSet, data_test, labels, labelProperties)
print(trees)
# 绘制剪枝后的决策树
createPlot(trees)
训练集如图所示:
为了观察剪枝效果,这里的验证集为我设置的两组数据。实际应将数据划分为训练集和验证集。
剪枝前的决策树为:
剪枝后的树为:
完整代码链接:链接:https://pan.baidu.com/s/1dxvh6qex35iGKz5z4N9kaw
提取码:nhh1