Python 使用多处理并行运行rpy2会引发无法捕获的奇怪异常
所以这是一个我一直无法解决的问题,我也不知道有什么好的方法来制造MCVE。从本质上讲,它已经被简要地讨论过,但正如评论所显示的那样,存在一些分歧,最终的裁决仍然没有定论。因此,我再次发布了一个类似的问题,希望得到一个更好的答案 背景 我每分钟都会收到几千个传感器的数据。我的兴趣在于预测数据。为此,我使用了ARIMA系列预测模型。长话短说,在与我的研究小组的其他成员讨论后,我们决定使用R包Python 使用多处理并行运行rpy2会引发无法捕获的奇怪异常,python,r,multithreading,parallel-processing,rpy2,Python,R,Multithreading,Parallel Processing,Rpy2,所以这是一个我一直无法解决的问题,我也不知道有什么好的方法来制造MCVE。从本质上讲,它已经被简要地讨论过,但正如评论所显示的那样,存在一些分歧,最终的裁决仍然没有定论。因此,我再次发布了一个类似的问题,希望得到一个更好的答案 背景 我每分钟都会收到几千个传感器的数据。我的兴趣在于预测数据。为此,我使用了ARIMA系列预测模型。长话短说,在与我的研究小组的其他成员讨论后,我们决定使用R包forecast中可用的Arima功能,而不是使用相同的statsmodels实现 问题定义 因为,我有几千个
forecast
中可用的Arima
功能,而不是使用相同的statsmodels
实现
问题定义
因为,我有几千个传感器的数据,我想至少分析一周的数据(首先),因为一周有7天,我有7倍的传感器数据。基本上是一个14k传感器日组合。对于每个传感器日组合,找到最佳ARIMA顺序(使BIC最小化)并预测每周第二天的数据大约需要1分钟。这意味着在单个核心上只需处理一周的数据就可以长达11天
这显然是一种浪费,当我有15个以上的核心只是闲置了整个时间。显然,这是并行处理的一个问题。请注意,每个传感器日组合不会影响任何其他传感器日组合。另外,我的代码的其余部分也进行了很好的分析和优化
问题
问题是我犯了一个奇怪的错误,我在任何地方都抓不到。下面是重现的错误:
Exception in thread Thread-3:
Traceback (most recent call last):
File "/home/kartik/miniconda3/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/home/kartik/miniconda3/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "/home/kartik/miniconda3/lib/python3.5/multiprocessing/pool.py", line 429, in _handle_results
task = get()
File "/home/kartik/miniconda3/lib/python3.5/multiprocessing/connection.py", line 251, in recv
return ForkingPickler.loads(buf.getbuffer())
File "/home/kartik/miniconda3/lib/python3.5/site-packages/rpy2/robjects/robject.py", line 55, in _reduce_robjectmixin
rinterface_level=rinterface_factory(rdumps, rtypeof)
ValueError: Mismatch between the serialized object and the expected R type (expected 6 but got 24)
以下是我发现的该错误的一些特征:
rpy2
包中提出rpy2
在我的代码中只有一个地方被使用,它可能需要将返回值重新编码为Python类型。在中保护该行,请尝试:。。。除此之外:…
未捕获该异常中的可执行块如果
调用读取数据的函数,进行必要的分组,然后将组传递给另一个函数,该函数导入多处理
并并行调用另一个函数,该函数导入导入rpy2
的处理模块,并将数据传递给R中的Arima
函数
基本上,在函数嵌套的深处调用并初始化rpy2
并不重要,因为它不知道可能会初始化另一个实例,或者如果全局调用并初始化一次,则如果涉及到多处理
,则会引发错误
伪码
这里尝试至少呈现一些基本的伪代码,以便可以重现错误
import numpy as np
import pandas as pd
def arima_select(y, order):
from rpy2 import robjects as ro
from rpy2.robjects.packages import importr
from rpy2.robjects import pandas2ri
pandas2ri.activate()
forecast = importr('forecast')
res = forecast.Arima(y, order=ro.FloatVector(order))
return res
def arima_wrapper(data):
data = data[['tstamp', 'val']]
data.set_index('tstamp', inplace=True)
return arima_select(data, (1,1,1))
def applyParallel(groups, func):
from multiprocessing import Pool, cpu_count
with Pool(cpu_count()) as p:
ret_list = p.map(func, [group for _, group in groups])
return pd.concat(ret_list, keys=[name for name, _ in groups])
def wrapper():
df = pd.read_csv('file.csv', parse_dates=[1], infer_datetime_format=True)
df['day'] = df['tstamp'].dt.day
res = applyParallel(df.groupby(['sensor', 'day']), arima_wrapper)
print(res)
显然,上面的代码可以进一步封装,但我认为它应该相当准确地再现错误
数据样本
下面是将打印(data.head(6))
放在数据的正下方时的输出。设置索引('tstamp',inplace=True)
在arima\u包装中,从上面的伪代码中:
或者,传感器一周的数据可以通过以下方式生成:
def data_gen(start_day):
r = pd.Series(pd.date_range('2016-09-{}'.format(str(start_day)),
periods=24*60, freq='T'),
name='tstamp')
d = pd.Series(np.random.randint(10, 80, 1440), name='val')
s = pd.Series(['sensor1']*1440, name='sensor')
return pd.concat([s, r, d], axis=1)
df = pd.concat([data_gen(day) for day in range(1,8)], ignore_index=True)
意见和问题
第一个观察结果是,只有当涉及到多处理时才会出现此错误,而不是在循环中调用函数(arima_wrapper
)时。因此,它必须以某种方式与多处理问题相关联。R对多进程不是很友好,但是当以伪代码中所示的方式编写时,R的每个实例都不应该知道其他实例的存在
按照伪代码的构造方式,对于由多处理
产生的多个子流程内的每个调用,必须初始化rpy2
。如果这是真的,那么rpy2
的每个实例都应该产生自己的R实例,R实例应该只执行一个函数,然后终止。这不会引起任何错误,因为它类似于单线程操作。我在这里的理解是准确的,还是完全或部分地遗漏了要点
如果rpy2
的所有实例都以某种方式共享了一个R实例,那么我可以合理地预期会出现错误。真实情况:R是在rpy2
的所有实例中共享的,还是在rpy2
的每个实例中都有R的实例
如何克服这个问题
由于SO讨厌包含多个问题的问题线索,我将优先考虑我的问题,以便部分答案被接受。这是我的优先事项清单:
如何克服这个问题?如果工作代码示例没有提出问题,则即使它没有回答任何其他问题,也会被接受为答案,前提是没有其他答案更好,或者更早发布
我对Python导入的理解是否准确,或者我是否忽略了R的多个实例?如果我错了,我应该如何编辑导入语句,以便在每个子程序中创建一个新实例
def arima_select(y, order):
# FIXME: check whether the rpy2.robjects package
# should be (re) imported as ro to be visible
res = forecast.Arima(y, order=ro.FloatVector(order))
return res
forecast = None
def worker_init():
from rpy2 import robjects as ro
from rpy2.robjects.packages import importr
from rpy2.robjects import pandas2ri
pandas2ri.activate()
global forecast
forecast = importr('forecast')
def applyParallel(groups, func):
from multiprocessing import Pool, cpu_count
with Pool(cpu_count(), worker_init) as p:
ret_list = p.map(func, [group for _, group in groups])
return pd.concat(ret_list, keys=[name for name, _ in groups])
>>> from rpy2.rinterface import str_typeint
>>> str_typeint(6)
'LANGSXP'
>>> str_typeint(24)
'RAWSXP'
import numpy as np
import pandas as pd
from rpy2 import rinterface as ri
ri.initr()
def arima_select(y, order):
def rimport(packname):
as_environment = ri.baseenv['as.environment']
require = ri.baseenv['require']
require(ri.StrSexpVector([packname]),
quiet = ri.BoolSexpVector((True, )))
packname = ri.StrSexpVector(['package:' + str(packname)])
pack_env = as_environment(packname)
return pack_env
frcst = rimport("forecast")
args = (('y', ri.FloatSexpVector(y)),
('order', ri.FloatSexpVector(order)),
('include.constant', ri.StrSexpVector(const)))
return frcst['Arima'].rcall(args, ri.globalenv)
import numpy as np
import pandas as pd
from rpy2 import rinterface as ri
ri.initr()
def arima(y, order=(1,1,1)):
# This is the same as arima_select above, just renamed to arima
...
def applyParallel(groups, func):
from multiprocessing import Pool, cpu_count
with Pool(cpu_count(), worker_init) as p:
ret_list = p.map(func, [group for _, group in groups])
return pd.concat(ret_list, keys=[name for name, _ in groups])
def main():
# Create your df in your favorite way:
def data_gen(start_day):
r = pd.Series(pd.date_range('2016-09-{}'.format(str(start_day)),
periods=24*60, freq='T'),
name='tstamp')
d = pd.Series(np.random.randint(10, 80, 1440), name='val')
s = pd.Series(['sensor1']*1440, name='sensor')
return pd.concat([s, r, d], axis=1)
df = pd.concat([data_gen(day) for day in range(1,8)], ignore_index=True)
applyParallel(df.groupby(['sensor', pd.Grouper(key='tstamp', freq='D')]),
arima) # Note one may use partial from functools to pass order to arima