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 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 chooseBestFeature(dataSet): """ :param dataSet:数据集 :return:返回最佳特征 """ lenFeature = len(dataSet[0]) - 1 # 计算特征维度时去掉"类别" Hd = CalcEntropy(dataSet) # 按类别,计算数据集的信息熵 baseValue = 0.0 # 比较基准值 bestFeature = 0 # 最佳特征 for i in range(lenFeature): Hd_a = 0.0 # 被特征i固定的条件熵 splitInfo = 0.0 # 定义分裂信息 feature = [example[i] for example in dataSet] # 提取每组数据的第i个特征 feature = set(feature) # 得到第i个特征的种类个数 for fea in feature: subData = splitDataSet(dataSet, i, fea) # 将特征i数据集按特征值fea分类 p = float(len(subData)) / float(len(dataSet)) # 计算特征值fea在特征i集合的概率 splitInfo -= p * log(p, 2) # 计算分裂信息 Hd_a += p * CalcEntropy(subData) Gain = Hd - Hd_a # 计算信息增益,即数据信息熵与单个特征的条件熵的差 if splitInfo == 0: bestFeature = i else: GainRatio = Gain / splitInfo # 计算信息增益率,即信息增益与分裂信息之比 # print(i, 'GainRatio=', GainRatio) if GainRatio > baseValue: # 将信息增益与比较基准量baseValue比较 baseValue = GainRatio bestFeature = i # 根据增益率选择最佳特征 return bestFeature
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 chooseBestFeature(dataSet): """ :param dataSet:数据集 :return:返回最佳特征 """ lenFeature = len(dataSet[0]) - 1 # 计算特征维度时去掉"类别" Hd = CalcEntropy(dataSet) # 按类别,计算数据集的信息熵 baseValue = 0.0 # 比较基准值 bestFeature = 0 # 最佳特征 for i in range(lenFeature): Hd_a = 0.0 # 被特征i固定的条件熵 feature = [example[i] for example in dataSet] # 提取每组数据的第i个特征 feature = set(feature) # 得到第i个特征的种类个数 for fea in feature: subData = splitDataSet(dataSet, i, fea) # 将特征i数据集按特征值fea分类 p = float(len(subData)) / float(len(dataSet)) # 计算特征值fea在特征i集合的概率 # print('p = ',p,'i = ',i,'fea = ',fea) # 在特征i集合中,特征值fea数据的概率与信息熵乘积相加,即为被特征i固定的条件熵 Hd_a += p * CalcEntropy(subData) Gain = Hd - Hd_a # 计算信息增益,即数据信息熵与单个特征的条件熵的差 # print(i, 'Gain=', Gain) if Gain > baseValue: # 将信息增益与比较基准量baseValue比较 baseValue = Gain bestFeature = i # 根据增益选择最佳特征 return bestFeature
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 branLapError(branch, dataSet, labelIndex, k): """ :param branch:子树分支集合 :param dataSet:数据集 :param labelIndex:类索引值 :return:误差加权和 """ ErTt = [] # 每个分支误差初值 nTt = [] # 每个分支样本总数初值 for keys in branch.keys(): # 计算每个分支的误差和数目 items = keys.split(',') # 如果该键包含多个特征值,那么进行分离 subDataSet = splitDataSet(dataSet, labelIndex, items) # for data in subDataSet: # print(data) ErTt.append(nodeLapError(subDataSet, k)) nTt.append(len(subDataSet)) # print('ErTt=', ErTt) Sum_ErTt = np.dot(ErTt, nTt) / sum(nTt) # 计算所有分支误差的加权和 return Sum_ErTt
def chooseBestFeature(dataSet): """ :param dataSet:数据集 :return:返回最佳特征,最佳特征值组合 """ lenFeature = len(dataSet[0]) - 1 # 计算特征维度时去掉"类别" featureBase = 1.0 # 基尼系数比较基准值 bestFeature = 0 # 最佳特征 bestFeatureTuple = [] # 最佳特征二分组合 for i in range(lenFeature): GiniD_A = 0.0 # 被特征i固定的基尼系数 valueBase = 1.0 # 该特征下某个特征比较基准值 feature = [example[i] for example in dataSet] # 提取每组数据的第i个特征 feature = set(feature) # 得到第i个特征的种类个数 # print('feature=', feature) if len(feature) == 1: # 如果该特征只有一个特征值 bestValueTuple = [feature] # 不需要划分特征组合 GiniD_A = 1.0 else: featureGroup = featureSplit(feature) # 将该特征划分为二分序列组合 for valueTuple in featureGroup: GiniD_a = 0.0 # 某个特征下某个特征值的基尼系数 for value in valueTuple: subData = splitDataSet(dataSet, i, value) # 将特征i数据集按特征值value分类 p = float(len(subData)) / float( len(dataSet)) # 计算特征值value在特征i集合的概率 # 在二分序列组合中,将每个特征值value概率与基尼系数乘积再相加,即为该组合基尼系数 GiniD_a += p * CalcGini(subData) # print('valueTuple=', valueTuple) # print('GiniD_a=', GiniD_a) if GiniD_a < valueBase: # 将基尼系数与比较基准量valueBase比较 valueBase = GiniD_a GiniD_A = GiniD_a # 特征值组合最小的基尼系数,即为该特征的基尼系数 bestValueTuple = valueTuple # 记录该最佳特征值组合 bestFeatureTuple.append(bestValueTuple) # 将所有特征值组合构成集合 # print('bestFeatureTuple=', bestFeatureTuple) # print('GiniD_A=', GiniD_A) if GiniD_A < featureBase: # 根据基尼系数选出最佳特征 featureBase = GiniD_A bestFeature = i return bestFeature, bestFeatureTuple[bestFeature] # 返回最佳特征,以及该特征下最佳特征值组合
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 calErrorRatio(Tree, labels, dataSet, NT, infoSet): """ :param Tree:决策树 :param labels:特征类别属性 :param dataSet:数据集 :param NT:数据集总样本数目 :param infoSet:所有节点的信息总集合 :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(): # 遍历第二个字典的键 if type(secondDict[keys]).__name__ == 'dict': items = keys.split(',') # 如果该键包含多个特征值,那么进行分离 subDataSet = splitDataSet(dataSet, labelIndex, items) # 划分数据集 info, infoSet = calErrorRatio(secondDict[keys], subLabels, subDataSet, NT, infoSet) info.setdefault('keys', keys) # 在节点信息集中,增加分类前特征 infoSet.append(info) # print('=============================') # print('Tree=', Tree) # print('firstFeat=', firstFeat) # print('secondDict=', secondDict) Rt = nodeError(dataSet) / NT # 计算节点误差率 RTt = leafError(Tree, labels, dataSet) / NT # 计算子树误差率 Nt = getNumLeaf(Tree) # 计算叶节点数目 if Nt == 1: a = 2.0 else: a = (Rt - RTt) / (Nt - 1) # 计算误差增加率 info = {'Tree': Tree, 'NumLeaf': Nt, 'a': a} # 构建节点信息集 # print('info=', info) return info, infoSet
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