Русский
Русский
English
Статистика
Реклама

Decision tree

Перевод Напишем и поймем Decision Tree на Python с нуля! Часть 1. Краткий обзор

02.09.2020 18:14:51 | Автор: admin
Привет, Хабр! Представляю вашему вниманию перевод статьи "Python01. ".

1.1 Что такое Decision Tree?


1.1.1 Пример Decision Tree


Например, у нас есть следующий набор данных (дата сет): погода, температура, влажность, ветер, игра в гольф. В зависимости от погоды и остального, мы ходили () или не ходили () играть в гольф. Предположим, что у нас есть 14 сложившихся вариантов.

Из этих данных мы можем составить структуру данных, показывающую, в каких случаях мы шли на гольф. Такая структура из-за своей ветвистой формы называется Decision Tree.

Например, если посмотреть на Decision Tree, изображенный на картинке выше, мы поймем, что сначала проверяли погоду. Если было ясно, мы проверяли влажность: если она высокая, то не шли играть в гольф, если низкая шли. А если погода была облачная, то шли играть в гольф вне зависимости от других условий.

1.1.2 Об этой статье


Существуют алгоритмы, которые создают такие Decision Tree автоматически, на основе имеющихся данных. В этой статье мы будем использовать алгоритм ID3 на Python.

Эта статья первая из серии. Следующие статьи:

(Прим. переводчика: если вас заинтересует продолжение, пожалуйста, сообщите нам в в комментариях.)

Основы программирования на Python
Необходимые основы библиотеки для анализа данных Pandas
Основы структуры данных (в случае с Decision Tree)
Основы информационной энтропии
Учим алгоритм для генерации Decision Tree

1.1.3 Немного о Decision Tree


Генерация Decision Tree связана с машинным обучением с учителем и классификацией. Классификация в машинном обучении способ, позволяющий создать модель, ведущую к правильному ответу, на основе обучении на дата сете с правильными ответами и ведущими к ним данными. Глубокое обучение (Deep Learning), которое в последние годы очень популярно, особенно в сфере распознавания изображений, тоже является частью машинного обучения на основе классификационного метода. Разница Deep Learning и Decision Tree в том, приводится ли конечный результат к форме, при которой человек понимает принципы генерации конечной структуры данных. Особенность Deep Learning мы получаем конечный результат, но не понимаем принцип его генерации. В отличие от Deep Learning, принцип построения Decision Tree прост для понимания человеком, что также является его важной особенностью.

Эта особенность Decision Tree хороша не только для машинного обучения, но и дата майнинга, где также важно понимание данных пользователем.

1.2 Об алгоритме ID3


ID3 алгоритм генерации Decision Tree, разработанный в 1986 году Россом Куинланом. У него есть две важные особенности:

1. Категориальные данные. Это данные по типу нашего примера выше (пойду на гольф или не пойду), данные с определенным категорийным ярлыком. ID3 не может использовать численные данные.
2. Информационная энтропия индикатор, который указывает на последовательность данных с наименьшей дисперсией свойств класса значений.

1.2.1 Об использовании численных данных


Алгоритм С4.5, который является более продвинутой версией ID3, может использовать численные данные, но так как базовая идея такая же, в этой серии статей, мы, для начала, будет использовать ID3.

1.3 Среда разработки


Программа, которую я описал ниже, я проверил и запустил в следующих условиях:

Jupyter Notebooks (с использованием Azure Notebooks)
Python 3.6
Библиотеки: math, pandas, functools (не использовал scikit-learn, tensorflow и т.д.)

1.4 Пример программы


1.4.1 Собственно, программа


Для начала, скопируем программу в Jupyter Notebook и запустим.
import mathimport pandas as pdfrom functools import reduce# Дата сетd = {    "Погода":["ясно","ясно","облачно","дождь","дождь","дождь","облачно","ясно","ясно","дождь","ясно","облачно","облачно","дождь"],    "Температура":["Жарко","Жарко","Жарко","Тепло","Холодно","Холодно","Холодно","Тепло","Холодно","Тепло","Тепло","Тепло","Жарко","Тепло"],     "Влажность":["Высокая","Высокая","Высокая","Высокая","Норм","Норм","Норм","Высокая","Норм","Норм","Норм","Высокая","Норм","Высокая"],    "Ветер":["Нет","Есть","Нет","Нет","Нет","Есть","Есть","Нет","Нет","Нет","Есть","Есть","Нет","Есть"],    # Последний массив - это наша целевая переменная, показывающая результат,     # основывающийся на предыдущих данных.    "Гольф":["","","","","","","","","","","","","",""],}df0 = pd.DataFrame(d)# Лямбда-выражение для распределения значений, аргумент - pandas.Series, # возвращаемое значение - массив с количеством каждого из значений# Из вводных данных s с помощью value_counts() находим частоту каждого из значений, # и пока в нашем словаре есть элементы, будет работать цикл, запускаемый items().# Чтобы выходные данные не менялись с каждым запуском цикла, мы используем sorted, # который меняет порядок от большего к меньшему# В итоге, генерируется массив, содержащий строку из следующих компонентов: ключ (k) и значение (v).cstr = lambda s:[k+":"+str(v) for k,v in sorted(s.value_counts().items())]# Структура данных Decision Treetree = {    # name: Название этого нода (узла)    "name":"decision tree "+df0.columns[-1]+" "+str(cstr(df0.iloc[:,-1])),    # df: Данные, связанные с этим нодом (узлом)    "df":df0,    # edges: Список ребер (ветвей), выходящих из этого узла,     # или пустой массив, если ниже нет листового узла.    "edges":[],}# Генерацию дерева, у узлов которого могут быть ветви, сохраняем в openopen = [tree]# Лямба-выражение для вычесления энтропии. # Аргумент - pandas.Seriesвозвращаемое значение - число энтропииentropy = lambda s:-reduce(lambda x,y:x+y,map(lambda x:(x/len(s))*math.log2(x/len(s)),s.value_counts()))# Зацикливаем, пока open не станет пустымwhile(len(open)!=0):    # Вытаскиваем из массива open первый элемент,    # и вытаскиваем данные, хранящиеся в этом узле    n = open.pop(0)    df_n = n["df"]    # В случае, если энтропия этого узла равна 0, мы больше не можем вырастить из него новые ветви    # поэтому прекращаем ветвление от этого узла    if 0==entropy(df_n.iloc[:,-1]):        continue    # Создаем переменную, в которую будем сохранять список значений атрибута с возможностью разветвления    attrs = {}    # Исследуем все атрибуты, кроме последнего столбца класса атрибутов    for attr in df_n.columns[:-1]:        # Создаем переменную, которая хранит значение энтропии при ветвлении с этим атрибутом,        # данные после разветвления и значение атрибута, который разветвляется.        attrs[attr] = {"entropy":0,"dfs":[],"values":[]}        # Исследуем все возможные значения этого атрибута.         # Кроме того, sorted предназначен для предотвращения изменения порядка массива,         # из которого были удалены повторяющиеся значения атрибутов, при каждом его выполнении.        for value in sorted(set(df_n[attr])):            # Фильтруем данные по значению атрибута            df_m = df_n.query(attr+"=='"+value+"'")            # Высчитываем энтропию, данные и значения сохрнаяем            attrs[attr]["entropy"] += entropy(df_m.iloc[:,-1])*df_m.shape[0]/df_n.shape[0]            attrs[attr]["dfs"] += [df_m]            attrs[attr]["values"] += [value]            pass        pass    # Если не осталось ни одного атрибута, значение которого можно разделить,     # прерываем исследование этого узла.    if len(attrs)==0:        continue    # Получаем атрибут с наименьшим значением энтропии    attr = min(attrs,key=lambda x:attrs[x]["entropy"])    # Добавляем каждое значение разветвленного атрибута    # и данные, полученные после разветвления, в наше дерево и в open.    for d,v in zip(attrs[attr]["dfs"],attrs[attr]["values"]):        m = {"name":attr+"="+v,"edges":[],"df":d.drop(columns=attr)}        n["edges"].append(m)        open.append(m)    pass# Выводим дата сетprint(df0,"\n-------------")# Метод преобразования дерева в символы, аргуметы - tree:структура данных древа,# indent:присоединяймый к дочернему узлу indent,# Возвращаемое значение - символьное представление древа.# Этот метод вызывается рекурсивно для преобразования всех данных в дереве в символы.def tstr(tree,indent=""):    # Создаем символьное представление этого узла.    # Если этот узел является листовым узлом (количество элементов в массиве ребер равно 0),     # частотное распределение последнего столбца данных df, связанных с деревом, преобразуется в символы.    s = indent+tree["name"]+str(cstr(tree["df"].iloc[:,-1]) if len(tree["edges"])==0 else "")+"\n"    # Зацикливаем все ветви этого узла.    for e in tree["edges"]:        # Добавляем символьное представление дочернего узла к символьному представлению родительского узла.        # Добавляем еще больше символов к indent этого узла.        s += tstr(e,indent+"  ")        pass    return s# Выводим древо в его символьном представлении.print(tstr(tree))


