#!/usr/bin/env python # coding: utf-8 # # Деревья решений # Шестаков А.В. Майнор по анализу данных 05/04/2016 # На прошлых занятиях мы рассматривали **линейные** модели классификации и регрессии. Деревья решений - совсем другая история. Во-первых, потому что их можно использовать и для регрессии и для классификации, а во-вторых линейностью там только слегка веет. # # Формально, деревья решений можно представить в виде вложенного набора правил "Если - То", но гораздо нагляднее изображать их именно в виде дерева. # # Например, дерево может выглядеть так: # # # ### Классификация с деревьями решений # Давайте попробуем вспомнить, как они стоятся. Рассмотрим следующий набор данных # # # | ID | Refund | Marital Status | Income | Cheat # |- # | 1 | Yes | Single | 125K | No # | 2 | No | Married | 100K | No # | 3 | No | Single | 70K | No # | 4 | Yes | Married | 120K | No # | 5 | No | Divorced | 95K | Yes # | 6 | No | Married | 60K | No # | 7 | Yes | Divorced | 220K | No # | 8 | No | Single | 85K | Yes # | 9 | No | Married | 75K | No # | 10 | No | Single | 90K | Yes # # Имеем 3 признака и класс *Cheat*. Нужно выбрать признак, который наилучшим образом дифференцирует между классами. Посчитать это можно с помощью Impurity Measures и прироста информации: # # **Impurity Measures: (меры неравенства\неопределенности)** # * Gini index $I(S) = 1 - \sum\limits_k (p_k)^2$ # * Entropy $I(S) = -\sum\limits_k p_k \log(p_k)$ # * Missclassification error $I(S) = 1 - \max\limits_k p_k$ # # $p_k$ - доля класса $k$ в узле дерева $S$ # # **Прирост информации: (насколько уменьшится неопределенность)**
# $$ Gain(S, A) = I(S) - \sum\limits_v\frac{|S_v|}{|S|}\cdot I(S_v),$$ где $A$ - это некий атрибут, а $v$ - его значения # # Например, для нашей таблицы: # $$I(S) = -(\frac{3}{10}\log(\frac{3}{10}) + \frac{7}{10}\log(\frac{7}{10})) = 0.61$$ # # Возьмем, например, атрибут *Marital Status* # # $$Gain(S, \text{`Marital Status`}) = I(S) - (\frac{4}{10}\cdot I(S_{single}) + \frac{2}{10}\cdot I(S_{divorced}) + \frac{4}{10}\cdot I(S_{married})) = 0.19$$ # # Проделаем тоже самое для остальных атрибутов.. # In[ ]: # Your code here # ### Регрессия с деревьями решений # В этом случае всё очень похоже, с той разницой, что мы будем пытаться уменьшить среднюю квадратичную ошибку # $$I(S) = \frac{1}{|S|} \sum\limits_{i \in S} (y_i - c)^2 $$ # $$ c = \frac{1}{|S|}\sum\limits_{i \in S} y_i $$ # ### Как посмотреть на деревья? # Для этого вам понадобится специальная библиотека по работе с графами `graphviz`. [Здесь](http://scikit-learn.org/stable/modules/tree.html) вы можете увидеть пример нарисованного в Python дерева. # # Если вы хотите увидеть правила, то можно использовать код [отсюда](http://stackoverflow.com/questions/20224526/how-to-extract-the-decision-rules-from-scikit-learn-decision-tree) # In[ ]: def get_code(tree, feature_names): left = tree.tree_.children_left right = tree.tree_.children_right threshold = tree.tree_.threshold features = [feature_names[i] for i in tree.tree_.feature] value = tree.tree_.value def recurse(left, right, threshold, features, node): if (threshold[node] != -2): print "if ( " + features[node] + " <= " + str(threshold[node]) + " ) {" if left[node] != -1: recurse (left, right, threshold, features,left[node]) print "} else {" if right[node] != -1: recurse (left, right, threshold, features,right[node]) print "}" else: print "return " + str(value[node]) recurse(left, right, threshold, features, 0) # ### Немного интуиции # In[ ]: import pandas as pd import numpy as np import subprocess from sklearn.datasets import make_circles from sklearn.tree import DecisionTreeRegressor from sklearn.tree import DecisionTreeClassifier from sklearn.tree import export_graphviz import matplotlib.pyplot as plt plt.style.use('ggplot') get_ipython().run_line_magic('matplotlib', 'inline') # #### Классификация # In[ ]: X, y = make_circles(n_samples=500, noise=0.1, factor=0.2) # In[ ]: # Your code here # In[ ]: with open('tree.dot', 'w') as fout: export_graphviz(tree, out_file=fout, feature_names=['x1', 'x2'], class_names=['0', '1']) command = ['dot', '-Tpng', 'tree.dot', '-o', 'tree.png'] subprocess.check_call(command) plt.figure(figsize=(10, 10)) plt.imshow(plt.imread('tree.png')) plt.axis('off') # #### Регрессия # In[ ]: X = np.linspace(-5, 5, 100) X += np.random.randn(X.shape[0]) y = 5*np.sin(2*X) + X**2 y += 2*np.random.randn(y.shape[0]) # In[ ]: plt.scatter(X, y) # In[ ]: # Your code here # In[ ]: with open('tree.dot', 'w') as fout: export_graphviz(tree, out_file=fout, feature_names=['x1']) command = ['dot', '-Tpng', 'tree.dot', '-o', 'tree.png'] subprocess.check_call(command) plt.figure(figsize=(20, 20)) plt.imshow(plt.imread('tree.png')) plt.axis('off') # ### Перейдем к практике # Загрузите [данные](https://www.dropbox.com/s/3t1moa1wpflx2u9/california.dat?dl=0). # # **Задание 1:** Найти оптимальную глубину дерева.
# Разделите выборку на train-test в пропорции 70/30.
# Обучите деревья с глубиной от `1` до `30`. Для каждой глубины расчитайте среднюю квадратичную ошибку на train и на test
# Изобразите эти ошибки на одном графике, сделайте вывод по поводу оптимальной глубины дерева. # In[ ]: # Your code here # **Задание 2:** Выведите важности признаков. Для этого воспользуйтесь `DecisionTreeRegressor.feature_importances_` # In[ ]: # Your code here # **Задание 3:** Поразмышляйте на темы: # * Нужна ли нормировка признаков для деревьев решений? # * Обработки пропусков в данных. # * Как сделать разделяющие плоскости непараллельные осям?