Я работаю над функцией из книги Лопеса де Прадо. Вот минимальный пример того, что я пытаюсь сделать:
import numpy as np
import pandas as pd
import multiprocessing as mp
# Define test function
def test_func(idx, a, b):
return [idx, a**1 + b**2]
test_func = np.vectorize(test_func)
test_df = pd.DataFrame(np.random.normal(size = (10, 2)), columns = ['a', 'b'])
test_df = test_df.reset_index()
func_dict = {col:test_df[col] for col in test_df.columns}
# Function used to evaluate test_func
def expandCall(kargs):
# Get the function argument
func = kargs['func']
# Delete it from the fictionary
del kargs['func']
# Evaluate function with other arguments
out = func(**kargs)
return out
parts = np.linspace(0, test_df.shape[0], 2).astype(int)
jobs = []
for i, j in zip(parts[:-1], parts[1:]):
job = {key:func_dict[key][i:j] for key in func_dict}
job.update({'func':test_func})
jobs.append(job)
pool = mp.Pool(processes = 2)
outputs = pool.imap_unordered(expandCall, jobs)
out = []
# Process asynchronous output, report progress
for out_ in outputs:
out.append(out_)
pool.close()
pool.join()
Я получаю ошибку
PicklingError: невозможно выбрать <функцию test_func по адресу 0x0000027FBF960F40>: это не тот же объект, что и --main--.test_func
Если я правильно читаю книгу, Лопес де Прадо предлагает решение:
def _unpickle_method(func_name, obj, cls):
for cls in cls.mro():
try:
func = cls.__dict__[func_name]
except KeyError:
pass
else:
break
return func.__get__(obj, cls)
def _pickle_method(method):
func_name = method.im_func.__name__
obj = method.im_self
cls = method.im_class
return _unpickle_method, (func_name, obj, cls)
import copyreg, types
copyreg.pickle(types.MethodType, _pickle_method, _unpickle_method)
Однако у меня это не работает. Возможно, из-за изменений в модуле многопроцессорности с момента выхода книги. К сожалению, я недостаточно хорошо понимаю решение, чтобы обновлять его. Буду признателен за любую помощь в запуске моего минимального примера.
Опубликованный вами метод позволяет вам выбирать методы класса, лямбды не являются методом класса, по крайней мере, в python3, а стандартный сборщик Python не позволяет выбирать лямбды.
вы можете использовать cloudpickle, который позволяет вам мариновать лямбды, вам нужно сделать obj = cloudpickle.dumps(real_object)
, а затем передать это obj
в пул, а затем real_object = cloudpickle.loads(obj)
на другую сторону.
np.vectorize написан на C и требует добавления поддержки травления в C, но вы можете обойти это, обернув его в простую «обертку» для облегчения травления, см. документы __getstate__ и __setstate__
import numpy as np
import pandas as pd
import multiprocessing as mp
class vectorize_wrapper:
def __init__(self, pyfunc):
self.func = np.vectorize(pyfunc)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def __setstate__(self, state):
self.func = np.vectorize(state)
def __getstate__(self):
return self.func.pyfunc
def test_func(idx, a, b):
return [idx, a**1 + b**2]
test_func_vectorized = vectorize_wrapper(test_func)
import pickle
a = pickle.dumps(test_func_vectorized)
b = pickle.loads(a)
Основное ограничение заключается в том, что векторизованная функция должна быть импортируемой, вы не можете иметь другую функцию или объект с таким же именем в том же файле, и она должна находиться в глобальной области видимости, другими словами, Python может получить к ней доступ, набрав
from some_module import my_python_function
потому что это то, что делает стандартный сборщик. Я думаю, что у Cloudpickle нет такого строгого требования.
@Charles0349 Charles0349 Я добавил ответ для части векторизации, «обертка» должна действовать точно так же, как np.vectorize
, за исключением того, что ее можно мариновать, но функции маринования - это то, что не очень переносимо, и в будущем у вас будет гораздо больше проблем.
@Charles0349 Charles0349 вместо функций травления вам следует выбирать классы, способ настройки классов обычно гарантирует, что пиклер сможет выполнять свою работу.
Я больше не получаю эту ошибку, но, как ни странно, мой минимальный пример работает уже больше часа, когда test_df содержит всего 20 строк, а задача разбита на 5 частей. (Для такого маленького примера я ожидаю, что он будет работать немного медленнее, чем прямой подход). Тем не менее, я не хочу, чтобы миссия в этом посте была полна, поэтому я собираюсь дать вам чек. Спасибо за помощь!
Извините, я не знал, что лямбда-функции нельзя собирать. Использование лямбда-функции вообще не обязательно (хотя векторизация важна). Я запускал минимальные примеры без лямбда-функций, и это не сработало. Я попробую обновить код.