1.4.2 Результат


Если запустить вышеописанную программу, наш Decision Tree будет представлен в виде символьной таблицы, как показано ниже.
decision tree Гольф [':5', ':9']  Погода=Ясно    Влажность=Обычная[':2']    Влажность=Высокая[':3']  Погода=Облачно[':4']  Погода=Дождь    Ветер=Есть[':2']    Ветер=Нет[':3']


1.4.3 Меняем атрибуты (массивы данных), которые хотим исследовать


Последний массив в дата сете d это атрибут класса (массив данных, который хотим классифицировать).
d = {    "Погода":["ясно","ясно","облачно","дождь","дождь","дождь","облачно","ясно","ясно","дождь","ясно","облачно","облачно","дождь"],    "Температура":["Жарко","Жарко","Жарко","Тепло","Холодно","Холодно","Холодно","Тепло","Холодно","Тепло","Тепло","Тепло","Жарко","Тепло"],     "Влажность":["Высокая","Высокая","Высокая","Высокая","Норм","Норм","Норм","Высокая","Норм","Норм","Норм","Высокая","Норм","Высокая"],    "Гольф":["","","","","","","","","","","","","",""],}    # Последний массив - это наша целевая переменная, показывающая результат, основывающийся на предыдущих данных.    "Ветер":["Нет","Есть","Нет","Нет","Нет","Есть","Есть","Нет","Нет","Нет","Есть","Есть","Нет","Есть"],}

Например, если поменять местами массивы Гольф и Ветер, как указано на примере выше, получится следующий результат:
decision tree Ветер ['Есть:6', 'Нет:8']  Гольф=    Погода=ясно      Температура=Жарко        Влажность=Высокая['Есть:1', 'Нет:1']      Температура=Тепло['Нет:1']    Погода=Дождь['Есть:2']  Гольф=    Погода=ясно      Температура=Тепло['Есть:1']      Температура=Холодно['Нет:1']    Погода=облачно      Температура=Жарко['Нет:2']      Температура=Тепло['Есть:1']      Температура=Холодно['Есть:1']    Погода=Дождь['Нет:3']

По сути, мы создаем правило, при котором сообщаем программе, что разветвляться нужно сначала по наличию и отсутствию ветра и по тому, идем играть в гольф или нет.

Спасибо за прочтение!
Мы будем очень рады, если вы расскажете нам, понравилась ли вам данная статья, понятен ли перевод, была ли она вам полезна?
Подробнее..

Перевод Напишем и поймем Decision Tree на Python с нуля! Часть 2. Основы Python, необходимые для генерации Decision Tree

11.09.2020 18:10:30 | Автор: admin
Привет, Хабр! Представляю вашему вниманию перевод статьи "Python02. Python".

Данная статья вторая в серии. Первую вы можете найти здесь.

2.1 Комментарии обозначаются # или ''' (три одинарные кавычки)


# Комментарийa = 1 # Комментарий ''' Это тоже комментарийb = cc = d'''

2.2 Использование динамической типизации (тип определяется автоматически)


# динамическая типизация переменных# = копирование значения справа налевоi = 1 #  целое число (int)f = 2.1 # число с плавающей запятой (float)s = "a" # строковый тип (string)b = True # логический тип (boolean)l = [0,1,2] # массив,список (array) t = (0,1,2) # кортеж (tuple)d = {"a":0, "b":1} # словарь, ассоциативный массивprint(i,f,s,b,l,t,d)# 1 2.1 a True [0, 1, 2] (0, 1, 2) {'a': 0, 'b': 1}# Когда хотим определить тип, используем typeprint(type(i)) # Вывод <class 'int'># Переменная не сохраняет, а содержит фактическое значение# Это, своего рода, переменная-ссылка, указывающая на местоположение значения# Можно получить идентификатор актуального значения через idprint(id(l)) # 00000000000000 (меняется от исполнения к исполнениюц)l2 = l # Приведу в пример копию массива, где ссылаюсь на 2 его элемента, а фактический массив - 1. print(id(l2)) # 00000000000000 (то же значение, что у вышеуказанного id(l))# Поскольку существует только один фактический массив, кажется, что он был добавлен в массив l, даже если вы добавили элемент со ссылкой на l2.l2.append(1)

2.3 Арифметические операторы (Вычисление и присвоение значений. Результат вычисления, как правило, помещается в переменную)


a = 1 # переменная а соответствует значению 1b = a # b так же соответствует 1 a = 2 # теперь а соответствует 2, но b не изменяетсяa += 1 # значение а увеличивается на 1 (на данном этапе а равно 3)a -= 1 # значение а уменьшается на 1 (на данном этапе а равно 2)a *= 2 # значение а увеличивается в два раза (на данном этапе а равно 4)a /= 3 # значение а составляет  предыдущего значения (на данном этапе а равно 1.3333333333333333)a = 1+2-3*4/5 # Вычисление в следующем порядке: умножение, деление, сложение, вычитание  a равно 0,6. (Однако, если учитывать погрешность, а равно 0.6000000000000001)

2.4 Создание группы программ через отступ


a = 0# Указываем условие выполнения группы программ, обозначенной отступом.# Далее прописываются условия, циклы, функционализация и т.д.if a == 1:    # Строка, которая смещена вправо на такую же величину в левой части строки от этой строки, составляет одну группу программ.    print(1)    print(2)    pass # Указание на конец группы с отступом    # при выполнении условия if выполняется группа программ до строки вышеprint(3) # вывод независимо от значения а, не подпадающий под условие if# вывод 3

2.4.1 Сгруппированным программам даётся контроль над выполнением операций


# Условие, позволяющее программе понять, запускаться или нетif условие :    # program group# Выполнение повторяется только для элементов массива# Элементы массива присваиваются переменной v в прямом порядкеfor v in массив    # program group# Выполнение продолжается только при соблюдении условийwhile условие:    # program group# Тайминг выполнение группы программ определяется позже# (в первую очередь нужно создать группу программ и дать им имена)def имя группы():    # program group    # После выполнения группы программ, возвращаем результат с помощью return    # Или можно вернуться к началу    return# Ниже описан пример с ошибкой# print(2) с измененным отступом будет относиться к другой группе программ# однако, program group 2 не имеет под собой никакого объясненияif a == 1:    # выполнение группы program group 1 регулируется условием if a==1:    print(1)        # в program group 2 нет регулирующей части, поэтому компилятор выдает ошибку.        print(2)

2.4.2 Формулировка условий


a = 1b = 2c = 2# переменные a и b одинаковы, но a и c разныеif a==b and a!=c:    print("отличается только с")# а больше или равно b (включает а=b) или а>c (не включает а=с)elif a>=b or a>c:    print("а больше или равно b или больше с")# не подходит под условия if и elifelse:    print("кроме этого")# выводит "кроме этого"

2.4.2.1 Другой пример написания условий


a = 1b = 2# если а=b, то v=0, если нет, то v=1v = 0 if a==b else 1print(v)# вывод 1 

2.4.3 Формирование циклов (for)


for v in [0,1,2,3]: # Следующая обработка повторяется для элементов [0,1,2,3]в массиве.    # Проходимся по массиву. Элементы массива выводятся через переменную v в прямом поряке   print(v)   pass # Обозначаем конец отступа# вывод 0 1 2 3# Используя enumerate, можно получить индекс и значение массива в итеративном процессе.for i,v in enumerate([5,3,7,8]):    # i - индекс, v - значение элемента    print("(",i,v,")")# вывод ( 0 5 ) ( 1 3 ) ( 2 7 ) ( 3 8 )# С помощью zip два массива могут быть объединены в один и обработаны итеративно.for v0,v1 in zip([1,2,3,4],[5,6,7,8]):    # v0 содержит элементы массива первого аргумента zip, а v1 содержит элементы второго аргумента.    print("(",v0,v1,")")# вывод ( 1 5 ) ( 2 6 ) ( 3 7 ) ( 4 8 )

2.4.4 Цикл while


a = 3# Пока a больше либо равно 0while a>=0:    print(a)    # Уменьшаем значение a на 1    a -= 1# вывод 3 2 1 0

