<p>numpy.isclose vs math.isclose</p>

Créé le 5 déc. 2017  ·  78Commentaires  ·  Source: numpy/numpy

numpy.isclose (https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.isclose.html):

abs(a - b) <= (atol + rtol * abs(b))

math.isclose (https://docs.python.org/3/library/math.html#math.isclose):

abs(a - b) <= max(rtol * max(abs(a), abs(b)), atol)

Notez que l'équation de Numpy n'est pas symétrique et met en corrélation les paramètres atol et rtol , les deux sont de mauvaises choses (IMO).

Voici une situation où Numpy marque «incorrectement» deux nombres comme étant égaux:

a = 0.142253
b = 0.142219
rtol = 1e-4
atol = 2e-5

# true because atol interferes with the rtol measurement
abs(a - b) <= (atol + rtol * abs(b))
Out[24]: True

# correct result, rtol fails
abs(a - b) <= max(rtol * max(abs(a), abs(b)), atol)
Out[29]: False

En voici un autre, ce problème de symétrie de cas:

a = 0.142253
b = 0.142219
rtol = 1e-4
atol = 1.9776e-05

# relative to b
abs(a - b) <= (atol + rtol * abs(b))
Out[73]: False

#relative to a
abs(a - b) <= (atol + rtol * abs(a))
Out[74]: True

# math one has no problems with this
abs(a - b) <= max(rtol * max(abs(a), abs(b)), atol)
Out[75]: False

La version mathématique de Python semble être à toute épreuve, est-ce que Numpy devrait commencer à l'utiliser? Y a-t-il des avantages à utiliser la version Numpy?

57 - Close?

Commentaire le plus utile

@njsmith : merci de m'avoir amené.

Un peu d'histoire: quand j'ai proposé isclose pour le stdlib, nous avons certainement considéré numpy comme de l'art antérieur. Si ce n'était que moi, j'aurais peut-être utilisé une approche compatible, par souci de compatibilité :-).

Mais le reste de la communauté a pensé qu'il était plus important de faire ce qui était "bien" pour Python, donc une longue discussion s'est ensuivie ... J'ai essayé de capturer l'essentiel du point dans le PEP, si vous voulez aller chercher.

Voici la référence à numpy:

https://www.python.org/dev/peps/pep-0485/#numpy -isclose

Vous pouvez voir que les mêmes points ont été soulevés que dans cette discussion.

Au final, trois points clés sont ressortis:

1) une approche symétrique entraînerait la moindre surprise.

2) la tolérance absolue par défaut doit probablement être nulle, afin de ne pas faire d'hypothèses sur l'ordre de grandeur des arguments.

3) la différence entre les tests «faible» et «fort» n'était pas pertinente lorsqu'elle était utilisée avec de petites tolérances, comme c'est le cas d'utilisation attendu.

En fin de compte, je pense que nous avons trouvé la «meilleure» solution pour math.isclose.

Mais est-ce assez «meilleur» pour briser la rétrocompatibilité? Je ne pense pas.

Malheureusement, de nombreuses fonctionnalités ont été ajoutées à une grande partie de numpy (et de python) car elles étaient utiles, mais sans beaucoup de discussion actuelle, ces choses obtiennent, nous avons donc beaucoup de conceptions sous-optimales. Ceci est attendu (et nécessaire) pour une jeune bibliothèque, et nous devons simplement vivre avec maintenant.

@njsmith a raison - je pense que très peu d'utilisations de np.isclose () ont les tolérances fixées par une analyse d'erreurs FP rigoureuse, mais plutôt, essayez une erreur, avec np.isclose () lui-même.

Cependant, je pense que le plus gros problème est la valeur par défaut atol - cela suppose que vos arguments sont d'ordre 1 - ce qui pourrait être une hypothèse TRÈS fausse, et comme cela entraînerait souvent des tests réussis, cela ne devrait pas , les utilisateurs peuvent ne pas remarquer.

In [85]: np.isclose(9.0e-9, 1.0e-9)
Out[85]: True

Aie!

et oui, c'est le atol cause ceci:

In [86]: np.isclose(9.0e-9, 1.0e-9, atol=0.0)
Out[86]: False

C'est donc probablement une bonne idée d'avoir un chemin à parcourir.

J'aime l'idée de l'argument de mot-clé - semble beaucoup plus simple que d'essayer de jouer avec __future__ et autres.

Et nous pourrions alors décider si nous voulions commencer à émettre des avertissements d'obsolescence et finalement changer les nombreuses versions par défaut en aval ...

Tous les 78 commentaires

Trop tard pour changer quoi que ce soit à mon humble avis.

C'est l'une des fonctions les plus largement utilisées dans tout numpy (via assert_allclose ) Le seul chemin pour cela serait de choisir un autre nom.

J'avoue que l'implémentation actuelle ressemble à un bug. Notez que l'utilisation de max(abs(a), abs(b)) au lieu de abs(b) ne cassera aucun test existant, cela détend simplement la condition dans le cas où abs(a) > abs(b) .

À tout le moins, cela nécessite un avertissement dans la docstring indiquant qu'il ne correspond pas à la

Que diriez-vous d'ajouter ceci?

from numpy.__future__ import isclose

Incidemment, cela peut être une idée pour le contrôle de version des nombres aléatoires ...

Pour clarifier, l'importation n'importerait pas réellement isclose mais activerait la nouvelle sémantique. Il serait ramassé dans un module.

L'idée est que nous pouvons permettre aux gens d'utiliser la fonction correcte et en même temps, nous n'avons pas besoin de casser un code existant.

, l'importation n'importerait pas réellement isclose mais activerait la nouvelle sémantique

Je ne pense pas qu'il soit possible de ne pas importer cela. Choisissez simplement un nom plus long, comme mathlike_ufunc s, qui pourrait également couvrir remainder .

Notez que from __future__ import print_function en fait un print_function global

En fait, le style d'importation from __future__ import * n'est peut-être pas approprié ici - en python, il affecte _syntax_ au niveau par fichier, et faire quelque chose de similaire dans numpy serait délicat

Les exceptions d'itérateur n'étaient pas un changement de syntaxe. Nous avons juste besoin d'un petit module C pour inspecter l'espace de noms du module appelant. Nous pouvons même mettre en cache les modules afin que seul le temps d'importation soit ralenti, et seulement de manière triviale.

Vous avez raison - et true_division est évidemment analogue à isclose.

C'est en fait un bon moyen d'éviter les problèmes de # 9444, au lieu d'utiliser des instructions with pour gérer les fonctionnalités obsolètes.

Soyons CC @ ChrisBarker-NOAA en tant que créateur de math.isclose .

Il s'agit d'un problème assez mineur IMO. Généralement, atol et rtol sont choisis en les tripotant jusqu'à ce que les tests réussissent, et le but est de détecter les erreurs d'un ordre de grandeur supérieur aux tolérances. Peut-être qu'il serait logique de relâcher la partie rtol comme @charris l'a suggéré, mais je ne pense vraiment pas que cela vaille la peine de retirer des astuces d'introspection de pile pour ce petit ajustement. Et nous aurions toujours le problème que isclose est souvent appelé indirectement par des choses comme assert_allclose ou divers assistants de test tiers.

J'ai demandé sur StackOverflow à propos de l'utilisation des importations de style __future__ : https://stackoverflow.com/questions/29905278/using-future-style-imports-for-module-specific-features-in-python

TLDR: c'est possible, mais pas facile ni propre.

L'introspection de pile n'est pas seulement pour cela, mais est proposée comme une politique générale pour changer les valeurs de retour des fonctions. La question est: en principe, faut-il changer cela? Je pense que si la réponse est oui, la période de dépréciation doit être d'au moins quelques années compte tenu de l'utilisation généralisée. Python n'a jamais inclus de module antérieur, mais c'est une option pour essayer d'améliorer la stabilité de l'API, s'il y a une demande.

L'autre méthode consisterait simplement à ajouter ceci en tant qu'option isclose(...,symmetric=True) ou assert_allclose(symmetric=True) où la valeur par défaut serait False , la situation actuelle.

Je conviens que c'est un problème mineur lorsque vous ne donnez pas de sens aux valeurs rtol et atol , il suffit de les régler pour réussir les tests unitaires, comme mentionné par @njsmith.

