web-dev-qa-db-ja.com

joblib.Parallel実行の進行状況の追跡

joblib.Parallel 実行の全体的な進行状況を追跡する簡単な方法はありますか?

何千ものジョブで構成される長時間実行があり、それらを追跡してデータベースに記録したいと思います。ただし、これを行うには、Parallelがタスクを終了するたびに、コールバックを実行して、残りのジョブの数を報告する必要があります。

以前、Pythonのstdlib multiprocessing.Poolを使用して、プールのジョブリストに保留中のジョブの数を記録するスレッドを起動することにより、同様のタスクを実行しました。

コードを見ると、ParallelはPoolを継承しているので、同じトリックを実行できると思いましたが、これらのリストを使用していないようで、内部の「読み取り」方法を他に理解できませんでした。他の方法でステータス。

22
Cerin

なぜ単純にtqdmを使用できないのですか?以下は私のために働いた

from joblib import Parallel, delayed
from datetime import datetime
from tqdm import tqdm

def myfun(x):
    return x**2

results = Parallel(n_jobs=8)(delayed(myfun)(i) for i in tqdm(range(1000))
100%|██████████| 1000/1000 [00:00<00:00, 10563.37it/s]
23
Jon

リンクしたドキュメントには、Parallelにはオプションのプログレスメーターがあると記載されています。これは、multiprocessing.Pool.apply_asyncによって提供されるcallbackキーワード引数を使用して実装されます。

# This is inside a dispatch function
self._lock.acquire()
job = self._pool.apply_async(SafeFunction(func), args,
            kwargs, callback=CallBack(self.n_dispatched, self))
self._jobs.append(job)
self.n_dispatched += 1

.。

class CallBack(object):
    """ Callback used by parallel: it is used for progress reporting, and
        to add data to be processed
    """
    def __init__(self, index, parallel):
        self.parallel = parallel
        self.index = index

    def __call__(self, out):
        self.parallel.print_progress(self.index)
        if self.parallel._original_iterable:
            self.parallel.dispatch_next()

そしてここにprint_progressがあります:

def print_progress(self, index):
    elapsed_time = time.time() - self._start_time

    # This is heuristic code to print only 'verbose' times a messages
    # The challenge is that we may not know the queue length
    if self._original_iterable:
        if _verbosity_filter(index, self.verbose):
            return
        self._print('Done %3i jobs       | elapsed: %s',
                    (index + 1,
                     short_format_time(elapsed_time),
                    ))
    else:
        # We are finished dispatching
        queue_length = self.n_dispatched
        # We always display the first loop
        if not index == 0:
            # Display depending on the number of remaining items
            # A message as soon as we finish dispatching, cursor is 0
            cursor = (queue_length - index + 1
                      - self._pre_dispatch_amount)
            frequency = (queue_length // self.verbose) + 1
            is_last_item = (index + 1 == queue_length)
            if (is_last_item or cursor % frequency):
                return
        remaining_time = (elapsed_time / (index + 1) *
                    (self.n_dispatched - index - 1.))
        self._print('Done %3i out of %3i | elapsed: %s remaining: %s',
                    (index + 1,
                     queue_length,
                     short_format_time(elapsed_time),
                     short_format_time(remaining_time),
                    ))

彼らがこれを実装する方法は、正直なところ、ちょっと奇妙です-タスクは常に開始された順序で完了すると想定しているようです。 print_progressに移動するindex変数は、ジョブが実際に開始されたときのself.n_dispatched変数にすぎません。したがって、最初に起動されたジョブは、たとえ3番目のジョブが最初に終了したとしても、常にindexが0で終了します。また、実際には完了ジョブの数を追跡していないことも意味します。したがって、監視するインスタンス変数はありません。

最善の方法は、独自のCallBackクラスを作成し、モンキーパッチを並列にすることだと思います。

from math import sqrt
from collections import defaultdict
from joblib import Parallel, delayed

class CallBack(object):
    completed = defaultdict(int)

    def __init__(self, index, parallel):
        self.index = index
        self.parallel = parallel

    def __call__(self, index):
        CallBack.completed[self.parallel] += 1
        print("done with {}".format(CallBack.completed[self.parallel]))
        if self.parallel._original_iterable:
            self.parallel.dispatch_next()

import joblib.parallel
joblib.parallel.CallBack = CallBack

if __name__ == "__main__":
    print(Parallel(n_jobs=2)(delayed(sqrt)(i**2) for i in range(10)))

出力:

done with 1
done with 2
done with 3
done with 4
done with 5
done with 6
done with 7
done with 8
done with 9
done with 10
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

そうすれば、デフォルトのコールバックではなく、ジョブが完了するたびにコールバックが呼び出されます。

13
dano

Joblibライブラリの最新バージョンに対するdanoの回答を拡張します。内部実装にいくつかの変更がありました。

from joblib import Parallel, delayed
from collections import defaultdict

# patch joblib progress callback
class BatchCompletionCallBack(object):
  completed = defaultdict(int)

  def __init__(self, time, index, parallel):
    self.index = index
    self.parallel = parallel

  def __call__(self, index):
    BatchCompletionCallBack.completed[self.parallel] += 1
    print("done with {}".format(BatchCompletionCallBack.completed[self.parallel]))
    if self.parallel._original_iterator is not None:
      self.parallel.dispatch_next()

import joblib.parallel
joblib.parallel.BatchCompletionCallBack = BatchCompletionCallBack
5
Connor Clark

DanoとConnorの回答からさらに一歩進んだのは、コンテキストマネージャーとしてすべてをラップすることです。

import contextlib
import joblib
from tqdm import tqdm
from joblib import Parallel, delayed

@contextlib.contextmanager
def tqdm_joblib(tqdm_object):
    """Context manager to patch joblib to report into tqdm progress bar given as argument"""
    class TqdmBatchCompletionCallback:
        def __init__(self, time, index, parallel):
            self.index = index
            self.parallel = parallel

        def __call__(self, index):
            tqdm_object.update()
            if self.parallel._original_iterator is not None:
                self.parallel.dispatch_next()

    old_batch_callback = joblib.parallel.BatchCompletionCallBack
    joblib.parallel.BatchCompletionCallBack = TqdmBatchCompletionCallback
    try:
        yield tqdm_object
    finally:
        joblib.parallel.BatchCompletionCallBack = old_batch_callback
        tqdm_object.close()    

次に、このように使用できます。完了したら、モンキーパッチを適用したコードを残さないでください。

with tqdm_joblib(tqdm(desc="My calculation", total=10)) as progress_bar:
    Parallel(n_jobs=16)(delayed(sqrt)(i**2) for i in range(10))

これは素晴らしいと思います。tqdmpandas統合)に似ています。

4
frenzykryger

テキストプログレスバー

Tqdmのような追加モジュールなしでテキストプログレスバーが必要な人のためのもう1つのバリエーション。 joblib = 0.11の実際、python 3.5.2、Linuxでは16.04.2018で、サブタスクの完了時に進行状況を示します。

ネイティブクラスを再定義します。

class BatchCompletionCallBack(object):
    # Added code - start
    global total_n_jobs
    # Added code - end
    def __init__(self, dispatch_timestamp, batch_size, parallel):
        self.dispatch_timestamp = dispatch_timestamp
        self.batch_size = batch_size
        self.parallel = parallel

    def __call__(self, out):
        self.parallel.n_completed_tasks += self.batch_size
        this_batch_duration = time.time() - self.dispatch_timestamp

        self.parallel._backend.batch_completed(self.batch_size,
                                           this_batch_duration)
        self.parallel.print_progress()
        # Added code - start
        progress = self.parallel.n_completed_tasks / total_n_jobs
        print(
            "\rProgress: [{0:50s}] {1:.1f}%".format('#' * int(progress * 50), progress*100)
            , end="", flush=True)
        if self.parallel.n_completed_tasks == total_n_jobs:
            print('\n')
        # Added code - end
        if self.parallel._original_iterator is not None:
            self.parallel.dispatch_next()

import joblib.parallel
joblib.parallel.BatchCompletionCallBack = BatchCompletionCallBack

ジョブの総数を使用して、使用前にグローバル定数を定義します。

total_n_jobs = 10

これにより、次のようになります。

Progress: [########################################          ] 80.0%
3
Nikolay

Jupyterでは、tqdmは出力するたびに出力で新しい行を開始します。したがって、JupyterNotebookの場合は次のようになります。

from joblib import Parallel, delayed
from datetime import datetime
from tqdm import tqdm_notebook

def myfun(x):
    return x**2

results = Parallel(n_jobs=8)(delayed(myfun)(i) for i in tqdm_notebook(range(1000)))  
100% 1000/1000 [00:06<00:00, 143.70it/s]

次の構文を使用した、質問に対する別の回答を次に示します。

aprun = ParallelExecutor(n_jobs=5)

a1 = aprun(total=25)(delayed(func)(i ** 2 + j) for i in range(5) for j in range(5))
a2 = aprun(total=16)(delayed(func)(i ** 2 + j) for i in range(4) for j in range(4))
a2 = aprun(bar='txt')(delayed(func)(i ** 2 + j) for i in range(4) for j in range(4))
a2 = aprun(bar=None)(delayed(func)(i ** 2 + j) for i in range(4) for j in range(4))

https://stackoverflow.com/a/40415477/232371

1
Ben Usman