繁体   English   中英

将 Python 函数应用于 Pandas 分组数据帧 - 加速计算的最有效方法是什么?

[英]Applying Python function to Pandas grouped DataFrame - what's the most efficient approach to speed up the computations?

我正在处理相当大的 Pandas DataFrame - 我的数据集类似于以下df设置:

import pandas as pd
import numpy  as np

#--------------------------------------------- SIZING PARAMETERS :
R1 =                    20        # .repeat( repeats = R1 )
R2 =                    10        # .repeat( repeats = R2 )
R3 =                541680        # .repeat( repeats = [ R3, R4 ] )
R4 =                576720        # .repeat( repeats = [ R3, R4 ] )
T  =                 55920        # .tile( , T)
A1 = np.arange( 0, 2708400, 100 ) # ~ 20x re-used
A2 = np.arange( 0, 2883600, 100 ) # ~ 20x re-used

#--------------------------------------------- DataFrame GENERATION :
df = pd.DataFrame.from_dict(
         { 'measurement_id':        np.repeat( [0, 1], repeats = [ R3, R4 ] ), 
           'time':np.concatenate( [ np.repeat( A1,     repeats = R1 ),
                                    np.repeat( A2,     repeats = R1 ) ] ), 
           'group':        np.tile( np.repeat( [0, 1], repeats = R2 ), T ),
           'object':       np.tile( np.arange( 0, R1 ),                T )
           }
        )

#--------------------------------------------- DataFrame RE-PROCESSING :
df = pd.concat( [ df,
                  df                                                  \
                    .groupby( ['measurement_id', 'time', 'group'] )    \
                    .apply( lambda x: np.random.uniform( 0, 100, 10 ) ) \
                    .explode()                                           \
                    .astype( 'float' )                                    \
                    .to_frame( 'var' )                                     \
                    .reset_index( drop = True )
                  ], axis = 1
                )

注意:为了有一个最小的例子,它可以很容易地df.loc[df['time'] <= 400, :] (例如使用df.loc[df['time'] <= 400, :] ),但由于我无论如何模拟数据,我认为原始大小会给出更好的概述。

对于['measurement_id', 'time', 'group']定义的每个组['measurement_id', 'time', 'group']我需要调用以下函数:

from sklearn.cluster import SpectralClustering
from pandarallel     import pandarallel

def cluster( x, index ):
    if len( x ) >= 2:
        data = np.asarray( x )[:, np.newaxis]
        clustering = SpectralClustering( n_clusters   =  5,
                                         random_state = 42
                                         ).fit( data )
        return pd.Series( clustering.labels_ + 1, index = index )
    else:
        return pd.Series( np.nan, index = index )

为了提高性能,我尝试了两种方法:

潘达列包

第一种方法是使用pandarallel包并行计算:

pandarallel.initialize( progress_bar = True )
df \
  .groupby( ['measurement_id', 'time', 'group'] ) \
  .parallel_apply( lambda x: cluster( x['var'], x['object'] ) )

然而,这似乎是次优的,因为它消耗大量 RAM,并且并非所有内核都用于计算(即使在pandarallel.initialize()方法中明确指定了内核数)。 此外,有时计算会因各种错误而终止,尽管我还没有机会找到原因(可能是 RAM 不足?)。

PySpark Pandas UDF

我还尝试了 Spark Pandas UDF,尽管我对 Spark 完全陌生。 这是我的尝试:

import findspark;  findspark.init()

from pyspark.sql           import SparkSession
from pyspark.conf          import SparkConf
from pyspark.sql.functions import pandas_udf, PandasUDFType
from pyspark.sql.types     import *

spark = SparkSession.builder.master( "local" ).appName( "test" ).config( conf = SparkConf() ).getOrCreate()
df = spark.createDataFrame( df )

@pandas_udf( StructType( [StructField( 'id', IntegerType(), True )] ), functionType = PandasUDFType.GROUPED_MAP )
def cluster( df ):
    if len( df['var'] ) >= 2:
        data = np.asarray( df['var'] )[:, np.newaxis]
        clustering = SpectralClustering( n_clusters   =  5,
                                         random_state = 42
                                         ).fit( data )
        return pd.DataFrame( clustering.labels_ + 1,
                             index = df['object']
                             )
    else:
        return pd.DataFrame( np.nan,
                             index = df['object']
                             )

res = df                                           \
        .groupBy( ['id_half', 'frame', 'team_id'] ) \
        .apply( cluster )                            \
        .toPandas()

不幸的是,性能也不令人满意,从我读到的主题来看,这可能只是使用 Python 编写的 UDF 函数的负担以及将所有 Python 对象转换为 Spark 对象并返回的相关需求。

