← Все заметки

Почему permutation importance для LightGBM считается долго и как ускорить её, не меняя результат

· English version

Permutation importance задаёт обученной модели прямой вопрос: насколько хуже она работает, если перемешать один признак? В scikit-learn это один вызов, sklearn.inspection.permutation_importance, и на модели LightGBM он часто оказывается самым долгим шагом анализа. Почти всё его время уходит на одно действие. Одни настройки меняют только это время и оставляют каждый бит результата прежним, другие быстрее потому, что считают уже другое. Ниже — замеры обоих видов и скрипт, чтобы их повторить.

Куда уходит время

permutation_importance(model, X, y, n_repeats=R) один раз оценивает модель на проверочной выборке. Потом для каждого из F признаков и каждого из R повторов перемешивает столбец этого признака и оценивает модель заново. Каждая оценка — полное предсказание по всем N строкам, а каждое предсказание проходит каждое из T деревьев от корня до листа для каждой строки:

(1 + F × R) вызовов predict × N строк × T деревьев

Наш пример из pi_cost.py — LGBMClassifier из 300 деревьев до 63 листьев, обученный на синтетических данных (make_classification). Проверочная выборка — 10 000 строк × 50 признаков, n_repeats=5, scoring="roc_auc". Это 251 вызов predict и 753 млн проходов по деревьям. Один predict_proba по 10 000 строк занял 234 мс на одном ядре AMD Threadripper PRO 5975WX (Zen 3) и 179 мс на одном ядре AMD Ryzen 9 7950X3D (Zen 4). 251 × 234 мс — это 58,8 с, а весь вызов занял 59,2 с: на предсказания уходит около 99% времени, на перемешивание, копирование и подсчёт метрики — остальное.

Значит, время пропорционально каждому из четырёх множителей. К модели относятся только деревья; если уменьшить F, R или N, изменится то, что измеряется.

Тот же результат за меньшее время

Все настройки в этой таблице дали побитово одинаковые важности: один и тот же SHA-256 всего массива признаки × повторы, на обеих машинах. Время — медиана трёх запусков вызова permutation_importance; процесс ограничен 8 процессорами на машине с Zen 3 и 4 на машине с Zen 4.

Настройка Zen 3, 8 CPU Zen 4, 4 CPU
LightGBM в одном потоке (n_jobs=1 у модели), вход pandas 59,6 с 46,0 с
то же с массивом numpy 59,2 с 45,9 с
как есть по умолчанию: вход pandas, потоки LightGBM по умолчанию 9,1 с 13,0 с
LightGBM на всех CPU, вход numpy 8,6 с 12,5 с
permutation_importance(n_jobs=<все CPU>), LightGBM в одном потоке на воркер 10,4 с 13,8 с
n_jobs=2, LightGBM на половине CPU в каждом воркере 10,4 с 14,5 с
n_jobs=<все CPU> и потоки LightGBM по умолчанию 11,6 с 15,2 с

Задействуйте каждое ядро, но один раз. LightGBM предсказывает строки параллельно. В его API для scikit-learn по умолчанию (n_jobs=None) берётся число физических ядер или число доступных процессу CPU, если их меньше, так что вызов «как есть» уже их использует: 9,1 с вместо 59,6 с. Если модель создана с n_jobs=1, что часто бывает в коде кросс-валидации, permutation importance идёт на одном ядре. Верните значение model.set_params(n_jobs=None) (по умолчанию в LightGBM) или числом ядер перед вызовом.

Потоки или процессы, но не то и другое сразу. permutation_importance(n_jobs=k) распределяет признаки по k процессам-воркерам, а LightGBM в каждом воркере всё равно запускает свои потоки. Если n_jobs равен числу CPU, а у LightGBM настройки по умолчанию, получается 64 потока на 8 CPU: 11,6 с, на 36% дольше, чем если отдать CPU одному LightGBM. Если распараллеливаете по признакам, дайте модели один поток на воркер (n_jobs=1) или следите, чтобы воркеры × потоки равнялись числу CPU. На этих машинах собственные потоки LightGBM оказались быстрее процессов (8,6 с против 10,4 с и 12,5 с против 13,8 с). 50 признаков не делятся поровну на 8 воркеров, и в последнем круге часть из них простаивает; кроме того, время уходит на запуск воркеров и передачу им модели и данных. Что выиграет у вас, зависит от числа строк и признаков, поэтому замерьте оба варианта на своих данных.

Почему результат не меняется. scikit-learn берёт из random_state одно зерно и перемешивает по нему каждый признак, каким бы ни был n_jobs. LightGBM считает предсказание каждой строки в одном потоке и складывает деревья в одном и том же порядке при любом числе потоков. Задайте random_state целым числом: при значении по умолчанию None каждый запуск перемешивает по-своему, и два запуска всё равно не совпадут.

pandas почти ничего не стоит, пока предсказание дорогое. LightGBM переводит DataFrame в массив numpy при каждом вызове predict, здесь 251 раз. При 300 деревьях в одном потоке это 0,6% времени (59,6 с против 59,2 с); при LightGBM на всех CPU — 6% на Zen 3 (9,1 с против 8,6 с) и 4% на Zen 4. Для модели из 30 деревьев на 8 CPU с DataFrame вышло 1,75 с, с массивом numpy — 1,40 с. Передача X.to_numpy() дала здесь тот же результат: все столбцы числовые и стоят в том же порядке, что при обучении. Если есть категориальные столбцы pandas, оставьте DataFrame: LightGBM сам сопоставляет категории.

