Застосування функції 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.initialize( progress_bar = True )
df \
  .groupby( ['measurement_id', 'time', 'group'] ) \
  .parallel_apply( lambda x: cluster( x['var'], x['object'] ) )

Однак це здається недостатньо оптимальним, оскільки воно споживає багато оперативної пам’яті, і не всі ядра використовуються в обчисленнях (навіть незважаючи на те, що в pandarallel.initialize()методі чітко вказано кількість ядер ). Крім того, іноді обчислення закінчуються різними помилками, хоча у мене не було можливості знайти причину для цього (можливо, відсутність оперативної пам’яті?).

PySpark Pandas UDF

Я також дав іскровим пандам 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()

На жаль, продуктивність також була незадовільною, і з того, що я читав по темі, це може бути лише тягарем використання функції UDF, написаної на Python, і пов'язаної з цим необхідності перетворення всіх об’єктів Python в об'єкти Spark і назад.

Тож ось мої запитання:

  1. Чи міг би бути скоригований будь-який із моїх підходів для усунення можливих вузьких місць та підвищення продуктивності? (наприклад, налаштування PySpark, коригування субоптимальних операцій тощо)
  2. Чи є якісь кращі альтернативи? Як вони порівнюють пропоновані рішення за показниками продуктивності?

2
Ви досліджували даск ?
Даніла Ганчар

1
Ще немає, але дякую за вашу пропозицію - я
підкажу

на жаль, я не працював dask((тому мій коментар - це лише порада для дослідження.
Даніла Ганчар,

Під виконанням я мав на увазі час, за який можна закінчити обчислення.
Kuba_

Відповіді:


1

Питання : " Чи міг би бути налаштований будь-який із моїх підходів для усунення можливих вузьких місць та підвищення продуктивності? (Наприклад, налаштування PySpark, коригування субоптимальних операцій тощо) "

+1згадування накладних витрат на додаткові настройки для будь-якої стратегії обчислень. Це завжди означає точку беззбитковості, лише після якої нестратегічна стратегія може досягти будь-якої користі від деякого бажаного прискорення домену - домену (але, якщо інші, як правило, - Доменні витрати дозволяють або залишаються нездійсненними - так, оперативна пам'ять. .. наявність та доступ до такого розміру пристрою, бюджету та інших подібних обмежень у реальному світі)[SERIAL][TIME][SPACE]

По-перше,
перевірка перед польотом, перед тим, як здійснити зліт

. Нова, чітко визначена в законі Амдаля норма в даний час може включати обидва ці додаткові pSO + pTOнакладні витрати і відображає це в прогнозуванні досяжних рівнів Швидкості, включаючи беззбитковість точка, з якої може стати сенсом (у розумінні витрат / ефекту, ефективності) йти паралельно.

введіть тут опис зображення

Але
це не є нашою основною проблемою тут .
Це наступне:

Далі,
зважаючи на обчислювальні витрати SpectralClustering(), які збираються тут використовувати ядро ​​Радиальної функції Больцмана, ~ exp( -gamma * distance( data, data )**2 )схоже, немає передового розколу на data-об'єкт на будь-яку кількість відокремлених робочих одиниць, як distance( data, data )-компонент, за визначенням, має, але відвідати всі data-елементи (посилання на витрати на комунікацію будь-яких цінностей- { process | node }розподілених топологій, з очевидних причин, жахливо погано, якщо не найгірші випадки використання для { process | node }-розподіленої обробки, якщо не прямі анти-шаблони (за винятком деяких дійсно затаємничених, без пам'яті / без стану, але все ж обчислювальних тканин).

Для педантичних аналітиків, так - додайте до цього (і ми вже можемо сказати, поганий стан) витрати - знову ж таки - будь-який-будь-який k-означає-на переробку, ось про O( N^( 1 + 5 * 5 ) )це йдеться, бо N ~ len( data ) ~ 1.12E6+дуже проти нашого бажання мати деяку кількість розумна і швидка обробка.

І що?

Незважаючи на те, що витрати на налаштування не нехтують, збільшені витрати на комунікацію майже напевно відключать будь-яке поліпшення від використання вищезазначених замальованих спроб перейти від [SERIAL]потоку чистого процесу до якоїсь форми справедливої - [CONCURRENT]або правдивої [PARALLEL]оркестрації деяких робочих підрозділів. через збільшення накладних витрат, пов’язаних із необхідністю впроваджувати (тандемну пару) будь-яких топологій, що передають значення, до будь-яких цінностей.

Якби не їх?

Ну, це звучить як оксиморон Computing Science - навіть якщо б це було можливо, витрати на будь-які заздалегідь обчислені відстані (які б зайняли ці величезні [TIME]витрати на доменну складність "заздалегідь" (Де? Як? Чи є якісь інша, затримка, яку не можна уникнути, дозволяючи маскувати затримку за допомогою деякої (невідомої до цього часу) поступового нарощування повної майбутньої матриці будь-якої відстані?)), але пересуває ці принципово поточні витрати в інше місце в [TIME]- і [SPACE]-Домени, не зменшуйте їх.

Питання : "Чи є кращі альтернативи? "

Єдине, про що я знаю, поки що - це спробувати, якщо проблему можливо переформулювати в іншу, проблемну моду, сформульовану QUBO (посилання: Q uantum- U nconstrained- B inary- O ptimisation , хороша новина полягає в тому, що інструменти для цього, база знань з перших рук і практичний досвід вирішення проблем існують і розширюються)

Питання : Як вони порівнюють із запропонованими рішеннями щодо продуктивності?

Продуктивність захоплююча - проблема, сформульована QUBO, має багатообіцяючий O(1)(!) [TIME]Вирішення постійного часу (в -Domain) і дещо обмежене в [SPACE]-Domain (де нещодавно оголошені хитрощі LLNL можуть допомогти уникнути цього фізичного світу, поточна реалізація QPU, обмеження проблеми розміри).


Це цікава відповідь, але, здається, не вдається проголосувати - ОП тренує декілька маленьких моделей, не одну. Тож ваше основне спостереження здебільшого не має значення.
користувач10938362

@ user10938362 Як ваша власність (навчання невеликих моделей) перекладається на якусь іншу, ніж вище розміщену велику метрику витрат на обробку? Звичайно, багато менших моделей обіцяють теоретично просто лінійно зростаючу суму (все-таки) великих витрат на O (індивідуальні (тепер менші за N, але не за іншими факторами)) , однак ви повинні додати до цього набагато дорожчу суму всіх витрати на додаткові витрати на встановлення та припинення-накладні витрати плюс усі додаткові накладні витрати на комунікацію (параметри / дані / результати + зазвичай також пари витрат на обробку SER / DES на кожному кроці)
користувач3666197

0

Це не відповідь, але ...

Якщо ти біжиш

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

(тобто, лише з Pandas), ви помітите, що ви вже використовуєте кілька ядер. Це відбувається тому , що sklearnвикористовує joblibза замовчуванням для parallelise роботи. Ви можете поміняти помічник планувальника на користь Dask і, можливо, отримати більшу ефективність при обміні даними між потоками, але поки робота, яку ви виконуєте, пов'язана з процесором, як це, ви нічого не зробите, щоб прискорити її.

Коротше кажучи, це проблема алгоритму: з’ясуйте, що вам потрібно насправді обчислити, перш ніж спробувати розглянути різні рамки для його обчислення.


Чи можете ви пояснити, чому ви згадуєте "... обмін даними між потоками ...", коли організаційний розкол був організованийjoblib -spawned процесів , які не мають нічого спільного з потоками, тим менше з обміном? Дякую вам за добрі роз'яснення аргументів.
user3666197

Точно, jboblib зазвичай використовує процеси, але альтернативно він може використовувати даск як бекенд, де ви можете вибрати свою суміш потоків і процесів.
mdurant

Я є новачком паралельних обчислень, але навіть якщо sklearn використовує паралелізацію, чи не марний у цих налаштуваннях? Я маю на увазі, що операції, проведені sklearn, надзвичайно прості, оскільки кожна операція кластеризації застосовується лише до 10 балів. Знову ж, я можу помилитися тут, але я думаю, що проблема, як ми паралелізуємо обробку фрагментів оригінальних даних, є справжньою проблемою.
Kuba_

"чи не марно в цих налаштуваннях" - добре, ви використовуєте 8 ядер процесора,
якісні

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.