Applicazione della funzione Python al DataFrame raggruppato Pandas: qual è l'approccio più efficiente per accelerare i calcoli?


9

Ho a che fare con Pandas DataFrame abbastanza grande - il mio set di dati assomiglia a una seguente dfconfigurazione:

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
                )

Nota: allo scopo di avere un esempio minimo, può essere facilmente sottoposto a sottoinsiemi (ad esempio con df.loc[df['time'] <= 400, :]), ma poiché simulo i dati comunque, ho pensato che le dimensioni originali avrebbero dato una panoramica migliore.

Per ogni gruppo definito da ['measurement_id', 'time', 'group']ho bisogno di chiamare la seguente funzione:

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 )

Per migliorare le prestazioni ho provato due approcci:

Pacchetto Pandarallel

Il primo approccio è stato quello di parallelizzare i calcoli usando il pandarallelpacchetto:

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

Tuttavia, questo sembra essere non ottimale in quanto consuma molta RAM e non tutti i core vengono utilizzati nei calcoli (anche se si specifica esplicitamente il numero di core nel pandarallel.initialize()metodo). Inoltre, a volte i calcoli vengono chiusi con vari errori, anche se non ho avuto la possibilità di trovare un motivo (forse una mancanza di RAM?).

PySpark Pandas UDF

Ho anche provato un Spark Panda UDF, anche se sono totalmente nuovo a Spark. Ecco il mio tentativo:

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()

Sfortunatamente, anche le prestazioni sono state insoddisfacenti, e da quello che ho letto sull'argomento, questo potrebbe essere solo il peso dell'uso della funzione UDF, scritta in Python e la necessità associata di convertire tutti gli oggetti Python in oggetti Spark e viceversa.

Quindi, ecco le mie domande:

  1. Uno dei miei approcci potrebbe essere adattato per eliminare eventuali strozzature e migliorare le prestazioni? (ad es. installazione di PySpark, regolazione delle operazioni non ottimali ecc.)
  2. Sono alternative migliori? Come si confrontano con le soluzioni fornite in termini di prestazioni?

2
hai fatto ricerche su Dask ?
Danila Ganchar,

1
Non ancora, ma grazie per il tuo suggerimento - Ci
proverò

