/
tree.py
296 lines (225 loc) · 11.4 KB
/
tree.py
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import math
import random
from node import Node
class Tree(object):
def __init__(self, attributes, attributes_types, target_class, instances):
self.attributes_types = attributes_types
self.attributes = attributes
self.target_class = target_class
self.instances = instances
self.decision_tree = None
def createDecisionTree(self):
if self.target_class in self.attributes:
self.attributes.remove(self.target_class)
self.decision_tree = self.decisionTree(self.instances, self.attributes, self.target_class)
def getBestAttribute(self, attributes, instances):
"""
Retorna o atributo com o maior ganho de informação, seguindo o algoritmo ID3.
"""
original_set_entropy = self.entropy(instances, self.target_class)
attributes_information_gain = {}
for attribute in attributes:
if self.attributes_types[attribute] == 'c':
#Pega todos os valores possíveis para o atributo em questão
possible_values = self.getDistinctValuesForAttribute(attribute, instances)
avg_entropy = 0
# Calcula entropia ponderada para cada subset originado a partir do atributo
for value in possible_values:
subset = self.getSubsetWithAttributeValue(attributes[i], value, instances)
entropy = self.entropy(subset, self.target_class)
weighted_entropy = float(entropy * (len(subset)/len(instances)))
avg_entropy = avg_entropy + weighted_entropy
info_gain = original_set_entropy - avg_entropy
attributes_information_gain[attribute] = info_gain
else:
# atributo numérico
# Gera os subconjuntos do atributo com base no seu ponto médio
values_sum = 0
for instance in instances:
values_sum = values_sum + float(instance[attribute])
avg_value = float(values_sum / len(instances))
subset_A, subset_B = self.getSubsetsForNumericAttribute(attribute, avg_value, instances)
subset_A_entropy = self.entropy(subset_A, self.target_class)
subset_B_entropy = self.entropy(subset_B, self.target_class)
weighted_entropy_A = float(subset_A_entropy * (len(subset_A)/len(instances)))
weighted_entropy_B = float(subset_B_entropy * (len(subset_B)/len(instances)))
weighted_entropy = weighted_entropy_A + weighted_entropy_B
info_gain = original_set_entropy - weighted_entropy
attributes_information_gain[attribute] = info_gain
best_attribute = max(attributes_information_gain, key=attributes_information_gain.get)
return best_attribute, attributes_information_gain[best_attribute]
def entropy(self, instances, target_class):
# Medida do grau de aleatoriedade de uma variável, dada em bits
# Está associada à dificuldade de predizer o atributo alvo a partir
# do atributo preditivo analisado.
possible_values = self.getDistinctValuesForAttribute(target_class, instances)
possible_values_count = [0 for i in range(len(possible_values))]
for i in range(len(possible_values)):
for j in range(len(instances)):
if instances[j][target_class] == possible_values[i]:
possible_values_count[i] = possible_values_count[i] + 1
entropy = 0
for v in possible_values_count:
percentage_of_values = float(v/len(instances))
partial_result = float(-1 * percentage_of_values * math.log2(percentage_of_values))
entropy = float(entropy + partial_result)
return entropy
def getMostFrequentClass(self, instances, target_class):
"""
Retorna a classificação mais frequente para target_class entre as
instances
"""
class_values = {}
for instance in instances:
value = instance[target_class]
if value in class_values:
class_values[value] = class_values[value] + 1
else:
class_values[value] = 1
return self.getItemWithMaxValue(class_values)
def getItemWithMaxValue(self, items):
"""
Retorna a chave do dicionário que possui o maior valor associado.
Exemplo: {'a': 1, 'b': 2, 'c': 3} -> Retorna 'c'
"""
return max(items, key=items.get)
def haveSameClass(self, instances, class_to_check):
"""
Retorna True se todos os elementos de instance têm a mesma classificação para
class_to_check. False, caso contrário
"""
class_value = instances[0][class_to_check]
for instance in instances:
if not instance[class_to_check] == class_value:
return False
return True
def getDistinctValuesForAttribute(self, attribute, instances):
"""
Retorna todos os valores distintos para um determinado atributo que estão
presentes em instances
"""
distinct_values = []
for instance in instances:
if instance[attribute] not in distinct_values:
distinct_values.append(instance[attribute])
return distinct_values
def getSubsetWithAttributeValue(self, attribute, value, instances):
"""
Retorna subconjunto com todas as intâncias que possuem o mesmo valor 'value'
para o atributo 'attribute'
"""
subset = []
for instance in instances:
if instance[attribute] == value:
subset.append(instance)
return subset
def getRandomAttributes(self, attributes, m):
"""
Retorna m atributos aleatórios da lista de atributos
valor default de m = sqrt(len(attributes))
"""
random_attributes = []
for x in range(m):
index = random.randint(0, len(attributes)-1)
random_attributes.append(attributes[index])
return random_attributes
def decisionTree(self, instances, attributes, target_class, top_edge=None):
"""
Função recursiva que cria uma árvore de decisão com base no conjunto
'instances'
"""
node = Node()
node.top_edge = top_edge
if len(instances) == 0:
return node
if self.haveSameClass(instances, target_class):
# Se todos os exemplos do conjunto possuem a mesma classificação,
# retorna node como um nó folha rotulado com a classe
# Pega a classe da primeira instância. Tanto faz, pois todos têm a mesma classe.
node.value = instances[0][target_class]
return node
if len(attributes) == 0:
# Se L é vazia, retorna node como um nó folha com a classe mais
# frequente no conjunto de instancias
value = self.getMostFrequentClass(instances, target_class)
node.value = value
return node
else:
# Seleciona m atributos aleatórios e escolhe o melhor
m = int(math.sqrt(len(attributes)))
random_attributes = self.getRandomAttributes(attributes, m)
attribute, info_gain = self.getBestAttribute(random_attributes, instances)
node.value = attribute
node.info_gain = info_gain
attributes.remove(attribute)
if self.attributes_types[attribute] == 'n':
# atributo numerico
values_sum = 0
for instance in instances:
values_sum = values_sum + float(instance[attribute])
avg_value = values_sum / len(instances)
subset_A, subset_B = self.getSubsetsForNumericAttribute(attribute, avg_value, instances)
subset_A_attribute_value = '<= ' + str(avg_value)
subset_B_attribute_value = '> ' + str(avg_value)
node.children.append(self.decisionTree(subset_A, attributes[:], target_class, subset_A_attribute_value))
node.children.append(self.decisionTree(subset_B, attributes[:], target_class, subset_B_attribute_value))
else:
# Para cada valor V distinto do atributo em questão, considerando os exemplos da lista de instancias:
distinct_attribute_values = self.getDistinctValuesForAttribute(attribute, instances)
for attribute_value in distinct_attribute_values:
subset = self.getSubsetWithAttributeValue(attribute, attribute_value, instances)
if len(subset) == 0:
# Se esse subset for vazio, retorna node como nó folha rotulado
# com a classe mais frequente no conjunto
node.value = self.getMostFrequentClass(instances, target_class)
return node
else:
node.children.append(self.decisionTree(subset, attributes[:], target_class, attribute_value))
return node
def getSubsetsForNumericAttribute(self, attribute, split_value, instances):
subset_A = []
subset_B = []
for instance in instances:
if float(instance[attribute]) <= split_value:
subset_A.append(instance)
else:
subset_B.append(instance)
return subset_A, subset_B
def printDecisionTree(self):
self.printTree(self.decision_tree)
def printTree(self, tree, level=0):
if tree.top_edge and tree.info_gain != None:
print(' ' * (level - 1) + '+---' * (level > 0) + '[' + tree.top_edge + '(INFO_GAIN = ' + str(tree.info_gain) + ')' + ']' + '--' + tree.value)
elif tree.top_edge:
print(' ' * (level - 1) + '+---' * (level > 0) + '[' + tree.top_edge + ']' + '--' + tree.value)
elif tree.info_gain != None:
print(' ' * (level - 1) + '+---' * (level > 0) + '--' + tree.value + '(INFO_GAIN = ' + str(tree.info_gain) + ')' )
else:
print(' ' * (level - 1) + '+---' * (level > 0) + '--' + tree.value)
for i in range(len(tree.children)):
if type(tree.children[i]) is Node:
self.printTree(tree.children[i], level + 1)
else:
print(' ' * level + '+---' + tree.children[i].value)
def classify(self, instance):
return self.predict(self.decision_tree, instance)
def predict(self, tree, instance):
if len(tree.children) > 0:
for i in range(len(tree.children)):
attribute = tree.value
attribute_value = tree.children[i].top_edge
if self.attributes_types[attribute] == 'n':
# atributo numerico
operator, num = attribute_value.split(' ')
if operator == '<=':
if instance[attribute] <= num:
return self.predict(tree.children[0], instance)
else:
if instance[attribute] > num:
return self.predict(tree.children[1], instance)
else:
# atributo categórico
if instance[attribute] == attribute_value:
return self.predict(tree.children[i], instance)
else:
return tree.value