forked from wepe/MachineLearning
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathid3_c45.py
More file actions
247 lines (208 loc) · 9.03 KB
/
Copy pathid3_c45.py
File metadata and controls
247 lines (208 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 10 22:04:33 2015
@author: wepon
"""
import numpy as np
class DecisionTree:
"""决策树使用方法:
- 生成实例: clf = DecisionTrees(). 参数mode可选,ID3或C4.5,默认C4.5
- 训练,调用fit方法: clf.fit(X,y). X,y均为np.ndarray类型
- 预测,调用predict方法: clf.predict(X). X为np.ndarray类型
- 可视化决策树,调用showTree方法
"""
def __init__(self,mode='C4.5'):
self._tree = None
if mode == 'C4.5' or mode == 'ID3':
self._mode = mode
else:
raise Exception('mode should be C4.5 or ID3')
def _calcEntropy(self,y):
"""
函数功能:计算熵
参数y:数据集的标签
"""
num = y.shape[0]
#统计y中不同label值的个数,并用字典labelCounts存储
labelCounts = {}
for label in y:
if label not in labelCounts.keys(): labelCounts[label] = 0
labelCounts[label] += 1
#计算熵
entropy = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/num
entropy -= prob * np.log2(prob)
return entropy
def _splitDataSet(self,X,y,index,value):
"""
函数功能:返回数据集中特征下标为index,特征值等于value的子数据集
"""
ret = []
featVec = X[:,index]
X = X[:,[i for i in range(X.shape[1]) if i!=index]]
for i in range(len(featVec)):
if featVec[i]==value:
ret.append(i)
return X[ret,:],y[ret]
def _chooseBestFeatureToSplit_ID3(self,X,y):
"""ID3
函数功能:对输入的数据集,选择最佳分割特征
参数dataSet:数据集,最后一列为label
主要变量说明:
numFeatures:特征个数
oldEntropy:原始数据集的熵
newEntropy:按某个特征分割数据集后的熵
infoGain:信息增益
bestInfoGain:记录最大的信息增益
bestFeatureIndex:信息增益最大时,所选择的分割特征的下标
"""
numFeatures = X.shape[1]
oldEntropy = self._calcEntropy(y)
bestInfoGain = 0.0
bestFeatureIndex = -1
#对每个特征都计算一下infoGain,并用bestInfoGain记录最大的那个
for i in range(numFeatures):
featList = X[:,i]
uniqueVals = set(featList)
newEntropy = 0.0
#对第i个特征的各个value,得到各个子数据集,计算各个子数据集的熵,
#进一步地可以计算得到根据第i个特征分割原始数据集后的熵newEntropy
for value in uniqueVals:
sub_X,sub_y = self._splitDataSet(X,y,i,value)
prob = len(sub_y)/float(len(y))
newEntropy += prob * self._calcEntropy(sub_y)
#计算信息增益,根据信息增益选择最佳分割特征
infoGain = oldEntropy - newEntropy
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeatureIndex = i
return bestFeatureIndex
def _chooseBestFeatureToSplit_C45(self,X,y):
"""C4.5
ID3算法计算的是信息增益,C4.5算法计算的是信息增益比,对上面ID3版本的函数稍作修改即可
"""
numFeatures = X.shape[1]
oldEntropy = self._calcEntropy(y)
bestGainRatio = 0.0
bestFeatureIndex = -1
#对每个特征都计算一下gainRatio=infoGain/splitInformation
for i in range(numFeatures):
featList = X[:,i]
uniqueVals = set(featList)
newEntropy = 0.0
splitInformation = 0.0
#对第i个特征的各个value,得到各个子数据集,计算各个子数据集的熵,
#进一步地可以计算得到根据第i个特征分割原始数据集后的熵newEntropy
for value in uniqueVals:
sub_X,sub_y = self._splitDataSet(X,y,i,value)
prob = len(sub_y)/float(len(y))
newEntropy += prob * self._calcEntropy(sub_y)
splitInformation -= prob * np.log2(prob)
#计算信息增益比,根据信息增益比选择最佳分割特征
#splitInformation若为0,说明该特征的所有值都是相同的,显然不能作为分割特征
if splitInformation==0.0:
pass
else:
infoGain = oldEntropy - newEntropy
gainRatio = infoGain/splitInformation
if(gainRatio > bestGainRatio):
bestGainRatio = gainRatio
bestFeatureIndex = i
return bestFeatureIndex
def _majorityCnt(self,labelList):
"""
函数功能:返回labelList中出现次数最多的label
"""
labelCount={}
for vote in labelList:
if vote not in labelCount.keys(): labelCount[vote] = 0
labelCount[vote] += 1
sortedClassCount = sorted(labelCount.iteritems(),key=lambda x:x[1], reverse=True)
return sortedClassCount[0][0]
def _createTree(self,X,y,featureIndex):
"""建立决策树
featureIndex,类型是元组,它记录了X中的特征在原始数据中对应的下标。
"""
labelList = list(y)
#所有label都相同的话,则停止分割,返回该label
if labelList.count(labelList[0]) == len(labelList):
return labelList[0]
#没有特征可分割时,停止分割,返回出现次数最多的label
if len(featureIndex) == 0:
return self._majorityCnt(labelList)
#可以继续分割的话,确定最佳分割特征
if self._mode == 'C4.5':
bestFeatIndex = self._chooseBestFeatureToSplit_C45(X,y)
elif self._mode == 'ID3':
bestFeatIndex = self._chooseBestFeatureToSplit_ID3(X,y)
bestFeatStr = featureIndex[bestFeatIndex]
featureIndex = list(featureIndex)
featureIndex.remove(bestFeatStr)
featureIndex = tuple(featureIndex)
#用字典存储决策树。最佳分割特征作为key,而对应的键值仍然是一棵树(仍然用字典存储)
myTree = {bestFeatStr:{}}
featValues = X[:,bestFeatIndex]
uniqueVals = set(featValues)
for value in uniqueVals:
#对每个value递归地创建树
sub_X,sub_y = self._splitDataSet(X,y, bestFeatIndex, value)
myTree[bestFeatStr][value] = self._createTree(sub_X,sub_y,featureIndex)
return myTree
def fit(self,X,y):
#类型检查
if isinstance(X,np.ndarray) and isinstance(y,np.ndarray):
pass
else:
try:
X = np.array(X)
y = np.array(y)
except:
raise TypeError("numpy.ndarray required for X,y")
featureIndex = tuple(['x'+str(i) for i in range(X.shape[1])])
self._tree = self._createTree(X,y,featureIndex)
return self #allow chaining: clf.fit().predict()
def predict(self,X):
if self._tree==None:
raise NotFittedError("Estimator not fitted, call `fit` first")
#类型检查
if isinstance(X,np.ndarray):
pass
else:
try:
X = np.array(X)
except:
raise TypeError("numpy.ndarray required for X")
def _classify(tree,sample):
"""
用训练好的决策树对输入数据分类
决策树的构建是一个递归的过程,用决策树分类也是一个递归的过程
_classify()一次只能对一个样本(sample)分类
To Do: 多个sample的预测怎样并行化?
"""
featIndex = tree.keys()[0]
secondDict = tree[featIndex]
key = sample[int(featIndex[1:])]
valueOfkey = secondDict[key]
if isinstance(valueOfkey, dict):
label = _classify(valueOfkey,sample)
else: label = valueOfkey
return label
if len(X.shape)==1:
return _classify(self._tree,X)
else:
results = []
for i in range(X.shape[0]):
results.append(_classify(self._tree,X[i]))
return np.array(results)
def show(self):
if self._tree==None:
raise NotFittedError("Estimator not fitted, call `fit` first")
#plot the tree using matplotlib
import treePlotter
treePlotter.createPlot(self._tree)
class NotFittedError(Exception):
"""
Exception class to raise if estimator is used before fitting
"""
pass