AI Notebook 5 — Деревья решений

Деревья решений и базовые техники интерпретации классификаторов.

Edit on GitHub

Источник: pr_Python/Pr_Ai/Notebook5.ipynb

Дисциплина «Искусственный интеллект»

Рабочая тетрадь № 5.

Петрушенко Александр Дмитриевич

Готова

Теоретический материал – Деревья принятия решений

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

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

Деревья принадлежат к числу основных структур данных, используемых в программировании. Древовидная структура является одним из способов представления иерархической структуры в графическом виде. Такое название она получила потому, что граф выглядит как перевернутое дерево. Корень дерева (корневой узел) находится на самом верху, а листья (потомки) — внизу.

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

Схематично дерево и его основные элементы приведены на рисунке ниже.

image.png

На рисунке изображены родительские отношения (ребра, ветви дерева) между узлами (вершинами) дерева. На верхнем уровне каждый «родитель» указывает на своих «потомков». То есть в этой иерархической структуре вершина всегда «знает» своих потомков.

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

− корневой узел — самый верхний узел дерева, он не имеет предков;

− лист, листовой или терминальный узел — конечный узел, то есть не имеющий потомков;

− внутренний узел — любой узел дерева, имеющий потомков, то есть не лист.

С корневого узла начинается выполнение большинства операций над деревом. Чтобы получить доступ к любому элементу структуры, необходимо, переходя по ветвям, перебирать элементы, начиная с головы — корневого узла. Корневой узел — это своеобразный вход в дерево. Большинство алгоритмов работы с деревом строятся на том, что каждый узел дерева рассматриваются как корневой узел поддерева, «растущего» из этого узла. Такой подход дает возможность зацикливать выполнение операций при прохождении по элементам дерева. Но в связи с тем, что при прохождении по дереву (в отличие от массива) неизвестно сколько шагов будет в этом цикле, используется другой инструмент — рекурсивный вызов.

Двоичное (бинарное) дерево — это древовидная структура данных, где каждый узел имеет не более двух детей. Этих детей называют левым (Л) и правым (П) потомком или «сыном». На рисунке выше дерево является двоичным.

1.1 Основы объектно-ориентированного программирования в Python

В предыдущих разделах мы рассматривали в основном традиционное программирование на Python, когда вся программа разбивается (или не разбивается) на отдельные модули, содержащие функции. Такое программирование соответствует парадигме структурного программирования. Само структурное программирование оказалось колоссальным шагом в построении программ. Однако еще большим шагом является парадигма объектно-ориентированного программирования. В этом подходе программа состоит из отдельных классов, которые объединяют в себе как переменные, называемые полями класса, так и функции, называемые методами класса.

На самом деле мы уже сталкивались с классами, когда создавали объекты для решения задач классификации и регрессии в Scikit-learn. В данном разделе подробнее познакомимся с основами объектноориентированного программирования (ООП).

Объектно-ориентированное программирование состоит из трех китов:

− инкапсуляция;

− наследование;

− полиморфизм.

Рассмотрим на примерах эти понятия. Первое - инкапсуляция - это объединение в одном объекте данных и программного кода таким образом, что для внешней работы внутренняя часть объекта может быть скрыта от пользователя. Инкапсуляция может быть реализована не только с помощью классов, но и с помощью модулей, но классы позволяют сделать инкапсуляцию естественным путем. Создадим класс в Python. Для этого необходимо определить класс (новый тип данных) и создать объект, называемый экземпляром класса. Мы рекомендуем имена классов начинать с заглавной буквы "T", подчеркивая тем самым, что речь идет о типе данных.

Делается это так:

class TAnimal: name = "" def init(self, name): self.name = name def say(self): print(self.name)

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

Animal = TAnimal("Обезьяна") Animal.say()

Рассмотрим синтаксис Python при создании классов. Все начинается с ключевого слова class. Далее в блоке из отступов мы определяем переменные, которые будем называть полями и функции, которые называются методами. Методы определяются, как обычные функции и могут возвращать значения. Единственное отличие состоит в том, что у всех методов есть обязательный первый параметр, который по традиции всегда называем self в котором передается ссылка на экземпляр класса. Поэтому когда внутри класса метод хочет обратиться к своему полю, то необходимо использовать конструкцию self.name. Заметим, что при вызове методов мы первый параметр не задаем.

