Ускорить вложенный цикл for с элементами возведения в степень

Я работаю над большим кодом, и мне нужно ускорить определенный бит. Я создал MWE показано ниже:

import numpy as np
import time

def random_data(N):
    # Generate some random data.
    return np.random.uniform(0., 10., N).tolist()

# Lists that contain all the data.
list1 = [random_data(10) for _ in range(1000)]
list2 = [random_data(1000), random_data(1000)]

# Start taking the time.
tik = time.time()

list4 = []
# Loop through all elements in list1.
for elem in list1:

    list3 = []
    # Loop through elements in list2.
    for elem2 in zip(*list2):

        A = np.exp(-0.5*((elem[0]-elem2[0])/elem[3])**2)
        B = np.exp(-0.5*((elem[1]-elem2[1])/elem[3])**2)
        list3.append(A*B)

    # Sum elements in list3 and append result to list4.
    sum_list3 = sum(list3) if sum(list3)>0. else 1e-06
    list4.append(sum_list3)

# Print the elapsed time.
print time.time()-tik

странный формат list1 и list2 потому что именно так этот блок кода получает их.

очевидная часть, где большая часть времени тратится на рекурсивный расчет A и B термины.

есть ли способ ускорить этот блок кода без необходимости распараллелить его (я пробовал это раньше, и он дал мне много бед)? Я открыт для использования любого пакета,numpy, scipy, etc..


добавить

это результат применения оптимизаций abarnert, а также Хайме советую сделать только одно возведение в степень. Оптимизированная функция в среднем ~60x быстрее в моей системе.

import numpy as np
import timeit

def random_data(N):
    return np.random.uniform(0., 10., N).tolist()

# Lists that contain all the data.
list1 = [random_data(10) for _ in range(1000)]
list2 = [random_data(1000), random_data(1000)]

array1 = np.array(list1)
array2 = np.array(zip(*list2))


# Old non-optimezed function.
def func1():
    list4 = []
    # Process all elements in list1.
    for elem in list1:
        # Process all elements in list2.
        list3 = []
        for elem2 in zip(*list2):
            A = np.exp(-0.5*((elem[0]-elem2[0])/elem[3])**2)
            B = np.exp(-0.5*((elem[1]-elem2[1])/elem[3])**2)
            list3.append(A*B)
        # Sum elements in list3 and append result to list4.
        sum_list3 = sum(list3) if sum(list3)>0. else 1e-06
        list4.append(sum_list3)

# New optimized function.
def func2():
    list4 = []
    # Process all elements in list1.
    for elem in array1:

        # Broadcast over elements in array2.
        A = -0.5*((elem[0]-array2[:,0])/elem[3])**2
        B = -0.5*((elem[1]-array2[:,1])/elem[3])**2
        array3 = np.exp(A+B)

        # Sum elements in array3 and append result to list4.
        sum_list3 = max(array3.sum(), 1e-10)
        list4.append(sum_list3)


# Get time for both functions.
func1_time = timeit.timeit(func1, number=10)
func2_time = timeit.timeit(func2, number=10)

# Print hom many times faster func2 is versus func1.
print func1_time/func2_time

1 ответов


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

первый шаг-не делать zip(*list2) снова и снова (особенно если это Python 2.икс.) Пока мы на нем, мы могли бы также сохранить его в массиве и сделать то же самое с list1-вы все еще можете перебирать их. Итак:

array1 = np.array(list1)
array2 = np.array(zip(*list2))
# …
for elem in array1:
    # …
    for elem2 in array2:

это не ускорит процесс многое-на моей машине это занимает от 14,1 секунды до 12,9-но это дает нам возможность начать работать.

вы также должны удалить двойной расчет sum(list3):

sum_list3 = sum(list3)
sum_list3 = sum_list3 if sum_list3>0. else 1e-06

между тем, это немного странно, что вы хотите value <= 0 перейти к 1e-6, а 0 < value < 1e-6 чтобы его оставили в покое. Это действительно намеренно? Если нет, вы можете исправить это и одновременно упростить код, просто сделав следующее:

sum_list3 = max(array3.sum(), 1e-06)

теперь давайте транслировать A и B расчет:

# Broadcast over elements in list2.
A = np.exp(-0.5*((elem[0]-array2[:,0])/elem[3])**2)
B = np.exp(-0.5*((elem[1]-array2[:, 1])/elem[3])**2)
array3 = A*B

# Sum elements in list3 and append result to list4.
sum_list3 = max(array3.sum(), 1e-06)

list4.append(sum_list3)

и это снижает нас с 12,9 секунды до 0,12. Вы можете пойти еще дальше, также транслируя через array1 и заменить list4 С предварительно выделенным массивом и так далее, но это, вероятно, уже достаточно быстро.