воскресенье, 27 марта 2022 г.

Машинное обучение с Python в розничной торговле : выбор гиперпараметров модели

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

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

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

В этом простом примере мы будем оценивать только один потенциальный гиперпараметр, n_neighbors для этого одного алгоритма. 


Загружаем наши данные и смотрим первые и последние пять строк :

df = pd.read_excel("data\shop_quit.xlsx")
pd.concat([df.head(), df.tail()])

sexexperiencewdsalaryquit
0female184.539.037380
1female34.547.030790
2female79.442.528870
3male4.747.527420
4female56.364.034830
116male28.126.026510
117female19.063.031770
118female18.949.022390
119male46.64.047310
120female21.517.521630

Расшифруем показатели :
  • sex - пол
  • experience - стаж работы в месяцах
  • wd - количество отработанных смен
  • salary - среднесменная заработная плата
  • quit - метка 0-работает, 1 - уволился
 Перекодируем пол

from sklearn.preprocessing import LabelEncoder
class_le = LabelEncoder ()

df.sex=class_le.fit_transform(df.sex.values)
df.sample(5)

sexexperiencewdsalaryquit
98193.840.560920
3800.919.037340
34020.623.035260
104013.244.036870
115018.533.534940

Оценим распределение меток :

print(df.quit.value_counts())
print(round(df.quit.mean(),2))

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 label:', np.bincount(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.neighbors import KNeighborsClassifier
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import cross_val_score

start = time()

skf = StratifiedKFold(n_splits=5)
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.

Из приведенного выше кода мы узнаем, что для всего числа проверенных соседей
4 дает наилучшую оценку точности и дальше она не улучшается. Таким образом,
 4 — лучшее значение для гиперпараметра KNN n_neighbors. 
Однако надо иметь в виду две вещи: во-первых, это только  лучшее значение среди
протестированных нами значений n_neighbors, а во-вторых, оно основано
только на показателе точности. Если  перейти на другую функцию подсчета очков, 
можно получить совершенно другие результаты.

Поиск по сетке 


Вместо многократного изменения значений параметров и вычисления результирующих

оценок  модели лучше использовать метод поиска по сетке. При поиске по сетке

определяется сетка значений параметров, применяется модель ко всем возможным

 комбинациям значений параметров  в сетке и определяется набор параметров,

 обеспечивающий наилучшую оценку производительности модели. Библиотека

 scikit-learn предоставляет объект GridSearchCV, который выполняет поиск по сетке

 с использованием перекрестной проверки, которая в конце дает оценку модели.

Используем GridSearchCV для вычисления наилучшего значения параметра
 n_neighbors при запуске  классификатора KNN для наших данных


start = time()
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 weights={be.get_params()["weights"]}')
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. Таким образом, перекрестная проверка случайного поиска может быть полезна для получения оптимальных значений гиперпараметров и при большом их количестве может быть быстрее, чем с помощью традиционных методов.

from sklearn.model_selection import RandomizedSearchCV

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


Комментариев нет:

Отправить комментарий