Далее, у каждого класса есть метод, с именем init, который называется конструктором класса. Этот метод вызывается в момент создания экземпляра Animal = TAnimal("Обезьяна"). Конструктор может иметь любое количество параметров. Предположим, что теперь нам нужно сделать класс для описания конкретного животного - кошки. Для это мы используем наследование классов, когда можно определять новые классы, как наследники существующих. При этом новый класс будет иметь все поля и методы наследуемого класса. Вот как это делается:

class TAnimal: name = "" def init(self, name): self.name = name def say(self): print(self.name) class TCat(TAnimal): def may(self): print("Мяу!") Cat = TCat("Кошка") Cat.say() Cat.may()

Мы видим, что у наследованного класса сохранился конструктор и метод say. В последнем примере мы выдели, что наследный класс, также как и исходный имеет конструктор, который принимает в качестве параметра - название животного тогда, что в данном случае излишне. Для решения этой проблемы мы воспользуемся объектно-ориентированным механизмом - полиморфизмом. Полиморфизм - это возможность замены методов при наследовании. Сделаем так, чтобы не нужно было передавать в конструкторе название "Кошка".

class TCat(TAnimal): def init(self): super().init("Кошка") def may(self): print("Мяу!") Cat = TCat() Cat.say() Cat.may()

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

class TDo: def Operation(self, x, y): return x + y def Run(self): x = int(input("Enter x > ")) y = int(input("Enter y > ")) z = self.Operation(x, y) print("Result = " + z.str()) Do = TDo() Do.Run()

С помощью полиморфизма заменим функцию Operation на другую в наследном классе:

class TDo2(TDo): def Operation(self, x, y): return x * y

1.2.1 Пример

Задача:

Необходимо разработать виртуальную модель процесса обучения. В программе должны быть объекты-ученики, учитель, кладезь знаний. Потребуется три класса – "учитель", "ученик", "данные". Учитель и ученик во многом похожи, оба – люди. Значит, их классы могут принадлежать одному надклассу "человек". Однако в контексте данной задачи у учителя и ученика вряд ли найдутся общие атрибуты. Определим, что должны уметь объекты для решения задачи "увеличить знания":

• Ученик должен уметь брать информацию и превращать ее в свои знания.

• Учитель должен уметь учить группу учеников.

• Данные могут представлять собой список знаний. Элементы будут извлекаться по индексу.

python
class Data:
    def __init__(self, *info):
        self.info = list(info)
    def __getitem__(self, i):
        return self.info[i]
    
class Teacher:
    def teach(self, info, *pupil):
        for i in pupil:
            i.take(info)

class Pupil:
    def __init__(self):
        self.knowledge = []
    def take(self, info):
        self.knowledge.append(info)
        
lesson = Data('class', 'object', 'inheritance', 'polymorphism', 'encapsulation')
marIvanna = Teacher()
vasy = Pupil()
pety = Pupil()
marIvanna.teach(lesson[2], vasy, pety)
marIvanna.teach(lesson[0], pety)
print(vasy.knowledge)
print(pety.knowledge)

Вывод:

text
['inheritance']
['inheritance', 'class']

1.2.2 Пример

Задача:

Напишите программу по следующему описанию. Есть класс "Воин". От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.

python
import random
class Warrior:
    def __init__(self, health):
        self.health = health
    
    def hit(self, target, target1):
        if target.health > 0:
            target.health -= 20
        if target1 == warrior1:
            target1 = 'Warrior1'
        if target1 == warrior2:
            target1 = 'Warrior2'
        print(target1, ' has attacked')
        print(target.health, ' left')
        if target.health == 0:
            print(target1, ' has won')
            
warrior1 = Warrior(100)
warrior2 = Warrior(100)
q = int(input('Enter 1 to attack. Enter 2 to stop program:'))

while q != 2:
    if q == 1:
        j = random.randint(1, 2)
        if j % 2 == 0:
            warrior1.hit(warrior2, warrior1)
            q = int(input('Enter 1 to let some warrior attack:'))
        else:
            warrior2.hit(warrior1, warrior2)
            q = int(input('Enter 1 to let some warrior attack:'))
    else:
        print('Wrong input.')
        break

Вывод:

