Самый быстрый способ вычислить энтропию каждой строки массива numpy?

У меня есть массив размером MxN, и мне нравится вычислять значение энтропии каждой строки. Какой способ сделать это быстрее всего?


person erogol    schedule 09.11.2015    source источник
comment
энтропия: -np.sum (вероятность * np.log2 (вероятность))   -  person erogol    schedule 09.11.2015
comment
Под самым быстрым вы имеете в виду, что вам нужна его оптимизированная версия, или вы хотите что-то, что умещается в одну строку и легко читается?   -  person Emilien    schedule 09.11.2015
comment
не самый простой, самый быстрый с точки зрения вычислений, так как у меня довольно большая матрица, итерация строки занимает слишком много времени.   -  person erogol    schedule 09.11.2015
comment
Ваш первый комментарий должен быть частью вопроса. Я интерпретирую вопрос как массив вероятностей probs, и мне нужна энтропия строк. Если у вас еще нет вероятностей, уточните вопрос.   -  person Warren Weckesser    schedule 09.11.2015


Ответы (2)


scipy.special.entr вычисляет -x * log (x) для каждого элемента в массиве. После этого вы можете суммировать строки.

Вот пример. Сначала создайте массив p положительных значений, сумма строк которого равна 1:

In [23]: np.random.seed(123)

In [24]: x = np.random.rand(3, 10)

In [25]: p = x/x.sum(axis=1, keepdims=True)

In [26]: p
Out[26]: 
array([[ 0.12798052,  0.05257987,  0.04168536,  0.1013075 ,  0.13220688,
         0.07774843,  0.18022149,  0.1258417 ,  0.08837421,  0.07205402],
       [ 0.08313743,  0.17661773,  0.1062474 ,  0.01445742,  0.09642919,
         0.17878489,  0.04420998,  0.0425045 ,  0.12877228,  0.1288392 ],
       [ 0.11793032,  0.15790292,  0.13467074,  0.11358463,  0.13429674,
         0.06003561,  0.06725376,  0.0424324 ,  0.05459921,  0.11729367]])

In [27]: p.shape
Out[27]: (3, 10)

In [28]: p.sum(axis=1)
Out[28]: array([ 1.,  1.,  1.])

Теперь вычислите энтропию каждой строки. entr использует натуральный логарифм, поэтому, чтобы получить логарифм по основанию 2, разделите результат на log(2).

In [29]: from scipy.special import entr

In [30]: entr(p).sum(axis=1)
Out[30]: array([ 2.22208731,  2.14586635,  2.22486581])

In [31]: entr(p).sum(axis=1)/np.log(2)
Out[31]: array([ 3.20579434,  3.09583074,  3.20980287])

Если вам не нужна зависимость от scipy, вы можете использовать явную формулу:

In [32]: (-p*np.log2(p)).sum(axis=1)
Out[32]: array([ 3.20579434,  3.09583074,  3.20980287])
person Warren Weckesser    schedule 09.11.2015
comment
Все мои вероятности равнялись 0. Чтобы решить эту проблему, мне пришлось преобразовать сумму знаменателя в float, например, p = x / float (x.sum (axis = 1, keepdims = True)). На случай, если у кого-то такая же проблема. - person JStrahl; 04.08.2016
comment
scipy.stats.entropy также вычисляет то же значение, что и entr(p).sum(axis=1) - person spinup; 07.02.2019

Как отметил @Warren, из вашего вопроса неясно, исходите ли вы из массива вероятностей или из самих сырых выборок. В своем ответе я предположил последнее, и в этом случае основным узким местом будет вычисление количества бункеров для каждой строки.

Предполагая, что каждый вектор образцов относительно длинный, самым быстрым способом сделать это, вероятно, будет использование _ 1_:

import numpy as np

def entropy(x):
    """
    x is assumed to be an (nsignals, nsamples) array containing integers between
    0 and n_unique_vals
    """
    x = np.atleast_2d(x)
    nrows, ncols = x.shape
    nbins = x.max() + 1

    # count the number of occurrences for each unique integer between 0 and x.max()
    # in each row of x
    counts = np.vstack((np.bincount(row, minlength=nbins) for row in x))

    # divide by number of columns to get the probability of each unique value
    p = counts / float(ncols)

    # compute Shannon entropy in bits
    return -np.sum(p * np.log2(p), axis=1)

Хотя метод Уоррена для вычисления энтропий из значений вероятности с использованием entr немного быстрее, чем использование явной формулы, на практике это, вероятно, представляет крошечную долю от общего времени выполнения по сравнению со временем, затрачиваемым на вычисление счетчиков бинов.

Правильность теста для одной строки:

vals = np.arange(3)
prob = np.array([0.1, 0.7, 0.2])
row = np.random.choice(vals, p=prob, size=1000000)

print("theoretical H(x): %.6f, empirical H(x): %.6f" %
      (-np.sum(prob * np.log2(prob)), entropy(row)[0]))
# theoretical H(x): 1.156780, empirical H(x): 1.157532

Тест скорости:

In [1]: %%timeit x = np.random.choice(vals, p=prob, size=(1000, 10000))
   ....: entropy(x)
   ....: 
10 loops, best of 3: 34.6 ms per loop

Если ваши данные не состоят из целочисленных индексов от 0 до количества уникальных значений, вы можете преобразовать их в этот формат, используя _ 6_:

y = np.random.choice([2.5, 3.14, 42], p=prob, size=(1000, 10000))
unq, x = np.unique(y, return_inverse=True)
x.shape = y.shape
person ali_m    schedule 09.11.2015
comment
вы можете сэкономить время, используя -np.dot(a, b) вместо -np.sum(a * b) - person atomsmasher; 09.12.2016
comment
@atomsmasher С np.dot я не могу легко векторизовать вычисление энтропии по нескольким строкам. Один из способов - это что-то вроде -np.einsum('ij,ij->i', p, np.log2(p)), хотя на самом деле вы могли бы просто использовать entr для этой части, поскольку у нее есть аргумент axis. В любом случае дорогостоящая часть обычно вычисляет количество бункеров. - person ali_m; 10.12.2016