簡體   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