text
Enter 1 to attack. Enter 2 to stop program:1
Warrior2  has attacked
80  left
Enter 1 to let some warrior attack:1
Warrior2  has attacked
60  left
Enter 1 to let some warrior attack:1
Warrior2  has attacked
40  left
Enter 1 to let some warrior attack:1
Warrior2  has attacked
20  left
Enter 1 to let some warrior attack:1
Warrior1  has attacked
80  left
Enter 1 to let some warrior attack:1
Warrior1  has attacked
60  left
Enter 1 to let some warrior attack:1
Warrior1  has attacked
40  left
Enter 1 to let some warrior attack:1
Warrior1  has attacked
20  left
Enter 1 to let some warrior attack:1
Warrior2  has attacked
0  left
Warrior2  has won
Enter 1 to let some warrior attack:2

1.2.3 Пример

Задача:

Создайте класс по работе с дробями. В классе должна быть реализована следующая функциональность:

− сложение дробей;

− вычитание дробей;

− умножение дробей;

− деление дробей

python
class Rational:
    
    @staticmethod
    def gcd(a, b):
        while (b != 0):
            a, b = b, a % b
        return a
    
    @staticmethod
    def sgn(x):
        if x > 0:
            return 1
        elif x < 0:
            return -1
        else:
            return 0
        
    def __init__(self, n, d):
        if n == 0:
            self.num = 0
            self.den = 1
        else:
            z = self.sgn(n) * self.sgn(d)
            n = abs(n)
            d = abs(d)
            k = self.gcd(n, d)
            self.num = z * n // k
            self.den = d // k
    
    def __str__(self):
        if self.num == 0:
            return '0'
        else:
            return str(self.num) + '/' + str(self.den)
    
    def __add__(self, o):
        n1 = self.num
        d1 = self.den
        if type(o) == int:
            n2 = o
            d2 = 1
        else:
            n2 = o.num
            d2 = o.den
        n = n1 * d2 + n2 * d1
        d = d1 * d2
        return Rational(n, d)
    
    def __radd__(self, o):
        n1 = self.num
        d1 = self.num
        if type(o) == int:
            n2 = o
            d2 = 1
        else:
            n2 = o.num
            d2 = o.den
        n = n1 * d2 + n2 * d1
        d = d1 * d2
        return Rational(n, d)
    
    def __sub__(self, o):
        n1 = self.num
        d1 = self.den
        n2 = o.num
        d2 = o.den
        n = n1 * d2 - n2 * d1
        d = d1 * d2
        return Rational(n, d)
    
    def __mul__(self, o):
        n1 = self.num
        d1 = self.den
        n2 = o.num
        d2 = o.den
        n = n1 * n2
        d = d1 * d2
        return Rational(n, d)
    
    def __floordiv__(self, o):
        n1 = self.num
        d1 = self.den
        n2 = o.num
        d2 = o.den
        n = n1 * d2
        d = d1 * n2
        return Rational(n, d)
    

d1 = Rational(1, 2)
d2 = Rational(1, 3)
d3 = d1 + d2
print(d3)
d4 = d1 - d2
print(d4)
d5 = d1 * d2
print(d5)
d6 = d1 * d2
print(d6)
d7 = d1 // d2
print(d7)
d8 = 6 + d1
print(d8)

Вывод:

text
5/6
1/6
1/6
1/6
3/2
7/1

Задание:

Создайте класс по работе с тригонометрическими функциями. В классе должны быть реализованы функции вычисления:

− косинуса;

− синуса;

− тангенса;

− арксинуса;

− арккосинуса;

− арктангенса;

− перевода из градусов в радианы.

python
import math
class Trigonometry:
    
    def __init__(self, arg):
        self.arg = arg
    
    def cos():
        return math.cos(self.arg)
    
    def sin():
        return math.sin(self.arg)
    
    def tg():
        return math.tan(self.arg)
    
    def arcsin():
        return math.asin(self.arg)
    
    def arccos():
        return math.acos(self.arg)
    
    def arctg():
        return math.atan(self.arg)
    
    def from_degrees_to_radians():
        self.arg *= math.pi / 180

1.2. Теоретический материал – Реализация деревьев в Python

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

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

image.png

Мы можем представить это дерево как список списков:

