tl ; dr : le site numpy.matrix
est en train d'être dépréciée. Il existe quelques bibliothèques de premier plan qui dépendent de la classe en tant que dépendance (la plus importante étant scipy.sparse
), ce qui entrave la dépréciation à court terme de la classe, mais les utilisateurs sont fortement encouragés à utiliser la classe ndarray
(généralement créée à l'aide de la classe numpy.array
) à la place. Avec l'introduction de la @
pour la multiplication de matrices, un grand nombre des avantages relatifs des matrices ont été supprimés.
Pourquoi (pas) la classe matricielle ?
numpy.matrix
est une sous-classe de numpy.ndarray
. Elle était à l'origine destinée à une utilisation pratique dans les calculs impliquant l'algèbre linéaire, mais il existe à la fois des limitations et des différences surprenantes dans la façon dont ils se comportent par rapport aux instances de la classe plus générale des tableaux. Exemples de différences fondamentales de comportement :
- Formes : les tableaux peuvent avoir un nombre arbitraire de dimensions allant de 0 à l'infini (ou 32). Les matrices sont toujours à deux dimensions. Curieusement, alors qu'une matrice ne peut pas être créé avec plus de dimensions, il est possible d'injecter des dimensions singulières dans une matrice pour se retrouver techniquement avec une matrice multidimensionnelle :
np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1)
(non que cela ait une quelconque importance pratique).
- Indexation : les tableaux d'indexation peuvent vous donner des tableaux de n'importe quelle taille selon comment vous l'indexez . Les expressions d'indexation sur les matrices vous donneront toujours une matrice. Cela signifie que les deux
arr[:,0]
et arr[0,:]
pour un tableau 2d vous donne un tableau 1d ndarray
alors que mat[:,0]
a une forme (N,1)
et mat[0,:]
a une forme (1,M)
en cas de matrix
.
- Opérations arithmétiques : la principale raison de l'utilisation des matrices dans l'ancien temps était que les opérations arithmétiques (en particulier, la multiplication et la puissance) sur les matrices effectuent des opérations matricielles (multiplication et puissance de la matrice). La même chose pour les tableaux donne lieu à une multiplication et une puissance par éléments. Par conséquent,
mat1 * mat2
est valable si mat1.shape[1] == mat2.shape[0]
mais arr1 * arr2
est valable si arr1.shape == arr2.shape
(et bien sûr le résultat signifie quelque chose de complètement différent). Aussi, étonnamment, mat1 / mat2
réalise par élément division de deux matrices. Ce comportement est probablement hérité de ndarray
mais n'a aucun sens pour les matrices, surtout à la lumière de la signification de *
.
- Attributs spéciaux : les matrices ont quelques attributs pratiques en plus de ce que les tableaux ont :
mat.A
et mat.A1
sont des tableaux ayant la même valeur que np.array(mat)
et np.array(mat).ravel()
respectivement. mat.T
et mat.H
sont la transposée et la transposée conjuguée (adjointe) de la matrice ; arr.T
est le seul attribut de ce type qui existe pour l'élément ndarray
classe. Enfin, mat.I
est la matrice inverse de mat
.
Il est assez facile d'écrire du code qui fonctionne soit pour les ndarrays soit pour les matrices. Mais quand il y a une chance que les deux classes doivent interagir dans le code, les choses commencent à devenir difficiles. En particulier, beaucoup de code pourrait fonctionnent naturellement pour les sous-classes de ndarray
mais matrix
est une sous-classe qui se comporte mal et qui peut facilement casser le code qui tente de s'appuyer sur le typage des canards. Considérez l'exemple suivant qui utilise des tableaux et des matrices de forme (3,4)
:
import numpy as np
shape = (3, 4)
arr = np.arange(np.prod(shape)).reshape(shape) # ndarray
mat = np.matrix(arr) # same data in a matrix
print((arr + mat).shape) # (3, 4), makes sense
print((arr[0,:] + mat[0,:]).shape) # (1, 4), makes sense
print((arr[:,0] + mat[:,0]).shape) # (3, 3), surprising
L'addition de tranches des deux objets est catastrophiquement différente selon la dimension le long de laquelle nous tranchons. L'addition de matrices et de tableaux se fait par éléments lorsque les formes sont identiques. Les deux premiers cas ci-dessus sont intuitifs : nous additionnons deux tableaux (matrices), puis nous ajoutons deux lignes de chacun. Le dernier cas est vraiment surprenant : nous voulions probablement ajouter deux colonnes et nous nous sommes retrouvés avec une matrice. La raison en est bien sûr que arr[:,0]
a une forme (3,)
qui est compatible avec la forme (1,3)
mais mat[:.0]
a une forme (3,1)
. Les deux sont diffuser ensemble pour façonner (3,3)
.
Enfin, le plus grand avantage de la classe des matrices (c'est-à-dire la possibilité de formuler de manière concise des expressions matricielles compliquées impliquant un grand nombre de produits matriciels) a été supprimé lorsque la classe des matrices a été supprimée. le site @
l'opérateur matmul a été introduit dans python 3.5 premièrement mis en œuvre dans numpy 1.10 . Comparez le calcul d'une forme quadratique simple :
v = np.random.rand(3); v_row = np.matrix(v)
arr = np.random.rand(3,3); mat = np.matrix(arr)
print(v.dot(arr.dot(v))) # pre-matmul style
# 0.713447037658556, yours will vary
print(v_row * mat * v_row.T) # pre-matmul matrix style
# [[0.71344704]]
print(v @ arr @ v) # matmul style
# 0.713447037658556
En regardant ce qui précède, on comprend pourquoi la classe matrice a été largement préférée pour travailler avec l'algèbre linéaire : l'infixe *
a rendu les expressions beaucoup moins verbeuses et beaucoup plus faciles à lire. Cependant, nous obtenons la même lisibilité avec l'opérateur @
en utilisant python et numpy modernes. De plus, notez que le cas de la matrice nous donne une matrice de forme (1,1)
qui devrait techniquement être un scalaire. Cela implique également que nous ne pouvons pas multiplier un vecteur colonne avec ce "scalaire" : (v_row * mat * v_row.T) * v_row.T
dans l'exemple ci-dessus soulève une erreur car les matrices de forme (1,1)
et (3,1)
ne peuvent pas être multipliés dans cet ordre.
Par souci d'exhaustivité, il convient de noter que si l'opérateur matmul corrige le scénario le plus courant dans lequel les ndarrays sont sous-optimaux par rapport aux matrices, il existe encore quelques lacunes dans la gestion de l'algèbre linéaire de manière élégante à l'aide de ndarrays (bien que les gens aient toujours tendance à croire qu'il est préférable de s'en tenir à ces derniers). L'un de ces exemples est la puissance des matrices : mat ** 3
est la troisième puissance matricielle propre d'une matrice (alors qu'il s'agit du cube par éléments d'un ndarray). Malheureusement, numpy.linalg.matrix_power
est beaucoup plus verbeux. De plus, la multiplication matricielle in-place ne fonctionne bien que pour la classe des matrices. En revanche, alors que les deux PEP 465 et le grammaire python permettre @=
comme une affectation augmentée avec matmul, ceci n'est pas implémenté pour les ndarrays à partir de numpy 1.15.
Historique des dépréciations
Considérant les complications ci-dessus concernant le matrix
Il y a longtemps que des discussions récurrentes sur sa possible dépréciation ont lieu. L'introduction de la @
l'opérateur infixe qui était un prérequis énorme pour ce processus s'est produit en septembre 2015 . Malheureusement, les avantages de la classe matricielle à l'époque ont fait que son utilisation s'est largement répandue. Il existe des bibliothèques qui dépendent de la classe matricielle (l'une des plus importantes est la suivante scipy.sparse
qui utilise à la fois numpy.matrix
et renvoie souvent des matrices lors de la densification), donc les déprécier complètement a toujours été problématique.
Déjà en un fil de discussion de la liste de diffusion numpy datant de 2009 J'ai trouvé des remarques telles que
numpy a été conçu pour les besoins généraux de calcul, pas pour une branche particulière des mathématiques. nd-arrays sont très utiles pour beaucoup de choses. Sur Par contre, Matlab, par exemple, a été conçu à l'origine pour être une interface facile d'algèbre linéaire. Personnellement, quand j'utilisais Matlab, je j'ai trouvé cela très gênant - j'écrivais généralement des centaines de lignes de code qui n'avaient rien à voir avec l'algèbre linéaire, pour quelques lignes qui qui faisaient réellement des mathématiques matricielles. Je préfère de loin la méthode de numpy - les lignes de code d'algèbre lignes de code d'algèbre linéaire sont plus longues et plus maladroites, mais le reste est bien meilleur. meilleur.
La classe Matrix est l'exception à la règle : elle a été écrite pour fournir un moyen naturel d'exprimer l'algèbre linéaire. manière naturelle d'exprimer l'algèbre linéaire. Cependant, les choses se compliquent un peu lorsque l'on mélange matrices et tableaux, et même lorsque l'on s'en tient aux matrices il y a des confusions et des limitations -- comment exprimer un vecteur ligne vs un vecteur colonne ? colonne ? Qu'obtenez-vous lorsque vous itérez sur une matrice ? etc.
Il y a eu beaucoup de discussions sur ces questions, beaucoup de bonnes idées. idées, un peu de consensus sur la façon de l'améliorer, mais personne ayant les compétences pour le faire n'a la motivation suffisante pour le faire.
Ceux-ci reflètent les avantages et les difficultés découlant de la classe de la matrice. La première suggestion de dépréciation que j'ai pu trouver est la suivante à partir de 2008 Bien que partiellement motivé par un comportement non intuitif qui a changé depuis (en particulier, le découpage et l'itération sur une matrice donneront des matrices (lignes) comme on s'y attend le plus). La suggestion a montré à la fois qu'il s'agit d'un sujet très controversé et que les opérateurs infixes pour la multiplication de matrices sont cruciaux.
La mention suivante que j'ai pu trouver date de 2014 qui s'est avéré être un très fil de discussion fructueux. La discussion qui s'ensuit soulève la question de la gestion des sous-classes numpy en général, dont le thème général est toujours d'actualité. . Il y a également critiques sévères :
Ce qui a déclenché cette discussion (sur Github) est qu'il n'est pas possible de écrire du code typé canard qui fonctionne correctement pour :
- ndarrays
- matrices
- scipy.sparse matrices éparses
La sémantique des trois est différente ; scipy.sparse se situe quelque part entre matrices et ndarrays, certaines choses fonctionnant aléatoirement comme des matrices et d'autres non.
Avec un peu d'hyberbole ajouté, on pourrait dire que du point de vue du développeur np.matrix fait et a déjà fait du mal juste en existant, en perturbant les règles non déclarées de la sémantique ndarray en Python.
suivi d'un grand nombre de discussions intéressantes sur l'avenir possible des matrices. Même sans @
opérateur à l'époque, on réfléchit beaucoup à la dépréciation de la classe matrix et à la façon dont cela pourrait affecter les utilisateurs en aval. Pour autant que je puisse dire, cette discussion a directement conduit à la création du PEP 465 introduisant matmul.
Début 2015 :
A mon avis, une version "fixe" de np.matrix devrait (1) ne pas être une sous-classe de np.ndarray et (2) exister dans une bibliothèque tierce et non dans numpy lui-même.
Je ne pense pas qu'il soit vraiment faisable de fixer np.matrix dans son état actuel en tant que une sous-classe de ndarray, mais même une classe de matrice fixe n'a pas vraiment sa place dans numpy lui-même, qui a des cycles de versions et des garanties de compatibilité trop longs de compatibilité pour l'expérimentation -- sans mentionner que la simple existence de la classe la classe matrix dans numpy égare les nouveaux utilisateurs.
Une fois que le @
L'opérateur était disponible depuis un certain temps la discussion sur la dépréciation a refait surface , Revenir sur le sujet sur la relation entre la dépréciation de la matrice et scipy.sparse
.
Éventuellement, première action à déprécier numpy.matrix
a été prise fin novembre 2017 . Concernant les personnes à charge de la classe :
Comment la communauté traiterait-elle les sous-classes scipy.sparse matrix ? Ces sont toujours utilisées couramment.
Ils n'iront nulle part avant un certain temps (jusqu'à ce que les matrices ndarrays éparses se matérialisent au moins). Par conséquent, np.matrix doit être déplacé, et non supprimé.
( source ) et
alors que je veux me débarrasser de np.matrix autant que quiconque. tout le monde, le faire de sitôt serait vraiment perturbateur.
-
Il existe des tonnes de petits scripts écrits par des personnes qui ne qui ne savaient pas mieux ; nous voulons qu'ils apprennent à ne pas utiliser np.matrix mais casser tous leurs scripts est un moyen douloureux d'y parvenir.
-
Il y a des projets majeurs comme scikit-learn qui n'ont tout simplement pas d'autre alternative à l'utilisation de np.matrix, à cause de scipy.sparse.
Donc je pense que la voie à suivre est quelque chose comme :
-
Maintenant ou à chaque fois que quelqu'un met en place un PR : émettre un PendingDeprecationWarning dans np.matrix.__init__ (à moins que cela ne tue les performance pour scikit-learn et ses amis), et mettre une grande boîte d'avertissement en haut de la documentation. L'idée ici est de ne pas casser réellement code de quelqu'un, mais de commencer à faire passer le message que nous ne pensons que nous ne pensons pas que quiconque devrait utiliser ceci s'il a une alternative.
-
Après qu'il y ait une alternative à scipy.sparse : augmenter les avertissements, éventuellement jusqu'à FutureWarning pour que les scripts existants ne se cassent pas cassent pas mais qu'ils reçoivent des avertissements bruyants.
-
Eventuellement, si nous pensons que cela réduira les coûts de maintenance : le diviser en un sous-paquetage
( source ).
Statu quo
À partir de mai 2018 (numpy 1.15, pertinente. demande de retrait et commettre ) le docstring de la classe de matrice contient la note suivante :
Il n'est plus recommandé d'utiliser cette classe, même pour l'algèbre linéaire. Utilisez plutôt des tableaux réguliers. Cette classe pourrait être supprimée à l'avenir.
Et en même temps, un PendingDeprecationWarning
a été ajouté à matrix.__new__
. Malheureusement, les avertissements de dépréciation sont (presque toujours) réduits au silence par défaut La plupart des utilisateurs finaux de numpy ne verront donc pas cette forte indication.
Enfin, la feuille de route de numpy en date de novembre 2018 mentionne de multiples sujets connexes comme l'un des " tâches et fonctionnalités dans lesquelles [la communauté numpy] investira des ressources. " :
Certaines choses à l'intérieur de NumPy ne correspondent pas réellement à la portée de NumPy.
- Un système backend pour numpy.fft (pour que par exemple fft-mkl n'ait pas besoin de monkeypatch numpy)
- Réécriture des tableaux masqués pour ne pas être une sous-classe de ndarray - peut-être dans un projet séparé ?
- MaskedArray comme type de tableau de canards, et/ou
- dtypes qui supportent les valeurs manquantes
- Écrire une stratégie sur la façon de traiter le chevauchement entre numpy et scipy pour linalg et fft (et l'implémenter).
- Dépréciation de np.matrix
Il est probable que cet état perdurera tant que les grandes bibliothèques et les grands utilisateurs (et en particulier les scipy.sparse
) s'appuient sur la classe de la matrice. Cependant, il y a discussion en cours de se déplacer scipy.sparse
de dépendre de quelque chose d'autre, comme pydata/sparse
. Indépendamment de l'évolution du processus de dépréciation, les utilisateurs doivent utiliser le système de gestion de l'information de l ndarray
dans le nouveau code et, de préférence, porter l'ancien code si possible. En fin de compte, la classe de matrice sera probablement placée dans un paquetage séparé afin d'éliminer certaines des charges causées par son existence sous sa forme actuelle.