机器学习 22 、决策树 、模型融合

你挡我一时,挡不了我一世

发布日期: 2019-06-21 08:22:26 浏览量: 79
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

前文链接:https://write-bug.com/article/2666.html

决策树

决策树(decision tree)是一种基本的分类与回归方法,模型:树结构

主要有两大类算法:ID3 、C4.5

学过数据结构的都知道,一棵树由节点node和有向边directed edge 构成,而节点又有几个名词:根节点、父节点、子节点、叶子节点

这里我们用各个节点表示特征(如年龄),边表示特征属性(如青年、老年),叶子节点表示label(如买、不买)

用决策树分类时,来了一个人根据他的特征,对某一特征进行比对,将其分到对应的子节点继续比对,直到到达叶子节点,得到预测label

特征维度:年龄、收入、信誉、学生

现在假设每个人都有这四类特征的数据,如何用一颗树来对这些人做一个是否购买的分类,也就是落地到某个叶子节点,那用哪个特征来做根节点父节点子节点呢?有很多组合方式。

首先处理训练数据:

数据特征值离散化:0 1 2 ……编码

我们在进行特征选择时,要选择对训练数据具有分类能力的特征,以提高决策树的学习效率,决定了用哪个特征来划分特征空间,如果利用一个特征进行分类的结果和随即分类的结果没有差别,那么这个特征没有分类能力,而特征选择的准则就是信息增益与信息增益率,从而引出了ID3与C4.5算法

ID3

ID3的特征选择就是利用信息增益来进行选择,这也是我们前面提到的到底用哪个特征作为根节点哪个特征作为子节点构建树呢?

说到信息增益,这里介绍几个概念:

熵=随机变量的不确定性

条件熵=在一定条件下,随机变量的不确定性

信息增益=熵-条件熵 在一定条件下,信息不确定性减少的程度

  • 我们通常使用H(X)符号代表随机变量X的熵:
  1. H(X)=E(I(X))