T = [["a", "b"], ["c"], ["d", ["e", "f"]]] print(T[0][1]) print(T[2][1][0])

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

1.2.1 Пример

Задача:

Определите класс бинарного дерева и задайте его объекты с отдельным атрибутом для каждого из потомков.

python
class Tree:
    def __init__(self, left, right):
        self.left = left
        self.right = right
        
t = Tree(Tree('a', 'b'), Tree('c', 'd'))
t.right.left

Вывод:

text
'c'

1.2.2 Пример

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

Распространенный способ реализации деревьев, особенно на языках, не имеющих встроенной поддержки списков, это так называемое представление «первый потомок, следующий брат». В нем каждый узел имеет два «указателя» или атрибута, указывающих на другие узлы, как в бинарном дереве. Однако, первый из этих атрибутов ссылается на первого потомка узла, а второй — на его следующего брата (т.е. узел, имеющий того же родителя, но находящийся правее, — прим. перев). Иными словами, каждый узел дерева имеет указатель на связанный список его потомков, а каждый из этих потомков ссылается на свой собственный аналогичный список. Таким образом, небольшая модификация бинарного дерева даст нам многопутевое дерево, показанное в листинге ниже.

python
class Tree:
    def __init__(self, kids, next=None):
        self.kids = self.val = kids
        self.next = next
        
t = Tree(Tree('a', Tree('b', Tree('c', Tree('d')))))
t.kids.next.next.val

Вывод:

text
'c'

Задание

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

image.png

python
class Tree:
    def __init__(self, kids, next=None):
        self.kids = self.val = kids
        self.next = next
        
t = Tree(Tree('a', Tree('b', Tree('d', 'e'))), Tree('c', Tree('f')))

print('root = ', t.kids.val)

print('left subtree = ', end='')
print(t.kids.next.val, end=', ')
print(t.kids.next.next.val, end=', ')
print(t.kids.next.next.next)

print('right subtree = ', end='')
print(t.next.kids, end=', ')
print(t.next.next.kids)

Вывод:

text
root =  a
left subtree = b, d, e
right subtree = c, f

Задание:

Дан класс, описывающий бинарное дерево.

class Tree: def init(self, data): self.left = None self.right = None self.data = data def PrintTree(self): print(self.data)

Реализуйте в классе функцию для вставки нового элемента в дерево по следующим правилам:

• Левое поддерево узла содержит только узлы со значениями меньше, чем значение в узле.

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

• Каждое из левого и правого поддеревьев также должно быть бинарным деревом поиска.

• Не должно быть повторяющихся узлов.

Метод вставки сравнивает значение узла с родительским узлом и решает куда доваить элемент (в левое или правое поддерево). Перепишите, метод PrintTree для печати полной версии дерева.

python
class Tree:
    def __init__(self, data):
        self.left = None
        self.right = None
        self.data = data

    def PrintTree(self):
        if self.left:
            self.left.PrintTree()
        print(self.data)
        if self.right:
            self.right.PrintTree()

    def AddItem(self, item):
        if self.data:
            if item < self.data:
                if self.left is None:
                    self.left = Tree(item)
                else:
                    self.left.AddItem(item)
            elif item > self.data:
                if self.right is None:
                    self.right = Tree(item)
                else:
                    self.right.AddItem(item)
        else:
            self.data = item

tree = Tree(5)
tree.AddItem(1)
tree.AddItem(2)
tree.AddItem(6)
tree.AddItem(7)
tree.PrintTree()

Вывод:

text
1
2
5
6
7

1.3. Теоретический материал – Деревья решений

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

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

  2. Они требуют относительно меньших усилий для обучения алгоритма.

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

  4. Они очень быстры и эффективны по сравнению с KNN и другими алгоритмами классификации.

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

1.3.1 Пример

Задача:

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

python
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

dataset = sns.load_dataset('iris')
dataset
dataset.shape
dataset.head()

Вывод:

text
   sepal_length  sepal_width  petal_length  petal_width species
0           5.1          3.5           1.4          0.2  setosa
1           4.9          3.0           1.4          0.2  setosa
2           4.7          3.2           1.3          0.2  setosa
3           4.6          3.1           1.5          0.2  setosa
4           5.0          3.6           1.4          0.2  setosa

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

python
from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(
    dataset.iloc[:, :-1],
    dataset.iloc[:, -1],
    test_size = 0.20
)

x_train.shape, x_test.shape, y_train.shape, y_test.shape
x_train.head()
y_train.head()

Вывод:

text
49         setosa
58     versicolor
68     versicolor
35         setosa
122     virginica
Name: species, dtype: object

После того, как данные были разделены на обучающие и тестовые наборы, последний шаг состоит в том, чтобы обучить алгоритм дерева решений на этих данных и сделать прогнозы. Scikit-Learn содержит библиотеку tree , которая содержит встроенные классы/методы для различных алгоритмов дерева решений. Поскольку мы собираемся выполнить здесь задачу классификации, мы будем использовать класс DecisionTreeClassifier для этого примера. Метод fit этого класса вызывается для обучения алгоритма на обучающих данных, которые передаются в качестве параметра методу fit . Выполним следующий сценарий для обучения алгоритма.

python
from sklearn.tree import DecisionTreeClassifier
classifier = DecisionTreeClassifier()
classifier.fit(x_train, y_train)

from sklearn import tree
tree.plot_tree(classifier)

Вывод:

text
[Text(0.3181818181818182, 0.9285714285714286, 'x[3] <= 0.8\ngini = 0.667\nsamples = 120\nvalue = [39, 41, 40]'),
 Text(0.22727272727272727, 0.7857142857142857, 'gini = 0.0\nsamples = 39\nvalue = [39, 0, 0]'),
 Text(0.4090909090909091, 0.7857142857142857, 'x[2] <= 4.75\ngini = 0.5\nsamples = 81\nvalue = [0, 41, 40]'),
 Text(0.18181818181818182, 0.6428571428571429, 'x[0] <= 4.95\ngini = 0.051\nsamples = 38\nvalue = [0, 37, 1]'),
 Text(0.09090909090909091, 0.5, 'gini = 0.0\nsamples = 1\nvalue = [0, 0, 1]'),
 Text(0.2727272727272727, 0.5, 'gini = 0.0\nsamples = 37\nvalue = [0, 37, 0]'),
 Text(0.6363636363636364, 0.6428571428571429, 'x[2] <= 4.95\ngini = 0.169\nsamples = 43\nvalue = [0, 4, 39]'),
 Text(0.45454545454545453, 0.5, 'x[1] <= 3.05\ngini = 0.49\nsamples = 7\nvalue = [0, 3, 4]'),
 Text(0.36363636363636365, 0.35714285714285715, 'x[3] <= 1.6\ngini = 0.32\nsamples = 5\nvalue = [0, 1, 4]'),
 Text(0.2727272727272727, 0.21428571428571427, 'gini = 0.0\nsamples = 1\nvalue = [0, 1, 0]'),
 Text(0.45454545454545453, 0.21428571428571427, 'gini = 0.0\nsamples = 4\nvalue = [0, 0, 4]'),
 Text(0.5454545454545454, 0.35714285714285715, 'gini = 0.0\nsamples = 2\nvalue = [0, 2, 0]'),
 Text(0.8181818181818182, 0.5, 'x[3] <= 1.7\ngini = 0.054\nsamples = 36\nvalue = [0, 1, 35]'),
 Text(0.7272727272727273, 0.35714285714285715, 'x[3] <= 1.55\ngini = 0.32\nsamples = 5\nvalue = [0, 1, 4]'),
 Text(0.6363636363636364, 0.21428571428571427, 'gini = 0.0\nsamples = 3\nvalue = [0, 0, 3]'),
 Text(0.8181818181818182, 0.21428571428571427, 'x[0] <= 6.6\ngini = 0.5\nsamples = 2\nvalue = [0, 1, 1]'),
 Text(0.7272727272727273, 0.07142857142857142, 'gini = 0.0\nsamples = 1\nvalue = [0, 1, 0]'),
 Text(0.9090909090909091, 0.07142857142857142, 'gini = 0.0\nsamples = 1\nvalue = [0, 0, 1]'),
 Text(0.9090909090909091, 0.35714285714285715, 'gini = 0.0\nsamples = 31\nvalue = [0, 0, 31]')]
text
<Figure size 640x480 with 1 Axes>

Графический вывод (изображение) опущен в версии MDX.

