ml 决策树
决策树算法
E(S) 是数据集S的熵
i 指每个结果,即 No,Yes的概率
E在0-1之间,如果P+的概率在0.5, 此时E最大,这时候说明信息对我们没有明确的意义,对分类没有帮助。
所以用到 Gain:信息增益
这个博客详细介绍了3种决策树算法:https://www.cnblogs.com/ECJTUACM-873284962/p/7355951.html
决策树的优点:
• 易于理解和解释。数可以可视化。也就是说决策树属于白盒模型,如果一个情况被观察到,使用逻辑判断容易表示这种规则。相反,如果是黑盒模型(例如人工神经网络),结果会非常难解释。
• 几乎不需要数据预处理。其他方法经常需要数据标准化,创建虚拟变量和删除缺失值。在sklearn中的决策树还不支持缺失值处理。
• 利用训练好的决策树进行分类或回归的时间复杂度仅是训练生成决策树的时间的对数,时间复杂度大大缩小。
• 既可以处理数值变量,也可以处理分类数据,也就是说,如果数据类型不同,也可以在一颗决策树中进行训练。其他方法通常只适用于分析一种类型的变量。
• 可以处理多值输出变量问题。
• 可以使用统计检验验证模型的可靠性。
• 如果真实的数据不符合决策树构建时的假设条件,也可以较好的适用。
决策树的缺点:
• 决策树学习可能创建一个过于复杂的树,降低泛化性能,这就是过拟合。修剪机制(现在不支持),构建决策树时设置一个叶子节点需要的最小样本数量,或者树的最大深度,都可以避免过拟合。
• 决策树容易受到噪声影响,同样的数据如果略微改变可能生成一个完全不同的树。这个问题通过集成学习来缓解。
• 学习一颗最优的决策树是一个**NP-完全问题**under several aspects of optimality and even for simple concepts。所以,考虑计算量,在实际工程当中,决策树算法采用启发式算法,例如贪婪算法,可以保证局部最优解,但无法确保是全局最优解。这个问题可以采用集成学习来解决。
• 决策树对于一些问题有局限性,主要是这些问题难以用条件判断来解决。例如,异或问题, parity,multiplexer问题等等.
• 如果某些分类占优势,决策树将会创建一棵有偏差的树。因此,建议在训练之前,先抽样使样本均衡。
信息增益:特征x使类y的不确定性减少的程度(有点问题)
构造决策树时,根节点选择依据是信息增益
信息增益率:考虑自身熵值,比信息增益好。
CART:用GINI系数当作衡量标准
下面是具体的几种决策树算法的比较。

