AI Notebook 5 — Деревья решений
Деревья решений и базовые техники интерпретации классификаторов.
Edit on GitHubИсточник:
pr_Python/Pr_Ai/Notebook5.ipynb
Дисциплина «Искусственный интеллект»
Рабочая тетрадь № 5.
Петрушенко Александр Дмитриевич
Готова
Теоретический материал – Деревья принятия решений
Деревья решений являются одним из наиболее эффективных инструментов интеллектуального анализа данных и предсказательной аналитики, которые позволяют решать задачи классификации и регрессии.
Перед тем как непосредственно перейти к решению задач с использование данного инструмента рассмотрим общее понятие "дерево" в информатике и способы задания деревьев в языке Python.
Деревья принадлежат к числу основных структур данных, используемых в программировании. Древовидная структура является одним из способов представления иерархической структуры в графическом виде. Такое название она получила потому, что граф выглядит как перевернутое дерево. Корень дерева (корневой узел) находится на самом верху, а листья (потомки) — внизу.
Деревья широко применяются в компьютерных технологиях. Примером является файловая система, представляющая собой иерархическую структуру из файлов и каталогов.
Схематично дерево и его основные элементы приведены на рисунке ниже.
На рисунке изображены родительские отношения (ребра, ветви дерева) между узлами (вершинами) дерева. На верхнем уровне каждый «родитель» указывает на своих «потомков». То есть в этой иерархической структуре вершина всегда «знает» своих потомков.
Для того чтобы более точно оперировать структурой Дерево, нужно дать определение некоторым ключевым понятиям:
− корневой узел — самый верхний узел дерева, он не имеет предков;
− лист, листовой или терминальный узел — конечный узел, то есть не имеющий потомков;
− внутренний узел — любой узел дерева, имеющий потомков, то есть не лист.
С корневого узла начинается выполнение большинства операций над деревом. Чтобы получить доступ к любому элементу структуры, необходимо, переходя по ветвям, перебирать элементы, начиная с головы — корневого узла. Корневой узел — это своеобразный вход в дерево. Большинство алгоритмов работы с деревом строятся на том, что каждый узел дерева рассматриваются как корневой узел поддерева, «растущего» из этого узла. Такой подход дает возможность зацикливать выполнение операций при прохождении по элементам дерева. Но в связи с тем, что при прохождении по дереву (в отличие от массива) неизвестно сколько шагов будет в этом цикле, используется другой инструмент — рекурсивный вызов.
Двоичное (бинарное) дерево — это древовидная структура данных, где каждый узел имеет не более двух детей. Этих детей называют левым (Л) и правым (П) потомком или «сыном». На рисунке выше дерево является двоичным.
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 Пример
Задача:
Необходимо разработать виртуальную модель процесса обучения. В программе должны быть объекты-ученики, учитель, кладезь знаний. Потребуется три класса – "учитель", "ученик", "данные". Учитель и ученик во многом похожи, оба – люди. Значит, их классы могут принадлежать одному надклассу "человек". Однако в контексте данной задачи у учителя и ученика вряд ли найдутся общие атрибуты. Определим, что должны уметь объекты для решения задачи "увеличить знания":
• Ученик должен уметь брать информацию и превращать ее в свои знания.
• Учитель должен уметь учить группу учеников.
• Данные могут представлять собой список знаний. Элементы будут извлекаться по индексу.
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)Вывод:
['inheritance']
['inheritance', 'class']1.2.2 Пример
Задача:
Напишите программу по следующему описанию. Есть класс "Воин". От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.
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Вывод:
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:21.2.3 Пример
Задача:
Создайте класс по работе с дробями. В классе должна быть реализована следующая функциональность:
− сложение дробей;
− вычитание дробей;
− умножение дробей;
− деление дробей
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)Вывод:
5/6
1/6
1/6
1/6
3/2
7/1Задание:
Создайте класс по работе с тригонометрическими функциями. В классе должны быть реализованы функции вычисления:
− косинуса;
− синуса;
− тангенса;
− арксинуса;
− арккосинуса;
− арктангенса;
− перевода из градусов в радианы.
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 / 1801.2. Теоретический материал – Реализация деревьев в Python
Любое представление графов, естественно, можно использовать для представления деревьев, потому что деревья — это особый вид графов. Однако, деревья играют свою большую роль в алгоритмах, и для них разработано много соответствующих структур и методов. Большинство алгоритмов на деревьях (например, поиск по деревьям) можно рассматривать в терминах теории графов, но специальные структуры данных делают их проще в реализации.
Проще всего описать представление дерева с корнем, в котором ребра спускаются вниз от корня. Такие деревья часто отображают иерархическое ветвление данных, где корень отображает все объекты (которые, возможно, хранятся в листьях), а каждый внутренний узел показывает объекты, содержащиеся в дереве, корень которого — этот узел. Это описание можно использовать, представив каждое поддерево списком, содержащим все его поддеревья-потомки. Рассмотрим простое дерево, показанное на рисунке ниже.
Мы можем представить это дерево как список списков:
T = [["a", "b"], ["c"], ["d", ["e", "f"]]] print(T[0][1]) print(T[2][1][0])
Каждый список в сущности является списком потомков каждого из внутренних узлов. Во втором примере мы обращаемся к третьему потомку корня, затем ко второму его потомку и в конце концов — к первому потомку предыдущего узла (этот путь отмечен на рисунке). В ряде случаев возможно заранее определить максимальное число потомков каждого узла. (Например, каждый узел бинарного дерева может иметь до двух потомков). Поэтому можно использовать другие представления, скажем, объекты с отдельным атрибутом для каждого из потомков как в листинге ниже.
1.2.1 Пример
Задача:
Определите класс бинарного дерева и задайте его объекты с отдельным атрибутом для каждого из потомков.
class Tree:
def __init__(self, left, right):
self.left = left
self.right = right
t = Tree(Tree('a', 'b'), Tree('c', 'd'))
t.right.leftВывод:
'c'1.2.2 Пример
Для обозначения отсутствующих потомков можно использовать None (в случае если у узла только один потомок). Само собой, можно комбинировать разные методы (например, использовать списки или множества потомков для каждого узла).
Распространенный способ реализации деревьев, особенно на языках, не имеющих встроенной поддержки списков, это так называемое представление «первый потомок, следующий брат». В нем каждый узел имеет два «указателя» или атрибута, указывающих на другие узлы, как в бинарном дереве. Однако, первый из этих атрибутов ссылается на первого потомка узла, а второй — на его следующего брата (т.е. узел, имеющий того же родителя, но находящийся правее, — прим. перев). Иными словами, каждый узел дерева имеет указатель на связанный список его потомков, а каждый из этих потомков ссылается на свой собственный аналогичный список. Таким образом, небольшая модификация бинарного дерева даст нам многопутевое дерево, показанное в листинге ниже.
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Вывод:
'c'Задание
Представьте дерево показанное на рисунке с использованием списка из списков. Выведите на печать корень дерева, а также его левое и правое поддеревья.
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)Вывод:
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 для печати полной версии дерева.
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()Вывод:
1
2
5
6
71.3. Теоретический материал – Деревья решений
Дерево решений – это один из наиболее часто и широко используемых алгоритмов контролируемого машинного обучения, который может выполнять как регрессионные, так и классификационные задачи. Использование деревьев решений для прогнозного анализа имеет ряд преимуществ:
-
Деревья решений могут быть использованы для прогнозирования как непрерывных, так и дискретных значений, т. е. они хорошо работают как для задач регрессии, так и для задач классификации.
-
Они требуют относительно меньших усилий для обучения алгоритма.
-
Они могут быть использованы для классификации нелинейно разделимых данных.
-
Они очень быстры и эффективны по сравнению с KNN и другими алгоритмами классификации.
Решим модельные примеры классификации и регрессии, разобранные в предыдущих раочих тетрадях, но с использованием деревьев принятия решений.
1.3.1 Пример
Задача:
Построим дерево решений для задачи классификации, для этого, построим границу решения для каждого класса. В качестве данных будем использовать уже знакомый нам и встроенный в библиотеку sklearn набор данных ирисов Фишера. Импортируем библиотеки, набор данных и посмотрим его характеристики.
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()Вывод:
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Далее, разделим наши данные на атрибуты и метки, а затем выделим в общей совокупности полученных данных обучающие и тестовые наборы. Таким образом, мы можем обучить наш алгоритм на одном наборе данных, а затем протестировать его на совершенно на другом наборе, который алгоритм еще не видел. Это дает вам более точное представление о том, как на самом деле будет работать ваш обученный алгоритм.
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()Вывод:
49 setosa
58 versicolor
68 versicolor
35 setosa
122 virginica
Name: species, dtype: objectПосле того, как данные были разделены на обучающие и тестовые наборы, последний шаг состоит в том, чтобы обучить алгоритм дерева решений на этих данных и сделать прогнозы. Scikit-Learn содержит библиотеку tree , которая содержит встроенные классы/методы для различных алгоритмов дерева решений. Поскольку мы собираемся выполнить здесь задачу классификации, мы будем использовать класс DecisionTreeClassifier для этого примера. Метод fit этого класса вызывается для обучения алгоритма на обучающих данных, которые передаются в качестве параметра методу fit . Выполним следующий сценарий для обучения алгоритма.
from sklearn.tree import DecisionTreeClassifier
classifier = DecisionTreeClassifier()
classifier.fit(x_train, y_train)
from sklearn import tree
tree.plot_tree(classifier)Вывод:
[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]')]<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.
Теперь, когда наш классификатор обучен, давайте сделаем прогнозы по тестовым данным. Для составления прогнозов используется метод predict класса Decision Tree Classifier. Взгляните на следующий код для использования.
y_pred = classifier.predict(x_test)
y_predВывод:
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, которые могут быть использованы для расчета этих метрик.
from sklearn.metrics import classification_report, confusion_matrix
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))Вывод:
[[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]
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))Вывод:
[[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<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.
1.4. Теоретический материал – Дерево решений для регрессии
Дерево решений для регрессии
Процесс решения регрессионной задачи с деревом решений с помощью Scikit Learn очень похож на процесс классификации. Однако для регрессии мы используем класс DecisionTreeRegressor древовидной библиотеки. Кроме того, оценочные показатели регрессии отличаются от показателей классификации. В остальном процесс почти такой же. Построим регрессию с использованием дерева решений в Python и библиотеки scikit-learn. В качестве исходного набора данных будем использовать зависимость заработной платы от опыта работы из предыдущей тетради:
1.4.1 Пример
Задача:
Постойте регрессию с использованием дерева решений, реализованного в 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Вывод:
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)<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.
[[ 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.6666666666675.421715809463662<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
Постройте прогноз. Оцените точность модели
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Вывод:
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)<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.
[[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]9.674553007043524<Figure size 640x480 with 1 Axes>Графический вывод (изображение) опущен в версии MDX.