surfista azul
En mi proyecto, necesito calcular 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?
Jarad
La respuesta de @Sanjeet Gupta es buena pero podría condensarse. Esta pregunta se refiere específicamente a la forma “más rápida”, pero solo veo tiempos en una respuesta, así que publicaré una comparación del uso de scipy y numpy con la respuesta entropy2 del póster original con ligeras modificaciones.
Cuatro enfoques diferentes: (1) scipy/numpy, (2) numpy/matemáticas, (3) pandas/numpy, (4) 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 tiempo:
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 tiempo:
# 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/matemáticas (entropy2
)
También vale la pena señalar que el entropy2
La función anterior puede manejar datos numéricos Y de texto. ex: entropy2(list('abcdefabacdebcab'))
. La respuesta del cartel original es de 2013 y tenía un caso de uso específico para agrupar entradas, pero no funcionará para texto.
-
Está utilizando una matriz tan pequeña que sus pruebas son básicamente inútiles. Realmente solo está midiendo la sobrecarga de llamadas para las diversas interfaces.
– Nombre falso
8 de julio de 2018 a las 4:03
-
Usando este código, obtuve el tiempo para mi respuesta (“Una respuesta que no depende de numpy, tampoco…”) también, y es
Method: eta, Avg.: 10.461799
. Como alguien sugirió, me pregunto si realmente está probando la sobrecarga de llamadas aquí.– blamblambunny
17/09/2018 a las 11:40
-
Es mejor tomar el mínimo de los resultados de tiempo, en lugar de la media. Ver el “nota” bajo la función de repetición del módulo timeit.
– MikeR
19 de diciembre de 2019 a las 0:06
-
las operaciones de vectorizador pueden ser preferibles para entradas grandes. El ciclo ‘for’ en la opción 2 puede no ser una buena opción en tales casos. En mi opinión, la opción 1 puede ser preferible.
– Allohvk
20 sep 2021 a las 14:15
Sanjeet Gupta
Con los datos como pd.Series
y scipy.stats
calcular la entropía de una cantidad dada es bastante sencillo:
import pandas as pd
import scipy.stats
def ent(data):
"""Calculates entropy of the passed `pd.Series`
"""
p_data = data.value_counts() # counts occurrence of each value
entropy = scipy.stats.entropy(p_data) # get entropy from counts
return entropy
Nota: scipy.stats
normalizará los datos proporcionados, por lo que no es necesario hacerlo explícitamente, es decir, pasar una matriz de conteos funciona bien.
-
los
value_counts()
el método solo cuenta. probablementep_data
no seríap_data = data.value_counts()/sum(data.value_counts().values)
Entonces tendrá el conteo de cada clase dividido por la cantidad total de datos, luego tendrá la probabilidad de cada clase.– mnsosa
28 de marzo a las 14:03
blablablabunny
Una respuesta que tampoco depende de numpy:
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 pueda arrojarle:
>>> eta(['mary', 'had', 'a', 'little', 'lamb'])
1.6094379124341005
>>> eta([c for c in "mary had a little lamb"])
2.311097886212714
La respuesta proporcionada por @Jarad también sugirió tiempos. 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
-
por qué necesitas problemas = [p for p in probs if p > 0.]?
– Vlad
20 de agosto de 2018 a las 5:41
-
Como estoy haciendo esa prueba cinco líneas después, sospecho que no la necesito en absoluto 🙂 Editado.
– blamblambunny
17 de septiembre de 2018 a las 11:28
-
más uno para no tener nuevas dependencias
– curob
9 de abril de 2020 a las 15:36
-
¿Puedes hacer recuentos = Contador (datos) en lugar de recorrer los caracteres de los datos?
– Alan Hamlett
23 de octubre de 2020 a las 4:22
Siguiendo la sugerencia de unutbu, creo una implementación pura de python.
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 me faltaba era que las etiquetas son una matriz grande, sin embargo, los problemas tienen 3 o 4 elementos de largo. Usando Python puro, mi aplicación ahora es el doble de rápida.
bronceado duong
Aquí está mi enfoque:
labels = [0, 0, 1, 1]
from collections import Counter
from scipy import stats
stats.entropy(list(Counter(labels).values()), base=2)
-
Esto parece funcionar para mis segmentos de imagen, pero en realidad necesito la probabilidad de valores de píxeles en el segmento de 0 a 255.
– mlestudiante33
12 de noviembre de 2019 a las 8:52
Ottotos
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 mejor manera de evitar el dict -> valores -> lista -> conversión np.array. Volveré a comentar si lo encuentro.
-
Esto parece funcionar para mis segmentos de imagen, pero en realidad necesito la probabilidad de valores de píxeles en el segmento de 0 a 255.
– mlestudiante33
12 de noviembre de 2019 a las 8:52
Comunidad
Datos distribuidos uniformemente (alta entropía):
s=range(0,256)
Cálculo de la entropía de Shannon paso a paso:
import collections
import math
# 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
De una sola línea (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
import math
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 de 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
-
Esto deja muy claro la capacidad de calcular la entropía en un rango específico de valores. Necesito aplicar este método al área conectada en 8 alrededor de un píxel y sus valores de escala de grises. Preguntándome si también me vendría bien un método incorporado.
– mlestudiante33
12 de noviembre de 2019 a las 8:09
¿Cuál es una longitud típica de
labels
?– unutbu
16 de marzo de 2013 a las 14:09
La longitud no es fija..
– surfista azul
16 de marzo de 2013 a las 14:12
Sería útil con la evaluación comparativa conocer los valores típicos de
labels
. Silabels
es demasiado corto, una implementación de python pura en realidad podría ser más rápida que usar NumPy.– unutbu
16 de marzo de 2013 a las 14:13
solo para confirmar, ¿esta pregunta es para la entropía de una variable aleatoria discreta (binaria)? y no la entropía diferencial de un rv continuo?
– develarista
4 de agosto de 2020 a las 8:37
No sé, ¿qué tan rápido se ejecuta tu código? Esto está basado en opiniones en su formato actual.
– TylerH
3 de noviembre de 2021 a las 19:26