2013-08-26 2 views
10

Я использую numpy, где функция много раз внутри нескольких контуров for, но она становится слишком медленной. Есть ли способы быстрее выполнять эту функцию? Я читал, что вы должны пытаться делать строки в циклах, а также создавать локальные переменные для функций перед циклами for, но ничто не улучшает скорость на много (< 1%). len(UNIQ_IDS) ~ 800. emiss_data и obj_data - numpy ndarrays с формой = (2600,5200). Я использовал import profile, чтобы получить ручку, где расположены узкие места, а where в for петлях - большая.быстрый python numpy, где функциональность?

import numpy as np 
max = np.max 
where = np.where 
MAX_EMISS = [max(emiss_data[where(obj_data == i)]) for i in UNIQ_IDS)] 
+0

А вы вычисление 'UNIQ_IDS' в этом скрипте или это предопределено? – Daniel

+0

UNIQ_IDS предопределен ... список ints len = 800.Это всего лишь фрагмент кода, извините за путаницу. –

ответ

2

Разве вы не можете просто сделать

emiss_data[obj_data == i] 

? Я не уверен, почему вы используете where.

+0

Хорошо, что работает, и это улучшение на ~ 45%. Благодарю. Я предполагаю, что я использую, где, потому что я так привык к IDL и пытаюсь преобразовать в python. Однако он все еще очень медленный. Это занимает 75 секунд, чтобы сделать это 800 раз, тогда как IDL сделал бы это за 2 секунды. И что, если вам действительно нужны местоположения/индексы для будущих операций? Я не думаю, что это было бы очень эффективно, если бы вы использовали его несколько раз в цикле for вместо выражения where в цикле for. –

+0

Кажется, что должен быть способ группировать значения 'emiss_data' значениями' obj_data' с помощью встроенных модулей numpy. Однако я его не нашел. – user2357112

+0

Вы можете использовать 'np.lexsort'; однако сам 'lexsort' является узким местом, ведущим к субоптимальному решению. – Daniel

0

Назначение кортежа выполняется намного быстрее, чем назначение списка в соответствии с Are tuples more efficient than lists in Python?, поэтому, возможно, просто создав кортеж вместо списка, это повысит эффективность.

+1

Я сомневаюсь. В некоторых случаях кортежи имеют преимущества, но ни один из них не применяется здесь. Этот вопрос (вернее, принятый ответ там) не показывает, что кортежи быстрее конструируются, он показывает, что * буквальные * кортежи можно построить один раз и использовать несколько раз. И даже если создание кортежей * было быстрее, чем создание списка, нет никакого пути, которое является узким местом. – delnan

+0

Спасибо за информацию! – Jblasco

7

Оказывается, что чистый цикл Python может быть намного быстрее, чем индексирование NumPy (или вызовы np.where) в этом случае.

Рассмотрим следующие варианты:

import numpy as np 
import collections 
import itertools as IT 

shape = (2600,5200) 
# shape = (26,52) 
emiss_data = np.random.random(shape) 
obj_data = np.random.random_integers(1, 800, size=shape) 
UNIQ_IDS = np.unique(obj_data) 

def using_where(): 
    max = np.max 
    where = np.where 
    MAX_EMISS = [max(emiss_data[where(obj_data == i)]) for i in UNIQ_IDS] 
    return MAX_EMISS 

def using_index(): 
    max = np.max 
    MAX_EMISS = [max(emiss_data[obj_data == i]) for i in UNIQ_IDS] 
    return MAX_EMISS 

def using_max(): 
    MAX_EMISS = [(emiss_data[obj_data == i]).max() for i in UNIQ_IDS] 
    return MAX_EMISS 

def using_loop(): 
    result = collections.defaultdict(list) 
    for val, idx in IT.izip(emiss_data.ravel(), obj_data.ravel()): 
     result[idx].append(val) 
    return [max(result[idx]) for idx in UNIQ_IDS] 

def using_sort(): 
    uind = np.digitize(obj_data.ravel(), UNIQ_IDS) - 1 
    vals = uind.argsort() 
    count = np.bincount(uind) 
    start = 0 
    end = 0 
    out = np.empty(count.shape[0]) 
    for ind, x in np.ndenumerate(count): 
     end += x 
     out[ind] = np.max(np.take(emiss_data, vals[start:end])) 
     start += x 
    return out 

def using_split(): 
    uind = np.digitize(obj_data.ravel(), UNIQ_IDS) - 1 
    vals = uind.argsort() 
    count = np.bincount(uind) 
    return [np.take(emiss_data, item).max() 
      for item in np.split(vals, count.cumsum())[:-1]] 

for func in (using_index, using_max, using_loop, using_sort, using_split): 
    assert using_where() == func() 

Вот ориентиры, с shape = (2600,5200):

In [57]: %timeit using_loop() 
1 loops, best of 3: 9.15 s per loop 

In [90]: %timeit using_sort() 
1 loops, best of 3: 9.33 s per loop 

In [91]: %timeit using_split() 
1 loops, best of 3: 9.33 s per loop 

In [61]: %timeit using_index() 
1 loops, best of 3: 63.2 s per loop 

In [62]: %timeit using_max() 
1 loops, best of 3: 64.4 s per loop 

In [58]: %timeit using_where() 
1 loops, best of 3: 112 s per loop 

Таким образом using_loop (чистый Python) оказывается больше, чем 11x быстрее, чем using_where.

Я не совсем уверен, почему чистый Python быстрее, чем NumPy здесь. Я предполагаю, что чистая версия Python zips (да, каламбур) через оба массива один раз. Он использует тот факт, что, несмотря на все причудливое индексирование, , мы действительно просто хотим посетить каждое значение после. Таким образом, он борется с тем, что нужно точно определить, в какую группу попало каждое значение в emiss_data. Но это просто расплывчатая спекуляция. Я не знал, что это будет быстрее, пока я не сравню их.

+0

Что такое «список» в use_loop? –

+0

['collections.defaultdict (list)'] (http://docs.python.org/2/library/collections.html#collections.defaultdict) создает объект типа dict, который возвращает список как значение по умолчанию. – unutbu

7

Можно использовать np.unique с return_index:

def using_sort(): 
    #UNIQ_IDS,uind=np.unique(obj_data, return_inverse=True) 
    uind= np.digitize(obj_data.ravel(), UNIQ_IDS) - 1 
    vals=uind.argsort() 
    count=np.bincount(uind) 

    start=0 
    end=0 

    out=np.empty(count.shape[0]) 
    for ind,x in np.ndenumerate(count): 
     end+=x 
     out[ind]=np.max(np.take(emiss_data,vals[start:end])) 
     start+=x 
    return out 

Используя ответ @ unutbu в качестве основы для shape = (2600,5200):

np.allclose(using_loop(),using_sort()) 
True 

%timeit using_loop() 
1 loops, best of 3: 12.3 s per loop 

#With np.unique inside the definition 
%timeit using_sort() 
1 loops, best of 3: 9.06 s per loop 

#With np.unique outside the definition 
%timeit using_sort() 
1 loops, best of 3: 2.75 s per loop 

#Using @Jamie's suggestion for uind 
%timeit using_sort() 
1 loops, best of 3: 6.74 s per loop 
+2

Я думаю, что если 'UNIQ_IDS' действительно имеет уникальные записи' obj_data', предварительно рассчитанные, вы можете вызвать 'np.digitize (obj_data, UNIQ_IDS) - 1', чтобы получить тот же результат, что и ваш' uind', примерно через половину времени. – Jaime

+0

Ваш метод действительно умный, но, к сожалению, я не могу получить такое же увеличение скорости. (Я добавил контрольный показатель для 'use_sort' при запуске на моем компьютере в моем сообщении. Для меня' use_loop' все еще немного быстрее.) Возможно, разница связана с версией Python или ОС? Я использую Python 2.7 на Ubuntu 11.10. Что вы используете? – unutbu

+0

@unutbu Я использую OSX и полностью обновленную установку anaconda (у нее есть ускорение, которое, как мне известно, в прошлом закручивало тайминги). Я также попытался использовать python 2.7.4 и numpy 1.7.1 в OSX, и я получил те же результаты; однако я попробовал в ящике Ubuntu с чипом AMD с numpy 1.6.1 и нашел, что тайминги будут эквивалентными. Мне не нравится оставлять сообщение [this] (http://stackoverflow.com/questions/18365073/why-is-numpys-einsum-faster-than-numpys-built-in-functions), но, похоже, что-то происходит с таймингами, которые я не совсем понимаю. – Daniel

5

Я считаю, что самый быстрый способ для достижения этой цели является использование groupby() операций в pandas упаковка. По сравнению с @using_sort() функции Офиона, в панде составляет около фактора 10 быстрее:

import numpy as np 
import pandas as pd 

shape = (2600,5200) 
emiss_data = np.random.random(shape) 
obj_data = np.random.random_integers(1, 800, size=shape) 
UNIQ_IDS = np.unique(obj_data) 

def using_sort(): 
    #UNIQ_IDS,uind=np.unique(obj_data, return_inverse=True) 
    uind= np.digitize(obj_data.ravel(), UNIQ_IDS) - 1 
    vals=uind.argsort() 
    count=np.bincount(uind) 

    start=0 
    end=0 

    out=np.empty(count.shape[0]) 
    for ind,x in np.ndenumerate(count): 
     end+=x 
     out[ind]=np.max(np.take(emiss_data,vals[start:end])) 
     start+=x 
    return out 

def using_pandas(): 
    return pd.Series(emiss_data.ravel()).groupby(obj_data.ravel()).max() 

print('same results:', np.allclose(using_pandas(), using_sort())) 
# same results: True 

%timeit using_sort() 
# 1 loops, best of 3: 3.39 s per loop 

%timeit using_pandas() 
# 1 loops, best of 3: 397 ms per loop 
0

Если obj_data состоит из относительно небольших целых чисел, вы можете использовать numpy.maximum.at (с v1.8.0):

def using_maximumat(): 
    n = np.max(UNIQ_IDS) + 1 
    temp = np.full(n, -np.inf) 
    np.maximum.at(temp, obj_data, emiss_data) 
    return temp[UNIQ_IDS] 
Смежные вопросы