2
votes

Pourquoi np.add.at () renvoie-t-il la mauvaise réponse pour les grands tableaux?

J'ai un grand ensemble de données, statistic , avec statistic.shape = (1E10,) que je veux statistic.shape = (1E10,) efficacement (somme) dans un tableau de zéros, out = np.zeros(1E10) . Chaque entrée statistic a un indice correspondant, idx , qui me dit où out bin il appartient. Les indices ne sont pas uniques, donc je ne peux pas utiliser out += statistic[idx] car cela ne comptera que la première fois qu'un index particulier est rencontré. Par conséquent, j'utilise np.add.at(out, idx, statistic) . Mon problème est que pour les très grands tableaux, np.add.at () renvoie la mauvaise réponse.

Vous trouverez ci-dessous un exemple de script illustrant ce comportement. La fonction check_add() doit renvoyer 1.

N = 1000.0 (log(N) = 3.0); output ratio is 1.0
N = 100000.0 (log(N) = 5.0); output ratio is 1.0
N = 100000000.0 (log(N) = 8.0); output ratio is 1.0
N = 10000000000.0 (log(N) = 10.0); output ratio is 0.1410065408

Cet exemple me renvoie:

import numpy as np

def check_add(N):
    N = int(N)
    out = np.zeros(N)
    np.add.at(out, np.arange(N), np.ones(N))
    return np.sum(out)/N

n_arr = [1E3, 1E5, 1E8, 1E10]
for n in n_arr:
    print('N = {} (log(N) = {}); output ratio is {}'.format(n, np.log10(n), check_add(n)))

Quelqu'un peut-il m'expliquer pourquoi la fonction échoue pour N=1E10 ?


6 commentaires

Avez-vous examiné le contenu de out après l'opération add.at ?


Et sur quelle version de NumPy êtes-vous?


Aussi, quel OS? Certains chemins de code NumPy dépendent du système d'exploitation.


Je suis sur Ubuntu 18.04.5 et j'utilise NumPy 1.16.0. J'ai regardé out et il semble qu'il remplit le tableau avec 1 comme prévu jusqu'à ce que out[1410065408] . Après cela, tout est à 0.


Bizarre. Dommage que cela prenne une quantité folle de mémoire à tester.


print(numpy.intp) vous si vous print(numpy.intp) ?


4 Réponses :


-1
votes

Je suppose que je ne pouvais pas l'exécuter, mais cela pourrait-il être un problème que vous dépassiez la valeur entière maximale en python pour la dernière option? Ie dépasse 2147483647. Utilisez plutôt le type longinteger comme indiqué ci-dessous.

En référence à: [entrez la description du lien ici] [1] https://docs.python.org/2.0/ref/integers.html

J'espère que cela t'aides. S'il vous plaît laissez-moi savoir si cela fonctionne.


2 commentaires

C'est gravement obsolète, les mathématiques NumPy fonctionnent de toute façon différemment des entiers Python ordinaires, et out a un dtype float64, pas un dtype entier. Il est probable qu'un effet de débordement soit impliqué quelque part, mais Python 2 long n'est pas la solution.


Bon à savoir. Il serait bon de savoir quel est le problème. Je n'ai jamais rencontré cela auparavant.



0
votes

Cela est probablement dû à une précision entière. Si vous jouez avec le type de données numpy (par exemple, vous le contraignez à une valeur (non signée) entre 0 et 255) en définissant uint8 , vous verrez que les ratios commencent déjà à diminuer pour le deuxième tableau. Je n'ai pas assez de mémoire pour le tester, mais définir tous les dtypes sur uint64 comme ci-dessous devrait aider:

def check_add(N):
    N = int(N)
    out = np.zeros(N,dtype='uint8')
    np.add.at(out, np.arange(N,dtype='uint8'), 1)
    return np.sum(out)/N

n_arr = [1E1, 1E3, 1E5,65536, 1E7]
for n in n_arr:
    print('N = {} (log(N) = {}); output ratio is {}'.format(n, np.log10(n), check_add(n)))