Cependant, vous voudrez parfois dire que l'erreur se situe dans, par exemple, 1% ( rtol=0.01 ).
Dans ce cas, le pire des cas d'erreur concernant la mesure rtol est de 100% ( rtol * abs(b) se rapproche de atol , puis atol + rtol * abs(b) ~= 2 * rtol * abs(b) ).

Ce qui signifie que certaines valeurs peuvent passer avec une erreur d'environ 2%:

atol = 1e-8 #default
rtol = 0.01 # 1%

b = 1e-6
a = 1.0199e-6 # ~ 2% larger comapared to b
abs(a - b) <= (atol + rtol * abs(b))
True

La mise en œuvre de l'idée de @charris rendrait ce cas particulier très légèrement pire, car cela détend encore plus la comparaison, mais cela en vaut la peine car il élimine le problème de symétrie et est en effet rétrocompatible.

IMO, il serait préférable que Numpy utilise la fonction Math, mais je comprends que le changement peut être trop perturbateur et peut-être pas important pour la plupart des utilisateurs. Faire l'option de changer entre le noyau isclose serait utile.

@njsmith : merci de m'avoir amené.

Un peu d'histoire: quand j'ai proposé isclose pour le stdlib, nous avons certainement considéré numpy comme de l'art antérieur. Si ce n'était que moi, j'aurais peut-être utilisé une approche compatible, par souci de compatibilité :-).

Mais le reste de la communauté a pensé qu'il était plus important de faire ce qui était "bien" pour Python, donc une longue discussion s'est ensuivie ... J'ai essayé de capturer l'essentiel du point dans le PEP, si vous voulez aller chercher.

Voici la référence à numpy:

https://www.python.org/dev/peps/pep-0485/#numpy -isclose

Vous pouvez voir que les mêmes points ont été soulevés que dans cette discussion.

Au final, trois points clés sont ressortis:

1) une approche symétrique entraînerait la moindre surprise.

2) la tolérance absolue par défaut doit probablement être nulle, afin de ne pas faire d'hypothèses sur l'ordre de grandeur des arguments.

3) la différence entre les tests «faible» et «fort» n'était pas pertinente lorsqu'elle était utilisée avec de petites tolérances, comme c'est le cas d'utilisation attendu.

En fin de compte, je pense que nous avons trouvé la «meilleure» solution pour math.isclose.

Mais est-ce assez «meilleur» pour briser la rétrocompatibilité? Je ne pense pas.

Malheureusement, de nombreuses fonctionnalités ont été ajoutées à une grande partie de numpy (et de python) car elles étaient utiles, mais sans beaucoup de discussion actuelle, ces choses obtiennent, nous avons donc beaucoup de conceptions sous-optimales. Ceci est attendu (et nécessaire) pour une jeune bibliothèque, et nous devons simplement vivre avec maintenant.

@njsmith a raison - je pense que très peu d'utilisations de np.isclose () ont les tolérances fixées par une analyse d'erreurs FP rigoureuse, mais plutôt, essayez une erreur, avec np.isclose () lui-même.

Cependant, je pense que le plus gros problème est la valeur par défaut atol - cela suppose que vos arguments sont d'ordre 1 - ce qui pourrait être une hypothèse TRÈS fausse, et comme cela entraînerait souvent des tests réussis, cela ne devrait pas , les utilisateurs peuvent ne pas remarquer.

In [85]: np.isclose(9.0e-9, 1.0e-9)
Out[85]: True

Aie!

et oui, c'est le atol cause ceci:

In [86]: np.isclose(9.0e-9, 1.0e-9, atol=0.0)
Out[86]: False

C'est donc probablement une bonne idée d'avoir un chemin à parcourir.

J'aime l'idée de l'argument de mot-clé - semble beaucoup plus simple que d'essayer de jouer avec __future__ et autres.

Et nous pourrions alors décider si nous voulions commencer à émettre des avertissements d'obsolescence et finalement changer les nombreuses versions par défaut en aval ...

J'aimerais proposer la suggestion de @bashtage :

"" "
L'autre méthode consisterait simplement à ajouter ceci comme une option isclose (..., symetric = True) ou assert_allclose (symmetric = True) où la valeur par défaut serait False, la situation actuelle.
"" "

Je pense que c'est une meilleure option qu'un nouveau nom de fonction, et pourrait ouvrir la voie à une dépréciation future et à un changement de la valeur par défaut (ou non).

Je pense qu'en plus du test symétrique (fort), atol devrait également être par défaut à zéro.

Compte tenu de cela, nous avons peut-être besoin d'un meilleur nom pour le paramètre que symmetric , même si ce n'est pas mal ...

Oh - et il pourrait être bon de vérifier que isclose() n'est pas appelé ailleurs dans numpy en plus de assert allclose() .

Le moyen le plus simple de le faire est probablement de mettre un avertissement d'obsolescence dans et
puis supprimez-le avant de fusionner. Nous pourrions simplement aller de l'avant et mettre le
avertissement d'obsolescence en ce sens qu'il faut changer le code existant en
symétrique = Faux; il n'y a aucune raison de dire que la valeur par défaut doit être
changé de sitôt, même si l'avertissement est là.

Avons-nous besoin de l'ajouter à assert_allclose aussi, ou faisons-nous simplement le changement en silence? Faire en sorte que les gens mettent à jour chacun de leurs tests pour faire taire un avertissement n'est pas vraiment différent de leur faire mettre à jour leurs tests pour corriger la réduction de la tolérance.

@xoviat @ eric-wieser il ne peut pas y avoir de modifications incompatibles vers l'arrière de assert_allclose ni d'avertissement d'obsolescence de sa part, bien trop perturbateur. À moins que je ne manque où vous souhaitez mettre un avertissement d'obsolescence?

J'ai peur que cela ressemble encore à beaucoup de douleur pour l'infinitésimal
gagner, pour moi.

Avoir atol différent de zéro par défaut n'est pas un accident. Il est nécessaire d'obtenir
comportement par défaut raisonnable pour tout test où le résultat attendu comprend
des zéros exacts.

Aucune modification de assert_allclose n'est requise, car elle serait mise à jour en
en interne pour utiliser l'ancien comportement.

Les changements silencieux de l'OMI sont désagréables! L'avertissement d'obsolescence est uniquement destiné à
les gens n'ont finalement pas besoin de taper l'indicateur dans une invite interactive pour
obtenir le nouveau comportement, rien de plus

Les changements silencieux de l'OMI sont désagréables!

D'accord. Cependant, il ne devrait y avoir aucun changement. Les avertissements de dépréciation dans les fonctions largement utilisées obligent également les utilisateurs (du moins ceux qui effectuent réellement la maintenance) à apporter des modifications.

Qu'en est-il de la résolution du problème de non-symétrie comme le suggère atol != 0.0 .

Nous ne pouvons pas résoudre ce problème sans informer les gens du changement
comportement. La seule façon dont je sais comment procéder, c'est avec un avertissement.

Ce qui serait vraiment bien, c'est un outil de refactoring automatisé pour injecter des indicateurs
dans l'ancien code. Ensuite, «maintenance» serait simplement d'exécuter un script sur votre
code; 5 minutes de travail.

Désolé, cela peut être corrigé, simplement avec un nouveau drapeau.

Qu'en est-il de la résolution du problème de non-symétrie comme le suggère