2.4.5 Функции и методы


# Определение функции: Название - function_name, аргументы: param1 и param2, param2 - аргумент по умолчанию# В случае, если аргумент не установлен, при вызове функции его значение будет равно 1 (значению по умолчанию)def function_name(param1,param2=1):    print("p1:",param1,"p2",param2)    # Эта функция возращает сумму аргументов param1 и param2    return param1 + param2# вызов функции (на данном этапе впервые запускается function_name)# значение param1 = 5,  param2 не устанавливается (используется аргумент по умолчанию)v = function_name(5)# вывод (вывод с помощью оператора print в функции function_name) p1: 5 p2 1print("возвращаемое значение",v)()# вывод "возвращаемое значение 6"

2.4.5.1 Лямба-выражение, пример написания функции


# название функции = lambda (входные аргументы) : (возвращаемое значение) - по такому принципу описывается функция.f = lambda x: x*x# Вызов функции происходит так же, как и в примере с defv = f(2)print(v) # отобразится 4

2.5 Преобразование строки в число


si = "1"   #Строкаsf = "2.3" #Строкаi = 4      #Целое число# Преобразовываем целое число i в строку, происходит конкатенация строкprint(str(i)+si)# 41# Обратная ситуация - преобразовываем строку si в целое число(int), происходит арифметическое сложениеprint(i+int(si))# 5# Преобразовываем строку в число с плавающей запятой (float), опять же происходит сложение (число при выводе автоматически заменяется на float)print(i+float(sf))# 6.3

2.6 Массив, список


# Создание массиваa = [1,1,2,3,2]              # a=[1,1,2,3,2]b = [n for n in range(8)]  # b=[0, 1, 2, 3, 4, 5, 6, 7]# Ссылка# Отрицательные значения становятся индексами, которые выссчитываются с -1 в качестве последнего элементаv = a[0]  # v=1v = a[-1] # v=2# Добавлениеa += [4]     # a=[1, 1, 2, 3, 2, 4]a.append(5)  # a=[1, 1, 2, 3, 2, 4, 5]# Извлечение (перед выполнением a=[1, 1, 2, 3, 2, 4, 5])v = a.pop(0) # v=1, a=[1, 2, 3, 2, 4, 5]# Вырезание (перед выполнением a=[1, 2, 3, 2, 4, 5])# a[первый индекс: предпоследний индекс]# если индексы не прописывать, а оставить просто a[:], первоначальный индекс автоматически станет  равен 0, последний - количество элементов)c = a[1:3] # c=[2, 3]# Максимумминимум (перед выполнением а= [1, 2, 3, 2, 4, 5])mx,mi = max(a),min(a) # mx=5, mi=1# Среднее значение(mean)Медианное значение(median)Наиболее частое значение(mode)Стандартное отклонение(stdev)Дисперсия(variance)# (перед выполнением a=[1, 2, 3, 2, 4, 5])from statistics import mean,median,mode,stdev,variancev = mean(a) # v=2.8333333333333335v = median(a) # v=2.5v = mode(a) # v=2v = stdev(a) # v=1.4719601443879744v = variance(a) #v=2.1666666666666665# Удаление повторений(перед выполнением a=[1, 2, 3, 2, 4, 5])c = set(a) # c={1, 2, 3, 4, 5}# SortedСоздается новый упорядоченный массив(перед выполнением a=[1, 2, 3, 2, 4, 5])c = sorted(a) # c=[1, 2, 2, 3, 4, 5] (массив a остается без изменений)# Sortупорядочивает элементы массива(перед выполнением a=[1, 2, 3, 2, 4, 5])a.sort()      # a=[1, 2, 2, 3, 4, 5]# Одинаково преобразует все элементы массиваmapping (перед выполнением a=[1, 2, 2, 3, 4, 5])# используем лямбда-выражение x: x+2.Значение элемента x меняется на x+2.# list преобразует вывод функции map в массивc = list(map(lambda x:x+2,a)) #c=[3, 4, 4, 5, 6, 7]# Фильтр(перед выполнением a=[1, 2, 2, 3, 4, 5])# используем лямбда-выражение x: x%2==0# Ищется элемент xкоторый делится на два без остатка. Подходящие элементы образуют новый массивc = list(filter(lambda x:x%2==0,a)) #c=[2, 2, 4]# Из всех элементов массива создается одно значение по заданным условиям# x - изначальное значениеесли запускаем цикл, х - значение первого элемента массива# xi - объект текущей операцииfrom functools import reduce# задаем x+xi, получаем сумму всех элементов массиваперед выполнением a=[1, 2, 2, 3, 4, 5]c = reduce(lambda x,xi:x+xi,a) #c=17# задаем max(xi,x), получаем наибольшее значение среди элементов массиваперед выполнением a=[1, 2, 2, 3, 4, 5]c = reduce(lambda x,xi:max(x,xi),a) #c=5

2.7 Словарь (dictionary)ассоциативный массив


# Создание. Ключи - Родной язык, Математика, Английский язык, а значения - массивыd = {    "Родной язык" : [81, 60, 97, 96],    "Математика" : [80, 78, 75, 96],    "Английский язык" : [76, 85, 65, 88],}print(d)# вывод {'Родной язык': [81, 60, 97, 96], 'Математика': [80, 78, 75, 96], 'Английский язык': [76, 85, 65, 88]}# Инициализация с использованием ford1 = {name:[0,1] for name in ["Родной язык","Математика","Английский язык"]}# вывод {'Родной язык': [0, 1], 'Математика': [0, 1], 'Английский язык': [0, 1]}# Получение значений через указание ключаa = d["Математика"] # a=[80, 78, 75, 96]# Замена значений через укзание ключаПерезаписьd["Английский язык"] = [77, 61, 91, 87] # d["Английский язык"]=[77, 61, 91, 87]# Цикл данныхfor k,v in d.items():    print(k,v)# вывод Родной язык [81, 60, 97, 96] Математика [80, 78, 75, 96] Английский язык [77, 61, 91, 87]# Максимальное значениеМинимальное значениев случае с ключом Родной языкjmx,jmi = max(d["Родной язык"]),min(d["Родной язык"])#print(jmx,jmi)# Находим название предмета с наивысшим средним значениемfrom statistics import mean# В массивах типа словарь при использовании max можно в качестве ключевого аргумента указать функцию, определяющую, среди чего искать максимальное значение# В этом случае лямбда-выражение используется для вычисления среднего значения из аргумента k(значение ключа в словаре)kmx = max(d,key=lambda k:mean(d[k])) # kmx="Родной язык"

2.8 Файловая операция with open


# with, файловая операция# with автоматизирует завершение процессов, требующих запуска и завершения.# Например, файл, открытый с помощью open, необходимо закрыть с помощью вызова функции close # Если использовать with и открыть файл с помощью open, при выходе из диапазона with функция close вызовется автоматически.# mode - r: чтениеw: записьa: дополнительная запись# Возвращаемое значение, которое мы получаем с помощью open, назовем fwith open("09_memo.txt",mode="a") as f:    # Записываем в файл    f.write("\n")# Чтение файлаwith open("09_memo.txt","r") as r:    print(r.read())

2.9 Случайное число random


# Использование случайных чиселimport random# Инициализация генерации случайных чисел# Случайные числа позволяют генерировать одну и ту же последовательность случайных чисел несколько раз для экспериментальной воспроизводимости.# Если аргумент seed указать численно, всегда будет выводиться одно и то же число.random.seed(0)# Генерация случайных чисел больше или равных 0 и меньше 1.print(random.random()) # Вывод 0.8444218515250481# Создает случайные целые числа больше первого аргумента и меньше второго аргумента.print(random.randint(1,3)) # Вывод одного числа из 1,2,3# Произвольно выбирает число из массиваprint(random.choice([0,1,2])) # Вывод одного числа из 0,1,2# Извлечение нескольких данных из массива# Вторым аргументом указывается количества выводимых данныхprint(random.sample([0,1,2],2)) # Выводимый массив [1, 2]# Перемешиваем данные в массиве# Новый массив не создается - данные перемешиваются непосредственно в массиве аa = [0,1,2]random.shuffle(a)print(a) # вывод [0, 2, 1]print(random.sample(a,len(a))) # Для создания нового массива можно использовать sample


Спасибо за прочтение!

Мы будем очень рады, если вы расскажете нам, понравилась ли вам данная статья, понятен ли перевод, была ли она вам полезна?
Подробнее..

Перевод Напишем и поймем Decision Tree на Python с нуля! Часть 4. Структуры данных

