Обучение моделей sklearn параллельно с joblib блокирует процесс

Как было предложено в этом ответе, я попытался использовать joblib для обучения нескольких scikit-learn параллельно.

import joblib
import numpy
from sklearn import tree, linear_model

classifierParams = {
                "Decision Tree": (tree.DecisionTreeClassifier, {}),''
                "Logistic Regression" : (linear_model.LogisticRegression, {})
}


XTrain = numpy.array([[1,2,3],[4,5,6]])
yTrain = numpy.array([0, 1])

def trainModel(name, clazz, params, XTrain, yTrain):
    print("training ", name)
    model = clazz(**params)
    model.fit(XTrain, yTrain)
    return model


joblib.Parallel(n_jobs=4)(joblib.delayed(trainModel)(name, clazz, params, XTrain, yTrain) for (name, (clazz, params)) in classifierParams.items())

Однако вызов последней строки занимает много времени без использования ЦП, на самом деле кажется, что он просто блокирует и никогда ничего не возвращает. В чем моя ошибка?

Тест с очень небольшим объемом данных в XTrain предполагает, что копирование массива numpy через несколько процессов не является причиной задержки.


person clstaudt    schedule 29.11.2017    source источник
comment
Модель имеет значение (в общей процедуре; некоторые из них уже параллельны, и вы только замедляете работу), и она не показана. Тогда также похоже, что вы не читали документы joblibs. Например. часть о разделяемой памяти - ›memmaps, которая, вероятно, дает здесь тонны копий.   -  person sascha    schedule 29.11.2017
comment
Некоторые модели sklearn позволяют напрямую передавать аргумент n_jobs. Например: sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, n_jobs=-1) При этом не будет обучаться несколько моделей одновременно, но будет обучена одна модель на нескольких ядрах, что потенциально ускорит вашу реализацию.   -  person collector    schedule 29.11.2017
comment
@sascha Как копирование объясняет заблокированный процесс без загрузки ЦП?   -  person clstaudt    schedule 29.11.2017
comment
Это базовая CS, правда? До завершения копирования работать не над чем. Если требуется частое копирование; ЦП голодает (ввод-вывод медленнее, чем ЦП). Если эти копии раздувают вашу оперативную память, происходит сбой, который влияет на то же самое. Что непонятно? (конечно тут догадки, но с таким редким и неподготовленным вопросом)   -  person sascha    schedule 29.11.2017
comment
@sascha С вашей стороны действительно много догадок (см. править). Могу я предложить вам немного отдохнуть от stackoverflow, чтобы остыть, немного поработать над своими навыками межличностного общения и позволить другим попробовать?   -  person clstaudt    schedule 29.11.2017
comment
Я понимаю. Меня сейчас критикуют за неправильные предположения, которые я сделал, прежде чем редактировать ваш вопрос, чтобы дать больше информации. Теперь использование небольших данных не совсем объясняет вашу блокировку, но небольшие данные также никогда не принесут вам ничего при таком крупномасштабном распараллеливании. Самая большая проблема с этим вопросом - это отсутствие деталей. Он не воспроизводится, не показывает, что распараллеливается, и информация об ОС также не приводится. Увидев эти проблемы, я снова догадываюсь: OS X.   -  person sascha    schedule 29.11.2017
comment
Не поймите меня неправильно, вас критикуют за неприятный, снисходительный и злой стиль письма. Другое дело, что техническое содержание ваших комментариев указывает неверное направление.   -  person clstaudt    schedule 29.11.2017
comment
Кажется, мои комментарии носят образовательный характер (см. Ваши правки). Ваш фрагмент работает на моей стороне. Итак, вы, вероятно, теперь знаете, что вам следует искать (после потенциальных обновлений для всех используемых библиотек): взаимодействие joblib-OS (проблемы с github!). По моему эмпирическому и субъективному опыту: OS X чаще всего страдает от этого.   -  person sascha    schedule 29.11.2017
comment
@collector, хотя ваше замечание является лексически правильным, вы упустили суть намерения clstaudt O / P, и ваш комментарий ничего не помог в направлении набора вопросов O / P. Одно и то же имя какого-либо атрибута не имеет ничего общего с наблюдаемой проблемой, с которой операционный директор попросил помочь.   -  person user3666197    schedule 29.11.2017
comment
@ user3666197 Поэтому это комментарий, а не ответ. Я также прямо указываю на эту разницу в своем комментарии.   -  person collector    schedule 29.11.2017
comment
@sasha Вы, конечно, были правы, потребовав более воспроизводимого кода в вопросе, но вместо того, чтобы просить об этом, вы показались злым и пренебрежительным, что раздражает и просто приводит к пустой трате нашего времени, прежде чем мы дойдем до сути проблема. Кстати, это ОС Windows.   -  person clstaudt    schedule 30.11.2017
comment
При всем уважении, сэр, измерили ли вы хотя бы один раунд фактических накладных расходов на создание экземпляров конструктора joblib.Parallel() перед тем, как проголосовать против на основании количественно подтвержденного разногласия, или это было актом чистая ненависть?   -  person user3666197    schedule 30.11.2017


