Как адаптировать дерево решений для бинарной классификации к мульти-маркировочной классификации?

Я хочу адаптировать алгоритм дерева решений, который работает для двух меток: Правда/Ложь для одной, которая может принимать решения по нескольким меткам.

Моя попытка:

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())

Ответы (0 шт):