剪枝策略:预剪枝(降低复杂度,更实用),后剪枝
预剪枝可以限制深度、叶子节点的个数,叶子节点样本数、信息增益量等
python代码实现
C4.5
参考这篇博客:http://www.cnblogs.com/wsine/p/5180315.html
DecisionTreeClassifier(class_weight=None, # 指某个类别所占的权重。字典类型,或以字典为元素的列表类型,或 balanced,或默认 None,一般单值分类问题都默认,即所有的权重都是1,对多输出问题,由于需要各个维度配合,所以经常需要处理数据以方便处理。如果一堆训练数据,其中一类的数据量特别小,而其他类的数据量巨大则可以采用 balanced,根据类别标签的频率,使频率低的权重提高,达到重视少数样本的目的。 criterion='entropy', # 构造决策树时选择属性的准则,entropy指信息增益,gini指基尼系数,还有一个信息增益率,这里不支持 max_depth=160, # 最大树深,一般不设置,它和每个节点最小需要的支持样本数相辅相成,共同决定数的结构 max_features=None, # 默认所有的属性特征总数,指寻找最优分支的时候要计算多少个属性,默认就是所有属性全部计算,如果是整数,那么就从整数个属性里选,如果是小数,按照百分比来计算,如果是 auto 或 sqrt,就是按照总属性数目开根号计算,如果是 log2,就按照取对数来计算。其目的主要是在属性数量太多的时候,加快计算速度。 max_leaf_nodes=None, # 默认为 None,没有限制,指的是在分割数据集时,选用的属性对分割后的结果有限制,其中的一个结点拥有的样本数不允许超过此参数值。目的是最大化降低不纯度。 min_samples_leaf=1, # 默认是1,指在构建树结点的时候,如果仅有一个子节点划分在它下面,说明随机性很大,就不允许这个样本单独来构造一个树节点。 min_samples_split=3, # 默认是2,指如果某个结点中仅有2个样本划分在它这里,则不构造子结点来对它分割。如果是整数,那么这个整数就是所需的样本数目如果是小数,必须得小于1,指占总训练数据的比例必须大于该数才有资格划分结点 min_weight_fraction_leaf=0.0, # 默认是0.0,The minimum weighted fraction of the sum total of weights (of allthe input samples) required to be at a leaf node. Samples haveequal weight when sample_weight is not provided. min_impurity_decrease=0.0, # 构造决策树的过程就是在降低不纯度,构造结点的时候,如果不纯度降低的值比这个要求的最小值还小,则说明该结点选择不够最优,重新选择。 min_impurity_split=0.0, # 指如果一个结点的不纯度低于这个参数,说明已经够纯了,不用再划分了,反之则需要继续切分。 presort=False, # 布尔类型,默认为False,指预处理,预先分类。是否加速训练速度,设置成True可能会降低训练速度。 random_state=None, # 在选择属性等过程中需要随机处理,也就需要随机数,若指定整数,则该数就是随机实例的seed值,如果是一个指定的随机实例,则使用它,如果采用默认None,会默认采用 np.random splitter='best' # best指从满足准则的里面选取信息增益最大的,gini系数最大的。random指并非从符合准则的里面选最优的,而是给出几个相对较优的,随机从里面选一个。这样做主要是为了克服criterion对某些取值数目较少的属性有偏好 ) # 其预测结果也可以选择,一种就是直接给出到底是哪个类别,第二种是给出属于每一类的概率值,再就是给出概率值的对数。 DecisionTreeRegressor(criterion='mse', # 回归问题采用的划分准则,mse指最小均方误差,是L2损失,friedman_mse是采用福雷曼增益值的最小均方误差。mae 指平均绝对误差,是L1损失。 ) ExtraTreeRegressor(# 是从上面两个标准的继承过来的,含义是极度随机化的树分类和树回归。其含义是在选择属性构造子节点时,是从最大特征数中随机选择一些进行构造。选择哪些则是完全随机的。这么做的目的是为了提高训练速度,避免过拟合,陷入极小点。 )
class C45DecisionTree(object): ''' 该决策树仅仅是决策树的最简版本,其局限性: 1、不能处理连续取值的属性值, 2、不能处理某些属性没有取值的数据, 3、没有剪枝策略, 4、数据没有预处理机制, 5、控制参数缺失,如最大树深等, 6、不支持多变量决策树。 该类的使用方法: c45 = C45DecisionTree() myTree = c45.createTree(myDat, labels) # 输入训练数据和标签,构造决策树 print myTree # 查看决策树的形状 predict = c45.classify(myTree, labels, testData) # 输入构造的树,标签集合,测试数据(只能是一条) ''' def __init__(self): import math import operator self.my_tree = {} # 用来查看构造出来的决策树 def calc_shannon_entropy(self, data_set): ''' 计算给定数据集的香浓熵。 data_set的结构是一个列表,其中的每个元素都是一个数据项,数据项结构依然是一个列表,前面n-1项都是属性对应的值,最后一项是分类结果 ''' num_entries = len(data_set) label_counts = {} # 类别字典(类别的名称为键,该类别的个数为值) for feat_vector in data_set: # 读取数据集中的每一条数据,统计每一条数据的类别 label 出现的次数,得到一个字典 current_label = feat_vector[-1] # 最末尾的一个数值 if current_label not in label_counts.keys(): # 还没添加到字典里的类型,获取字典里的所有键值 label_counts[current_label] = 0 label_counts[current_label] += 1 shannon_entropy = 0.0 for key in label_counts: # 求出每种类型的熵,将所有的熵相加,得到信息熵 prob = float(label_counts[key]) / num_entries # 每种类型个数占所有的比值 shannon_entropy -= prob * math.log(prob, 2) # 计算熵 return shannon_entropy # 返回熵 def split_data_set(self, data_set, axis, value): ''' 按照给定的特征划分数据集,特征就是第axis列对应的特征,该特征值等于value的划分为一个集合,作为该函数的返回 :param dataSet:数据集不解释 :param axis:数据集中每一项数据的第axis列的属性 :param value:给定的第axis列的可能的取值中的一个 :return:返回将第axis列的属性数据删除之后的数据集 ''' ret_data_set = [] for feat_vector in data_set: # 按dataSet矩阵中的第axis列的值等于value的分数据集 if feat_vector[axis] == value: # 值等于value的,每一行为新的列表(去除第axis个数据) reduced_feat_vector = feat_vector[:axis] reduced_feat_vector.extend(feat_vector[axis + 1:]) # 也就是说,去掉了第 axis 维的数据 ret_data_set.append(reduced_feat_vector) return ret_data_set # 返回分类后的新矩阵 def choose_best_feature_to_split(self, data_set): ''' 选择最好的数据集划分方法,即找出来某个属性,作为决策树下一步要采用的划分属性。按照信息增益来找的 但是针对离散数据比较好操作,针对连续数据这里需要增加代码 :param data_set:数据集 :return:找到到底按照哪个属性来划分决策树,返回该属性的索引 ''' num_features = len(data_set[0]) - 1 # 求属性的个数 base_entropy = self.calc_shannon_entropy(data_set) best_info_gain = 0.0 # 要选择最高的信息增益 best_feature = -1 # 最好的特征的索引,-1表示不存在,还没开始选择 for i in range(num_features): # 求所有属性的信息增益 feat_list = [example[i] for example in data_set] # 将数据集中所有的第i个特征值全部提取出来,组成一个列表 unique_vals = set(feat_list) # 第 i列属性的取值(不同值)数集合,对于离散的数据值还比较好用,如果是连续数据,数据量太大 new_entropy = 0.0 split_info = 0.0 for value in unique_vals: # 求第 i列属性每个不同值的熵 *他们的概率 sub_data_set = self.split_data_set(data_set, i, value) # 子数据集,是按照 i属性划分成的多个部分的其中一个 prob = len(sub_data_set) / float(len(data_set)) # 求出该值在i列属性中的概率 new_entropy += prob * self.calc_shannon_entropy(sub_data_set) # 求i列属性各值对于的熵求和 split_info -= prob * math.log(prob, 2) info_gain = (base_entropy - new_entropy) / split_info # 求出第i列属性的信息增益率 # print infoGain if info_gain > best_info_gain: # 保存信息增益率最大的信息增益率值以及所在的下表(列值i) best_info_gain = info_gain best_feature = i return best_feature def majority_count(self, class_list): ''' 找到出现次数最多的分类的名称,即每个数据项的最后一列,分类的结果标签 :param class_list: 类别列表 :return: 返回的值是类别最多的那一类的类名 ''' class_count = {} # 类别计数 for vote in class_list: if vote not in class_count.keys(): # 增加分类,class_count就是标明了每一个类有多少个数据项 class_count[vote] = 0 class_count[vote] += 1 sorted_class_count = sorted(class_count.iteritems(), key=operator.itemgetter(1), reverse=True) # 将字典可迭代化,按照迭代化后的每个数据项的1索引的取值排序,逆序排序,其实完全没有必要,时间复杂度比较高 return sorted_class_count[0][0] def create_tree(self, data_set, labels): ''' 创建决策树。很明显,使用过的属性不可以再使用,所以对于连续属性取值不可用,且树的深度也不会很深。 :param data_set: 数据集不多解释 :param labels: 指属性的名字,字符串类型的列表 :return: ''' class_list = [example[-1] for example in data_set] # 训练数据的结果列表(例如最外层的列表是[N, N, Y, Y, Y, N, Y]) if class_list.count(class_list[0]) == len(class_list): # 如果所有的训练数据都是属于一个类别,则返回该类别 return class_list[0] if len(data_set[0]) == 1: # 训练数据只给出类别数据(没给任何属性值数据),返回出现次数最多的分类名称 return self.majority_count(class_list) best_feat = self.choose_best_feature_to_split(data_set) # 选择信息增益最大的属性进行分(返回值是属性类型列表的下标) best_feat_label = labels[best_feat] # 根据下表找属性名称,当作树的根节点,树结构用字典来表示 my_tree = {best_feat_label: {}} # 以best_feat_label为根节点建一个空树 del (labels[best_feat]) # 从属性列表中删掉已经被选出来当根节点的属性,当数据属于连续类型的时候,这一条就是错误的 feat_values = [example[best_feat] for example in data_set] # 找出该属性所有训练数据的值(创建列表) unique_vals = set(feat_values) # 求出该属性的所有值的集合(集合的元素不能重复) for value in unique_vals: # 根据该属性的值求树的各个分支,如果该属性为真那该怎样,为假又该怎样…… sub_labels = labels[:] # 已经是剔除了上面已选的属性之后的子属性集合 my_tree[best_feat_label][value] = self.create_tree(self.split_data_set(data_set, best_feat, value), sub_labels) # 根据各个分支递归创建树 self.my_tree = my_tree return my_tree # 生成的树 def classify(self, input_tree, feat_labels, test_vec): ''' 使用决策树进行分类 :param input_tree: 输入决策树,树结构按字典给出的 :param feat_labels: 特征标签,列表,顺序和数据集中的属性排列顺序一致 :param test_vec: 测试数据集 :return: 返回分类的标签 ''' first_str = input_tree.keys()[0] # 使用的决策树的所有的键的第一个 second_dict = input_tree[first_str] feat_index = feat_labels.index(first_str) # 找到类别标签的索引 for key in second_dict.keys(): # 第二层判断 if test_vec[feat_index] == key: # 找到使用哪颗子树 if type(second_dict[key]).__name__ == 'dict': class_label = self.classify(second_dict[key], feat_labels, test_vec) # 继续分类 else: class_label = second_dict[key] # 已经到了树的叶子节点,它指示什么类别就是什么了。 return class_label
from math import log import operator """ 函数说明:计算给定数据集的经验熵(香农熵) Parameters: dataSet:数据集 Returns: shannonEnt:经验熵 Modify: 2018-03-12 """ def calcShannonEnt(dataSet): #返回数据集行数 numEntries=len(dataSet) #保存每个标签(label)出现次数的字典 labelCounts={} #对每组特征向量进行统计 for featVec in dataSet: currentLabel=featVec[-1] #提取标签信息 if currentLabel not in labelCounts.keys(): #如果标签没有放入统计次数的字典,添加进去 labelCounts[currentLabel]=0 labelCounts[currentLabel]+=1 #label计数 shannonEnt=0.0 #经验熵 #计算经验熵 for key in labelCounts: prob=float(labelCounts[key])/numEntries #选择该标签的概率 shannonEnt-=prob*log(prob,2) #利用公式计算 return shannonEnt #返回经验熵 """ 函数说明:创建测试数据集 Parameters:无 Returns: dataSet:数据集 labels:分类属性 Modify: 2018-03-13 """ 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 """ 函数说明:按照给定特征划分数据集 Parameters: dataSet:待划分的数据集 axis:划分数据集的特征 value:需要返回的特征值 Returns: 无 Modify: 2018-03-13 """ def splitDataSet(dataSet,axis,value): #创建返回的数据集列表 retDataSet=[] #遍历数据集 for featVec in dataSet: if featVec[axis]==value: #去掉axis特征 reduceFeatVec=featVec[:axis] #将符合条件的添加到返回的数据集 reduceFeatVec.extend(featVec[axis+1:]) retDataSet.append(reduceFeatVec) #返回划分后的数据集 return retDataSet """ 函数说明:计算给定数据集的经验熵(香农熵) Parameters: dataSet:数据集 Returns: shannonEnt:信息增益最大特征的索引值 Modify: 2018-03-13 """ def chooseBestFeatureToSplit(dataSet): #特征数量 numFeatures = len(dataSet[0]) - 1 #计数数据集的香农熵 baseEntropy = calcShannonEnt(dataSet) #信息增益 bestInfoGain = 0.0 #最优特征的索引值 bestFeature = -1 #遍历所有特征 for i in range(numFeatures): # 获取dataSet的第i个所有特征 featList = [example[i] for example in dataSet] #创建set集合{},元素不可重复 uniqueVals = set(featList) #经验条件熵 newEntropy = 0.0 #计算信息增益 for value in uniqueVals: #subDataSet划分后的子集 subDataSet = splitDataSet(dataSet, i, value) #计算子集的概率 prob = len(subDataSet) / float(len(dataSet)) #根据公式计算经验条件熵 newEntropy += prob * calcShannonEnt((subDataSet)) #信息增益 infoGain = baseEntropy - newEntropy #打印每个特征的信息增益 print("第%d个特征的增益为%.3f" % (i, infoGain)) #计算信息增益 if (infoGain > bestInfoGain): #更新信息增益,找到最大的信息增益 bestInfoGain = infoGain #记录信息增益最大的特征的索引值 bestFeature = i #返回信息增益最大特征的索引值 return bestFeature """ 函数说明:统计classList中出现次数最多的元素(类标签) Parameters: classList:类标签列表 Returns: sortedClassCount[0][0]:出现次数最多的元素(类标签) Modify: 2018-03-13 """ def majorityCnt(classList): classCount={} #统计classList中每个元素出现的次数 for vote in classList: if vote not in classCount.keys(): classCount[vote]=0 classCount[vote]+=1 #根据字典的值降序排列 sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) return sortedClassCount[0][0] """ 函数说明:创建决策树 Parameters: dataSet:训练数据集 labels:分类属性标签 featLabels:存储选择的最优特征标签 Returns: myTree:决策树 Modify: 2018-03-13 """ def createTree(dataSet,labels,featLabels): #取分类标签(是否放贷:yes or no) 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=chooseBestFeatureToSplit(dataSet) #最优特征的标签 bestFeatLabel=labels[bestFeat] featLabels.append(bestFeatLabel) #根据最优特征的标签生成树 myTree={bestFeatLabel:{}} #删除已经使用的特征标签 del(labels[bestFeat]) #得到训练集中所有最优特征的属性值 featValues=[example[bestFeat] for example in dataSet] #去掉重复的属性值 uniqueVls=set(featValues) #遍历特征,创建决策树 for value in uniqueVls: myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value), labels,featLabels) return myTree """ 使用决策树进行分类 Parameters: inputTree;已经生成的决策树 featLabels:存储选择的最优特征标签 testVec:测试数据列表,顺序对应最优特征标签 Returns: classLabel:分类结果 Modify:2018-03-13 """ def classify(inputTree,featLabels,testVec): #获取决策树节点 firstStr=next(iter(inputTree)) #下一个字典 secondDict=inputTree[firstStr] featIndex=featLabels.index(firstStr) for key in secondDict.keys(): if testVec[featIndex]==key: if type(secondDict[key]).__name__=='dict': classLabel=classify(secondDict[key],featLabels,testVec) else: classLabel=secondDict[key] return classLabel if __name__=='__main__': dataSet,labels=createDataSet() featLabels=[] myTree=createTree(dataSet,labels,featLabels) #测试数据 testVec=[0,1] result=classify(myTree,featLabels,testVec) if result=='yes': print('放贷') if result=='no': print('不放贷')
输出:

梯度提升决策树(GBDT)
GBDT概述
梯度提升决策树GBDT(Gradient Boosting Decision Tree)也被称为是MART(Multiple Additive Regression Tree))或者是GBRT(Gradient Boosting Regression Tree),也是一种基于集成思想的决策树模型,但是它和Random Forest有着本质上的区别。不得不提的是,GBDT是目前竞赛中最为常用的一种机器学习算法,因为它不仅可以适用于多种场景,更难能可贵的是,GBDT有着出众的准确率。这也是为什么很多人称GBDT为机器学习领域的“屠龙刀”。
“Boosting,迭代,即通过迭代多棵树来共同决策。这怎么实现呢?难道是每棵树独立训练一遍,比如A这个人,第一棵树认为是10岁,第二棵树认为是0岁,第三棵树认为是20岁,我们就取平均值10岁做最终结论?当然不是!且不说这是投票方法并不是GBDT,只要训练集不变,独立训练三次的三棵树必定完全相同,这样做完全没有意义。之前说过,GBDT是把所有树的结论累加起来做最终结论的,所以可以想到每棵树的结论并不是年龄本身,而是年龄的一个累加量。
GBDT的核心就在于:每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学,如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。这就是Gradient Boosting在GBDT中的意义。
其实从这里我们可以看出GBDT与Random Forest的本质区别,GBDT不仅仅是简单地运用集成思想,而且它是基于对残差的学习的。我们在这里利用一个GBDT的经典实例进行解释。
在GBDT的迭代中,假设我们前一轮迭代得到的强学习器是ft−1(x)ft−1(x), 损失函数是L(y,ft−1(x))L(y,ft−1(x))L(y,ft−1(x))L(y,ft−1(x)), 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器ht(x)ht(x),让本轮的损失损失L(y,ft(x)=L(y,ft−1(x)+ht(x))L(y,ft(x)=L(y,ft−1(x)+ht(x))最小。也就是说,本轮迭代找到决策树,要让样本的损失尽量变得更小。
GBDT主要的优点有:
1) 可以灵活处理各种类型的数据,包括连续值和离散值。
2) 在相对少的调参时间情况下,预测的准备率也可以比较高。这个是相对SVM来说的。
3)使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。
GBDT的主要缺点有:
1)由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。
最后说一点:Graphviz还没下载。目前可视化决策树弄不了,之后有时间想弄:https://blog.csdn.net/jiaoyangwm/article/details/79525237 里面有答案

浙公网安备 33010602011771号