La forma más rápida de calcular la entropía en Python
numpy entropy (9)
En mi proyecto necesito computar la entropía de los vectores 0-1 muchas veces. Aquí está mi código:
def entropy(labels):
""" Computes entropy of 0-1 vector. """
n_labels = len(labels)
if n_labels <= 1:
return 0
counts = np.bincount(labels)
probs = counts[np.nonzero(counts)] / n_labels
n_classes = len(probs)
if n_classes <= 1:
return 0
return - np.sum(probs * np.log(probs)) / np.log(n_classes)
¿Hay una manera mas rápida?
Aquí está mi enfoque:
labels = [0, 0, 1, 1]
from collections import Counter
from scipy import stats
counter = Counter(labels)
stats.entropy([x for x in counter.values()], base=2)
Datos distribuidos uniformemente (alta entropía):
s=range(0,256)
Cálculo de la entropía de Shannon paso a paso:
import collections
# calculate probability for each byte as number of occurrences / array length
probabilities = [n_x/len(s) for x,n_x in collections.Counter(s).items()]
# [0.00390625, 0.00390625, 0.00390625, ...]
# calculate per-character entropy fractions
e_x = [-p_x*math.log(p_x,2) for p_x in probabilities]
# [0.03125, 0.03125, 0.03125, ...]
# sum fractions to obtain Shannon entropy
entropy = sum(e_x)
>>> entropy
8.0
One-liner (asumiendo import collections
):
def H(s): return sum([-p_x*math.log(p_x,2) for p_x in [n_x/len(s) for x,n_x in collections.Counter(s).items()]])
Una función adecuada:
import collections
def H(s):
probabilities = [n_x/len(s) for x,n_x in collections.Counter(s).items()]
e_x = [-p_x*math.log(p_x,2) for p_x in probabilities]
return sum(e_x)
Casos de prueba: texto en inglés tomado del estimador de entropía CyberChef :
>>> H(range(0,256))
8.0
>>> H(range(0,64))
6.0
>>> H(range(0,128))
7.0
>>> H([0,1])
1.0
>>> H(''Standard English text usually falls somewhere between 3.5 and 5'')
4.228788210509104
Eche un vistazo aquí también, hay una Entropía de Shannon clásica, debería ser un poco más rápida que la de JohnEntropy http://pythonfiddle.com/shannon-entropy-calculation/
La respuesta anterior es buena, pero si necesita una versión que pueda funcionar en diferentes ejes, aquí tiene una implementación que funciona.
def entropy(A, axis=None):
"""Computes the Shannon entropy of the elements of A. Assumes A is
an array-like of nonnegative ints whose max value is approximately
the number of unique values present.
>>> a = [0, 1]
>>> entropy(a)
1.0
>>> A = np.c_[a, a]
>>> entropy(A)
1.0
>>> A # doctest: +NORMALIZE_WHITESPACE
array([[0, 0], [1, 1]])
>>> entropy(A, axis=0) # doctest: +NORMALIZE_WHITESPACE
array([ 1., 1.])
>>> entropy(A, axis=1) # doctest: +NORMALIZE_WHITESPACE
array([[ 0.], [ 0.]])
>>> entropy([0, 0, 0])
0.0
>>> entropy([])
0.0
>>> entropy([5])
0.0
"""
if A is None or len(A) < 2:
return 0.
A = np.asarray(A)
if axis is None:
A = A.flatten()
counts = np.bincount(A) # needs small, non-negative ints
counts = counts[counts > 0]
if len(counts) == 1:
return 0. # avoid returning -0.0 to prevent weird doctests
probs = counts / float(A.size)
return -np.sum(probs * np.log2(probs))
elif axis == 0:
entropies = map(lambda col: entropy(col), A.T)
return np.array(entropies)
elif axis == 1:
entropies = map(lambda row: entropy(row), A)
return np.array(entropies).reshape((-1, 1))
else:
raise ValueError("unsupported axis: {}".format(axis))
La respuesta de @Sanjeet Gupta es buena pero podría estar condensada. Esta pregunta se refiere específicamente a la forma "más rápida", pero solo veo los tiempos en una respuesta, así que publicaré una comparación del uso de scipy y numpy a la respuesta de entropía del póster original2 con ligeras alteraciones.
Cuatro enfoques diferentes: scipy / numpy , numpy / math , pandas / numpy , numpy
import numpy as np
from scipy.stats import entropy
from math import log, e
import pandas as pd
import timeit
def entropy1(labels, base=None):
value,counts = np.unique(labels, return_counts=True)
return entropy(counts, base=base)
def entropy2(labels, base=None):
""" Computes entropy of label distribution. """
n_labels = len(labels)
if n_labels <= 1:
return 0
value,counts = np.unique(labels, return_counts=True)
probs = counts / n_labels
n_classes = np.count_nonzero(probs)
if n_classes <= 1:
return 0
ent = 0.
# Compute entropy
base = e if base is None else base
for i in probs:
ent -= i * log(i, base)
return ent
def entropy3(labels, base=None):
vc = pd.Series(labels).value_counts(normalize=True, sort=False)
base = e if base is None else base
return -(vc * np.log(vc)/np.log(base)).sum()
def entropy4(labels, base=None):
value,counts = np.unique(labels, return_counts=True)
norm_counts = counts / counts.sum()
base = e if base is None else base
return -(norm_counts * np.log(norm_counts)/np.log(base)).sum()
Operaciones de Timeit:
repeat_number = 1000000
a = timeit.repeat(stmt=''''''entropy1(labels)'''''',
setup=''''''labels=[1,3,5,2,3,5,3,2,1,3,4,5];from __main__ import entropy1'''''',
repeat=3, number=repeat_number)
b = timeit.repeat(stmt=''''''entropy2(labels)'''''',
setup=''''''labels=[1,3,5,2,3,5,3,2,1,3,4,5];from __main__ import entropy2'''''',
repeat=3, number=repeat_number)
c = timeit.repeat(stmt=''''''entropy3(labels)'''''',
setup=''''''labels=[1,3,5,2,3,5,3,2,1,3,4,5];from __main__ import entropy3'''''',
repeat=3, number=repeat_number)
d = timeit.repeat(stmt=''''''entropy4(labels)'''''',
setup=''''''labels=[1,3,5,2,3,5,3,2,1,3,4,5];from __main__ import entropy4'''''',
repeat=3, number=repeat_number)
Resultados de Timeit:
# for loop to print out results of timeit
for approach,timeit_results in zip([''scipy/numpy'', ''numpy/math'', ''pandas/numpy'', ''numpy''], [a,b,c,d]):
print(''Method: {}, Avg.: {:.6f}''.format(approach, np.array(timeit_results).mean()))
Method: scipy/numpy, Avg.: 63.315312
Method: numpy/math, Avg.: 49.256894
Method: pandas/numpy, Avg.: 884.644023
Method: numpy, Avg.: 60.026938
Ganador: numpy / math (entropy2)
También vale la pena señalar que la función entropy2
anterior puede manejar datos numéricos Y de texto. ej: entropy2(list(''abcdefabacdebcab''))
. La respuesta del póster original es de 2013 y tuvo un caso de uso específico para compartir ints, pero no funcionará para texto.
Mi función favorita para la entropía es la siguiente:
def entropy(labels):
prob_dict = {x:labels.count(x)/len(labels) for x in labels}
probs = np.array(list(prob_dict.values()))
return - probs.dot(np.log2(probs))
Todavía estoy buscando una forma mejor de evitar la conversión de dict -> valores -> lista -> np.array. Volveré a comentar si lo encontré.
Siguiendo la sugerencia de unutbu, creo una implementación de python pura.
def entropy2(labels):
""" Computes entropy of label distribution. """
n_labels = len(labels)
if n_labels <= 1:
return 0
counts = np.bincount(labels)
probs = counts / n_labels
n_classes = np.count_nonzero(probs)
if n_classes <= 1:
return 0
ent = 0.
# Compute standard entropy.
for i in probs:
ent -= i * log(i, base=n_classes)
return ent
El punto que faltaba era que las etiquetas son una gran variedad, sin embargo, los problemas son de 3 o 4 elementos. Usando python puro, mi aplicación ahora es el doble de rápida.
Una respuesta que no depende de numpy, tampoco:
import math
from collections import Counter
def eta(data, unit=''natural''):
base = {
''shannon'' : 2.,
''natural'' : math.exp(1),
''hartley'' : 10.
}
if len(data) <= 1:
return 0
counts = Counter()
for d in data:
counts[d] += 1
ent = 0
probs = [float(c) / len(data) for c in counts.values()]
for p in probs:
if p > 0.:
ent -= p * math.log(p, base[unit])
return ent
Esto aceptará cualquier tipo de datos que puedas lanzarle:
>>> eta([''mary'', ''had'', ''a'', ''little'', ''lamb''])
1.6094379124341005
>>> eta([c for c in "mary had a little lamb"])
2.311097886212714
La respuesta provista por @Jarad sugirió tiempos también. Con ese fin:
repeat_number = 1000000
e = timeit.repeat(
stmt=''''''eta(labels)'''''',
setup=''''''labels=[1,3,5,2,3,5,3,2,1,3,4,5];from __main__ import eta'''''',
repeat=3,
number=repeat_number)
Resultados de Timeit: (Creo que esto es ~ 4 veces más rápido que el mejor enfoque numpy)
print(''Method: {}, Avg.: {:.6f}''.format("eta", np.array(e).mean()))
Method: eta, Avg.: 10.461799
import pandas as pd
import scipy as sc
# Input a pandas series
def ent(data):
p_data= data.value_counts()/len(data) # calculates the probabilities
entropy=sc.stats.entropy(p_data) # input probabilities to get the entropy
return entropy