Как оптимизировать гиперпараметры модели машинного обучения и как ускорить процесс
Что такое настройка гиперпараметров?
Настройка гиперпараметров – это метод точной настройки модели машинного обучения. Гиперпараметры специально не изучаются в процессе обучения, но их можно настроить для оптимизации производительности модели. Вот несколько советов, о которых мне нравится думать, когда дело доходит до настройки гиперпараметров:
- Настройка гиперпараметров обычно является последним шагом, который необходимо выполнить при построении модели непосредственно перед окончательной оценкой.
- Вы не получите резко отличающихся результатов от настройки параметров. Наибольшее влияние на производительность модели оказывают выбор функций и выбор модели.
- Настройка гиперпараметров может помочь с обобщением модели, уменьшая переобучение.
При этом настройка вашей модели является важным шагом в рабочем процессе. Вот краткое изображение, которое может помочь вам понять процесс.
- Подготовка данных: процесс очистки ваших данных и подготовки их к машинному обучению.
- Исследовательский анализ данных. Этот шаг следует выполнять всегда. Процесс изучения нового набора данных и понимания распределения данных, корреляций и многого другого. Смотрите этот пост для пошагового руководства.
- Разработка и выбор функций: процесс создания новых функций (столбцов) из ваших данных и выбора лучших функций в зависимости от того, какой вклад они вносят. производительность модели.
- Выбор модели. Использование перекрестной проверки для выбора алгоритма с наибольшей эффективностью на основе показателя оценки.
- Настройка гиперпараметров: процесс, описанный в этом посте.
- Оценка модели: выбор правильного показателя производительности и оценка результатов.
Примеры гиперпараметров
Некоторые примеры гиперпараметров:
- Какой решатель следует использовать в модели логистической регрессии?
- Какое значение лучше для 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 долларов в месяц, что дает вам неограниченный доступ к тысячам статей. Если вы зарегистрируетесь по моей ссылке, я получу небольшую комиссию без каких-либо дополнительных затрат для вас.