IPython中的并行映射函数具有多个参数

时间:2012-07-06 23:46:01

标签: python parallel-processing ipython

我正在尝试使用IPython的并行环境,到目前为止,它看起来很棒,但我遇到了问题。假设我有一个在库中定义的函数

def func(a,b):
   ...

当我想要评估a的一个值和b的一堆值时,我会使用它。

[func(myA, b) for b in myLongList]

显然,真正的功能更复杂,但问题的实质是它需要多个参数,我只想映射其中一个。问题是map,@ dview.parallel等映射了所有参数。

所以我想说我想得到func(myA,myLongList)的答案。显而易见的方法是咖喱,或者是functools.partial,或者只是

dview.map_sync(lambda b: func(myA, b),   myLongList)

但是,这在远程计算机上无法正常工作。原因是当lambda表达式被pickle时,myA的值不包括在内,而是使用远程机器上本地作用域的myA值。当闭包被腌制时,它们关闭的变量不会。

我能想到的两种实际工作方式是为每个参数手动构建列表,并对所有参数进行映射工作,

dview.map_sync(func, [myA]*len(myLongList), myLongList)   

或者恐怖地使用数据作为函数的默认参数,强制它被腌制:

# Can't use a lambda here b/c lambdas don't use default arguments :(
def parallelFunc(b, myA = myA):
    return func(myA, b)

dview.map_sync(parallelFunc, myLongList)

真的,当真正的函数需要很多参数并且更复杂时,这一切似乎都非常扭曲。是否有一些惯用的方法呢?像

这样的东西
@parallel(mapOver='b')
def  bigLongFn(a, b):
   ...

但据我所知,没有像'mapOver'这样的东西存在。我可能已经知道如何实现它...这只是一个非常基本的操作,应该存在支持,所以我想检查我是否遗漏了一些东西。

5 个答案:

答案 0 :(得分:15)

我可以对batu的答案有所改进(我认为这是一个很好的答案,但也许不会详细记录为什么你使用这些选项)。目前,ipython文档在这一点上也是非常不合适的。所以你的功能是:

def myfxn(a,b,c,d):
  ....
  return z

并存储在名为 mylib 的文件中。假设b,c和d在运行期间是相同的,所以你编写一个lambda函数将它减少为1参数函数。

import mylib
mylamfxn=lambda a:mylib.myfxn(a,b,c,d)

你要运行:

z=dview.map_sync(mylamfxn, iterable_of_a)

在梦想世界中,一切都会神奇地发挥作用。但是,首先你会收到“找不到mylib”的错误,因为ipcluster进程没有加载mylib。如果需要,确保ipcluster进程在其python路径中具有“mylib”并且位于myfxn的正确工作目录中。然后你需要添加到你的python代码:

dview.execute('import mylib')

在每个进程上运行import mylib命令。如果你再试一次,你会得到一个错误:“全局变量b未定义”,因为虽然变量在你的python会话中,但它们不在ipcluster进程中。但是,python提供了一种将一组变量复制到子进程的方法。继续上面的例子:

mydict=dict(b=b, c=c, d=d)
dview.push(mydict)

现在所有子进程都可以访问b,c和d。然后你可以运行:

z=dview.map_sync(mylamfxn, iterable_of_a)

它现在应该像宣传的那样工作。无论如何,我是python并行计算的新手,并发现这个线程很有用,所以我想我会尝试帮助解释一些让我感到困惑的点......

最终的代码是:

import mylib

#set up parallel processes, start ipcluster from command line prior!
from IPython.parallel import Client
rc=Client()
dview=rc[:]

#...do stuff to get iterable_of_a and b,c,d....

mylamfxn=lambda a:mylib.myfxn(a,b,c,d)

dview.execute('import mylib')
mydict=dict(b=b, c=c, d=d)
dview.push(mydict)
z=dview.map_sync(mylamfxn, iterable_of_a)

这可能是制作python中并行运行的任何令人尴尬的并行代码的最快捷,最简单的方法....

更新您还可以使用dview在没有循环的情况下推送所有数据,然后使用lview(即lview=rc.load_balanced_view(); lview.map(...)以负载平衡的方式进行实际计算。

答案 1 :(得分:6)

这是我给StackOverflow的第一条消息所以请温柔;)我试图做同样的事情,并提出以下内容。我很确定这不是最有效的方式,但似乎有点工作。 目前有一点需要注意的是,由于某些原因,我只看到两台发动机100%工作,其他发动机几乎闲置......

为了在map中调用多个arg函数,我首先在我的个人parallel.py模块中编写了这个例程:

def map(r,func, args=None, modules=None):
"""
Before you run parallel.map, start your cluster (e.g. ipcluster start -n 4)

map(r,func, args=None, modules=None):
args=dict(arg0=arg0,...)
modules='numpy, scipy'    

examples:
func= lambda x: numpy.random.rand()**2.
z=parallel.map(r_[0:1000], func, modules='numpy, numpy.random')
plot(z)

A=ones((1000,1000));
l=range(0,1000)
func=lambda x : A[x,l]**2.
z=parallel.map(r_[0:1000], func, dict(A=A, l=l))
z=array(z)

"""
from IPython.parallel import Client
mec = Client()
mec.clear()
lview=mec.load_balanced_view()
for k in mec.ids:
  mec[k].activate()
  if args is not None:
    mec[k].push(args)
  if modules is not None:
    mec[k].execute('import '+modules)
z=lview.map(func, r)
out=z.get()
return out

正如您所看到的,该函数采用了args参数,该参数是头节点工作空间中参数的dict。然后将这些参数推送到引擎。此时它们成为本地对象,可以直接在函数中使用。例如,在上面评论中给出的最后一个例子中,使用l engine-local变量对A矩阵进行切片。

我必须说,即使上述功能有效,我也不会百分之百满意。如果我能想出更好的东西,我会在这里发布。

更新:2013年4月11日 我对代码做了一些小改动: - activate语句缺少括号。导致它不能运行。 - 将mec.clear()移动到函数的顶部,而不是结束。 我还注意到,如果我在ipython中运行它,效果最好。例如,如果我使用上面的函数运行脚本为“python ./myparallelrun.py”,我可能会收到错误,但如果我使用“%run ./myparallelrun.py”在ipython中运行它,则不会出错。不知道为什么......

答案 2 :(得分:0)

执行此操作的优雅方法是使用部分功能。

如果你知道你希望foo的第一个参数是myArg,你可以通过

创建一个新的功能栏
from functools import partial
bar = partial(foo, myARg)

bar(otherArg)将返回foo(myArg,otherArg)

答案 3 :(得分:0)

让我们建立:

dview.map_sync(func, [myA]*len(myLongList), myLongList)

也许以下方法可行:

from itertools import izip_longest
dview.map_sync(func, izip_longest(myLongList, [], fillvalue=myA))

示例:

>>> # notice that a is a tuple
... concat = lambda a: '%s %s' % a
>>> mylonglist = range(10)
>>> from itertools import izip_longest
>>> map(concat, izip_longest(mylonglist, [], fillvalue='mississippi'))
['0 mississippi', '1 mississippi', '2 mississippi', '3 mississippi',
'4 mississippi', '5 mississippi', '6 mississippi', '7 mississippi',
'8 mississippi', '9 mississippi']

答案 4 :(得分:0)

我发布Alex S.评论作为回答。这可能是解决这个问题的正确方法:

使用lambda进行部分应用。我知道它看起来很奇怪,但是使用my_f = lambda a,my = other,arguments = go,right = here:f(a,my,arguments,right)是最简单的方法,不会陷入酸洗和推动问题