Параллельное обучение scikit-learn: выбрать один уровень n_jobs

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

Назначить владельца вычислительного бюджета

n_jobs управляет тем параллелизмом, который реализован через joblib у конкретного estimator. Дополнительно численные библиотеки могут запускать собственные потоки. Поэтому n_jobs=-1 во всех местах не задаёт согласованный общий предел. Сначала выберите внешний уровень: независимые модели, части поиска или отдельные деревья одного леса.

В примере владельцем бюджета будет внешний Parallel с двумя процессами. Каждый RandomForest использует n_jobs=1, а parallel_config ограничивает поддерживаемые внутренние пулы потоков в рабочих процессах loky. Такой план проще объяснить: одновременно обучаются не более двух кандидатов, у каждого нет дополнительного joblib-пула деревьев.

Вернуть компактный результат из работника

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

Пример проверяет выполнение двух реальных обучений с разными параметрами. Он не замеряет ускорение: число доступных ядер и объём данных у читателя другие. После выбора корректного бюджета измеряйте полное время и пиковую память своего процесса, включая создание работников и передачу входа.

from joblib import Parallel, delayed, parallel_config
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier

X, y = load_iris(return_X_y=True)
def train(depth):
    model = RandomForestClassifier(n_estimators=8, max_depth=depth,
                                    n_jobs=1, random_state=19).fit(X, y)
    return {"depth": depth, "trees": len(model.estimators_),
            "predictions": model.predict(X[:3]).tolist()}

if __name__ == "__main__":
    with parallel_config(backend="loky", n_jobs=2, inner_max_num_threads=1):
        results = Parallel()(delayed(train)(depth) for depth in [2, 4])
    assert [item["depth"] for item in results] == [2, 4]
    assert all(item["trees"] == 8 for item in results)
    assert all(len(item["predictions"]) == 3 for item in results)
    print(results)

Когда лучше параллелить внутри модели

Если обучается один крупный лес, внешний пул может быть не нужен: выделите бюджет n_jobs самому лесу. Если кандидатов много и они небольшие, внешний уровень часто удобнее. Сравнивайте эти планы на одинаковом задании; число работников не является универсальной мерой скорости.

Учитывайте память входа и моделей. Даже при совместном отображении больших массивов каждый работник создаёт собственные промежуточные объекты. Ограничение потоков помогает CPU, но не превращает четыре больших estimator в один по памяти. Для тяжёлых запусков уменьшите число одновременно живущих кандидатов.

Закрепить базовую операцию

В открытом уроке сначала выполните один fit и predict. Когда отдельный запуск понятен и воспроизводим, переходите к управляемому параллельному выполнению нескольких таких запусков. Откройте урок «Первый fit и predict на своих данных». Затем вернитесь к сценарию статьи и проверьте его на своём небольшом наборе. Посмотрите программу курса первой ML-модели, чтобы пройти путь от обучения к использованию результата.

Продолжить на практике