import scipy.stats as sps
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Ridge
from sklearn.metrics import accuracy_score
from sklearn import datasets
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import warnings
sns.set('notebook', style='whitegrid', font_scale=1.3)
Поиск гиперпараметров¶
В машинном обучении существуют различные семейства моделей, выбор которых зависит от задачи, которая перед вам стоит.
Предположим, вы выбрали семейство моделей, например, линейную регрессию. Теперь вам нужно настроить гиперпараметры данной модели. Для начала нужно определить, что в машинном обучении называют гиперпараметрами.
Гиперпараметры — это параметры модели, которые задаются ДО ее обучения, т.е. они не являются обучаемымии параметрами и не изменяются в ходе обучения модели.
Пример: выбор способа регуляризации в модели линейной регрессии. Строго говоря, выбор семейства моделей также является гиперпараметром.
В данном ноутбуке мы изучим различные методы подбора гиперапараметров, затем применим их к ридж-регрессии.
В контексте линейной регрессии мы будем работать с таким гиперпараметром, как коэффициент регуляризации. Этот гиперпараметр относится к группе, которую мы в дальнейшем для удобства будем называть красными гиперпараметрами:
- красные гиперпараметры — гиперпараметры, ограничивающие модель. Если увеличить значение гиперпараметра такого типа, то можно снизить переобучение модели. И, наоборот, уменьшение значений даст модели возможность лучше улавливать зависимости в данных.
Пример: коэффициент регуляризации в Ridge или Lasso регрессии является красным гиперпараметром. Чем больше данный коэффициент, тем меньше зависимостей улавливает модель, именно поэтому при больших значениях параметра регуляризации коэффициенты признаков зануляются.
Поиск гиперпараметров для Ridge-регрессии¶
Будем предсказывать цены квартир в Бостоне при помощи линейной регрессии с $l_2$-регуляризацией. Вспомним, что в Ridge-регрессии мы оптимизируем
$$ \lVert Y - X \theta\rVert^2_2 + \alpha \cdot \lVert\theta\rVert^2_2, $$
где $Y$ — истинные значения целевой переменной, $X$ — матрица "объект-признак", $\theta$ — параметр модели, $\alpha$ — параметр регуляризации.
boston = datasets.load_boston() # данные о ценах квартир в Бостоне
X = pd.DataFrame(data=boston['data'], columns=boston['feature_names'])
y = boston['target']
Описание датасета:
print(boston['DESCR'])
.. _boston_dataset: Boston house prices dataset --------------------------- **Data Set Characteristics:** :Number of Instances: 506 :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target. :Attribute Information (in order): - CRIM per capita crime rate by town - ZN proportion of residential land zoned for lots over 25,000 sq.ft. - INDUS proportion of non-retail business acres per town - CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise) - NOX nitric oxides concentration (parts per 10 million) - RM average number of rooms per dwelling - AGE proportion of owner-occupied units built prior to 1940 - DIS weighted distances to five Boston employment centres - RAD index of accessibility to radial highways - TAX full-value property-tax rate per $10,000 - PTRATIO pupil-teacher ratio by town - B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town - LSTAT % lower status of the population - MEDV Median value of owner-occupied homes in $1000's :Missing Attribute Values: None :Creator: Harrison, D. and Rubinfeld, D.L. This is a copy of UCI ML housing dataset. https://archive.ics.uci.edu/ml/machine-learning-databases/housing/ This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University. The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic prices and the demand for clean air', J. Environ. Economics & Management, vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, 'Regression diagnostics ...', Wiley, 1980. N.B. Various transformations are used in the table on pages 244-261 of the latter. The Boston house-price data has been used in many machine learning papers that address regression problems. .. topic:: References - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261. - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.
X.head()
CRIM | ZN | INDUS | CHAS | NOX | RM | AGE | DIS | RAD | TAX | PTRATIO | B | LSTAT | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.00632 | 18.0 | 2.31 | 0.0 | 0.538 | 6.575 | 65.2 | 4.0900 | 1.0 | 296.0 | 15.3 | 396.90 | 4.98 |
1 | 0.02731 | 0.0 | 7.07 | 0.0 | 0.469 | 6.421 | 78.9 | 4.9671 | 2.0 | 242.0 | 17.8 | 396.90 | 9.14 |
2 | 0.02729 | 0.0 | 7.07 | 0.0 | 0.469 | 7.185 | 61.1 | 4.9671 | 2.0 | 242.0 | 17.8 | 392.83 | 4.03 |
3 | 0.03237 | 0.0 | 2.18 | 0.0 | 0.458 | 6.998 | 45.8 | 6.0622 | 3.0 | 222.0 | 18.7 | 394.63 | 2.94 |
4 | 0.06905 | 0.0 | 2.18 | 0.0 | 0.458 | 7.147 | 54.2 | 6.0622 | 3.0 | 222.0 | 18.7 | 396.90 | 5.33 |
Разберемся, что означают эти признаки:
CRIM
— уровень преступности на душу населенияZN
— доля жилой земли на каждые 25 000 км$^2$INDUS
— доля неторговых площадей в районеCHAS
— индиактор того, что неподалеку река ЧарльзNOX
— концентрация оксида азотаRM
— среднее количество комнат в домеAGE
— доля зданий, построенных до 1940 годаDIS
— взвешенные расстояния до пяти бостонских центров занятостиRAD
— индекс доступности радиальных магистралейTAX
— ставка налога на полную стоимость имущества за 10 000 долларовPTRATIO
— количество учеников на учителяB
— доля темнокожихLSTAT
— доля населения с низким статусом
Получить гиперпараметры модели можно при помощи метода get_params()
.
model = Ridge()
model.get_params()
{'alpha': 1.0, 'copy_X': True, 'fit_intercept': True, 'max_iter': None, 'normalize': False, 'random_state': None, 'solver': 'auto', 'tol': 0.001}
Самым важным гиперпараметром тут является alpha
— коэффициент регуляризации. Именно его мы и будем искать при помощи техники полного перебора по сетке — GridSearch
.
Суть перебора по сетке заключается в следующем: вы задаете область значений гиперпараметров, которые хотите оптимизировать, обучаете все модели, которые возможно получить в рамках заданного пространства поиска гиперпараметров. Затем выбираете из данных моделей ту, которая дала наилучшее качество на кросс-валидации. Этой модели соответствует конкретный набор значений гиперпараметров — его мы и ищем.
В sklearn
вам нужно просто указать одномерную сетку отдельно для каждого из гиперпараметров, построение многомерного пространства поиска оптимальных гиперпараметров спрятано в реализации метода. Применим GridSearch
на примере:
# задаем пространство поиска
parameters_grid = {
'alpha' : np.linspace(0.00001, 2, num=1000)
}
# задаем стратегию кросс-валидации
ss = ShuffleSplit(n_splits=5, test_size=0.25, random_state=0)
# задаем модель
model = Ridge()
# определяем поиск по сетке
gs = GridSearchCV(
# модель для обучения, в нашем случае Ridge
estimator=model,
# сетка значений гиперпараметров
param_grid=parameters_grid,
# метрика качества, берем MSE
scoring='neg_mean_squared_error',
# GridSearch отлично параллелится, указываем количество параллельных джоб
# -1 означает использование всех ядер
n_jobs=-1,
# стратегия кросс-валидации
cv=ss,
# сообщения с логами обучения: больше значение - больше сообщений
verbose=10,
# значение, присваиваемое scorer в случае ошибки при обучении
error_score='raise'
)
Стоит отметить, что в качестве scoring мы используем neg_mean_squared_error
. Префикс neg показывает, что мы оптимизируем $(-1) \cdot \mathrm{MSE}$. Дело в том, что оптимизации в sklearn подразумевают максимизацию метрики качества.
%%time
# выполняем поиск по сетке
gs.fit(X, y)
Fitting 5 folds for each of 1000 candidates, totalling 5000 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 4 concurrent workers. [Parallel(n_jobs=-1)]: Done 5 tasks | elapsed: 6.0s [Parallel(n_jobs=-1)]: Done 10 tasks | elapsed: 6.2s [Parallel(n_jobs=-1)]: Done 17 tasks | elapsed: 6.5s [Parallel(n_jobs=-1)]: Done 24 tasks | elapsed: 6.7s [Parallel(n_jobs=-1)]: Done 33 tasks | elapsed: 7.0s [Parallel(n_jobs=-1)]: Batch computation too fast (0.1960s.) Setting batch_size=2. [Parallel(n_jobs=-1)]: Done 42 tasks | elapsed: 7.3s [Parallel(n_jobs=-1)]: Done 58 tasks | elapsed: 7.6s [Parallel(n_jobs=-1)]: Done 80 tasks | elapsed: 7.9s [Parallel(n_jobs=-1)]: Batch computation too fast (0.1835s.) Setting batch_size=4. [Parallel(n_jobs=-1)]: Done 108 tasks | elapsed: 8.2s [Parallel(n_jobs=-1)]: Done 160 tasks | elapsed: 8.8s [Parallel(n_jobs=-1)]: Done 220 tasks | elapsed: 9.4s [Parallel(n_jobs=-1)]: Done 280 tasks | elapsed: 10.0s [Parallel(n_jobs=-1)]: Done 348 tasks | elapsed: 10.8s [Parallel(n_jobs=-1)]: Done 416 tasks | elapsed: 11.6s [Parallel(n_jobs=-1)]: Done 492 tasks | elapsed: 12.3s [Parallel(n_jobs=-1)]: Done 568 tasks | elapsed: 12.9s [Parallel(n_jobs=-1)]: Done 652 tasks | elapsed: 13.8s [Parallel(n_jobs=-1)]: Done 736 tasks | elapsed: 14.8s [Parallel(n_jobs=-1)]: Done 828 tasks | elapsed: 15.5s [Parallel(n_jobs=-1)]: Batch computation too fast (0.1968s.) Setting batch_size=8. [Parallel(n_jobs=-1)]: Batch computation too fast (0.1903s.) Setting batch_size=16. [Parallel(n_jobs=-1)]: Done 952 tasks | elapsed: 16.3s [Parallel(n_jobs=-1)]: Done 1352 tasks | elapsed: 18.6s [Parallel(n_jobs=-1)]: Done 1752 tasks | elapsed: 20.9s [Parallel(n_jobs=-1)]: Done 2184 tasks | elapsed: 23.7s [Parallel(n_jobs=-1)]: Done 2616 tasks | elapsed: 26.4s [Parallel(n_jobs=-1)]: Done 3080 tasks | elapsed: 29.3s [Parallel(n_jobs=-1)]: Done 3544 tasks | elapsed: 31.6s [Parallel(n_jobs=-1)]: Done 4040 tasks | elapsed: 34.0s [Parallel(n_jobs=-1)]: Done 4536 tasks | elapsed: 36.9s [Parallel(n_jobs=-1)]: Done 4974 tasks | elapsed: 39.6s [Parallel(n_jobs=-1)]: Done 5000 out of 5000 | elapsed: 39.7s finished
CPU times: user 5.07 s, sys: 194 ms, total: 5.27 s Wall time: 39.8 s
GridSearchCV(cv=ShuffleSplit(n_splits=5, random_state=0, test_size=0.25, train_size=None), error_score='raise', estimator=Ridge(alpha=1.0, copy_X=True, fit_intercept=True, max_iter=None, normalize=False, random_state=None, solver='auto', tol=0.001), iid='deprecated', n_jobs=-1, param_grid={'alpha': array([1.00000000e-05, 2.01199199e-03, 4.01398398e-03, 6.01597598e-03... 1.96196215e+00, 1.96396414e+00, 1.96596614e+00, 1.96796813e+00, 1.96997012e+00, 1.97197211e+00, 1.97397410e+00, 1.97597610e+00, 1.97797809e+00, 1.97998008e+00, 1.98198207e+00, 1.98398406e+00, 1.98598606e+00, 1.98798805e+00, 1.98999004e+00, 1.99199203e+00, 1.99399402e+00, 1.99599602e+00, 1.99799801e+00, 2.00000000e+00])}, pre_dispatch='2*n_jobs', refit=True, return_train_score=False, scoring='neg_mean_squared_error', verbose=10)
Итак, мы выполнили полный перебор по сетке. Давайте посмотрим, какие атрибуты есть у GridSearch
и чему они равны.
Атрибут cv_results_
возвращает информацию о времени выполнения обучения и подробности о значениях метрик. Значения данного атрибута удобнее отображать через pd.DataFrame
.
results = gs.cv_results_
pd.DataFrame(results)
mean_fit_time | std_fit_time | mean_score_time | std_score_time | param_alpha | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.019730 | 0.006207 | 0.011997 | 0.005321 | 1e-05 | {'alpha': 1e-05} | -29.782255 | -32.267382 | -28.280225 | -22.238042 | -22.424877 | -26.998556 | 4.018289 | 1 |
1 | 0.014094 | 0.008158 | 0.013653 | 0.007455 | 0.00201199 | {'alpha': 0.002011991991991992} | -29.784217 | -32.269228 | -28.280004 | -22.237358 | -22.424236 | -26.999009 | 4.019338 | 2 |
2 | 0.014363 | 0.005050 | 0.007562 | 0.005435 | 0.00401398 | {'alpha': 0.004013983983983983} | -29.786178 | -32.271071 | -28.279797 | -22.236683 | -22.423603 | -26.999466 | 4.020384 | 3 |
3 | 0.018396 | 0.005929 | 0.005049 | 0.004292 | 0.00601598 | {'alpha': 0.006015975975975975} | -29.788137 | -32.272911 | -28.279602 | -22.236017 | -22.422978 | -26.999929 | 4.021426 | 4 |
4 | 0.015930 | 0.012884 | 0.007728 | 0.003205 | 0.00801797 | {'alpha': 0.008017967967967967} | -29.790096 | -32.274748 | -28.279421 | -22.235360 | -22.422361 | -27.000397 | 4.022464 | 5 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
995 | 0.013368 | 0.010216 | 0.007332 | 0.007039 | 1.99199 | {'alpha': 1.991992032032032} | -30.862732 | -33.139910 | -28.908917 | -22.498731 | -22.507605 | -27.583579 | 4.358976 | 996 |
996 | 0.010284 | 0.008301 | 0.002275 | 0.000125 | 1.99399 | {'alpha': 1.993994024024024} | -30.863263 | -33.140273 | -28.909397 | -22.499066 | -22.507703 | -27.583940 | 4.359077 | 997 |
997 | 0.007465 | 0.002904 | 0.008263 | 0.004809 | 1.996 | {'alpha': 1.995996016016016} | -30.863794 | -33.140635 | -28.909876 | -22.499400 | -22.507801 | -27.584301 | 4.359177 | 998 |
998 | 0.024899 | 0.013004 | 0.005273 | 0.005824 | 1.998 | {'alpha': 1.997998008008008} | -30.864324 | -33.140997 | -28.910355 | -22.499735 | -22.507899 | -27.584662 | 4.359278 | 999 |
999 | 0.007314 | 0.003550 | 0.005138 | 0.005197 | 2 | {'alpha': 2.0} | -30.864853 | -33.141358 | -28.910834 | -22.500069 | -22.507997 | -27.585022 | 4.359378 | 1000 |
1000 rows × 14 columns
Каждой строке в этой таблице соответствуют результаты эксперимента для конкретного значения гиперпараметра. Для поиска оптимального параметра регуляризации мы выбрали сетку из 1000 элементов, соответственно, в данной таблице 1000 строк. Также напомним, что в каждом эксперименте проводится процедура кросс-валидации, то есть модель обучается несколько раз на разных батчах. Разберемся, что в каждом столбце таблицы выше:
mean_fit_time
— среднее время обучения модели.std_fit_time
— стандартное отклонение времени обучения модели.mean_score_time
— среднее время предсказания модели.std_score_time
— стандартное отклонение времение предсказания модели.param_alpha
— значение гиперпараметра.params
— значения всех гиперпараметров в виде словаря.split0_test_score
и тд — значения метрики на каждом из этапов кросс-валидации.mean_test_score
— усредненное значение метрики на тестовых батчах.std_test_score
— стандартное отклонение значений метрики на тестовых батчах.rank_test_score
— ранг эксперимента. Все эксприменты отсортированы поmean_test_score
, ранг — это место эксперимента в отсортированном списке экспериментов.
Лучшая модель:
gs.best_estimator_
Ridge(alpha=1e-05, copy_X=True, fit_intercept=True, max_iter=None, normalize=False, random_state=None, solver='auto', tol=0.001)
Значение метрики лучшей модели:
gs.best_score_
-26.998556105304555
Значения гиперпараметров лучшей модели:
gs.best_params_
{'alpha': 1e-05}
Время обучения лучшей модели в секундах:
gs.refit_time_
0.019248247146606445