Все модели машинного обучения имеют набор гиперпараметров и при работе с выбранной моделью задача их оптимального выбора становится основной. В этой статье мы рассмотрим тему выбора модели, сначала вручную оценив один гиперпараметр для одного алгоритма машинного обучения в конкретном наборе данных. После этого мы рассмотрим поиск по сетке, чтобы найти наилучшие комбинации нескольких гиперпараметров. Наконец, мы рассмотрим дополнительные методы выбора модели, такие как случайный поиск гиперпараметров.
Формально выбор модели — это задача выбора наилучшей модели машинного обучения для заданного набора данных. Таким образом, для начала нам сначала нужен набор данных, к которому мы можем применить алгоритмы машинного обучения. Для этой цели мы будем использовать набор данных о работающих и уволившихся сотрудниках.
Для простоты мы будем использовать только один алгоритм машинного обучения и сосредоточимся на поиске лучших гиперпараметров для этого алгоритма на этих данных. Алгоритм машинного обучения, который мы будем использовать, представляет собой классификацию k ближайших соседей.
В этом простом примере мы будем оценивать только один потенциальный гиперпараметр, n_neighbors для этого одного алгоритма.
sex | experience | wd | salary | quit | |
---|---|---|---|---|---|
0 | female | 184.5 | 39.0 | 3738 | 0 |
1 | female | 34.5 | 47.0 | 3079 | 0 |
2 | female | 79.4 | 42.5 | 2887 | 0 |
3 | male | 4.7 | 47.5 | 2742 | 0 |
4 | female | 56.3 | 64.0 | 3483 | 0 |
116 | male | 28.1 | 26.0 | 2651 | 0 |
117 | female | 19.0 | 63.0 | 3177 | 0 |
118 | female | 18.9 | 49.0 | 2239 | 0 |
119 | male | 46.6 | 4.0 | 4731 | 0 |
120 | female | 21.5 | 17.5 | 2163 | 0 |
- sex - пол
- experience - стаж работы в месяцах
- wd - количество отработанных смен
- salary - среднесменная заработная плата
- quit - метка 0-работает, 1 - уволился
sex | experience | wd | salary | quit | |
---|---|---|---|---|---|
98 | 1 | 93.8 | 40.5 | 6092 | 0 |
38 | 0 | 0.9 | 19.0 | 3734 | 0 |
34 | 0 | 20.6 | 23.0 | 3526 | 0 |
104 | 0 | 13.2 | 44.0 | 3687 | 0 |
115 | 0 | 18.5 | 33.5 | 3494 | 0 |
0 103 1 18 Name: quit, dtype: int64 0.15
Как видим, уволившихся 18 из 123 сотрудников, то есть всего 15%.
Попробуем визуально оценить, сможет ли метод KNN решить эту задачу
классификации, для этого выведем график в двух наиболее важных измерениях
(это было установлено ранее).
fig, ax = plt.subplots(figsize=(7, 5))
ax = sns.scatterplot(x="experience", y="wd", palette="Set2", hue="quit", data=df, ax=ax, s=100)
ax.set_title("Retired employees", fontsize=15)
ax.set_xlabel('experience', fontsize=15)
ax.set_ylabel('wd', fontsize=15)
Похоже, что сможет. Видно, что уволившиеся организуют некоторую область,
правда есть два выброса. Область уволившихся можно определить, как область риска.
Работаем дальше.
#Выделяем признаки и метки кластеров
label_col = "quit"
data = df.drop(label_col, axis=1)
label = df[label_col]
#Выделяем название признаков
features_names=data.columns
print(features_names)
Index(['sex', 'experience', 'wd', 'salary'], dtype='object')Разделяем данные на обучающую и тестовую части
d_train, d_test, l_train, l_test = train_test_split(data, label, test_size=0.4,
random_state=23, stratify=label)
Проверяем распределение меток
print('Labels counts in l_train:', np.bincount(l_train))
print('Labels counts in l_test:', np.bincount(l_test))
print('Labels % in label:', round(label.mean(),2))
print('Labels % in l_train:', round(l_train.mean(),2))
print('Labels % in l_test:', round(l_test.mean(),2))
Labels counts in label: [103 18] Labels counts in l_train: [61 11] Labels counts in l_test: [42 7] Labels % in label: 0.15 Labels % in l_train: 0.15 Labels % in l_test: 0.14
Как видим, метки распределились практически равномерно
Следующим шагом является создание итератора перекрестной проверки, который будет применяться к обучающим данным для оценки гиперпараметров модели. В этом случае мы используем метод стратифицированной k-кратной перекрестной проверки, чтобы еще раз сохранить баланс классов. Мы также указываем 5 кратностей (через параметр n_splits), что создаст 5 уникальных наборов данных для обучения и проверки для каждого гиперпараметра.
Мы создаем массив из девяти значений n_neighbors от 1 до 9 для процесса выбора модели.
Затем мы перебираем каждое значение в массиве значений гиперпараметров, вычисляем и отображаем оценки перекрестной проверки для каждого значения. Наконец, мы отображаем общее время, затраченное на эту конкретную перекрестную проверку.
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score
start = time()
neighbors = range(1,10)
for n in neighbors:
knc = KNeighborsClassifier(n_neighbors=n)
score = cross_val_score(knc, d_train, l_train, cv=skf)
print(f'neighbors={n}, score={np.mean(score)*100:4.1f}%')
print(f'Compute time = {time() - start:.2f} seconds.')
neighbors=1, score=76.3%
neighbors=2, score=83.3% neighbors=3, score=82.0% neighbors=4, score=84.8% neighbors=5, score=84.8% neighbors=6, score=84.8% neighbors=7, score=84.8% neighbors=8, score=84.8% neighbors=9, score=84.8% Compute time = 0.29 seconds.
только на показателе точности. Если перейти на другую функцию подсчета очков,
Поиск по сетке
Вместо многократного изменения значений параметров и вычисления результирующих
оценок модели лучше использовать метод поиска по сетке. При поиске по сетке
определяется сетка значений параметров, применяется модель ко всем возможным
комбинациям значений параметров в сетке и определяется набор параметров,
обеспечивающий наилучшую оценку производительности модели. Библиотека
scikit-learn предоставляет объект GridSearchCV, который выполняет поиск по сетке
с использованием перекрестной проверки, которая в конце дает оценку модели.
Используем GridSearchCV для вычисления наилучшего значения параметра
n_neighbors при запуске классификатора KNN для наших данных
skf = StratifiedKFold(n_splits=5)
knc = KNeighborsClassifier()
neighbors = range(1,10)
params = {'n_neighbors':neighbors, 'weights':weights}
gse = GridSearchCV(estimator=knc, param_grid=params, cv=skf)
# Обучаем на данных
gse.fit(d_train, l_train)
# Выводим время и лучший результат
print(f'Compute time = {time() - start:.2f} seconds.\n')
print(f'Best n_neighbors={gse.best_estimator_.get_params()["n_neighbors"]:.4f}')
print(f'Best CV Score = {gse.best_score_:.3f}')
Compute time = 0.51 seconds.
Best n_neighbors=4.0000 Best weights=uniform Best CV Score = 0.848
Таким образом подтверждаем, что оптимальное количество соседей равно 4
Рандомизированный поиск по сетке
При большом количестве гиперпараметров количество возможных комбинаций быстро становится избыточным. Поскольку мы должны оценивать модель для каждой возможной комбинации параметров, выбор модели со стандартной сеткой может стать трудным для вычислений.
Альтернативный подход заключается в случайном выборе возможных комбинаций гиперпараметров из предоставленной сетки значений для выявления хороших комбинаций параметров.
В следующем коде проведем случайный поиск по сетке с использованием оценщика RandomizedSearchCV. Сначала мы передаем наш классификатор KNN вместе с сеткой параметров и количеством значений параметров для выборки. По мере увеличения этого последнего значения производится выборка большего количества комбинаций значений параметров. Эти значения выбираются случайным образом из распределения каждого параметра. Если предоставляется список значений (как мы делали с предыдущим поиском по сетке), значения выбираются из списка случайным образом. В качестве альтернативы можно использовать случайное распределение. Требование к этим распределениям состоит в том, что они должны содержать метод rvs, который предоставляется случайными распределениями в модуле scipy.stats.
С RandomizedSearchCV мы можем установить n_iter равным 20, что означает, что мы вычисляем оценки только для двадцати различных комбинаций параметров. Двадцать комбинаций выбираются случайным образом из всех комбинаций.
Далее извлекаем наилучшую оценку и отображаем оптимальные параметры и соответствующую оценку. В результате получаем результат, аналогичный GridSearchCV. Таким образом, перекрестная проверка случайного поиска может быть полезна для получения оптимальных значений гиперпараметров и при большом их количестве может быть быстрее, чем с помощью традиционных методов.
start = time()
knc = KNeighborsClassifier()
skf = StratifiedKFold(n_splits=5)
neighbors = range(1, 10)
weights = ['uniform', 'distance']
params = {'n_neighbors':neighbors, 'weights':weights}
num_samples = 20
rscv = RandomizedSearchCV(knc, param_distributions=params, n_iter=num_samples, random_state=23)
rscv.fit(d_train, l_train)
print(f'Compute time = {time() - start:.2f} seconds', end='')
print(f' for {num_samples} parameter combinations')
Compute time = 0.53 seconds for 20 parameter combinations
be = rscv.best_estimator_ print(f'Best n_neighbors={be.get_params()["n_neighbors"]:.4f}')
print(f'Best weights={be.get_params()["weights"]}') print(f'Best CV Score = {rscv.best_score_:.3f}')
Best n_neighbors=4.0000 Best weights=uniform Best CV Score = 0.848
Комментариев нет:
Отправить комментарий