04.11.2020 14:09:33 | Автор: admin
Данная статья четвертая в серии. Ссылки на предыдущие статьи: первая, вторая, третья

4.1 Структуры данных


Структура данных это представление того, как организованы отдельные данные.

Массив


image

Отдельные данные представлены одним рядом. Чтобы идентифицировать один фрагмент данных, например, каким по порядку является этот отдельный фрагмент массива, необходим его идентификатор.

#Пример реализации массива в Pythona = [2,6,4,5,1,8]


Таблица, двумерный массив


image

Данные выстроены в несколько столбцов, отдельные элементы двумерного массива также образуют строки.
Чтобы идентифицировать фрагмент данных в этом случае, требуются два идентификатора: например, номер столбца и номер элемента.

#Пример реализации таблицы в Pythona = [   [2,6,4,5,1,8],   [4,4,1,3,4,2],   [5,3,6,6,5,3],   [7,8,0,9,5,3],]


Tree, древовидная структура




Это структура данных, где отдельные данные как будто связаны линией.
Эти линии являются маршрутом, напрямую соединяющим одни данные с другими. И один такой маршрут, например, и будет являтся древовидной структурой данных.
Часто такую структуру изображают как схему, напоминающую растущее сверху вниз дерево. Линии называются ребрами или ветвями, данные узлами (node), данные, после которых линия не продолжается, называются листьями(leaf), а данные, располагающиеся на самом верху корневым узлом или просто корнем.

# Пример реализации Tree в Python, содержащий список дочерних узлов.# Записывается в формате [Значение,список дочерних массивов]. Например, дерево с картинки выше будет записываться в порядке сверху вниз, слева направо.# Помимо подобной записи, существуют варианты с использованием классов, родительских нодов и другие.tree = \[2,[     [6,[]],     [4,[          [5,[               [6,[]],          ]],          [8,[]],          [1,[]],     ]],]]# Функция для преобразования древовидной структуры в символьнуюdef tstr(node,indent=""):      s = indent+str(node[0])+"\n"      for c in node[1]: # Цикл на дочернем ноде           s += tstr(c,indent+"+-")           pass      return sprint(tstr(tree))# Вывод# 2# +-6# +-4# +-+-5# +-+-+-6# +-+-8# +-+-1# Можно не создавать все дерево сразу, а создать несколько переменных для нодов# Создаем все ноды-листья. Цифра в переменной указывает на строку нода и его порядковый номер в этой строке слева направо.n10 = [6,[]]n21 = [8,[]]n22 = [1,[]]n40 = [6,[]]# Создаем родительские ноды для уже созданных дочерних.n20 = [5,[n40]]n11 = [4,[n20,n21,n22]]n00 = [2,[n10,n11]]# Выводим получившееся Древо, указав нод, который необходимо считать корневым.print(tstr(n11))# Вывод# 4# +-5# +-+-6# +-8# +-1


Network или графы


image

Это структура, в которой у одного нода может быть несколько ребер. Отдельные данные также называются узлами, а линии ребрами. Зачастую в графах нет корневого узла, как в древовидной структуре.

# Реализация графа на Pythonimport pandas as pd# В нашем случае имя узла и его значение совпадают.# Если имя нода и его значение не совпадают, необходимо будет достать значение нода.nodes = [2,6,4,5,8,1]# Указываем связи между нодами в виде матрицы. Если от нода 2 (первая строка)к ноду 6 (вторая строка) исходит ребро, # Значение 1-ой строки 2-ого столбца матрицы будет 1, а если ребро не проходит, то - 0. Такая матрица называется матрица смежности.df = pd.DataFrame([  # 2,6,4,5,8,1   [ 0,1,1,0,0,0], # от нода 2  [ 1,0,0,1,0,0], # от нода 6  [ 1,0,0,1,1,1], # от нода 4  [ 0,1,1,0,0,0], # от нода 5  [ 0,0,1,0,0,0], # от нода 8  [ 0,0,1,0,0,0], # от нода 1],columns=nodes,index=nodes)print(df)# Вывод#     2  6  4  5  8  1# 2  0  1  1  0  0  0# 6  1  0  0  1  0  0# 4  1  0  0  1  1  1# 5  0  1  1  0  0  0# 8  0  0  1  0  0  0# 1  0  0  1  0  0  0# С помощью библиотек matplotlib и networkx рисуем граф.import matplotlib.pyplot as pltimport networkx as nxplt.figure(figsize=(4,4))plt.axis("off")nx.draw_networkx(nx.from_pandas_adjacency(df))plt.show()


Вывод:
image

4.2 Пример реализации Decision Tree в Python


Decision Tree, как понятно из названия, может быть представлен в виде древовидной структуры.
В нодах хранятся следующие данные: список дочерних нодов, правила ветвления, путь от одного нода к другому в рамках Decision Tree.

Как показано ниже, корневой узел соединяет все данные. Числовое значение [...], прикрепленное к узлу, представляет собой номер исходных данных, из которых создается это дерево решений. Из корневого узла спускаются вниз только те данные, которые удовлетворяют условиям дочерних узлов. Например, это видно, если посмотреть на узлы иду на гольф и не иду на гольф в первом дереве решений.

image

image

Реализация в python-е происходит, например, следующим образом. Делаем один узел ассоциативным массивом, name это символьное представление состояния этого узла, df это данные, связанные с этим узлом, а ребра это список дочерних узлов.

# Данные древовидной структурыtree = {      # name: название этого узла      "name":"decision tree "+df0.columns[-1]+" "+str(cstr(df0.iloc[:,-1])),      # df: данные, связанные с данным узлом      "df":df0,# edges: список ребер (ветвей), выходящих из данного узла. Если узел листовой, то есть если ребер у него нет, массив остается пустым.    "edges":[],}

Функция tstr, текстифицирующая данную древовидную структуру, будет выглядеть следующим образом.

# Лямбда-выражение для распределения значений, аргумент - pandas.Series,  возвращаемое значение - массив с количеством каждого из значений# Из вводных данных s с помощью value_counts() находим частоту каждого из значений, и пока в нашем словаре есть элементы, будет работать цикл, запускаемый items().# Чтобы выходные данные не менялись с каждым запуском цикла, мы используем sorted,  который меняет порядок от большего к меньшему# В итоге, генерируется массив, содержащий строку из следующих компонентов: ключ (k) и значение (v).cstr = lambda s:[k+":"+str(v) for k,v in sorted(s.value_counts().items())]# Метод текстификации дерева: аргумент - tree (структура данных дерева), indent (отступ дочерних узлов),# А возвращаемое значение будет текстовым отображением tree. Данный метод вызывается рекурсивно для преобразования всего в дереве в символы.def tstr(tree,indent=""):    # Создаем символьное представление этого узла.# Если этот узел является листовым узлом (количество элементов в массиве ребер равно 0),# Частотное распределение последнего столбца данных df, связанных с деревом, преобразуется в символы.s = indent+tree["name"]+str(cstr(tree["df"].iloc[:,-1]) if len(tree["edges"])==0 else "")+"\n"      # Зацикливаем все ветви этого узла.for e in tree["edges"]:# Добавляем символьное представление дочернего узла к символьному представлению родительского узла.# Добавляем еще больше символов к indent этого узла.s += tstr(e,indent+"  ")passreturn s

Decision Tree, текстифицированное функцией tstr, будет выглядеть следующим образом. В корневом узле отображается текст (decision tree Гольф), который мы задали в самом начале при создании переменной tree, а также частотное распределение иду / не иду на гольф со всех данных. Каждый узел представленный ниже, отображает правила ветвления, и в случае, если узел оказывается листовым, отображается частотное распределение иду / не иду на гольф на основе данных, связанных с этим узлом.

decision tree Гольф [':5', ':9']  Погода=Ясно    Влажность=Нормальная[':2']    Влажность=Высокая[':3'] Погода=Облачно[':4'] Погода=Дождь    Ветер=Есть[':2']    Ветер=Нет[':3']


Спасибо за прочтение!

Мы будем очень рады, если вы расскажете нам, понравилась ли вам данная статья, понятен ли перевод, была ли она вам полезна?
Подробнее..

Перевод Напишем и поймем Decision Tree на Python с нуля! Часть 5. Информационная энтропия

09.11.2020 12:16:47 | Автор: admin
Данная статья пятая в серии. Ссылки на предыдущие статьи: первая, вторая, третья, четвертая

5.1 Информационная энтропия (Средний объем информации)


