Python 使用多处理并行运行rpy2会引发无法捕获的奇怪异常

Python 使用多处理并行运行rpy2会引发无法捕获的奇怪异常,python,r,multithreading,parallel-processing,rpy2,Python,R,Multithreading,Parallel Processing,Rpy2,所以这是一个我一直无法解决的问题,我也不知道有什么好的方法来制造MCVE。从本质上讲,它已经被简要地讨论过,但正如评论所显示的那样,存在一些分歧,最终的裁决仍然没有定论。因此,我再次发布了一个类似的问题,希望得到一个更好的答案 背景 我每分钟都会收到几千个传感器的数据。我的兴趣在于预测数据。为此,我使用了ARIMA系列预测模型。长话短说,在与我的研究小组的其他成员讨论后,我们决定使用R包forecast中可用的Arima功能,而不是使用相同的statsmodels实现 问题定义 因为,我有几千个

所以这是一个我一直无法解决的问题,我也不知道有什么好的方法来制造MCVE。从本质上讲,它已经被简要地讨论过,但正如评论所显示的那样,存在一些分歧,最终的裁决仍然没有定论。因此,我再次发布了一个类似的问题,希望得到一个更好的答案

背景 我每分钟都会收到几千个传感器的数据。我的兴趣在于预测数据。为此,我使用了ARIMA系列预测模型。长话短说,在与我的研究小组的其他成员讨论后,我们决定使用R包
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
    包中提出
  • 它与线程3有关。由于Python是零索引的,我猜这是第四个线程。因此,4x6=24,这等于最终错误语句中显示的数字
  • rpy2
    在我的代码中只有一个地方被使用,它可能需要将返回值重新编码为Python类型。在
    中保护该行,请尝试:。。。除此之外:…
    未捕获该异常
  • 当我放弃多重处理并在循环中调用函数时,不会引发异常
  • 异常不会使程序崩溃,只是永久挂起它(直到我按住Ctrl+C键终止它)
  • 到目前为止,我所做的一切都没有解决这个错误
  • 尝试过的事情 我尝试了各种方法,从极端的过程编码,用函数来处理最少的情况(即只有一个函数需要并行调用),到极端的封装,在极端封装中,
    中的可执行块如果
    调用读取数据的函数,进行必要的分组,然后将组传递给另一个函数,该函数导入
    多处理
    并并行调用另一个函数,该函数导入导入
    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