Форум программистов, компьютерный форум, киберфорум
AI_Generated
Войти
Регистрация
Восстановить пароль

Настройка гиперпараметров с помощью Grid Search и Random Search в Python

Запись от AI_Generated размещена 15.05.2025 в 21:41
Показов 1490 Комментарии 0

Нажмите на изображение для увеличения
Название: 248747af-74ff-4e01-a0bd-a7ce9921b3dc.jpg
Просмотров: 87
Размер:	264.2 Кб
ID:	10810
В машинном обучении существует фундаментальное разделение между параметрами и гиперпараметрами моделей. Если параметры – это те величины, которые алгоритм "изучает" непосредственно из данных (веса нейронной сети, коэфициенты линейной регрессии), то гиперпараметры – конфигурационные настройки, определяющие саму процедуру обучения. Они задаются "вручную" до начала тренировки модели и остаются неизменными на протяжении всего процесса.

Глубинное значение гиперпараметров заключается в их способности влиять на баланс между переобучением и недообучением модели. Например, слишком большое количество деревьев в случайном лесу может привести к переобучению – модель "заучит" тренировочные данные, но потеряет способность обобщать закономерности на новых примерах. При этом слишком маленькое значение не позволит модели уловить сложные зависимости в данных. Удивительно, но даже небольшое изменение гиперпараметров способно драматически преобразить производительность модели. Будь то скорость обучения (learning rate) для градиентных методов, глубина дерева решений или параметр регуляризации С в SVM – их верная "настройка" часто становится ключом между посредственным и великолепным результатом.

Проблематика настройки гиперпараметров



Настройка гиперпараметров – это своего рода тёмное искуство машинного обучения. Даже опытные специалисты порой чувствуют себя как алхимики, смешивающие ингредиенты в поисках философского камня. И это неудивительно. Пространство поиска оптимальных параметров часто настолько огромно, что полный перебор всех возможных комбинаций технически невозможен. Представьте модель с десятью гиперпараметрами, каждый из которых может принимать хотя бы 5 разных значений – это уже 5^10 или почти 10 миллионов комбинаций!

Традиционно подход к настройке напоминал метод научного тыка: специалисты опирались на интуицию, предыдущий опыт и общие рекомендации. "Для случайного леса начните с 100 деревьев", "для нейронной сети выберите скорость обучения 0.001" – подобные эвристики работают... иногда. Проблема в том, что они не универсальны – оптимальные значения гиперпараметров сильно зависят от конкретного набора данных и решаемой задачи. Не менее важный аспект – вычислительная сложность процеса. Каждое тестирование комбинации гиперпараметров требует полного цикла обучения и валидации модели. Для сложных моделей на больших данных это может занимать часы или даже дни. Приходится постоянно искать компромисс: либо ограничиться небольшим количеством проверенных комбинаций (рискуя не найти оптимальную), либо потратить значительные ресурсы на более тщательный поиск.

Еще один подводный камень – переобучение на валидационном наборе. Это происходит, когда мы слишком усердно оптимизируем гиперпараметры под конкретную выборку, и модель теряет способность к обобщению на реальных данных. Исследование Каннинга и др. показывает, что чрезмерная оптимизация может снизить производительность на 5-10% на независымых тестовых выборках.

Оптимизация гиперпараметров также сталкивается с проблемой мультимодальности целевой функции. Часто поверхность ошибки имеет множество локальных минимумов, и простые методы поиска могут "застрять" в локальном оптимуме, не достигнув глобального. Это особенно актуально для сложных моделей вроде глубоких нейронных сетей, где взаимодействие между гиперпараметрами создает крайне нелинейные зависимости.

Почему model без подбора гиперпараметров лучше, чем с подбором?
Всем, привет. Подскажите пожалуйста с проблемой. Для классификации использую xgboost. Почему...

Подбор гиперпараметров для нейронной сети
Помогите с подбором гиперпараметров для нейросети, а именно: скорость обучения, момент

Влияние гиперпараметров алгоритма на степень обучения модели
Всем, доброго времени суток! Подскажите пожалуйста, как можно построить график влияния...

Серия экспериментов по перебору гиперпараметров нейронной сети
1. Поменяйте количество нейронов в сети, используя следующие значения: один слой 10 нейронов один...


Принципы работы Grid Search



Grid Search (поиск по сетке) – один из самых прозрачных и интуитивно понятных методов оптимизации гиперпараметров. В основе этого подхода лежит исчерпывающий перебор всех возможных комбинаций из заданного набора значений. По сути, это метод полного перебора на стероидах – мы определяем диапазоны значений для каждого гиперпараметра, а затем алгоритм методично исследует каждое пересечение в этой многомерной сетке. Математически Grid Search можно представить как задачу оптимизации, где мы ищем набор гиперпараметров λ, максимизирующий функцию качества модели на валидационной выборке:

λ* = argmax_{λ ∈ Λ} f(λ)

где Λ – конечное множество всех возможных комбинаций гиперпараметров, а f(λ) – функция, оценивающая качество модели при заданных гиперпараметрах λ (обычно через кросс-валидацию).

Особую ценность этому методу придаёт его предсказуемость и полнота. Если оптимальная комбинация существует среди заданных значений, Grid Search гарантированно её найдёт – вопрос лишь в количестве вычислительных ресурсов и времени. Можно сказать, что это "ленивый, но безошибочный поисковик" – он проверит все варианты, не пропустив ни одного, даже самого неожиданного.

Реализовать Grid Search в Python невероятно просто благодаря библиотеке scikit-learn. Всё начинается с определения пространства поиска – словаря, где ключи – это имена гиперпараметров, а значения – списки возможных значений:

Python
1
2
3
4
5
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [None, 10, 20, 30],
    'min_samples_split': [2, 5, 10]
}
В этом примере мы определили 36 уникальных комбинаций (3 × 4 × 3) для Random Forest. И это, кстати, наглядно демонстрирует одну из основных проблем Grid Search – "проклятие размерности". Добавьте еще пару гиперпараметров с 4-5 значениями каждый, и число комбинаций взлетит до нескольких тысяч! Непосредственно поиск осуществляется с помощью класса GridSearchCV, который обеспечивает одновременно и перебор комбинаций, и оценку через кросс-валидацию:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
 
model = RandomForestClassifier(random_state=42)
grid_search = GridSearchCV(
    estimator=model,
    param_grid=param_grid,
    cv=5,  # 5-кратная кросс-валидация
    n_jobs=-1,  # Использовать все доступные ядра процессора
    verbose=1
)
 
# Запуск поиска
grid_search.fit(X_train, y_train)
 