Ответы (1)


У конвейеров машинного обучения производственного уровня загрузка ЦП примерно такая же, почти 24 часа в сутки, 7 дней в неделю, 365 дней в году:

введите описание изображения здесь

Проверьте показатели состояния как CPU%, так и других ресурсов на этом узле.


В чем моя ошибка?

Прочитав ваш профиль, это был потрясающий момент, сэр:

Я ученый-компьютерщик, специализирующийся на алгоритмах и данных. анализ по обучению и универсал по натуре. Мой набор навыков сочетает сильную научную подготовку с опытом в области архитектуры программного обеспечения и разработки, особенно решений для анализа больших данных < / сильный>. Я предлагаю услуги по консультированию и развитию, и мне нужны сложные проекты в области науки о данных.

Проблема ЯВЛЯЕТСЯ глубоко детерминированной уважением к элементарным правилам алгоритмов Computer Science +.

Проблема НЕ требует сильной научной подготовки, а требует здравого смысла.

Проблема НЕ в каких-либо особенно больших данных, но требует, чтобы понюхал, как все работает на самом деле.


введите описание изображения здесь

Факты
или
Эмоции? ... это вопрос! (Трагедия Гамлета, принца Дании)


Могу я быть честным? Давайте всегда отдавать предпочтение ФАКТАМ:

Шаг № 1:
Никогда не нанимайте и не увольняйте всех и каждого Консультанта, который не уважает факты (ответ, упомянутый выше, ничего не подсказывает, тем меньше обещает). Игнорирование фактов может быть "грехом успеха" в PR / MARCOM / рекламе / медиа-бизнесе (в случае, если Заказчик терпит такую ​​нечестность и / или привычку манипулировать), но не в научно справедливые количественные области. Это непростительно.

Шаг № 2:
Никогда не нанимайте и не увольняйте каждого консультанта, заявившего, что у него есть опыт в архитектуре программного обеспечения, особенно в решениях для ... больших данных , но не обращает внимания на совокупную сумму всех дополнительных накладных расходов, которые будут вноситься каждым из соответствующих элементов системной архитектуры, как только обработка начинает распределяться по некоторому пулу оборудования. и программные ресурсы. Это непростительно.

Шаг № 3:
Никогда не нанимайте и не увольняйте каждого консультанта, который становится пассивно-агрессивным, когда факты не соответствуют его / его желаниям, и начинает обвинять другого знающего человека, который уже оказал помощь. рука скорее "улучшить (свои) коммуникативные навыки" вместо того, чтобы учиться на ошибках. Конечно, умение может помочь выразить очевидные ошибки каким-либо другим способом, но гигантские ошибки останутся гигантскими ошибками, и каждый ученый, честно признавая свое научное звание, должен НИКОГДА прибегать к нападайте на помощника коллеги, а лучше начните искать причину ошибок, одну за другой. Этот ---

@sascha ... Могу я предложить вам сделать небольшой перерыв в stackoverflow, чтобы остыть, поработать немного над межличностным коммуникативные навыки

--- был не чем иным, как прямым и интеллектуально неприемлемым неприятным фолом для @sascha.


Далее, игрушки
Важные факты об архитектуре, ресурсах и планировании процессов:

Императивная форма синтаксического конструктора запускает огромное количество действий:

joblib.Parallel( n_jobs = <N> )( joblib.delayed( <aFunction> )
                                               ( <anOrderedSetOfFunParameters> )
                                           for ( <anOrderedSetOfIteratorParams> )
                                           in    <anIterator>
                                 )

Чтобы хотя бы догадаться, что происходит, научно справедливый подход заключался бы в тестировании нескольких репрезентативных случаев, сравнительном анализе их фактического выполнения, сборе количественно подтвержденных фактов и построении гипотезы о модели поведения и ее основных зависимостях от CPU_core-count, от размера RAM. , по <aFunction>-сложности и конвертам распределения ресурсов и т. д.

Тестовый пример A:

def a_NOP_FUN( aNeverConsumedPAR ):
    """                                                 __doc__
    The intent of this FUN() is indeed to do nothing at all,
                             so as to be able to benchmark
                             all the process-instantiation
                             add-on overhead costs.
    """
    pass

##############################################################
###  A NAIVE TEST BENCH
##############################################################
from zmq import Stopwatch; aClk = Stopwatch()
JOBS_TO_SPAWN =  4         # TUNE:  1,  2,  4,   5,  10, ..
RUNS_TO_RUN   = 10         # TUNE: 10, 20, 50, 100, 200, 500, 1000, ..
try:
     aClk.start()
     joblib.Parallel(  n_jobs = JOBS_TO_SPAWN
                      )( joblib.delayed( a_NOP_FUN )
                                       ( aSoFunPAR )
                                   for ( aSoFunPAR )
                                   in  range( RUNS_TO_RUN )
                         )
except:
     pass
finally:
     try:
         _ = aClk.stop()
     except:
         _ = -1
         pass
print( "CLK:: {0:_>24d} [us] @{1: >3d} run{2: >5d} RUNS".format( _,
                                                                 JOBS_TO_SPAWN,
                                                                 RUNS_TO_RUN
                                                                 )
        )

Собрав достаточно репрезентативные данные по этому случаю NOP на разумно масштабируемом 2D-ландшафте [ RUNS_TO_RUN, JOBS_TO_SPAWN]-декартовых пространств DataPoints, чтобы получить хотя бы некоторый опыт из первых рук о фактических системных затратах на запуск фактически пустых процессов » служебные нагрузки, связанные с императивно проинструктированным joblib.Parallel(...)( joblib.delayed(...) ) -синтаксическим конструктором, порождаются в системном планировщике всего несколько joblib управляемых a_NOP_FUN() экземпляров.

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

Таким образом, из этого простейшего случая последует научно обоснованная и тщательная работа, уже показывающая сравнительные затраты всех связанных накладных расходов на настройку наименьшего когда-либо joblib.Parallel() штрафа sine-qua-non в направлении, где реальные алгоритмы в реальном времени - лучшие с следующим добавлением все большего и большего размера "полезной нагрузки" в цикл тестирования:

Тестовый пример B:

def a_NOP_FUN_WITH_JUST_A_MEM_ALLOCATOR( aNeverConsumedPAR ):
    """                                                 __doc__
    The intent of this FUN() is to do nothing but
                             a MEM-allocation
                             so as to be able to benchmark
                             all the process-instantiation
                             add-on overhead costs.
    """
    import numpy as np              # yes, deferred import, libs do defer imports
    SIZE1D    = 1000                # here, feel free to be as keen as needed
    aMemALLOC = np.zeros( ( SIZE1D, #       so as to set
                            SIZE1D, #       realistic ceilings
                            SIZE1D, #       as how big the "Big Data"
                            SIZE1D  #       may indeed grow into
                            ),
                          dtype = np.float64,
                          order = 'F'
                          )         # .ALLOC + .SET
    aMemALLOC[2,3,4,5] = 8.7654321  # .SET
    aMemALLOC[3,3,4,5] = 1.2345678  # .SET

    return aMemALLOC[2:3,3,4,5]

Опять же,
собирает достаточно репрезентативные количественные данные о затратах на фактическое выделение MEM удаленных процессов, выполнив a_NOP_FUN_WITH_JUST_A_MEM_ALLOCATOR() некоторый разумно широкий ландшафт SIZE1D -масштабирования,
снова
над разумно масштабируемым 2D-ландшафтом из [ RUNS_TO_RUN, JOBS_TO_SPAWN] декартовых пространств DataPoints, чтобы коснуться нового измерения в масштабировании производительности, в рамках расширенного эксперимента PROCESS_under_TEST внутри инструмента joblib.Parallel() с черным ящиком, оставляя его магию еще нераскрытой.

Тестовый пример C:

