РЕДАКТИРОВАТЬ: я добавил свои собственные методы для энтропийного / inf-усиления / лучшего разделения внизу на случай, если кто-нибудь захочет помочь мне отладить, чтобы им не пришлось писать свои собственные!
Каквызов, после просмотра этого видео я хотел создать решение на Python (класс, основанный на практике ООП).Поскольку данные здесь категоричны, я хотел создать дерево, чтобы оно отображалось визуально так же, как видео (т. Е. Возможно,> 2 дочерних элемента, метки на разделении и значения и т. Д.).
Я сделал все возможное, чтобы отладить код, чтобы он работал.В своем текущем состоянии у него две огромные проблемы:
(1) Список дочерних элементов экземпляра класса показывает некоторые значения None и некоторые объекты Tree.Поскольку значения None нарушают функцию .predict, я поместил временный оператор if, чтобы игнорировать их
(2) Атрибуты значения показывают «Да» и «Нет», которые являются целевыми значениями, а не значениями функций.например,
In: dt.tree_.children[0].value
Out: "Yes"
Разделение объектов вообще не входит в атрибут label.Поле метки равно None независимо от узла / дочернего элемента, и прогноз также возвращает None.Я часами просматривал код, но не могу понять почему.Я включил данные (дублированные из видео) и настройку DataFrame для простоты доступа и чтобы показать, как я пытаюсь запустить программу.
Заранее извиняюсь за очень длинный пост!Я пытаюсь объяснить свою логику (и не хочу, чтобы люди думали, что я просто прошу других написать мой код), хотя это, вероятно, удержит большинство людей от помощи!
Примечание: дерево решенийкласс использует вложенные функции, так что .fit всегда начинается с нового экземпляра Tree (), и поэтому .predict будет использовать атрибут dt.tree_, который должен быть заполнен после .fit
Tree () class:
class Tree():
def __init__(self, children = [], label = None, value = None):
self.children = children #used in place of left and right for binary solutions
self.label = label #to label which feature this node's children are split on
self.value = value #the values of the above node's split feature. This should always be None for the head node
Псевдокод для dt.fit:
def fit(self, data, target, features)
def run_id3(data, target, features, tree):
(base case)
Check if target column has only one unique value.
If so, set current tree label to target column, add one child below current tree with target value
return (end recursion)
find the best feature to split data on
set current node label to feature
for each unique value in splitting feature:
create a node and set value equal to unique value
append new node to the children list of the current tree
recur with data filtered for the current unique feature value (split) and with the child tree as the head
run_id3(data, target, features, self.tree_)
Код для dt.fit:
class DecisionTree():
tree_: Tree
def __init__(self):
self.tree_ = Tree()
pass
def fit(self, data, target, features):
def run_id3(data, target, features, tree):
unique_targets = pd.unique(data[target])
if len(unique_targets) == 1:
tree.label = target
tree.children.append(Tree(value=unique_targets[0]))
return
best_split = find_best(data, target, features)
tree.label = best_split
for unique_val in np.unique(data[best_split]):
new_tree = Tree()
new_tree.value = unique_val
tree.children.append(run_id3(data[data[best_split] == unique_val], target, features, new_tree))
run_id3(data, target, features, self.tree_)
Псевдокод для dt.predict:
def predict(self, row):
def get_prediction(tree, row):
check if current node has no children
return node label (should be target prediction)
set current column (feature split) to current node label
for each child of current node
if child is not null (THIS IS NOT GOOD, EXISTS TO STOP PROGRAM HALTING)
if child’s value is equal to the value in that column in our test row
recur (go down tree), set current child tree to head in parameter
tree = self.tree_ (so tree starts at the head of the instantiated tree, should be populated after dt.fit)
return get_prediction(tree, row)
Код для dt.predict:
def predict(self, row):
def get_prediction(tree, row):
if len(tree.children) == 0:
return tree.label
column = tree.label
for child in tree.children:
# the below conditional is purely to stop the program halting since I haven't worked out why the children attribute keeps adding NoneType objects
if child is not None:
if child.value == row[column]:
return get_prediction(child, row)
tree = self.tree_
return get_prediction(tree, row)
Настройка данных:
outlook = ['Sunny', 'Sunny', 'Overcast', 'Rain', 'Rain', 'Rain', 'Overcast', 'Sunny', 'Sunny', 'Rain', 'Sunny', 'Overcast', 'Overcast', 'Rain', 'Rain']
humidity = ['High', 'High', 'High', 'High', 'Normal', 'Normal', 'Normal', 'High', 'Normal', 'Normal', 'Normal', 'High', 'Normal', 'High', 'High']
wind = ['Weak', 'Strong', 'Weak', 'Weak', 'Weak', 'Strong', 'Strong', 'Weak', 'Weak', 'Weak', 'Strong', 'Strong', 'Weak', 'Strong', 'Weak']
play = ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes', 'Yes', 'Yes', 'Yes', 'No', '?']
columns = ["Outlook", "Humidity", "Wind", "Play"]
data = pd.DataFrame([outlook, humidity, wind, play]).T
data.columns = columns
train = data.iloc[:-1, :]
test = data.iloc[-1, :3]
features = columns.copy()
features.remove("Play")
target = "Play"
dt = DecisionTree()
dt.fit(train, target, features)
pred = dt.predict(test)
Методы получения информации:
import numpy as np
import pandas as pd
def entropy(column):
elements, counts = np.unique(column, return_counts=True)
# if statement in comprehension stops nan result since 0*log2(x) is undefined, returns 0. in this case,
# 1*log2(1) + 0*log2(0) = 0. zero entropy result, zero uncertainty is consistent with theory
entropy = np.sum(
[-(counts[i] / np.sum(counts)) * np.log2(counts[i] / np.sum(counts)) if counts[i] > 0 else 0 for i in
range(len(counts))])
return entropy
def information_gain(data, split_name, target_name):
target_entropy = entropy(data[target_name])
vals, counts = np.unique(data[split_name], return_counts=True)
weighted_entropy = np.sum(
[counts[i] / np.sum(counts) * entropy(data.loc[data[split_name] == vals[i], target_name]) for i in
range(len(counts))])
return target_entropy - weighted_entropy
def find_best(data, target_name, features):
max_gain = 0
best_col = ""
for col in features:
gain = information_gain(data, col, target_name)
if gain > max_gain:
max_gain = gain
best_col = col
return best_col