#决策树算法
@(机器学习)[决策树算法]
1.什么是决策树:
二十个问题游戏:游戏的规则其中一个参与者想出一个事物,其他参与者通过提出问题来猜出事物,最多提出二十个问题,来缩小和确定物体。决策树的原理和二十个问题的原则很像。也是通过特征对数据进行归类。决策树(Decision Tree)及其变种是另一类将输入空间分成不同的区域,每个区域有独立参数的算法。决策树分类算法是一种基于实例的归纳学习方法,它可以使用不熟悉得数据集合,并从中提取出一系列得规则。树中的每个非叶子节点记录了使用哪个特征来进行类别的判断,每个叶子节点则代表了最后判断的类别。根节点到每个叶子节点均形成一条分类的路径规则。而对新的样本进行测试时,只需要从根节点开始,在每个分支节点进行测试,沿着相应的分支递归地进入子树再测试,一直到达叶子节点,该叶子节点所代表的类别即是当前测试样本的预测类别。每一个节点都是通过计算其熵来确定的。
2.树得组成:
根节点:第一个选择得节点(最重要大条件),在特质中可以起决定性左右的特征
非叶子节点:中间过程
叶子节点:最终得决策结果
节点:
添加节点相当于在数据中切了一刀(以什么条件进行分类数据)
越多得特征就是节点越多,数据得分类越多
3.决策树的优缺点
优点:计算复杂度不高,输出的结果易于理解,对中间值的缺失不敏感,可以处理不相关数据
缺点:可能产生过度匹配问题及过拟合问题
使用数据类型:数值型或者标称型
4.决策树得训练与测试
训练阶段:从给定得训练集构造出来一颗树(从跟节点开始选择特征),提取数据中的规则并创建规则的过程。
测试阶段:根据构造出来得树模型从上到下走一遍就好了
5.如何创建树
一旦构造好了抉择树,那么分类或者预测得任务就很简单了,只需要走一遍就行,难点在于如何构造出来一颗树.
5.1.选择节点(特征切分)
根节点应该是分类得效果最好得一个,就像选老大一样.通过一种衡量标准,来计算通过不同特征进行分支选择后得分类情况,找到最好得那个当作根节点。
#### 5.2.一般步骤
1)计算信息增益
2)划分数据集
3)递归创建决策树
5.3.衡量标准
5.3.1信息量
信息量是表达一段信息所能传递的信息量的大小,就是能让人获取到信息的多少。越不确定,越不可能发生,概率就越小,信息量也就越大,也就是信息越多。比如说“今天肯定会天黑”,实现概率100%,说了和没说差不多,信息量就是0。表达式如下
l ( x i ) = − log 2 p ( x i ) l(x_i)=-\log_2p(x_i) l(xi)=−log2p(xi)
其中 x i x_i xi代指信息, p ( x i ) p(x_i) p(xi)是 x i x_i xi的概率
5.3.2熵
熵:熵是表示随机变量不确定得度量(解释:说白了就是物体内部得混乱程度,比如杂货商店里面什么都有那一定混乱,专卖店只卖一个牌子一定稳定得多)
公式如下:
H
(
x
)
=
−
∑
i
=
1
n
P
(
x
i
)
log
2
P
(
x
i
)
H(x) = -\sum_{i=1}^n P(x_i)\log_2P(x_i)
H(x)=−i=1∑nP(xi)log2P(xi)
其中 P ( x i ) P(x_i) P(xi)是选择该分类的概率
-
熵:不确定性越大,得到得熵值也就越大,如下图
当p=0或1时,H§ = 0,随机变量完全没有不确定性
当p=0.5时,H§ = 1,此时随机变量得不确定性最大
5.2.例子:
A集合[1,1,1,1,1,1,1,1,1,2,2]
B集合[1,2,3,4,5,6,7,8,9,1,2]
显示A集合得熵值要低,因为A得类别少相对稳定一些而B中得类别太多,熵值就会很大 -
代码实现
#信息熵,对数据集的要求必须是由列元素组成的列表,长度相同,最后一列为标签列 def calcShannonEnt(dataSet): #获取数据总数 numEntries = len(dataSet) #标签数组 labelCounts={} #循环统计每一个标签得个数 for featVec in dataSet: #获取当前标签 currentLabel = featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel]=0 labelCounts[currentLabel]+=1 shannonEnt=0.0 #计算熵 for key in labelCounts: #获取每一个标签得概率 pro = float(labelCounts[key])/numEntries shannonEnt -= pro*log(pro,2) return shannonEnt
5.3.3 ID3信息增益
信息增益:表示特征X使得Y得不确定性减少得程度。是信息量的差值。也就是说,一开始是A,用了条件后变成了B,则条件引起的变化是A-B,即信息增益(它描述的是变化Delta)。好的条件就是信息增益越大越好,即变化完后熵越小越好(熵代表混乱程度,最大程度地减小了混乱)。因此我们在树分叉的时候,应优先使用信息增益最大的属性,这样降低了复杂度,也简化了后边的逻辑。
g ( o u t l o o k 信 息 增 益 ) = g ( 总 体 的 熵 ) − g ( 特 征 熵 ) g(outlook信息增益) = g(总体的熵)-g(特征熵) g(outlook信息增益)=g(总体的熵)−g(特征熵)
3.1 信息增益计算
案例:
数据如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fMqG94f-1571228483441)(C:\Users\lv\Desktop\截图/决策树案例1.PNG)]
1计算总体的熵:
在历史数据中(14天)有9天打球,5天不打球,所以此时的熵应为
g
(
x
)
=
−
9
14
log
2
9
14
−
5
14
log
2
5
14
=
0.940
g(x)=-\frac{9}{14}\log_2\frac{9}{14}-\frac{5}{14}\log_2\frac{5}{14}=0.940
g(x)=−149log2149−145log2145=0.940
2计算特征的熵值(以outlook特征为例)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YywatxiD-1571228483445)(C:\Users\lv\Desktop\截图/决策树案例2.PNG)]
Outlook = sunny时,熵值为0.971
−
2
5
log
2
2
5
−
3
5
log
35
=
0.971
-\frac{2}{5}\log_2\frac{2}{5}-\frac{3}{5}\log{3}{5}=0.971
−52log252−53log35=0.971
Outlook = overcast时,熵值为0
−
4
4
log
2
4
4
−
0
=
0
-\frac{4}{4}\log_2\frac{4}{4}-0=0
−44log244−0=0
Outlook = rainy时,熵值为0.971
3
5
log
2
3
5
−
2
5
log
2
2
5
=
0.971
\frac{3}{5}\log_2\frac{3}{5}-\frac{2}{5}\log_2\frac{2}{5}=0.971
53log253−52log252=0.971
根据数据统计,outlook取值分别为sunny,overcast,rainy的概率分别为:5/14, 4/14, 5/14
熵值计算:
5
14
∗
0.971
+
4
14
∗
0
+
5
14
∗
0.971
=
0.693
\frac{5}{14}*0.971+\frac{4}{14}*0+\frac{5}{14}*0.971=0.693
145∗0.971+144∗0+145∗0.971=0.693
3计算信息增益:
g
(
o
u
t
l
o
o
k
信
息
增
益
)
=
g
(
总
体
的
熵
)
−
g
(
特
征
熵
)
=
0.940
−
0.693
=
0.247
g(outlook信息增益) = g(总体的熵)-g(特征熵)=0.940-0.693 = 0.247
g(outlook信息增益)=g(总体的熵)−g(特征熵)=0.940−0.693=0.247
同样的方式可以计算出其他特征的信息增益,那么我们选择最大的那个就可以,相当于是遍历了一遍特征,找出来了大当家,然后再其余的中继续通过信息增益找二当家!
4 缺点:没有考虑到自身的熵,如果一个特征的取值比较稀少集中,类似与列号一样那么其熵就会很小但是没有意义
5代码实现
#选择最好的数据集划分方式
#dataSet:数据集
def choseBestFeatureToSplit(dataSet):
#获取特征得数目,处理标签之外得其他特征数目
unmFeature = len(dataSet[0])-1
# print("unmFeature:",unmFeature)
#计算整个数据集得熵
bestEntropy = calcShannonEnt(dataSet)
# print("bestEntropy:",bestEntropy)
bestInfoGain = 0.0
bestFeature = -1
for i in range(unmFeature):
#获取特征列表, 此处要求数据集得长度一致且最后一位为标签
featList = [example[i] for example in dataSet]
# print("第%d个得featList:"%i,featList)
#去掉重复得特征
uniqueVals = set(featList)
# print('uniqueVals:',uniqueVals)
newEntropy = 0.0
#遍历不重复得特征
for value in uniqueVals:
#按照i列得value值进行数据分割
subDataSet = splitDataSet(dataSet,i,value)
#所分割得子数据占总数据得比例
prob = len(subDataSet)/float(len(dataSet))
newEntropy +=prob*calcShannonEnt(subDataSet)
# print('newEntropy:',newEntropy)
#找到newEntropy最小得那个i
inforGain = bestEntropy-newEntropy #计算信息增益
if inforGain>bestInfoGain:
bestInfoGain=inforGain
bestFeature=i
return bestFeature
#划分数据集(就是找到符合条件得数据,把相关列删掉再拼接在一起)
#dataSet:数据集
#axis:要匹配得位置(列号)
#value:要匹配得值
def splitDataSet(dataSet,axis,value):
retDataSet=[]
for featVec in dataSet:
#判断featVec是否符合规定条件
if featVec[axis]==value:
# print("featVec:",featVec)
#获取axis之间得部分
reducedFeatVec = featVec[:axis]
# print('reducedFeatVec',reducedFeatVec)
#获取axis之后得部分
tem=featVec[axis+1:]
# print('tem:',tem)
#合并除去axis之外得数据
reducedFeatVec.extend(tem)
# print('reducedFeatVec 2',reducedFeatVec)
retDataSet.append(reducedFeatVec)
return retDataSet
5.3.4 C4.5:信息增益率(解决ID3问题,考虑自身熵)
4.1 那么下面来看信息增益存在的一个问题:假设某个属性存在大量的不同值,如ID编号(在上面例子中加一列为ID,编号为a~n),在划分时将每个值成为一个结点,这样就形成了下面的图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9tpP3Id-1571228483449)(C:\Users\lv\Desktop\截图/1364523479_1485.png)]
那么导致这样的偏差的原因是什么呢?从上面的例子应该能够感受出来,原因就是该特征可以选取的值过多。解决办法自然就想到了如何能够对节点分支过多的情况进行惩罚,这样就引入了公式来表示属性A的内部信息。
4.2 属性A的内部信息(Intrinsic Information of an Attribute)
I
n
t
I
(
Y
,
X
)
=
−
∑
i
X
i
Y
log
2
X
i
Y
IntI(Y,X) = -\sum_i\frac{X_i}{Y}\log_2\frac{X_i}{Y}
IntI(Y,X)=−i∑YXilog2YXi
注 : X 表 示 某 个 属 性 A 的 随 机 变 量 , ∣ X i ∣ 表 示 属 性 A 的 第 i 个 分 类 的 个 数 , \color{red}{注:X表示某个属性A的随机变量,\left | X_i \right |表示属性A的第i个分类的个数,} 注:X表示某个属性A的随机变量,∣Xi∣表示属性A的第i个分类的个数,
∣ Y ∣ 表 示 样 本 的 总 个 数 , ∣ X i ∣ ∣ Y ∣ 表 示 属 性 A 的 第 i 个 分 类 占 样 本 总 个 数 的 比 例 \color{red}{\left | Y \right |表示样本的总个数,\frac{\left | X_i \right |}{\left | Y \right |}表示属性A的第i个分类占样本总个数的比例} ∣Y∣表示样本的总个数,∣Y∣∣Xi∣表示属性A的第i个分类占样本总个数的比例
4.3 信息增益率
g
=
g
(
信
息
增
益
)
I
n
t
I
(
Y
,
X
)
g = \frac{g(信息增益)}{IntI(Y,X)}
g=IntI(Y,X)g(信息增益)
注 : 就 是 在 信 息 增 益 上 又 除 了 一 个 量 , 这 个 量 能 够 起 到 惩 罚 的 作 用 , 类 别 越 多 这 个 量 越 大 。 \color{red}{注:就是在信息增益上又除了一个量,这个量能够起到惩罚的作用,类别越多这个量越大。} 注:就是在信息增益上又除了一个量,这个量能够起到惩罚的作用,类别越多这个量越大。
如:
(1)Day日期的内部信息
I
n
t
I
(
D
a
y
)
=
14
∗
(
−
1
14
∗
log
2
1
14
)
=
3.8074
IntI(Day) = 14*(-\frac{1}{14}*\log_2\frac{1}{14})=3.8074
IntI(Day)=14∗(−141∗log2141)=3.8074
(2)Outlook天气的内部信息
I
n
t
I
(
O
u
t
l
o
o
k
)
=
−
5
14
log
2
5
14
−
4
14
log
2
4
14
−
5
14
log
2
5
14
=
1.5774
IntI(Outlook)=-\frac{5}{14}\log_2\frac{5}{14}-\frac{4}{14}\log_2\frac{4}{14}-\frac{5}{14}\log_2\frac{5}{14}=1.5774
IntI(Outlook)=−145log2145−144log2144−145log2145=1.5774
(3)Day日期信息的增益率
g
(
)
=
g
(
总
体
)
I
n
t
I
(
D
a
y
)
=
0.940
3.8094
=
1.3788
g() = \frac{g(总体)}{IntI(Day)}=\frac{0.940}{3.8094}=1.3788
g()=IntI(Day)g(总体)=3.80940.940=1.3788
(4)Outlook天气的信息增益率
g
(
)
=
g
(
o
u
t
l
o
o
k
增
益
)
I
n
t
i
(
o
u
t
l
o
o
k
)
=
0.3949
1.5774
=
0.2503
g()=\frac{g(outlook增益)}{Inti(outlook)}=\frac{0.3949}{1.5774}=0.2503
g()=Inti(outlook)g(outlook增益)=1.57740.3949=0.2503
5.3.5 CART:使用GINI系数来当衡量标准
GINI系数:
G
i
n
i
(
p
)
=
∑
k
=
1
k
P
k
(
1
−
P
k
)
=
1
−
∑
k
=
1
k
P
k
2
(
和
熵
得
衡
量
标
准
类
似
,
计
算
方
式
不
同
)
Gini(p) = \sum_{k=1}^kP_k(1-P_k)=1-\sum_{k=1}^kP_k^2(和熵得衡量标准类似,计算方式不同)
Gini(p)=k=1∑kPk(1−Pk)=1−k=1∑kPk2(和熵得衡量标准类似,计算方式不同)
(和熵得衡量标准类似,计算方式不同)
5.4创建树
代码实现
#创建决策树
def createTree(dataSet,labels):
#获取数据集得标签
classList = [example[-1] for example in dataSet]
print('classList:',classList)
#如果都是同标签退出
if classList.count(classList[0])==len(classList):
return classList[0]
#如果只有标签则退出
if len(dataSet[0])==1:
return majorityCnt(dataSet)
#获取最好的分割数据得分割点
bestFeat = choseBestFeatureToSplit(dataSet)
print('bestFeat',bestFeat)
#获取分割点得列名称,不是数据集中得标签类
bestFeatLabel = labels[bestFeat]
print('bestFeatLabel:',bestFeatLabel)
myTree = {bestFeatLabel:{}}
#删除列名称
del labels[bestFeat]
#获取最佳切割点的数据值组成列表
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
print('uniqueVals:',uniqueVals)
for value in uniqueVals:
subLables = labels[:]
tem = createTree(splitDataSet(dataSet,bestFeat,value),subLables)
myTree[bestFeatLabel][value] = tem
return myTree
#获取次数最多得分类名称
#classList:数据集
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys():
classCount[vote]=0
classCount[vote]+=1
# classCount[vote]=classCount.get(vote,-1)+1
soetedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return soetedClassCount[0][0]
5.5画图
由于对matplotlib还在学习中就直接上代码
import matplotlib.pyplot as plt
#设置文本框和箭头得样式
decisionNode = dict(boxstyle="sawtooth",fc='0.8')
leafNode = dict(boxstyle="round4",fc='0.8')
arrow_args = dict(arrowstyle='<-')
#绘制带箭头得标注
#nodeTxt:文本
#centerPt:文本得位置
#parentPt:点得位置
#nodeType:对方框得设置
def plotNode(nodeTxt,centerPt,parentPt,nodeType):
createPlot.ax1.annotate(nodeTxt,xy=parentPt,xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',
va='center',ha='center',bbox=nodeType,arrowprops=arrow_args)
def createPlot(inTree):
#设置一个绘图区域
fig=plt.figure(1,facecolor='white')
fig.clf()
axprops = dict(xticks=[],yticks=[])
createPlot.ax1=plt.subplot(111,frameon=False,**axprops)
#获取树得宽度(叶子节点得数目),高度
plotTree.totalW = float(getNumLeafs(inTree))
plotTree.totalD=float(getTreeDepth(inTree))
plotTree.xOff = -0.5/plotTree.totalW
plotTree.yOff=1.0
plotTree(inTree,(0.5,1.0),'')
plt.show()
#获取数得叶子节点
def getNumLeafs(myTree):
numLeafs = 0
firstStr = list(myTree.keys())[0]
seconDict = myTree[firstStr]
for key in seconDict.keys():
if type(seconDict[key]).__name__=='dict':
numLeafs+=getNumLeafs(seconDict[key])
else:
numLeafs+=1
return numLeafs
#获取树得层数
def getTreeDepth(myTree):
maxDepth=0
firstStr = list(myTree.keys())[0]
seconDict = myTree[firstStr]
for key in seconDict.keys():
if type(seconDict[key]).__name__=='dict':
thisDepth = 1+getTreeDepth(seconDict[key])
else:
thisDepth=1
if thisDepth>maxDepth:
maxDepth=thisDepth
return maxDepth
#在父子节点间显示文字 --文字--》
def plotMidText(cntrPt,parentPt,textString):
xMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[0]
yMid=(parentPt[1]-cntrPt[1])/2.0+cntrPt[1]
createPlot.ax1.text(xMid,yMid,textString)
#绘制图
#myTree:树结构
#parentPt:父节点位置
#nodeText:文字
def plotTree(myTree,parentPt,nodeTxt):
#计算高度和宽度
numLeafs = getNumLeafs(myTree)
depth = getTreeDepth(myTree)
firstStr = list(myTree.keys())[0]
#计算子节点位置
cntrPt = (plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,plotTree.yOff)
#向父子节点之间打印文字
plotMidText(cntrPt,parentPt,nodeTxt)
#标注文字
plotNode(firstStr,cntrPt,parentPt,decisionNode)
secondDict = myTree[firstStr]
#根据树得高度设置y坐标
plotTree.yOff = plotTree.yOff-1.0/plotTree.totalD
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':
plotTree(secondDict[key],cntrPt,str(key))
else:
#根据树得宽度设置x坐标
plotTree.xOff = plotTree.xOff+1.0/plotTree.totalW
plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)
plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key))
plotTree.yOff = plotTree.yOff+1.0/plotTree.totalD
5.6封装测试以保存模型
def classify(inputTree,featLabels, testVec ):
firstStr = list(inputTree.keys())[0]
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
"""
以下是保存和读取决策树模型
"""
#保存树模型
def storeTree(inputTree,filename):
import pickle
fw =open(filename,'wb+')
pickle.dump(inputTree,fw)
fw.close()
#读取树模型
def grabTree(filename):
import pickle
fw = open(filename,'rb+')
myTree = pickle.load(fw)
fw.close()
return myTree
6 特值是连续值
进行离散化就可以了
7.决策树剪枝策略:
为什么要剪枝:决策树过拟合风险很大,理论上可以完全得分开数据
剪枝策略:
1 预剪枝:边建立决策树边进行剪枝操作
限制深度,叶子节点个数,叶子节点样本数,信息增益量等
2 后剪枝:当建立完决策树后进行剪枝操作
C a ( T ) = C ( T ) + α ∗ ∣ T l e a f ∣ ( 叶 子 节 点 越 多 , 损 失 越 大 ) C_a(T) = C(T)+\alpha*|T_leaf| (叶子节点越多,损失越大) Ca(T)=C(T)+α∗∣Tleaf∣(叶子节点越多,损失越大)
C ( T ) 表 示 当 前 损 失 , 对 于 每 一 个 叶 子 节 点 来 说 , 用 叶 子 节 点 得 样 本 数 乘 上 熵 值 或 g i n n i 系 数 \color{red}{C(T)表示当前损失,对于每一个叶子节点来说,用叶子节点得样本数乘上熵值或ginni系数} C(T)表示当前损失,对于每一个叶子节点来说,用叶子节点得样本数乘上熵值或ginni系数
α ∗ ∣ T l e a f ∣ 要 限 制 叶 子 节 点 得 个 数 \color{red}{\alpha*|T_leaf| 要限制叶子节点得个数} α∗∣Tleaf∣要限制叶子节点得个数
8 .集成算法
8.1 目的
让机器学习效果更好
8.2 集成算法种类
##### 8.2.1 Bagging算法
全称是bootstrap aggregation ,训练多个分类器取平均值 f ( x ) = 1 M ∑ m = 1 M f m ( x ) f(x) = \frac{1}{M}\sum_{m=1}^M f_m(x) f(x)=M1∑m=1Mfm(x),就是并行训练一堆分类器。
最典型的代表就是随机森林,随机森林要做到数据采样随机,特征选择随机 (就是数量一样,内容不同),随机可以保证泛化能力较强
森林:很多决策树并行放在一起
随机森林的优势:
-
1.能够处理很高维度(feature很多)的数据,并且不用做特征选择
-
2.在训练完后,它能够给出哪些feature比较重要
-
3.容易做成并行化方法,速度比较快
-
4.可以进行可视化展示,便于分析
评估某个特征的重要性:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Z2XM573-1571228483457)(C:\Users\lv\Desktop\截图\特值评估.png)]
8.2.2 Boosting 算法
从弱学习器开始加强,通过加权来进行训练 F m ( x ) = F m − 1 ( x ) + a r g m i n n ∑ i = 1 n L ( y i , F m − 1 ( x i ) + h ( x i ) ) F_m(x)=F_{m-1}(x)+argmin_n\sum_{i=1}^n L(y_i,F_{m-1}(x_i)+h(x_i)) Fm(x)=Fm−1(x)+argminn∑i=1nL(yi,Fm−1(xi)+h(xi))(加入一棵树,要比原来的强)
代表算法:AdaBoost, Xgboost
Adaboost会根据前一次的分类效果调整数据权重
(如果某一个数据在这次分错了,那么在下一次我就会给它更大的权重)
最终的结果:每个分类器根据自身的准确性来确定各自的权重,再合体
8.2.3 Stacking 算法
聚合多个分类或回归模型 (KNN,SVM,RF等等 )可以分阶段来做(第一阶段得出各自结果,第二阶段再用前一阶段结果训练 )
初 步 学 习 , 总 结 理 解 , 若 有 错 误 , 还 望 指 点 , 谢 谢 \color{red}{初步学习,总结理解,若有错误,还望指点,谢谢} 初步学习,总结理解,若有错误,还望指点,谢谢