Для-петли с массива numpy слишком медленно


Я пытаюсь ускорить выполнение следующей задачи:

У меня есть две функции, которые используются вместе.

Здесь представлены объяснил функции:

Первая функция имитирует N монета переворачивается и возвращает массив строк с результатами:

def throw_a_coin(N):
    return np.random.choice(['H','T'], size=N)

Вторая функция принимает в number_of_samples, sample_size и возвращает sample_probs. Данная функция имитирует number_of_samples пробеги sample_size монета переворачивается и сохраняет результат num_heads / sample size в sample_probs для каждого прогона.

Вот код:

def make_throws(number_of_samples, sample_size):
    sample_probs = np.zeros(number_of_samples)
    for i in range(number_of_samples):
        num_heads = sum(throw_a_coin(sample_size) == "H")
        sample_probs[i] = float(num_heads) / sample_size
    return sample_probs

Теперь я хочу, чтобы имитировать несколько прогонов с разным sample_sizes и 200 выполняется для каждого размера образца. Так sample_size = 1 Я буду делать 200 пробегов 1 монетку, для sample_size = 10 я буду делать 200 пробегов 10 бросков монеты, и так далее.

Вот код, который занимает так много времени:

mean_of_sample_means = np.zeros(len(sample_sizes))
std_dev_of_sample_means = np.zeros(len(sample_sizes))

for i in range(len(sample_sizes)):
    prob = make_throws(200, sample_sizes[i])
    mean_of_sample_means[i] = np.mean(prob)
    std_dev_of_sample_means[i] = np.std(prob)   

Я уверен, что этот процесс можно улучшить, избавившись от циклов for и используя массив-операций вместо. Но я просто не могу думать о том, как применять throw_a_coin или make_throws в массив.



578
8
задан 15 апреля 2018 в 10:04 Источник Поделиться
Комментарии
1 ответ

Мой пример игрушки я строю из фрагментов:

import numpy as np

def throw_a_coin(N):
return np.random.choice(['H','T'], size=N)

def make_throws(number_of_samples, sample_size):
sample_probs = np.zeros(number_of_samples)
for i in range(number_of_samples):
num_heads = sum(throw_a_coin(sample_size) == "H")
sample_probs[i] = float(num_heads) / sample_size
return sample_probs

sample_sizes = [np.random.randint(1, 1e3) for idx in range(500)]

mean_of_sample_means = np.zeros(len(sample_sizes))
std_dev_of_sample_means = np.zeros(len(sample_sizes))

for i in range(len(sample_sizes)):
prob = make_throws(200, sample_sizes[i])
mean_of_sample_means[i] = np.mean(prob)
std_dev_of_sample_means[i] = np.std(prob)

cProfile (упорядочены по cumtime) раскрывает проблему (выполняется в 82.359 секунд):

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
133/1 0.003 0.000 82.359 82.359 {built-in method builtins.exec}
1 0.002 0.002 82.359 82.359 fast_flip.py:1(<module>)
500 0.940 0.002 82.200 0.164 fast_flip.py:8(make_throws)
100000 79.111 0.001 79.111 0.001 {built-in method builtins.sum}
100000 0.058 0.000 2.148 0.000 fast_flip.py:4(throw_a_coin)
100000 1.455 0.000 2.090 0.000 {method 'choice' of 'mtrand.RandomState' objects}
100000 0.207 0.000 0.635 0.000 fromnumeric.py:2456(prod)
100000 0.026 0.000 0.429 0.000 _methods.py:34(_prod)
101500 0.409 0.000 0.409 0.000 {method 'reduce' of 'numpy.ufunc' objects}
6 0.000 0.000 0.176 0.029 __init__.py:1(<module>)

Есть крутой разрыв после buildins.большинство суммы, т. е. вы проводите время там. Мы можем использовать np.sum вместо (толкая его вниз до 3.457 секунд):

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
133/1 0.003 0.000 3.457 3.457 {built-in method builtins.exec}
1 0.002 0.002 3.457 3.457 fast_flip.py:1(<module>)
500 0.905 0.002 3.307 0.007 fast_flip.py:8(make_throws)
100000 0.046 0.000 1.869 0.000 fast_flip.py:4(throw_a_coin)
100000 1.287 0.000 1.823 0.000 {method 'choice' of 'mtrand.RandomState' objects}
201500 0.702 0.000 0.702 0.000 {method 'reduce' of 'numpy.ufunc' objects}
100000 0.172 0.000 0.536 0.000 fromnumeric.py:2456(prod)
100000 0.136 0.000 0.532 0.000 fromnumeric.py:1778(sum)
100000 0.023 0.000 0.378 0.000 _methods.py:31(_sum)
100000 0.021 0.000 0.364 0.000 _methods.py:34(_prod)
6 0.000 0.000 0.178 0.030 __init__.py:1(<module>)

далее мы можем заменить строки "H" и "T" С логическим и остаться в numpy для длительного (до 1.633 секунд):

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
133/1 0.003 0.000 1.633 1.633 {built-in method builtins.exec}
1 0.001 0.001 1.633 1.633 fast_flip.py:1(<module>)
500 0.101 0.000 1.485 0.003 fast_flip.py:11(make_throws)
100000 0.169 0.000 0.893 0.000 fast_flip.py:4(throw_a_coin)
100000 0.724 0.000 0.724 0.000 {method 'uniform' of 'mtrand.RandomState' objects}
100000 0.122 0.000 0.491 0.000 fromnumeric.py:1778(sum)
100000 0.024 0.000 0.354 0.000 _methods.py:31(_sum)
101500 0.334 0.000 0.334 0.000 {method 'reduce' of 'numpy.ufunc' objects}
6 0.000 0.000 0.178 0.030 __init__.py:1(<module>)

далее мы можем избавиться от throw_a_coin и вместо того, чтобы попробовать numer_of_samples x sample_size массив равномерно распределенных случайных чисел и порог их. Это также позволяет векторизовать цикл и оставаться в пакете numpy даже больше (0.786 секунд):

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
133/1 0.003 0.000 0.786 0.786 {built-in method builtins.exec}
1 0.003 0.003 0.786 0.786 fast_flip.py:1(<module>)
500 0.053 0.000 0.634 0.001 fast_flip.py:4(make_throws)
500 0.526 0.001 0.526 0.001 {method 'uniform' of 'mtrand.RandomState' objects}
6 0.000 0.000 0.179 0.030 __init__.py:1(<module>)

Вот код:

import numpy as np

def make_throws(number_of_samples, sample_size):
# True == Heads
throws = np.random.uniform(size=(number_of_samples, sample_size)) > 0.5
sample_probs = np.sum(throws, axis=1) / sample_size
return sample_probs

sample_sizes = [np.random.randint(1, 1e3) for idx in range(500)]

mean_of_sample_means = np.zeros(len(sample_sizes))
std_dev_of_sample_means = np.zeros(len(sample_sizes))

for i in range(len(sample_sizes)):
prob = make_throws(200, sample_sizes[i])
mean_of_sample_means[i] = np.mean(prob)
std_dev_of_sample_means[i] = np.std(prob)

В этот момент мы могли бы начать решать for петли, но это будет сделать очень микро-оптимизации. Можно рассматривать агрегирование результатов в задачах, а затем, используя np.mean(prob, axis=1) и np.std(prob, axis=1) вне цикла, но это только в сети 20ms так что это больше из личных предпочтений.

4
ответ дан 15 апреля 2018 в 02:04 Источник Поделиться