【问题标题】:Training sklearn models in parallel with joblib blocks the process与 joblib 并行训练 sklearn 模型会阻止该过程
【发布时间】:2018-05-13 02:34:03
【问题描述】:

按照this answer 的建议,我尝试使用 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())

但是,对最后一行的调用需要很长时间而不使用 CPU,实际上它似乎只是阻塞并且从不返回任何内容。我的错误是什么?

XTrain 中使用非常少量数据进行的测试表明,跨多个进程复制 numpy 数组并不是延迟的原因。

【问题讨论】:

  • 一些 sklearn 模型允许您直接传递 n_jobs 参数。例如:sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, n_jobs=-1) 这不会同时训练多个模型,但它会跨多个内核训练一个模型,从而可能加快您的实施速度。
  • 这是基本的 CS,对吧?在复制完成之前,没有什么可做的。如果需要频繁复制; CPU 饿死了(IO slower 比 CPU)。如果这些副本使您的 RAM 膨胀,则会发生颠簸,效果相同。有什么不清楚的? (当然这里只是猜测,但问题如此稀疏和毫无准备)
  • 我明白了。在编辑您的问题以提供更多信息之前,我现在因做出错误的假设而受到批评。现在使用小数据并不能真正解释你的阻塞,但小数据也永远不会在这种粗粒度的并行化中为你带来任何好处。这个问题最大的问题是缺少细节。它不可重现,没有显示并行化的内容,也没有给出操作系统信息。看到这些问题我会再次猜测:OS X。
  • 好吧,看来我的 cmets 很有教育意义(看到你的编辑)。你的 sn-p 对我有用。所以你现在可能知道你应该寻找什么(在所有使用的库的潜在更新之后):joblib-OS 交互(github 问题!)。根据我的经验和主观经验:OS X 更常受此影响。
  • @user3666197 这就是为什么它是评论而不是答案的原因。我还在评论中明确说明了这种差异。

标签: python machine-learning parallel-processing scikit-learn joblib


【解决方案1】:

生产级机器学习管道的 CPU 利用率更像这样,几乎是 24 / 7 / 365:

检查CPU% 以及该节点上其他资源的状态数据。


我的错误是什么?

先生,阅读您的个人资料是一个令人惊叹的时刻:

I am a computer scientist specializing on algorithms and data analysis by training, and a generalist by nature. My skill set combines a strong scientific background with experience in software architecture and development, especially on solutions for the analysis of big data. I offer consulting and development services and I am looking for challenging projects in the area of data science.

问题深深地由尊重基本计算机科学+算法规则决定。

问题不是要求强大的科学背景,而是常识。

问题不是任何尤其是大数据,而是需要了解事情的实际运作方式。


事实
还是
情绪? ... 这就是问题所在! (丹麦王子哈姆雷特的悲剧)


我可以说实话吗?让我们更喜欢 FACTS,永远:

第 1 步
永远不要直接雇佣或解雇每一个不尊重事实的顾问(上面提到的答案没有提出任何建议,承诺的承诺越少)。忽略事实可能是 PR / MARCOM / 广告 / 媒体业务中的“成功之罪(以防客户容忍此类不诚实和/或操纵习惯),但在一个科学公平的定量领域。 这是不可原谅的。

第 2 步
永远不要直接雇佣或解雇每一位声称在软件架构方面拥有经验的顾问,尤其是...一旦处理开始分布在某个硬件和软件资源池中,系统架构的每个相应元素将引入的所有附加开销成本。这是不可原谅的。

第 3 步
永远不要直接雇佣或解雇每一位顾问,一旦事实不符合她/他的意愿,他们就会变得被动攻击,并开始指责其他已经伸出援助之手的知识渊博的人,而不是“改进(他们的) 沟通技巧”,而不是从错误中学习。当然,技巧可能有助于以其他方式表达明显的错误,但是,巨大的错误将仍然是巨大的错误,每个科学家,公平对待她/他的科学头衔,都应该绝不诉诸攻击帮助的同事,而是一个接一个地开始寻找错误的根本原因。这---

@sascha ... May I suggest you take little a break from stackoverflow to cool off, work a little on your interpersonal communication skills

--- 对@sascha 的直接和理智上无法接受的恶意犯规。


接下来是玩具
重要的架构、资源和流程调度事实:

syntax-constructor 的命令式形式会启动大量活动

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

为了至少猜测会发生什么,科学上公平的方法是测试几个代表性案例,对其实际执行进行基准测试,收集定量支持的事实,并就行为模型及其对 CPU_core-count 的主要依赖关系提出假设,on RAM 大小,&lt;aFunction&gt;-complexity 和资源分配信封等。

测试用例 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
                                                                 )
        )

已经在[ RUNS_TO_RUN, JOBS_TO_SPAWN]-cartesian-space DataPoints 的合理缩放的 2D 环境中收集了有关此 NOP 案例的足够代表性数据,以便至少产生一些关于启动实际系统成本的第一手经验本质上为空进程的开销工作负载,与命令式指示的 joblib.Parallel(...)( joblib.delayed(...) )-syntax 构造函数相关,仅在系统调度程序中产生了一些 joblib-managed a_NOP_FUN() 实例。

