【问题标题】:Multiprocessing Pool gets successively slower after multiple calls多次调用后,多处理池逐渐变慢
【发布时间】:2020-02-08 23:13:15
【问题描述】:

我想在一个数据集上迭代训练 1000 个随机森林。为了加快速度,我试图在迭代训练循环中利用多个核心。下面是一个工作示例:

from sklearn.ensemble import RandomForestClassifier
from multiprocessing import Pool,cpu_count
import numpy as np
import pandas as pd
from time import time

n = 2000
ndims = 5000

X = pd.DataFrame(np.random.normal(0,1,n*ndims).reshape((n,ndims)))
y = pd.Series(np.random.choice([0, 1], size=(n)))


def draw_batches(n,size=100):
    steps = np.arange(0,n,size)
    if not n%size == 0:
        steps = np.append(steps,n%size)[1:]
    for step in steps:
        if not step%size == 0:
            yield step
        else:
            yield size


def pool(method,iters):
    output = []
    p = Pool(4)
    try:
        output = p.map(method,iters)
    except Exception as e:
        print(e)
        pass
    finally:
        p.close()
        p.join()
        del p
    return output


def importances(args):
    model, i = args
    y_ = y.copy()
    model.fit(X,y_)
    return model.feature_importances_

n_iters = 100
model_cls = RandomForestClassifier

for batch in draw_batches(n_iters,4):
    print(batch)
    t = time()
    train_args = [(model_cls(n_estimators=50),i) for i in np.arange(batch)]
    imps = pool(importances,train_args)
    print((time()-t)/batch)

虽然不像我正在工作的代码那样明显,但上面显示每个模型的处理时间会随着您运行的批次数逐渐增加。我不希望会出现这种情况,因为池处理已全部包含在内,并且在每次运行结束时都会删除所有内容。

是什么导致速度变慢?

【问题讨论】:

  • RandomForestClassifier 可以使用多核 cpu - n_jobs 参数。试试这种方法并比较运行时间。
  • @Poolka,谢谢,但问题不是“我怎样才能让 sklearn.RandomForest 运行得更快”,而是“为什么同一个森林在使用 multiprocessing.Pool 时运行速度越来越慢”
  • @PF1 你在什么操作系统上运行,你用的是什么版本的 Python?
  • @Jed 如果您的环境在单个池中表现出减速,或者我对每次创建/销毁池的猜测是否正确,我会很感兴趣

标签: python scikit-learn multiprocessing random-forest


【解决方案1】:

我无法使用所提供的代码复制长期持续的增长。不过,我想我会展示我的发现,以防万一他们有所帮助。

总时间

将您的脚本导入feedgnuplot 为我提供了这个运行时(我已经删除了按批次划分,因为在本示例中批次是恒定的,并且我想比较在代码的其他部分花费的时间)。

对于缺少标签,我深表歉意。以秒为单位的时间在 y 轴上,而 x 轴只是批次(因此变为 100/4 = 25)。如您所见,我有很多变化,但没有持续增加。 (我用不同的参数尝试了很多次,但我无法复制持续增加,所以我放弃了)。我从来没有用完 ram 或 cpu(我有 6 个内核,所以负载很好)。

打破它

好吧:也许如果我不能复制问题,我仍然可以弄清楚可能发生了什么?毕竟,这是一个很好的多汁赏金......这是一个可怕的未标记图表(对不起!):

我懒得输入行标签,而且我不知道在运行 gnuplot 后如何添加它们。这是通过像这样修改您的 pool 函数产生的:

def pool(method, iters):
    start = time()
    output = []
    p = Pool(4)
    pooled = time()
    try:
        output = p.map(method, iters)
        mapped = time()
    except Exception as e:
        print(e)
        pass
    finally:
        p.close()
        p.join()
        del p
        ended = time()
    return output, start, pooled, mapped, ended

然后输出运行池的总时间、启动池的时间、运行池的时间、关闭池的时间:

processing = time() - t
print(processing, pooled - start, mapped - pooled, ended - mapped)

现在(这就是我们标记事物的原因!)蓝线是总时间,相对于左侧 y 轴。橙色线是池执行时间,相对于同一轴。紫线是水池开始的时间,相对于右侧 y 轴,绿线是时间结束水池,相对于右侧 y 轴。

从中我们可以看出,虽然时间运行池占主导地位,但时间关闭池与时间运行池成正比.我不知道为什么会这样:我的第一个猜测是垃圾回收。

另一个测试,运行你的结局:

如果我们使用一个池会发生什么?执行速度会变慢吗?如果这样做,我会得到:

我不确定最终导致下降的确切原因,但它是可重现的。这是通过以下方式制作的:

def importances(args):
    start = time()
    model, i = args
    y_ = y.copy()
    model.fit(X, y_)
    return model.feature_importances_, time() - start

n_iters = 100
model_cls = RandomForestClassifier
p = Pool(4)

args = [(model_cls(n_estimators=50), i) for i in np.arange(n_iters)]
outputs = p.map(importances, args)
for _, runtime in outputs:
    print(runtime)

此运行显示执行/结果略有减少,但这纯粹是随机的。我将运行时/函数的增加归因于 time() 的使用。

总体而言,您最好不要进行批处理,因为池可以在一个完成后立即开始新的运行(但您可能知道这一点)。但我很想知道这是否也会在您的环境中减慢速度。如果是这样,它会反驳关闭池导致问题的假设。

结论

我不认为自己已经回答了这个问题 :( 但也许我们更接近一个答案:我推测在机器上以及在时间 运行 池开始爆裂的条件下起来,时间关闭池会更快地爆炸。大概有更大的数字,你将开始对 ram 进行碎片化,甚至交换,这样的事情会导致增加。(只需删除最终对象即可并不总是能防止碎片,尤其是在中间步骤使用了大量内存的情况下。)

但由于我无法复制,我无法证明任何事情。

假设

  • time() 是原子的(不是)
  • 好的,那么time() 每次花费相同的时间

【讨论】:

  • 请注意您的一个假设:time() 是尽最大努力...多核 cpu 可能会略微偏离)我认为它可能在几微秒内足够准确,但不是纳秒(对于像这样运行缓慢的东西来说非常准确......)。需要特别注意超精确计时应用(计算时钟周期......)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多