Теперь, когда наш классификатор обучен, давайте сделаем прогнозы по тестовым данным. Для составления прогнозов используется метод predict класса Decision Tree Classifier. Взгляните на следующий код для использования.

python
y_pred = classifier.predict(x_test)
y_pred

Вывод:

text
array(['virginica', 'setosa', 'virginica', 'virginica', 'setosa',
       'versicolor', 'virginica', 'versicolor', 'setosa', 'versicolor',
       'virginica', 'virginica', 'versicolor', 'setosa', 'setosa',
       'setosa', 'setosa', 'setosa', 'versicolor', 'virginica',
       'virginica', 'virginica', 'virginica', 'virginica', 'setosa',
       'setosa', 'virginica', 'versicolor', 'setosa', 'versicolor'],
      dtype=object)

На данный момент мы обучили наш алгоритм и сделали некоторые прогнозы. Теперь посмотрим, насколько точен наш алгоритм. Для задач классификации обычно используются такие метрики, как матрица путаницы, точность. Библиотека Scikit-Learn metrics содержит методы classification_report и confusion_matrix, которые могут быть использованы для расчета этих метрик.

python
from sklearn.metrics import classification_report, confusion_matrix
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))

Вывод:

text
[[11  0  0]
 [ 0  7  2]
 [ 0  0 10]]
              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        11
  versicolor       1.00      0.78      0.88         9
   virginica       0.83      1.00      0.91        10

    accuracy                           0.93        30
   macro avg       0.94      0.93      0.93        30
weighted avg       0.94      0.93      0.93        30

Из матрицы оценок алгоритма вы можете видеть, что из 30 тестовых экземпляров наш алгоритм неправильно классифицировал только 3. Это приблизительно 91 % точности.

Задание

Задача:

Постройте классификатор на основе дерева принятия решений следующего датасета:

#данные x = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]]) target = [0, 0, 0, 1, 1, 1]

python
from sklearn.metrics import classification_report, confusion_matrix
x = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
target = [0, 0, 0, 1, 1, 1]

dataset = pd.DataFrame(data=x)
dataset
dataset.shape
dataset.head()

x_train, x_test, y_train, y_test = train_test_split(
    dataset.iloc[:, :-1],
    dataset.iloc[:, -1],
    test_size=0.20
)

x_train.shape, x_test.shape, y_train.shape, y_test.shape
x_train.head()
y_train.head()

classifier = tree.DecisionTreeClassifier()
classifier.fit(x_train, y_train)

tree.plot_tree(classifier)

y_pred = classifier.predict(x_test)
y_pred

print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred, zero_division=0))

Вывод:

text
[[0 1]
 [0 1]]
              precision    recall  f1-score   support

          -2       0.00      0.00      0.00         1
          -1       0.50      1.00      0.67         1

    accuracy                           0.50         2
   macro avg       0.25      0.50      0.33         2
weighted avg       0.25      0.50      0.33         2
text
<Figure size 640x480 with 1 Axes>

Графический вывод (изображение) опущен в версии MDX.

1.4. Теоретический материал – Дерево решений для регрессии

Дерево решений для регрессии

Процесс решения регрессионной задачи с деревом решений с помощью Scikit Learn очень похож на процесс классификации. Однако для регрессии мы используем класс DecisionTreeRegressor древовидной библиотеки. Кроме того, оценочные показатели регрессии отличаются от показателей классификации. В остальном процесс почти такой же. Построим регрессию с использованием дерева решений в Python и библиотеки scikit-learn. В качестве исходного набора данных будем использовать зависимость заработной платы от опыта работы из предыдущей тетради:

https://raw.githubusercontent.com/AnnaShestova/salary-years-simplelinear-regression/master/Salary_Data.csv

1.4.1 Пример

Задача:

Постойте регрессию с использованием дерева решений, реализованного в Python.

python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

url = r'https://raw.githubusercontent.com/AnnaShestova/salary-years-simple-linear-regression/master/Salary_Data.csv'
dataset = pd.read_csv(url)
print(dataset.head())

print(dataset.shape)
dataset.describe()

plt.scatter(dataset['YearsExperience'], dataset['Salary'], color='b', label='Заработная плата')
plt.xlabel('Опыт(лет)')
plt.ylabel('Заработная плата')
plt.show()

