Numpy: Эффективно избегать 0s при приеме логарифма (матрицы)

from numpy import *

m = array([[1,0],
           [2,3]])

Я хотел бы вычислить элемент-мудрый log2(m), но только в тех местах, где m не равен 0. В этих местах я хотел бы иметь 0. Как результат.

Сейчас я борюсь:

RuntimeWarning: divide by zero encountered in log2

Попробуйте 1: используя where

res = where(m != 0, log2(m), 0)

который вычисляет мне правильный результат, но я все еще регистрирую a RuntimeWarning: divide by zero encountered in log2. Похоже, что (и синтаксически это совершенно очевидно) numpy все еще вычисляет log2(m) на полной матрице, и только после этого where выбирает значения, которые нужно сохранить.

Я бы хотел избежать этого предупреждения.


Попробуйте 2: использование масок

from numpy import ma

res = ma.filled(log2(ma.masked_equal(m, 0)), 0)

Конечно, маскировка нулей предотвратит log2, чтобы применить к ним, не так ли? К сожалению, нет: мы все равно получаем RuntimeWarning: divide by zero encountered in log2.

Несмотря на то, что матрица маскируется, log2 по-прежнему применяется к каждому элементу.


Как я могу эффективно вычислить элементный массив массива numpy без получения предупреждений о делении на нуль?

  • Конечно, я мог временно отключить ведение журнала этих предупреждений с помощью seterr, но это не похоже на чистое решение.
  • И уверен, что цикл double for поможет справиться с 0s специально, но победит эффективность numpy.

Любые идеи?

Ответ 1

Мы можем использовать маскированные массивы для этого:

>>> from numpy import *
>>> m = array([[1,0], [2,3]])
>>> x = ma.log(m)
>>> print x.filled(0)
[[ 0.          0.        ]
 [ 0.69314718  1.09861229]]

Ответ 2

Просто отключите предупреждение для этого вычисления:

from numpy import errstate,isneginf,array

m = array([[1,0],[2,3]])
with errstate(divide='ignore'):
    res = log2(m)

И затем вы можете постобработать -inf, если хотите:

res[isneginf(res)]=0

ОБНОВЛЕНИЕ: я разместил здесь некоторые комментарии о другом варианте, который использует маскированные массивы, опубликованные в другом ответе. Вы должны отключить ошибку по двум причинам:

1) Использование замаскированных массивов намного менее эффективно, чем кратковременное отключение ошибки, и вы попросили об эффективности.

2) Отключение специального предупреждения "деление на ноль" НЕ отключает другую проблему с вычислением логарифма числа, которое является отрицательным входом. Отрицательный ввод фиксируется как предупреждение о недопустимом значении, и вам придется с ним справиться.

С другой стороны, использование замаскированных массивов фиксирует две ошибки как одинаковые и приведет к тому, что вы не заметите отрицательное число на входе. Другими словами, отрицательное число на входе обрабатывается как ноль и в результате даст ноль. Это не то, что вы спросили.

3) В качестве последнего пункта и как личного мнения, отключение предупреждения очень читабельно, очевидно, что делает код, и делает его более понятным. В этом отношении я нахожу это решение чище, а затем использую маскированные массивы.

Ответ 3

Другой вариант - использовать параметр where для numpy ufuncs:

m = np.array([[1., 0], [2, 3]])
res = np.log2(m, out=np.zeros_like(m), where=(m!=0))

Нет RuntimeWarning, и нули вводятся там, где лог не вычисляется.

Ответ 4

Решение с замаскированным массивом и решение, которое отключает предупреждение, в порядке. Для разнообразия, вот другой, который использует scipy.special.xlogy. np.sign(m) задается в качестве аргумента x, поэтому xlogy возвращает 0, где np.sign(m) равно 0. Результат делится на np.log(2) для получения логарифма с основанием-2.

In [4]: from scipy.special import xlogy

In [5]: m = np.array([[1, 0], [2, 3]])

In [6]: xlogy(np.sign(m), m) / np.log(2)
Out[6]: 
array([[ 0.       ,  0.       ],
       [ 1.       ,  1.5849625]])

Ответ 5

Как насчет следующего

from numpy import *
m=array((-1.0,0.0,2.0))
p=m > 0.0
print 'positive=',p
print m[p]
res=zeros_like(m)
res[p]=log(m[p])
print res

Ответ 6

Вы можете использовать что-то вроде - m = np.clip(m, 1e-12, None), чтобы избежать ошибки журнала (0). Это установит нижнюю границу на 1e-12.

Ответ 7

Проблема

Вопросы: февраль 2014 года, май 2012 года

Для массива, содержащего zeros или negatives, мы получаем соответствующие ошибки.

y = np.log(x)
# RuntimeWarning: divide by zero encountered in log
# RuntimeWarning: invalid value encountered in log

Решение

markroxor предлагает np.clip, в моем примере это создает горизонтальный пол. gg349 и другие используют np.errstate и np.seterr, я думаю, что они неуклюжи и не решают проблему. Как примечание np.complex не работает для нулей. user3315095 использует индексирование p=0<x, а NumPy.log имеет эту встроенную функциональность, where/out. mdeff демонстрирует это, но заменяет -inf на 0, что для меня было недостаточным и не решает проблемы с негативами.

Я предлагаю 0<x и np.nan (или при необходимости np.NINF/-np.inf).

y = np.log(x, where=0<x, out=np.nan*x)

Джон Цвинк использует матрицу масок np.ma.log, это работает, но вычислительно медленнее, попробуйте App: timeit.

Пример

import numpy as np
x = np.linspace(-10, 10, 300)

# y = np.log(x)                         # Old
y = np.log(x, where=0<x, out=np.nan*x)  # New

import matplotlib.pyplot as plt
plt.plot(x, y)
plt.show()

приложение: timeit

Сравнение времени для mask и where

import numpy as np
import time
def timeit(fun, xs):
    t = time.time()
    for i in range(len(xs)):
        fun(xs[i])
    print(time.time() - t)

xs = np.random.randint(-10,+10, (1000,10000))
timeit(lambda x: np.ma.log(x).filled(np.nan), xs)
timeit(lambda x: np.log(x, where=0<x, out=np.nan*x), xs)