def createCARTTree(dataSet, labels): """ :param dataSet: 数据集 :param labels: 类别标签 :return:决策树 """ classList = [example[-1] for example in dataSet] # classList是数据集的所有类别 # 情况1:若classList只有一类,则停止划分 if classList.count(classList[0]) == len(classList): return classList[0] # 情况2:若完成所有的特征分类,返回个数最多的 if len(dataSet[0]) == 1: return majorityCnt(classList) # 投票选出最多的特征 # 情况3:classList有多类,开始进行分类 feaBest, feature = chooseBestFeature(dataSet) # 选择最佳分类特征 # print('=====================================') # print('feaBest=', feaBest, 'feature', feature) newLabel = labels[feaBest] # 得到该特征的名称 del (labels[feaBest]) # 删掉已分类的特征名称 Tree = {newLabel: {}} # 创建一个多重字典,存储决策树分类结果 for value in feature: subLabels = labels[:] # 构建特征名称子集合 strValue = ','.join(value) # 利用递归函数不断创建分支,直到所有特征完成分类,分类结束 subData = splitDataSet(dataSet, feaBest, value) # print('---------------------------------------') # print('value=', value) Tree[newLabel][strValue] = createCARTTree(subData, subLabels) return Tree
def prunBranch(Tree, labels, infoBran, dataSet): """ :param Tree:决策树 :param labels:特征类别属性 :param infoBran:需剪掉的支树信息集 :param dataSet:数据集 :return:剪枝后的决策树 """ firstFeat = list(Tree.keys())[0] # 取出tree的第一个键名 secondDict = Tree[firstFeat] # 取出tree第一个键值 labelIndex = labels.index(firstFeat) # 找到键名在特征属性的索引值 subLabels = labels[:labelIndex] # 剔除预处理的键名 subLabels.extend(labels[labelIndex + 1:]) for keys in secondDict.keys(): # 遍历第二个字典的键 items = keys.split(',') # 如果该键包含多个特征值,那么进行分离 subDataSet = splitDataSet(dataSet, labelIndex, items) # 划分数据集 classList = [example[-1] for example in subDataSet] majorClass = majorityCnt(classList) # 找到数量最多的类别 # 如果当前支树分类前特征和支树都和预处理相同,则把该支树剪掉 if keys == infoBran['keys'] and secondDict[keys] == infoBran['Tree']: secondDict[keys] = majorClass # 剪掉支树,即返回最大类 return Tree elif type(secondDict[keys]).__name__ == 'dict': # 如果不相同,继续向下寻找 secondDict[keys] = prunBranch(secondDict[keys], subLabels, infoBran, subDataSet) return Tree
def createC4_5Tree(dataSet, labels): """ :param dataSet: 数据集 :param labels: 类别标签 :return:决策树 """ classList = [example[-1] for example in dataSet] # classList是数据集的所有类别 # 情况1:若classList只有一类,则停止划分 if classList.count(classList[0]) == len(classList): return classList[0] # 情况2:若完成所有的特征分类,返回个数最多的 if len(dataSet[0]) == 1: return majorityCnt(classList) # 投票选出最多的特征 # 情况3:classList有多类,开始进行分类 feaBest = chooseBestFeature(dataSet) # 选择最佳分类特征 feature = [example[feaBest] for example in dataSet] # 提取每组数据的第i个特征 feature = set(feature) # 得到第i个特征的种类个数 newLabel = labels[feaBest] # 得到该特征的名称 del (labels[feaBest]) # 删掉已分类的特征名称 Tree = {newLabel: {}} # 创建一个多重字典,存储决策树分类结果 for value in feature: subLabels = labels[:] # 构建特征名称子集合 # 利用递归函数不断创建分支,直到所有特征完成分类,分类结束 Tree[newLabel][value] = createC4_5Tree( splitDataSet(dataSet, feaBest, value), subLabels) return Tree
def PrePruning(trainSet, testSet, labels): """ :param trainSet: 训练集 :param testSet: 测试集 :param labels: 类别标签 :return: 剪枝后的决策树 """ classList = [example[-1] for example in trainSet] # classList是训练集的所有类别 # 情况1:若classList只有一类,则停止划分 if classList.count(classList[0]) == len(classList): return classList[0] # 情况2:若完成所有的特征分类,返回个数最多的 if len(trainSet[0]) == 1: return majorityCnt(classList) # 投票选出最多的特征 # 情况3:classList有多类,开始进行分类 feaBest = chooseBestFeature(trainSet) # 选择最佳分类特征 feature = [example[feaBest] for example in trainSet] # 提取每组数据的第i个特征 feature = set(feature) # 得到第i个特征的种类个数 # print('feaBest=', labels[feaBest]) # print('feature=', feature) newLabel = labels[feaBest] # 得到该特征的名称 labelIndex = labels.index(newLabel) # 获取该特征在原特征集合的索引值 beforeAccuracy = testMajor(trainSet, testSet) # 划分前样本集精度 afterAccuracy = testCreate(trainSet, testSet, feaBest, labelIndex) # 划分后样本集精度 # print('beforeAccuracy=', beforeAccuracy) # print('afterAccuracy=', afterAccuracy) del (labels[feaBest]) # 删掉已分类的特征名称 Tree = {newLabel: {}} # 创建一个多重字典,存储决策树分类结果 if afterAccuracy > beforeAccuracy: # 如果划分后验证集精度比划分前高 for value in feature: # 那么该特征可以划分,构造决策树 # print('==============================') # print('value=', value) subLabels = labels[:] # 构建特征名称子集合 # 利用递归函数不断创建分支,直到所有特征完成分类,分类结束 subTrainSet = splitDataSet(trainSet, feaBest, value) # 按对应特征,划分训练集 subTestSet = splitDataSet(testSet, feaBest, value) # 按对应特征,划分测试集 Tree[newLabel][value] = PrePruning(subTrainSet, subTestSet, subLabels) return Tree else: # 如果划分后没有划分前精度高 return majorityCnt(classList) # 那么停止划分,返回最多的类别
def PostPruning_REP(trainSet, testSet, labels): """ :param trainSet:训练集 :param testSet:测试集 :param labels:类别属性集 :return:剪枝后的决策树 """ classList = [example[-1] for example in trainSet] # classList是训练集的所有类别 # 情况1:若classList只有一类,则停止划分 if classList.count(classList[0]) == len(classList): return classList[0] # 情况2:若完成所有的特征分类,返回个数最多的 if len(trainSet[0]) == 1: return majorityCnt(classList) # 投票选出最多的特征 # 情况3:classList有多类,开始进行分类 feaBest = chooseBestFeature(trainSet) # 选择最佳分类特征 feature = [example[feaBest] for example in trainSet] # 提取每组数据的第i个特征 feature = set(feature) # 得到第i个特征的种类个数 newLabel = labels[feaBest] # 得到该特征的名称 del (labels[feaBest]) # 删掉已分类的特征名称 Tree = {newLabel: {}} # 创建一个多重字典,存储决策树分类结果 for value in feature: # 那么该特征可以划分,构造决策树 subLabels = labels[:] # 构建特征名称子集合 # 利用递归函数不断创建分支,直到所有特征完成分类,分类结束 subTrainSet = splitDataSet(trainSet, feaBest, value) # 按对应特征,划分训练集 subTestSet = splitDataSet(testSet, feaBest, value) # 按对应特征,划分测试集 Tree[newLabel][value] = PostPruning_REP(subTrainSet, subTestSet, subLabels) # print('=============================') # print('Tree=', Tree) labels.insert(feaBest, newLabel) # 为了使用决策树分类,将删掉的类别还原 beforeAccuracy = testPrun(testSet, Tree, labels) # 计算剪枝前测试集精度 afterAccuracy = testMajor(trainSet, testSet) # 计算剪枝后测试集精度 # print('beforeAccuracy=', beforeAccuracy) # print('afterAccuracy=', afterAccuracy) del (labels[feaBest]) # 再将分类好的类别删掉 if afterAccuracy > beforeAccuracy: # 如果剪枝后精度比剪枝前大,那么进行剪枝 return majorityCnt(classList) else: # 否则不剪枝,返回原决策树 return Tree
def nodeError(dataSet): """ :param dataSet:数据集 :return:误差 """ error = 0.0 classList = [example[-1] for example in dataSet] majorClass = majorityCnt(classList) # 找到数量最多的类别 for i in range(len(dataSet)): # 游历数据集每个元素,找出正确样本个数 if dataSet[i][-1] != majorClass: error += 1 # 如果不一致,错误加1 return float(error)
def testMajor(trainSet, testSet): """ :param trainSet:训练集 :param testSet:测试集 :return:验证集精度 """ classList = [example[-1] for example in trainSet] majorClass = majorityCnt(classList) # 找到数量最多的类别 # print('majorClass=', majorClass) accuracy = 0.0 for i in range(len(testSet)): # 游历测试集每个元素,找出正确样本个数 if testSet[i][-1] == majorClass: accuracy += 1 return float(accuracy)
def PostPruning_PEP(Tree, labels, dataSet): """ :param Tree:预处理的决策树 :param labels:特征类别属性 :param dataSet:数据集 :return:剪枝后的决策树 """ classList = [example[-1] for example in dataSet] majorClass = majorityCnt(classList) # 找到数量最多的类别 et = nodeError(dataSet) + 1 / 2 # 计算非叶节点t误差 Nt = getNumLeaf(Tree) # 子树Tt叶节点数目 eTt = leafError(Tree, labels, dataSet) + Nt / 2 # 子树Tt所有叶节点误差 nt = len(dataSet) # 节点t训练实例数目 if nt > eTt: SeTt = np.sqrt(eTt * (nt - eTt) / nt) # 子树Tt总误差 else: SeTt = 0 # print('================================') # print('Tree=', Tree) # print('et=', et) # print('eTt=', eTt) # print('Nt=', Nt) # print('nt=', nt) #print('SeTt=', SeTt) # print('eTt + SeTt=', eTt + SeTt) if et < eTt + SeTt: # 若节点t误差小于子树Tt误差 return majorClass # 则进行剪枝,直接返回最大类 firstFeat = list(Tree.keys())[0] # 取出tree的第一个键名 secondDict = Tree[firstFeat] # 取出tree第一个键值 labelIndex = labels.index(firstFeat) # 找到键名在特征属性的索引值 # print('firstFeat=', firstFeat) # print('secondDict=', secondDict) subLabels = labels[:labelIndex] # 剔除预处理的键名 subLabels.extend(labels[labelIndex + 1:]) for keys in secondDict.keys(): # 遍历第二个字典的键 if type(secondDict[keys]).__name__ == 'dict': items = keys.split(',') # 如果该键包含多个特征值,那么进行分离 # print('items=', items) subDataSet = splitDataSet(dataSet, labelIndex, items) # 划分数据集 secondDict[keys] = PostPruning_PEP(secondDict[keys], subLabels, subDataSet) return Tree
def testCreate(trainSet, testSet, feaBest, labelIndex): """ :param trainSet:训练集 :param testSet:测试集 :param feaBest:最佳分类特征 :param labelIndex:该特征在原集合的索引值 :return:验证集精度 """ accuracy = 0.0 feature = [example[feaBest] for example in trainSet] # 提取每组数据的最佳分类特征 feature = set(feature) # 得到该特征的种类集合 for value in feature: # 游历该特征下每个属性 # print('value=', value) subTrainSet = splitDataSet(trainSet, feaBest, value) subClass = [example[-1] for example in subTrainSet] majorSub = majorityCnt(subClass) # 统计得到最大类别 # print('majorSub=', majorSub) for data in testSet: # 游历测试集每个元素 if data[labelIndex] == value: # 对应特征下特征属性相同 if data[-1] == majorSub: # 如果类别与对应最大类别相同,则样本正确 accuracy += 1 return float(accuracy)
def PostPruning_IMEP(Tree, labels, dataSet, m): """ :param Tree:预处理的决策树 :param labels:特征类别属性 :param dataSet:数据集 :param m:先验概率对后验概率的影响因子 :return:剪枝后的决策树 """ classList = [example[-1] for example in dataSet] majorClass = majorityCnt(classList) # 找到数量最多的类别 firstFeat = list(Tree.keys())[0] # 取出tree的第一个键名 secondDict = Tree[firstFeat] # 取出tree第一个键值 labelIndex = labels.index(firstFeat) # 找到键名在特征属性的索引值 subLabels = labels[:labelIndex] # 剔除预处理的键名 subLabels.extend(labels[labelIndex + 1:]) # print('======================') # print('Tree=', Tree) # print('firstFeat=', firstFeat) for keys in secondDict.keys(): # 遍历第二个字典的键 # print('keys=', keys) if type(secondDict[keys]).__name__ == 'dict': items = keys.split(',') # 如果该键包含多个特征值,那么进行分离 # print('items=', items) subDataSet = splitDataSet(dataSet, labelIndex, items) # 划分数据集 secondDict[keys] = PostPruning_IMEP(secondDict[keys], subLabels, subDataSet, m) # print('--------------------') Ert = newNodeLapError(dataSet, m) # 计算非叶节点的误差 Sum_ErTt = newBranLapError(secondDict, dataSet, labelIndex, m) # 计算节点t分支的误差加权和 # print('Ert=', Ert) # print('Sum_ErTt=', Sum_ErTt) if Ert > Sum_ErTt: # 如果节点误差大于分支误差和,则子树保留 return Tree else: # 否则进行剪枝,返回主类 return majorClass