Как оптимизировать гиперпараметры модели машинного обучения и как ускорить процесс
Что такое настройка гиперпараметров?
Настройка гиперпараметров – это метод точной настройки модели машинного обучения. Гиперпараметры специально не изучаются в процессе обучения, но их можно настроить для оптимизации производительности модели. Вот несколько советов, о которых мне нравится думать, когда дело доходит до настройки гиперпараметров:
- Настройка гиперпараметров обычно является последним шагом, который необходимо выполнить при построении модели непосредственно перед окончательной оценкой.
- Вы не получите резко отличающихся результатов от настройки параметров. Наибольшее влияние на производительность модели оказывают выбор функций и выбор модели.
- Настройка гиперпараметров может помочь с обобщением модели, уменьшая переобучение.
При этом настройка вашей модели является важным шагом в рабочем процессе. Вот краткое изображение, которое может помочь вам понять процесс.

- Подготовка данных: процесс очистки ваших данных и подготовки их к машинному обучению.
- Исследовательский анализ данных. Этот шаг следует выполнять всегда. Процесс изучения нового набора данных и понимания распределения данных, корреляций и многого другого. Смотрите этот пост для пошагового руководства.
- Разработка и выбор функций: процесс создания новых функций (столбцов) из ваших данных и выбора лучших функций в зависимости от того, какой вклад они вносят. производительность модели.
- Выбор модели. Использование перекрестной проверки для выбора алгоритма с наибольшей эффективностью на основе показателя оценки.
- Настройка гиперпараметров: процесс, описанный в этом посте.
- Оценка модели: выбор правильного показателя производительности и оценка результатов.
Примеры гиперпараметров
Некоторые примеры гиперпараметров:
- Какой решатель следует использовать в модели логистической регрессии?
- Какое значение лучше для C или константы регуляризации?
- Какой регуляризирующий штраф следует использовать?
- Какой должна быть максимальная глубина для моего дерева решений?
- количество деревьев, которое я должен включить в свой случайный лес?
Многое из этого может быть очень сложно понять самостоятельно. Хорошая новость заключается в том, что для поиска оптимального набора параметров можно применять различные методики. Теперь, когда у вас есть общее представление о том, что они из себя представляют и как они вписываются в процесс, давайте посмотрим, как это работает.
Выбор модели
Для краткости мы пропустим первоначальную очистку и выбор функций. Этот код доступен в этом Блокноте на GitHub. Мы возьмем результаты нашего выбора функций и создадим переменные X и y.
X = df[['categories', 'postal_code', 'text_len', 'review_count', 'text_clean']] y = df['target']
Далее у нас есть функция, которая позволяет нам многократно создавать конвейер вместе с экземпляром классификатора.
def create_pipe(clf, ngrams=(1,1)):
column_trans = ColumnTransformer(
[('Text', TfidfVectorizer(stop_words='english', ngram_range=ngrams), 'text_clean'),
('Categories', TfidfVectorizer(), 'categories'),
('OHE', OneHotEncoder(dtype='int', handle_unknown='ignore'),['postal_code']),
('Numbers', MinMaxScaler(), ['review_count', 'text_len'])],
remainder='drop')
pipeline = Pipeline([('prep',column_trans),
('over', SMOTE(random_state=42)),
('under', RandomUnderSampler(random_state=42)),
('clf', clf)])
return pipeline
Конвейер содержит все необходимые этапы предварительной обработки. Затем мы можем выполнить классическую перекрестную проверку, чтобы найти лучшую модель.
models = {'RandForest' : RandomForestClassifier(random_state=42),
'LogReg' : LogisticRegression(random_state=42)
}
for name, model, in models.items():
clf = model
pipeline = create_pipe(clf)
scores = cross_val_score(pipeline,
X,
y,
scoring='f1_macro',
cv=3,
n_jobs=1,
error_score='raise')
print(name, ': Mean f1 Macro: %.3f and Standard Deviation: (%.3f)' % (np.mean(scores), np.std(scores)))
[OUT]
RandForest : Mean f1 Macro: 0.785 and Standard Deviation: (0.003)
LogReg : Mean f1 Macro: 0.854 and Standard Deviation: (0.001)
В целом, мы видим, что классификатор LogisticRegression работал лучше с этими данными, чем RandomForestClassifier. Как упоминалось выше, инженерия признаков, выбор признаков и выбор модели дадут вам наибольшую выгоду при обучении вашей модели, поэтому мы всегда начинаем здесь. .
Доступ к параметрам модели в конвейерах
Одна из первых вещей, на которые я хочу обратить внимание, это как получить доступ к параметрам модели в конвейере. Обычно, когда у вас есть экземпляр оценщика (модели), вы вызываете estimator.get_params() и видите их. Процесс такой же в трубопроводах; однако результирующий результат немного отличается.
При доступе к параметрам непосредственно из оценщика на выходе будет значение, например C. Напротив, в конвейере выходные данные сначала будут иметь имя, которое вы дали оценщику, вместе с двойным подчеркиванием, а затем, наконец, имя параметра, например clf__C; знать, как получить доступ к параметрам, важно, поскольку вам нужны эти имена для построения таблицы параметров для поиска.
Ниже приведены выходные данные моего конвейера, усеченные для краткости. Вы можете увидеть параметры классификатора в конце списка, все из которых в настоящее время являются значениями по умолчанию.
pipeline.get_params()
{'memory': None,
'steps': [('prep',
ColumnTransformer(transformers=[('Text', TfidfVectorizer(stop_words='english'),
'text_clean'),
('Categories', TfidfVectorizer(), 'categories'),
('OHE',
OneHotEncoder(dtype='int',
handle_unknown='ignore'),
['postal_code']),
('Numbers', MinMaxScaler(),
['review_count', 'text_len'])])),
... truncated for brevity ...
'clf__C': 1.0,
'clf__class_weight': None,
'clf__dual': False,
'clf__fit_intercept': True,
'clf__intercept_scaling': 1,
'clf__l1_ratio': None,
'clf__max_iter': 500,
'clf__multi_class': 'auto',
'clf__n_jobs': None,
'clf__penalty': 'l2',
'clf__random_state': 42,
'clf__solver': 'lbfgs',
'clf__tol': 0.0001,
'clf__verbose': 0,
'clf__warm_start': False}
Поиск по сетке
Первый метод, который мы рассмотрим, — это перекрестная проверка в поиске по сетке, в которой используется та же логика, которую мы использовали бы для обычной перекрестной проверки, используемой для выбора модели. Однако поиск по сетке перебирает каждую комбинацию параметров, выполняет перекрестную проверку и возвращает наилучшую модель. Первым шагом здесь является создание сетки параметров, и мы делаем это, создавая список словарей для GridSearch для итерации.
parameters = [{'clf__solver' : ['newton-cg', 'lbfgs', 'sag', 'liblinear'],'clf__C' : [.1, 1, 10, 100, 1000], 'prep__Text__ngram_range': [(1, 1), (2, 2), (1, 2)]}]
Кроме того, вы можете добавить в список несколько словарей. Он будет перебирать комбинации каждого словаря независимо; полезно, если у вас есть некоторые параметры, которые не совместимы с другими. Например, в LogisticRegression определенные значения штрафа работают только с определенными решателями.
parameters = [
{'clf__penalty': ['l1', 'l2'], 'clf__solver' : ['liblinear']},
{'clf__penalty': ['l1', 'none'], 'clf__solver' : ['newton-cg']},
]
Теперь, когда у нас есть сетка параметров, мы можем сначала создать экземпляр нашего базового классификатора и передать его в нашу конвейерную функцию.
clf = LogisticRegression(random_state=42, max_iter=500) pipeline = create_pipe(clf)
Далее мы запустим поиск по сетке с помощью GridSearchCV.
%time grid = GridSearchCV(pipeline,
parameters,
scoring='f1_macro',
cv=3,
random_state=0).fit(X_train, y_train)
print("Best cross-validation accuracy: {:.3f}".format(grid.best_score_))
print("Test set score: {:.3f}".format(grid.score(X_test, y_test)))
print("Best parameters: {}".format(grid.best_params_))
log_C = grid.best_params_['clf__C']
log_solver = grid.best_params_['clf__solver']
log_ngram = grid.best_params_['prep__Text__ngram_range']
58m 3s
Best cross-validation accuracy: 0.867
Test set score: 0.872
Best parameters: {'clf__C': 100, 'clf__solver': 'newton-cg',
'prep__Text__ngram_range': (1, 2)}
Наш поиск по сетке занял 58m 3s времени, и для каждого из них были получены наилучшие параметры.
Одна из вещей, которая может броситься вам в глаза при просмотре приведенного выше списка, заключается в том, что в нашей сетке параметров есть довольно много потенциальных комбинаций. В приведенном выше примере есть 4x solvers, 5x C, 3x n-grams, в результате чего общая сумма составляет 4 x 5 x 3 = 60. Поскольку обучение нашей модели заняло около одной минуты, однократное линейное прохождение сетки занимает около часа.
Примечание. Поиск по сетке можно распараллелить с помощью аргумента n_jobs=-1; однако я не показал относительную производительность для этого примера.
Далее, давайте посмотрим, как улучшить общую производительность.
Сокращение поиска
Однако есть способ ускорить GridSearch и вернуть очень похожие результаты за гораздо меньшее время. Метод известен как Последовательное деление пополам. Он будет использовать подмножество данных в начале процесса, чтобы найти некоторые из наиболее эффективных комбинаций параметров, и постепенно увеличивать объем используемых данных по мере того, как он сужается до лучших комбинаций.
Вы можете поменять местами вызов GridSearchCV с вызовом HalvingGridSearchCV`, чтобы использовать поиск по халвинговой сетке. Просто как тот. Давайте повторно запустим приведенный выше поиск с этим новым подходом и посмотрим, как он работает.
%time grid = HalvingGridSearchCV(pipeline,
parameters,
scoring='f1_macro',
cv=3,
random_state=0).fit(X_train, y_train)
print("Best cross-validation accuracy: {:.3f}".format(grid.best_score_))
print("Test set score: {:.3f}".format(grid.score(X_test, y_test)))
print("Best parameters: {}".format(grid.best_params_))
log_C_b = grid.best_params_['clf__C']
log_solver_b = grid.best_params_['clf__solver']
log_ngram_b = grid.best_params_['prep__Text__ngram_range']
14m 28s
Best cross-validation accuracy: 0.867
Test set score: 0.872
Best parameters: {'clf__C': 100, 'clf__solver': 'lbfgs',
'prep__Text__ngram_range': (1, 2)}
Довольно впечатляющий! с часа до 15 минут! В некоторых случаях я видел, что он работает даже быстрее. Мы также можем видеть, что результаты очень похожи. На этот раз было выбрано решение lbfgs против newton-cg. Теперь мы можем сравнить производительность обоих.
Оценка результатов
У нас есть простая функция, которая возьмет конвейер, подгонит данные к обучающему и тестовому набору и оценит результаты с помощью классификационного отчета и матрицы путаницы. Давайте последовательно рассмотрим ненастроенную модель, настроенную модель поиска по сетке и, наконец, настроенную модель поиска по сетке вдвое. Во-первых, это оригинальная модель.
Примечание. Мы используем здесь показатель оценки F1-Macro; мы стремимся сбалансировать точность и отзыв.
def fit_and_print(pipeline, name):
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
score = metrics.f1_score(y_test, y_pred, average='macro')
print(metrics.classification_report(y_test, y_pred, digits=3))
ConfusionMatrixDisplay.from_predictions(y_test,
y_pred,
cmap=plt.cm.Greys)
plt.tight_layout()
plt.title(name)
plt.ylabel('True Label')
plt.xlabel('Predicted Label')
plt.tight_layout()
plt.savefig(name + '.png', dpi=300)
plt.show;
clf = LogisticRegression(random_state=42, max_iter=500)
pipeline = create_pipe(clf)
fit_and_print(pipeline, 'Default Parameters')
precision recall f1-score support
0 0.789 0.845 0.816 9545
1 0.925 0.894 0.909 20370
accuracy 0.879 29915
macro avg 0.857 0.869 0.863 29915
weighted avg 0.882 0.879 0.880 29915

Наша оценка F1-Macro здесь составляет 0.863. Далее давайте попробуем настроенную модель Grid Search.
clf = LogisticRegression(C=log_C, solver=log_solver, random_state=42, max_iter=500)
pipeline = create_pipe(clf, log_ngram)
fit_and_print(pipeline, 'GridSearch Parameters')
precision recall f1-score support
0 0.839 0.810 0.824 9545
1 0.913 0.927 0.920 20370
accuracy 0.890 29915
macro avg 0.876 0.869 0.872 29915
weighted avg 0.889 0.890 0.889 29915

Наша оценка F1-Macro здесь составляет 0.872. Наш процесс настройки улучшил общие результаты для режима, и мы увеличили оценку F1-Macro на 0.09. Далее давайте попробуем настроенную модель Halving Grid Search.
clf = LogisticRegression(C=log_C_b, solver=log_solver_b, random_state=42, max_iter=500)
pipeline = create_pipe(clf, log_ngram_b)
fit_and_print(pipeline, 'HalvingGridSearch Parameters')
precision recall f1-score support
0 0.839 0.811 0.824 9545
1 0.913 0.927 0.920 20370
accuracy 0.890 29915
macro avg 0.876 0.869 0.872 29915
weighted avg 0.889 0.890 0.889 29915

Наконец, мы видим результаты модели поиска по сетке Halving. Оценка F1-Macro такая же, как и у модели Grid Search. Мы сократили время настройки с 60 минут до 15 без ущерба для результатов настройки. Каждый раз, когда вы используете эти методы, ваши результаты могут отличаться, но это отличный способ настроить модель, не отнимая у вас массу времени.
Заключение
Настройка гиперпараметров — это способ настроить модель после тщательного выбора функций и выбора модели. Гиперпараметры — это не параметры, полученные в процессе обучения, а те, которые настраиваются для улучшения общей производительности вашей модели. Мы увидели, как получить доступ к параметрам в конвейере и выполнить поиск по сетке, чтобы выбрать лучший. Наконец, вы увидели, как можно использовать метод поиска с половинной сеткой, чтобы сократить время, необходимое для поиска лучших параметров. Надеюсь, вам понравилась эта статья. Удачной постройки модели!
Если вы хотите воссоздать это, весь этот код доступен в Блокноте на GitHub.
Если вам нравится читать такие истории и вы хотите поддержать меня как писателя, подумайте о том, чтобы зарегистрироваться и стать участником Medium. Это 5 долларов в месяц, что дает вам неограниченный доступ к тысячам статей. Если вы зарегистрируетесь по моей ссылке, я получу небольшую комиссию без каких-либо дополнительных затрат для вас.