2013-02-27 5 views
1

У меня есть структура вроде этого:среднее массива из массива

a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]]) 

это 2х2 структура, каждый элемент может иметь произвольный размер

Я хочу, чтобы получить среднее значение каждого элемента a:

np.mean(a) 

ValueError: operands could not be broadcast together with shapes (3) (5) 

Я пытался играть с axis, но он не работает. Я хочу получить новый np.array, равный [[2, 2], [2, 2].

В общем, я хочу иметь возможность запускать любую векторную функцию на a таким же образом.

Как это сделать? Мне нужен быстрый код, поэтому, пожалуйста, избегайте явных циклов.

Лучшее, что я могу сделать, это:

f = np.mean 
result = np.zeros(a.shape) 
for i in np.ndindex(a.shape): 
    result[i] = f(a[i]) 
+1

Что именно вы подразумеваете под «быстрым кодом»? Если 'a' всегда будет 2x2, но его элементы могут быть огромными, выполнение медленного внешнего цикла 2x2 не имеет никакого значения, если вы все еще выполняете быструю итерацию по внутренним петлям (что' np.mean' делает). – abarnert

+0

быстрый способ «быстро по мере возможности». 'a' является матрицей' N x M x ... 'с N, M, ...~ 10, размеры должны быть <~ 4. Каждый элемент может быть довольно большим. Вероятно, я должен попытаться распараллелить оценку, используя поток для каждого матричного элемента. –

+0

Но вы не делаете что-то как можно быстрее, оптимизируя что-то, что занимает менее 2% от общего времени. – abarnert

ответ

3

Я предполагаю, что вы хотите numpy.vectorize, которая в основном как встроено map для ndarrays.

>>> a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]]) 
>>> vmean = np.vectorize(np.mean) 
>>> vmean(a) 
array([[ 2., 2.], 
     [ 2., 2.]]) 
+0

приятно! кстати, вы должны объяснить мне немного больше. –

+1

Проблема с 'vectorize' заключается в том, что она дает иллюзию эффективности numpy, но выполняет ее как чистый цикл python. – Jaime

+0

@Jaime: +1 для указания этого. Это по-прежнему полезно для простоты - это лучше, чем, например, получение итератора массиву для перехода к карте и перехода к конструктору массива. Но это не спасет времени. – abarnert

3

Это лучшее, что я могу сделать:

a = np.array([[np.array([1,2,3]), np.array([2])], [np.array([0,1,2,3,4]), np.array([0,4])]]) 
np.array([ np.mean(b) for b in a.ravel()]).reshape(a.shape) 

выходы

array([[ 2., 2.], 
     [ 2., 2.]]) 

Может быть обобщена для других функций:

def ravel_func(func,arr): 
    return np.asarray([ func(part) for part in arr.ravel()]).reshape(arr.shape) 

теста скорости, благодаря Jaime

In [82]: timeit vmean(a) 
10000 loops, best of 3: 65 us per loop 

In [83]: timeit ravel_func(np.mean,a) 
10000 loops, best of 3: 67 us per loop 
+2

Вы используете понимание списка, не очень отличающееся от явного цикла. Я хотел бы использовать некоторую функцию numpy –

+0

Более ник, да, но такая же скорость. – askewchan

2

От вашего комментария: у вас относительно небольшое количество довольно больших элементов. Это означает, что скорость итерации внешнего цикла не имеет значения, в то время как скорость итераций внутренних циклов имеет решающее значение.

Приведем некоторые фактические цифры. Ваш внешний массив имеет до 4-х размеров до размера 10. Это означает, что существует до 10000 элементов. Между тем, элементы «довольно большой» -лет интерпретируют так консервативно, как всего 50. Итак, у вас есть итерации 510000. Все, что вы делаете, чтобы улучшить скорость 10000 внешних итераций, составит менее 2% разницы в вашем коде. Фактически, это намного меньше, чем то, что 2% полагают, что нет никакой работы, кроме самой итерации, что, очевидно, неверно.

Итак, вы фокусируетесь на неправильном месте. Только 500 000 внутренних итераций имеют значение. Если бы вы могли заменить массив массивов на один массив с одним размером и сделать все это в numpy, это может быть быстрее, но сделать ваш код намного сложнее и трудно понять для выигрыша порядка нескольких 1% глупо. Просто используйте ответ vectorize ответ или понимание, которые просты и очевидны.


Тем:

Вероятно, я должен попытаться распараллелить оценку, используя поток для каждого элемента матрицы.

Параллелизм - хорошая идея здесь. Но не использовать потоки, а не по одному элементу.

Если у вас есть, скажем, 8-ядерная машина, использующая 8 аппаратных потоков, вы получаете почти 8-кратную скорость. Но использование 10000 аппаратных потоков не означает, что вы делаете это в 10000 раз быстрее, или даже в 8 раз быстрее - вы, вероятно, тратите столько времени на переключение контекста, распределение и освобождение стеков потоков и т. Д., Что на самом деле это занимает больше времени, чем последовательный версия. Таким образом, вы хотите создать рабочий пул из 8 аппаратных потоков и вставить свои 10000 задач в очередь, которую должен запустить этот пул.

Кроме того, 10000, вероятно, слишком гранулирован. У каждой работы немного накладных расходов, и если ваши задания слишком малы, вы тратите слишком много времени на накладные расходы. Очевидные способы пакетной обработки на одну ось - 1000 заданий, каждый из которых выполняет одну строку из 10 элементов или 100 заданий, каждый из которых выполняет один 2D-массив из 100 элементов. Тестирование размеров партии 0, 1, 2 и 3 осей и см., Что дает лучшую производительность. Если различия огромны, вам может потребоваться несколько более сложное дозирование, например, разбиение на 3D-массивы 3x10x10 и 4x10x10.

Наконец, хотя потоки Python являются настоящими потоками операционной системы (и, следовательно, аппаратного), GIL предотвращает запуск одного из нескольких кодов Python за один раз. numpy делает некоторые работы, чтобы обойти это, но это не идеально (особенно если у вас много накладных расходов между вызовами numpy). Самый простой способ - использовать multiprocessing, который позволяет иметь пул из 8 отдельных процессов вместо 8 потоков в 1 процессе. Это значительно увеличивает значительную нагрузку - вам либо нужно скопировать субмассивы (либо неявно, как параметры, так и возвращаемые значения функций задачи или явно через канал) или поместить их в общую память. Но если размер ваших задач достаточно велик, это обычно не проблема.


Вот пример того, как распараллелить существующий код:

f = np.mean 
result = np.zeros(a.shape) 
future_map = {} 
with futures.ProcessPoolExecutor(max_workers=8) as executor: 
    for i in np.ndindex(a.shape): 
     future_map[executor.submit(f, a[i])] = i 
    for future in futures.as_completed(future_map): 
     i = future_map[future] 
     result[i] = future.result() 

Очевидно, что вы могли бы упростить (например, заменить цикл представления с dict понимания), но я хотел бы сделать это насколько это возможно, что вам нужно изменить.

Кроме того, я использую futures (для чего требуется 3.2+, если вы используете 2,7, установите backport из PyPI), потому что он делает код немного проще; если вам нужна большая гибкость, вам нужна более сложная библиотека multiprocessing.

И, наконец, я не делаю никаких дозированных операций - каждая задача - это один элемент, поэтому накладные расходы, вероятно, будут довольно плохими.

Но начните с этого, упростите его, насколько вам удобно, затем преобразуйте его, чтобы использовать партии 1, 2 и 3 осей и т. Д., Как описано ранее.

Смежные вопросы