python numpy matrix deprecated

python - Estado de desaprobación de la clase de matriz NumPy



matrix deprecated (1)

¿Cuál es el estado de la clase de matrix en NumPy?

Me siguen ndarray que debería usar la clase ndarray lugar. ¿Vale la pena / seguro usar la clase de matrix en el nuevo código que escribo? No entiendo por qué debería usar ndarray s en su lugar.


tl dr: la clase numpy.matrix está en desuso. Hay algunas bibliotecas de alto perfil que dependen de la clase como una dependencia (la más grande es scipy.sparse ) que dificulta la desaprobación adecuada a corto plazo de la clase, pero se recomienda encarecidamente a los usuarios que utilicen la clase ndarray (generalmente creada usando el numpy.array conveniencia numpy.array ) en cambio. Con la introducción del operador @ para la multiplicación de matrices, se han eliminado muchas de las ventajas relativas de las matrices.

¿Por qué (no) la clase matriz?

numpy.matrix es una subclase de numpy.ndarray . Originalmente fue diseñado para un uso conveniente en cálculos que involucran álgebra lineal, pero existen limitaciones y diferencias sorprendentes en la forma en que se comportan en comparación con los casos de la clase de matriz más general. Ejemplos de diferencias fundamentales en el comportamiento:

  • Formas: las matrices pueden tener un número arbitrario de dimensiones que van desde 0 hasta el infinito (o 32). Las matrices son siempre bidimensionales. Por extraño que parezca, si bien no se puede crear una matriz con más dimensiones, es posible inyectar dimensiones singleton en una matriz para terminar con una matriz multidimensional técnicamente: np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1) (no es que esto tenga alguna importancia práctica).
  • Indexación: las matrices de indexación pueden proporcionarle matrices de cualquier tamaño, dependiendo de cómo lo indexe . Las expresiones de indexación en matrices siempre te darán una matriz. Esto significa que tanto arr[:,0] como arr[0,:] para una matriz 2d le dan una ndarray 1d, mientras que mat[:,0] tiene forma (N,1) y mat[0,:] tiene forma (1,M) en el caso de una matrix .
  • Operaciones aritméticas: la razón principal para usar matrices en los viejos tiempos era que las operaciones aritméticas (en particular, la multiplicación y la potencia) en las matrices realizan operaciones matriciales (multiplicación de la matriz y potencia de la matriz). Lo mismo para las matrices da como resultado una multiplicación y potencia elementales. En consecuencia, mat1 * mat2 es válido si mat1.shape[1] == mat2.shape[0] , pero arr1 * arr2 es válido si arr1.shape == arr2.shape (y, por supuesto, el resultado significa algo completamente diferente). Además, sorprendentemente, mat1 / mat2 realiza una división elemental de dos matrices. Este comportamiento probablemente se hereda de ndarray pero no tiene sentido para las matrices, especialmente a la luz del significado de * .
  • Atributos especiales: las matrices tienen algunos atributos útiles además de lo que tienen los arreglos: mat.A y mat.A1 son vistas de arreglo con el mismo valor que np.array(mat) y np.array(mat).ravel() , respectivamente . mat.T y mat.H son la transposición y la transposición conjugada (adjunta) de la matriz; arr.T es el único atributo que existe para la clase ndarray . Finalmente, mat.I es la matriz inversa de mat .

