Python
Python
a, La estructura de
datos de la matriz NumPy y sus campos de metadatos asociados. b, Indexar una matriz con
sectores y pasos. Estas operaciones devuelven una "vista" de los datos originales. c, Indexar
una matriz con máscaras, coordenadas escalares u otras matrices, de modo que devuelva una
'copia' de los datos originales. En el ejemplo inferior, una matriz se indexa con otras matrices;
esto difunde los argumentos de indexación antes de realizar la búsqueda. d, Vectorización
aplica operaciones de manera eficiente a grupos de elementos. e, Difusión en la multiplicación
de matrices bidimensionales. f, Las operaciones de reducción actúan a lo largo de uno o más
ejes. En este ejemplo, una matriz se suma a lo largo de ejes seleccionados para producir un
vector, o a lo largo de dos ejes consecutivamente para producir un escalar. g, Ejemplo de
código NumPy, que ilustra algunos de estos conceptos.
El tipo de datos describe la naturaleza de los elementos almacenados en una matriz. Una
matriz tiene un único tipo de datos y cada elemento de una matriz ocupa el mismo número de
bytes en la memoria. Ejemplos de tipos de datos incluyen números reales y complejos (de
menor y mayor precisión), cadenas, marcas de tiempo y punteros a objetos Python. La forma
de una matriz determina el número de elementos a lo largo de cada eje y el número de ejes es
la dimensionalidad de la matriz. Por ejemplo, un vector de números se puede almacenar como
una matriz unidimensional de la forma N, mientras que los videos en color son matrices de
forma de cuatro dimensiones (T, M, N, 3). Los pasos son necesarios para interpretar la
memoria del equipo, que almacena elementos linealmente, como matrices
multidimensionales. Describen el número de bytes para avanzar en la memoria para saltar de
fila en fila, columna a columna, etc. Considere, por ejemplo, una matriz bidimensional de
números de punto flotante con forma (4, 3), donde cada elemento ocupa 8 bytes en memoria.
Para movernos entre columnas consecutivas, necesitamos saltar hacia delante 8 bytes en la
memoria, y para acceder a la siguiente fila, 3 × 8 x 24 bytes. Por lo tanto, los pasos de esa
matriz son (24, 8). NumPy puede almacenar matrices en orden de memoria C o Fortran,
iterando primero sobre filas o columnas. Esto permite que las bibliotecas externas escritas en
esos idiomas accedan directamente a los datos de la matriz NumPy en la memoria. Los
usuarios interactúan con matrices NumPy utilizando 'indexación' (para acceder a subarrays o
elementos individuales), 'operadores' (por ejemplo, +, á y × para operaciones vectorizadas y
para multiplicación de matrices), así como 'funciones compatibles con matrices'; juntos, estos
proporcionan una API fácil de leer, expresiva y de alto nivel para la programación de matrices,
mientras que NumPy se ocupa de la mecánica subyacente de hacer las operaciones rápidas. La
indexación de una matriz devuelve elementos individuales, subarrays o elementos que
cumplen una condición específica (Fig. 1b). Las matrices incluso se pueden indexar utilizando
otras matrices (Fig. 1c). Siempre que sea posible, la indexación que recupera una subbarra
devuelve una 'vista' en la matriz original de forma que los datos se compartan entre las dos
matrices. Esto proporciona una forma eficaz de operar en subconjuntos de datos de matriz
mientras se limita el uso de memoria. Para complementar la sintaxis de la matriz, NumPy
incluye funciones que realizan cálculos vectorizados en matrices, incluyendo aritmética,
estadísticas y trigonometría (Fig. 1d). La vectorización, que funciona en arreglos de discos
completos en lugar de en sus elementos individuales, es esencial para la programación de
arreglos de discos. Esto significa que las operaciones que tomarían muchas decenas de líneas
para expresar en lenguajes como C a menudo se pueden implementar como una sola
expresión de Python clara. Esto da como resultado código conciso y libera a los usuarios para
centrarse en los detalles de su análisis, mientras que NumPy controla el bucle sobre los
elementos de matriz de forma casi óptima, por ejemplo, teniendo en cuenta los pasos
necesarios para utilizar mejor la memoria caché rápida del equipo. Al realizar una operación
vectorizada (como la adición) en dos matrices con la misma forma, está claro lo que debería
suceder. A través de la "difusión" NumPy permite que las dimensiones difieran, y produce
resultados que apelan a la intuición. Un ejemplo trivial es la adición de un valor escalar a una
matriz, pero la difusión también se generaliza a ejemplos más complejos, como escalar cada
columna de una matriz o generar una cuadrícula de coordenadas. En la radiodifusión, una o
ambas matrices están prácticamente duplicadas (es decir, sin copiar ningún dato en la
memoria), de modo que las formas de los operandos coincidan (Fig. 1d). La difusión también
se aplica cuando una matriz se indexa utilizando matrices de índices (Fig. 1c). Otras funciones
compatibles con matrices, como sum, mean y maximum, realizan "reducciones" elemento por
elemento, agregando resultados en uno, varios o todos los ejes de una sola matriz. Por
ejemplo, la suma de una matriz de n dimensiones sobre ejes d da como resultado una matriz
de dimensión n a d (Fig. 1f). NumPy también incluye funciones compatibles con matrices para
crear, remodelar, concatenar y rellenar matrices; búsqueda, clasificación y recuento de datos;
y leer y escribir archivos. Proporciona un amplio soporte para generar números
pseudoaleatorios, incluye una variedad de distribuciones de probabilidad y realiza álgebra
lineal acelerada, utilizando uno de varios backends como OpenBLAS18,19 o Intel MKL
optimizado para las CPU en cuestión (consulte Métodos suplementarios para obtener más
detalles). En conjunto, la combinación de una simple representación de matriz en memoria,
una sintaxis que imita de cerca las matemáticas y una variedad de funciones de utilidad
compatibles con matrices forma un lenguaje de programación de matriz productivo y
poderosamente expresivo.
NumPy proporciona matrices en memoria, multidimensionales, con tipo homogéneo (es decir,
puntero único y strided) en CPU. Funciona en máquinas que van desde dispositivos integrados
hasta los superordenadores más grandes del mundo, con un rendimiento que se acerca al de
los lenguajes compilados. Para la mayor parte de su existencia, NumPy abordó la gran mayoría
de los casos de uso de cálculo de matriz. Sin embargo, los conjuntos de datos científicos ahora
superan rutinariamente la capacidad de memoria de una sola máquina y pueden almacenarse
en varias máquinas o en la nube. Además, la reciente necesidad de acelerar las aplicaciones de
aprendizaje profundo e inteligencia artificial ha llevado a la aparición de hardware de
acelerador especializado, incluidas unidades de procesamiento de gráficos (GPU), unidades de
procesamiento de tensores (TPU) y matrices de compuertas programables en campo (FPGA).
Debido a su modelo de datos en memoria, NumPy actualmente no puede utilizar directamente
dicho almacenamiento y hardware especializado. Sin embargo, tanto los datos distribuidos
como la ejecución paralela de GPU, APU y FPGA se asignan bien al paradigma de la
programación de matrices: lo que conduce a una brecha entre las arquitecturas de hardware
modernas disponibles y las herramientas necesarias para aprovechar su potencia
computacional.
Los esfuerzos de la comunidad para llenar este vacío condujeron a una proliferación de nuevas
implementaciones de arreglos de discos. Por ejemplo, cada marco de aprendizaje profundo
crea sus propios arreglos de discos; las matrices PyTorch38, Tensorflow39, Apache MXNet40 y
JAX tienen la capacidad de ejecutarse en CPU y GPU de forma distribuida, utilizando una
evaluación diferida para permitir optimizaciones de rendimiento adicionales. SciPy y
PyData/Sparse proporcionan matrices dispersas, que normalmente contienen pocos valores
distintos de cero y almacenan solo los que están en la memoria para mayor eficiencia. Además,
hay proyectos que se basan en matrices NumPy como contenedores de datos y amplían sus
capacidades. Las matrices distribuidas son posibles de esa manera por Dask, y los arrays
etiquetados, que hacen referencia a las dimensiones de una matriz por nombre en lugar de por
índice para mayor claridad, comparan x[:, 1] frente a x.loc[:, 'time']— por xarray41. Estas
bibliotecas a menudo imitan la API de NumPy, porque esto reduce la barrera de entrada para
los recién llegados y proporciona a la comunidad más amplia una interfaz de programación de
matriz estable. Esto, a su vez, evita cismos disruptivos como la divergencia entre Numeric y
Numarray. Pero explorar nuevas formas de trabajar con arreglos de discos es experimental por
naturaleza y, de hecho, varias bibliotecas prometedoras (como Theano y Caffe) ya han cesado
el desarrollo. Y cada vez que un usuario decide probar una nueva tecnología, debe cambiar las
instrucciones de importación y asegurarse de que la nueva biblioteca implementa todas las
partes de la API de NumPy que utilizan actualmente. Idealmente, operar en matrices
especializadas usando funciones o semántica NumPy simplemente funcionaría, de modo que
los usuarios pudieran escribir código una vez, y luego se beneficiarían de cambiar entre
matrices NumPy, matrices de GPU, matrices distribuidas y así sucesivamente según
corresponda. Para admitir operaciones de matriz entre objetos de matriz externos, NumPy
agregó la capacidad de actuar como un mecanismo de coordinación central con una API bien
especificada (Fig. 2). Para facilitar esta interoperabilidad, NumPy proporciona 'protocolos' (o
contratos de operación), que permiten pasar matrices especializadas a las funciones NumPy
(Fig. 3). NumPy, a su vez, distribuye las operaciones a la biblioteca de origen, según sea
necesario. Más de cuatrocientas de las funciones NumPy más populares son compatibles. Los
protocolos son implementados por bibliotecas ampliamente utilizadas como Dask, CuPy,
xarray y PyData/Sparse. Gracias a estos desarrollos, los usuarios ahora pueden, por ejemplo,
escalar su cálculo de una sola máquina a sistemas distribuidos utilizando Dask. Los protocolos
también se componen bien, lo que permite a los usuarios volver a implementar código NumPy
a escala en sistemas distribuidos de varias GPU a través, por ejemplo, de matrices CuPy
incrustadas en matrices Dask. Con la API de alto nivel de NumPy, los usuarios pueden
aprovechar la ejecución de código altamente paralelo en varios sistemas con millones de
núcleos, todo ello con cambios mínimos de código42. Estos protocolos de matriz son ahora
una característica clave de NumPy y se espera que solo aumenten en importancia. Los
desarrolladores de NumPy, muchos de los cuales son autores de esta revisión, refinan y
agregan de forma iterativa diseños de protocolo para mejorar la utilidad y simplificar la
adopción.