与joblib并行训练sklearn模型会阻止该过程

时间:2017-11-29 11:34:36

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

正如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数组不是延迟的原因。

1 个答案:

答案 0 :(得分:-5)

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

enter image description here

检查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.

问题 IS 深受尊重对基本计算机科学+算法规则的影响。

问题要求强大的科学背景,但常识。

问题任何特别是大数据但需要闻到事情的实际效果。

enter image description here

事实

情绪?那个问题! (丹麦王子哈姆雷特的悲剧)

我可以诚实吗?让我们更喜欢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而言,只不过是一种直接的,在理智上令人无法接受的令人讨厌的犯规。

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

语法构造函数的命令式形式点燃了大量的活动

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
                                                                 )
        )

通过[ RUNS_TO_RUN, JOBS_TO_SPAWN] - 笛卡尔空间DataPoints的合理缩放2D景观收集了关于此NOP案例的足够代表性数据,以便至少生成实际系统启动成本的第一手经验实际上本质上是空的过程&#39;与强制性指示 joblib.Parallel(...)( joblib.delayed(...) ) -syntax构造函数相关的开销工作负载,仅产生一些joblib - 管理 a_NOP_FUN() 实例。

让我们同意所有现实问题,包括机器学习模型,都是更复杂的工具,刚刚测试过的 a_NOP_FUN() ,而在这两种情况下,你必须支付已经基准的开销成本(即使是为了获得真正的零产品而付出的代价)。

因此,从这个最简单的案例中可以得出一个科学公平,严谨的工作,已经显示了所有相关设置开销的基准成本最小joblib.Parallel()惩罚正弦 - 非 - 转发到真实世界算法所在的方向 - 最佳,然后向测试循环中添加一些越来越大的&#34; - 大小:< / p>

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

再次
通过在 a_NOP_FUN_WITH_JUST_A_MEM_ALLOCATOR() -scaling, SIZE1D -scaling的合理广泛范围内运行[ RUNS_TO_RUN, JOBS_TO_SPAWN]来收集有关实际远程处理MEM分配成本的足够代表性的定量数据> 再次
joblib.Parallel() - 笛卡尔空间DataPoints的合理缩放的2D景观上,以便在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] 工具内的扩展黑盒PROCESS_under_TEST实验中触及性能缩放中的新维度,留下其魔法尚未开放。

测试用例C:

~ 100 .. 300 [ns]

Bang,架构相关问题开始慢慢显现:

人们可能很快就会注意到,不仅静态大小很重要,而且MEM-transport BANDWIDTH (硬件硬连线)也会引发问题,因为将数据从/向CPU移动到/来自MEM的成本比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] 好,比任何智能改组的几个字节都要好......#34;内部&#34; CPU_core,{CPU_core_private | CPU_core_shared | CPU_die_shared} -cache层次结构 - 单独(和任何非本地NUMA转移表现出相同数量级的附加痛苦)。

enter image description here

所有上述测试用例都没有从CPU那里得到太多努力

所以让我们开始燃烧油!

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

测试用例D:

{ aMlModelSPACE, aSetOfHyperParameterSPACE, aDataSET }

enter image description here

与机器学习多D空间领域中常见的有效载荷级别相比, O( N ) - 状态空间的所有维度都会影响到所需处理的范围(一些具有O( N.logN ),一些其他~ 15+ [ms]复杂度),其中几乎立即,精心设计,不仅仅是一个CPU_core很快就会被利用一个单一的工作&#34; -being run。

一旦天真(读取资源 - 使用不协调)CPU负载混合物开始行动,并且当任务相关的CPU负载的混合开始变得混乱时,一个确实令人讨厌的气味开始了(读取资源 - 使用O / S调度程序进程碰巧争用通用(仅仅采用一种天真的共享使用策略)资源 - 即MEM(将SWAP引入HELL),CPU(引入缓存未命中和MEM重新获取(是) ,添加了SWAPs处罚),如果忘记并让流程触及 fileIO,则不会支付任何超过 [SERIAL] 延迟费用的费用 - ( 5(!) - 数量级更慢 +共享+纯粹 - [CONCURRENTLY],性质) - 设备。这里没有祈祷帮助(包括SSD) ,只需几个数量级,但仍然是一个地狱分享和运行设备非常快速的磨损+撕裂坟墓。)

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

虚拟内存分页和交换开始严重恶化到目前为止的其余部分&#34;只是巧合 - (读:弱协调) - 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 - 计划处理(读取:进一步降低单个PROCESS-under-TEST性能)。

如果没有适当的控制,事情可能会很快破坏。监督。

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

>>> 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

最后但并非最不重要的是,为什么人们可以轻松支付超过收益的回报?

除了逐步建立的证据记录之外,真实世界的系统部署附加开销如何累积成本,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最大附加成本阈值,如果是分布式的话可能是合理的处理是为了提供任何以上 [SERIAL]计算过程加速

不遵守重新制定的Amdahl法律的明确逻辑导致流程比在纯粹的 joblib.Parallel()( joblib.delayed(...) ) 流程调度中处理更糟糕(有时,当 {{1}} 方法 &#34;阻止流程&#34;时,设计和/或操作不当的结果可能看起来好像就是这种情况#34; )。