# Получение лучших параметров
best_params = grid_search.best_params_
print(f"Лучшие параметры: {best_params}")
Параметр n_jobs=-1 здесь особенно важен – он позволяет распараллелить вычисления на все доступные ядра процессора, что существенно ускоряет поиск. Для больших моделей и обширных сеток это иногда означает разницу между часами и днями ожидания. Важный аспект, о котором часто забывают новички – Grid Search может быть непродуктивно затратным для глубоких нейронных сетей или очень больших датасетов. Иногда умное планирование эксперимента позволяет достич тех же результатов с гораздо меньшими затратами. Например, можно начать с грубой сетки с широко расставленными значениями, а затем сузить поиск вокруг найденного оптимума.

Одна из серьезных трудностей при работе с Grid Search – проблема визуализации многомерного пространства параметров. Ведь человеческий мозг отлично воспринимает 2-3 измерения, но когда речь идет о 5-10 гиперпараметрах, понять, как именно работает наш поиск, становится нетривиальной задачей. К счастью, существуют техники, позволяющие "заглянуть" в этот многомерный космос настроек.

Для визуализации результатов поиска по сетке особенно полезны heatmap-диаграммы и графики попарных зависимостей. Например, можно построить тепловую карту, отображающую производительность модели при разных комбинациях двух ключевых гиперпараметров, зафиксировав остальные:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
 
# Предположим, у нас есть результаты GridSearchCV
results = grid_search.cv_results_
 
# Извлекаем параметры для визуализации
n_estimators = np.array([params['n_estimators'] for params in results['params']])
max_depth = np.array([params['max_depth'] if params['max_depth'] is not None else 100 
                    for params in results['params']])
 
# Создаем сетку для тепловой карты
pivot_table = np.zeros((len(np.unique(n_estimators)), len(np.unique(max_depth))))
for i, n in enumerate(np.unique(n_estimators)):
    for j, d in enumerate(np.unique(max_depth)):
        mask = (n_estimators == n) & (max_depth == d)
        if np.any(mask):
            pivot_table[i, j] = np.max(results['mean_test_score'][mask])
 
# Визуализация
plt.figure(figsize=(10, 6))
sns.heatmap(pivot_table, annot=True, fmt='.3f', 
           xticklabels=np.unique(max_depth),
           yticklabels=np.unique(n_estimators))
plt.xlabel('max_depth')
plt.ylabel('n_estimators')
plt.title('Средняя точность при разных комбинациях параметров')
plt.show()
Такая визуализация мгновенно показывает, в каких областях сосредоточены наиболее производительные настройки, и позволяет "почувствовать" ландшафт оптимизации.

При всей своей простоте и прямолинейности, Grid Search может оказатся чудовищно затратным вычислительно. В реальных проектах необходимо применять стратегии оптимизации этого процесса. Рассмотрим несколько проверенных практикой подходов.

Стратегия постепенного уточнения



Вместо того чтобы сразу запускать исчерпывающий поиск по плотной сетке, начните с редкой "разведочной" сетки. Определите перспективные регионы, а затем проведите детальный поиск только в них:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Этап 1: "Разведка" с грубой сеткой
param_grid_coarse = {
    'n_estimators': [50, 200, 500],
    'max_depth': [5, 15, None],
    'min_samples_split': [2, 10]
}
 
# Поиск с грубой сеткой
grid_search_coarse = GridSearchCV(
    estimator=model, param_grid=param_grid_coarse, cv=3, n_jobs=-1
)
grid_search_coarse.fit(X_train, y_train)
 
# Определяем лучшие параметры
best_params = grid_search_coarse.best_params_
 
# Этап 2: Уточняющий поиск вокруг найденного оптимума
n_est_best = best_params['n_estimators']
max_depth_best = best_params['max_depth']
 
param_grid_fine = {
    'n_estimators': [max(50, n_est_best - 100), n_est_best, min(1000, n_est_best + 100)],
    'max_depth': [
        max(3, max_depth_best - 3) if max_depth_best is not None else 15,
        max_depth_best,
        min(30, max_depth_best + 3) if max_depth_best is not None else None
    ],
    'min_samples_split': [2, best_params['min_samples_split'], 
                        best_params['min_samples_split'] + 5]
}
 
# Уточняющий поиск
grid_search_fine = GridSearchCV(
    estimator=model, param_grid=param_grid_fine, cv=5, n_jobs=-1
)
grid_search_fine.fit(X_train, y_train)
Этот двухэтапный подход значительно сокращает общее количество комбинаций и, соответствено, время поиска, при этом практически не ухудшая качество найденного решения.

Оптимизация через предварительную выборку



Еще одна эффективная стратегия – выполнять предварительный отбор на уменьшеной выборке данных. Можно сначала найти перспективные области на неболшом подмножестве тренировочных данных, а затем уточнить результаты на полном наборе:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import pandas as pd
from sklearn.model_selection import train_test_split
 
# Создаем уменьшенную выборку для предварительного поиска
X_sample, _, y_sample, _ = train_test_split(
    X_train, y_train, train_size=0.3, random_state=42
)
 
# Поиск на уменьшенной выборке
grid_search_sample = GridSearchCV(
    estimator=model, param_grid=param_grid, cv=3, n_jobs=-1
)
grid_search_sample.fit(X_sample, y_sample)
 
# Выбираем топ-3 комбинации для проверки на полном наборе
top_params = pd.DataFrame(grid_search_sample.cv_results_)[
    ['params', 'mean_test_score']
].sort_values('mean_test_score', ascending=False).head(3)['params'].tolist()
 
# Создаем сокращенную сетку из перспективных комбинаций
param_grid_refined = {param: [] for param in param_grid.keys()}
for params in top_params:
    for param, value in params.items():
        if value not in param_grid_refined[param]:
            param_grid_refined[param].append(value)
 
# Финальный поиск на полном наборе данных
grid_search_final = GridSearchCV(
    estimator=model, param_grid=param_grid_refined, cv=5, n_jobs=-1
)
grid_search_final.fit(X_train, y_train)

Распараллеливание: получаем максимум от железа



Одно из главных преимуществ Grid Search – его прекрасная параллелизуемость. Каждая комбинация гиперпараметров может быть оценена независимо, что позволяет эффективно распределить нагрузку на все доступные ядра процессора.
В scikit-learn это управляется параметром n_jobs. Значение -1 означает использование всех доступных ядер:

Python
1
2
3
4
5
6
7
grid_search = GridSearchCV(
    estimator=model,
    param_grid=param_grid,
    cv=5,
    n_jobs=-1,  # Используем все ядра
    verbose=2   # Получаем подробный вывод о прогрессе
)
В сложных случаях распараллеливание можно довести до уровня распределённых вычислений, например с использованием Dask или Apache Spark. Это особенно полезно, когда поиск занимает дни или недели:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
from dask.distributed import Client
from dask_ml.model_selection import GridSearchCV as DaskGridSearchCV
 
# Запускаем локальный Dask-кластер
client = Client()
 
# Используем GridSearchCV из dask_ml
grid_search = DaskGridSearchCV(
    estimator=model,
    param_grid=param_grid,
    cv=5
)
grid_search.fit(X_train, y_train)

Разумное планирование: где искать имеет значение