sfortunatamente non ho lavorato con dask(((quindi il mio commento è solo un consiglio per la ricerca.
Danila Ganchar,

Per prestazione intendevo il tempo in cui i calcoli possono essere finiti.
Kuba_

Risposte:


1

D : " Uno dei miei approcci potrebbe essere regolato per eliminare eventuali colli di bottiglia e migliorare le prestazioni? (Ad es. Installazione di PySpark, regolazione di operazioni non ottimali ecc.) "

+1per menzionare i costi generali del componente aggiuntivo di installazione per entrambe le strategie di elaborazione. Questo fa sempre un punto di pareggio, solo dopo il quale una non strategia può raggiungere qualsiasi gioia benefica per alcuni desiderati avere -Distanza accelerata (eppure, se diversa, in genere -I costi di spesa consentono o rimangono fattibili - sì, RAM. .. esistenza e accesso a un dispositivo di queste dimensioni, budget e altri vincoli del mondo reale simili)[SERIAL][TIME][SPACE]

In primo luogo,
il controllo pre-volo, prima del decollo

La nuova formulazione rigorosa della legge di Amdahl è attualmente in grado di incorporare entrambi questi pSO + pTOcosti aggiuntivi e li riflette nella previsione dei livelli di Speedup raggiungibili, incluso il break-even punto, dal quale può diventare significativo (in termini di costi / effetti, senso dell'efficienza) andare in parallelo.

inserisci qui la descrizione dell'immagine

Tuttavia,
questo non è il nostro problema principale qui .
Questo viene dopo:

Successivamente,
dati i costi computazionali di SpectralClustering(), che sta andando qui per usare il kernel della funzione Radial Boltzmann, ~ exp( -gamma * distance( data, data )**2 )non sembra esserci alcun progresso dalla divisione di data-oggetto su un numero qualsiasi di unità di lavoro disgiunte, poiché il componente distance( data, data ), per definizione, deve solo visitare tutti gli dataelementi (rif. i costi di comunicazione di qualsiasi { process | node }topologia distribuita da qualsiasi valore che passi per valore sono, per ovvi motivi, terribilmente cattivi se non i peggiori casi d'uso per l' { process | node }elaborazione distribuita, se non i semplici schemi (ad eccezione di alcuni tessuti davvero arcani, senza memoria / senza stato, ma di calcolo).

Per gli analisti pedanti, sì - aggiungi a questo (e potremmo già dire un cattivo stato) i costi di - di nuovo - qualsiasi-a-qualsiasi -k- elaborazione - elaborazione, qui su O( N^( 1 + 5 * 5 ) )questo va, per N ~ len( data ) ~ 1.12E6+, terribilmente contro il nostro desiderio di avere un po ' elaborazione intelligente e veloce.

E allora?

Mentre i costi di installazione non sono trascurati, gli aumentati costi di comunicazione sarà quasi sicuramente disabilitare qualsiasi miglioramenti usando i tentativi sopra schizzo di passare da un puro- [SERIAL]flusso di processo in una qualche forma di solo - [CONCURRENT]o True- [PARALLEL]orchestrazione di alcuni work-sottounità , a causa delle maggiori spese generali legate all'obbligo di implementare (una coppia in tandem) topologie da qualsiasi valore a passaggio di valore.

Se non fosse per loro?

Bene, questo suona come un ossimoro della scienza informatica - anche se fosse possibile, i costi delle distanze pre-calcolate da qualsiasi a qualsiasi (che porterebbe a tali immensi [TIME]costi di complessità "anticipatamente" (Dove? Come? Esistono altra latenza non evitabile, che consente un possibile mascheramento della latenza da parte di un accumulo incrementale (finora sconosciuto) di una matrice di distanza da qualunque a qualsiasi in futuro?)), ma riposizionerebbe questi costi principalmente presenti in un'altra posizione in [TIME]- e [SPACE]-Dispone, non ridurli.

D : "Sono alternative migliori? "

L'unico, lo so fino ad ora, è provare, se è possibile riformulare il problema in un altro, una moda del problema formulata da QUBO (rif .: Q uantum- U nconstrained- B inary- O ptimisation , una buona notizia è che esistono strumenti per farlo, una base di conoscenza diretta ed esperienza pratica di risoluzione dei problemi che crescono)

D : In che modo si confrontano con le soluzioni fornite in termini di prestazioni?

Le prestazioni sono mozzafiato: il problema formulato da QUBO ha un O(1)risolutore promettente (!) A tempo costante (in [TIME]-Domain) e un po 'limitato in [SPACE]-Domain (dove i trucchi LLNL recentemente annunciati possono aiutare a evitare questo mondo fisico, l'attuale implementazione di QPU, il vincolo del problema dimensioni).


Questa è una risposta interessante, ma sembra mancare il punto: OP addestra più piccoli modelli, non uno solo. Quindi la tua osservazione principale è per lo più irrilevante.
user10938362

@ user10938362 In che modo la tua proprietà rivendicata (formazione di modelli di piccole dimensioni) si traduce in una metrica big-O diversa dai costi di elaborazione pubblicati in precedenza? Sicuramente molti modelli più piccoli promettono una somma teoricamente solo linearmente crescente di costi (ancora) big-O di elaborazione individuale (ora più piccoli in N, ma non in altri fattori) , tuttavia, è necessario aggiungere a questo una somma terribilmente più costosa di tutti costi aggiuntivi di entrambi i costi generali di installazione e terminazione più tutti i costi aggiuntivi di comunicazione aggiuntiva (parametri / dati / risultati + in genere anche coppie di costi di elaborazione SER / DES in ogni fase)
user3666197

0

Questa non è una risposta, ma ...

Se corri

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

(vale a dire, solo con i panda), noterai che stai già utilizzando diversi core. Questo perché sklearnutilizza joblibper impostazione predefinita per parallelizzare il lavoro. È possibile scambiare l'utilità di pianificazione a favore di Dask e forse ottenere più efficienza nel condividere i dati tra i thread, ma a condizione che il lavoro che state facendo è CPU-bound in questo modo, non ci sarà niente che puoi fare per accelerarlo.

In breve, questo è un problema di algoritmo: capire cosa è veramente necessario calcolare, prima di provare a considerare diversi framework per calcolarlo.


Potresti spiegare perché dici "... condividere i dati tra thread ..." una volta che la divisione del lavoro è stata organizzata da processijoblib creati , che non hanno nulla a che fare con i thread, tanto meno con la condivisione? Grazie per il gentile chiarimento degli argomenti.
user3666197

Esattamente, jboblib normalmente usa i processi, ma in alternativa può usare dask come backend, dove puoi scegliere il tuo mix di thread e processi.
mdurant

Sono un novizio del calcolo parallelo, ma anche se sklearn usa il parallelismo, non è inutile in queste impostazioni? Voglio dire, le operazioni eseguite da sklearn sono estremamente semplici in quanto ogni operazione di clustering viene applicata a soli 10 punti. Ancora una volta, potrei sbagliarmi qui, ma penso che il vero problema sia il modo in cui parallelizziamo l'elaborazione di blocchi di dati originali.
Kuba_

"non è inutile in queste impostazioni" - beh, usi le prestazioni di 8 core della CPU invece di 1.
mdurant

0

Non sono un esperto Dask, ma fornisco il seguente codice come base:

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.