ml5 Ансамбли
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn import datasets
df = pd.read_csv('HR-dataset.csv')
np.random.seed(42)
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
target = 'left'
features = df.columns.drop(target)
features = features.drop('empid') # Удалим идентификатор пользователя как нерепрезентативный признак
print(features)
X, y = df[features].copy(), df[target]
Index(['satisfaction_level', 'last_evaluation', 'number_project', 'average_montly_hours', 'time_spend_company', 'Work_accident', 'promotion_last_5years', 'dept', 'salary'], dtype='object')
Предобработка¶
Частотное кодирование
- Заменим идентификатор отдела
dept
, к которому относился сотрудник, на количество людей в отделе
Ординальноге кодирование
Зарплату
salary
— на ординальную категорию, используяsalary_ordinals
Масштабируем признаки для последующего сравнения результатов с помошью
StandardScaler
# частотное кодирование
dept_val_counts = X['dept'].value_counts()
X['dept'] = X['dept'].map(dept_val_counts)
# ординальное кодирование
salary_ordinals = {'low': 1, 'medium': 2, 'high': 3}
X['salary'] = X['salary'].map(salary_ordinals)
# X = X.copy()
# ьаштабируем данные
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X = pd.DataFrame(data=scaler.fit_transform(X),
columns=X.columns)
Оценка Модели¶
Как будем оценивать качество модели?
В дальнейшем будем оценивать качество модели
- на кросс-валидации (cross validation)
cross_val_score
- на пяти фолдах при помощи f1 score (
f1 меры
).
from sklearn.model_selection import cross_val_score
def estimate_accuracy(clf, X, y, cv=5,metric='f1'):
cv_mean = cross_val_score(clf, X, y, cv=5, scoring=metric).mean()
return round(cv_mean,3)
Бэггинг (Bagging)¶
- Bootstrap aggregating
- Mетод построения
композиции алгоритмов
, в котором каждый алгоритм строится независимо от другихна подвыборках
обучающей выборки Итоговый алгоритм
принимает решения посредствомголосования
среди всех алгоритмов (возвращается самый частый ответ)- Обертование исходного класса (
BaggingClassifier(clf)
)
- Посмотрим на точность одного дерева решении с максимальной глубиной 30 (
max_depth=30
) - Проведём
бэггинг
: для этого достаточно обернуть исходный классификатор вBaggingClassifier
# Посмотрим на точность одного дерева.
tree = DecisionTreeClassifier(max_depth=30)
print("Decision tree:", estimate_accuracy(tree, X, y))
# Проведём бэггинг: для этого достаточно обернуть исходный классификатор в BaggingClassifier.
bagging_trees = BaggingClassifier(tree)
print("Decision tree bagging:", estimate_accuracy(bagging_trees, X, y))
# Это явно улучшает результат не только беггинга но модель одного дерева
Decision tree: 0.945 Decision tree bagging: 0.975
Композиция
отдельных деревьев показывает себя лучше, чем одно дерево
Приемущество Бэггинга¶
Структура дерева серьёзно зависит от обучающей выборки
- Это значит, что если немного изменить обучающую выборку, то дерево сильно изменится
- Kомпозиция алгоритмов при помощи голосования работает наилучшим образом, когда модели различны
- Увеличить различность построенных деревьев можно, указав параметры
max_features
иmax_depth
mfeats = int(np.sqrt(len(features)))
print(f'Number of features: {mfeats}')
random_tree = DecisionTreeClassifier(max_features=mfeats,
max_depth=30)
print("Random tree:", estimate_accuracy(random_tree, X, y))
bagging_random_trees = BaggingClassifier(random_tree)
print("Random tree bagging:", estimate_accuracy(bagging_random_trees, X, y))
Number of features: 3 Random tree: 0.954 Random tree bagging: 0.979
Случайный Лес¶
Именно так внутри и работает так называемый случайный лес
Oн обучает набор деревьев n_esimators
, каждое из которых:
- обучается на подмножестве признаков (
метод случайных подпространств
) - и на подмножестве объектов (
бэггинг
) случайный лес
получается случайным по двум этим параметрамответы аггрегируются
при помощиголосования
# Стандартная эвристика:
# - в задаче классификации брать **квадратный корень числа признаков**, задаче регрессии — **треть числа признаков**
mfeats = int(np.sqrt(len(features)))
random_forest = RandomForestClassifier(n_estimators=100,
n_jobs=-1,
max_features=mfeats,
max_depth=30)
print("Random Forest:", estimate_accuracy(random_forest, X, y))
Random Forest: 0.983
Метрика OOB¶
Ещё одно преимущество использования беггинга для аггрегации моделей
Получение оценки работы классификатора без дополнительного проведения кросс-валидации
при помощи out-of-bag
метрики
Это метод вычисления произвольной оценки качества во время обучения беггинга
Для подсчёта требуется указать параметр oob_score = True
mfeats = int(np.sqrt(len(features)))
random_forest = RandomForestClassifier(n_estimators=100,
max_features=mfeats,
max_depth=30,
oob_score=True,
n_jobs=-1)
random_forest.fit(X, y)
# тектируем модель на данных который алгоритм не использовал
round(random_forest.oob_score_,3)
0.993
Бэггинг Логистической Регресии¶
Метод бэггинга
можно применять к произвольным алгоритмам, например, к логистической регрессии
from sklearn.linear_model import LogisticRegression
import warnings; warnings.filterwarnings('ignore')
# базовый алгоритм
lr = LogisticRegression(solver='saga',
max_iter=200)
lr.fit(X, y)
print("LR:", estimate_accuracy(lr, X, y))
from sklearn.ensemble import BaggingClassifier
# бэггинг классификатор
random_logreg = BaggingClassifier(lr,
n_estimators=10,
n_jobs=-1,
random_state=42)
print("Bagging for LR:", estimate_accuracy(random_logreg, X, y))
LR: 0.442 Bagging for LR: 0.437
Почему так?
- В её случае он не так сильно повышает качество, поскольку
линейные
модели не так сильно зависят от состава обучающей выборки - Попробуем убрать часть признаков с помощью
max_features
random_logreg = BaggingClassifier(
lr,
n_estimators=10,
n_jobs=-1,
max_features=0.5, # выбираем только часть фич
random_state=42
)
print("Bagging for LR:", estimate_accuracy(random_logreg, X, y))
Bagging for LR: 0.22
В случае линейной регрессии:
- Повышение
разнообразности
моделей не дает такого прироста, как с деревьями, поскольку модели сильно теряют в качестве. - Случайный лес на примере нашей задачи справляется лучше.
Задача Классификации изображений¶
датасет¶
- Загрузите датасет digits с помощью функции
load_digits
из sklearn.datasets - Нам предстоит решать задачу
классификации изображений
с цифрами по численным признакам
оценка качества¶
- Для оценки качества мы будем использовать
cross_val_score
из sklearn.model_selection с параметром . Эта функция реализует k-fold cross validation c равным значению параметра . - Предлагается использовать k=10, чтобы полученные оценки качества имели небольшой разброс, и было проще проверить полученные ответы. На практике же часто хватает и k=5. Функция cross_val_score будет возвращать numpy.ndarray, в котором будет чисел — качество в каждом из экспериментов k-fold cross validation.
- Для получения среднего значения (которое и будет оценкой качества работы) вызовите метод .mean() у массива, который возвращает
cross_val_score
Задание 5.7.1¶
Создайте DecisionTreeClassifier
с настройками по умолчанию и измерьте качество его работы с помощью cross_val_score
import pandas as pd
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
digits = load_digits()
X = digits.data
y = digits.target
scaler = StandardScaler()
X_view = pd.DataFrame(data=scaler.fit_transform(X),
columns=digits.feature_names)
features = digits.feature_names
X_view.head()
pixel_0_0 | pixel_0_1 | pixel_0_2 | pixel_0_3 | pixel_0_4 | pixel_0_5 | pixel_0_6 | pixel_0_7 | pixel_1_0 | pixel_1_1 | ... | pixel_6_6 | pixel_6_7 | pixel_7_0 | pixel_7_1 | pixel_7_2 | pixel_7_3 | pixel_7_4 | pixel_7_5 | pixel_7_6 | pixel_7_7 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0 | -0.335016 | -0.043081 | 0.274072 | -0.664478 | -0.844129 | -0.409724 | -0.125023 | -0.059078 | -0.624009 | ... | -0.757436 | -0.209785 | -0.023596 | -0.299081 | 0.086719 | 0.208293 | -0.366771 | -1.146647 | -0.505670 | -0.196008 |
1 | 0.0 | -0.335016 | -1.094937 | 0.038648 | 0.268751 | -0.138020 | -0.409724 | -0.125023 | -0.059078 | -0.624009 | ... | -0.757436 | -0.209785 | -0.023596 | -0.299081 | -1.089383 | -0.249010 | 0.849632 | 0.548561 | -0.505670 | -0.196008 |
2 | 0.0 | -0.335016 | -1.094937 | -1.844742 | 0.735366 | 1.097673 | -0.409724 | -0.125023 | -0.059078 | -0.624009 | ... | 0.259230 | -0.209785 | -0.023596 | -0.299081 | -1.089383 | -2.078218 | -0.164037 | 1.565686 | 1.695137 | -0.196008 |
3 | 0.0 | -0.335016 | 0.377661 | 0.744919 | 0.268751 | -0.844129 | -0.409724 | -0.125023 | -0.059078 | 1.879691 | ... | 1.072563 | -0.209785 | -0.023596 | -0.299081 | 0.282736 | 0.208293 | 0.241430 | 0.379040 | -0.505670 | -0.196008 |
4 | 0.0 | -0.335016 | -1.094937 | -2.551014 | -0.197863 | -1.020657 | -0.409724 | -0.125023 | -0.059078 | -0.624009 | ... | -0.757436 | -0.209785 | -0.023596 | -0.299081 | -1.089383 | -2.306869 | 0.849632 | -0.468564 | -0.505670 | -0.196008 |
5 rows × 64 columns
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
random_tree = DecisionTreeClassifier()
print("Random tree:", estimate_accuracy(random_tree, X, y,metric='accuracy'))
Random tree: 0.782
Задание 5.7.2¶
- Теперь давайте обучим
BaggingClassifier
на основеDecisionTreeClassifier
- Из sklearn.ensemble импортируйте
BaggingClassifier
, все параметры задайте по умолчанию - Нужно изменить только количество базовых моделей, задав его равным 100
- Подумайте, какие выводы можно сделать из соотношения качества одиночного дерева и беггинга деревьев?
# Количество базовых моделей 100
bagging_random_trees = BaggingClassifier(random_tree,
n_estimators=100)
print("Random Tree Bag:", estimate_accuracy(bagging_random_trees,
X, y,metric='accuracy'))
Random Tree Bag: 0.91
Задание 5.7.3¶
Теперь изучите параметры BaggingClassifier
и выберите их такими
- Чтобы каждый базовый алгоритм обучался не на всех d признаках, а на sqrt(d) случайных признаках
Корень из числа признаков - часто используемая эвристика в задачах классификации
- В задачах
регрессии
же часто берут число признаков, деленное на три, log(d) тоже имеет место быть
Но в общем случае ничто не мешает вам выбирать любое другое число случайных признаков, добиваясь лучшего качества на кросс-валидации
import warnings; warnings.filterwarnings('ignore')
import numpy as np
# базовый коассификатор
decision_tree = DecisionTreeClassifier()
print("Decision Tree:", estimate_accuracy(decision_tree,X,y,metric='accuracy'))
# Используем только часть признаков
mfeats = int(np.sqrt(len(features)))
print('bagging',mfeats,'features')
# Бэггинг классификатор
bagging_random_trees = BaggingClassifier(decision_tree,
n_estimators=100,
max_features=mfeats,
random_state=42)
print("Random Tree Bag:", estimate_accuracy(bagging_random_trees,X,y,metric='accuracy'))
print('')
Decision Tree: 0.786 bagging 8 features Random Tree Bag: 0.919
Задание 5.7.4¶
В предыдущем пункте мы выбирали подмножество один раз для каждого очередного дерева
Следующим нашим шагом будет построение беггинга на основе деревьев, которые выбирают случайное подмножество признаков для каждой вершины дерева
Для этого нам потребуется перенести, отвечающий за это параметр из
BaggingClassifier
вDecisionTreeClassifier
Для этого вам из документации нужно выяснить, какой параметр
DecisionTreeClassifier
за это отвечаетПо-прежнему сэмплируем
sqrt(d)
признаков
'''
Decision Tree Arguments
criterion='gini', splitter='best', max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, class_weight=None, ccp_alpha=0.0
'''
'''
BaggingClassifier Arguments
base_estimator=None, n_estimators=10, *, max_samples=1.0, max_features=1.0, bootstrap=True, bootstrap_features=False, oob_score=False, warm_start=False, n_jobs=None, random_state=None, verbose=0
'''
'\nBaggingClassifier Arguments\nbase_estimator=None, n_estimators=10, *, max_samples=1.0, max_features=1.0, bootstrap=True, bootstrap_features=False, oob_score=False, warm_start=False, n_jobs=None, random_state=None, verbose=0\n'
# mfeats = int(np.log(len(features)))
mfeats = int(np.sqrt(len(features)))
print('bagging',mfeats,'features')
decision_tree = DecisionTreeClassifier()
# decision_tree = DecisionTreeClassifier(max_features=mfeats)
print("Decision Tree:", estimate_accuracy(decision_tree,X,y,metric='accuracy'))
bagging_random_trees = BaggingClassifier(base_estimator=decision_tree,
n_estimators=100,
n_jobs=-1,
random_state=42)
print("Random Tree Bag:", estimate_accuracy(bagging_random_trees,X,y,metric='accuracy'))
bagging 8 features Decision Tree: 0.782 Random Tree Bag: 0.914
Задание 5.7.5¶
- Полученный в задании 4 классификатор - бэггинг на рандомизированных деревьях (в которых при построении каждой вершины выбирается случайное подмножество признаков и разбиение ищется только по ним).
- Это в точности соответствует алгоритму Random Forest, поэтому почему бы не сравнить качество работы классификатора с RandomForestClassifier из sklearn.ensemble. -Сделайте это, а затем изучите, как качество классификации на данном датасете зависит от количества деревьев, количества признаков, выбираемых при построении каждой вершины дерева, а также ограничений на глубину дерева.
- Для наглядности лучше построить графики зависимости качества от значений параметров, но для сдачи задания это делать не обязательно.
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
model = RandomForestClassifier()
gs = GridSearchCV(model,{'n_estimators':[5,10,15,20,25,30,35,40,45,50]})
gs.fit(X,y)
results = gs.cv_results_
results.keys()
dict_keys(['mean_fit_time', 'std_fit_time', 'mean_score_time', 'std_score_time', 'param_n_estimators', 'params', 'split0_test_score', 'split1_test_score', 'split2_test_score', 'split3_test_score', 'split4_test_score', 'mean_test_score', 'std_test_score', 'rank_test_score'])
print('валидационная ')
dict(zip([i for i in range(5,55,5)],results['mean_test_score']))
валидационная
{5: 0.8698050139275765, 10: 0.9048808418446301, 15: 0.9237790157845869, 20: 0.9243376663571649, 25: 0.9304642525533892, 30: 0.9332389353141443, 35: 0.9326864747756114, 40: 0.9310182606004334, 45: 0.9338037759207676, 50: 0.9349117920148562}
- При очень маленьком числе деревьев (5, 10, 15) случайный лес работает хуже, чем при большем числе деревьев
- С ростом количества деревьев в случайном лесе, в какой-то момент деревьев становится достаточно для высокого качества классификации, а затем качество существенно не меняется.
- При большом количестве признаков (для данного датасета - 40-50) качество классификации становится хуже, чем при малом количестве признаков (10-15). Это связано с тем, что чем меньше признаков выбирается в каждом узле, тем более различными получаются деревья (ведь деревья сильно неустойчивы к изменениям в обучающей выборке), и тем лучше работает их композиция.
model = RandomForestClassifier()
gs = GridSearchCV(model,{'max_depth':[5,10,15,20,25,30,35,40,None]})
gs.fit(X,y)
results = gs.cv_results_
print('валидационная ')
param_vals = [i for i in range(5,45,5)]
param_vals.append(None)
dict(zip(param_vals,results['mean_test_score']))
валидационная
{5: 0.9009594552770039, 10: 0.9321324667285671, 15: 0.9343562364593005, 20: 0.937137109254101, 25: 0.9421417517796348, 30: 0.937137109254101, 35: 0.9382497678737233, 40: 0.9421417517796348, None: 0.9382513153822346}
- При небольшой максимальной глубине деревьев (5-6) качество работы случайного леса заметно хуже, чем без ограничений, т.к. деревья получаются недообученными.
- С ростом глубины качество сначала улучшается, а затем не меняется существенно, т.к. из-за усреднения прогнозов и различий деревьев их переобученность в бэггинге не сказывается на итоговом качестве (все деревья преобучены по-разному, и при усреднении они компенсируют переобученность друг друга).