Проблема эффективности настройки векторизованной работы numpy - PullRequest
0 голосов
/ 24 сентября 2018

У меня есть функция Python, указанная ниже:

def myfun(x):
    if x > 0:
        return 0
    else:
        return np.exp(x)

, где np - это библиотека numpy.Я хочу сделать функцию векторизованной в numpy, поэтому я использую:

vec_myfun = np.vectorize(myfun)

Я сделал тест для оценки эффективности.Сначала я генерирую вектор из 100 случайных чисел:

x = np.random.randn(100)

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

%timeit np.exp(x)

%timeit vec_myfun(x)

Время выполнения для np.exp(x) равно 1.07 µs ± 24.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each).

Время выполнения для vec_myfun(x) равно 71.2 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Мой вопрос: по сравнению с np.exp, vec_myfun имеет только один дополнительный шаг для проверки значения $ x $, ноон работает намного медленнее, чем np.exp.Есть ли эффективный способ векторизации myfun, чтобы сделать его таким же эффективным, как np.exp?

Ответы [ 3 ]

0 голосов
/ 24 сентября 2018

Просто мыслите нестандартно, как насчет реализации функции piecewise_exp(), которая в основном умножает np.exp() на arr < 0?

import numpy as np


def piecewise_exp(arr):
    return np.exp(arr) * (arr < 0)

Написание кода, предложенного для функций:

@np.vectorize
def myfun(x):
    if x > 0:
        return 0.0
    else:
        return np.exp(x)


def bnaeker_exp(arr):
    return np.where(arr > 0, 0, np.exp(arr))

И проверка того, что все согласовано:

np.random.seed(0)


# : test that the functions have the same behavior
num = 10
x = np.random.rand(num) - 0.5

print(x)
print(myfun(x))
print(piecewise_exp(x))
print(bnaeker_exp(x))

Выполнение некоторых микропроцессоров для небольших входов:

# : micro-benchmarks for small inputs
num = 100
x = np.random.rand(num) - 0.5

%timeit np.exp(x)
# 1.63 µs ± 45.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit myfun(x)
# 54 µs ± 967 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit bnaeker_exp(x)
# 4 µs ± 87.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit piecewise_exp(x)
# 3.38 µs ± 59.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

... и для больших входов:

# : micro-benchmarks for larger inputs
num = 100000
x = np.random.rand(num) - 0.5

%timeit np.exp(x)
# 32.7 µs ± 1.78 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit myfun(x)
# 44.9 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit bnaeker_exp(x)
# 481 µs ± 25.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit piecewise_exp(x)
# 149 µs ± 2.65 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Это показывает, что piecewise_exp() быстрее, чем все, что было предложено до сих пор, особенно для больших входов, для которых np.where() становится более неэффективным, поскольку использует целочисленную индексацию вместо логических масок и разумно приближается к np.exp()speed.

EDIT

Кроме того, производительность версии np.where() (bnaeker_exp()) зависит от количества элементов массива, фактически удовлетворяющих условию.Если ни один из них не подходит (например, при тестировании на x = np.random.rand(100)), это немного быстрее, чем версия умножения логического массива (piecewise_exp()) (128 µs ± 3.26 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) на моем компьютере для n = 100000).

0 голосов
/ 24 сентября 2018

ufunc как np.exp имеет параметр where, который может использоваться как:

In [288]: x = np.random.randn(10)
In [289]: out=np.zeros_like(x)
In [290]: np.exp(x, out=out, where=(x<=0))
Out[290]: 
array([0.        , 0.        , 0.        , 0.        , 0.09407685,
       0.92458328, 0.        , 0.        , 0.46618914, 0.        ])
In [291]: x
Out[291]: 
array([ 0.37513573,  1.75273458,  0.30561659,  0.46554985, -2.3636433 ,
       -0.07841215,  2.00878429,  0.58441085, -0.76316384,  0.12431333])

Это фактически пропускает вычисление, где where ложно.

Для сравнения:

np.where(arr > 0, 0, np.exp(arr))

сначала вычисляет np.exp(arr) для всех arr (это нормальный порядок оценки Python), а затем выполняет выбор where.С этим exp это не имеет большого значения, но с log это могут быть проблемы.

0 голосов
/ 24 сентября 2018

Использование np.where:

>>> x = np.random.rand(100,)
>>> %timeit np.exp(x)
1.22 µs ± 49.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> %timeit np.where(x > 0, 0, np.exp(x))
4.09 µs ± 282 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Для сравнения, ваша векторизованная функция работает на моем компьютере примерно за 30 микросекунд.

Что касается того, почему она работает медленнее, то она намного сложнеечем np.exp.Он делает много выводов типа, широковещания и, возможно, совершает много обращений к реальному методу.Многое из этого происходит в самом Python, в то время как почти все в вызове np.exp (и версии np.where здесь) находится в C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...