El algoritmo de Strassen está diseñado para la multiplicación rápida de matrices . Fue desarrollado por Volker Strassen en 1969 y es una generalización del método de multiplicación de matrices de Karatsuba .
A diferencia del algoritmo de multiplicación de matrices tradicional (según la fórmula ) que se ejecuta en el tiempo , el algoritmo de Strassen multiplica las matrices en el tiempo , lo que proporciona una ganancia en matrices densas grandes.
A pesar de que el algoritmo de Strassen no es asintóticamente el más rápido de los algoritmos de multiplicación de matrices rápidas existentes, es más fácil de programar y más eficiente cuando se multiplican matrices relativamente pequeñas, por lo que es el más utilizado en la práctica.
Si sumamos las mismas filas y columnas cero a las matrices , su producto se vuelve igual a la matriz con las mismas filas y columnas agregadas. Por lo tanto, solo se pueden considerar matrices de tamaño , y otros casos se pueden reducir a esto agregando ceros, que solo pueden duplicarse.
Sean matrices de tamaño . Se pueden representar como matrices de bloque de tamaño de -matrices:
Por el principio de la multiplicación de bloques , una matriz se expresa en términos de su producto
donde del lado derecho hay ocho multiplicaciones de matrices de tamaño . Dado que las matrices forman un anillo , entonces cualquier algoritmo para multiplicar matrices que use solo sumas, restas y multiplicaciones es adecuado para calcular el lado derecho. Strassen propuso el siguiente algoritmo con siete multiplicaciones:
Cada multiplicación se puede hacer recursivamente usando el mismo procedimiento, y la suma se puede hacer de manera trivial agregando elementos. Luego, el tiempo de ejecución del algoritmo se estima a través de la relación recursiva :
A continuación se muestra un ejemplo de implementación del algoritmo en Python usando la biblioteca NumPy para tomar submatrices rápidamente. La función principal es strassen_mul. Se supone que todas las matrices son cuadradas, representadas por type numpy.array, y su tamaño es una potencia de 2.
Para tamaños de matriz pequeños, la multiplicación directa es más rápida que el algoritmo de Strassen debido a la gran cantidad de adiciones en este último. El límite de tales tamaños depende de la proporción del tiempo de adición y multiplicación de elementos y, por lo tanto, puede variar según el entorno del hardware. En el código, la constante es responsable de su propósito TRIVIAL_MULTIPLICATION_BOUND.
desde itertools importar producto importar numpy como np def split_to_2x2_blocks ( matriz ): lista de retorno ( mapa ( fila lambda : np . hsplit ( fila , 2 ), np . vsplit ( matriz , 2 ) )) def strassen_mul_2x2 ( lb , rb ): d = strassen_mul ( lb [ 0 ][ 0 ] + lb [ 1 ][ 1 ], rb [ 0 ][ 0 ] + rb [ 1 ][ 1 ]) d_1 = strassen_mul ( lb [ 0 ][ 1 ] - lb [ 1 ][ 1 ], rb [ 1 ][ 0 ] + rb [ 1 ][ 1 ]) d_2 = strassen_mul ( lb [ 1 ][ 0 ] - lb [ 0 ][ 0 ], rb [ 0 ][ 0 ] + rb [ 0 ][ 1 ]) izquierda = strassen_mul ( lb [ 1 ][ 1 ], rb [ 1 ][ 0 ] - rb [ 0 ][ 0 ]) derecha = strassen_mul ( lb [ 0 ][ 0 ], rb [ 0 ][ 1 ] - rb [ 1 ][ 1 ]) arriba = strassen_mul ( lb [ 0 ][ 0 ] + lb [ 0 ][ 1 ], rb [ 1 ][ 1 ]) abajo = strassen_mul ( lb [ 1 ][ 0 ] + lb [ 1 ] [ 1 ], rb [ 0 ][ 0 ]) return [[ d + d_1 + izquierda - arriba , derecha + arriba ], [ izquierda + abajo , d + d_2 + derecha - abajo ]] def trivial_mul ( izquierda , derecha ): altura , tamaño medio = izquierda . forma mid_size , right = right . formas resultado = np . ceros (( alto , ancho )) para fila , columna , medio en producto ( * mapa ( rango , [ alto , ancho , tamaño medio ])): resultado [ fila ][ columna ] += izquierda [ fila ][ medio ] * derecha [ medio ][ columna ] resultado devuelto TRIVIAL_MULTIPLICATION_BOUND = 8 def strassen_mul ( izquierda , derecha ): afirmar ( izquierda . forma == derecha . forma ) afirmar ( izquierda . forma [ 0 ] == izquierda . forma [ 1 ]) si queda _ forma [ 0 ] <= TRIVIAL_MULTIPLICATION_BOUND : devuelve trivial_mul ( izquierda , derecha ) afirmar ( izquierda . forma [ 0 ] % 2 == 0 ) devuelve np . bloque ( strassen_mul_2x2 ( * mapa ( split_to_2x2_blocks , [ izquierda , derecha ]))) )Strassen fue el primero en mostrar la posibilidad de multiplicar matrices de una forma más eficiente que la estándar. Después de la publicación de su trabajo en 1969, comenzó una búsqueda activa de un algoritmo más rápido. El algoritmo más asintóticamente rápido en la actualidad es el algoritmo Coppersmith-Winograd , que permite multiplicar matrices en operaciones [1] , propuesto en 1987 y mejorado en 2011 al nivel [1] . Este algoritmo no tiene interés práctico debido a la constante astronómicamente grande en la estimación de la complejidad aritmética. La cuestión de la tasa de limitación asintótica de la multiplicación de matrices no se ha resuelto. Existe la conjetura de Strassen de que para lo suficientemente grande existe un algoritmo para multiplicar dos matrices de tamaño en operaciones, donde un número positivo preasignado es arbitrariamente pequeño. Esta conjetura tiene un interés puramente teórico, ya que el tamaño de las matrices, para el cual es realmente pequeño, es aparentemente muy grande.
La cuestión de construir el algoritmo práctico más rápido y estable para multiplicar matrices grandes también sigue sin resolverse.
Hay una modificación del algoritmo de Strassen que requiere 7 multiplicaciones y 15 sumas (en lugar de 18 para el algoritmo de Strassen normal).
Las matrices se dividen en submatrices de bloque como se muestra arriba.
Los elementos intermedios se calculan
Los elementos de la matriz se calculan de la siguiente manera:
El algoritmo de Strassen es un algoritmo bilineal, sus coeficientes son las raíces del sistema cúbico de las ecuaciones de Brent . [2] Para la clase de algoritmos exactos <2x2x2> este es un problema mínimo, cuya solución permite reducir el número de multiplicaciones en el anillo de elementos de la matriz. [3] [4] El problema de encontrar nuevos algoritmos es que el sistema Brent es no lineal, el número de incógnitas y ecuaciones (estos números no coinciden) crece rápidamente con el tamaño de las matrices, y solo soluciones con un gran Se requiere una cantidad de ceros.
En 2013, tras superar parcialmente estos problemas, fue posible encontrar el primer algoritmo bilineal práctico para la multiplicación de matrices, que es asintóticamente más rápido que el algoritmo de Strassen. [5] Algoritmo de Smirnov <3x3x6; 40> multiplica una matriz de 3X3 por una matriz de 3x6 utilizando 40 multiplicaciones en lugar de 54. Su complejidad asintótica es . (La multiplicación de tensor del algoritmo por sí mismo con un cambio cíclico de argumentos conduce a un algoritmo para matrices cuadradas <54x54x54; 64000> con la misma complejidad). Para una aceleración real de la multiplicación, se requiere una optimización significativa: la eliminación de muchos cálculos duplicados en formas lineales.
Hoy (2022), este es asintóticamente el algoritmo bilineal práctico más rápido para un campo arbitrario de elementos de matriz.
El 5 de octubre de 2022, DeepMind, utilizando el algoritmo de red neuronal AlphaZero, encontró varios algoritmos nuevos para multiplicar matrices de varias dimensiones. Sin embargo, su velocidad para un campo arbitrario está lejos de la velocidad de los mejores algoritmos conocidos. Entonces, para matrices de 4X4, el algoritmo de Strassen requiere 49 multiplicaciones, y AlphaTensor encontró un algoritmo que requiere 47 multiplicaciones, pero solo funciona para el campo . [6] [7]