Es bastante fácil escribir código que funcione tanto para ndarrays como para matrices. Pero cuando existe la posibilidad de que las dos clases tengan que interactuar en código, las cosas comienzan a ser difíciles. En particular, una gran cantidad de código podría funcionar naturalmente para las subclases de ndarray , pero la matrix es una subclase de mal comportamiento que puede romper fácilmente el código que trata de basarse en la tipificación de pato. Considere el siguiente ejemplo utilizando matrices y matrices de forma (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

Agregar segmentos de los dos objetos es catastróficamente diferente dependiendo de la dimensión a lo largo de la cual cortamos. La adición de ambas matrices y matrices ocurre de manera elemental cuando las formas son las mismas. Los primeros dos casos de lo anterior son intuitivos: agregamos dos matrices (matrices), luego agregamos dos filas de cada una. El último caso es realmente sorprendente: probablemente quisimos agregar dos columnas y terminamos con una matriz. Por supuesto, la razón es que arr[:,0] tiene forma (3,) que es compatible con la forma (1,3) , pero mat[:.0] tiene forma (3,1) . Los dos se broadcast juntos para dar forma (3,3) .

Finalmente, la mayor ventaja de la clase de matriz (es decir, la posibilidad de formular de manera concisa expresiones de matriz complicadas que involucran una gran cantidad de productos de matriz) se eliminó cuando el operador @ matmul se introdujo en Python 3.5 , implementado por primera vez en el número 1.10 . Compara el cálculo de una forma cuadrática 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

Viendo lo anterior, está claro por qué la clase de matriz fue ampliamente preferida para trabajar con álgebra lineal: el operador infijo * hizo que las expresiones fueran mucho menos detalladas y mucho más fáciles de leer. Sin embargo, obtenemos la misma legibilidad con el operador @ utilizando python y numpy modernos. Además, tenga en cuenta que el caso de matriz nos da una matriz de forma (1,1) que técnicamente debería ser un escalar. Esto también implica que no podemos multiplicar un vector de columna con este "escalar": (v_row * mat * v_row.T) * v_row.T en el ejemplo anterior genera un error porque las matrices con forma (1,1) y (3,1) no se puede multiplicar en este orden.

Para completar, debe tenerse en cuenta que si bien el operador de matmul soluciona el escenario más común en el que los ndarrays son subóptimos en comparación con las matrices, todavía hay algunas deficiencias en el manejo del álgebra lineal con elegancia utilizando ndarrays (aunque la gente todavía tiende a creer que en general preferible atenerse a este último). Un ejemplo de ello es el poder de la matriz: mat ** 3 es el poder de la tercera matriz de una matriz (mientras que es el cubo elementwise de un ndarray). Desafortunadamente, numpy.linalg.matrix_power es bastante más detallado. Además, la multiplicación de matrices in situ solo funciona bien para la clase de matrices. En contraste, mientras que tanto PEP 465 como la gramática de python permiten @= como una asignación aumentada con matmul, esto no se implementa para ndarrays a partir del número 1.15.

Historia de la deprecacion

Teniendo en cuenta las complicaciones anteriores relacionadas con la clase de matrix , se han mantenido discusiones recurrentes sobre su posible desaprobación durante mucho tiempo. La introducción del operador @ infix, que era un gran requisito previo para este proceso, tuvo lugar en septiembre de 2015 . Desafortunadamente, las ventajas de la clase de matriz en días anteriores significaron que su uso se extendió ampliamente. Hay bibliotecas que dependen de la clase de matriz (una de las dependientes más importantes es scipy.sparse que usa la semántica numpy.matrix y, a menudo, devuelve matrices al densificar), por lo que su eliminación total siempre ha sido problemática.

Ya en un hilo de la lista de correo numpy de 2009 encontré comentarios como

numpy fue diseñado para necesidades computacionales de propósito general, no para una rama de matemáticas. Los nd-arrays son muy útiles para muchas cosas. En contraste, Matlab, por ejemplo, se diseñó originalmente para ser un frente fácil para el paquete de álgebra lineal. Personalmente, cuando usé Matlab, lo encontré muy incómodo: por lo general escribía cientos de líneas de código que no tenían nada que ver con el álgebra lineal, por cada pocas líneas que realmente hicieron matrices matriciales. Así que prefiero la manera de numpy: las líneas de código de álgebra lineal son más torpes, pero el resto es mucho mejor.

La clase Matrix es la excepción a esto: se escribió para proporcionar una forma natural de expresar el álgebra lineal. Sin embargo, las cosas se complican un poco cuando se mezclan matrices y matrices, e incluso cuando se adhieren a ellas, hay confusiones y limitaciones. ¿Cómo se expresa una fila frente a un vector de columna? ¿Qué obtienes cuando recorres una matriz? etc.

Ha habido mucha discusión sobre estos temas, muchas buenas ideas, un poco de consenso sobre cómo mejorarlo, pero nadie con la habilidad para hacerlo tiene la motivación suficiente para hacerlo.

Estos reflejan los beneficios y dificultades que surgen de la clase matriz. La primera sugerencia de desaprobación que pude encontrar es de 2008 , aunque está motivada en parte por un comportamiento no intuitivo que ha cambiado desde entonces (en particular, cortar y iterar sobre una matriz dará lugar a matrices (filas) como es de esperar). La sugerencia mostró que este es un tema altamente controvertido y que los operadores de infijo para la multiplicación de matrices son cruciales.

La siguiente mención que pude encontrar es de 2014, que resultó ser un hilo muy fructífero. La discusión subsiguiente plantea la cuestión del manejo de subclases numpy en general, cuyo tema general todavía está muy sobre la mesa . También hay fuertes críticas :

Lo que provocó esta discusión (en Github) es que no es posible escribir código de tipo pato que funcione correctamente para:

  • ndarrays
  • matrices
  • scipy.sparse matrices dispersas

La semántica de los tres es diferente; scipy.sparse está en algún lugar entre matrices y ndarrays con algunas cosas que funcionan aleatoriamente como matrices y otras que no.

Con un poco de texto agregado, se podría decir que, desde el punto de vista del desarrollador, np.matrix está haciendo y ya ha hecho el mal por el hecho de desordenar las reglas no declaradas de la semántica ndarray en Python.

seguido por una gran cantidad de discusiones valiosas sobre los posibles futuros de las matrices. Incluso con el operador no @ en el momento, se piensa mucho en la desaprobación de la clase de matriz y en cómo podría afectar a los usuarios en sentido descendente. Hasta donde puedo decir, esta discusión ha llevado directamente a la creación de PEP 465 introduciendo matmul.

A principios de 2015 :

En mi opinión, una versión "fija" de np.matrix debería (1) no ser una subclase np.ndarray y (2) existir en una biblioteca de terceros, no en sí misma.

No creo que sea realmente factible reparar np.matrix en su estado actual como una subclase ndarray, pero incluso una clase de matriz fija no pertenece realmente al número, que tiene ciclos de liberación demasiado largos y garantías de compatibilidad para la experimentación. Sin mencionar que la mera existencia de la clase de matriz en el número desvía a los nuevos usuarios.

Una vez que el operador @ estuvo disponible durante un tiempo, volvió a surgir la discusión sobre la depreciación , volviendo a plantear el tema sobre la relación entre la depreciación de la matriz y scipy.sparse .

Finalmente, la primera acción para desaprobar numpy.matrix se realizó a fines de noviembre de 2017 . Respecto a los dependientes de la clase:

¿Cómo manejaría la comunidad las subclases de matriz scipy.sparse? Estos son todavía de uso común.

No irán a ningún lado durante bastante tiempo (hasta que las ndarrays dispersas se materialicen al menos). Por lo tanto np.matrix necesita ser movido, no eliminado.

( source ) y

Si bien quiero deshacerme de np.matrix tanto como cualquiera, hacerlo en cualquier momento pronto sería realmente perturbador.

  • Hay toneladas de pequeños guiones escritos por personas que no sabían mejor; queremos que aprendan a no usar np.matrix pero romper todos sus scripts es una forma dolorosa de hacerlo

  • Hay proyectos importantes como scikit-learn que simplemente no tienen alternativa al uso de np.matrix, debido a scipy.sparse.

Así que creo que el camino a seguir es algo como:

  • Ahora o cada vez que alguien reúna un RP: emita un Aviso de Dependencia Pendiente en np.matrix .__ init__ (a menos que mata el rendimiento de scikit-learn y amigos), y ponga una gran casilla de advertencia en la parte superior de los documentos. La idea aquí es no romper realmente el código de nadie, pero comenzar a transmitir el mensaje de que definitivamente no creemos que nadie debería usar esto si tienen alguna alternativa.

  • Después hay una alternativa a scipy.sparse: aumentar las advertencias, posiblemente hasta FutureWarning para que los scripts existentes no se rompan pero sí reciban advertencias ruidosas

  • Eventualmente, si creemos que reducirá los costos de mantenimiento: divídalo en un subpaquete

( source ).

Status quo

A partir de mayo de 2018 (número 1.15, solicitud de extracción relevante y commit ), la cadena de documentación de la clase de matriz contiene la siguiente nota:

Ya no se recomienda usar esta clase, incluso para el álgebra lineal. En su lugar, utilice matrices regulares. La clase puede ser eliminada en el futuro.

Y al mismo tiempo, se ha agregado una PendingDeprecationWarning a la matrix.__new__ . Desafortunadamente, las advertencias de desaprobación están (casi siempre) silenciadas de manera predeterminada , por lo que la mayoría de los usuarios finales de numpy no verán este fuerte indicio.

Finalmente, la hoja de ruta numpy a partir de noviembre de 2018 menciona múltiples temas relacionados como una de las " tareas y características [la comunidad numpy] en la que se invertirán recursos ":

Algunas cosas dentro de NumPy no coinciden realmente con el alcance de NumPy.

  • Un sistema backend para numpy.fft (de modo que, por ejemplo, fft-mkl no necesita monkeypatch numpy)
  • Reescriba los arreglos enmascarados para que no sean una subclase de ndarray, ¿quizás en un proyecto separado?
  • MaskedArray como un tipo duck-array, y / o
  • Dtypes que apoyan valores perdidos
  • Escriba una estrategia sobre cómo lidiar con la superposición entre entumecimiento y timidez para linalg y fft (e implementarlo).
  • Depredar np.matrix

Es probable que este estado permanezca mientras las bibliotecas más grandes / muchos usuarios (y en particular scipy.sparse ) confíen en la clase de matriz. Sin embargo, hay una discusión en curso para mover scipy.sparse para depender de otra cosa, como pydata/sparse . Independientemente de los desarrollos del proceso de desaprobación, los usuarios deben usar la clase ndarray en el nuevo código y, preferiblemente, portar el código más antiguo si es posible. Eventualmente, la clase de matriz probablemente terminará en un paquete separado para eliminar algunas de las cargas causadas por su existencia en su forma actual.