Очень важно разумно определить диапазоны поиска для каждого гиперпараметра. Выбор слишком широкого диапазона приведет к излишним вычислительным затратам, а слишком узкого – может пропустить оптимальное решение.
Полезный подход – логарифмическая шкала для параметров, которые могут варьироватся на порядки. Например, для параметра регуляризации С в SVM или learning rate в градиентном бустинге имеет смысл рассматривать значения 0.001, 0.01, 0.1, 1, 10, 100 вместо линейного диапазона:

Python
1
2
3
4
5
param_grid = {
    'C': [0.001, 0.01, 0.1, 1, 10, 100],
    'kernel': ['linear', 'rbf', 'poly'],
    'degree': [2, 3, 4]  # Только для kernel='poly'
}
Для непрерывных параметров иногда полезно использовать np.logspace для создания логарифмически распределенных значений:

Python
1
2
3
4
5
6
7
8
import numpy as np
 
# Создаем 10 значений, логарифмически распределенных в диапазоне от 10^-3 до 10^3
C_values = np.logspace(-3, 3, 10)
param_grid = {
    'C': C_values,
    'kernel': ['linear', 'rbf']
}
Неправильное планирование сетки – одна из наиболее распространённых ошибок при работе с Grid Search. Оно может привести как к бесполезной трате ресурсов, так и к субоптимальным результатам.

Принципы работы Random Search



Random Search (случайный поиск) – это альтернатива Grid Search, которая решает проблему "проклятия размерности" за счёт умного компромисса между полнотой исследования и эффективностью. Вместо того чтобы методично прочёсывать всё пространство гиперпараметров, Random Search генерирует случайные комбинации из заданных распределений, что позволяет исследовать гораздо более широкие диапазоны при том же бюджете вычислений.
Математически Random Search можно представить как метод оптимизации, где мы ищем набор гиперпараметров λ, приближающий максимум функции качества:

λ* ≈ argmax_{λ ~ P(Λ)} f(λ)

где P(Λ) – вероятностное распределение, из которого мы сэмплируем гиперпараметры, а f(λ) – функция оценки качества модели. В отличие от Grid Search, пространство поиска здесь не дискретизируется полностью, что даёт возможность обнаруживать неожиданные "оазисы" производительности между узлами обычной сетки.

Эффективность Random Search основана на закономерности: во многих практических задачах лишь несколько гиперпараметров по-настоящему критичны для производительности модели. Представьте двумерную функцию, где один параметр сильно влияет на результат, а другой – почти нет. Grid Search потратит много ресурсов на исследование малозначимого измерения, тогда как Random Search распределит "бюджет" более равномерно по всему пространству.

Реализация Random Search в scikit-learn столь же проста, как и Grid Search, но с использованием класса RandomizedSearchCV:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
import numpy as np
 
# Определяем модель
rf = RandomForestClassifier(random_state=42)
 
# Пространство поиска - обратите внимание на более широкие диапазоны
param_distributions = {
    'n_estimators': [50, 100, 200, 300, 500],
    'max_depth': [None, 10, 20, 30, 40, 50],
    'min_samples_split': [2, 5, 10, 15, 20],
    'max_features': ['sqrt', 'log2', None]
}
 
# Запускаем случайный поиск
random_search = RandomizedSearchCV(
    estimator=rf,
    param_distributions=param_distributions,
    n_iter=100,  # Количество комбинаций для оценки
    cv=5,
    random_state=42,
    n_jobs=-1
)
 
# Обучаем
random_search.fit(X_train, y_train)
 
# Получаем лучшие параметры
print(f"Лучшие параметры: {random_search.best_params_}")
Ключевое отличие от Grid Search – параметр n_iter, который определяет, сколько случайных комбинаций будет проверено. Вы сами контролируете свой "бюджет" на эксперименты!
Особую гибкость Random Search проявляет при работе с непрерывными распределениями параметров. Вместо дискретного набора значений можно задать полноценное вероятностное распределение:

Python
1
2
3
4
5
6
7
8
9
from scipy.stats import uniform, randint
 
# Смешанное пространство поиска с непрерывными и дискретными распределениями
param_distributions = {
    'n_estimators': randint(50, 500),      # Равномерное целочисленное от 50 до 500
    'max_depth': [None] + list(randint(5, 50).rvs(10)),  # None или случайное из 5-50
    'min_samples_split': randint(2, 20),   # Равномерное целочисленное от 2 до 20
    'learning_rate': uniform(0.01, 0.3)    # Равномерное от 0.01 до 0.31
}
Эта возможность делает Random Search особенно мощным для тонкой настройки гиперпараметров типа скорости обучения, где нужна высокая гранулярность исследования. И что важно – это не увеличивает вычислительную сложность, так как количество проверяемых комбинаций фиксировано параметром n_iter.

Для задач с ограниченым бюджетом на поиск (по времени или ресурсам) Random Search почти всегда предпочтительнее Grid Search. Исследования показывают, что при одинаковом количестве проверенных комбинаций Random Search обычно находит лучшее или сравнимое решение, особенно в многомерных пространствах параметров.

Вероятностная модель эффективности Random Search



Почему же случайный поиск так эффективен? Ответ скрывается в работе Бергстры и Бенджио "Random Search for Hyper-Parameter Optimization", которая математически доказывает преимущество этого метода. Основная идея заключается в том, что для большинства практических задач "неважные" измерения в пространстве гиперпараметров образуют гораздо больший объём, чем "важные". Представьте функцию потерь как горный ландшафт в многомерном пространстве. Часто лишь несколько "долин" действительно содержат глобальные минимумы, а большая часть пространства – это "плато" или "холмы" с высокими значениями ошиблк. Grid Search тратит одинаковое количество проб на каждое измерение, даже когда оно малозначительно. Random Search же распределяет ресурсы более равномерно, что часто приводит к лучшим результатам при том же количестве экспериментов.

Математически вероятность найти значение параметра в пределах p% лучших можно выразить как (1-(1-p)^n), где n – количество случайных проб. Например, при 10 пробах вероятность попасть в топ-10% значений составляет примерно 65%, а при 50 пробах – уже 99.5%. Это объясняет, почему даже небольшое число случайных комбинаций может давать очень хорошие результаты.

Важно понимать, что эффективность Random Search сильно зависит от структуры пространства гиперпараметров. В ситуациях, когда оптимальные значения сосредоточены в узкой области, или когда между параметрами существуют сложные взаимозависимости, чисто случайный подход может оказаться неэффективным. Именно в таких случаях стоит обратить внимание на адаптивные стратегии поиска.

Адаптивные стратегии для улучшения случайного поиска



Классический Random Search не учитывает результаты предыдущих итераций. Каждая новая проба выбирается независимо от того, насколько "удачными" или "неудачными" были предыдущие. Это упущенная возможность! Современные адаптивные стратегии позволяют "обучаться" в процессе поиска, концентрируясь на более перспективных областях.