from sklearn.tree import DecisionTreeRegressor
x = dataset.iloc[:, :-1].values
y = dataset.iloc[:, -1].values
print(x)
print(y)

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)

regressor = DecisionTreeRegressor()
regressor.fit(x_train, y_train)

from sklearn import tree
tree.plot_tree(regressor)

y_pred = regressor.predict(x_test)
y_pred

df = pd.DataFrame({'Actual': y_test, 'Predicted': y_pred})
df

from sklearn import metrics
print('Mean Squared Error:', metrics.mean_squared_error(y_test, y_pred))
print('Mean Absolute Error:', metrics.mean_absolute_error(y_test, y_pred))

metrics.mean_absolute_error(y_test, y_pred) / np.average(y) * 100

Вывод:

text
   YearsExperience   Salary
0              1.1  39343.0
1              1.3  46205.0
2              1.5  37731.0
3              2.0  43525.0
4              2.2  39891.0
(30, 2)
text
<Figure size 640x480 with 1 Axes>

Графический вывод (изображение) опущен в версии MDX.

text
[[ 1.1]
 [ 1.3]
 [ 1.5]
 [ 2. ]
 [ 2.2]
 [ 2.9]
 [ 3. ]
 [ 3.2]
 [ 3.2]
 [ 3.7]
 [ 3.9]
 [ 4. ]
 [ 4. ]
 [ 4.1]
 [ 4.5]
 [ 4.9]
 [ 5.1]
 [ 5.3]
 [ 5.9]
 [ 6. ]
 [ 6.8]
 [ 7.1]
 [ 7.9]
 [ 8.2]
 [ 8.7]
 [ 9. ]
 [ 9.5]
 [ 9.6]
 [10.3]
 [10.5]]
[ 39343.  46205.  37731.  43525.  39891.  56642.  60150.  54445.  64445.
  57189.  63218.  55794.  56957.  57081.  61111.  67938.  66029.  83088.
  81363.  93940.  91738.  98273. 101302. 113812. 109431. 105582. 116969.
 112635. 122391. 121872.]
Mean Squared Error: 25498988.416666668
Mean Absolute Error: 4120.666666666667
text
5.421715809463662
text
<Figure size 640x480 with 1 Axes>

Графический вывод (изображение) опущен в версии MDX.

Средняя абсолютная ошибка для нашего алгоритма составляет 4120.66, что составляет менее 6 процентов от среднего значения всех значений в столбце.

Задание

Задача:

Постройте модель регрессии для данных из предыдущей рабочей тетради.Для примера можно взять потребления газа (в миллионах галлонов) в 48 штатах США или набор данных о качестве красного вина: https://raw.githubusercontent.com/likarajo/petrol_consumption/master/data/petrol_consumption.csv https://raw.githubusercontent.com/aniruddhachoudhury/Red-WineQuality/master/winequality-red.csv

Постройте прогноз. Оцените точность модели

python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor
from sklearn import tree
from sklearn import metrics

url = r'https://raw.githubusercontent.com/likarajo/petrol_consumption/master/data/petrol_consumption.csv'
dataset = pd.read_csv(url)
print(dataset.head())

print(dataset.shape)
dataset.describe()

plt.scatter(dataset['Petrol_tax'],dataset['Average_income'],color='b',label='Average income')
plt.xlabel('Average income')
plt.ylabel('Petrol tax')
plt.show()

x=dataset.iloc[:, :-1].values
y=dataset.iloc[:, -1].values
print(x)
print(y)

x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=0)

regressor=DecisionTreeRegressor()
regressor.fit(x_train,y_train)

tree.plot_tree(regressor)
y_pred = regressor.predict(x_test)
y_pred
df = pd.DataFrame({'Actual': y_test, 'Predicted': y_pred})
df

print('Squared ', metrics.mean_squared_error(y_test, y_pred))
print('Absolute', metrics.mean_absolute_error(y_test, y_pred))
metrics.mean_absolute_error(y_test, y_pred) / np.average(y) * 100

Вывод:

text
   Petrol_tax  Average_income  Paved_Highways  Population_Driver_licence(%)  \