Cette suggestion (https://github.com/numpy/numpy/issues/10161#issuecomment-349384830) semble réalisable.

Nous ne pouvons pas résoudre ce problème sans informer les gens du changement de comportement.

Nous ne le réparons pas. C'est le problème du rapport coût / bénéfice de tout changement qui vient d'être discuté dans le problème du versionnage sémantique. C'est certainement un cas dans lequel nous n'apporterons aucun changement ni ne publierons d'avertissement d'une fonction de test largement utilisée. Notez que le comportement actuel n'est pas un bogue (bien que sans doute un choix sous-optimal).

Pour être clair, je proposais un changement pour isclose mais pas assert_allclose.
En regardant les sources, ce changement sera un tiers comme perturbateur.
Cependant, l'avantage est encore probablement trop faible. Je ne pense à personne
des objections à l'ajout d'un drapeau, correct?

Je pense que personne ne s'oppose à l'ajout d'un drapeau, n'est-ce pas?

Pas sûr, cela dépend des détails. La suggestion de @charris peut ne pas nécessiter de drapeau, et tout drapeau qui introduit cela est un peu discutable pour les tableaux:

In [1]: import math

In [2]: math.isclose(0, 1e-200)
Out[2]: False

Je suis opposé à l'ajout d'indicateurs sans motivations concrètes ni cas d'utilisation. Test décisif utile: si un développeur novice vous demandait pourquoi ce drapeau existe, comment l'expliqueriez-vous?

comment expliquez-vous?

  1. Vous utilisez l'indicateur symmetric=True si vous souhaitez vectoriser les appels vers math.isclose
  2. Ce devait être un drapeau pour ne pas casser le code existant.

Je recherche plutôt des situations où il y a un problème réel en cours de résolution.

Le seul problème réel résolu ici est probablement de faire des débutants
pensez plus à la précision en virgule flottante. Je veux dire, l'exemple de @rgommers
semble que ce serait faux, mais que faire si vous avez affaire à de très petits
Nombres? IMO l'implémentation math.isclose est meilleure, mais je ne
pense qu'il y a consensus là-dessus. Malheureusement, il n'y a pas vraiment de
solution universelle pour déterminer si les nombres sont «proches». Mais
basé sur les réponses des autres (qui ne sont pas fausses!), je n'ai pas vraiment
toute modification de l'API à l'avenir. Je suppose que la seule action à entreprendre est
probablement une mise à jour de la documentation alors (fameux derniers mots, étant donné que je
pensait auparavant qu'un drapeau serait bien)?

Sûrement une note comparant à math.isclose et documentant comment obtenir un comportement vectoriel identique en définissant rtol et atol, si l'on veut, serait ok.

Aussi, si je comprends bien, @charris a proposé une solution pour permettre
isclose pour être moins tolérant qu'il ne l'est actuellement, ce qui ne casserait pas
tous les tests. Je pense toujours que ce serait une bonne idée d'émettre un avertissement (le
avertissement doit être émis une fois) s'il y a une situation où isclose
considèrent les nombres comme «proches» alors qu'ils ne l'étaient pas auparavant. C'est beaucoupmieux que de simplement changer le comportement de la fonction en silence et ne pas laisser
tout le monde le sait quand cela les affecte.

Je pense que des éclaircissements sont nécessaires ici sur ce dont nous discutons. Il y a deux choses qui font que math.isclose diffère:

  • Une valeur par défaut différente pour atol
  • Une définition différente de rtol

Je ne pense pas que nous puissions faire quoi que ce soit au sujet du premier problème, à part le documenter comme étant différent de `math.isclose.

Le deuxième problème, je pense, est mieux résolu en ajoutant un argument symmetric qui vaut par défaut False . Maintenant, nous pouvons écrire dans nos documents _ " np.isclose(x, y, symmetric=True, atol=0) est une version vectorisée de math.isclose(x, y) " _, ce qui me semble être la chose que nous essayons de résoudre.

De là, nous avons trois options:

  1. Documentez l'argument supplémentaire et ne faites rien d'autre
  2. Déprécier l'appel de isclose sans l'argument, forçant les utilisateurs à écrire isclose(..., symmetric=False) pour obtenir l'ancien comportement sans avertissement (et similaire pour allclose ). Je soupçonne que cela n'atteindra pas trop de code, mais le résultat est moins lisible sans gain énorme. assert_close serait changé pour appeler isclose(..., symmetric=False) interne, donc les utilisateurs de celui-ci ne seraient pas affectés
  3. Comme ci-dessus, mais nécessite également l'argument symmetric à assert_close . Ce serait un taux de désabonnement massif en aval
  4. Changer silencieusement le comportement

Parmi ceux-ci, je pense que l'option 1 est irréprochable, mais le reste ne ressemble pas à des choses qui valent la peine d'être perturbées.

Edit: 2 pourrait être acceptable si l'avertissement n'est émis que si le comportement change, ce qui serait beaucoup moins bruyant.

@ eric-wieser Il y a une troisième différence: la façon dont atol et rtol sont combinés. ( math.isclose utilise max , numpy.isclose utilise + ). Cela signifie qu'à moins que atol ou rtol soit nul, il n'existe aucun moyen général de faire correspondre un appel math.isclose appel numpy.isclose .

Je ne pense toujours pas que cela vaille la peine d'ajouter des API visibles par l'utilisateur.

J'étais en faveur de la deuxième option. Je suis toujours en faveur de cette option, avec
la stipulation supplémentaire que numpy fournirait un système automatisé
outil de refactoring (ajouté à entry_points) que vous pouvez simplement exécuter sur votre
projets existants pour les corriger. D'après ce que les autres ont dit, cela ressemble à
cette option ne serait pas favorisée par d'autres.

Je ne suis pas et je n'ai jamais été en faveur des trois ou quatre options. Dans
De plus, je ne suis pas favorable au changement de comportement des fonctions jusqu'à un avertissement
a été émis pour au moins quatre versions majeures.

En supposant que les autres ne sont pas d'accord avec l'option deux (qu'ils ont), je serais
en faveur de la première option. Mais d'autres (en particulier @njsmith) ne sont pas en faveur
de l' une des options que vous avez données ici. Du moins c'est ma perception.

@njsmith Ce n'est pas correct; vous pouvez changer le comportement de la fonction avec l'indicateur.

Je plie cette troisième différence dans un handwavey _ "rtol is different" _

"" "
Je recherche plutôt des situations où il y a un problème réel en cours de résolution.
"" "
Je parierai des dollars sur des beignets (ce que je vais faire car je n'ai aucune idée de ce que cela signifie ...) qu'il y a des tests qui réussissent qui ne devraient pas parce qu'atol rend le test beaucoup moins sensible que ça devrait être.

Il semble qu'il y ait trois "problèmes" avec l'implémentation actuelle:

1) ce n'est pas symétrique

  • Je pense que c'est dommage, mais vraiment pas un gros problème, et cela ne fait presque aucune différence quand les valeurs sont vraiment proches :-) Je pense que cela ne fait littéralement aucune différence si rtol <1e-8 (du moins si atol vaut 0,0)

2) atol affecte le résultat même s'il n'est pas comparé à zéro (c'est inévitable) - mais il change effectivement la tolérance d'environ un facteur de deux - ou même plus si atol est grand, ce qu'il pourrait être si vous travaillez avec un grand ordre de valeurs de magnitude.

3) atol est différent de zéro par défaut - je pense en fait que c'est le plus gros problème (en particulier avec l'algorithme actuel d'ajout des deux) car il peut très facilement conduire à des tests réussis qui ne devraient pas - et trop souvent nous le sommes un peu paresseux - nous écrivons le test avec la tolérance par défaut, et s'il réussit, nous pensons que nous avons terminé. (quand j'ai réalisé que c'était ainsi que cela fonctionnait, je suis retourné au code y, et en ai trouvé quelques-uns - oups!

Quelqu'un dans ce fil a dit quelque chose à propos de "il y aurait quelque chose de mal si:

isclose (1e-200, 0,0)

a renvoyé False par défaut. Je ne suis pas d'accord - oui, ce serait surprenant, mais cela obligerait l'utilisateur à réfléchir à ce qui se passe, alors que l'implémentation actuelle donne (par exemple):

Dans [8]: np.isclose (1e-20, 1e-10)
Sortie [8]: Vrai

vraiment? on est DIX ORDRES de MAGNITUDE plus grands que les autres et il revient Vrai ????

Mon point est que le fait d'avoir un atol différent de zéro donne peut-être des résultats moins surprenants dans le cas courant de la comparaison à zéro, mais BEAUCOUP plus dangereux et des résultats erronés lorsque vous travaillez avec de petits nombres (petit étant vraiment rien de moins de cet ordre de grandeur 1.

Et si vous travaillez avec de grands nombres, disons supérieurs à 1e8, l'atol par défaut est également inapproprié.

Et associé à (2), cela signifie que l'atol par défaut peut également gâcher les tests de tolérance relative de manière surprenante.

Donc: non, ce n'est pas un bug, et ce n'est pas "cassé", mais c'est assez sous-optimal, donc ce serait bien d'avoir un moyen de progresser vers une meilleure implémentation.

J'ai aimé l'approche du drapeau, mais je pense que je change d'avis - le problème est que, à moins que nous ne la déconseillons et que le drapeau par défaut ne change à un moment donné, presque tout le monde utilisera "l'ancien" algorithme à peu près pour toujours. Et il y a eu beaucoup de bons arguments pour lesquels nous ne pourrions probablement pas le déprécier.

Alors peut-être qu'une nouvelle fonction, avec un nouveau nom est en ordre. Nous pourrions ajouter à la documentation encourageant les gens à utiliser le nouveau, et _peut-être_ ajouter des avertissements à un moment donné lorsque les gens utilisent le nouveau, mais nous ne casserions jamais le code de personne.

Tout le monde a une idée d'un bon nom pour une nouvelle fonction ????

Peut-être np.math.isclose et amis? Je ne sais pas.

Cela résout que np.remainder est également différent, et vous permet également de vectoriser facilement le code en utilisant from numpy import math

Donc je serais +1 sur un module np.math

Introduire de nouvelles fonctions avec un comportement légèrement différent des autres fonctions qui fonctionnent comme ça depuis une décennie est en général une très mauvaise idée. Dans ce cas, il n'y a vraiment pas beaucoup de problème qui ne puisse pas être résolu de manière adéquate avec la documentation. Donc -1 sur une nouvelle fonction. Et certainement -1 sur un tout nouveau sous-module.

vraiment? on est DIX ORDRES de MAGNITUDE plus grands que les autres et il revient Vrai ????

Que votre attente soit vraie ou fausse dépend totalement du contexte. Si c'est pour les nombres qui proviennent d'une distribution continue sur [0, 1) alors oui, vous vous attendez probablement à True. Un utilisateur doit vraiment comprendre les tolérances absolues / relatives et ce qu'une fonction fait réellement, et c'est à cela que servent les documents.

symmetric_isclose() est un peu long, mais pas offensant à mes yeux.: bikeshed: emoji>

Le 10 décembre 2017 à 20h09, Ralf Gommers [email protected] a écrit:

Présentation de nouvelles fonctions avec un comportement légèrement différent des autres
fonctions qui ont fonctionné comme ça pendant une décennie est en général un très mauvais
idée.

Je n'aime pas ça non plus - mais casser le code des gens avec un changement est pire.
Avez-vous une autre idée que de ne pas améliorer votre engourdissement?

Dans ce cas, il n'y a pas vraiment de problème qui ne puisse pas être
résolu avec la documentation.

Je ne suis pas d'accord - les valeurs par défaut comptent - beaucoup.

Et je ne pense pas qu'il y ait moyen d'ajuster les paramètres pour vous donner un
comparaison symétrique.

Et certainement -1 sur un tout nouveau sous-module.

Moi aussi. S'il y a d'autres choses dans le module maths sans numpy
équivalents qui seraient utiles, ils pourraient être ajoutés à l'espace de noms de numpy
comme tout le reste.

vraiment? l'un est DIX ORDRES de MAGNITUDE plus grand que l'autre et il revient
Vrai????

Que votre attente soit vraie ou fausse dépend totalement du contexte.

Exactement - c'est pourquoi il n'y a AUCUN défaut «raisonnable» pour atol.

Si c'est pour les nombres qui proviennent d'une distribution continue sur [0, 1)
alors oui, vous vous attendez probablement à True.

Mais isclose est présenté comme une comparaison relative - et ici le
la relative est en train d'être complètement effacée par la comparaison absolue,
sans que l'utilisateur n'ait à y penser.

C'est tout mon point - la valeur par défaut est UNIQUEMENT appropriée si vous attendez votre
les valeurs doivent être de l'ordre de grandeur 1. Un cas courant, bien sûr, mais pas
universel.

Cela revient à ce qui est pire - un faux négatif ou un faux positif.
Et dans le cas d'usage courant des tests, un faux positif est bien pire.

(c'est à dire un test qui ne devrait pas passer)

Un utilisateur doit vraiment comprendre les tolérances absolues / relatives et ce qu'un
fonction fait réellement,

Absolument - mais une fonction doit également avoir des valeurs par défaut raisonnables et un
algorithme robuste et facile à comprendre.

J'ai tendance à améliorer la documentation et à laisser la fonction tranquille. Le manque de symétrie peut être expliqué comme "isclose (a, b) signifie que a est proche de b, mais en raison de la précision absolue variable de la virgule flottante, il n'est pas toujours le cas que b est proche de a. Lors des tests, b doit être le résultat attendu et a doit être le résultat réel. " Notez que cela a du sens pour les tests, le cas de la fonction symétrique est en fait un peu plus compliqué à justifier. L'erreur relative, impliquant la division comme elle le fait, n'est pas symétrique.

Je n'aime pas ça non plus - mais casser le code des gens avec un changement est pire. Avez-vous une autre idée que de ne pas améliorer votre engourdissement?

Vous émettez déjà ici un jugement de valeur selon lequel l'ajout de votre nouvelle fonction vaut mieux qu'aucune nouvelle fonction. Ce n'est pas à mon humble avis. Trois options:

  1. Un changement radical - inacceptable, vous pouvez arrêter d'en discuter.
  2. Simplement ajouter de meilleurs documents
  3. Ajouter une fonction

2 est globalement un meilleur choix que 3, c'est donc ce qui rend numpy "meilleur". Vous ignorez également que cette chose atol / rtol n'est pas limitée à une seule fonction, alors quelle est la prochaine étape - un nouveau et légèrement "meilleur" assert_allclose ? Cela rend le cas pour doc uniquement encore plus clair.

C'est un défaut assez grave, essentiellement le code testant votre code est bogué ... pas bon. Souhaitez-vous envoyer quelque chose à la lune qui est testé avec numpy.isclose et atol par défaut? J'y réfléchirais à deux fois ... et c'est pourquoi nous devons faire ressortir ces écueils dans la documentation.
L'ajout d'une fonction d'alias ne fera qu'encombrer la base de code à moins qu'elle ne soit imposée aux utilisateurs (cela ne se produit pas).

Je suis d'accord pour dire que le problème de symétrie est mineur, mais nous devons tout de même le corriger. Le laisser pourrait distraire les utilisateurs de véritables pièges.

@rgommers a écrit:
"" "
Vous émettez déjà ici un jugement de valeur selon lequel l'ajout de votre nouvelle fonction vaut mieux qu'aucune nouvelle fonction. Ce n'est pas à mon humble avis.
"" "
Eh bien, j'ai passé BEAUCOUP de temps à réfléchir et à débattre de math.isclose , et nous avons commencé par examiner l'implémentation numpy entre autres. donc, oui, je pense que cette approche est meilleure. Et j'ai pensé à partir de cette discussion que c'était à peu près un consensus.

Et obtenir un meilleur algorithme / interface dans numpy le rend meilleur, oui.

Peut-être voulez-vous dire qu'avoir à la fois l'ancienne et la nouvelle, meilleure, fonction dans numpy n'est PAS mieux que de simplement laisser l'ancienne fonction (peut-être mieux documentée). Bien sûr, c'est un point tout à fait valable, mais j'essayais d'avoir cette discussion, et le commentaire précédent selon lequel "L'introduction de nouvelles fonctions avec un comportement légèrement différent des autres fonctions qui fonctionnent comme ça depuis une décennie est en général une très mauvaise idée" semblait mettre un terme à la discussion - mon point est que si la nouvelle méthode est «suffisamment meilleure», elle en vaudrait la peine. Ce sur quoi nous n'avons manifestement pas de consensus, c'est de savoir si cette option particulière est "suffisamment meilleure", pas si elle est "meilleure".

Et au fait, personnellement, je ne me suis pas convaincu que cela valait la peine de changer, mais je veux avoir la discussion.

Voici quelques hypothèses de ma part. Je ne sais pas si nous pourrons jamais savoir avec certitude si elles sont correctes, mais:

1) la plus grande utilisation de np.isclose () est pour les tests - vos réponses sont-elles suffisamment proches de ce que vous attendez? - c'est via np.assert_all_close, ou plus directement, dans les tests pytest, ou ....

2) La plupart des gens, la plupart du temps, ne font rien de tel qu'une analyse rigoureuse des erreurs en virgule flottante pour déterminer la qualité des réponses attendues. Au contraire, ils essaient une valeur, et si elle échoue, ils examinent les résultats et décident s'il s'agit vraiment d'une erreur, ou s'ils doivent ajuster la ou les tolérances du test.

  • cela signifie qu'il importe peu que l'atol et le rtol soient fusionnés et que le test soit symétrique.

3) beaucoup de gens, la plupart du temps, démarrent le processus en (2) avec des tolérances par défaut, et ne cherchent à ajuster la tolérance que si un test échoue.

  • CELA signifie qu'avoir un atol par défaut est assez dangereux - des tests réussis qui ne devraient pas être une très mauvaise chose.

4) Les gens ne lisent pas les documents (au-delà de l'étape initiale «comment appeler cela») - du moins pas jusqu'à ce qu'ils trouvent un comportement déroutant, puis ils pourraient essayer de comprendre comment quelque chose fonctionne vraiment pour dissiper la confusion . Mais voyez (3) - si un test n'échoue pas, ils ne savent pas qu'il faut aller regarder la documentation pour comprendre pourquoi.

Tout cela m'amène à la conclusion que numpy serait "meilleur" avec un test de proximité FP plus mathématique.

Et pourquoi mieux docs une bonne idée, mais pas assez.

Peut-être que je me trompe totalement, et la plupart des gens lisent attentivement la documentation et sélectionnent soigneusement rtol et atol pour leur problème la plupart du temps - mais je sais que moi, ni la demi-douzaine de personnes de mon équipe, je l'ai fait jusqu'à ce que je devienne conscient de ces problèmes.

: bikeshed: (putain, ça n'a pas fonctionné - pas d'emoji astucieux)

peut-être relatively_close , ou rel_close ?

Une ride "amusante" supplémentaire: assert_allclose utilise réellement atol = 0 par
défaut. Il y a un autre fil de discussion quelque part pour savoir si nous pouvons réparer
cette incohérence. (Sur mon téléphone, je ne peux donc pas le trouver facilement.)

Le 11 décembre 2017 à 14h58, "Chris Barker" [email protected] a écrit:

@rgommers https://github.com/rgommers a écrit:
"" "
Vous portez déjà un jugement de valeur ici en ajoutant votre nouvelle fonction
est mieux qu'aucune nouvelle fonction. Ce n'est pas à mon humble avis.
"" "
Eh bien, j'ai passé BEAUCOUP de temps à réfléchir et à débattre
math.isclose, et nous avons commencé par examiner l'implémentation numpy
entre autres. donc, oui, je pense que cette approche est meilleure. Et j'ai pensé
de cette discussion que c'était à peu près un consensus.

Et obtenir un meilleur algorithme / interface dans numpy le rend meilleur, oui.

Peut-être voulez-vous dire qu'avoir à la fois l'ancien et le nouveau, mieux, fonctionnent
numpy n'est PAS meilleur, que de simplement quitter l'ancien (peut-être mieux
documenté) y fonctionner. Bien sûr, c'est un point tout à fait valable, mais j'étais
essayez d'avoir cette discussion, et le commentaire précédent que "Présentation
nouvelles fonctions avec un comportement légèrement différent des autres fonctions
qui fonctionnent comme ça depuis une décennie est en général une très mauvaise idée "
semblait mettre un terme à la discussion - mon point est que si la nouvelle façon
"assez mieux" que cela en vaudrait la peine. Ce que nous n'avons clairement pas
le consensus est de savoir si cette option particulière est "assez meilleure", et non
si c'est «mieux».

Et au fait, personnellement, je ne me suis pas convaincu que ça vaut le coup
changer, mais je veux avoir la discussion.

Voici quelques hypothèses de ma part. Je ne sais pas que nous pouvons
jamais savoir avec certitude si elles sont correctes, mais:

1.

la plus grande utilisation de np.isclose () est pour les tests - sont vos réponses
assez proche de ce que vous attendez? - c'est via np.assert_all_close, ou
plus directement, dans les tests pytest, ou ....
2.

La plupart des gens, la plupart du temps, ne font rien de tel que rigoureux
analyse des erreurs en virgule flottante pour déterminer la qualité des réponses
devrait être. Au contraire, ils essaient une valeur, et si elle échoue, ils regardent
résultats et décidez s'il s'agit vraiment d'une erreur ou si vous devez ajuster le
tolérance (s) du test.

  • cela signifie que peu importe si l'atol et
    rtol sont fusionnés et si le test est symétrique.

  • beaucoup de gens, la plupart du temps, commencent le processus en (2) avec
    tolérances par défaut, et ne cherchez à ajuster la tolérance que si un test échoue.

  • CECI signifie qu'avoir un atol par défaut est assez dangereux - tests
    passer qui ne devrait pas est une très mauvaise chose.

  • Les gens ne lisent pas les documents (au-delà du premier "comment appeler cela"
    stade) - du moins pas jusqu'à ce qu'ils trouvent un comportement déroutant, puis ils
    pourrait entrer et essayer de comprendre comment quelque chose fonctionne vraiment pour éclaircir
    la confusion. Mais voir (3) - si un test n'échoue pas, ils ne savent pas
    allez regarder la documentation pour comprendre pourquoi.

Tout cela m'amène à la conclusion que numpy serait "mieux" avec un
plus test de proximité FP de type math.isclose.

Et pourquoi mieux docs une bonne idée, mais pas assez.

Peut-être que je me trompe totalement et que la plupart des gens lisent attentivement les documents et
sélectionner soigneusement rtol et atol pour leur problème la plupart du temps -
mais je sais que moi, ni la demi-douzaine de personnes de mon équipe, je l'ai fait jusqu'à ce que je
a pris conscience de ces problèmes.

: bikeshed: (putain, ça n'a pas fonctionné - pas d'emoji astucieux)

peut-être relativement_close, ou rel_close?

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/10161#issuecomment-350886540 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AAlOaNquy47fsOkvBlxcSa-Mxyhgdimlks5s_bORgaJpZM4Q2J-P
.

Vous voulez dire # 3183?

assert_allclose utilise réellement atol = 0 par défaut.

Intéressant - cela me fait me sentir mieux.

Cela ramène un souvenir flou des discussions passées sur les différences entre les deux - était-ce le seul?

Salut,
Mes bibliothèques thermo, fluides et ht utilisent largement assert_allclose de numpy, donc je partage quelques réflexions ici car il est lié. Ce fil rend la différence de comportement très alarmante, et un peu comme les gens devraient s'attendre à trouver des bogues dans leur code à cause du manque de symétrie et / ou de la différence de combinaison de atol et rtol ainsi que de l'atol par défaut (non présent dans assert_allclose , Je sais). J'ai donc voulu voir si mes bibliothèques avaient des bogues et j'ai piraté une implémentation très grossière de assert_allclose que j'ai ensuite changé mes tests pour l'utiliser temporairement.

Je pense que mon utilisation de assert_allclose est représentative - je compare les valeurs sélectionnées individuellement et fais des tests où je fuzz les choses et appelle assert_allclose sur le résultat des fonctions de manière paramétrique. Je compare des ints et des flotteurs de toutes sortes de magnitudes. J'ai relativement peu réfléchi au choix d'un rtol ou d'un atol, ajoutant rarement un atol ou changeant rtol à un défaut moins strict. Les bibliothèques appellent respectivement assert_allclose 13159, 1178 et 4243 fois.

J'ai donc fait le changement et j'ai exécuté mes tests. Je suis très heureux de dire que je n'ai trouvé aucun nouveau bogue ou échec de test ; les seuls échecs de tests que j'ai pu trouver concernaient mon implémentation d'assert_allclose.

Je sais qu'il y en a d'autres qui auraient moins de chance et se heurteraient à un problème si quelque chose dans assert_allclose ou isclose était modifié. Personnellement, je me sentirais plus à l'aise pour écrire des tests si j'avais un mode assert_allclose qui répliquait le comportement de math.allclose, que ce soit via une autre fonction ou le drapeau symmetric . Mais je sais que c'est beaucoup de travail pour quelqu'un et qu'il n'y en a pas encore eu de PR. Je suis content d'avoir le confort d'avoir vérifié mon code pour cette faille même une fois!

4880 est celui auquel je pensais.

Le 11 décembre 2017, à 17h01, Nathaniel J.Smith [email protected]
a écrit:

4880 https://github.com/numpy/numpy/pull/4880 est celui auquel je pensais

de.

Merci, notez que l'exemple de statsmodel fait bien mon point sur le
défaut de l'atol - tout autre chose que 0.0 est dangereux.

Le fait que cela arrive tous les deux ans indique-t-il que nous devrions
enfin faire quelque chose?

Peut-être que oui. C'est une petite verrue, mais parfois ces petits ennuis ajoutent
au fil du temps.

Et même si je suis content que assert_allclose ait la valeur par défaut de l'atol à zéro
(soutenant mon point de vue que c'est une option acceptable, même si vous ne
d'accord que c'est la meilleure option) beaucoup d'entre nous - et plus tout le temps - ne le sont pas
en utilisant unittest, et peut donc utiliser np.all_close directement dans les tests.

Et oui Ralf, si nous faisons un changement, nous voudrons changer les trois
fonctions étroitement liées :-(. Mais cela nous donne la possibilité de les rendre
plus cohérent, un bonus?

Alors que la transition py2-3 ne s'est pas très bien déroulée, il y a quelque chose à faire
dit pour avoir une version «c'est OK pour nettoyer les verrues» à un moment donné :-)

Il existe une autre option, pour déplacer réellement is_close vers la version mathématique (en fait meilleure) de atol=0 : changez la valeur par défaut en None , et essayez avec atol=0 ; si tout True retourne simplement; si des False , réessayez avec la norme atol et si les résultats changent, émettez un avertissement de dépréciation (et retournez le "vieux" résultat).

Malheureusement, on ne peut pas faire à la fois la symétrie et l'atol en même temps, car ils vont dans des directions différentes (c'est-à-dire qu'il faudrait toujours exécuter les deux cas, ce qui semble un peu trop). Mais je n'aime pas beaucoup plus le atol default que le manque de symétrie ...

Je ne suis pas favorable à la modification des fonctions sauf s'il existe une fonction qui
vous pouvez utiliser qui a l'ancien comportement. De plus, il devrait y avoir un
outil de refactoring automatisé pour effectuer ce changement si cela est fait. Cependant ce n'est pas
même sur la table maintenant.

La transition py3k n'est pas quelque chose que nous voulons jamais avoir à nouveau et
n'est pas un modèle. Rouler les nettoyages en une seule grande version n'est pas une bonne chose
approche de l'OMI.

Bien que la transition py2-3 ne se soit pas très bien déroulée, il y a quelque chose à dire pour avoir une version «c'est OK de nettoyer les verrues» à un moment donné :-)

Juste pour aborder cette idée générale selon laquelle les numéros de version spéciaux peuvent apporter des modifications décisives: la règle générale est qu'il est correct de nettoyer les verrues, s'il existe un plan de transition clair qui évite des niveaux de douleur inacceptables et que les avantages sont suffisants pour justifier frais. Le point clé ici est que le jugement doit porter sur l'effet sur les utilisateurs, et non sur la question de savoir si nous avons "suivi les règles". (Si vous voulez être sophistiqué, nous sommes des conséquentialistes , pas des déontologues .) Donc la seule raison de déclarer une version spécifique comme "c'est celle qui casse les choses" est si cela rend les choses beaucoup plus faciles à gérer pour les utilisateurs. OMI, les avantages de regrouper les modifications de rupture sont généralement mineurs s'ils existent - le code cassé est du code cassé - et même s'ils existent, il est très rare qu'ils vont faire basculer l'analyse coûts / avantages de "non" à " Oui".

atol est différent de zéro par défaut - je pense en fait que c'est le plus gros problème (en particulier avec l'algorithme actuel d'ajout des deux) car il peut très facilement conduire à des tests réussis qui ne devraient pas - et trop souvent nous sommes paresseux - nous écrivons le test avec la tolérance par défaut, et s'il réussit, nous pensons que nous avons terminé. (quand j'ai réalisé que c'était ainsi que cela fonctionnait, je suis retourné au code y, et en ai trouvé quelques-uns - oups!

Notez que l'exemple de statsmodel rend bien mon point de vue sur la valeur par défaut de atol - tout autre chose que 0.0 est dangereux. [...] beaucoup d'entre nous - et plus tout le temps - n'utilisons pas unittest, et peuvent donc utiliser np.all_close directement dans les tests.

«Nous devrions casser le code utilisateur pour augmenter la cohérence avec un autre package» ne se classe pas très bien sur l'échelle des coûts / bénéfices. "Il y a des incohérences à l'intérieur de numpy qui ne sont pas seulement déroutantes, mais déroutantes d'une manière qui conduit directement à des bogues silencieux dans le code utilisateur, et nous pouvons corriger cela" est bien plus convaincant. Je ne me suis pas encore forgé une opinion, mais si vous voulez progresser ici, c'est ce sur quoi je vais insister.

Une note:

"" "J'ai peu réfléchi au choix d'un rtol ou d'un atol,
ajouter rarement un atol
-couper-

$ call assert_allclose 13159, 1178 et 4243 fois respectivement.

-couper-

Je n'ai trouvé aucun nouveau bogue ou échec de test ;

"" "

Bonne nouvelle, même si je note que assert_allclose a par défaut atol à zéro. Et
c'est pourquoi :-)

@njsmith a écrit:

"Nous devrions casser le code utilisateur pour augmenter la cohérence avec un autre package"

Je ne pense pas que quiconque sur ce fil préconise cela - je suis sûr que non. La seule cohérence que quiconque préconise est la cohérence entre les fonctions numpy associées.

"Il y a des incohérences à l'intérieur de numpy qui ne sont pas seulement déroutantes, mais déroutantes d'une manière qui conduit directement à des bogues silencieux dans le code utilisateur, et nous pouvons y remédier"

C’est ce que je préconise, du moins. Et je pense que la plupart des autres.

Le problème est que nous ne pouvons pas le résoudre sans:

Rompre la rétrocompatibilité, ce que je ne pense pas que quiconque pense que c'est assez sérieux à faire - même avec un cycle de dépréciation.

Ou

Créer un nouveau drapeau ou une nouvelle fonction.

Je pense qu'une nouvelle fonction serait une façon plus propre de le faire. L'ancien code pourrait rester inchangé aussi longtemps qu'il le souhaite, un nouveau code pourrait utiliser les nouvelles fonctions, et une recherche et remplacement (ou quelques importations sous forme d'appels) pourrait faciliter le changement d'un fichier à la fois.

(Je suppose que vous pourriez même faire un patch de singe numpy dans votre code de test ....)

Nous avons déjà:

  • allclose
  • assert_allclose
  • assert_almost_equal
  • assert_approx_equal
  • assert_array_almost_equal
  • assert_array_almost_equal_nulp
  • assert_array_max_ulp

Je ne pense pas que l'ajout d'options à cette liste fera vraiment une grande différence pour les vrais utilisateurs.

Eh bien, j'ai passé BEAUCOUP de temps à réfléchir et à débattre de math.isclose, et nous avons commencé par examiner l'implémentation numpy entre autres. donc, oui, je pense que cette approche est meilleure.

Moi aussi, et j'ai été l'un des principaux responsables de numpy.testing - ce n'est guère un problème nouveau. Insister sur les majuscules pour dire que vous avez raison ne le fait pas.

Peut-être voulez-vous dire qu'avoir à la fois l'ancienne et la nouvelle, meilleure, fonction dans numpy n'est PAS mieux que de simplement laisser l'ancienne fonction (peut-être mieux documentée). Bien sûr, c'est un point tout à fait valable, mais j'essayais d'avoir cette discussion,

En effet, il devrait être clair que c'est ce que je voulais dire.

"L'introduction de nouvelles fonctions dont le comportement n'est que légèrement différent des autres fonctions qui fonctionnent comme ça depuis une décennie est en général une très mauvaise idée" a semblé fermer la discussion

Non, cela souligne un réel problème avec l'ajout de nouvelles fonctions qui sont trop souvent ignorées ou qui n'ont pas assez de poids. Dans ce cas, cela semble assez clair pour de nombreux développeurs principaux - il semble que vous êtes en train de juger que ce navire a navigué de @njsmith , @pv , @charris et moi.

Je m'excuse à l'avance d'avoir ressenti cela, mais assert_allclose est le comportement correct (ou du moins assez proche de lui). Cependant, isclose peut causer des ennuis car il suppose une tolérance absolue comme d'autres l'ont noté. J'envisage de soumettre un PR pour mettre une grande boîte rouge sur la page de documentation np.isclose pour avertir les gens de ce comportement. Comment ça sonne?

Je m'excuse à l'avance d'avoir ressenti cela, mais assert_allclose est le
comportement correct (ou du moins assez proche de lui). Cependant, isclose peut obtenir
les gens en difficulté parce que cela suppose une tolérance absolue comme les autres l'ont
c'est noté. J'envisage de soumettre un PR pour mettre une grande boîte rouge sur le
np.isclose la page de documentation pour avertir les gens de ce comportement. Comment
ce son?

+1

I il y a un consensus sur le fait que les documents devraient être meilleurs.

Merci,

-CHB

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/10161#issuecomment-351182296 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AA38YDw4sdhRWYmeeyr4met1RCwWsjMQks5s_uBmgaJpZM4Q2J-P
.

J'appuie l'amélioration de la documentation, mais je pense que le changement proposé n'est pas aussi utile qu'il pourrait l'être.

.. avertissement :: La valeur par défaut atol n'est pas appropriée pour les nombres
sont bien moins d'un.

Premièrement, cet avertissement n'est même pas vrai, en général. La valeur par défaut atol _est_ appropriée lorsque vous souhaitez traiter des nombres inférieurs à 1e-8 comme égaux à zéro. Cela n'est pas possible avec une tolérance relative, c'est pourquoi une tolérance absolue existe. Inversement, la valeur par défaut atol _ n'est pas_ appropriée lorsque vous souhaitez traiter des nombres inférieurs à 1e-8 comme significatifs. Ne supposons pas qu'une taille unique convient à tous.

Par conséquent, @xoviat , avec le plus profond respect, je m'oppose fermement à votre déclaration subjective:

... assert_allclose _est_ le comportement correct (ou du moins assez proche).

Je pense que le problème avec la documentation actuelle, c'est qu'elle décrit très bien _ce que_ atol est, mais ne décrit pas _pourquoi_ elle est là, et donc les utilisateurs peuvent ne pas comprendre quand changer la valeur par défaut .

Je propose quelque chose du genre:

"La définition d'une petite valeur différente de zéro de atol permet de traiter les nombres très proches de zéro comme étant effectivement égaux à zéro. Par conséquent, la valeur par défaut de atol peut ne pas convenir à votre utilisation cas. Choisissez atol sorte que les nombres supérieurs à atol soient considérés par vous comme significatifs (distincts de zéro) et les nombres inférieurs à atol sont considérés par vous comme négligeables (identique à zéro). "

Enfin, j'ajouterais la même note aux docstrings de isclose et allclose .

C'est ma suggestion. À prendre ou a laisser.
À votre santé.

Veuillez laisser des commentaires sur le PR. En particulier, votre proposition est trop verbeuse IMO.

Désolé si j'étais trop ennuyeux - je ne pensais vraiment pas qu'il y avait encore un consensus.

@xoviat : merci d'avoir travaillé sur la documentation.

Puisque cela reviendra, je vais (probablement) mettre fin à cette discussion avec une mini version d'un NEP conçu pour être rejeté - résumer les propositions et les raisons du rejet. Pour mémoire - depuis que les numéros précédents ont tout simplement disparu.

Désolé d'avoir soulevé le problème py2 / 3 - si / comment nettoyer les verrues dans numpy est une discussion pour un autre endroit et une autre heure.

Le problème avec isclose()

Ce problème (et d'autres référencés) est le résultat d'observations selon lesquelles numpy.isclose() et ses amis utilisent un algorithme moins qu'optimal, a une valeur par défaut litigieuse pour atol et les diverses fonctions associées ont des valeurs par défaut différentes. Cela entraîne globalement de la confusion pour les utilisateurs et, dans le pire des cas, des tests de faux positifs lorsque les utilisateurs ne réfléchissent pas à la définition de la atol .

En particulier, l'implémentation dans la stdlib: math.isclose() , fournit un algorithme et des valeurs par défaut différents, et sans doute meilleurs: c'est un test symétrique, il ne mélange pas rtol et atol, et l'atol par défaut est 0.0

Il existe un quasi-consensus sur le fait que la situation n'est pas idéale, mais aucun consensus sur le fait qu'elle est suffisamment mauvaise pour y remédier.

Options envisagées:

Modification des algorithmes et / ou des valeurs par défaut:

Rejeté universellement en raison de problèmes de compatibilité descendante, même avec une période d'obsolescence et des avertissements - ces fonctions sont largement utilisées dans les tests, ce serait donc à tout le moins un gros ennui.

Ajout d'un paramètre supplémentaire avec un indicateur pour sélectionner un algorithme différent

Cela ne casserait aucun code existant, mais persisterait pour toujours avec une API laide et déroutante.

Ajout d'une directive de type __future__

TLDR: c'est possible, mais pas facile ni propre. Personne ne semblait vouloir poursuivre cela.

Créer encore une autre fonction

Celui-ci semble être la seule option qui a gagné du terrain, mais n'a pas été pris en charge par les développeurs principaux qui ont participé à la discussion.

La manière la plus propre de "corriger" ce problème serait d'ajouter un np.rel_close() [ou un autre nom] avec un nouvel algorithme et des valeurs par défaut, probablement celles utilisées dans math.isclose . La nouvelle fonction serait documentée comme "recommandée" à utiliser pour le code futur. Il serait possible d'ajouter des avertissements d'obsolescence à l'ancien à l'avenir - mais personne ne semblait penser que le bruit en vaudrait la peine à ce stade.

Un outil de refactoring pourrait être construit pour effectuer le remplacement - mais qui va le faire pour cette seule utilisation?

Il en résulterait probablement deux fonctions très similaires dans un avenir prévisible, et "L'introduction de nouvelles fonctions avec un comportement légèrement différent des autres fonctions qui ont fonctionné comme ça pendant une décennie est en général une très mauvaise idée."

Conclusion:

Cela ne vaut pas la peine pour le petit gain, mais de meilleurs documents sont en ordre, et cela se fait ici: # 10214

Il me reste encore une question:

@njsmith a écrit:

"" "
Nous avons déjà:

allclose
assert_allclose
assert_almost_equal
assert_approx_equal
assert_array_almost_equal
assert_array_almost_equal_nulp
assert_array_max_ulp

Je ne pense pas que l'ajout d'options à cette liste fera vraiment une grande différence pour les vrais utilisateurs.
"" "

Voulez-vous dire que l'ajout d'un nouveau serait une bonne idée?

Je note également:

la plupart sont des affirmations - et la prolifération des affirmations est un effet secondaire de l'architecture unittest.

Comme nous sommes nombreux à passer à d'autres architectures de test (par exemple pytest), le besoin d'assertions disparaît.

Alors que faire avec numpy.testing est une question distincte de ce qu'il faut faire avec le noyau numpy.

Comme nous sommes nombreux à passer à d'autres architectures de test (par exemple pytest), le besoin d'assertions disparaît.

En fait, c'est un danger en soi. Les tests peuvent passer de assert_allclose(...) à assert np.allclose(...) , et deviendront silencieusement moins stricts, ce qui est une mauvaise chose.

J'utilise toujours assert_allclose dans l'environnement pytest car il donne un message d'échec utile:

    def test_shs():
        a = [0.1, 0.2, 0.3, 0.4]
        b = [0.2, 0.3, 0.3, 0.4]

>       np.testing.assert_allclose(a,b)
E       AssertionError: 
E       Not equal to tolerance rtol=1e-07, atol=0
E       
E       (mismatch 50.0%)
E        x: array([ 0.1,  0.2,  0.3,  0.4])
E        y: array([ 0.2,  0.3,  0.3,  0.4])

vs en utilisant assert np.allclose()

    def test_shs():
        a = [0.1, 0.2, 0.3, 0.4]
        b = [0.2, 0.3, 0.3, 0.4]
>       assert np.allclose(a, b)
E       assert False
E        +  where False = <function allclose at 0x7f20b13c9840>([0.1, 0.2, 0.3, 0.4], [0.2, 0.3, 0.3, 0.4])
E        +    where <function allclose at 0x7f20b13c9840> = np.allclose

Il est peut-être possible de résoudre ce problème en implémentant pytest_assertrepr_compare , mais je ne sais pas comment l'appliquer aux appels de fonction, et je ne peux même pas savoir où pytest l'invoque.

@ eric-wieser: a écrit:

"En fait, c'est un danger en soi. Les tests pourraient être modifiés de assert_allclose (...) à assert np.allclose (...), et deviendront silencieusement moins stricts, ce qui est une mauvaise chose."

Exactement mon point - c'est une "mauvaise idée" de supposer que tout le monde va utiliser les assertions pour les tests, et donc ne pas se soucier de savoir si les valeurs par défaut de isclose () et allclose () sont appropriées pour les tests - dans un idéal monde, ils devraient certainement être.

Voulez-vous dire que l'ajout d'un nouveau serait une bonne idée?

Non, je voulais dire que, étant donné que nous avons déjà toute une ménagerie de façons légèrement différentes d'exprimer des tests presque égaux, malheureusement, la plupart des utilisateurs ne remarqueront pas ou ne comprendront pas encore un autre ajout.

Je note également:
la plupart sont des affirmations - et la prolifération des affirmations est un effet secondaire de l'architecture unittest.
Comme nous sommes nombreux à passer à d'autres architectures de test (par exemple pytest), le besoin d'assertions disparaît.

Ils se trouvent être écrits comme des assertions, mais AFAICT chacune de ces fonctions code en fait une définition différente de «presque égal». (Je pense. Certaines distinctions sont si obscures que je ne peux pas dire si elles sont réelles ou non.)

Modification des algorithmes et / ou des valeurs par défaut:
Rejeté universellement en raison de problèmes de compatibilité descendante, même avec une période d'obsolescence et des avertissements - ces fonctions sont largement utilisées dans les tests, ce serait donc à tout le moins un gros ennui.

Je ne dirais pas tout à fait ça comme ça. Pour moi, c'est la seule approche qui aurait potentiellement des avantages suffisants pour justifier le coût. Je ne dis pas qu'ils le feraient , et je ne peux pas parler pour les autres développeurs de base; Je voudrais voir des données, et sans données, se tromper du côté du conservatisme semble être le bon choix. Mais si quelqu'un produisait des données, je les regarderais au moins :-).

Par exemple, si quelqu'un se présentait et disait: «J'ai essayé la modification proposée sur 3 grands projets, et cela a conduit à 12 échecs supplémentaires sur 10 000 tests, et sur ces 12, 8 étaient de véritables bogues silencieux qui avaient été cachés par l'ancien mauvais défauts "... ce serait assez convaincant. D'autant que c'est un cas où nous avons la capacité technique de faire des alertes étroitement ciblées. Je suppose que les chiffres réels ne seraient pas aussi favorables, mais jusqu'à ce que quelqu'un vérifie, qui sait.

Le lun 18 décembre 2017 à 15:57, Nathaniel J. Smith <
[email protected]> a écrit:

Non, je voulais dire que, étant donné que nous avons déjà toute une ménagerie de
des manières légèrement différentes d'exprimer des tests presque égaux, malheureusement
la plupart des utilisateurs ne remarqueront pas ou ne comprendront pas encore un autre ajout.

Eh bien, il y a une grande différence - chacune d'elles a été ajoutée parce qu'elle l'a fait
quelque chose de différent , et avec les caprices de la virgule flottante, ceux
les différences peuvent être importantes.

Une nouvelle fonction de proximité relative ferait essentiellement la même chose, mais
fais le mieux". Et le but serait de recommander que le nouveau soit utilisé
au lieu de l'ancien (et peut-être déprécier l'ancien éventuellement).

Honnêtement, je ne sais pas si c'est un argument pour ou contre l'idée,
bien que.

Par exemple, si quelqu'un se présentait et disait: "J'ai essayé ma proposition de modification sur 3 gros

projets, et cela a conduit à 12 échecs supplémentaires sur 10000 tests, et parmi ceux-ci
12, 8 d'entre eux étaient de véritables bugs silencieux qui avaient été cachés par le vieux mauvais
par défaut "... ce serait assez convaincant. D'autant plus qu'il s'agit d'un
cas où nous avons la capacité technique de faire des avertissements étroitement ciblés.
Je suppose que les chiffres réels ne seraient pas aussi favorables, mais jusqu'à ce que
quelqu'un vérifie, qui sait.

hmm, ma base de code a environ 1500 tests - pas 10000, mais je vais vous donner un
coup. Si rien d'autre, je peux trouver un bogue ou un mauvais test ou deux. Ou obtenez plus
réconfort!

Salut à tous,

Je voulais intervenir ici après qu'un collègue ait rencontré ce problème.

_Disclaimer: Je n'ai pas vraiment réfléchi à la logique avant de crier "feu dans le hall" et de le jeter par-dessus la clôture. Si j'en ai fait un hash embarrassant, soyez gentil, plutôt s'il vous plaît.

Pour moi, tout cela ressemble beaucoup au problème classique d'architecture / conception «poulet et œuf» - sémantique vs implémentation. Tous les bogues n'existent pas dans le code, parfois ils existent dans la sémantique. Ce n'est pas très différent (voire pas du tout) d'un algorithme parfaitement implémenté pour découvrir que l'algorithme est défectueux - le bogue n'est pas dans le code, il est dans l'algorithme, mais de toute façon, c'est toujours un bogue. Fondamentalement, cette discussion ressemble un peu à la blague classique «ce n'est pas un bug, c'est une fonctionnalité». Bien sûr, un «choix» est juste cela, mais à mon humble avis, cela signale normalement la fin de la justification - si la sémantique est choisie telle quelle, telle qu'elle est mise en œuvre, alors qu'il en soit ainsi, la mise en œuvre est tout cool, fin de la discussion.

Alors, quelles sont les sémantiques «désirées» et / ou «attendues» de isclose() . En y réfléchissant, je me retrouve inexorablement en train de converger vers la sémantique définie par l'utilisateur, c'est-à-dire que l'utilisateur doit être capable de définir la sémantique définition de «fermer».

Par défaut

En ce qui concerne les valeurs par défaut, cela ne rompt finalement aucune sémantique. Cependant, un mauvais choix est dangereux. Si la valeur par défaut différente de zéro a été _uniquement_ choisie pour donner un comportement raisonnable lorsque abs(a - b) == 0 alors cela ressemble définitivement à la solution incorrecte. Cet état particulier ferait mieux d'être casé spécial car il est casé spécial sémantiquement. Il n'y a pas de «marge de manœuvre» dans la définition de «fermer» lorsque le diff est égal à zéro, c'est la pierre angulaire qui se définit automatiquement, c'est-à-dire qu'ils ne sont pas _close_, ils sont _exact_ - où «close» est un écart relatif par rapport à l'exact . (_Remarque: _La condition spéciale de boîtier peut (ou non) affecter les performances.)

+ vs max

Le mélange ou «shadowing» de rel_tol et a_tol raison de l'implémentation utilisant une somme et non max _does_ brise la sémantique de la fonction ci-dessus. La définition de l'utilisateur de «fermer» est liée d'une manière non triviale car elle déforme, et donc rompt, la sémantique de «relatif» et «absolu». En ne considérant que la sémantique ci-dessus, cet aspect _est_ un bogue. Je trouve @gasparka exemple d'ouverture un argument irréfutable de ce point.

Commutativité (c.-à-d. Symétrie)

Il n'y a vraiment que deux opérateurs ici: - in a-b , et <= in |a-b| <= f(a, b, atol, rtol) . Alors que la soustraction est anticommutative, |a - b| ne l'est pas, elle est commutative, par définition. Cependant, cela seul peut ne pas être suffisant pour indiquer la commutative de f(a, b, atol, rtol) . Il y a cependant ici un argument fort et faible.

  • Le faible étant le plus simple - encapsulation de la séparation des fonctions ou absence de - alors qu'arithémiquement la commutativité de |a - b| peut ne pas appliquer celle de f(a, b, atol, rtol) , par programme, f(a, b, atol, rtol) n'est pas vraiment question mais isClose(a, b, atol, rtol) . Ici, le calcul |a - b| est effectué en interne à la fonction soit isClose() := |a-b| <= f(a, b, atol, rtol) . Comme c'est interne et que a et b sont passés, pour que |a-b| soit commutatif, isClose() doit également l'être. Si ce n'est pas le cas, la commutativité de |a-b| perd tout son sens.
  • Argument fort: l'opération de comparaison n'est pas stricte, c'est-à-dire qu'elle est <= non < , donc pour la partie égalité de la comparaison, vous devez satisfaire |a-b| = f(a, b, atol, rtol) ce qui implique f(a, b, atol, rtol) _doit_ aussi être commutatif (je pense?). Ne pas le faire signifie que soit l'égalité ci-dessus est _never_ vraie (il n'y a pas de valeurs de a & b qui satisfont ceci) ou que l'inégalité stricte, < , a effectivement été défini sémantiquement, non?

Ce que vous faites (ou ne faites pas) à propos de tout ou partie de tout cela est une question complètement distincte.

Cette page vous a été utile?
0 / 5 - 0 notes