E代表期望值函数,I(X)代表随机变量X的自信息量。

  • 不确定性函数I称为事件的信息量,事件X发生概率p的单调递减函数:
  1. 𝐼(X = 𝑙𝑜𝑔(1/ 𝑝)=−𝑙𝑜𝑔 𝑝
  • 信息熵:一个信源中,不能仅考虑单一事件发生的不确定性,需要考虑所有可能情况的平均不确定性,为−𝑙𝑜𝑔 𝑝 的统计平均值E
  1. 𝐻 (X) = 𝐸 [−𝑙𝑜𝑔 (𝑝 (x𝑖))] = −∑𝑖 𝑝(x𝑖 )𝑙𝑜𝑔 (𝑝 (x𝑖))

这里的熵可以理解为混乱度,数据混乱度越大,熵取值越大,随机变量的不确定性越大

如果H=0或1,说明数据为纯净的,一定为买或不买,而H=0.5时,混乱度最大

伯努利分布时熵与概率的关系

选择特征做父节点:选择熵最大(混乱程度最大)的特征

例:

  • ID3 完全用熵的概念:信息增益

  • 原始数据,无条件下的熵:

  1. label=买+不买=1024
  2. p买=640/1024=0.635
  3. p不买 = 384/1024=0.375

根据信息熵公式

  1. H=-∑p logp
  2. H=0.9544

那么在年龄条件下的条件熵:

  1. P青年=384/1024=0.375

青年分为买和不买:

  1. p买=128/384
  2. p不买=256/384
  3. S=384
  4. H=0.9183

同理:
中年:由于都是买的,所以H=0
老年:H=0.9157

年龄的三个信息熵得到了,那如何使用呢,我们对年龄求平均期望:

  1. 平均值=占比\*信息熵
  2. E(年龄)=0.375*0.9183+0.25*0+0.375*0.9157=0.6877
  3. 这个年龄作为区分的话,可以带来0.6877的增益
  4. G(年龄)=0.9544-0.6877=0.2667 为信息增益

同理

  • E(学生)=0.7811 G(学生)=0.1733

  • E(收入)=0.9361 G(收入)=0.0183

  • E(信誉)=0.9048 G(信誉)=0.0496

从上述可以看出,按照年龄划分,收益是最大的,信息增益最大,所以年龄作为根节点,同理,从而构建整颗树.

所以我们构建一个attrList特征列表作为一个容器存放特征,无放回的取特征

如果取完了特征,构建出了树,但是叶子节点还是不是纯净的怎么办呢?

分类问题:少数服从多数

回归问题:概率比较

  • 缺点:因为优先选择信息增益最大的,所以导致倾向于选择属性值较多的

  • 贪婪性:选择收益最大的

贪婪不是最优的算法,可能觉得本次收益最大,但从长远看收益没那么大

奥卡姆剃刀原理:尽量用最少的东西做最多的事

用最少的特征,区分类别,不需要所有特征都用,用最简单的方式效果最好,最优的树

不适用于连续变量:离散型特征属性如果是身高这种线性连续的特征,对应过多类别特征,应离散化使用

Pseudocode:

  1. ID3 (Examples, Target_Attribute, Attributes)
  2. Create a root node for the tree
  3. If all examples are positive, Return the single-node tree Root, with label = +.
  4. If all examples are negative, Return the single-node tree Root, with label = -.
  5. If number of predicting attributes is empty, then Return the single node tree Root,
  6. with label = most common value of the target attribute in the examples.
  7. Otherwise Begin
  8. A The Attribute that best classifies examples.
  9. Decision Tree attribute for Root = A.
  10. For each possible value, vi, of A,
  11. Add a new tree branch below Root, corresponding to the test A = vi.
  12. Let Examples(vi) be the subset of examples that have the value vi for A
  13. If Examples(vi) is empty
  14. Then below this new branch add a leaf node with label = most common target value in the examples
  15. Else below this new branch add the subtree ID3 (Examples(vi), Target_Attribute, Attributes {A})
  16. End
  17. Return Root

ID3实践:

  1. class ID3DTree(object):
  2. def __init__(self):
  3. self.tree={}
  4. self.dataSet=[]
  5. self.labels=[]
  6. def loadDataSet(self,path,labels):
  7. recordlist = []
  8. fp = open(path,"r") # 读取文件内容
  9. content = fp.read()
  10. fp.close()
  11. rowlist = content.splitlines() # 按行转换为一维表
  12. recordlist=[row.split("\t") for row in rowlist if row.strip()]
  13. self.dataSet = recordlist
  14. self.labels = labels
  15. def train(self):
  16. labels = copy.deepcopy(self.labels)
  17. self.tree = self.buildTree(self.dataSet,labels)
  18. # 创建决策树主程序
  19. def buildTree(self,dataSet,labels):
  20. cateList = [data[-1] for data in dataSet] # 抽取源数据集的决策标签列
  21. # 程序终止条件1 : 如果classList只有一种决策标签,停止划分,返回这个决策标签
  22. if cateList.count(cateList[0]) == len(cateList):
  23. return cateList[0]
  24. # 程序终止条件2: 如果数据集的第一个决策标签只有一个 返回这个决策标签
  25. if len(dataSet[0]) == 1:
  26. return self.maxCate(cateList)
  27. # 算法核心:
  28. bestFeat = self.getBestFeat(dataSet) # 返回数据集的最优特征轴:
  29. bestFeatLabel = labels[bestFeat]
  30. tree = {bestFeatLabel:{}}
  31. del(labels[bestFeat])
  32. # 抽取最优特征轴的列向量
  33. uniqueVals = set([data[bestFeat] for data in dataSet]) # 去重
  34. for value in uniqueVals:
  35. subLabels = labels[:] #将删除后的特征类别集建立子类别集
  36. splitDataset = self.splitDataSet(dataSet, bestFeat, value) # 按最优特征列和值分割数据集
  37. subTree = self.buildTree(splitDataset,subLabels) # 构建子树
  38. tree[bestFeatLabel][value] = subTree
  39. return tree
  40. def maxCate(self,catelist): # 计算出现最多的类别标签
  41. items = dict([(catelist.count(i), i) for i in catelist])
  42. return items[max(items.keys())]
  43. def getBestFeat(self,dataSet):
  44. # 计算特征向量维,其中最后一列用于类别标签,因此要减去
  45. numFeatures = len(dataSet[0]) - 1 # 特征向量维数 = 行向量维度-1
  46. baseEntropy = self.computeEntropy(dataSet) # 基础熵:源数据的香农熵
  47. bestInfoGain = 0.0; # 初始化最优的信息增益
  48. bestFeature = -1 # 初始化最优的特征轴
  49. # 外循环:遍历数据集各列,计算最优特征轴
  50. # i 为数据集列索引:取值范围 0~(numFeatures-1)
  51. for i in range(numFeatures): # 抽取第i列的列向量
  52. uniqueVals = set([data[i] for data in dataSet]) # 去重:该列的唯一值集
  53. newEntropy = 0.0 # 初始化该列的香农熵
  54. for value in uniqueVals: # 内循环:按列和唯一值计算香农熵
  55. subDataSet = self.splitDataSet(dataSet, i, value) # 按选定列i和唯一值分隔数据集
  56. prob = len(subDataSet)/float(len(dataSet))
  57. newEntropy += prob * self.computeEntropy(subDataSet)
  58. infoGain = baseEntropy - newEntropy # 计算最大增益
  59. if (infoGain > bestInfoGain): # 如果信息增益>0;
  60. bestInfoGain = infoGain # 用当前信息增益值替代之前的最优增益值
  61. bestFeature = i # 重置最优特征为当前列
  62. return bestFeature
  63. def computeEntropy(self,dataSet): # 计算香农熵
  64. datalen = float(len(dataSet))
  65. cateList = [data[-1] for data in dataSet] # 从数据集中得到类别标签
  66. items = dict([(i,cateList.count(i)) for i in cateList]) # 得到类别为key,出现次数value的字典
  67. infoEntropy = 0.0 # 初始化香农熵
  68. for key in items: # 计算香农熵
  69. prob = float(items[key])/datalen
  70. infoEntropy -= prob * math.log(prob,2) # 香农熵:= - p*log2(p) --infoEntropy = -prob * log(prob,2)
  71. return infoEntropy
  72. # 分隔数据集:删除特征轴所在的数据列,返回剩余的数据集
  73. # dataSet:数据集; axis:特征轴; value:特征轴的取值
  74. def splitDataSet(self, dataSet, axis, value):
  75. rtnList = []
  76. for featVec in dataSet:
  77. if featVec[axis] == value:
  78. rFeatVec = featVec[:axis] # list操作 提取0~(axis-1)的元素
  79. rFeatVec.extend(featVec[axis+1:]) # list操作 将特征轴(列)之后的元素加回
  80. rtnList.append(rFeatVec)
  81. return rtnList
  82. def predict(self,inputTree,featLabels,testVec): # 分类器
  83. root = list(inputTree.keys())[0] # 树根节点
  84. secondDict = inputTree[root] # value-子树结构或分类标签
  85. featIndex = featLabels.index(root) # 根节点在分类标签集中的位置
  86. key = testVec[featIndex] # 测试集数组取值
  87. valueOfFeat = secondDict[key] #
  88. if isinstance(valueOfFeat, dict):
  89. classLabel = self.predict(valueOfFeat, featLabels, testVec) # 递归分类
  90. else: classLabel = valueOfFeat
  91. return classLabel
  92. # 存储树到文件
  93. def storeTree(self,inputTree,filename):
  94. fw = open(filename,'w')
  95. pickle.dump(inputTree,fw)
  96. fw.close()
  97. # 从文件抓取树
  98. def grabTree(self,filename):
  99. fr = open(filename)
  100. return pickle.load(fr)

C4.5

C4.5 用的是信息增益率的概念

由上面,我们的信息增益:

  1. 𝐺𝑎𝑖𝑛 (𝑆,𝐴)= 𝐸𝑛𝑡𝑟𝑜𝑝𝑦(𝑆) ∑𝑣∈𝑉𝑎𝑙𝑢𝑒(𝐴) |𝑆𝑣|/ |𝑆| 𝐸𝑛𝑡𝑟𝑜𝑝𝑦 (𝑆𝑣)

信息增益率:

  1. 𝐺𝑎𝑖𝑛𝑅𝑎𝑡𝑖𝑜 𝐴 =𝐺𝑎𝑖𝑛(𝐴) / 𝐸𝑛𝑡𝑟𝑜𝑝𝑦(𝐴)

比原来多除了一个entropy(A):属性背后的信息熵

比如:前面的信息增益G(年龄)=0.9544-0.6877=0.2667

  1. 𝐺𝑎𝑖𝑛𝑅𝑎𝑡𝑖𝑜 𝐴=0.2667/0.6877

除以一个信息熵有什么好处呢?

如果只考虑信息增益的话,我们没有考虑A集合背后的大小,A越大也就意味着集合越混乱的可能性越高,那么C4.5考虑到了这种情况,给集合的混乱度做一个中和

C4.5实践:

  1. class C45DTree(object):
  2. def __init__(self):
  3. self.tree={}
  4. self.dataSet=[]
  5. self.labels=[]
  6. def loadDataSet(self,path,labels):
  7. recordlist = []
  8. fp = open(path,"r")
  9. content = fp.read()
  10. fp.close()
  11. rowlist = content.splitlines()
  12. recordlist=[row.split("\t") for row in rowlist if row.strip()]
  13. self.dataSet = recordlist
  14. self.labels = labels
  15. def train(self):
  16. labels = copy.deepcopy(self.labels)
  17. self.tree = self.buildTree(self.dataSet,labels)
  18. def buildTree(self,dataSet,labels):
  19. cateList = [data[-1] for data in dataSet]
  20. if cateList.count(cateList[0]) == len(cateList):
  21. return cateList[0]
  22. if len(dataSet[0]) == 1:
  23. return self.maxCate(cateList)
  24. bestFeat, featValueList = self.getBestFeat(dataSet)
  25. bestFeatLabel = labels[bestFeat]
  26. tree = {bestFeatLabel:{}}
  27. del(labels[bestFeat])
  28. for value in featValueList:
  29. subLabels = labels[:]
  30. splitDataset = self.splitDataSet(dataSet, bestFeat, value)
  31. subTree = self.buildTree(splitDataset,subLabels)
  32. tree[bestFeatLabel][value] = subTree
  33. return tree
  34. def maxCate(self,catelist):
  35. items = dict([(catelist.count(i), i) for i in catelist])
  36. return items[max(items.keys())]
  37. def getBestFeat(self, dataSet):
  38. Num_Feats = len(dataSet[0][:-1])
  39. totality = len(dataSet)
  40. BaseEntropy = self.computeEntropy(dataSet)
  41. ConditionEntropy = [] # 初始化条件熵
  42. splitInfo = [] # for C4.5, calculate gain ratio
  43. allFeatVList=[]
  44. for f in range(Num_Feats):
  45. featList = [example[f] for example in dataSet]
  46. [splitI,featureValueList] = self.computeSplitInfo(featList)
  47. allFeatVList.append(featureValueList)
  48. splitInfo.append(splitI)
  49. resultGain = 0.0
  50. for value in featureValueList:
  51. subSet = self.splitDataSet(dataSet, f, value)
  52. appearNum = float(len(subSet))
  53. subEntropy = self.computeEntropy(subSet)
  54. resultGain += (appearNum/totality)*subEntropy
  55. ConditionEntropy.append(resultGain) # 总条件熵
  56. infoGainArray = BaseEntropy*ones(Num_Feats)-array(ConditionEntropy)
  57. infoGainRatio = infoGainArray/array(splitInfo) # c4.5, info gain ratio
  58. bestFeatureIndex = argsort(-infoGainRatio)[0]
  59. return bestFeatureIndex, allFeatVList[bestFeatureIndex]
  60. def computeSplitInfo(self, featureVList):
  61. numEntries = len(featureVList)
  62. featureVauleSetList = list(set(featureVList))
  63. valueCounts = [featureVList.count(featVec) for featVec in featureVauleSetList]
  64. # caclulate shannonEnt
  65. pList = [float(item)/numEntries for item in valueCounts ]
  66. lList = [item*math.log(item,2) for item in pList]
  67. splitInfo = -sum(lList)
  68. return splitInfo, featureVauleSetList
  69. def computeEntropy(self,dataSet):
  70. datalen = float(len(dataSet))
  71. cateList = [data[-1] for data in dataSet]
  72. items = dict([(i,cateList.count(i)) for i in cateList])
  73. infoEntropy = 0.0
  74. for key in items:
  75. prob = float(items[key])/datalen
  76. infoEntropy -= prob * math.log(prob,2)
  77. return infoEntropy
  78. def splitDataSet(self, dataSet, axis, value):
  79. rtnList = []
  80. for featVec in dataSet:
  81. if featVec[axis] == value:
  82. rFeatVec = featVec[:axis]
  83. rFeatVec.extend(featVec[axis+1:])
  84. rtnList.append(rFeatVec)
  85. return rtnList
  86. # 树的后剪枝
  87. # testData: 测试集
  88. def prune(tree, testData):
  89. pass
  90. def predict(self,inputTree,featLabels,testVec):
  91. root = list(inputTree.keys())[0]
  92. secondDict = inputTree[root]
  93. featIndex = featLabels.index(root)
  94. key = testVec[featIndex]
  95. valueOfFeat = secondDict[key] #
  96. if isinstance(valueOfFeat, dict):
  97. classLabel = self.predict(valueOfFeat, featLabels, testVec)
  98. else: classLabel = valueOfFeat
  99. return classLabel
  100. def storeTree(self,inputTree,filename):
  101. fw = open(filename,'w')
  102. pickle.dump(inputTree,fw)
  103. fw.close()
  104. def grabTree(self,filename):
  105. fr = open(filename)
  106. return pickle.load(fr)

之前的ID3和C4.5停止条件都是结点数据类型一致(即纯净),要不就是没有特征可以选择了,但是只有这两个条件限制,在特征多树学习过深的情况下,模型容易过拟合

防止过拟合:

  • 停止条件:
    信息增益(比例)增长低于阈值,则停止

– 阈值需要调校
将数据分为训练集和测试集

– 测试集上错误率增长,则停止

剪枝:

  • 相邻的叶子节点,如果合并后不纯度增加在允许范围内,则合并

  • 测试集错误率下降,则合并 等等

sklearn- tree实践:

  1. from sklearn.tree import DecisionTreeRegressor
  2. # Fit regression model
  3. regr_2 = DecisionTreeRegressor(max_depth=10)#深度阈值
  4. print >> sys.stderr, 'begin train ==============='
  5. regr_2.fit(X, y)
  6. print >> sys.stderr, 'begin predict ==============='
  7. # Predict
  8. y_2 = regr_2.predict(X_test)
  9. for p_label in y_2:
  10. print p_label #输出预测标签(回归值)
  11. print >> sys.stderr, 'end ==============='

多分类用MSE做评估:

  1. import sys
  2. from sklearn import metrics
  3. import numpy as np
  4. import random
  5. res_file = sys.argv[1]
  6. y_test = []
  7. y_pred = []
  8. with open(res_file, 'r') as fd:
  9. for line in fd:
  10. ss = line.strip().split('\t')
  11. if len(ss) != 2:
  12. continue
  13. t_label = float(ss[0].strip())
  14. p_label = float(ss[1].strip())
  15. y_test.append(t_label)
  16. y_pred.append(p_label)
  17. print "MSE:",metrics.mean_squared_error(y_test, y_pred)
  18. print "RMSE:",np.sqrt(metrics.mean_squared_error(y_test, y_pred))

模型融合Ensemble Learning(集成学习)

什么是集成学习呢?假设我有多个模型,每个模型的学习准确率都是80%,或者说每个模型的准确率有学习好的有学习坏的,这时就有很多数据没有预估正确,那么我使用多个模型来共同决策,少数服从多数,通过这种方式提升准确率,把多个弱分类器集成获得一个强分类器。

集成学习里面有两类算法:Bagging和Boosting算法

Bagging算法是以random forest随机森林为代表的算法

Boosting算法是以AdaBoost算法和Gbdt为代表的算法

通过介绍我们知道这是将多个模型融合,得到一个强模型的方法,那么我们是如何证明这种方式是否靠谱呢?

用数学上的排列组合问题表示,假设二分类问题有5个精确度为70%的弱分类器,相互独立

采用投票的方式将5个分类器的结果进行集成:

  1. 𝐶5 3(0.73)(0.32)+ 𝐶5 4(0.74)(0.31)+ 𝐶5 5(0.71)
  2. = 10(.7^3)(.3^2)+5(.7^4)(.3)+(.7^5) = 83.7%

如果有101个分类器,精确度就可以提升到99.9%

  • 问题:如何得到不同的分类器,并且尽量独立? (每个模型不尽相同,不相互影响)

  • 方法:将一个复杂的学习问题,分解成多个简单的学习问题

Bagging

– 对训练数据采样,得到多个训练集,分别训练模型

– 对得到的多个模型进行平均
分类问题:投票方式进行集成
回归问题:模型的输出进行平均

训练:同一训练数据随机抽取数据并行用不同的机器和模型同时训练

测试:对模型预测出的各个分数进行投票或者平均

适合弱分类器(容易过拟合,效果一般):

不稳定:随机采样会得到较不同的分类器,每个基分类器准确率略高于50%

例如:决策树

不适合强分类器(本身准确率高,效果较好) :

稳定:随机采样对结果影响不大

甚至可能不如不集成,每个基分类器只有更少的训练样本

例如:DNN(模型复杂时,时间成本大并且容易过拟合,为了抗过拟合,最有效的办法就是让训练数据越大越好,和bagging的选取数据思想不同)

Random Forest

Random Forest = Bagging+决策树

随机森林生成方法:

– 从样本集中通过重采样的方式产生n个样本

– 建设样本特征数目为a,对n个样本选择a中的k个特征,用建立决策树的方式获得最佳分割点

– 重复m次,产生m棵决策树

– 多数投票机制进行预测

每棵树样本不同,选择特征可能不同,形成树的形状、深度都不同。

优点:

  • 训练速度快、容易实现并行

  • 训练完成后,反馈哪些是重要特征(类似LR中的特征权重,这里的决策树靠信息熵筛选)

  • 抗过拟合能力强 (多棵树抗过拟合)

  • 可解决分类、回归问题

  • 不平衡数据集,rf是平衡误差有效方法

缺点:

  • 取值较多的属性,影响大

  • 噪音大的分类、回归问题会过拟合

  • 只能尝试参数和种子的选择,无法洞悉内部

Boosting

每个基分类器,基于之前的分类结果,已经分对的样本不用太多关心,集中力量对付分错样本,不等权投票,好的基分类器权重大

– 对训练数据集进行采样,对错分样本加权采样

– 多个模型按照分类效果加权融合

训练:同一训练数据随机抽取数据串行用不同的机器和模型进行训练,首先模型一学习预估评分后,对出错的样本,提高采样权重,用模型二再次随机抽取数据,这时由于出错样本采样权重提高重点会放在出错的样本上,以此类推串行迭代下去

测试:对各个模型预测出的各个分数乘以对应的错误率权重公式相加

Boosting方法的两类思想:

传统Boost→ Adaboost:对正确、错分样本加权,每步的迭代,错误样本加权,正确样本降权

Gradient Boosting:每次建模建立在之前模型损失函数的梯度下降方向

Adaboost

对不同的模型分数加权求和,错误样本加权,正确样本降权

  • 给数据中的每一个样本一个权重

  • 训练数据中的每一个样本,得到第一个分类器

  • 计算该分类器的错误率,根据错误率计算要给分类器分配的权重(注意这里是分类器的权重) 即前面表示的每个模型预估后的分数乘对应的分类器权重求和

错误率:
𝜀 =未正确分类的样本数目/ 所有样本数目
分类器权重:
𝛼 =1/ 2 ln((1 – 𝜀)/ 𝜀)

  • 将第一个分类器分错误的样本权重增加,分对的样本权重减小(注意这里是样本的权重)

错分样本权重:𝐷𝑖^ (𝑡+1) = 𝐷𝑖 ^(𝑡)𝑒^𝑎 /𝑆𝑢𝑚(𝐷),Di^t表示上一次样本的权重,分母:训练集的规模,样本个数

正确样本权重:𝐷𝑖^ (𝑡+1) = 𝐷𝑖^ (𝑡)𝑒^(−𝑎) / 𝑆𝑢𝑚(𝐷)

  • 然后再用新的样本权重训练数据,得到新的分类器,到步骤3

  • 直到步骤3中分类器错误率为0,或者到达迭代次数

  • 将所有弱分类器加权求和,得到分类结果(注意是分类器权重)

GBDT(Gradient Boosting Decision Tree)

是一种迭代的决策树算法,该算法由多棵决策树组,所有树的结论累加起来做最终答案

三个概念组成:

– Regression Decistion Tree(DT、RT)

– Gradient Boosting(GB)

– Shrinkage(步长)

分类树:衡量标准最大熵——预测分类标签值

回归树:衡量标准是最小化均方差——预测实际数值

GBDT中的树,属于回归树,不是分类树
– 要求所有树的结果进行累加是有意义的
– GBDT是结果累加,而不是多数投票

GBDT核心:
– 每一棵树学的是之前所有树结论和的残差
– 残差就是一个加预测值后能得真实值的累加量

残差是什么?

因为是回归树,那么我的节点值应该是这个节点的平均值即预估值,那么来了一个人落在左边这个节点就是15岁,但是对于某些人预估的大了,有的预估的小了,那我把残差整理成新的样本再建一棵树重新学习,尽量把残差学没了,就像以前的误差值,尽力把它学为0

这里同样的,特征选择时也是利用信息熵的方式。

残差是学习的最优方向,体现了Gradient

Gradient Descend梯度下降实际上是在更新参数,并且最终参数等于每次迭代的增量的累加和:

  1. 𝜃𝑡 = 𝜃𝑡−1 + 𝜃𝑡
  2. 𝜃𝑡 = −𝑎𝑡𝑔𝑡 参数更新方向为负梯度方向
  3. 𝜃 =∑𝜃𝑡 最终参数等于每次迭代的增量的累加和, 𝜃0为初值

Gradient Boost是在更新整个函数

  1. 𝑓𝑡 𝑥 = 𝑓𝑡−1 𝑥 + 𝑓𝑡 (𝑥)
  2. 𝑓𝑡(𝑥) = −𝑎𝑡𝑔𝑡(𝑥) 类似地,拟合了负梯度
  3. 𝐹(𝑥) = ∑𝑓𝑡(𝑥) 最终参数等于每次迭代的增量的累加和 𝑓0(𝑥)为模型初值,通常为函数
  4. 所以,Boosting是一种加法模型

GBDT模型F定义为加法模型

  1. 𝐹 (𝑥;𝑤) = ∑𝑎𝑡 ℎ𝑡(𝑥;𝑤𝑡)= 𝑓𝑡(𝑥;𝑤𝑡)

x为输入样本,h为分类回归树,w是分类回归树的参数,a是每棵树的权重

2.1的公式是loss值更新函数预估值,2.2是求残差

但是这样的加法模式比较容易过拟合,这就涉及到了Shrinkage缩减技术:

思想:每棵树只学到了真理的一小部分,累加的时候只累加一小部分,通过多学几棵树弥补不足

假设: yi表示第i棵树上y的预测值, y(1~i)表示前i棵树y的综合预测值

没有shrinkage时:

– y(i+1) = 残差(y1~yi), 其中: 残差(y1~yi) = y真实值 - y(1 ~ i)

– y(1 ~ i) = SUM(y1, …, yi)

有shrinkage时:

– 第2个方程调整为:y(1 ~ i) = y(1 ~ i-1) + step /* yi

即对每次的结果都乘一个权重

仍然以残差作为学习目标,但对于残差的学习,每次只累加一小部分(step残差)逐步逼近目标

本质上,Shrinkage为每棵树设置了一个weight,累加时要乘以这个weight

偏差、方差

偏差bias:模型不准确

尽量选择正确模型与复杂度

模型越简单,预估能力越差,偏差越大,方差越低

方差variance:模型不稳定

防止过拟合

我们的模型融合是可以同时把方差与偏差同时降低的方法。

RF实践:

sklearn - rf:

测试数据:

  1. import sys
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. import pandas as pd
  5. # Importing the datasets
  6. datasets = pd.read_csv('Position_Salaries.csv')#pandas自动解析读取
  7. X = datasets.iloc[:, 1:2].values #级别
  8. Y = datasets.iloc[:, 2].values #金额
  9. # Fitting the Regression model to the dataset
  10. from sklearn.ensemble import RandomForestRegressor
  11. regressor = RandomForestRegressor(n_estimators = 300, random_state = 0)
  12. #300颗树
  13. regressor.fit(X,Y)
  14. # Predicting a new result with the Random Forest Regression
  15. Y_Pred = regressor.predict([[6.5]])
  16. print(Y_Pred)
  17. # Visualising the Random Forest Regression results in higher resolution and smoother curve
  18. X_Grid = np.arange(min(X), max(X), 0.01)
  19. X_Grid = X_Grid.reshape((len(X_Grid), 1))
  20. plt.scatter(X,Y, color = 'red')
  21. plt.plot(X_Grid, regressor.predict(X_Grid), color = 'blue')
  22. plt.title('Random Forest Regression Results')
  23. plt.xlabel('Position level')
  24. plt.ylabel('Salary')
  25. plt.show()

output:

pyspark-mllib-rf:

测试数据:

  1. #coding=utf8
  2. from __future__ import print_function
  3. import sys
  4. from pyspark import SparkContext, SparkConf
  5. from pyspark.mllib.regression import LabeledPoint
  6. from pyspark.mllib.feature import HashingTF,IDF,StandardScaler
  7. from pyspark.mllib.classification import LogisticRegressionWithSGD,SVMWithSGD,NaiveBayes
  8. from pyspark.mllib.tree import DecisionTree
  9. from pyspark.mllib.tree import RandomForest, RandomForestModel
  10. from pyspark.mllib.util import MLUtils
  11. reload(sys)
  12. sys.setdefaultencoding('utf-8')
  13. if __name__ == "__main__":
  14. #conf = SparkConf().setMaster("spark://master:7077").setAppName("lr_pyspark_test")
  15. conf = SparkConf().setMaster("local").setAppName("lr_pyspark_test")
  16. sc = SparkContext(conf=conf)
  17. #in_file = sc.textFile("file:///root/spark_test_5/pyspark_test/lr_pyspark_test/data/a8a")
  18. data = MLUtils.loadLibSVMFile(sc, "file:///root/spark_test_5/pyspark_test/lr_pyspark_test/data/a8a")
  19. (trainingData, testData) = data.randomSplit([0.7, 0.3])
  20. model = RandomForest.trainClassifier(\
  21. trainingData, numClasses=2, \
  22. categoricalFeaturesInfo={},\
  23. numTrees=3, \
  24. featureSubsetStrategy="auto",\
  25. impurity='gini', maxDepth=4, maxBins=32)
  26. #3颗树,gini系数方式
  27. predictions = model.predict(testData.map(lambda x: x.features))
  28. labelsAndPredictions = testData.map(lambda lp: lp.label).zip(predictions)
  29. testErr = labelsAndPredictions.filter(lambda (v, p): v != p).count() / float(testData.count())
  30. print('Test Error = ' + str(testErr))
  31. print('Learned classification forest model:')
  32. print(model.toDebugString())
  33. ## Save and load model
  34. #model.save(sc,"target/tmp/myRandomForestClassificationModel")
  35. #sameModel = RandomForestModel.load(sc, "target/tmp/myRandomForestClassificationModel")
  36. sc.stop()

GBDT实践:

微软lightgbm方式:

  1. import sys
  2. import lightgbm as lgb #pip安装
  3. import numpy as np
  4. from scipy.sparse import csr_matrix
  5. import pandas as pd
  6. from sklearn.metrics import mean_squared_error
  7. from sklearn.datasets import load_iris
  8. from sklearn.model_selection import train_test_split
  9. from sklearn.datasets import make_classification
  10. data_in = 'D:\\bd\\share_folder\\a9a'
  11. def load_data():
  12. target_list = []
  13. fea_row_list = []
  14. fea_col_list = []
  15. data_list = []
  16. row_idx = 0
  17. max_col = 0
  18. with open(data_in, 'r') as fd:
  19. for line in fd:
  20. ss = line.strip().split(' ')
  21. label = ss[0]
  22. fea = ss[1:]
  23. target_list.append(int(label))
  24. for fea_score in fea:
  25. sss = fea_score.strip().split(':')
  26. if len(sss) != 2:
  27. continue
  28. feature, score = sss
  29. fea_row_list.append(row_idx)
  30. fea_col_list.append(int(feature))
  31. data_list.append(float(score))
  32. if int(feature) > max_col:
  33. max_col = int(feature)
  34. row_idx += 1
  35. row = np.array(fea_row_list)
  36. col = np.array(fea_col_list)
  37. data = np.array(data_list)
  38. fea_datasets = csr_matrix((data, (row, col)), shape=(row_idx, max_col + 1))
  39. x_train, x_test, y_train, y_test = train_test_split(fea_datasets, target_list, test_size=0.2, random_state=0)
  40. return x_train, x_test, y_train, y_test
  41. X_train,X_test,y_train,y_test = load_data()
  42. # 创建成lgb特征的数据集格式
  43. lgb_train = lgb.Dataset(X_train, y_train)
  44. lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
  45. params = {
  46. 'task': 'train',
  47. 'boosting_type': 'gbdt', # 设置提升类型
  48. 'objective': 'regression', # 目标函数
  49. 'metric': {'l2', 'auc'}, # 评估函数
  50. 'num_leaves': 31, # 叶子节点数
  51. 'learning_rate': 0.05, # 学习速率
  52. 'feature_fraction': 0.9, # 建树的特征选择比例
  53. 'bagging_fraction': 0.8, # 建树的样本采样比例
  54. 'bagging_freq': 5, # k 意味着每 k 次迭代执行bagging
  55. 'verbose': 1 # <0 显示致命的, =0 显示错误 (警告), >0 显示信息
  56. }
  57. print('Start training...')
  58. # 训练 cv and train
  59. gbm = lgb.train(params,lgb_train,num_boost_round=20,valid_sets=lgb_eval,early_stopping_rounds=5)
  60. print('Save model...')
  61. # 保存模型到文件
  62. gbm.save_model('model.txt')
  63. print('Start predicting...')
  64. # 预测数据集
  65. y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)
  66. print(y_test)
  67. print(y_pred)
  68. # 评估模型
  69. print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5)

部分模型output:

上传的附件
最近文章
eject