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

Мы будем использовать Titanic Data from kaggle. Для этого поста мы будем выполнять как можно меньше функций, так как это не является целью этого поста.

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

Загрузить данные поезда

# get titanic & test csv files as a DataFrame
train = pd.read_csv(“input/train.csv”)
print train.shape
 > (891, 12)

Проверить отсутствующие значения

#Checking for missing data
NAs = pd.concat([train.isnull().sum()], axis=1, keys=[‘Train’])
NAs[NAs.sum(axis=1) > 0]
>Age 177
Cabin 687
Embarked 2

Мы удалим столбцы «Кабина», «Имя» и «Билет», так как они требуют некоторой обработки для извлечения полезных функций.

# At this point we will drop the Cabin feature since it is missing a lot of the data
train.pop(‘Cabin’)
train.pop(‘Name’)
train.pop(‘Ticket’)
train.shape
> (891, 9)

Заполните отсутствующие значения возраста средним значением

# Filling missing Age values with mean
train[‘Age’] = train[‘Age’].fillna(train[‘Age’].mean())

Заполните отсутствующие значения "Embarked" наиболее частым значением.

# Filling missing Embarked values with most common value
train[‘Embarked’] = train[‘Embarked’].fillna(train[‘Embarked’].mode()[0])

«Pclass» - это категориальная функция, поэтому мы преобразуем его значения в строки.

train[‘Pclass’] = train[‘Pclass’].apply(str)

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

# Getting Dummies from all other categorical vars
for col in train.dtypes[train.dtypes == ‘object’].index:
 for_dummy = train.pop(col)
 train = pd.concat([train, pd.get_dummies(for_dummy, prefix=col)], axis=1)
# Prepare data for training models
labels = train.pop(‘Survived’)

Для тестирования мы решили разделить наши данные на 75% для обучения и 25% для тестирования.

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(train, labels, test_size=0.25)

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

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier()
dt.fit(x_train, y_train)
> DecisionTreeClassifier(class_weight=None, criterion=’gini’, max_depth=None,
 max_features=None, max_leaf_nodes=None,
 min_impurity_split=1e-07, min_samples_leaf=1,
 min_samples_split=2, min_weight_fraction_leaf=0.0,
 presort=False, random_state=None, splitter=’best’)
y_pred = dt.predict(x_test)

Мы будем использовать AUC (Площадь под кривой) в качестве метрики оценки. Наше целевое значение двоичное, поэтому это проблема двоичной классификации. AUC - хороший способ оценки для этого типа проблем.

from sklearn.metrics import roc_curve, auc
false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred)
roc_auc = auc(false_positive_rate, true_positive_rate)
roc_auc
> 0.72657348804500699

Максимальная глубина

Первый параметр для настройки - max_depth. Это показывает, насколько глубоким может быть дерево. Чем глубже дерево, тем больше в нем разделений и больше информации о данных. Мы подбираем дерево решений с глубиной от 1 до 32 и наносим на график оценки AUC для обучения и тестирования.

max_depths = np.linspace(1, 32, 32, endpoint=True)
train_results = []
test_results = []
for max_depth in max_depths:
   dt = DecisionTreeClassifier(max_depth=max_depth)
   dt.fit(x_train, y_train)
   train_pred = dt.predict(x_train)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_train, train_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   # Add auc score to previous train results
   train_results.append(roc_auc)
   y_pred = dt.predict(x_test)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   # Add auc score to previous test results
   test_results.append(roc_auc)
from matplotlib.legend_handler import HandlerLine2D
line1, = plt.plot(max_depths, train_results, ‘b’, label=”Train AUC”)
line2, = plt.plot(max_depths, test_results, ‘r’, label=”Test AUC”)
plt.legend(handler_map={line1: HandlerLine2D(numpoints=2)})
plt.ylabel(‘AUC score’)
plt.xlabel(‘Tree depth’)
plt.show()

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

min_samples_split

min_samples_split представляет минимальное количество выборок, необходимое для разделения внутреннего узла. Это может варьироваться от рассмотрения хотя бы одной выборки в каждом узле до рассмотрения всех выборок в каждом узле. Когда мы увеличиваем этот параметр, дерево становится более ограниченным, поскольку ему приходится рассматривать больше выборок на каждом узле. Здесь мы будем варьировать параметр от 10% до 100% образцов.

min_samples_splits = np.linspace(0.1, 1.0, 10, endpoint=True)
train_results = []
test_results = []
for min_samples_split in min_samples_splits:
   dt = DecisionTreeClassifier(min_samples_split=min_samples_split)
   dt.fit(x_train, y_train)
   train_pred = dt.predict(x_train)
   false_positive_rate, true_positive_rate, thresholds =    roc_curve(y_train, train_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   train_results.append(roc_auc)
   y_pred = dt.predict(x_test)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   test_results.append(roc_auc)
from matplotlib.legend_handler import HandlerLine2D
line1, = plt.plot(min_samples_splits, train_results, ‘b’, label=”Train AUC”)
line2, = plt.plot(min_samples_splits, test_results, ‘r’, label=”Test AUC”)
plt.legend(handler_map={line1: HandlerLine2D(numpoints=2)})
plt.ylabel(‘AUC score’)
plt.xlabel(‘min samples split’)
plt.show()

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

min_samples_leaf

min_samples_leaf: минимальное количество выборок, которое требуется для конечного узла. Этот параметр аналогичен параметру min_samples_splits, однако он описывает минимальное количество выборок выборок на листьях, в основании дерева.

min_samples_leafs = np.linspace(0.1, 0.5, 5, endpoint=True)
train_results = []
test_results = []
for min_samples_leaf in min_samples_leafs:
   dt = DecisionTreeClassifier(min_samples_leaf=min_samples_leaf)
   dt.fit(x_train, y_train)
   train_pred = dt.predict(x_train)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_train, train_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   train_results.append(roc_auc)
   y_pred = dt.predict(x_test)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   test_results.append(roc_auc)
from matplotlib.legend_handler import HandlerLine2D
line1, = plt.plot(min_samples_leafs, train_results, ‘b’, label=”Train AUC”)
line2, = plt.plot(min_samples_leafs, test_results, ‘r’, label=”Test AUC”)
plt.legend(handler_map={line1: HandlerLine2D(numpoints=2)})
plt.ylabel(‘AUC score’)
plt.xlabel(‘min samples leaf’)
plt.show()

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

max_features

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

max_features = list(range(1,train.shape[1]))
train_results = []
test_results = []
for max_feature in max_features:
   dt = DecisionTreeClassifier(max_features=max_feature)
   dt.fit(x_train, y_train)
   train_pred = dt.predict(x_train)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_train, train_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   train_results.append(roc_auc)
   y_pred = dt.predict(x_test)
   false_positive_rate, true_positive_rate, thresholds = roc_curve(y_test, y_pred)
   roc_auc = auc(false_positive_rate, true_positive_rate)
   test_results.append(roc_auc)
from matplotlib.legend_handler import HandlerLine2D
line1, = plt.plot(max_features, train_results, ‘b’, label=”Train AUC”)
line2, = plt.plot(max_features, test_results, ‘r’, label=”Test AUC”)
plt.legend(handler_map={line1: HandlerLine2D(numpoints=2)})
plt.ylabel(‘AUC score’)
plt.xlabel(‘max features’)
plt.show()

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

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