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


9

我正在处理相当大的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, :]),但是由于无论如何我都模拟数据,所以我认为原始大小会提供更好的概览。

对于定义的每个组,['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包并行化计算:

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熊猫UDF

尽管我是Spark的新手,但我也试了一下Spark Pandas UDF。这是我的尝试:

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. 他们有更好的选择吗?在性能方面,它们如何与提供的解决方案进行比较?

2
你研究吗?
Danila Ganchar

1
尚未,但感谢您的建议-我给您一个机会
Kuba_

不幸的是我没有工作dask(((所以我的意见是对的研究只是建议。
达尼拉Ganchar

所谓性能,是指可以完成计算的时间。
库巴

Answers:


1

是否可以调整我的任何一种方法来消除可能的瓶颈并改善性能?(例如,PySpark设置,调整次优操作等)

+1提及了两种计算策略的设置 附加管理费用。这总是达到收支平衡点,只有在此之后,非[SERIAL]策略才能实现某些希望获得的[TIME]-Domain加速的任何有益的喜悦(但是,如果其他通常情况下[SPACE]-Domain成本允许或保持可行-是的,RAM)。 ..存在和访问这种大小的设备,预算和其他类似的现实世界限制)

首先,
起飞前的飞行前检查
阿姆达尔定律
新的,开销受限的新公式目前能够合并这两个附加pSO + pTO开销,并在预测可达到的加速水平(包括收支平衡)时反映出这些开销要点,自此并行(从成本/效果,效率的角度)可能变得有意义。

在此处输入图片说明

但是,
不是我们这里的核心问题
接下来是:

接下来,
考虑到的计算成本SpectralClustering(),将在这里使用径向Boltzmann函数内核~ exp( -gamma * distance( data, data )**2 ),似乎没有从data-object的分离到任何数量的分离工作单元的前进,因为distance( data, data )-component从定义上说,必须拜访所有data元素(参见。任何{ process | node }显而易见的原因,如果不是最差的用例,{ process | node }分布式处理的通信成本都是非常糟糕的,如果不是最简单的反模式的话) (除了某些确实神秘的,无内存/无状态的计算架构)。

对于迂腐的分析师,是-添加到这个(我们可能已经说了不好的状态)的成本- -任意到任意k均值 -处理,这里大约O( N^( 1 + 5 * 5 ) )是去,对N ~ len( data ) ~ 1.12E6+,对天威我们希望有一些智能,快速的处理。

所以呢?

虽然安装成本不被忽略,则增加了沟通成本几乎从使用上面的草图试图确保禁用任何改进,从纯铁移动[SERIAL]过程中流入某种形式的只是 - [CONCURRENT]或道道通[PARALLEL]的一些工作,子单元编排,这是因为与必须实施(一对串联)任何一对任何值传递拓扑有关的开销增加了。

如果不是为了他们?

好吧,这听起来像是计算机科学的矛盾之词-即使有可能,任何到任何预先计算的距离的[TIME]成本(这将花费那些巨大的-域复杂性成本“事前”(在哪里?如何?有没有其他无法避免的延迟,允许通过(目前为止未知的)增量构建完整的未来任意距离矩阵来进行潜在的延迟掩盖?)),但是将这些主要是将成本重新定位到其他位置[TIME]-和- [SPACE]域,不减少'em。

“他们还有更好的选择吗?

据我所知,到目前为止,唯一的尝试就是尝试将问题重新构造为QUBO公式化的问题形式(参考:Q tumtum- U nconstrained- B inary- O ptimization)。 ,好消息是,这样做的工具,第一手知识和实践中的解决问题的经验的基础已经存在并且正在扩大)

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

性能令人叹为观止-QUBO公式化的问题O(1)在恒定时间内(在[TIME]-Domain中)有一个有前途的(!)求解器,而在-Domain中有所限制[SPACE](最近发布的LLNL技巧可能有助于避免这个物理世界,当前的QPU实现,问题的约束)尺寸)。


这是一个有趣的答案,但似乎没有抓住重点-OP会训练多个小型模型,而不是单个模型。因此,您的核心观察结果基本上无关紧要。
user10938362

@ user10938362您要求的财产(训练小型模型)如何转化为不同于上面公布的big-O的处理成本指标?当然,许多较小的模型都保证了单个(仍然)大O成本的理论上线性增长的总和(现在在N中较小,但在其他因素中则没有)但是,您必须在此基础上增加一个更为昂贵的总和设置和终止开销成本的附加成本加上所有通信开销的附加成本(参数/数据/结果+每个步骤中通常还包括成对的SER / DES处理成本)
user3666197

0

这不是答案,但是...

如果你跑

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

(即仅使用Pandas),您会注意到您已经在使用多个内核。这是因为默认情况下sklearn使用joblib来并行化工作。您可以换用Dask换掉调度程序,也许可以通过在线程之间共享数据来提高效率,但是只要您所做的工作像这样的CPU受限,您将无能为力。

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


您能否解释一下为什么工作拆分是由-spawned 进程组织的,为什么与线程无关,而与共享无关,为什么提到“ ...在线程之间共享数据...”呢?感谢您对参数的明确说明。joblib
user3666197

确实,jboblib通常使用进程,但也可以使用dask作为后端,在这里您可以选择线程和进程的混合。
mdurant

我是并行计算的新手,但是即使sklearn使用并行化,在这种设置下它也没有用吗?我的意思是,sklearn执行的操作非常简单,因为每个聚类操作仅应用于10个点。同样,我在这里可能是错的,但是我认为我们如何并行处理原始数据块的方式才是真正的问题。
库巴

“是不是很没用在此设置” -好了,你用8个CPU内核的价值的表现,而不是1
mdurant

0

我不是的专家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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.