我们也同意,所有现实世界的问题,包括机器学习模型,都是比刚刚测试的 a_NOP_FUN() 更复杂的工具,而在这两种情况下,您都必须支付已经对间接成本进行了基准测试(即使它是为获得几乎为零的产品而支付的)。

因此,一个科学公平、严谨的工作将从这个最简单的案例开始,已经显示了所有相关设置开销的基准成本有史以来最小的joblib.Parallel()正弦惩罚转发朝着现实世界算法生存的方向 - 最佳接下来将一些越来越大的“有效负载”-尺寸添加到测试循环中:

测试用例 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]

再次,
通过在 SIZE1D 的一些合理广泛的范围内运行a_NOP_FUN_WITH_JUST_A_MEM_ALLOCATOR() 来收集有关实际远程进程 MEM 分配成本的具有代表性的定量数据,
再次
[ RUNS_TO_RUN, JOBS_TO_SPAWN]-cartesian-space DataPoints 的合理缩放的 2D 环境上,以便在joblib.Parallel() 工具内的扩展黑盒 PROCESS_under_TEST 实验下触及性能缩放的新维度,让它的魔力尚未消失-打开。

测试用例 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(硬件硬连线)也会开始导致问题,因为将数据从/到 CPU 移动到/来自MEM的成本很高~ 100 .. 300 [ns],比CPU_core“内部”的几个字节的任何智能改组要高得多,{ CPU_core_private | CPU_core_shared | CPU_die_shared }-cache hierarchy-architecture 单独(和任何非本地 NUMA 传输都表现出相同数量级的附加痛苦)。

上述所有测试用例都没有要求 CPU 付出太多努力

所以让我们开始烧油吧!

如果以上所有内容都可以开始闻到引擎盖下的东西实际上是如何工作的,那么这将变得丑陋和肮脏。

测试用例 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 }-state-space 的所有维度都会影响所需的处理(一些具有O( N ),另一些具有O( N.logN ) 复杂性),其中几乎立即,其中精心设计即使在单个“工作”上,也很快会使用多个CPU_core -正在运行。

一旦天真的(读取资源使用不协调)CPU负载混合开始,并且当与任务相关的CPU负载混合开始与天真的混合(读取资源使用un-coordinated ) O/S-scheduler 进程碰巧在争夺公共资源(仅采用幼稚的共享使用策略)资源 - 即 MEM(将 SWAP 作为 HELL 引入)、CPU(引入缓存未命中和 MEM 重新获取(是的) , 加上 SWAPs 惩罚), 不是说支付任何超过 ~ 15+ [ms] 延迟费用, 如果一个人忘记并让一个过程触摸 fileIO -( 5 (!)-orders-of-magnitude Slow + 共享 + 是纯粹的-[SERIAL],本质上)-设备。这里没有祈祷帮助(包括 SSD,只有几个订单数量级更小,但仍然难以分享和运行设备以令人难以置信的速度进入磨损严重的状态)。


如果所有派生的进程都不适合物理 RAM,会发生什么?

虚拟内存分页和交换开始从字面上恶化到目前为止的其余部分“恰好”-巧合-(阅读:弱协调)-[CONCURRENTLY]-调度处理(阅读:进一步降低了单独的 PROCESS-under-TEST 性能)。


如果没有得到应有的控制和监督,事情可能很快就会遭到破坏。

再一次 - 事实很重要:轻量级资源监视器类可能会有所帮助:

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

最后但并非最不重要的一点是,为什么一个人可以轻松地付出多于收入的回报?

除了逐渐建立的证据记录之外,现实世界的系统部署附加开销如何累积成本,recently re-formulated Amdahl's Law, extended so as to cover both the add-on overhead-costs plus the "process-atomicity" of the further indivisible parts' sizing, defines 最大附加成本阈值,如果某些分布式处理是合理的支付提供上述任何&gt;= 1.00计算进程加速

不遵守重新制定的阿姆达尔定律的明确逻辑会导致流程进行得比在纯-[SERIAL] 流程调度中处理时更糟糕(有时结果当 joblib.Parallel()( joblib.delayed(...) ) 方法 “阻塞进程” 时,糟糕的设计和/或操作实践可能看起来像是一个案例。

【讨论】:

  • @sascha 正如您所指出的,joblib 在 OSX v?.? 上存在问题,CRAY-Chapel 团队在默认后端编译器上的经验可能会很有趣在 OSX 10.12+ 之前(据报道,在 10.12+ 和 clang 9 上从源代码重新编译停止显示确实奇怪的性能问题,所以如果这也可能有助于 joblib 社区,那就更好了)。 详情在 >>> stackoverflow.com/q/47561759/3666197
  • The problem IS deeply determined by a respect to elementary Computer Science + algorithm rules 中的“确定”是否意味着类似于“确定”?
猜你喜欢
  • 2015-06-17
  • 2021-02-08
  • 2019-02-24
  • 2017-07-28
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
相关资源
最近更新 更多