При создании дерева решений из данных алгоритм ID3 использует индекс, называемый информационной энтропией, чтобы определить, какой атрибут следует использовать для ветвления с наиболее эффективным распределением данных.

В начале, определимся с понятием объем информации. Интуитивно понятно, что объем данных = сложность, запутанность данных. Дерево решений собирает данные с одинаковыми значениями классов с каждого ветвления, таким образом снижая степень запутанности значений класса. Следовательно, при выборе атрибута, согласно которому лучше всего проводить ветвление, опираться стоит на то, насколько простыми стали данные после разветвления.

5.1.1 Определяем понятие объем информации


Объем данных, по сути, это ценность полученной информации.
Предположим, что информация о явлениях с низкой вероятностью возникновения (например, данные, согласно которым произойдет то или иное явление), встречается гораздо чаще, чем информация о событиях, которые произойдут с высокой вероятностью.

Например, знание правильного ответа из пяти предложенных вариантов, по объему информации больше, чем знание правильного ответа из двух вариантов.

И для того, чтобы передать это знание другому человеку, представим, что оно закодировано как двоичное число и отправлено по каналу связи. В данном случае объем такого сообщения (длина в битах) и будет определяться как объем информации.

image

Когда вероятность того, что событие E произойдет, равна P (E), объем информации I (E), который знает, что событие E произошло, определяется следующим образом.

I(E)=log2(1/P(E))=log2P(E)

5.1.2 Что такое информационная энтропия (средний объем информации)


У любого атрибута есть несколько значений. Например атрибут Погода представлен в 3 вариантах: Ясно, Облачно, Дождь. Среднее значение атрибутов того объема информации, который был получен из каждой вероятности появления события и называется энтропией (средним объемом информации).

В следующей формуле она представления буквой Н.

H=EP(E)log2P(E)

Например, рассчитать значение энтропии 2-ух представленных на рисунке атрибутов, можно следующим образом. Более смешанная, хаотичная левая сторона имеет высокое значение энтропии, по сравнению с черной правой стороной, имеющей низкое значение энтропии.

image

Однако, даже не используя запутанные формулы, из примера сверху можно понять, что правой стороне не хватает сложности, если посмотреть на количество черных точек. Можно, например, рассмотреть случай с добавлением желтой точки, создав тем самым ситуацию с 3 значениями. Информационную энтропию можно вычислить одинаковым образом как для двух значений, так и для трех значений, что делает ее, можно сказать, унифицированной и простой в обращении.

В примере ниже, несмотря на одинаковое количество черных точек с обеих сторон, значение энтропии будет выше на стороне, где присутствуют желтый и красный цвет, нежели чем на стороне, где оставшиеся точки только красные.

image

И алгоритм ID3 ищет значения атрибутов, которые разделяют данные на группы с более низкой энтропией.

5.2 Вычисление информационной энтропии


Информационная энтропия может быть вычислена с помощью следующего лямбда-выражения с DataFrame в качестве ввода и значением энтропии в качестве вывода.

entropy = lambda df:-reduce(lambda x,y:x+y,map(lambda x:(x/len(df))*math.log2(x/len(df)),df.iloc[:,-1].value_counts()))


Так как в данном лямбда-выражении уже присутствует другое лямбда-выражение, можно все немного упорядочить, и получится следующее:

entropy = lambda df:-reduce( #4.reduce создает одно значение из всех элементов массива.    lambda x,y:x+y,#5.Складываем значения энтропии, полученные из индивидуальных значений (9,5).    map( #2.Преобразовываем число (9,5) частотного массива (["": 9, "": 5]) в энтропию согласно следующему лямбда-выражению        lambda x:(x/len(df))*math.log2(x/len(df)),#3.Вычисляем P(E)log2(P(E))        df.iloc[:,-1].value_counts() #1.Частота последнего столбца DataFrameнапример:["":9,"":5]    ))

Данное выражение можно упорядочить следующим образом:

  1. df.iloc[:,-1]извлекает последний столбец DataFrame, а value_counts дает его частотное распределение (пример частотного распределения: ["": 9, "": 5])
  2. map преобразует каждое из значений частотного распределения (например, 9,5) в значения энтропии.
  3. (x / len (df)) * math.log2 (x / len (df)) вычисляет формулу P (E) log2P (E) для одного значения энтропии.
  4. reduce используется для создания единого значения из всех элементов массива. Например, его можно использовать для расчета сумм, средних значений и т. д.
  5. Лямбда-выражение x, y: x + y дает сумму двух аргументов (x, y), то есть сумму массивов. Это часть с сигмой в формуле энтропии (EP(E)log2P(E)). Так как выражение имеет минус в начале, оно также имеет минус перед reduce в программе.

5.2.1 Вычисление информационной энтропии


Информационная энтропия для следующих данных составляет 0,9402859586706309.

d = {"Гольф":["","","","","","","","","","","","","",""]}# Энтропия равна 0.9402859586706309

С другой стороны, в случае, если первые два x изменяются на , станут доминирующими данными (сложность снизится), то энтропия будет равна 0,74959525725948.

d = {"Гольф":["","","","","","","","","","","","","",""]}# Энтропия равна 0.74959525725948

Ниже приведен список всех программ, вычисляющих информационную энтропию.

import pandas as pdfrom functools import reduceimport mathd = {"Гольф":["","","","","","","","","","","","","",""]}df0 = pd.DataFrame(d)entropy = lambda df:-reduce(    lambda x,y:x+y,    map(        lambda x:(x/len(df))*math.log2(x/len(df)),        df.iloc[:,-1].value_counts()    ))print(entropy(df0)) # Вывод 0.9402859586706309

Спасибо за прочтение!

Мы будем очень рады, если вы расскажете нам, понравилась ли вам данная статья, понятен ли перевод, была ли она вам полезна?
Подробнее..

Перевод Глубокие нейронные деревья принятия решений

20.01.2021 00:15:59 | Автор: admin

Описание

Глубокие нейронные сети доказали свою эффективность при обработке данныхот органов чувств, таких,как изображения и аудио. Однако для табличных данных более популярны древовидные модели. Хорошим свойством древовидных моделей является их естественная интерпретируемость. В этой работе мы представляемDeepNeuralDecisionTrees(DNDT)древовидныемодели, реализованные нейронными сетями. DNDT внутренне интерпретируем, так как это дерево. Тем не менее, поскольку это также нейронная сеть (NN), ее можно легко реализоватьс помощьюинструментарияNN и обучитьпо алгоритмуградиентногоспуска, а непо жадному алгоритму (алгоритмужадного разбиения). Мы проводим оценку DNDT на нескольких табличных наборах данных, проверяем его эффективность и исследуем сходства и различия между DNDT и обычными деревьями решений. Интересно, что DNDT самообучаетсякак на разделенном, так и на функциональном уровне.

  1. Введение

Интерпретируемость прогностических моделей важна, особенно в тех случаях, когда речь идет об этикеправовой, медицинскойи финансовой,критически важных приложениях, где мы хотим вручную проверитьрелевантностьмодели. Глубокие нейронные сети (Lecunetal., 2015 [18];Schmidhuber, 2015 [25]) достигли превосходных результатов во многих областях, таких как компьютерное зрение, обработка речи и языковое моделирование. Однако отсутствие интерпретируемости не позволяет использоватьв приложенияхэто семейство моделейкак черныйящик, для которогомы должны знатьпроцедурупрогноза, чтобыверифицироватьпроцесс принятия решения. Более того, в некоторых областях, таких как бизнес-аналитика (BI), часто более важно знать, как каждый фактор влияет на прогноз, а не сам вывод. Методы, основанные на дереве решений (DT), такие как C4.5 (Quinlan, 1993 [23]) и CART (Breimanetal., 1984 [5]), имеют явное преимущество в этом аспекте, поскольку можно легко проследить структуру дерева и точно проверить, как делается прогноз.

В этой работе мы предлагаем новую модель на пересечении этих двух подходов глубокое нейронное дерево решений (DNDT),исследуем егосвязи с каждым из них. DNDT- это нейронные сети со специальной архитектурой, где любой выборвесов DNDT соответствует определенному дереву решений и поэтому интерпретируем. Однако, поскольку DNDT реализуется нейронной сетью (NN), она наследует несколько интересных свойств, отличныхоттрадиционныхDT: DNDT может быть легкореализованнесколькимистрокамикода в любом программном фреймворке NN; все параметры одновременно оптимизируются с помощью стохастического градиентного спуска, а не более сложной и потенциально неоптимальной процедурыжадногорасщепления.DNDTготов к крупномасштабной обработке с обучением на основе мини-патчейи ускорением GPUот коробочного решения, его можно подключить к любой более крупной модели NN в качестве строительного блока для сквозного обучения с обратным распространением (back-propagation).

2.Похожие работы

Модели на основе деревьев решений.Древовидные модели широко используются в обучении под наблюдением, например, взадачахклассификации. Они рекурсивно разбивают входное пространство и присваивают метку/оценку конечному узлу. Хорошо известные древовидные моделииспользуютC4. 5 (Quinlan, 1993 [23]) и CART (Breimanetal., 1984 [5]). Ключевым преимуществом древовидных моделей является то, что они легко интерпретируются, поскольку предсказания задаются набором правил. Также часто используется ансамбль из нескольких деревьев, таких какслучайный лес(Breiman, 2001 [6]) иXGBoost(Chen&Guestrin, 2016 [8]), чтобы повысить производительность за счет интерпретируемости. Такие древовидные модели часто конкурируют или превосходят нейронные сети в задачах прогнозирования с использованием табличных данных.

Интерпретируемые модели. По мере того как предсказания, основанные на машинном обучении,используютсяповсеместнои затрагивают многие аспекты нашей повседневной жизни, фокус исследований смещается от производительности модели (например, эффективности и точности) к другим факторам, таким как интерпретируемость (Weller, 2017 [26];Doshi-Velez, 2017 [11]). Это особеннонеобходимов приложениях, где существуютпроблемыэтические (Bostrom&Yudkowsky, 2014 [4]) или безопасности, и предсказания моделей должны быть объяснимы, чтобы проверить правильность процесса рассуждения или обосновать решениядля них. В настоящее время предпринимается ряд попыток сделать моделиобъяснимыми.Некоторые из них являются модельно-агностическими (Ribeiroetal., 2016 [24]), в то время как большинство из них связаны с определенным типом модели, например, классификаторамина основе правил (Dashetal., 2015 [10];Malioutovetal., 2017 [19]), моделями ближайших соседей (Kimetal., 2016 [15]) и нейроннымисетями (Kimetal., 2017 [16]).

Нейронные сети и деревья решений. В некоторых исследованиях предлагалось унифицировать модели нейронной сети и деревьев решений.Bul&Kontschieder(2014) [7] предложили нейронныелеса решений( Neural Decision Forests NDF) как ансамбль нейронных деревьев решений, где расщепленные функции реализуютсяслучайнымимногослойными персептронами.Deep-NDF (Kontschiederetal., 2015 [17]) использовал стохастическую и дифференцируемую модель дерева решений, которая совместно изучает представления (черезCNNs) и классификацию (через деревья решений). Предлагаемый нами DNDT во многом отличается от этих методов. Во-первых, у нас нет альтернативной процедуры оптимизации для изучения структуры (разделения) и обучения параметров (матрица оценок). Вместо этого мы изучаем их все с помощьюоднопроходногообратного распространения (back propagation). Во-вторых, мы не ограничиваем разбиение двоичным (левым или правым), поскольку мы применяем дифференцируемую функцию разбиения, которая может разбивать узлы на несколько ( 2) листьев. Наконец, что наиболее важно, мы разрабатываем нашу модель специально для интерпретируемости, особенно для приложенийк табличным данным, где мы можем интерпретировать каждую входную функцию. Напротив, модели в (Bul&Kontschieder, 2014 [7];Kontschiederetal., 2015 [17]) предназначены для прогнозирования и применяются к необработанным данным изображения. Некоторые проектные решения делают их непригодными для табличных данных. Например, вKontschiederetal. (2015 [17]), они используют менее гибкое дерево, в котором структуражесткофиксируется, пока изучается разбиение узла.

Несмотря на похожее название, наша работа кардинально отличается от работыБалестриеро(2017 [2]), которая разработала своего рода наклонное дерево решений, реализованное нейронной сетью. В отличие от обычных одномерных деревьев решений, каждый узел внаклонномдереве решений включает в себя все функции, а не одну функцию, что делает модель не интерпретируемой.

Альтернативныеактиваторыдереварешений.ОбычныеDT изучаются рекурсивнымжаднымрасщеплением признаков (Quinlan, 1993;Breimanetal., 1984 [23]). Это эффективно и имеет некоторые преимущества для выбора признаков, однако такойжадныйпоиск может быть неоптимальным (Norouzietal., 2015 [20]). В некоторых недавних работах исследуются альтернативные подходы к обучению деревьев решений, которые направлены на достижение лучшей производительности при менеетребовательнойоптимизации, например с помощью латентного структурированного прогнозирования переменных (Norouzietal., 2015 [20]) или обучения контроллера расщепления RNN с использованием обучения с подкреплением (Xiongetal., 2017 [28]). Напротив,нашDNDT намного проще, чемуказанные, но все же потенциально может найти лучшие решения, чем обычные индукторы DT,содновременным поискомструктурыи параметровдерева с SGD. Наконец, также отмечаем, что в то время как обычные активаторы DT используют только двоичные расщепления(для простоты), наша модель DNDT может одинаково легко работать с расщеплениями произвольной мощности, что иногда может привести к более интерпретируемым деревьям.

3.Методология

3.1.Функция мягкого контейнера

Основной модуль, который мы здесь реализуем, - это функция мягкой ячейки(Doughertyetal., 1995)или объединения множества точечных объектов в динамические полигоны (бины),которуюмы будем использовать для принятия решений о разделении в DNDT. Как правило, функциябиннингапринимает в качестве входных данныхвещественныйскаляр x и выдает индексячейки, которойонпринадлежит.Жесткоеразделение по ячейкамнедифференцируемое, поэтому мы предлагаем дифференцируемую аппроксимацию этой функции.

Предположим, что у нас есть непрерывная переменная x, которую мы хотим разбить на N + 1 интервалов. Это приводит к необходимости n точек среза, которые в данном контексте являются обучаемыми переменными. Обозначим точки среза [1, 2,, n]какмонотонно возрастающие, то есть 1 < 2 < < n. Во времяобученияпорядок может быть перетасован после обновления, поэтому мы должны сначала сортировать их в каждом прямом проходе. Однако это не повлияет на дифференцируемость, потому что сортировка просто меняет местами позиции .

Теперь построим однослойную нейронную сеть с функцией активацииsoftmax.

Здесь w-это константа, а не обучаемая переменная, и ее значение задается как w = [1; 2;:: : ; n + 1]. b строится как,

> 0 - фактор напряженности. При 0 выход стремится ктекущемувектору.

Мы можем проверить это, проверив три последовательныхлогита

o_{i-1},o_i, o_{i+1}.

Когда у нас есть как

o_i> o_{i-1} (при \quad x > _i), так \quad и \quad o_i> o_{i+1} (при \quad x < _{i+1}),

x должен попасть в интервал

(_i, _{i+1}).

Таким образом, нейронная сеть в уравнении 1 будет производить почти однократноегорячеекодированиеячейкиx, особенно при более низкой напряженности. При желании,мы можем применить трюкотжига наклона(Chungetal., 2017 [9]), который постепенно снижает напряжение приобучении, чтобы,в конце концов,получить более детерминированную модель.

Если кто-то предпочитает фактическийгорячий (текущийкодируемый)вектор, можно применитьStraight-Through(ST)Gumbel-Softmax(Jangetal., 2017): для прямого прохода мысэмплируемоднократный вектор, используя хитрость с Gumbel-Max, тогда как для обратного прохода (backward pass) мы используемGumbel-Softmaxпривычисленииградиента (см.Bengio(2013 [3]) для более подробного анализа.

На рис.1 показан конкретный пример, где мы имеем скаляр x в диапазоне [0, 1] и две точки среза в 0.33 и 0.66 соответственно. Основываясь на уравнениях1 и 2, мы имеем трилогитаo1 = x, o2 = 2x 0.33, o3 = 3x 0.99.

Рисунок 1. Конкретный пример нашей функции мягкого биннинга с использованием точек среза в 0.33 и 0.66. Ось x - это значение непрерывной входной переменной x2 [0; 1]. Вверху слева: исходные значения логитов; вверху справа: значения после применения функции softmax с т = 1; Внизу слева: т= 0.1; внизу справа: т = 0.01.Рисунок 1. Конкретный пример нашей функции мягкого биннинга с использованием точек среза в 0.33 и 0.66. Ось x - это значение непрерывной входной переменной x2 [0; 1]. Вверху слева: исходные значения логитов; вверху справа: значения после применения функции softmax с т = 1; Внизу слева: т= 0.1; внизу справа: т = 0.01.

3.2Построениепрогнозов

Учитывая нашу функциюбиннинга, ключевая идея состоит в том, чтобы построить дерево решений с помощьюоперацииКронекера. Предположим, что у нас есть входной экземпляр

x \in R^D \, c \, функциями \,D

Связывая каждый признакxd со своей собственной нейронной сетьюfd(xd), мы можем исчерпывающе найти все конечные узлы с помощью,

Здесь z теперь также является почтигорячимвектором, который указывает индекс листового узла, куда поступает экземпляр x. Наконец, мы предполагаем, что линейный классификатор на каждом листе z классифицирует поступающие туда экземпляры. DNDT проиллюстрирован на Рис. 2.

Рисунок 2. Изученный DNDT для набора данных Iris (сокращенная версия с двумя функциями). Вверху: DNDT - показано, где красным шрифтом указаны обучаемые переменные, а черным константы. Внизу: DT визуализация той же сети, что и обычное дерево решений. Дроби указывают маршрут случайно выбранных 6 классифицируемых экземпляров.Рисунок 2. Изученный DNDT для набора данных Iris (сокращенная версия с двумя функциями). Вверху: DNDT - показано, где красным шрифтом указаны обучаемые переменные, а черным константы. Внизу: DT визуализация той же сети, что и обычное дерево решений. Дроби указывают маршрут случайно выбранных 6 классифицируемых экземпляров.

3.3Обучение дерева

С помощью описанного выше метода мы можем направлять входные экземпляры в конечные узлы и классифицировать их. Таким образом, обучение дерева решений теперь становится вопросом обученияв узловыхточкахсрезаи листьевклассификаторов. Поскольку все шаги нашего прямого прохода дифференцируемы, все параметры (Рис. 2, Красный) теперь могут бытьнапрямуюи одновременно обучены с помощью SGD.

Обсуждение. DNDT хорошо масштабируется с количеством экземпляров благодаря мини-пакетному обучению в стиле нейронной сети. Однако ключевым недостатком этогостилядо сих пор является то, что из-за использования продуктаKroneckerон не масштабируется по количеству функций. В нашей текущей реализации мы избегаем этой проблемы с "широкими" наборами данных, обучаялесслучайногоподпространства(Ho, 1998 [13]) - за счет нашей интерпретируемости. То есть вводится несколько деревьев, каждое из которых обучается на случайном подмножестве признаков. Лучшим решением, которое не требует неинтерпретируемоголеса, является использование разреженности конечногоразделения на ячейкиво время обучения: количество непустых листьев растет намного медленнее, чем общее количество листьев. Но это несколько усложняет простую в остальном реализацию DNDT.

4.Эксперименты

4.1Реализация

DNDT концептуально прост и легок в реализации с помощью 20 строк кода вTensorFlowилиPyTorch. Поскольку он реализован как нейронная сеть, DNDT поддерживает "из коробки" ускорение GPU и мини-пакетное обучение наборов данных, которые не помещаются в память, благодаря современным фреймворкам глубокого обучения.

4.2Наборы данных и конкуренты

Мы сравниваем DNDT с нейронными сетями (реализованнымиTensorFlow(Abadietal., 2015) [1]) и деревом решений (отScikit-learn(Pedregosaetal., 2011 [22])) на 14 наборах данных, собранных изKaggleи UCI (подробности набора данных см. В табл. 1).

Для базовой линии дерева решений (DT) мы установили два ключевых критериягиперпараметров: критерий'gini' иразделитель 'best'. Для нейронной сети (NN) мы используем архитектуру из двух скрытых слоев по 50 нейронов в каждом для всех наборов данных. DNDT также имеетгиперпараметр-количество точек среза для каждого объекта (коэффициент ветвления), который мы устанавливаемравным1 для всех объектов и наборов данных. Подробный анализ эффекта этогогиперпараметраможно найти в разделе 4.4.Длянаборовданных с более чем 12 признаками,мы используем ансамбль DNDT, где каждое дерево выбирает 10 признаков случайным образом, и у нас есть 10уровнейв общей сложности. Окончательный прогноз дается большинством голосов.

4.3Точность

Мы оцениваем производительность DNDT, дерева решений инейросетевыхмоделей на каждом из наборов данных в Табл. 1. точность тестового набора представлена в Табл.2.

В целом наиболее эффективной моделью является DT. Хорошая производительность DT неудивительна, поскольку эти наборы данных в основном табличные, а размерность объектов относительно невелика.

Таблица 1. Коллекция из 14 наборов данных от Kaggle (обозначается буквой (K)) и UCI: количество экземпляров (#inst.), количество объектов (#feat.) и количество классов (#cl.)Таблица 1. Коллекция из 14 наборов данных от Kaggle (обозначается буквой (K)) и UCI: количество экземпляров (#inst.), количество объектов (#feat.) и количество классов (#cl.)Таблица 2. Точность тестового набора каждой модели: DT: дерево решений. NN: нейронная сеть. DNDT: наше глубокое нейронное дерево решений, где ( * ) указывает, что используется ансамблевая версия.Таблица 2. Точность тестового набора каждой модели: DT: дерево решений. NN: нейронная сеть. DNDT: наше глубокое нейронное дерево решений, где ( * ) указывает, что используется ансамблевая версия.

Условно говоря, нейронные сети не имеют явного преимущества в отношении такого рода данных. Однако DNDT немного лучше, чемванильнаянейронная сеть, так как она ближе к дереву решений попостроению. Конечно, это только ориентировочный результат, так как все эти модели имеют настраиваемыегиперпараметры. Тем не менее интересно, что ни одна модель не обладает доминирующим преимуществом. Это напоминает теоремы об отсутствиибесплатного обеда(Wolpert, 1996[27]).

4.4Анализ активных точек среза

В DNDT количество точек среза на объект является параметром сложности модели. Мы не связываем значения точек среза, а это значит, что некоторые из них неактивны, например, они либо меньше минимальногоxd, либо больше максимальногоxd.

В этом разделе мы исследуем, сколько точек среза фактически используется после обучения DNDT. Точка среза активна, когда по крайней мере один экземпляр из набора данных попадает на каждую ее сторону. Для четырех наборов данных-CarEvaluation,Pima,IrisиHabermanмы устанавливаем количество точек среза на объект от 1 до 5 и вычисляем процент активных точек среза, как показано на рис. 3.Видно, что по мере увеличения числа точек среза их использование в целом уменьшается. Это означает, что DNDT несколько саморегулируется: он не использует все доступные ему параметры.

Рисунок 3. Доля (%) активных точек, используемых DNDT.Рисунок 3. Доля (%) активных точек, используемых DNDT.

Мы можем дополнительно исследовать, как количество доступных точек среза влияет на производительность этих наборов данных. Как видно из Рис. 4, производительность первоначально увеличивается с увеличением числа точек среза, а затем стабилизируется после определенного значения. Это обнадеживает, потому что это означает, что большие DNDT не слишком подходят к данным обучения, даже без явной регуляризации.

Рисунок 4. Точность тестирования DNDT для увеличения числа точек разреза (сложность модели).Рисунок 4. Точность тестирования DNDT для увеличения числа точек разреза (сложность модели).

4.5Анализ активных признаков

При обучении DNDT также возможно, что для определенного объекта все точки среза неактивны. Это соответствует отключению функции, чтобы она не влияла на прогнозирование,аналогично обычному ученику DT, который никогда не выбирает данную функцию, чтобызадать узел-распознавательв любом месте дерева. В этом разделе мы анализируем, как DNDT исключает функции таким образом. Мы запускаем DNDT 10 раз и записываем,сколькораз данный объект исключаетсяиз-за того,что все его точки среза неактивны.

Учитывая случайностьчерезинициализируемыевесадлямини-пакетнойвыборки, мы наблюдаем, что некоторые функции (например, функция индекса 0 вiris) последовательно игнорируются DNDT (см. табл. 3 для всех результатов). Это говорит о том, что DNDT делает некоторый неявныйвыбор объектов, выталкивая точки отсечениязаграницы данных для несущественных объектов. В качестве побочного продукта мы можем получить меру важности(веса)функции изнаборафункции в течение нескольких запусков: чем больше функция игнорируется, тем менее важной она, вероятно,ибудет.

Таблица 3. Процент ( % ) случаев, когда DNDT игнорирует каждую функциюТаблица 3. Процент ( % ) случаев, когда DNDT игнорирует каждую функцию

4.6Сравнение с деревом решений

Используя методы, разработанные в разделе 4.5, мы исследуем, благоприятствуют ли DNDT и DTсосходнымихарактеристиками. Мы сравниваем важность признака черезкритерийgini (Джини), используемыйв дереве решений (Рис. 5), с нашей метрикой скорости отбора (табл.3).

Рисунок 5. Рейтинг важности характеристик, произведенный DT (Gini).Рисунок 5. Рейтинг важности характеристик, произведенный DT (Gini).

Сравнивая эти результаты, мы видим, что иногда DNDT и DT предпочитаютвыбор признаков, например, дляIrisони оба оценивают Признак 3 как наиболее важный. Но бывает, что они также могут иметь разные взгляды, например, дляХаберманаDT выбрал функцию 0 как наиболее важную, тогда как DNDT полностью проигнорировал ее. На самом деле DNDT использует только функцию 2 для прогнозирования, которая занимает второе место поDT.Однако такого рода разногласия не обязательно могут привести к существенным различиям в производительности. Как видно из Табл. 2, дляХаберманаточность испытаний DNDT и DT составляет 70,9% и 66,1% соответственно.

Наконец, мы количественно оцениваем сходстворанжированийпризнаков DNDT и признаков DT, вычисляяTauкритерияКендаллаподвумрейтинговымспискам. Результаты, приведенные в Табл.4, свидетельствуют об умеренной корреляции в целом.

Таблица 4. Рейтинг функций DNDT и DT по Кендаллу: большие значения означают большее сходство.Таблица 4. Рейтинг функций DNDT и DT по Кендаллу: большие значения означают большее сходство.

4.7Ускорение GPU

Наконец, мы проверяем легкость ускорения обучения DNDT с помощью обработки графическим процессором - возможность, которая не является обычной или простойдля DT. Увеличивая количество точек отсечения для каждой функции, мы можем получить более крупные модели, для которых режим графического процессора имеет значительно меньшее время работы (см. Рис. 6).

Рисунок 6. Иллюстрация ускорения GPU: время обучения DNDT включено. 3,6 ГГц CPU против GTX Titan GPU. В среднем за 5 прогонов.Рисунок 6. Иллюстрация ускорения GPU: время обучения DNDT включено. 3,6 ГГц CPU против GTX Titan GPU. В среднем за 5 прогонов.

5.Заключение

Мы представили древовидную модель на основе нейронной сети DNDT. Он имеет лучшую производительность, чем NN для определенных наборов табличных данных, при этом обеспечиваетинтерпретируемое дерево решений. Между тем, по сравнению с обычными DT, DNDT проще в реализации, одновременно выполняет поиск в древовидной структуре и параметрах с помощью SGD и легко ускоряется на GPU. Есть много возможностей для будущей работы. Мы хотим исследовать источник наблюдаемой нами саморегуляции; изучить подключение DNDT как модуля, подключенного к обычному элементу обучения CNN, для сквозного обучения; выяснить, можно ли использовать обучение на основе SGD целого дерева DNDT в качестве постобработки для точной настройки обычных,жаднообученных DT и повышения ихпроизводительности; выяснить, можно ли использовать многие подходы кадаптивномуобучению на основе NN для обеспечения возможности переносаобучения для DT.

Ссылки
  1. Abadi, Martn, Agarwal, Ashish, Barham, Paul, Brevdo, Eugene, Chen, Zhifeng, Citro, Craig, Corrado, Greg S., Davis, Andy, Dean, Jeffrey, Devin, Matthieu, Ghemawat, Sanjay, Goodfellow, Ian, Harp, Andrew, Irving, Geoffrey, Isard, Michael, Jia, Yangqing, Jozefowicz, Rafal, Kaiser, Lukasz, Kudlur, Manjunath, Levenberg, Josh, Mane, Dandelion, Monga, Rajat, Moore, Sherry, Murray, Derek, Olah, Chris, Schuster, Mike, Shlens, Jonathon, Steiner, Benoit, Sutskever, Ilya, Talwar, Kunal, Tucker, Paul, Vanhoucke, Vincent, Vasudevan, Vijay, Viegas, Fernanda, Vinyals, Oriol, Warden, Pete, Wattenberg, Martin, Wicke, Martin, Yu, Yuan, and Zheng, Xiaoqiang. TensorFlow: Large-scale machine learning on heterogeneous systems, 2015. URL https://www.tensorflow.org/.

  2. Balestriero, R. Neural Decision Trees. ArXiv e-prints, 2017.

  3. Bengio, Yoshua. Estimating or propagating gradients through stochastic neurons. CoRR, abs/1305.2982, 2013.

  4. Bostrom, Nick and Yudkowsky, Eliezer. The ethics of artificial intelligence, pp. 316334. Cambridge University Press, 2014.

  5. Breiman, L., H. Friedman, J., A. Olshen, R., and J. Stone, C. Classification and Regression Trees. Chapman & Hall, New York, 1984.

  6. Breiman, Leo. Random forests. Machine Learning, 45(1): 532, October 2001.

  7. Bul, S. and Kontschieder, P. Neural decision forests for semantic image labelling. In CVPR, 2014.

  8. Chen, Tianqi and Guestrin, Carlos. Xgboost: A scalable tree boosting system. In KDD, 2016.

  9. Chung, J., Ahn, S., and Bengio, Y. Hierarchical Multiscale Recurrent Neural Networks. In ICLR, 2017.

  10. Dash, S., Malioutov, D. M., and Varshney, K. R. Learning interpretable classification rules using sequential rowsampling. In ICASSP, 2015.

  11. Doshi-Velez, Finale; Kim, Been. Towards a rigorous science of interpretable machine learning. ArXiv e-prints, 2017.

  12. Dougherty, James, Kohavi, Ron, and Sahami, Mehran. Supervised and unsupervised discretization of continuous features. In ICML, 1995.

  13. Ho, Tin Kam. The random subspace method for constructing decision forests. IEEE Transactions on Pattern Analysis and Machine Intelligence, 20(8):832844, 1998.

  14. Jang, E., Gu, S., and Poole, B. Categorical Reparameterization with Gumbel-Softmax. In ICLR, 20

  15. Kim, B., Gilmer, J., Viegas, F., Erlingsson, U., and Wattenberg, M. TCAV: Relative concept importance testing with Linear Concept Activation Vectors. ArXiv e-prints, 2017.

  16. Kim, Been, Khanna, Rajiv, and Koyejo, Sanmi. Examples are not enough, learn to criticize! Criticism for interpretability. In NIPS, 2016.

  17. Kontschieder, P., Fiterau, M., Criminisi, A., and Bul, S. R. Deep neural decision forests. In ICCV, 2015.

  18. Lecun, Yann, Bengio, Yoshua, and Hinton, Geoffrey. Deep learning. Nature, 521(7553):436444, 5 2015.

  19. Malioutov, Dmitry M., Varshney, Kush R., Emad, Amin, and Dash, Sanjeeb. Learning interpretable classification rules with boolean compressed sensing. In Transparent Data Mining for Big and Small Data, pp. 95121. Springer International Publishing, 2017.

  20. Norouzi, Mohammad, Collins, Maxwell D., Johnson, Matthew, Fleet, David J., and Kohli, Pushmeet. Efficient non-greedy optimization of decision trees. In NIPS, 2015.

  21. Paszke, Adam, Gross, Sam, Chintala, Soumith, Chanan, Gregory, Yang, Edward, DeVito, Zachary, Lin, Zeming, Desmaison, Alban, Antiga, Luca, and Lerer, Adam. Automatic differentiation in pytorch. In NIPS Workshop on Autodiff, 2017.

  22. Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J., Passos, A., Cournapeau, D., Brucher, M., Perrot, M., and Duchesnay, E. Scikit-learn: Machine learning in Python. Journal of Machine Learning Research, 12:28252830, 2011.

  23. Quinlan, J. Ross. C4.5: Programs for Machine Learning. Morgan Kaufmann Publishers Inc., 1993.

  24. Ribeiro, Marco Tulio, Singh, Sameer, and Guestrin, Carlos. why should i trust you?: Explaining the predictions of any classifier. In KDD, 2016.

  25. Schmidhuber, J. Deep learning in neural networks: An overview. Neural Networks, 61:85117, 2015.

  26. Weller, Adrian. Challenges for transparency. In ICML Workshop on Human Interpretability in Machine Learning, pp. 5562, 2017.

  27. Wolpert, David H. The lack of a priori distinctions between learning algorithms. Neural Computation, 8(7):13411390, 1996.

  28. Xiong, Zheng, Zhang, Wenpeng, and Zhu, Wenwu. Learning decision trees with reinforcement learning. In NIPS Workshop on Meta-Learning, 2017.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru