Скачать ipynb
trees

Введение в анализ данных

Решающие деревья

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

In [1]:
import numpy as np
import pandas as pd
import scipy.stats
import warnings
import graphviz
from tqdm import tqdm_notebook

from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn import datasets
from sklearn.metrics import accuracy_score, mean_squared_error
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, export_graphviz
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.model_selection import train_test_split

sns.set(font_scale=1.5)
warnings.filterwarnings("ignore")

1. Решающие деревья в задаче классификации

Для начала рассмотрим задачу классификации на простом датасете, состоящем только из двух признаков. Это сделает удобным процесс визуализации решающего дерева. Для генерации такого простого датасета воспользуемся методом `make_classification` модуля sklearn.datasets.

Генерация данных

Основные аргументы функции:

  • n_samples — размер выборки;
  • n_features — количество признаков всего (кроме целевого);
  • n_informative — количество "информативных"признаков, от которых непосредственно зависит целевой признак;
  • n_classes — количество классов;
  • n_redundant — количество "лишних" признаков, не оказывающих воздействие на целевой.
In [2]:
data, target = datasets.make_classification(
    n_samples=100, n_features=2, n_informative=2, n_classes=3, 
    n_redundant=0, n_clusters_per_class=1, random_state=3
)

data.shape, target.shape
Out[2]:
((100, 2), (100,))

Сопоставим каждому классу цвет

In [3]:
colors = ListedColormap(['#FF3300', '#0099CC', '#00CC66'])
light_colors = ListedColormap(['lightcoral', 'lightblue', 'lightgreen'])

Посмотрим на данные.

In [4]:
plt.figure(figsize=(8, 6))
grid_x1 = data[:, 0]
grid_x2 = data[:, 1]
plt.scatter(grid_x1, grid_x2, c=target, cmap=colors, s=100, alpha=0.7)
plt.xlabel('Признак 1'), plt.ylabel('Признак 2');

Разобьём данные на обучающую и тестовую выборки.

In [5]:
X_train, X_test, y_train, y_test = train_test_split(
    data, target, test_size=0.3, random_state=777
)

Инициализируем и обучим решающее дерево для классификации с помощью `DecisionTreeClassifier`. Общий интерфейс класса аналогичен классу LinearRegression, который мы подробно разбирали ранее. Гиперпараметры дерева мы разберем далее.

In [6]:
clf = DecisionTreeClassifier(random_state=42)
clf.fit(X_train, y_train)
Out[6]:
DecisionTreeClassifier(random_state=42)

Посчитаем предсказания дерева и посчитаем точность полученной классификации. Точность классификации вычисляется как доля правильно предсказанных классов.

In [7]:
predictions = clf.predict(X_test)
accuracy_score(predictions, y_test)
Out[7]:
0.9

Неплохой результат. Но на таком простом датасете могло быть и лучше.

Визуализация решающей поверхности (decision surface)

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

In [8]:
def get_meshgrid(data, step=.05, border=.5):
    '''
    Функция для получения сетки точек (x1, x2) 
    для дальнейшего отображения их на графиках
    
    Параметры:
    1) data - входной датасет, набор точек (x1_i, x2_i);
    2) step - мелкость сетки;
    3) border - отступ от минимальных и максимальных значений x1, x2 в data 
    в сетке
    '''
    
    x1_min, x1_max = data[:, 0].min() - border, data[:, 0].max() + border
    x2_min, x2_max = data[:, 1].min() - border, data[:, 1].max() + border
    return np.meshgrid(np.arange(x1_min, x1_max, step), 
                       np.arange(x2_min, x2_max, step))
In [9]:
def plot_decision_surface(
    estimator, X_train, y_train, X_test, y_test, colors=colors, 
    light_colors=light_colors, title='', metric=accuracy_score
):
    '''
    Функция для отображения разделяющей поверхности классификатора
    
    Параметры:
    1) estimator - классификатор;
    2) X_train, y_train - данные и разметка обучающей выборки;
    3) X_test, y_test - данные и разметка тестовой выборки;
    4) colors - цвета для отображения точек из разных классов;
    5) light_colors - цветовая схема для отображения разделяющей поверхности;
    6) title - заголовок графика.
    7) metric - метрика качества классификации.
    '''
    
    estimator.fit(X_train, y_train)  # обучаем модель
    
    plt.figure(figsize=(16, 6))
    
    # отображаем разделяющую поверхность и точки обучающей выборки
    plt.subplot(1,2,1)
    x1_values, x2_values = get_meshgrid(X_train)
    x1_ravel, x2_ravel = x1_values.ravel(), x2_values.ravel()
    mesh_predictions_ravel = estimator.predict(np.c_[x1_ravel, x2_ravel])
    mesh_predictions = np.array(mesh_predictions_ravel).reshape(x1_values.shape)
    
    plt.grid(False)
    plt.pcolormesh(x1_values, x2_values, mesh_predictions, cmap=light_colors)
    plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, 
                s=100, cmap=colors, edgecolors='black')
    plt.xlabel('Признак 1'), plt.ylabel('Признак 2')
    plt.title('Обучающая выборка, {}={:.2f}'.format(
        metric.__name__,metric(y_train, estimator.predict(X_train))
    ))
    
    # отображаем разделяющую поверхность и точки тестовой выборки
    plt.subplot(1,2,2)
    plt.grid(False)
    plt.pcolormesh(x1_values, x2_values, mesh_predictions, cmap=light_colors)
    plt.scatter(X_test[:, 0], X_test[:, 1], c=y_test, 
                s=100, cmap=colors, edgecolors='black')
    plt.title('Тестовая выборка, {}={:.2f}'.format(
        metric.__name__, metric(y_test, estimator.predict(X_test))
    ))
    plt.xlabel('Признак 1'), plt.ylabel('Признак 2')
    plt.suptitle(title, fontsize=20)

Визуализация разделяющей поверхности при изменении параметра max_depth

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

In [10]:
estimator = DecisionTreeClassifier(random_state=42, max_depth=1)
plot_decision_surface(
    estimator, X_train, y_train, 
    X_test, y_test, title='max_depth=1'
)

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

Несложно заметить, что если в датасете для классификации $k$ классов, то необходимо брать дерево с глубиной не менее $\log_2 k$, так как мы хотим, чтобы в полученном дереве было не менее $k$ листьев. Иначе дерево будет предсказывать $< k$ классов, чего мы хотим избежать.

Попробуем увеличить максимальную глубину дерева.

In [11]:
estimator = DecisionTreeClassifier(random_state=42, max_depth=2)
plot_decision_surface(
    estimator, X_train, y_train, 
    X_test, y_test, title='max_depth=2'
)
In [12]:
estimator = DecisionTreeClassifier(random_state=42, max_depth=3)
plot_decision_surface(
    estimator, X_train, y_train, 
    X_test, y_test, title='max_depth=3'
)

Заметим, что сложность разделяющей поверхности заметно увеличилась. Точность предсказания дерева заметно возросла.

А теперь посмотрим, что произойдёт, если резко увеличить значение параметра max_depth.

In [13]:
estimator = DecisionTreeClassifier(random_state=42, max_depth=20)
plot_decision_surface(
    estimator, X_train, y_train, 
    X_test, y_test, title='max_depth=20'
)

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

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

Вывод.

Как правило,

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

Визуализация разделяющей поверхности при изменении параметра min_samples_leaf

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

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

In [14]:
for min_samples_leaf in [1, 2, 10, 20]:
    estimator = DecisionTreeClassifier(
        random_state=42, min_samples_leaf=min_samples_leaf
    )
    plot_decision_surface(
        estimator, X_train, y_train, X_test, y_test, 
        title=f'min_samples_leaf={min_samples_leaf}'
    )

Построим график зависимости accuracy от min_samples_leaf на обучающей и на тестовой выборках.

In [15]:
def get_train_and_test_accuracy(param_name, grid):
    '''
    Функция для оценки точности классификации 
    для заданных значений параметра param_name
    
    Параметры:
    1) param_name - название параметра, который собираемся варьировать,
    2) grid - сетка значений параметра
    '''
        
    train_acc, test_acc = [], []
    
    for param_value in grid:
        estimator = DecisionTreeClassifier(**{param_name: param_value})
        estimator.fit(X_train, y_train)
        train_acc.append(accuracy_score(y_train, estimator.predict(X_train)))
        test_acc.append(accuracy_score(y_test, estimator.predict(X_test)))
    return train_acc, test_acc
In [16]:
def plot_dependence(param_name, grid=range(2, 20), title=''):
    '''
    Функция для отображения графика зависимости accuracy 
    от значения параметра c названием param_name
    
    Параметры:
    1) param_name - название параметра, который собираемся варьировать,
    2) grid - сетка значений параметра,
    3) title - заголовок графика
    '''
    
    plt.figure(figsize=(12, 6))
    
    train_acc, test_acc = get_train_and_test_accuracy(param_name, grid)
        
    plt.plot(grid, train_acc, label='train', lw=3)
    plt.plot(grid, test_acc, label='test', lw=3)
    plt.legend(fontsize=14)
    plt.xlabel(param_name)
    plt.ylabel('Точность классификации')
    plt.title(title, fontsize=20)
    plt.show()
In [17]:
plot_dependence(
    'min_samples_leaf', range(1, 50), 
    title='Зависимость метрики от min_samples_leaf'
)

Вывод.

В целом наблюдается следующая закономерность. С увеличением значения min_samples_leaf качество

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

Получается, увеличение значения параметра min_samples_leaf — один из способов борьбы с переобучением при использовании решающих деревьев.

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

In [18]:
estimator = DecisionTreeClassifier(
    random_state=42, min_samples_leaf=80
)
plot_decision_surface(
    estimator, X_train, y_train, 
    X_test, y_test, title='min_samples_leaf=80'
)

Здесь мы привели пример решающего дерева при использовании min_samples_leaf=80.

Визуализация разделяющей поверхности при изменении параметра min_samples_split

Последний параметр, который мы будем подробно визуализировать — min_samples_split, минимальное количество элементов, которое должно попасть в вершину, чтобы её можно было делить.

In [19]:
for min_samples_split in [2, 10, 20]:
    estimator = DecisionTreeClassifier(
        random_state=42, min_samples_leaf=1,
        min_samples_split=min_samples_split
    )
    plot_decision_surface(
        estimator, X_train, y_train, X_test, y_test, 
        title=f'min_samples_split={min_samples_split}'
    )

А теперь попробуем резко увеличить значение min_samples_split.

In [20]:
estimator = DecisionTreeClassifier(
    random_state=42, min_samples_leaf=1, min_samples_split=50
)
plot_decision_surface(
    estimator, X_train, y_train, 
    X_test, y_test, title='min_samples_split=50'
)

Вывод.

При изменении значения min_samples_split происходит ситуация, аналогичной случаю, когда мы варьируем min_samples_leaf. И здесь наблюдается следующая закономерность: с увеличением значения min_samples_split качество

  • на обучающей выборке падает,
  • на тестовой выборке — до некоторого момента возрастает.

Кроме того, с увеличением min_samples_split разделяющая поверхность становится проще.

Другие параметры.

Кроме того, обратите внимание на другие параметры класса DecisionTreeClassifier в sklearn:

  1. criterion — критерий, по которому происходит разбиение вершины дерева. Стандартные критерии для классификации — критерий Джини (giny) и энтропийный критерий (entropy), при этом giny — критерий по умолчанию. В этом ноутбуке мы брали для классификации критерий по умолчанию. Более подробную информацию по критериям можно найти в документации sklearn.
  1. splitter — способ разбиения вершины решающего дерева. Есть два возможных варианта: best и random. В первом случае рассматриваются все возможные способы разбить вершину дерева на две и берётся тот из них, значение критерия для которого оптимально. При splitter=random берётся несколько случайных возможных разбиений и среди них выбирается то, значение критерия для которого оптимально.
  1. max_features — максимальное количество признаков, которые могут быть перебраны при разбиении вершины дерева. Перед каждым разбиением вершины генерируется выборка из min(d, max_features) случайных признаков (d — количество признаков в датасете) и только эти признаки рассматриваются как разделяющие.
  1. min_impurity_decrease — минимальное значение уменьшения взвешенного критерия неопределенности (impurity), чтобы можно было разбить выборку в данной вершине.

Об остальных гиперпараметрах класса решающего дерева в sklearn можно прочитать в документации.

2. Регрессия с использованием решающего дерева

В регрессии будем использовать все те же параметры разбиения. Но в качестве критерия неопределенности (параметр criterion) используются squared_error, friedman_mse, absolute_error, причём squared_error — критерий по умолчанию..

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

In [21]:
data, target = datasets.make_regression(
    n_features=2, n_informative=2, random_state=3, n_samples=200
)

Визуализируем. Отклик показан цветом точки.

In [22]:
plt.figure(figsize=(8, 6))
grid_x1 = data[:, 0]
grid_x2 = data[:, 1]
plt.scatter(grid_x1, grid_x2, c=target, s=100, alpha=0.7, cmap='winter')
plt.xlabel('Признак 1'), plt.ylabel('Признак 2');

Разобьём данные на обучение и тест.

In [23]:
X_train, X_test, y_train, y_test = train_test_split(
    data, target, random_state=42
)

Исследуем зависимость качества работы регрессионного дерева в зависимости от максимально возможной его глубины.

Цветом поверхности обозначается ответ в листе. Но для регрессит это не класс, а некоторое число.

In [24]:
for max_depth in [1, 2, 3, 5, 10, 20]:
    estimator = DecisionTreeRegressor(random_state=42, max_depth=max_depth)
    plot_decision_surface(
        estimator, X_train, y_train, X_test, y_test, 
        title='max_depth={}'.format(max_depth), colors='winter',
        light_colors='winter', metric=mean_squared_error
    )

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

3. Визуализация решающих деревьев

Посмотрим, как можно визуализировать структуру решающего дерева, в этом нам поможет библиотека graphviz.

Для установки библиотеки через pip:

pip install graphiz

Для установки библиотеки через conda:

conda install -c conda-forge python-graphiz

В качестве данных возьмём датасет с Kaggle по определению цены камеры по её имеющимся характеристикам.

К её характеристикам относятся:

  • Release date — дата выпуска,

  • Max resolution — максимальное разрешение,

  • Low resolution — нижнее разрешение,

  • Effective pixels — число эффективных пикселей,

  • Zoom wide (W) — ширина зума,

  • Zoom tele (T) — телезум,

  • Normal focus range — нормальный фокусный диапазон,

  • Macro focus range — макрофокусный диапазон,

  • Storage included — встроенный объём памяти,

  • Weight (inc. batteries) — вес,

  • Dimensions — габариты.

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

In [25]:
camera_df = pd.read_csv('camera_dataset.csv')[:100]
camera_df = camera_df.drop('Model', axis=1)

print(camera_df.shape)
camera_df.head()
(100, 12)
Out[25]:
Release date Max resolution Low resolution Effective pixels Zoom wide (W) Zoom tele (T) Normal focus range Macro focus range Storage included Weight (inc. batteries) Dimensions Price
0 1997 1024.0 640.0 0.0 38.0 114.0 70.0 40.0 4.0 420.0 95.0 179.0
1 1998 1280.0 640.0 1.0 38.0 114.0 50.0 0.0 4.0 420.0 158.0 179.0
2 2000 640.0 0.0 0.0 45.0 45.0 0.0 0.0 2.0 0.0 0.0 179.0
3 1999 1152.0 640.0 0.0 35.0 35.0 0.0 0.0 4.0 0.0 0.0 269.0
4 1999 1152.0 640.0 0.0 43.0 43.0 50.0 0.0 40.0 300.0 128.0 1299.0

Выделим отдельно признаки и таргет

In [26]:
X, y = camera_df.loc[:, camera_df.columns != 'Price'], camera_df['Price']

Напишем функцию визуализации дерева

In [27]:
def get_graphviz_graph(decision_tree_regressor):
    '''
    Функция для обучения решающего дерева и построения
    по нему графа graphviz для визуализации
    '''
    
    decision_tree_regressor.fit(X, y)
    dot_data = export_graphviz(
        decision_tree_regressor, out_file=None, 
        feature_names=X.columns, class_names='Price',  
        filled=True, rounded=True, special_characters=True
    )  
    return graphviz.Source(dot_data)  

Визуализируем деревья для разной глубины