Одна из таких стратегий – сужающийся случайный поиск (Narrowing Random Search). После начального этапа определяются наиболее перспективные области пространства параметров, и дальнейший поиск концентрируется уже внутри них:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np
from sklearn.model_selection import RandomizedSearchCV, cross_val_score
 
# Начальный широкий поиск
wide_param_distributions = {
    'n_estimators': randint(50, 1000),
    'max_depth': [None] + list(randint(5, 100).rvs(10)),
    'min_samples_split': randint(2, 30)
}
 
initial_search = RandomizedSearchCV(
    rf, wide_param_distributions, n_iter=30, cv=3, n_jobs=-1
)
initial_search.fit(X_train, y_train)
 
# Анализируем топ-5 результатов
top_params = pd.DataFrame(initial_search.cv_results_)[
    ['params', 'mean_test_score']
].sort_values('mean_test_score', ascending=False).head(5)
 
# Определяем новые, суженные диапазоны для каждого параметра
narrow_distributions = {}
for param in wide_param_distributions:
    values = [p[param] for p in top_params['params']]
    if all(isinstance(v, (int, float)) for v in values):
        min_val, max_val = min(values), max(values)
        # Расширяем диапазон на 20% в обе стороны
        range_width = max_val - min_val
        narrow_distributions[param] = uniform(
            max(min_val - 0.2 * range_width, 0),
            min(max_val + 0.2 * range_width, 1000) - max(min_val - 0.2 * range_width, 0)
        )
    else:
        # Для категориальных параметров
        narrow_distributions[param] = values
 
# Второй этап поиска в суженной области
refined_search = RandomizedSearchCV(
    rf, narrow_distributions, n_iter=50, cv=5, n_jobs=-1
)
refined_search.fit(X_train, y_train)
Такой двухэтапный подход позволяет эффективно исследовать наиболее перспективные области, одновременно сохраняя преимущества случайного поиска.

Байесовская оптимизация - умный взгляд на поиск гиперпараметров



Если Grid Search можно сравнить с методичным землекопом, а Random Search – с золотоискателем, размахивающим лотком наугад, то Байесовскую оптимизацию можно представить как опытного геолога, который тщательно анализирует каждую находку и планирует, где копать дальше.

Байесовская оптимизация моделирует целевую функцию (например, точность модели) как гауссовский процесс и использует все предыдущие наблюдения для выбора следующего набора гиперпараметров для оценки. Ключевой компонент этого подхода – функция приобретения (acquisition function), которая балансирует между исследованием неизвестных областей и эксплуатацией известных хороших регионов. В Python для байесовской оптимизации гиперпараметров можно использовать библиотеки scikit-optimize, hyperopt или optuna:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from skopt import BayesSearchCV
from skopt.space import Real, Integer, Categorical
 
# Определяем пространство поиска
search_space = {
    'n_estimators': Integer(50, 500),
    'max_depth': Integer(3, 50),
    'min_samples_split': Integer(2, 20),
    'min_samples_leaf': Integer(1, 10),
    'max_features': Categorical(['sqrt', 'log2', None])
}
 
# Создаем байесовский оптимизатор
bayes_search = BayesSearchCV(
    rf,
    search_space,
    n_iter=50,  # количество итераций
    cv=5,
    n_jobs=-1,
    verbose=0
)
 
# Запускаем поиск
bayes_search.fit(X_train, y_train)
 
# Лучшие параметры
print(f"Лучшие параметры: {bayes_search.best_params_}")
Байесовский подход особенно эффективен, когда оценка каждой комбинации гиперпараметров дорога (например, для глубоких нейронных сетей или вычислительно сложных моделей на больших данных). Исследования показывают, что байесовская оптимизация может находить сравнимые или лучшие решения в 5-10 раз быстрее, чем случайный поиск, особенно когда количество гиперпараметров велико (более 5-7).

Практические советы по использованию Random Search



1. Начинайте с широких диапазонов: Одно из главных преимуществ Random Search – возможность исследовать широкие диапазоны с разумным количеством проб. Не ограничивайте себя узкими интервалами, особенно на начальных этапах.
2. Логарифмические шкалы для численных параметров: Для многих гиперпараметров (скорость обучения, параметры регуляризации) имеет смысл использовать логарифмическую шкалу вместо линейной. Например loguniform(1e-5, 1e-1) вместо uniform(0, 0.1).
3. Визуализируйте результаты: Анализ взаимосвязей между значениями гиперпараметров и производительностью модели может дать ценное понимание работы алгоритма. Используйте частичные графики зависимости (partial dependence plots) для визуализации:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import matplotlib.pyplot as plt
from sklearn.model_selection import validation_curve
 
# Строим график зависимости метрики от одного гиперпараметра
n_estimators_range = [50, 100, 200, 300, 400, 500]
train_scores, test_scores = validation_curve(
    RandomForestClassifier(),
    X_train, y_train,
    param_name="n_estimators",
    param_range=n_estimators_range,
    cv=5,
    scoring="accuracy",
    n_jobs=-1
)
 
plt.figure(figsize=(10, 6))
plt.plot(n_estimators_range, np.mean(train_scores, axis=1), 'o-', label='Training score')
plt.plot(n_estimators_range, np.mean(test_scores, axis=1), 'o-', label='Validation score')
plt.xlabel('n_estimators')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Validation Curve')
plt.show()
4. Правильно выбирайте количество итераций: Количество комбинаций для проверки (n_iter) – ключевой параметр Random Search. Хорошее эмпирическое правило – от 10 до 20 итераций на каждый гиперпараметр. Например, для настройки 5 параметров разумно начать с 50-100 итераций.

Сравнительный анализ методов



Выбор между Grid Search и Random Search часто напоминает классический спор физиков: что лучше — методичный, полный перебор всех возможных состояний или "квантовый" вероятностный подход? Оба метода имеют свои уникальные сильные стороны и ограничения, понимание которых критически важно для эффективного решения задач оптимизации гиперпараметров.

Grid Search – это надежный бульдозер, прокладывающий путь через пространство параметров строго по намеченной схеме. Его основное преимущество – гарантированный поиск оптимума среди заданных точек. Это создаёт своего рода "страховочную сетку" – если оптимальная комбинация существует среди узлов сетки, она обязательно будет найдена. Кроме того, детерминированная природа поиска по сетке обеспечивает полную воспроизводимость экспериментов, что особенно ценно в исследовательских проектах.

Random Search же больше напоминает умного золотоискателя, который, вместо того чтобы методично перекапывать каждый квадратный метр, делает пробы в разных местах. Его главное преимущество – эффективное использование ограниченных вычислительных ресурсов. Когда бюджет на поиск лимитирован, Random Search способен исследовать значительно более широкие диапазоны параметров и часто находит неожиданно хорошие комбинации, лежащие "между" узлами стандартной сетки.

