Как адаптировать дерево решений для бинарной классификации к мульти-маркировочной классификации?
Я хочу адаптировать алгоритм дерева решений, который работает для двух меток: Правда/Ложь для одной, которая может принимать решения по нескольким меткам.
Моя попытка:
from functools import partial
from statistics import mode
def build_tree_id3(inputs, split_candidates = None):
# если это наш первый проход
# все ключи первого ввода разделены кандидатами.
if split_candidates is None:
split_candidates = inputs[0][0].keys()
# считай разные классы
num_inputs = len(inputs)
num_different_classes = len(set([label for item, label in inputs if label]))
if num_different_classes == 1: return False # only one class? Return this one
if num_different_classes == num_inputs: return False# all classes are different?
if not split_candidates:
return max(set(inputs), key = inputs.count)
# в противном случае, разделитесь на лучшие атрибуты
best_attribute = min(split_candidates, key = partial(partition_entropy_by,inputs))
partitions = partition_by(inputs, best_attribute)
new_candidates = [a for a in split_candidates
if a!= best_attribute]
#recursively build the subtrees #
subtrees = {attribute_value: build_tree_id3(subset, new_candidates)
for attribute_value,
subset in partitions.items()}
# subtrees[None] = max(inputs, key=collections.Counter(inputs).get) # if tree is empty we give the most frequent one
subtrees[None] = None
return(best_attribute, subtrees)
Но когда я пытаюсь это сделать, я получаю " False ", а не "Will buy", "Don't buy" или "Maybe". В самом деле:
data = [({'a': 1, 'b': 'A', 'c': 10}, 'Will buy'),
({'a': 2, 'b': 'B', 'c': 20}, "Won't buy"),
({'a': 3, 'b': 'C', 'c': 30}, 'Will buy'),
({'a': 4, 'b': 'D', 'c': 40}, 'Will buy')]
tree = build_tree_id3(data)
отчёты:
best_attribute: a
subtrees: {1: False, 2: False, 3: False, 4: False, None: None}
Ho classify(tree, {'a': 1, 'b': 'A', 'c': 10}) отчёты:
C:\ProgramData\Anaconda3\lib\site-packages\ipykernel_launcher.py:3: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
This is separate from the ipykernel package so we can avoid doing imports until
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-456-34de52a7a854> in <module>
----> 1 classify(tree, {'a': 1, 'b': 'A', 'c': 10})
<ipython-input-453-9dce8ec72645> in classify(tree, input)
15
16 subtree = subtree_dict[subtree_key]
---> 17 return classify(subtree, input)
18
<ipython-input-453-9dce8ec72645> in classify(tree, input)
7 # and a dictionnary whose keys are values of that attribute
8 # and whose values of are subtrees to consider next
----> 9 attribute, subtree_dict = tree
10
11 subtree_key = input.get(attribute) # None if input is missing in attribute
TypeError: cannot unpack non-iterable bool object
Двоичное дерево решений, из которого меня вдохновили
Действительно, это дерево решений хорошо работает для бинарной классификации:
def build_tree_id3(inputs, split_candidates = None):
# если это наш первый проход
# все ключи первого ввода разделены кандидатами.
if split_candidates is None:
split_candidates = inputs[0][0].keys()
# посчитайте Истины и Ложь во входах # Здесь мне нужно адаптироваться, так как у меня более 2-х ярлыков.
num_inputs = len(inputs)
num_trues = len ([label for item, label in inputs if label])
num_falses = num_inputs - num_trues
if num_trues == 0: return False # Нет "Истинок"? Верни "Ложный" лист.
if num_falses == 0: return True # нет Фальцев? вернуть "Истинный" лист.
if not split_candidates:
return num_trues >= num_falses # default cases
# в противном случае, разделитесь на лучшие атрибуты
best_attribute = min(split_candidates, key = partial(partition_entropy_by,inputs))
partitions = partition_by(inputs, best_attribute)
new_candidates = [a for a in split_candidates
if a!= best_attribute]
# рекурсивно строить поддеревья
subtrees = {attribute_value: build_tree_id3(subset, new_candidates)
for attribute_value, subset in partitions.iteritems()}
subtrees[None] = num_trues >num_falses
return(best_attribute, subtrees)
И я классифицирую новый ввод в соответствии с деревом решений таким образом:
def classify(tree, input):
"""classify the input using the given decision tree"""
if tree in [True,False]: # здесь мне нужно изменить, чтобы адаптировать его к нескольким категориям.
return tree
# в противном случае это дерево состоит из атрибута, который можно разделить на
# и словарь, ключи которого являются значениями этого атрибута.
# и чьи значения поддеревья рассматривать дальше.
print("tree: ", tree)
print("df.TL_Segment.unique(): ",df.TL_Segment.unique())
attribute, subtree_dict = tree
subtree_key = input.get(attribute) # Нет, если в атрибуте отсутствует ввод.
if subtree_key not in subtree_dict: # если нет поддерева для ключа
subtree_key = None
subtree = subtree_dict[subtree_key]
return classify(subtree, input)
Поэтому, если я попытаюсь создать дерево решений со следующим набором данных:
[({'a': 1, 'b': 'A', 'c': 10}, 'Will buy'),
({'a': 2, 'b': 'B', 'c': 20}, 'Maybe'),
({'a': 3, 'b': 'C', 'c': 30}, 'Won't buy'),
({'a': 4, 'b': 'D', 'c': 40}, 'Will buy')]
Я получу обратно True, потому что num_trues - это то же самое, что num_inputs. Так что, когда я применю classify(), я получу True на что угодно.
Так как же я могу адаптировать дерево решений для бинарной классификации к классификации нескольких меток?
Other function used here
import math
import collections
def entropy(class_probabilities):
"""given a list of class probabilities, compute the entropy"""
return sum(-p * math.log(p,2)
for p in class_probabilities
if p)
def class_probabilities(labels):
total_count = len(labels)
return [count / total_count
for count in collections.Counter(labels).values()]
def data_entropy(labeled_data):
labels = [label for _,label in labeled_data]
probabilities = class_probabilities(labels)
return entropy(probabilities)
def partition_entropy(subsets):
"""find entropy from this partition of data into subsets
subsets is a list of lists of labeled data"""
total_count =sum(len(subset) for subset in subsets)
return sum(data_entropy(subset)* len(subset)/total_count
for subset in subsets)
def partition_by(inputs, attribute):
"""each input is a pair (attriute_dict,label).
returns a dict : attribute_value -> inputs"""
groups = collections.defaultdict(list)
for input in inputs:
key = input[0][attribute] # get the value of the specified attribute
groups[key].append(input) # then add this input to the correct list
return groups
def partition_entropy_by(inputs, attribute):
"""computes the entropy corresponding to the given partition"""
partitions = partition_by(inputs, attribute)
return partition_entropy(partitions.values())