Pour comprendre le comportement, je recommande de définir dtype='uint8' et de vérifier le comportement pour les petits N. Donc, ce qui se passe, c'est que la fonction np.arange crée des entiers ascendants pour les éléments vectoriels jusqu'à ce qu'il atteigne la limite entière. Il recommence alors à 0 et compte à nouveau, donc au début (Ns plus petits) vous obtenez la somme correcte (bien que votre vecteur out contienne beaucoup d'éléments> 1 dans les positions 0: limite et beaucoup d'éléments = 0 au-delà du limite). Si toutefois vous choisissez N assez grand, les éléments de votre vecteur de out commencent à dépasser la limite entière et recommencent à partir de 0. Dès que cela se produit, votre somme est largement désactivée. Pour revérifier, sachez que la limite uint8 est de 255 (256 entiers) et 256 ^ 2 = 65536. Définissez N = 65536 avec dtype='uint8' et check_add(65536) renverra 0.

importer numpy comme np

def check_add(N):
    N = int(N)
    out = np.zeros(N,dtype='uint64')
    np.add.at(out, np.arange(N,dtype='uint64'), 1)
    return np.sum(out)/N

Notez également que vous n'avez pas besoin du vecteur np.ones mais que vous pouvez simplement le remplacer par 1, si tout ce qui vous intéresse est d'incrémenter uniformément tout de 1.


4 commentaires

Mais l'original out et ones dtypes étaient déjà float64, qui ne sont pas soumis à débordement d'entier et a assez de précision pour gérer toutes les opérations en cause.


L'arange dtype original aurait été int64, ce qui est également beaucoup.


Afaik, le type de données par défaut pour np.arange est int32, ce qui n'est pas suffisant pour gérer 1E10. Je ne crois pas que ce soit un bug, mais un comportement mathématique.


Le dtype par défaut pour numpy.arange dépend des arguments et du système d'exploitation, mais quel que soit le système d'exploitation, numpy.arange(int(1e10)) utilisera le dtype int64.



1
votes

Vous int32 :

1E10 % (np.iinfo(np.int32).max - np.iinfo(np.int32).min + 1)  # + 1 for 0
Out[]: 1410065408

Il y a votre numéro étrange (googler ce numéro m'a en fait amené ici, c'est ainsi que j'ai compris cela.)

Maintenant, ce qui se passe dans votre fonction est un peu plus étrange. D'après la documentation de ufunc.at vous devriez simplement accumuler en ajoutant les 1 valeurs des indices inférieurs à np.iinfo(np.int32).max et les indices négatifs au-dessus de np.iinfo(np.int32).min - mais il semble que 1) travaille à l'envers et 2) s'arrête quand il arrive au dernier débordement. Sans creuser dans le code c , je ne pourrais pas vous dire pourquoi, mais c'est probablement une bonne chose que cela fasse - votre fonction échouerait silencieusement et avec le "correct" moyen si elle avait fait les choses de cette façon, tout en corrompant vos résultats (avoir 2 ou 3 dans ces indices et 0 au milieu).


3 commentaires

Cela devrait juste fonctionner - int32 ne devrait pas du tout être impliqué. Quelque chose dans le code C utilise peut-être un type de données trop petit quelque part.


numpy_intp l' ufunc.at trouvé - il y avait un int qui aurait dû être un numpy_intp dans le code source ufunc.at .


Je l'ai compris, mais je ne suis pas du tout qualifié pour fouiller dans le code c



2
votes

Il s'agit d'un vieux bogue, numéro 13286 de NumPy . ufunc.at utilisait une variable trop petite pour le compteur de boucles. Il a été corrigé il y a quelque temps, alors mettez à jour votre NumPy. (Le correctif est présent dans la version 1.16.3 et les versions ultérieures.)


2 commentaires

Mec, j'ai eu 90% du chemin et je n'ai aucun crédit: P


C'est ça! Merci pour l'aide. @DanielF, malheureusement, je ne peux pas vous donner 90% du crédit. Si je pouvais je le ferais.