В контексте вычислительной эффективности Random Search почти всегда выигрывает. Для пространства с d параметрами, где каждый может принимать m различных значений, Grid Search требует оценки m^d комбинаций. Экспоненциальный рост с увеличением числа параметров делает его неприменимым для задач с большим количеством гиперпараметров. Random Search же позволяет явно задать количество тестируемых комбинаций, независимо от размерности пространства. При этом качество найденных решений часто оказывается не хуже, а иногда даже лучше. В знаменитом исследовании Бергстра и Бенджио демонстрируется, что при одинаковом вычислительном бюджете Random Search обычно находит параметры, которые как минимум так же хороши, как найденные с помощью Grid Search, а в многомерных задачах – заметно лучше.

Что касается практического применения, Grid Search обычно предпочтительнее в следующих ситуациях:
  • Когда число гиперпараметров невелико (2-3).
  • Когда уже примерно известна область оптимальных значений.
  • Когда требуется полная воспроизводимость результатов.
  • Для задач, где необходимо исчерпывающее сравнение конкретных комбинаций.

Random Search же демонстрирует превосходство в таких случаях:
  • Для моделей с большим числом гиперпараметров.
  • При наличии ограниченных вычислительных ресурсов.
  • На стадии предварительного исследования, когда оптимальный диапазон значений неизвестен.
  • Когда в пространстве параметров есть измерения с низким влиянием на результат.

Любопытно, что современная практика часто рекомендует гибридный подход: начать с широкого Random Search для определения перспективных регионов, а затем применить более узкий Grid Search в этих областях для точной настройки. Важно также рассмотреть типичные паттерны поведения этих методов в разных сценариях. Многолетний опыт применения Grid Search и Random Search в индустрии позволил выявить несколько характерных закономерностей, о которых редко пишут в академической литературе.

Интересный феномен наблюдается при настройке ансамблевых методов: Grid Search часто "застревает" на локальных оптимумах из-за дискретной природы параметров таких моделей. Например, при настройке XGBoost модели параметры max_depth и min_child_weight создают сложную дискретную поверхность ошибки с множеством "ям" и "пиков". Random Search благодаря своей стохастической природе с большей вероятностью преодолевает эти локальные ловушки.

Но есть и обратные примеры. При тонкой настройке SVM с RBF-ядром параметры C и gamma демонстрируют очень четкие, почти линейные зависимости. В таких случаях Grid Search с логарифмической шкалой значений показывает превосходные результаты, последовательно сужая область поиска к глобальному оптимуму.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Пример эффективной стратегии для SVM с RBF ядром
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
import numpy as np
 
# Модель SVM
svm = SVC(kernel='rbf')
 
# Логарифмическая сетка для C и gamma
param_grid = {
    'C': np.logspace(-3, 3, 7),          # 0.001, 0.01, 0.1, 1, 10, 100, 1000
    'gamma': np.logspace(-4, 0, 5)       # 0.0001, 0.001, 0.01, 0.1, 1
}
 
# Grid Search с логарифмической шкалой
grid_search = GridSearchCV(
    estimator=svm,
    param_grid=param_grid,
    cv=5,
    n_jobs=-1
)
Любопытное наблюдение из практики - эффективность методов поиска сильно связана с "шумностью" оцениваемой метрики. Если метрика сильно варирует при кросс-валидаци (высокая дисперсия), Random Search показывает себя лучше благодаря более широкому охвату пространства параметров. Если же метрика стабильна, Grid Search обеспечивает более систематичное улучшение.

Нельзя не упомянуть и временной аспект. В компаниях с ограниченными вычислительными мощностями и жесткими дедлайнами Random Search даёт возможность получить "достаточно хорошее" решение гораздо быстрее, особенно для моделей со множеством гиперпараметров. Как однажды заметил мой колега-дата сайнтист: "Лучше иметь хорошую модель сегодня, чем идеальную через неделю, когда проект уже завершен". Отдельного внимания заслуживает вопрос масштабирования поиска гиперпараметров. Для очень больших моделей (например, глубоких нейронных сетей) даже Random Search может оказаться слишком затратным. В таких случаях часто применяют метод ранней остановки (early stopping) в сочетании с последовательным сужением области поиска:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import GradientBoostingClassifier
 
# Начальный широкий поиск
wide_param_dist = {
    'n_estimators': [100, 200, 500, 1000],
    'max_depth': [3, 5, 7, 9],
    'learning_rate': [0.01, 0.05, 0.1, 0.2]
}
 
# Первая итерация - широкий поиск с малым числом фолдов
first_search = RandomizedSearchCV(
    GradientBoostingClassifier(),
    wide_param_dist,
    n_iter=20,  # Ограниченное число итераций
    cv=3,       # Меньше фолдов для ускорения
    n_jobs=-1
)
 
# После выполнения first_search.fit() анализируем лучшие комбинации
# и сужаем область поиска для более детального исследования
Настоящий вызов возникает при работе с ансамблями моделей или пайплайнами, где гиперпараметры затрагивают несколько слоёв обработки. Здесь преимущество Random Search становится ещё более очевидным – он позволяет хотя бы частично исследовать гигантское пространство комбинаций, когда полный перебор технически невозможен.
В эксперементах на соревновательных наборах данных вроде тех, что встречаются на Kaggle, было замечено, что стратегия "широкий Random Search → узкий Grid Search" часто даёт наилучший результат при фиксированном временном бюджете. Этот гибридный подход позволяет совместить преимущества обоих методов: исследовательскую силу случайного поиска и методическую точность сеточного.

Практический пример комплексной настройки SVM и Random Forest



Давайте применим наши знания о Grid Search и Random Search к реальному кейсу, комбинируя разные алгоритмы и стратегии поиска. Для нашего примера возьмём классическую задачу классификации — определение типа вина по его химическим характеристикам.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split, GridSearchCV, RandomizedSearchCV
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
 
# Загружаем датасет
wine = load_wine()
X, y = wine.data, wine.target
 
# Разделяем на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Здесь мы используем набор данных Wine от UCI, который содержит 13 химических характеристик трёх классов вина. Такой набор данных идеально подходит для настройки гиперпараметров, поскольку он не слишком большой (что ускоряет экперименты), но и не тривиальный.
Для SVM критически важна предварительная нормализация данных. Мы можем объединить этот шаг с настройкой гиперпараметров, используя конвейеры (Pipeline):

Python
1
2
3
4
5
6
7
8
9
10
11
12
# Создаём конвейер для SVM с масштабированием
svm_pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('svm', SVC(random_state=42))
])
 
# Параметры для Grid Search
svm_param_grid = {
    'svm__C': [0.1, 1, 10, 100],
    'svm__gamma': [0.001, 0.01, 0.1, 1],
    'svm__kernel': ['rbf', 'linear']
}
Обратите внимание на префикс 'svm__' перед названими параметров – это особенность работы с пайплайнами в scikit-learn. Теперь запустим Grid Search для SVM:

Python
1
2
3
4
5
# Grid Search для SVM
grid_svm = GridSearchCV(svm_pipeline, svm_param_grid, cv=5, n_jobs=-1)
grid_svm.fit(X_train, y_train)
print(f"Лучшие параметры SVM: {grid_svm.best_params_}")
print(f"Лучший результат: {grid_svm.best_score_:.4f}")
Посмотрев на результаты Grid Search для SVM, мы можем перейти к настройке Random Forest, используя Random Search. Это отличная возможность сравнить оба подхода на одном и том же наборе данных:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Определяем Random Forest
rf = RandomForestClassifier(random_state=42)
 
# Пространство поиска для Random Search
rf_param_dist = {
    'n_estimators': randint(50, 200),
    'max_depth': [None] + list(range(5, 30, 5)),
    'min_samples_split': randint(2, 20),
    'min_samples_leaf': randint(1, 10),
    'max_features': ['sqrt', 'log2', None]
}
 
# Random Search для Random Forest
random_rf = RandomizedSearchCV(
    rf, rf_param_dist,
    n_iter=30,  # 30 случайных комбинаций
    cv=5,
    n_jobs=-1,
    random_state=42
)
random_rf.fit(X_train, y_train)
print(f"Лучшие параметры RF: {random_rf.best_params_}")
print(f"Лучший результат: {random_rf.best_score_:.4f}")
Интересно сравнить не только итоговую точность, но и эффективность самого процесса поиска. Один из способов – измерить время выполнения и построить график "время vs качество" для обоих методов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time
import matplotlib.pyplot as plt
 
# Функция для отслеживания времени/производительности
def track_performance(estimator, param_grid, method='grid', n_iter=50):
    results = []
    if method == 'grid':
        search = GridSearchCV(estimator, param_grid, cv=5, n_jobs=-1)
    else:
        search = RandomizedSearchCV(estimator, param_grid, n_iter=n_iter, cv=5, n_jobs=-1)
    
    start_time = time.time()
    search.fit(X_train, y_train)
    total_time = time.time() - start_time
    
    for i, params in enumerate(search.cv_results_['params']):
        score = search.cv_results_['mean_test_score'][i]
        results.append((i+1, score, total_time * (i+1) / len(search.cv_results_['params'])))
    
    return results, search.best_score_
 
# Применяем к обоим методам
grid_results, grid_best = track_performance(svm_pipeline, svm_param_grid, 'grid')
random_results, random_best = track_performance(rf, rf_param_dist, 'random', n_iter=30)
Полученные результаты могут быть визуализированы для лучшего понимания различий между подходами:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Строим график "оценка vs итерация"
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.plot([r[0] for r in grid_results], [r[1] for r in grid_results], 'b-', label='Grid - SVM')
plt.plot([r[0] for r in random_results], [r[1] for r in random_results], 'r-', label='Random - RF')
plt.xlabel('Итерация')
plt.ylabel('Score')
plt.legend()
plt.title('Производительность по итерациям')
 
# График "оценка vs время"
plt.subplot(1, 2, 2)
plt.plot([r[2] for r in grid_results], [r[1] for r in grid_results], 'b-', label='Grid - SVM')
plt.plot([r[2] for r in random_results], [r[1] for r in random_results], 'r-', label='Random - RF')
plt.xlabel('Время (секунды)')
plt.ylabel('Score')
plt.legend()
plt.title('Производительность по времени')
plt.tight_layout()
plt.show()
Окончательную оценку качества моделей с наилучшими найденными параметрами выполним на тестовой выборке:

Python
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.metrics import classification_report
 
# Оцениваем лучшие модели на тестовых данных
best_svm = grid_svm.best_estimator_
best_rf = random_rf.best_estimator_
 
# Сводная таблица результатов
print("Результаты SVM:")
print(classification_report(y_test, best_svm.predict(X_test)))
 
print("\nРезультаты Random Forest:")
print(classification_report(y_test, best_rf.predict(X_test)))
Этот пример демонстрирует не только техническую реализацию методов настройки гиперпараметров, но и стратегический подход к сравнению и выбору наиболее подходящего алгоритма. В реальных проектах часто требуются именно такие комплексные решения — мы не просто настраиваем одну модель, а исследуем нескольких кандидатов с различными подходами к оптимизации.

Кросс-валидация как основа надёжной оценки моделей



Продолжая наше погружение в практический пример с настройкой SVM и Random Forest, необходимо обратить внимание на один из фундаментальных аспектов этого процесса — стратегию кросс-валидации. Многие разработчики недооценивают этот компонент, сосредотачиваясь исключительно на выборе гиперпараметров, но правильная схема валидации может оказаться не менее важной, чем сами параметры.

В наших предыдущих примерах мы использовали стандартную 5-кратную кросс-валидацию через параметр cv=5. Однако, в зависимости от специфики данных, такой подход может оказаться неоптимальным. Давайте рассмотрим несколько альтернативных стратегий и их реализацию:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.model_selection import KFold, StratifiedKFold, TimeSeriesSplit
 
# Стандартная K-блочная кросс-валидация
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
 
# Стратифицированная кросс-валидация - сохраняет пропорции классов
stratified_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
 
# Кросс-валидация для временных рядов
timeseries_split = TimeSeriesSplit(n_splits=5)
 
# Применяем выбранную стратегию в GridSearchCV
grid_search_stratified = GridSearchCV(
    estimator=svm_pipeline,
    param_grid=svm_param_grid,
    cv=stratified_kfold,  # Используем стратифицированную валидацию
    n_jobs=-1
)
Стратифицированная кросс-валидация особенно полезна для несбалансированных данных, где количество образцов в разных классах сильно отличается. Она гарантирует, что пропорции классов в каждом фолде примерно соответствуют их пропорциям в полном наборе данных. В нашем примере с винами эта стратегия может быть предпочтительней, если клессы распределены неравномерно. Однако просто выбрать правильную стратегию кросс-валидации недостаточно. Важно также оценить стабильность результатов. Часто мы получаем модель, которая показывает великолепные результаты на валидации, но "сыпется" на реальных данных. Это происходит из-за вариативности в оценках метрик между разными фолдами.

Python
1
2
3
4
5
6
7
8
9
10
11
# Получение детальных результатов валидации для анализа стабильности
cv_results = grid_search_stratified.cv_results_
 
# Вычисляем стандартное отклонение для лучшей модели
best_index = grid_search_stratified.best_index_
best_std = cv_results['std_test_score'][best_index]
best_mean = cv_results['mean_test_score'][best_index]
 
print(f"Среднее значение метрики: {best_mean:.4f}")
print(f"Стандартное отклонение: {best_std:.4f}")
print(f"Коэффициент вариации: {best_std/best_mean:.4f}")
Коэфициент вариации (отношение стандартного отклонения к среднему) дает представление о том, насколько стабильны результаты модели. Значения выше 0.1-0.15 могут свидетельствовать о нестабильности модели и ее потенциальных проблемах на новых данных.