def a_NOP_FUN_WITH_SOME_MEM_DATAFLOW( aNeverConsumedPAR ):
    """                                                 __doc__
    The intent of this FUN() is to do nothing but
                             a MEM-allocation plus some Data MOVs
                             so as to be able to benchmark
                             all the process-instantiation + MEM OPs
                             add-on overhead costs.
    """
    import numpy as np              # yes, deferred import, libs do defer imports
    SIZE1D    = 1000                # here, feel free to be as keen as needed
    aMemALLOC = np.ones(  ( SIZE1D, #       so as to set
                            SIZE1D, #       realistic ceilings
                            SIZE1D, #       as how big the "Big Data"
                            SIZE1D  #       may indeed grow into
                            ),
                          dtype = np.float64,
                          order = 'F'
                          )         # .ALLOC + .SET
    aMemALLOC[2,3,4,5] = 8.7654321  # .SET
    aMemALLOC[3,3,4,5] = 1.2345678  # .SET

    aMemALLOC[:,:,:,:]*= 0.1234567
    aMemALLOC[:,3,4,:]+= aMemALLOC[4,5,:,:]
    aMemALLOC[2,:,4,:]+= aMemALLOC[:,5,6,:]
    aMemALLOC[3,3,:,:]+= aMemALLOC[:,:,6,7]
    aMemALLOC[:,3,:,5]+= aMemALLOC[4,:,:,7]

    return aMemALLOC[2:3,3,4,5]

Бац, проблемы, связанные с архитектурой, постепенно начинают проявляться:

Вскоре можно заметить, что не только статический размер имеет значение, но и MEM-транспорт BANDWIDTH (аппаратно-аппаратный) начнет вызывать проблемы, так как перемещение данных из / в ЦП в / из MEM стоит ну ~ 100 .. 300 [ns], намного больше, чем любая умная перетасовка нескольких байтов «внутри» CPU_core, {CPU_core_private | CPU_core_shared | CPU_die_shared} -cache только иерархия-архитектура (и любая нелокальная передача NUMA вызывает дополнительные проблемы того же порядка).

введите описание изображения здесь

Все вышеперечисленные тестовые примеры еще не потребовали больших усилий от процессора.

Итак, начнем жечь масло!

Если все вышеперечисленное подходит для того, чтобы начать чувствовать запах того, как на самом деле работают вещи под капотом, он станет уродливым и грязным.

Тестовый пример D:

def a_CPU_1_CORE_BURNER_FUN( aNeverConsumedPAR ):
    """                                                 __doc__
    The intent of this FUN() is to do nothing but
                             add some CPU-load
                             to a MEM-allocation plus some Data MOVs
                             so as to be able to benchmark
                             all the process-instantiation + MEM OPs
                             add-on overhead costs.
    """
    import numpy as np              # yes, deferred import, libs do defer imports
    SIZE1D    = 1000                # here, feel free to be as keen as needed
    aMemALLOC = np.ones(  ( SIZE1D, #       so as to set
                            SIZE1D, #       realistic ceilings
                            SIZE1D, #       as how big the "Big Data"
                            SIZE1D  #       may indeed grow into
                            ),
                          dtype = np.float64,
                          order = 'F'
                          )         # .ALLOC + .SET
    aMemALLOC[2,3,4,5] = 8.7654321  # .SET
    aMemALLOC[3,3,4,5] = 1.2345678  # .SET

    aMemALLOC[:,:,:,:]*= 0.1234567
    aMemALLOC[:,3,4,:]+= aMemALLOC[4,5,:,:]
    aMemALLOC[2,:,4,:]+= aMemALLOC[:,5,6,:]
    aMemALLOC[3,3,:,:]+= aMemALLOC[:,:,6,7]
    aMemALLOC[:,3,:,5]+= aMemALLOC[4,:,:,7]

    aMemALLOC[:,:,:,:]+= int( [ np.math.factorial( x + aMemALLOC[-1,-1,-1] )
                                               for x in range( 1005 )
                                ][-1]
                            / [ np.math.factorial( y + aMemALLOC[ 1, 1, 1] )
                                               for y in range( 1000 )
                                ][-1]
                              )

    return aMemALLOC[2:3,3,4,5]

введите описание изображения здесь

По-прежнему ничего экстраординарного по сравнению с обычным уровнем полезной нагрузки в области многомерного пространства машинного обучения, где все измерения { aMlModelSPACE, aSetOfHyperParameterSPACE, aDataSET } -пространства состояний влияют на объем требуемой обработки (некоторые имеющий O( N ), некоторую другую O( N.logN ) сложность), где почти сразу, где хорошо спроектировано, вскоре задействуется больше, чем одно ядро ​​CPU_core, даже при выполнении одного "задания".