0         9.0            3571            1976                         0.525   
1         9.0            4092            1250                         0.572   
2         9.0            3865            1586                         0.580   
3         7.5            4870            2351                         0.529   
4         8.0            4399             431                         0.544   

   Petrol_Consumption  
0                 541  
1                 524  
2                 561  
3                 414  
4                 410  
(48, 5)
text
<Figure size 640x480 with 1 Axes>

Графический вывод (изображение) опущен в версии MDX.

text
[[9.0000e+00 3.5710e+03 1.9760e+03 5.2500e-01]
 [9.0000e+00 4.0920e+03 1.2500e+03 5.7200e-01]
 [9.0000e+00 3.8650e+03 1.5860e+03 5.8000e-01]
 [7.5000e+00 4.8700e+03 2.3510e+03 5.2900e-01]
 [8.0000e+00 4.3990e+03 4.3100e+02 5.4400e-01]
 [1.0000e+01 5.3420e+03 1.3330e+03 5.7100e-01]
 [8.0000e+00 5.3190e+03 1.1868e+04 4.5100e-01]
 [8.0000e+00 5.1260e+03 2.1380e+03 5.5300e-01]
 [8.0000e+00 4.4470e+03 8.5770e+03 5.2900e-01]
 [7.0000e+00 4.5120e+03 8.5070e+03 5.5200e-01]
 [8.0000e+00 4.3910e+03 5.9390e+03 5.3000e-01]
 [7.5000e+00 5.1260e+03 1.4186e+04 5.2500e-01]
 [7.0000e+00 4.8170e+03 6.9300e+03 5.7400e-01]
 [7.0000e+00 4.2070e+03 6.5800e+03 5.4500e-01]
 [7.0000e+00 4.3320e+03 8.1590e+03 6.0800e-01]
 [7.0000e+00 4.3180e+03 1.0340e+04 5.8600e-01]
 [7.0000e+00 4.2060e+03 8.5080e+03 5.7200e-01]
 [7.0000e+00 3.7180e+03 4.7250e+03 5.4000e-01]
 [7.0000e+00 4.7160e+03 5.9150e+03 7.2400e-01]
 [8.5000e+00 4.3410e+03 6.0100e+03 6.7700e-01]
 [7.0000e+00 4.5930e+03 7.8340e+03 6.6300e-01]
 [8.0000e+00 4.9830e+03 6.0200e+02 6.0200e-01]
 [9.0000e+00 4.8970e+03 2.4490e+03 5.1100e-01]
 [9.0000e+00 4.2580e+03 4.6860e+03 5.1700e-01]
 [8.5000e+00 4.5740e+03 2.6190e+03 5.5100e-01]
 [9.0000e+00 3.7210e+03 4.7460e+03 5.4400e-01]
 [8.0000e+00 3.4480e+03 5.3990e+03 5.4800e-01]
 [7.5000e+00 3.8460e+03 9.0610e+03 5.7900e-01]
 [8.0000e+00 4.1880e+03 5.9750e+03 5.6300e-01]
 [9.0000e+00 3.6010e+03 4.6500e+03 4.9300e-01]
 [7.0000e+00 3.6400e+03 6.9050e+03 5.1800e-01]
 [7.0000e+00 3.3330e+03 6.5940e+03 5.1300e-01]
 [8.0000e+00 3.0630e+03 6.5240e+03 5.7800e-01]
 [7.5000e+00 3.3570e+03 4.1210e+03 5.4700e-01]
 [8.0000e+00 3.5280e+03 3.4950e+03 4.8700e-01]
 [6.5800e+00 3.8020e+03 7.8340e+03 6.2900e-01]
 [5.0000e+00 4.0450e+03 1.7782e+04 5.6600e-01]
 [7.0000e+00 3.8970e+03 6.3850e+03 5.8600e-01]
 [8.5000e+00 3.6350e+03 3.2740e+03 6.6300e-01]
 [7.0000e+00 4.3450e+03 3.9050e+03 6.7200e-01]
 [7.0000e+00 4.4490e+03 4.6390e+03 6.2600e-01]
 [7.0000e+00 3.6560e+03 3.9850e+03 5.6300e-01]
 [7.0000e+00 4.3000e+03 3.
... [output truncated]
text
9.674553007043524
text
<Figure size 640x480 with 1 Axes>

Графический вывод (изображение) опущен в версии MDX.