Интересный подход, который я часто применяю в своей практике — комбинирование нескольких стратегий валидации. Например, можно выполнить первичный отбор моделей с помощью KFold, а затем провести более тщательную оценку финалистов через StratifiedKFold:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Первый этап: базовый отбор с KFold
initial_models = [
    ('svm', svm_pipeline),
    ('rf', RandomForestClassifier(random_state=42))
]
 
best_models = {}
for name, model in initial_models:
    if name == 'svm':
        param_grid = svm_param_grid
    else:
        param_grid = rf_param_dist
    
    search = RandomizedSearchCV(
        estimator=model, 
        param_distributions=param_grid,
        n_iter=20,
        cv=KFold(n_splits=3, shuffle=True),  # Более быстрая валидация
        n_jobs=-1
    )
    search.fit(X_train, y_train)
    best_models[name] = search.best_estimator_
 
# Второй этап: детальная оценка лучших моделей
final_scores = {}
for name, model in best_models.items():
    scores = cross_val_score(
        model, 
        X_train, y_train, 
        cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=42),
        scoring='accuracy'
    )
    final_scores[name] = (scores.mean(), scores.std())
    
# Выбираем лучшую модель с учетом стабильности
for name, (mean, std) in final_scores.items():
    print(f"{name}: {mean:.4f} ± {std:.4f}")
Такой "двухуровневый" подход к валидации экономит вычислительные ресурсы (первый этап быстрее) и одновременно дает более надежную финальную оценку (второй этап тщательнее).

Интеграция предобработки данных и поиска гиперпараметров через Pipeline



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

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

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest, f_classif
 
# Предположим, у нас есть данные с числовыми и категориальными признаками
# и мы знаем их индексы
numeric_features = [0, 1, 2, 3, 4, 5, 6, 7, 8]  # индексы числовых колонок
categorical_features = [9, 10, 11, 12]  # индексы категориальных колонок
 
# Создаем предобработчики для разных типов данных
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])
 
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])
 
# Объединяем их с помощью ColumnTransformer
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])
 
# Создаем полный пайплайн, включающий предобработку и модель
full_pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('feature_selection', SelectKBest(f_classif, k=5)),  # Выбор лучших признаков
    ('classifier', RandomForestClassifier())
])
Теперь самое интересное: мы можем настраивать не только гиперпараметры классификатора, но и параметры каждого этапа предобработки! Например, можно одновременно искать оптимальное количество признаков для отбора и глубину деревев в случайном лесе:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
# Параметры для поиска, включая этапы предобработки
param_grid = {
    'preprocessor__num__imputer__strategy': ['mean', 'median'],
    'feature_selection__k': [3, 5, 7, 'all'],  # 'all' - использовать все признаки
    'classifier__n_estimators': [50, 100, 200],
    'classifier__max_depth': [None, 10, 20]
}
 
# GridSearchCV найдет оптимальную комбинацию на всех уровнях
grid_search = GridSearchCV(
    full_pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=-1
)
grid_search.fit(X_train, y_train)
Такой интегрированный подход имеет несколько принципиальных преимуществ:

1. Устранение утечки данных: Все преобразования применяются только к обучающим данным в каждом фолде кросс-валидации, исключая влияние тестовых данных на процесс предобработки.
2. Согласованная оптимизация: Некоторые этапы предобработки могут взаимодействовать с параметрами модели неочевидным образом. Совместная оптимизация позволяет найти наилучшую комбинацию параметров на всех уровнях.
3. Простота воспроизведения: Весь процесс от сырых данных до финальной модели инкапсулирован в единый объект, что упрощает развертывание и повторное обучение.

Однако есть и подводные камни, о которых следует помнить. Основной — это экспоненциальный рост вычислительной сложности. Если у вас 5 параметров предобработки и 5 параметров модели с 3 вариантами каждый, то полная сетка уже содержит 3^10 = 59049 комбинаций! В таких случаях Random Search становится не просто предпочтительным, а практически единственным жизнеспособным вариантом.

Объединение всех компонентов в целостный рабочий процесс



Теперь, когда мы рассмотрели все ключевые элементы процесса настройки гиперпараметров, давайте соберем их воедино в целостный рабочий процесс, который можно применить к любой задаче классификации. Я называю такой подход "прогрессивный поиск с динамическим пайплайном":

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, RandomizedSearchCV, StratifiedKFold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import classification_report
import time
 
# Функция для создания модели с прогрессивным поиском
def progressive_hyperparameter_search(X, y, test_size=0.2, random_state=42):
    # Разделение на обучающую и тестовую выборки
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=test_size, random_state=random_state
    )
    
    # 1. Этап: Быстрая предварительная оценка моделей
    models = {
        'rf': RandomForestClassifier(random_state=random_state),
        'svm': SVC(random_state=random_state)
    }
    
    # Базовый пайплайн для начальной оценки
    base_pipeline = Pipeline([
        ('imputer', SimpleImputer(strategy='median')),
        ('scaler', StandardScaler()),
        ('model', None)  # Будет заменен на конкретную модель
    ])
    
    model_scores = {}
    for name, model in models.items():
        start_time = time.time()
        pipeline = base_pipeline.set_params(model=model)
        
        # Простая кросс-валидация для быстрой оценки
        cv_score = np.mean(cross_val_score(
            pipeline, X_train, y_train, cv=3, scoring='accuracy'
        ))
        elapsed = time.time() - start_time
        
        model_scores[name] = {
            'score': cv_score,
            'time': elapsed
        }
        print(f"Базовая оценка {name}: {cv_score:.4f} (за {elapsed:.2f} сек)")
    
    # Выбираем две лучшие модели по скору
    top_models = sorted(
        model_scores.items(), 
        key=lambda x: x[1]['score'], 
        reverse=True
    )[:2]
    
    # 2. Этап: Случайный поиск для лучших моделей с расширенным пайплайном
    best_model_info = None
    best_score = 0
    
    for model_name, _ in top_models:
        print(f"\nПрогрессивный поиск для {model_name}...")
        
        # Определяем параметры поиска в зависимости от типа модели
        if model_name == 'rf':
            model = RandomForestClassifier(random_state=random_state)
            param_dist = {
                'model__n_estimators': randint(50, 300),
                'model__max_depth': [None] + list(range(5, 30, 5)),
                'model__min_samples_split': randint(2, 20),
                'feature_sel__estimator__n_estimators': [100],  # Фиксированные параметры для отбора признаков
                'imputer__strategy': ['mean', 'median']
            }
        else:  # SVM
            model = SVC(probability=True, random_state=random_state)
            param_dist = {
                'model__C': loguniform(1e-1, 1e3),
                'model__gamma': loguniform(1e-3, 1e1),
                'model__kernel': ['rbf', 'linear'],
                'feature_sel__estimator__n_estimators': [100],
                'imputer__strategy': ['mean', 'median']
            }
        
        # Создаем расширенный пайплайн с отбором признаков
        feature_selector = SelectFromModel(
            RandomForestClassifier(random_state=random_state)
        )
        
        pipeline = Pipeline([
            ('imputer', SimpleImputer()),
            ('scaler', StandardScaler()),
            ('feature_sel', feature_selector),
            ('model', model)
        ])
        
        # Случайный поиск с стратифицированной кросс-валидацией
        search = RandomizedSearchCV(
            pipeline,
            param_distributions=param_dist,
            n_iter=30,  # Количество случайных комбинаций
            cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=random_state),
            scoring='accuracy',
            n_jobs=-1,
            random_state=random_state
        )
        
        search.fit(X_train, y_train)
        
        if search.best_score_ > best_score:
            best_score = search.best_score_
            best_model_info = {
                'name': model_name,
                'pipeline': search.best_estimator_,
                'params': search.best_params_,
                'score': search.best_score_
            }
    
    # 3. Этап: Финальная оценка на тестовых данных
    best_pipeline = best_model_info['pipeline']
    y_pred = best_pipeline.predict(X_test)
    
    print("\nЛучшая модель:", best_model_info['name'])
    print("Лучшие параметры:", best_model_info['params'])
    print("Валидационный скор:", best_model_info['score'])
    print("\nРезультаты на тестовой выборке:")
    print(classification_report(y_test, y_pred))
    
    return best_model_info
 
