web-dev-qa-db-fra.com

Méthode optimale pour calculer les informations mutuelles par paire à l'aide de numpy

Pour une matrice mxn , quelle est la façon optimale (la plus rapide) de calculer les informations mutuelles pour toutes les paires de colonnes ( nxn )?

Par information mutuelle , je veux dire:

I (X, Y) = H(X) + H(Y) - H (X, Y)

H (X) fait référence à l'entropie de Shannon de [~ # ~] x [~ # ~] .

J'utilise actuellement np.histogram2d et np.histogram pour calculer l'articulation (X, Y) et individuelle (X ou Y) compte. Pour une matrice donnée A (par exemple une matrice de flottants 250000 X 1000), je fais une boucle imbriquée for,

    n = A.shape[1]
    for ix = arange(n)  
        for jx = arange(ix+1,n):
           matMI[ix,jx]= calc_MI(A[:,ix],A[:,jx])

Il doit sûrement y avoir des moyens meilleurs/plus rapides de le faire?

En passant, j'ai également cherché des fonctions de mappage sur les colonnes (opérations colonne par ligne ou ligne par ligne) sur les tableaux, mais je n'ai pas encore trouvé de bonne réponse générale.

Voici mon implémentation complète, en suivant les conventions de la page Wiki :

import numpy as np

def calc_MI(X,Y,bins):

   c_XY = np.histogram2d(X,Y,bins)[0]
   c_X = np.histogram(X,bins)[0]
   c_Y = np.histogram(Y,bins)[0]

   H_X = shan_entropy(c_X)
   H_Y = shan_entropy(c_Y)
   H_XY = shan_entropy(c_XY)

   MI = H_X + H_Y - H_XY
   return MI

def shan_entropy(c):
    c_normalized = c / float(np.sum(c))
    c_normalized = c_normalized[np.nonzero(c_normalized)]
    H = -sum(c_normalized* np.log2(c_normalized))  
    return H

A = np.array([[ 2.0,  140.0,  128.23, -150.5, -5.4  ],
              [ 2.4,  153.11, 130.34, -130.1, -9.5  ],
              [ 1.2,  156.9,  120.11, -110.45,-1.12 ]])

bins = 5 # ?
n = A.shape[1]
matMI = np.zeros((n, n))

for ix in np.arange(n):
    for jx in np.arange(ix+1,n):
        matMI[ix,jx] = calc_MI(A[:,ix], A[:,jx], bins)

Bien que ma version de travail avec des boucles imbriquées for le fasse à une vitesse raisonnable, j'aimerais savoir s'il existe un moyen plus optimal d'appliquer calc_MI sur toutes les colonnes de A (pour calculer leurs informations mutuelles par paire)?

J'aimerais aussi savoir:

  1. Existe-t-il des moyens efficaces de mapper les fonctions pour qu'elles fonctionnent sur les colonnes (ou lignes) de np.arrays (peut-être comme np.vectorize, qui ressemble plus à un décorateur)?

  2. Existe-t-il d'autres implémentations optimales pour ce calcul spécifique (informations mutuelles)?

45
nahsivar

Je ne peux pas suggérer un calcul plus rapide pour la boucle externe sur les vecteurs n * (n-1)/2, mais votre implémentation de calc_MI(x, y, bins) peut être simplifiée si vous pouvez utiliser scipy version 0.13 ou scikit-learn .

Dans 0.13 scipy, le lambda_ l'argument a été ajouté à scipy.stats.chi2_contingency Cet argument contrôle la statistique calculée par la fonction. Si tu utilises lambda_="log-likelihood" (ou lambda_=0), le rapport log-vraisemblance est renvoyé. Ceci est aussi souvent appelé le G ou G2 statistique. Autre qu'un facteur de 2 * n (où n est le nombre total d'échantillons dans le tableau de contingence), c'est c'est l'information mutuelle. Vous pouvez donc implémenter calc_MI comme:

from scipy.stats import chi2_contingency

def calc_MI(x, y, bins):
    c_xy = np.histogram2d(x, y, bins)[0]
    g, p, dof, expected = chi2_contingency(c_xy, lambda_="log-likelihood")
    mi = 0.5 * g / c_xy.sum()
    return mi

La seule différence entre cela et votre implémentation est que cette implémentation utilise le logarithme naturel au lieu du logarithme en base 2 (elle exprime donc les informations en "nats" au lieu de "bits"). Si vous préférez vraiment les bits, divisez simplement mi par log (2).

Si vous avez (ou pouvez installer) sklearn (c'est-à-dire scikit-learn), vous pouvez utiliser sklearn.metrics.mutual_info_score , et implémentez calc_MI comme:

from sklearn.metrics import mutual_info_score

def calc_MI(x, y, bins):
    c_xy = np.histogram2d(x, y, bins)[0]
    mi = mutual_info_score(None, None, contingency=c_xy)
    return mi
47
Warren Weckesser