Поистине неприятный запах начинается, когда наивные (чтение ресурсов-использование нескоординировано) смеси загрузки ЦП переходят в путь и когда смеси связанных с задачами загрузки ЦП начинают смешиваться с наивными (чтение ресурсов-использование несогласованно) Процессы O / S-планировщика вынуждены бороться за общие (прибегающие к простой политике совместного использования) ресурсы - то есть MEM (введение SWAP как HELL), CPU (введение промахов кэша и повторной выборки MEM (да, с SWAP) добавлены штрафы), не говоря уже об уплате каких-либо сборов за задержку, превышающих ~ 15+ [ms], если кто-то забывает и позволяет процессу коснуться fileIO - (5 ( !) - на порядки медленнее + общее + чистое-[SERIAL] по своей природе устройство. Никакие молитвы здесь не помогут (SSD включен, всего на несколько порядков меньше, но все равно ад & запускать устройство невероятно быстро в его могилу износа + слезы).


Что произойдет, если все порожденные процессы не поместятся в физическую оперативную память?

Подкачка виртуальной памяти и свопы начинают буквально портить остальную часть, пока что как-то «просто» - по совпадению- (читай: слабо скоординированная) - [CONCURRENTLY] -планированная обработка (читай: далее- снижение индивидуальных показателей ПРОЦЕССА в рамках ТЕСТИРОВАНИЯ).


Все может так скоро пойти навстречу хаосу, если не будет находиться под должным контролем и надзором.

Опять же - факт имеет значение: облегченный класс монитора ресурсов может помочь:

aResRECORDER.show_usage_since0() method returns:

ResCONSUMED[T0+   166036.311 (           0.000000)]
user=               2475.15
nice=                  0.36
iowait=                0.29
irq=                   0.00
softirq=               8.32
stolen_from_VM=       26.95
guest_VM_served=       0.00

Точно так же немного более богатый сконструированный монитор ресурсов может сообщать о более широком контексте O / S, чтобы увидеть, где дополнительные условия кражи ресурсов / конкуренции / гонки ухудшают фактически достигнутый поток процесса:

>>> psutil.Process( os.getpid()
                    ).memory_full_info()
                                      ( rss          =       9428992,
                                        vms          =     158584832,
                                        shared       =       3297280,
                                        text         =       2322432,
                                        lib          =             0,
                                        data         =       5877760,
                                        dirty        =             0
                                        )
           .virtual_memory()
                          (             total        =   25111490560,
                                        available    =   24661327872,
                                        percent      =             1.8,
                                        used         =    1569603584,
                                        free         =   23541886976,
                                        active       =     579739648,
                                        inactive     =     588615680,
                                        buffers      =             0,
                                        cached       =    1119440896
                                        )
           .swap_memory()
                       (                total        =    8455712768,
                                        used         =     967577600,
                                        free         =    7488135168,
                                        percent      =            11.4,
                                        sin          =  500625227776,
                                        sout         =  370585448448
                                        )

Wed Oct 19 03:26:06 2017
        166.445 ___VMS______________Virtual Memory Size  MB
         10.406 ___RES____Resident Set Size non-swapped  MB
          2.215 ___TRS________Code in Text Resident Set  MB
         14.738 ___DRS________________Data Resident Set  MB
          3.305 ___SHR_______________Potentially Shared  MB
          0.000 ___LIB_______________Shared Memory Size  MB
                __________________Number of dirty pages           0x

И последнее, но не менее важное: почему можно легко заплатить больше, чем заработать взамен?

Помимо постепенно накапливаемых свидетельств того, как накладные расходы на реальное развертывание системы накапливают затраты, недавно повторно сформулированный закон Амдала, расширенный таким образом, чтобы покрыть как дополнительные накладные расходы, так и "атомарность процесса" определения размеров других неделимых частей, определяет максимальный порог дополнительных затрат, это может быть разумно оплаченным, если некоторая распределенная обработка должна обеспечить любое вышеупомянутое >= 1.00 ускорение вычислительного процесса.

Несоблюдение явной логики переформулированного закона Амдала приводит к тому, что процесс протекает хуже, чем если бы он был обработан с использованием чистого [SERIAL] планирования процесса (а иногда и из-за плохого проектирования и / или практика операций может выглядеть так, как если бы это был случай, когда метод joblib.Parallel()( joblib.delayed(...) ) «блокирует процесс»).

person user3666197    schedule 29.11.2017
comment
@sascha Как вы заметили, у joblib возникают проблемы с OSX v?.?, может быть интересен опыт команды CRAY-Chapel с внутренним компилятором по умолчанию до OSX 10.12+ (повторно компиляция из исходного кода на 10.12+ и clang 9 перестали проявлять действительно странные проблемы с производительностью, так что, если это также может помочь сообществу joblib, тем лучше). Подробности см. в ››› stackoverflow.com/q/47561759/3666197 - person user3666197; 30.11.2017
comment
Означает ли определение в The problem IS deeply determined by a respect to elementary Computer Science + algorithm rules что-то вроде решенного? - person ; 29.08.2019