【问题标题】:How does scikit-learn handle multiple n_jobs arguments?scikit-learn 如何处理多个 n_jobs 参数?
【发布时间】:2020-03-26 22:03:21
【问题描述】:

我在 scikit-learn 中创建了一个管道,如下所示:

estimators2 = [
    ('tfidf', TfidfVectorizer(tokenizer=lambda string: string.split())),
    ('clf', SGDClassifier(n_jobs=13, early_stopping=True, class_weight='balanced'))
]
parameters2 = {
    'tfidf__min_df': np.arange(10, 30, 10),
    'tfidf__max_df': np.arange(0.75, 0.9, 0.05),
    'tfidf__ngram_range': [(1, 1), (2, 2), (3, 3)],
    'clf__alpha': (1e-2, 1e-3)
}

p2 = Pipeline(estimators2)
grid2 = RandomizedSearchCV(p2, param_distributions=parameters2, 
                           scoring='balanced_accuracy', n_iter=20, cv=3, n_jobs=13, pre_dispatch='n_jobs')

在这个管道中,有两次参数 n_jobs? scikit-learn 是如何处理它们的?

【问题讨论】:

    标签: python scikit-learn parallel-processing


    【解决方案1】:

    您可以尝试使用引入其内部线程的算法的加速实现 - 例如 scikit-learn-intelex - https://github.com/intel/scikit-learn-intelex

    scikit-learn-intelex 基于 TBB 进行并行化,可以利用整个系统进行加速算法。因此,您可以在不使用 joblib 并行化的情况下获得更好的性能。

    第一个安装包

    pip install scikit-learn-intelex
    

    然后添加你的python脚本

    from sklearnex import patch_sklearn
    patch_sklearn()
    

    【讨论】:

      【解决方案2】:

      Qscikit-learn 是如何处理它们的?

      所以,让我们从 2019/4Q 的文档开始:

      n_jobsintNone,可选(默认 = None

      并行运行的作业数。 None 表示 1,除非在 joblib.parallel_backend context 中。 -1 表示使用所有处理器。详情请见Glossary


      我们在这里有哪些替代方案?哪个是最好的?

      A)完全没有并行性
      B)锁定 CPU 而不是提高性能
      C)USTOM 设置 CPU - 核心映射以获得最佳性能
      D)Dask-nodes 集群中的 ITRIBUTE 工作负载


      选项 A : A)完全没有并行性

      因此,可以明确避免并行处理RandomisedSearchCV(Pipeline([(…),(…,SGDClassifier( n_jobs=13,…)),]),…,n_jobs=13,… ) 用于 CPU 密集型但受 MEM 限制的任务处理,通过显式使用 "threading"-后端上下文构造函数:

      with parallel_backend( 'threading' ):  # also ref.'d via sklearn.utils.parallel_backend
           grid2.fit( … )
      

      在这里,无论有多少线程被实例化,都只是在等待一个,central GIL-lock所有等待,但一个执行。这是一个已知的 GIL-lock-introduced re-[SERIAL]-isation 任何基于线程的代码执行到 更昂贵的,只是交错的,纯粹的-[SERIAL]python 代码执行。除了那些(不是这个),主要是延迟屏蔽技巧(为了更好地利用 I/O 绑定任务在 NOP 中花费的时间,等待完成并产生任何结果的 I/O 操作)这无助于更快地充分利用基于 python 的 ML 管道,但是非常对面。


      选项 B : B)锁定 CPU 而不是提高性能

      人们可以选择一个更好的、不那么令人望而却步的后端 - "multiprocessing" 或最近的 joblib-releases 也可以是 "loky",其中没有 GIL 锁给我们带来了麻烦(以实例化 n_jobs-many python-process 副本为代价,每个副本都有其内部和不可避免的 GIL 锁,现在至少不会与其他自己的线程竞争一个通过首先获取 GIL 锁来执行时间片的工作量),但这里的故事还没有结束。这个选项是一个典型的例子,当多个级别的 n_jobs 感知处理出现在同一个管道中时 - 他们每个人都在争夺(在 O/S 调度程序级别)以获得CPU 内核时间片在分时运行中的进程多于 CPU 内核的数量。 结果? 进程已生成,但必须在队列中等待轮到它们(如果用户允许的内核数量超过 - 不仅要检查内核数量,还要检查允许的内核数量) CPU-core-affinity 设置,由操作系统为给定的用户/进程有效权限强制执行,在严格管理的系统上可能远小于 CPU-cores 的物理(或虚拟化模拟)数量),浪费时间,丢失 CPU 缓存预取的数据块(因此一次又一次地花费昂贵的 RAM 获取(每次支付 ~300~350 ns),而不是重新使用预取(并且已经付费)来自 L1/L2/L3 缓存的数据,成本仅为 0.5 ns(!))


      选项 C:C)USTOM 设置 CPU 内核映射以获得最佳性能

      一个好的工程实践是仔细映射 CPU 内核以进行处理。

      如果有一个合适的后端,就必须确定性能瓶颈在哪里 - 在这里,最有可能(如果有选项 D,则有可能例外)配备了一个巨大的强和胖内存机器的集群),一个人会更喜欢让每一个 SGDClassifier.fit() 更快(花费更多n_jobs-specified process-instances for most昂贵的子任务 - 训练),而不是拥有“更多” RandomisedSearchCV()-initated “玩具”在操场上,但因内存不足和 CPU 缓存效率低下而窒息

      即使在不知道所有细节的幕后,您的代码也总是必须“服从”才能在所有 CPU 内核上运行,而只能在那些允许被任何此类multiprocessing 使用的 CPU 内核上运行-请求的子流程, 数量不高于:len( os.sched_getaffinity( 0 ) )。如果对详细信息感兴趣,请阅读并使用代码here

      一般来说,良好的规划和分析实践将有助于实现n_jobs-instantiated 进程映射到可用 CPU 核心集的最佳合理配置。没有什么魔法,只有常识、过程监控和运行时的基准测试/计时将帮助我们提高这种能力。


      选项 D:D)在Dask-nodes 的集群中分配工作负载

      在可能的情况下,using Dask-module 启用 节点,可以设置:

      with parallel_backend( 'dask' ):
           grid2.fit( … )
      

      这将利用所有 Dask 集群计算资源来使“繁重”任务更智能地完成,而不是仅使用 localhost-CPU/RAM 资源。最终,这是 Python 生态系统在其当前原样状态下可能实现的最大并发处理级别。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-07
        • 2012-10-15
        • 2020-09-30
        • 2019-05-09
        • 2017-12-08
        • 2018-07-07
        • 1970-01-01
        • 2013-03-18
        相关资源
        最近更新 更多