In [28]:
camera_regressor = DecisionTreeRegressor(
    criterion='absolute_error', max_depth=2, min_samples_split=10
)
graph = get_graphviz_graph(camera_regressor)
graph
Out[28]:
Tree 0 Weight (inc. batteries) ≤ 885.0 absolute_error = 608.35 samples = 100 value = 189.0 1 Zoom wide (W) ≤ 14.0 absolute_error = 193.207 samples = 92 value = 179.0 0->1 True 4 absolute_error = 1562.5 samples = 8 value = 4499.0 0->4 False 2 absolute_error = 150.0 samples = 10 value = 1299.0 1->2 3 absolute_error = 95.549 samples = 82 value = 149.0 1->3
In [29]:
camera_regressor = DecisionTreeRegressor(
    criterion='absolute_error', max_depth=3, min_samples_split=10
)
graph = get_graphviz_graph(camera_regressor)
graph
Out[29]:
Tree 0 Weight (inc. batteries) ≤ 885.0 absolute_error = 608.35 samples = 100 value = 189.0 1 Zoom tele (T) ≤ 16.5 absolute_error = 193.207 samples = 92 value = 179.0 0->1 True 8 absolute_error = 1562.5 samples = 8 value = 4499.0 0->8 False 2 Release date ≤ 2002.5 absolute_error = 150.0 samples = 10 value = 1299.0 1->2 5 Zoom tele (T) ≤ 205.0 absolute_error = 95.549 samples = 82 value = 149.0 1->5 3 absolute_error = 0.0 samples = 2 value = 549.0 2->3 4 absolute_error = 0.0 samples = 8 value = 1299.0 2->4 6 absolute_error = 75.775 samples = 71 value = 149.0 5->6 7 absolute_error = 66.818 samples = 11 value = 399.0 5->7
In [30]:
camera_regressor = DecisionTreeRegressor(
    criterion='absolute_error', max_depth=8, min_samples_split=8
)
graph = get_graphviz_graph(camera_regressor)
graph
Out[30]:
Tree 0 Weight (inc. batteries) ≤ 885.0 absolute_error = 608.35 samples = 100 value = 189.0 1 Zoom tele (T) ≤ 16.5 absolute_error = 193.207 samples = 92 value = 179.0 0->1 True 24 Low resolution ≤ 3528.0 absolute_error = 1562.5 samples = 8 value = 4499.0 0->24 False 2 Release date ≤ 2002.5 absolute_error = 150.0 samples = 10 value = 1299.0 1->2 5 Zoom tele (T) ≤ 205.0 absolute_error = 95.549 samples = 82 value = 149.0 1->5 3 absolute_error = 0.0 samples = 2 value = 549.0 2->3 4 absolute_error = 0.0 samples = 8 value = 1299.0 2->4 6 Weight (inc. batteries) ≤ 463.5 absolute_error = 75.775 samples = 71 value = 149.0 5->6 19 Normal focus range ≤ 52.5 absolute_error = 66.818 samples = 11 value = 399.0 5->19 7 Storage included ≤ 36.0 absolute_error = 61.25 samples = 64 value = 149.0 6->7 18 absolute_error = 144.286 samples = 7 value = 499.0 6->18 8 Zoom wide (W) ≤ 32.5 absolute_error = 43.968 samples = 63 value = 149.0 7->8 17 absolute_error = 0.0 samples = 1 value = 1299.0 7->17 9 absolute_error = 83.333 samples = 6 value = 399.0 8->9 10 Dimensions ≤ 88.5 absolute_error = 30.526 samples = 57 value = 139.0 8->10 11 Zoom tele (T) ≤ 106.5 absolute_error = 23.333 samples = 12 value = 199.0 10->11 14 Dimensions ≤ 98.0 absolute_error = 25.778 samples = 45 value = 139.0 10->14 12 absolute_error = 12.222 samples = 9 value = 199.0 11->12 13 absolute_error = 3.333 samples = 3 value = 139.0 11->13 15 absolute_error = 15.833 samples = 12 value = 149.0 14->15 16 absolute_error = 26.364 samples = 33 value = 139.0 14->16 20 Max resolution ≤ 3456.0 absolute_error = 26.875 samples = 8 value = 399.0 19->20 23 absolute_error = 86.667 samples = 3 value = 139.0 19->23 21 absolute_error = 3.0 samples = 5 value = 399.0 20->21 22 absolute_error = 33.333 samples = 3 value = 499.0 20->22 25 absolute_error = 916.667 samples = 6 value = 4499.0 24->25 26 absolute_error = 0.0 samples = 2 value = 7999.0 24->26

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

Случайный лес

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

Основные параметры

Реализации: RandomForestClassifier, RandomForestRegressor

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

О других гиперпараметрах случайного леса можно почитать в документации.

Решение задачи регрессии с помощью Random Forest

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

In [31]:
X_train = scipy.stats.multivariate_normal.rvs(
    size=500, mean=[0, 0], cov=[[3, 0], [0, 3]]
)
y_train = (X_train[:, 0] ** 2 + X_train[:, 1] ** 2) ** 0.5

Визуализируем

In [32]:
plt.figure(figsize=(10, 8))
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap='spring', 
            s=100, alpha=0.8, linewidths=1, edgecolors='black')
plt.show()

Напишем функции для визуализации

In [33]:
def generate_grid(sample, border=1, step=0.05):
    ''' Создает сетку на основе выборки для раскраски пространства '''
    
    return np.meshgrid(np.arange(min(sample[:, 0]) - border, 
                                 max(sample[:, 1]) + border, 
                                 step), 
                       np.arange(min(sample[:, 1]) - border, 
                                 max(sample[:, 1]) + border, 
                                 step))


def create_picture(X_train, y_train, model, border=1, step=0.05, 
                   cmap='spring', alpha=1, create_new_figure=True, 
                   figsize=(7, 7), s=100, linewidths=1, points=True):
    '''
    Раскрашивает пространство в соответствии с предсказаниями 
    случайного леса/решающего дерева
    
    Параметры.
    1) X_train — данные обучающей выборки,
    2) y_train — метки обучающей выборки,
    3) model — визуализируемая модель,
    4) border — размер границы между областями пространства, 
       полученными моделью,
    5) step — точность сетки пространства,
    6) cmap — цветовая схема,
    7) aplha — прозрачность точек обучающей выборки,
    8) create_new_figure — флаг, определяющий создавать ли
       новую фигуру,
    9) figsize — размер создаваемой фигуры,
    10) s — размер точек обучающей выборки,
    11) linewidths — размер границы каждой точки,
    12) point — флаг, определяющий, отображать ли точки
        обучающей выборки на графике.
    '''
    
    # Создание сетки
    grid = generate_grid(X_train, border, step)
    # Выворачивание сетки для применения модели
    grid_ravel = np.c_[grid[0].ravel(), grid[1].ravel()]
    
    # Предсказание значений для сетки
    grid_predicted_ravel = model.predict(grid_ravel)
    grid_predicted = grid_predicted_ravel.reshape(grid[0].shape)  # Подгоняем размер
    
    
    # Построение фигуры
    if create_new_figure:
        plt.figure(figsize=figsize)
        
    plt.pcolormesh(grid[0], grid[1], grid_predicted, cmap=cmap)
    if points:
        plt.scatter(
            X_train[:, 0], X_train[:, 1], c=y_train, s=s, 
            alpha=alpha, cmap=cmap, linewidths=linewidths, edgecolors='black'
        )
    plt.xlim((min(grid_ravel[:, 0]), max(grid_ravel[:, 0])))
    plt.ylim((min(grid_ravel[:, 1]), max(grid_ravel[:, 1])))
    plt.title('max_depth = ' + str(model.get_params()['max_depth']))
    
    if create_new_figure:
        plt.show()

Визуализация предсказания решающими деревьями с различным значением максимальной глубины

In [34]:
for max_depth in range(2, 6):
    create_picture(
        X_train, y_train, 
        DecisionTreeRegressor(max_depth=max_depth).fit(X_train, y_train)
    )

Визуализация предсказаний случайного леса

Обучим случайный лес на 35 деревьев, такое число взято для удобства визуализации

In [35]:
n_estimators = 35
model = RandomForestRegressor(n_estimators=n_estimators)
model.fit(X_train, y_train)
Out[35]:
RandomForestRegressor(n_estimators=35)

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

In [36]:
plt.figure(figsize=(20, 25))
for i in range(n_estimators):
    plt.subplot(int(np.floor(n_estimators / 5)), 5, i+1)
    create_picture(X_train, y_train, model.estimators_[i], 
                   create_new_figure=False, s=20, linewidths=0, points=False)
    plt.title('')
    plt.xticks([]), plt.yticks([])

Усредненное предсказание

In [37]:
create_picture(X_train, y_train, model, figsize=(12, 12))

То же самое с 100 деревьями

In [38]:
n_estimators = 100
model = RandomForestRegressor(n_estimators=n_estimators)
model.fit(X_train, y_train)
Out[38]:
RandomForestRegressor()
In [39]:
create_picture(X_train, y_train, model, figsize=(12, 12))