web-dev-qa-db-ja.com

マルチプロセッシング使用時のPicklingError

マルチプロセッシングモジュールでPool.map_async()(およびPool.map())を使用すると問題が発生します。 Pool.map_asyncへの関数入力が「通常の」関数である限り、うまく機能するparallel-for-loop関数を実装しました。機能が例えばクラスへのメソッドを実行すると、PicklingErrorが発生します。

cPickle.PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

私はPythonを科学計算にのみ使用しているので、酸洗いの概念にあまり精通していません。今日それについて少し学びました。以前のいくつかの答えを見てみました- pythonのマルチプロセッシングPool.map() を使用する場合、<type 'instancemethod'>をピクルすることはできませんが、回答で提供されているリンクをたどっても、それを機能させる方法を理解できません。

私のコードは、複数のコアを使用して通常のr.vのベクトルをシミュレートすることを目的としていました。これは単なる例であり、複数のコアで実行しても効果がない場合もあります。

import multiprocessing as mp
import scipy as sp
import scipy.stats as spstat

def parfor(func, args, static_arg = None, nWorkers = 8, chunksize = None):
    """
    Purpose: Evaluate function using Multiple cores.

    Input:
        func       - Function to evaluate in parallel
        arg        - Array of arguments to evaluate func(arg)  
        static_arg - The "static" argument (if any), i.e. the variables that are      constant in the evaluation of func.
        nWorkers   - Number of Workers to process computations.
    Output:
        func(i, static_arg) for i in args.

    """
    # Prepare arguments for func: Collect arguments with static argument (if any)
    if static_arg != None:
        arguments = [[arg] + static_arg for arg in list(args)]
    else:
        arguments = args

    # Initialize workers
    pool = mp.Pool(processes = nWorkers) 

    # Evaluate function
    result = pool.map_async(func, arguments, chunksize = chunksize)
    pool.close()
    pool.join()

    return sp.array(result.get()).flatten() 

# First test-function. Freeze location and scale for the Normal random variates generator.
# This returns a function that is a method of the class Norm_gen. Methods cannot be pickled
# so this will give an error.
def genNorm(loc, scale):
    def subfunc(a):
        return spstat.norm.rvs(loc = loc, scale = scale, size = a)
    return subfunc

# Second test-function. The same as above but does not return a method of a class. This is a "plain" function and can be 
# pickled
def test(fargs):
    x, a, b = fargs
    return spstat.norm.rvs(size = x, loc = a, scale = b)

# Try it out.
N = 1000000

# Set arguments to function. args1 = [1, 1, 1,... ,1], the purpose is just to generate a random variable of size 1 for each 
# element in the output vector.
args1 = sp.ones(N)
static_arg = [0, 1] # standarized normal.

# This gives the PicklingError
func = genNorm(*static_arg)
sim = parfor(func, args1, static_arg = None, nWorkers = 12, chunksize = None)

# This is OK:
func = test
sim = parfor(func, args1, static_arg = static_arg, nWorkers = 12, chunksize = None)

pythonのマルチプロセッシングPool.map() を使用している場合、<type 'instancemethod'>をピクルすることはできません)に続くリンクに従って、Steven Bethard(ほぼ最後に)はcopy_regの使用を提案しますモジュール。彼のコードは次のとおりです。

def _pickle_method(method):
    func_name = method.im_func.__name__
    obj = method.im_self
    cls = method.im_class
    return _unpickle_method, (func_name, obj, cls)

def _unpickle_method(func_name, obj, cls):
    for cls in cls.mro():
        try:
            func = cls.__dict__[func_name]
        except KeyError:
            pass
        else:
            break
    return func.__get__(obj, cls)

import copy_reg
import types

copy_reg.pickle(types.MethodType, _pickle_method, _unpickle_method)

どうすればこれを活用できるのかよくわかりません。私が思いついたのは、コードの直前に置くことだけでしたが、役に立ちませんでした。もちろん、簡単な解決策は、機能するものをそのまま使用し、copy_regに関与しないようにすることです。毎回問題を回避する必要なく、マルチプロセッシングを十分に活用するためにcopy_regを適切に機能させることに、より関心があります。

よろしくお願いします。

マティアス

26
matiasq

ここでの問題は、概念的なものより「ピクル」エラーメッセージの方が少ないです。マルチプロセスは、その魔法を実行するために、「ワーカー」の異なるプロセスでコードをフォークします。

次に、データ(ピクルスを使用する部分)をシームレスにシリアル化および逆シリアル化することにより、異なるプロセスとの間でデータを送受信します。

やり取りされるデータの一部が関数である場合、同じ名前の関数が呼び出し先プロセスに存在すると想定し、(おそらく)関数名を文字列として渡します。関数はステートレスであるため、呼び出されたワーカープロセスは、受け取ったデータを使用して同じ関数を呼び出すだけです。 (Python関数はpickleを介してシリアル化できないため、参照のみがマスタープロセスとワーカープロセスの間で渡されます)

関数がインスタンス内のメソッドである場合-pythonをコード化する場合、 "自動" self変数を使用して、関数と同じものに似ていますが、下は同じではありません。インスタンス(オブジェクト)はステートフルであるためです。つまり、ワーカープロセスには、呼び出したいメソッドの所有者であるオブジェクトのコピーが反対側にありません。

メソッドを関数としてmap_async呼び出しに渡す方法を回避することもできません。マルチプロセスは、関数の参照を使用するだけで、関数の参照を実際の関数ではなく使用するためです。

したがって、(1)コードを変更して、メソッドではなく関数をワーカープロセスに渡し、オブジェクトが保持する状態を新しいパラメーターに変換して呼び出す必要があります。 (2)ワーカープロセス側で必要なオブジェクトを再構築するmap_async呼び出しの「ターゲット」関数を作成し、その中の関数を呼び出します。 Python=の最も単純なクラスはそれ自体が選択可能であるため、map_async呼び出しで関数の所有者であるオブジェクト自体を渡すことができます。 "target"関数はワーカーの適切なメソッド自体を呼び出します側。

(2)「難しい」ように聞こえるかもしれませんが、オブジェクトのクラスをpickle化できない場合を除いて、おそらく次のようなものです。

import types

def target(object, *args, **kw):
    method_name = args[0]
    return getattr(object, method_name)(*args[1:])
(...)    
#And add these 3 lines prior to your map_async call:


    # Evaluate function
    if isinstance (func, types.MethodType):
        arguments.insert(0, func.__name__)
        func = target
    result = pool.map_async(func, arguments, chunksize = chunksize)

*免責事項:私はこれをテストしていません

19
jsbueno