8.3. 并行, 资源管理和配置

8.3.1. 并行计算

一些scikit-learn的评估器与实用程序可以通过多核CPU进行并行计算,这要归功于下面的组件:

  • 通过 joblib 库. 可以通过 n_jobs 参数控制程序的进程或线程数量.
  • 通过OpenMP, 使用 C 或者 Cython 代码.

此外,如果numpy安装了特定的数字库(如MKL、OpenBLAS或BLIS),scikit learn内部使用的一些numpy例程也可以并行化。

我们将在下面的小节中描述这三个场景。

8.3.1.1. 基于Joblib的并行计算

当底层实现使用joblib时,可以通过n_jobs参数来控制并行生成的worker(线程或进程)的数量。

注意 在估计器中并行化发生的位置(和方式)目前文献记录不多。请帮助我们改进我们的文档并解决 issue 14228!

Joblib能够同时支持多处理和多线程。joblib选择生成线程还是进程取决于它使用的后端。Scikit learn通常依赖于loky后端,这是joblib的默认后端。Loky是一个多处理后端。

在进行多进程计算时,为了避免在每个进程中复制内存(对于大数据集来说这是不合理的),joblib将创建一个memmap当数据大于1MB时,所有进程都可以共享。

在某些特定情况下(当并行运行的代码释放GIL时),scikit learn将向joblib指示多线程后端是更可取的。

作为用户,您可以通过使用上下文管理器来控制joblib将使用的后端(无论scikit learn推荐什么):

from joblib import parallel_backend

with parallel_backend('threading', n_jobs=2):
    # Your scikit-learn code here

有关更多详细信息,请参阅joblib’s docs

实际上,并行性运算是否有助于改进运行时取决于许多因素。实验通常是一个好主意,而不是假设增加并行计算的数量总是一件好事。在某些情况下,并行运行某些估计器或函数的多个副本会对性能造成极大的损害(请参阅下面的超额订阅)。

8.3.1.2. 基于OpenMP的并行运算

OpenMP使用Cython或C编写的代码实现并行化,完全依赖多线程。默认情况下(除非joblib试图避免超额使用)将使用尽可能多的线程。

您可以通过OMP-NUM-threads环境变量控制使用的线程的确切数量:

OMP_NUM_THREADS=4 python my_script.py

8.3.1.3. 基于特定数字库的numpy并行化例程

Scikit learn在很大程度上依赖于NumPy和SciPy,后者在内部调用在MKL、OpenBLAS或BLIS等库中实现的多线程线性代数例程。

OpenBLAS、MKL或BLIS库使用的线程数可以通过MKL_NUM_threadsOpenBLAS_NUM_threadsBLIS_NUM_threads环境变量设置。

请注意,scikit learn无法直接控制这些实现。Scikit-learn完全依赖于Numpy和Scipy

注意 在撰写本文时(2019年),NumPy和SciPy软件包在pypi.org网站(通过pip安装)。conda forge通道上安装的conda包与OpenBLAS链接,而从anaconda.org 默认通道上安装的conda包与MKL链接。

8.3.1.4. 过度消耗: 生成太多线程

通常建议避免使用比计算机上CPU数量多得多的进程或线程。当程序同时运行太多线程时,会发生过度消耗。

假设你有一台有8个CPU的机器。考虑一种情况,在histgradientboostingcrifier(与OpenMP并行)上运行GridSearchCV(与joblib并行)和n_jobs=8HistGradientBoostingClassifier的每个实例将产生8个线程(因为您有8个CPU)。总共有8*8=64个线程,这会导致物理CPU资源的超额订阅和调度开销。

使用嵌套在joblib调用中的MKL,OpenBLAS或BLIS的并行化例程,可以以完全相同的方式产生超额订阅。

joblib>=0.14开始,当使用oky后端(这是默认值)时,joblib将告诉其子进程,以限制它们可以使用的线程数,从而避免超额订阅。在实践中,joblib使用的启发式方法是通过相应的环境变量告诉进程使用max_threads=n_cpu//n_jobs。回到上面的例子,由于GridSearchCV的joblib后端是loky,因此每个进程只能使用1个线程而不是8个线程,从而减少了订阅过多的问题。

注意:

  • 手动设置其中一个环境变量(OMP NUM_THREADSMKL NUM_THREADSOPENBLAS NUM_THREADSBLIS NUM_THREADS)将优先于joblib尝试执行的操作。线程总数将为n_jobs*_NUM_threads。请注意,设置此限制还将影响主进程中的计算,该进程只使用NUM线程。Joblib公开一个上下文管理器,以便更好地控制其工作线程的数量(请参阅下面链接的Joblib文档)。

  • Joblib当前无法避免多线程上下文中的超额订阅。它只能使用“loky”后端(它生成进程)。

您将在joblib文档中找到有关joblib缓解超额订阅的其他详细信息。

8.3.2. 配置开关

8.3.2.1. python 运行时

sklearn.set_config 参数控制下列行为:

  • assume_finite(假设有限)

    用于跳过验证,这可以加快计算速度,但如果数据包含NaN,则可能导致分段错误。

  • working_memory(工作内存)

    一些算法使用的临时数组的最佳大小。

8.3.2.2. 环境变量

在导入scikit-learn之前应该先设置这些变量。

  • SKLEARN_SITE_JOBLIB

    当此环境变量设置为非零值时,scikit-learn使用站点joblib而不是其供应商版本。因此,必须安装joblib才能运行scikit-learn。请注意,使用网站joblib需要您自担风险:scikit-learn和joblib的版本必须兼容。当前,支持joblib 0.11+。此外,joblib.Memory的转储可能不兼容,您可能会丢失一些缓存并必须重新下载一些数据集。

    *从版本0.21开始不推荐使用:*从版本0.21开始,此参数无效,已删除供应商的joblib,并且始终使用站点joblib。

  • SKLEARN_ASSUME_FINITE

    assume_finite参数 设置默认值 sklearn.set_config.

  • SKLEARN_WORKING_MEMORY

    working_memory参数 设置默认值 sklearn.set_config.

  • SKLEARN_SEED

    为了运行可重复性,在运行测试时设置全局随机数生成器的种子。

  • SKLEARN_SKIP_NETWORK_TESTS

    当此环境变量设置为非零值时,将跳过需要网络访问的测试。