Быстрее, но результат другой

Настройка (воркеры на всех CPU, LightGBM в одном потоке в каждом) Zen 3 Zen 4 Результат
все строки, 5 повторов (эталон из таблицы выше) 10,4 с 13,8 с эталон
max_samples=0.5 6,1 с 8,0 с другой
n_repeats=2 5,3 с 6,8 с другой
вход, приведённый к float32 10,3 с 13,9 с здесь тот же, но это не гарантировано; не быстрее

Для беглого взгляда это может быть правильным выбором. Просто числа будут другие.

Более быстрая библиотека с теми же результатами: lightgbm-takt

lightgbm-takt — наша бесплатная сборка LightGBM 4.7.0 для Linux x86-64. Это официальный пакет Python с заменённой библиотекой, поэтому import lightgbm и API остаются прежними:

pip uninstall -y lightgbm && pip install lightgbm-takt

Её предсказания на той же машине совпадают с официальным пакетом бит в бит. Бесплатный пакет ускоряет однопоточные анализы с повторными вызовами — permutation importance, частичную зависимость, сценарии «что если» — на моделях до 300 деревьев. Замеры из нашей витрины LightGBM: весь процесс Python на одном ядре, множитель по тактам процессора относительно официального пакета 4.7.0:

Нагрузка Бесплатно, Zen 3 Бесплатно, Zen 4 Полная версия, Zen 3 Полная версия, Zen 4
Permutation importance, 158 количественных признаков, классификатор 3,30× 2,74× 16,75× 18,35×
Permutation importance, OpenML kick (категории, пропуски) 2,63× 2,82× 12,97× 14,64×
Частичная зависимость + ICE, 20 признаков 3,67× 3,12× 16,77× 18,31×
Сценарии «что если» (30) 2,50× 2,60× — —

«Бесплатно» измерено на моделях из 300 деревьев — это предел бесплатного пакета. «Полная версия» — коммерческая лицензия, измерена на моделях из 1 000 деревьев (количественные признаки) и 500 деревьев (kick); модели такого размера бесплатный пакет считает с официальной скоростью.

На нагрузке из этой заметки, с тем же хешем результата, что у официального пакета, в каждом запуске:

Настройка Zen 3, официальный Zen 3, lightgbm-takt Zen 4, официальный Zen 4, lightgbm-takt
одно ядро 59,2 с 22,7 с 45,9 с 18,0 с
n_jobs=<все CPU>, LightGBM в одном потоке в каждом 10,4 с 7,8 с 13,8 с 10,2 с
LightGBM на всех CPU 8,6 с 9,4 с 12,5 с 10,9 с
как есть по умолчанию (pandas, потоки по умолчанию) 9,1 с 9,2 с 13,0 с 11,0 с

С бесплатным пакетом используйте процессы-воркеры (n_jobs у permutation_importance) и один поток LightGBM на воркер: на обеих машинах это была самая быстрая настройка без изменения результата. С собственными потоками LightGBM чередующиеся запуски дали разную картину: на Zen 4 бесплатный пакет был быстрее официального пакета (медиана 10,96 с против 12,41 с), на Zen 3 — медленнее (9,35 с против 8,66 с).

Запустите сами

python -m venv a && a/bin/pip install lightgbm==4.7.0 scikit-learn pandas
a/bin/python pi_cost.py prep work      # данные и модель, около 2 с
a/bin/python pi_cost.py count work     # число вызовов predict и время одного вызова
a/bin/python pi_cost.py all work 3     # все настройки по 3 запуска, около 12 минут на 8 CPU

Чтобы закрепить процессоры, добавьте перед командами taskset -c 0-7 (или другой список CPU). Чтобы проверить lightgbm-takt, поставьте его во второе окружение и запустите там pi_cost.py all work 3; одинаковые хеши означают одинаковые результаты. Наши замеры — в zen3-official.txt, zen4-official.txt, zen3-lightgbm-takt.txt и zen4-lightgbm-takt.txt.

Измерено 12 октября 2026 года: LightGBM 4.7.0, scikit-learn 1.9.1, numpy 2.5.4, pandas 3.0.6, joblib 1.6.0, Python 3.12. Zen 3: Ubuntu 24.04, 8 CPU на 8 разных ядрах. Zen 4: Debian 13, 4 ядра.

Раскрытие: TAKT продаёт ускоренные сборки библиотек вроде LightGBM, вывод которых на той же системе совпадает с оригиналом бит в бит; см. пакет LightGBM и lightgbm-takt на PyPI.

Заметка подготовлена с AI-ассистентом по нашим измерениям; скрипты запускались на Linux, как описано выше. Исправления присылайте на [email protected].

Все заметки · Новые заметки: Telegram · RSS (на английском)

Обсудить заметку на GitHub. Комментарии — в открытом обсуждении GitHub Discussions. Ссылка ведёт на GitHub, где действуют его условия и политика конфиденциальности; читать можно без аккаунта, писать — с аккаунтом GitHub.

Исходный текст: lightgbm-permutation-importance.ru.md