# Использование функции
from scipy.stats import randint, loguniform
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_wine
 
# Загружаем данные
wine = load_wine()
X, y = wine.data, wine.target
 
# Запускаем прогрессивный поиск
best_model = progressive_hyperparameter_search(X, y)
Этот подход объединяет всё, что мы обсуждали ранее: предварительную оценку моделей, интегрированный пайплайн предобработки, стратифицированную кросс-валидацию и Random Search для эффективного поиска гиперпараметров. Особенно хочу отметить элемент отбора признаков внутри пайплайна. Это частый источник "утечки данных" в проектах машинного обучения: многие инженеры выполняют отбор признаков на всем наборе данных перед разделением на обучающую и тестовую выборки, что приводит к переоцененным показателям производительности. В нашем подходе отбор выполняется независимо в каждом фолде кросс-валидации, что дает более реалистичную оценку.

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

Search in random list случайной длины, user = [input(*)] последовательность чисел, и вывести все места их расположения
Всем привет. Задача такая: Найти в сгенерированном списке случайной длины, вводимую пользователем...

Wxpython. wx.grid.Grid. фон таблицы
Можно как-то сделать, чтобы фон таблицы не вылезал за границы таблицы (см рис)? либо как-то сделать...

Вывести с помощью метода random случайное число, находящееся между числами a и b
Даны два числа, формата "a.n"(например 5.0) и "b.m"(например 6.9) Необходимо вывести с помощью...

Как отправлять данные на сервер с помощью Python и принимать их там с помощью php?
Здравствуйте, мне необходимо отправлять данные на сервер с помощью Python и принимать их там с...

Сетка изображений с помощью tkinter grid
Доброго времени суток! Хочу создать окно, в котором по желанию пользователя файлы изображений...

Модуль random в python
Как сгенерировать 4-значное рандомное число без повторяющихся элементов?

Python discord.py random
я сделал бота для дискорда который должен говорить правда или действие: from discord.ext import...

Python,random
У меня есть задание которое я не могу выполнить помогите пожалуйста: создайте класс Die Diсe с...

Автоматизация действий Python + Playwright, ошибка Search error: 'NoneType' object is not subscriptable
Эта часть кода, отвечающая за поведение, запускает профиль, печатает запрос в поисковой системе,...

Drag&drop в Grid c Grid.Row и Grid.Column
Написал код который тягает элемент, но мой XAML поделен на колонки и столбцы, что накладывает...

Возможно ли задать в XAML число строк и столбцов Grid сразу, без исп <Grid.ColumnDefinitions> и <Grid.RowDefinnitions>?
Возможно ли задать в XAML число строк и столбцов Grid сразу, без исп &lt;Grid.ColumnDefinitions&gt; и...

Как получить master key, client random, server random c SSL
Доброго времени суток, Собственно сабж, как получить значения master key, client random и server...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Популярные LM модели ориентированы на увеличение затрат ресурсов пользователями сгенерированного кода (грязь -заслуги чистоплюев).
Hrethgir 12.06.2025
Вообще обратил внимание, что они генерируют код (впрочем так-же ориентированы разработчики чипов даже), чтобы пользователь их использующий уходил в тот или иной убыток. Это достаточно опытные модели,. . .
Топ10 библиотек C для квантовых вычислений
bytestream 12.06.2025
Квантовые вычисления - это та область, где теория встречается с практикой на границе наших знаний о физике. Пока большая часть шума вокруг квантовых компьютеров крутится вокруг языков высокого уровня. . .
Dispose и Finalize в C#
stackOverflow 12.06.2025
Работая с C# больше десяти лет, я снова и снова наблюдаю одну и ту же историю: разработчики наивно полагаются на сборщик мусора, как на волшебную палочку, которая решит все проблемы с памятью. Да,. . .
Повышаем производительность игры на Unity 6 с GPU Resident Drawer
GameUnited 11.06.2025
Недавно копался в новых фичах Unity 6 и наткнулся на GPU Resident Drawer - штуку, которая заставила меня присвистнуть от удивления. По сути, это внутренний механизм рендеринга, который автоматически. . .
Множества в Python
py-thonny 11.06.2025
В Python существует множество структур данных, но иногда я сталкиваюсь с задачами, где ни списки, ни словари не дают оптимального решения. Часто это происходит, когда мне нужно быстро проверять. . .
Работа с ccache/sccache в рамках C++
Loafer 11.06.2025
Утилиты ccache и sccache занимаются тем, что кешируют промежуточные результаты компиляции, таким образом ускоряя последующие компиляции проекта. Это означает, что если проект будет компилироваться. . .
Настройка MTProxy
Loafer 11.06.2025
Дополнительная информация к инструкции по настройке MTProxy: Перед сборкой проекта необходимо добавить флаг -fcommon в конец переменной CFLAGS в Makefile. Через crontab -e добавить задачу: 0 3. . .
Изучаем Docker: что это, как использовать и как это работает
Mr. Docker 10.06.2025
Суть Docker проста - это платформа для разработки, доставки и запуска приложений в контейнерах. Контейнер, если говорить образно, это запечатанная коробка, в которой находится ваше приложение вместе. . .
Тип Record в C#
stackOverflow 10.06.2025
Многие годы я разрабатывал приложения на C#, используя классы для всего подряд - и мне это казалось естественным. Но со временем, особенно в крупных проектах, я стал замечать, что простые классы. . .
Разработка плагина для Minecraft
Javaican 09.06.2025
За годы существования Minecraft сформировалась сложная экосистема серверов. Оригинальный (ванильный) сервер не поддерживает плагины, поэтому сообщество разработало множество альтернатив. CraftBukkit. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru