【问题标题】:How to parallelize multiple model-building procedures in sklearn如何在 sklearn 中并行化多个模型构建过程
【发布时间】:2021-07-18 01:53:30
【问题描述】:

有没有办法在 scikit-learn 中并行化多个模型构建过程?我知道我可以在GridSearchCVcross_validate 中使用n_jobs 参数来实现某种并行化在一个模型构建过程中。但是,我在具有不同输入参数的 for 循环中运行多个模型构建过程,并将结果保存在列表中。举个例子,假设我有 15 个空闲 CPU,我在 cross_validate 中使用 n_jobs=5。如果我没记错的话,这意味着一个模型构建过程使用 5 个 CPU。现在有没有办法在我的 for 循环中开始接下来的 2 个模型构建过程,所以我正在使用所有 15 个 CPU?这是一个虚拟示例:

import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import KFold, GridSearchCV, cross_validate

# load breast cancer data set
X,y = load_breast_cancer(return_X_y=True)

# define different types of penalty strategies
# let's make a toy example and pretend we would be interested in
# running different penalty strategies (I use three times 'l2' here,
# but imagine these would be different)
penalty_types = ['l2','l2','l2']

# define output list where we add the results using different penalty strategies
nested_cv_scores_list = []

for penalty_type in penalty_types:
    
    # create a random number generator
    rng = np.random.RandomState(42)

    # z-standardize features
    scaler = StandardScaler()
    
    # use linear L2-regularized Logistic Regression as classifier
    lr = LogisticRegression(random_state=rng,penalty=penalty_type)
    
    # define parameter grid to optimize over (optimize C)
    lr_c = np.linspace(start=1,stop=16,num=11,endpoint=True)
    p_grid = {'lr__C':lr_c}
    
    # create pipeline
    lr_pipe = Pipeline([
        ('scaler',scaler),
        ('lr',lr)
        ])
    
    # define cross validation strategy
    cv = KFold(shuffle=True,random_state=rng)
    
    # implement GridSearch (inner cross validation)
    grid = GridSearchCV(lr_pipe,param_grid=p_grid,cv=cv)
    
    # implement cross_validate (outer cross validation)
    nested_cv_scores = cross_validate(grid,X,y,cv=cv,n_jobs=5)

    # append result to list
    nested_cv_scores_list.append(nested_cv_scores)

有没有办法并行化这个 for 循环?

【问题讨论】:

  • 如果设置n_jobs = -1,它将使用所有可用的CPU。
  • 我知道,但这只会影响我的 for 循环中的一个模型构建过程的并行化(因此在我的示例中:使用 5 个 CPU 用于 'l1' 然后 5 个 CPU 用于 'l2 ' 最后是 5 个 CPU 用于 'elastic')。但我想并行化模型构建过程。如果您想这样称呼它,则为“元”并行化。
  • 我认为GridSearchCV 能够并行执行所有这些计算,而无需创建for-loop。你只需要像往常一样通过它们。除非你真的有 for-loop 的具体原因(我没看到),否则你可以创建自己的 GridSearchCV 并在 for-loop 中使用多处理(即池)方法。
  • 当然,上面的脚本只是一个简化的例子。我的模型构建程序之间存在系统/固定的差异(例如,在我的示例中,我假装使用三种不同的惩罚策略),我想进行比较。这就是 for 循环的用途。我不想将它们优化为超参数,因为我想确保某些参数在我的模型构建过程中保持不变(“使用固定参数 a、b、c 运行三个嵌套交叉验证”)。我不知道GridSearchCV 怎么解决这个问题?

标签: python multithreading scikit-learn parallel-processing


【解决方案1】:

joblib.parallel 是为这项工作而生的!只需将循环内容放入一个函数中,然后使用Paralleldelayed 调用它

from joblib.parallel import Parallel, delayed
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import KFold, GridSearchCV, cross_validate

# load breast cancer data set
X,y = load_breast_cancer(return_X_y=True)

# define different types of penalty strategies
# let's make a toy example and pretend we would be interested in
# running different penalty strategies (I use three times 'l2' here,
# but imagine these would be different)
penalty_types = ['l2','l2','l2']

# define output list where we add the results using different penalty strategies
nested_cv_scores_list = []

# put rng-seed outside of loop so that not all results are the same
rng = np.random.RandomState(42)

def run_as_job(penalty_type, X, y):

    # create a random number generator
    

    # z-standardize features
    scaler = StandardScaler()
    
    # use linear L2-regularized Logistic Regression as classifier
    lr = LogisticRegression(random_state=rng,penalty=penalty_type)
    
    # define parameter grid to optimize over (optimize C)
    lr_c = np.linspace(start=1,stop=16,num=11,endpoint=True)
    p_grid = {'lr__C':lr_c}

    .... # additional calculation that is missing in the example
    .... # e.g. res = cross_val_score(clf, X, y, n_jobs=2)
    return res

if __name__ == '__main__':
    results = Parallel(n_jobs=2)(delayed(run_as_job)(penalty_type) for penalty_type in penalty_types)

更多使用选项请查看joblib: Embarrassingly parallel for loops

【讨论】:

  • 我已经考虑过了,但不确定这是否可行? Parallel(n_jobs=2)...(“最外层循环”)如何与 cross_validate(...,n_jobs) 交互,因为它们都定义了要使用的 CPU 数量?因此,例如在您的脚本中,您使用Parallel(n_jobs=2)...如果我得到正确的文档,应该定义两个迭代并行运行,每个迭代都占用一个 CPU。这不是自动意味着嵌套在其中的所有内容都仅限于一个与cross_validate(n_jobs=5)相矛盾的CPU?
  • 并行将产生两个进程,每个进程运行一次函数。在这个函数中cross_validate(n_jobs=5) 将产生另外 5 个进程。因此,总共将运行最多 10 个进程。您必须尝试一下如何以最佳方式利用所有内核,有时2x5 的运行速度会比5x2 慢,这完全取决于不同子任务的并行程度以及函数调用的开销是多少。 Python 中的并行化有时有点棘手,因为 threading 由于 GIL 仅在单个 CPU 上运行。只有生成进程才会使用所有 CPU。
  • 这与GridSearchCVn_jobs=2 所做的完全一样!
  • @Yahya 你为什么这么认为? Parallel(n_jobs=5) 产生 5 个进程,GridSearchCV(X,y,n_jobs=2) 分别产生另外 2 个进程,所以更多。根据您进行的折叠次数,这种方法很有意义。
  • 不,这是错误的。首先,您的代码(以及 OP 的示例中)中的所有内容都非常轻量级。因此,唯一需要多处理的就是尝试不同的参数,这就是GridSearchCV 在不需要外部for-loop 的情况下以并行方式所做的事情。此外,如果 OP 想要检查每个具有不同参数的模型的元结果保持不变,他们也可以通过GridSearchCV。最后,任何机器上的资源都是有限的,因此,抛出所有这些进程几乎肯定会导致高性能计算机 CPU 或 GPU 的节流。
猜你喜欢
  • 2015-06-17
  • 1970-01-01
  • 2018-05-13
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多