所以这里是我的问题:

  1. 是否可以调整我的任何一种方法以消除可能的瓶颈并提高性能? (例如 PySpark 设置、调整次优操作等)
  2. 他们有更好的选择吗? 在性能方面,它们与提供的解决方案相比如何?

是否可以调整我的任一方法以消除可能的瓶颈并提高性能? (例如 PySpark 设置、调整次优操作等)

+1提及两种计算策略的设置附加开销成本 这总是一个盈亏平衡点,只有在此之后,非[SERIAL]策略才能实现某些希望拥有的[TIME] -Domain 加速(然而,如果其他,通常[SPACE] -Domain 成本)允许或保持可行 - 是的,RAM ...存在和访问如此大小的设备,预算和其他类似的现实世界限制)

第一的,
起飞前的飞行前检查
阿姆达尔定律新的、开销严格的公式目前能够合并这两个附加的pSO + pTO开销,并在预测可实现的加速水平(包括收支平衡点)时反映这些,因为它可能变得有意义(在成本/效果,效率意识)并行。

在此处输入图片说明

然而,
不是我们的核心问题
接下来是:

下一个,
考虑到SpectralClustering()的计算成本,它在这里使用径向 Boltzmann 函数内核~ exp( -gamma * distance( data, data )**2 )似乎没有从data拆分超过任何分离的工作单元的数量,作为distance( data, data ) ,根据定义,只需要访问所有data元素(参考任意值传递{ process | node }的通信成本{ process | node } - 分布式拓扑,出于显而易见的原因,如果不是最糟糕的{ process | node }用例的话,是非常糟糕的 - 分布式处理,如果不是直接的反模式(除了一些确实神秘的、无内存/无状态的,还计算结构)。

对于迂腐的分析师,是的 - 添加到这个(我们可能已经说一个糟糕的状态) -再次- 任意对任意k 均值 -处理的成本,这里是O( N^( 1 + 5 * 5 ) )对于N ~ len( data ) ~ 1.12E6+ ,这与我们希望进行一些智能和快速处理的愿望N ~ len( data ) ~ 1.12E6+

所以呢?

虽然设置成本没有被忽视,但增加的通信成本几乎肯定会阻止使用上述尝试从纯[SERIAL]流程转移到某种形式的只是- [CONCURRENT]或 True- [PARALLEL]一些工作子单元的编排,因为与必须实现(串联对)任意值传递拓扑相关的开销增加。

如果不是为了他们?

嗯,这听起来像是计算科学的矛盾——即使有可能,任何到任何预先计算的距离的成本(这将花费巨大的[TIME]复杂性成本“预先” (在哪里?如何?)是否有任何其他不可避免的延迟,允许通过一些(目前未知)增量构建一个完整的未来任意到任意距离矩阵来实现可能的延迟屏蔽?))但会重新定位这些主要存在的成本给某些人[TIME] - 和[SPACE] -Domains 中的其他位置,而不是减少它们。

“它们有更好的选择吗?

唯一的一个,我知道关至今,是尝试,如果问题可以得到重新配制成另一种,一个QUBO配制的,问题的方式(参见:Q uantum-ünconstrained-inary-Ø器优化,好消息是这样做的工具、第一手知识和实际问题解决经验的基础已经存在并且越来越大)

在性能方面,它们与提供的解决方案相比如何

性能令人叹为观止 - QUBO 公式化的问题在恒定时间(在[TIME] -Domain 中)有一个有前途的O(1) (!) 求解器,并且在[SPACE] -Domain 中受到一些限制(最近宣布的 LLNL 技巧可能有助于避免这种情况)物理世界、当前的 QPU 实现、问题大小的约束)。

这不是答案,而是……

如果你跑

df.groupby(['measurement_id', 'time', 'group']).apply(
    lambda x: cluster(x['var'], x['object']))

(即,单独使用 Pandas),您会注意到您已经在使用多个内核。 这是因为sklearn默认使用joblib来并行化工作。 可以替换调度程序以支持 Dask,并且可能会通过在线程之间共享数据获得更高的效率,但是只要您正在执行的工作像这样受 CPU 限制,您将无法做任何事情来加速它。

简而言之,这是一个算法问题:在尝试考虑不同的计算框架之前,弄清楚你真正需要计算什么。

我不是Dask专家,但我提供以下代码作为基准:

import dask.dataframe as ddf

df = ddf.from_pandas(df, npartitions=4) # My PC has 4 cores

task = df.groupby(["measurement_id", "time", "group"]).apply(
    lambda x: cluster(x["var"], x["object"]),
    meta=pd.Series(np.nan, index=pd.Series([0, 1, 1, 1])),
)

res = task.compute()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM