Ir al contenido
Background Image
  1. Inteligencia Artificial/
  2. Matemática para Machine Learning/

Álgebra Lineal para Machine Learning: vectores y matrices que todo ingeniero en IA debe conocer

Autor
Julian Nonino
Platform Engineer - DevOps
Tabla de contenido
Matemática para Machine Learning - Este artículo es parte de una serie.
Parte 1: Este artículo

En el módulo anterior exploramos los fundamentos conceptuales de la Inteligencia Artificial. Ahora es momento de sumergirnos en una de las áreas de la matemática que hacen posible que los algoritmos de Machine Learning funcionen: el álgebra lineal.

Si eres como la mayoría de los ingenieros de software, probablemente te preguntes: “¿por qué necesito álgebra lineal para programar IA?” La respuesta es simple pero profunda: el álgebra lineal es el lenguaje nativo del machine learning.


¿Por qué el Álgebra Lineal es crucial en IA?
#

Imagina que estás desarrollando un sistema de recomendaciones para Netflix. Cada usuario tiene preferencias (acción, comedia, drama) que pueden representarse como un vector. Cada película también tiene características (género, año, rating) que forman otro vector. El problema de recomendar películas se convierte en encontrar similitudes entre vectores: álgebra lineal.

O considera una red neuronal procesando una imagen de \(224x224\) píxeles. Esa imagen se convierte en un vector de \(50176\) elementos. Las operaciones de la red (convoluciones, transformaciones) son multiplicaciones de matrices. El entrenamiento optimiza estas matrices, otra vez: álgebra lineal.

Los tres pilares del ML que dependen del Álgebra Lineal
#

  1. Representación de Datos: Todo en ML se convierte en vectores y matrices
  2. Transformaciones: Los algoritmos manipulan datos mediante operaciones lineales
  3. Optimización: Los métodos de entrenamiento usan gradientes (derivadas de operaciones matriciales)

Como programadores, estamos acostumbrados a pensar en estructuras de datos como arrays, listas o objetos. En machine learning, pensamos en vectores y matrices. En este módulo aprenderemos a hacer esa transición mental.


Vectores: más que arrays
#

Un vector no es simplemente un array de números. Es una entidad matemática que representa tanto magnitud como dirección. En el contexto de machine learning, un vector es una forma de codificar información.

Definición formal
#

Un vector \(v\) en el espacio \(R^n\) es una tupla ordenada de \(n\) números reales:

$$v = \begin{pmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{pmatrix}$$

Pero más importante que la definición formal es la interpretación práctica:

  • En un sistema de recomendaciones: $$v = \begin{pmatrix} rating_{accion} \\ rating_{comedia} \\ rating_{drama} \end{pmatrix}$$
  • En procesamiento de texto: $$v = \begin{pmatrix} frecuencia_{palabra1} \\ frecuencia_{palabra2} \\ frecuencia_{palabra3} \\ \vdots \end{pmatrix}$$
  • En visión por computadora: $$v = \begin{pmatrix} pixel_1 \\ pixel_2 \\ pixel_3 \\ \vdots \end{pmatrix}$$

Interpretación geométrica
#

Un vector en dos dimensiones (2D) se puede visualizar como una flecha desde el origen \((0,0)\) hasta el punto \((v_1, v_2)\). Esta visualización es clave para entender las operaciones vectoriales.

import matplotlib.pyplot as plt
import numpy as np

def plot_vector(vector, color='blue', label='Vector'):
    plt.quiver(0, 0, vector[0], vector[1],
               angles='xy', scale_units='xy', scale=1,
               color=color, label=label, width=0.005)
    plt.xlim(-1, 5)
    plt.ylim(-1, 5)
    plt.grid(True)
    plt.axhline(y=0, color='k', linewidth=0.5)
    plt.axvline(x=0, color='k', linewidth=0.5)

# Ejemplo: vector que representa preferencias de usuario
user_preferences = np.array([3, 4])  # [acción: 3, comedia: 4]
plt.figure(figsize=(8, 6))
plot_vector(user_preferences, 'blue', 'Preferencias Usuario')
plt.xlabel('Rating Acción')
plt.ylabel('Rating Comedia')
plt.title('Vector de Preferencias de Usuario')
plt.legend()
plt.show()

Suma de vectores
#

La suma vectorial es componente a componente:

$$\mathbf{u} + \mathbf{v} = \begin{pmatrix} u_1 + v_1 \\ u_2 + v_2 \\ \vdots \\ u_n + v_n \end{pmatrix}$$

Interpretación en ML: Si tenemos las preferencias de dos usuarios similares, podemos promediar sus vectores para encontrar preferencias “típicas” de ese segmento.

Multiplicación por escalar
#

$$c \cdot \mathbf{v} = \begin{pmatrix} c \cdot v_1 \\ c \cdot v_2 \\ \vdots \\ c \cdot v_n \end{pmatrix}$$

Interpretación en ML: Amplificar o reducir la importancia de ciertas características.

Producto Punto
#

El producto punto es quizás la operación más importante en ML:

$$\mathbf{u} \cdot \mathbf{v} = \sum_{i=1}^{n} u_i v_i = u_1 v_1 + u_2 v_2 + ... + u_n v_n$$

¿Por qué es tan importante?

  • Similitud: Vectores similares tienen productos punto altos
  • Proyección: Mide cuánto un vector “apunta” en la dirección de otro
  • Redes neuronales: La base de las operaciones en cada neurona

La interpretación geométrica es crucial, el producto punto es igual al producto entre las magnitudes de cada vector y el coseno del ángulo entre ellos:

$$\mathbf{u} \cdot \mathbf{v} = ||\mathbf{u}|| \cdot ||\mathbf{v}|| \cdot \cos(\theta)$$

Donde \(\theta\) es el ángulo entre los vectores.

O de otra manera:

$$\cos(\theta) = \frac{\mathbf{u} \cdot \mathbf{v}}{||\mathbf{u}|| \cdot ||\mathbf{v}||}$$$$\theta = \arccos(\frac{\mathbf{u} \cdot \mathbf{v}}{||\mathbf{u}|| \cdot ||\mathbf{v}||})$$

Conocer el ángulo entre los vectores permite determinar que tan alineados están.

Implementación desde cero: clase Vector
#

Antes de usar NumPy, implementemos nuestras propias operaciones vectoriales para entender qué sucede en el detrás de escena:

import math
from typing import List


class Vector:
    """
    Implementación básica de un vector matemático.

    Esta clase nos ayuda a entender las operaciones vectoriales
    antes de usar bibliotecas optimizadas como NumPy.
    """

    def __init__(self, componentes: List[float]):
        """
        Inicializa un vector con una lista de componentes.

        Args:
            componentes: Lista de números que forman el vector
        """
        if not componentes:
            raise ValueError("Un vector debe tener al menos un componente")
        self.componentes = componentes
        self.dimension = len(componentes)

    def __repr__(self):
        return f"Vector({self.componentes})"

    def __len__(self):
        return self.dimension

    def __getitem__(self, index):
        return self.componentes[index]

    def __add__(self, otro_vector):
        """
        Suma vectorial: componente por componente.

        Ejemplo:
        v1 = Vector([1, 2, 3])
        v2 = Vector([4, 5, 6])
        v3 = v1 + v2  # Vector([5, 7, 9])
        """
        if self.dimension != otro_vector.dimension:
            raise ValueError("Los vectores deben tener la misma dimensión")

        componentes_resultado = [
            a + b for a, b in zip(self.componentes, otro_vector.componentes)
        ]
        return Vector(componentes_resultado)

    def __sub__(self, otro_vector):
        """Resta vectorial."""
        if self.dimension != otro_vector.dimension:
            raise ValueError("Los vectores deben tener la misma dimensión")

        componentes_resultado = [
            a - b for a, b in zip(self.componentes, otro_vector.componentes)
        ]
        return Vector(componentes_resultado)

    def __mul__(self, escalar):
        """
        Multiplicación por escalar.

        Ejemplo:
        v = Vector([1, 2, 3])
        v_scaled = v * 2  # Vector([2, 4, 6])
        """
        return Vector([escalar * componente for componente in self.componentes])

    def producto_punto(self, otro_vector):
        """
        Producto punto: la operación más importante en Machine Learning.

        El producto punto mide la similitud direccional entre vectores.
        - Producto alto: vectores apuntan en direcciones similares
        - Producto cero: vectores perpendiculares
        - Producto negativo: vectores apuntan en direcciones opuestas

        Args:
            otro_vector: Otro vector de la misma dimensión

        Returns:
            float: El producto punto
        """
        if self.dimension != otro_vector.dimension:
            raise ValueError("Los vectores deben tener la misma dimensión")

        return sum(a * b for a, b in zip(self.componentes, otro_vector.componentes))

    def magnitud(self):
        """
        Calcula la magnitud (norma) del vector.

        La magnitud representa la "longitud" del vector.
        Es importante para normalización y cálculo de distancias.

        Returns:
            float: La magnitud del vector
        """
        return math.sqrt(sum(componente ** 2 for componente in self.componentes))

    def normalizar(self):
        """
        Normaliza el vector (magnitud = 1).

        Los vectores normalizados son cruciales en Machine Learning porque:
        - Eliminan el efecto de la escala
        - Facilitan la comparación de direcciones
        - Son requeridos en muchos algoritmos

        Returns:
            Vector: Nuevo vector normalizado
        """
        mag = self.magnitud()
        if mag == 0:
            raise ValueError("No se puede normalizar el vector cero")

        return Vector([componente / mag for componente in self.componentes])

    def similitud_coseno(self, otro_vector):
        """
        Calcula la similitud coseno entre dos vectores.

        La similitud coseno es fundamental en:
        - Sistemas de recomendación
        - Procesamiento de lenguaje natural
        - Búsqueda semántica

        Retorna valores entre -1 y 1:
        - 1: Vectores idénticos en dirección
        - 0: Vectores perpendiculares
        - -1: Vectores opuestos

        Args:
            otro_vector: Otro vector

        Returns:
            float: Similitud coseno
        """
        dot_prod = self.producto_punto(otro_vector)
        producto_magnitudes = self.magnitud() * otro_vector.magnitud()

        if producto_magnitudes == 0:
            return 0

        return dot_prod / producto_magnitudes


# Ejemplos de uso
def demo_operaciones_vectoriales():
    """
    Prueba las operaciones vectoriales con ejemplos de Machine Learning.
    """
    mensaje = "Ejemplos de Operaciones Vectoriales"
    print("#" * len(mensaje))
    print(mensaje)
    print("#" * len(mensaje))
    print("\n")

    # Ejemplo 1: Preferencias de usuarios
    print("=== Ejemplo 1: Preferencias de usuarios ===")
    print("   Cada usuario se corresponde con un vector que mapea sus preferencias en películas")
    print("   Vector([acción, comedia, drama])")
    print("\n")
    usuarios = [
        Vector([4, 2, 5]),
        Vector([3, 4, 2]),
        Vector([9, 1, 2]),
        Vector([3, 8, 1]),
        Vector([1, 2, 9])
    ]
    for index_i, usuario_i in enumerate(usuarios):
        print(f"   Usuario {index_i}: {usuario_i}")
        for index_j in range(index_i + 1, len(usuarios)):
            usuario_j = usuarios[index_j]
            print(f"      Cálculos de similitud con el usuario {index_j}")
            combinadas = usuario_i + usuario_j
            print(f"         Suma: preferencias combinadas: {combinadas}")
            similitud_producto_punto = usuario_i.producto_punto(usuario_j)
            print(f"         Similitud (producto punto): {similitud_producto_punto}")
            similitud_coseno = usuario_i.similitud_coseno(usuario_j)
            print(f"         Similitud coseno: {similitud_coseno:.3f}")
    print("\n")

    # Ejemplo 2: Vectores de características
    print("=== Ejemplo 2: Análisis de Documentos ===")
    print("   Cada documento se corresponde con un vector que mapea las frecuencias de las palabras que contiene")
    print("   Vector([frecuencia_palabra_1, frecuencia_palabra_2, frecuencia_palabra_3, frecuencia_palabra_4])")
    documento_1 = Vector([2, 1, 0, 3])  # Frecuencias de palabras
    documento_2 = Vector([1, 2, 1, 2])  # Frecuencias de palabras
    print(f"   Documento 1: {documento_1}")
    print(f"   Documento 2: {documento_2}")
    similitud_documentos_producto_punto = documento_1.producto_punto(documento_2)
    print(f"      Similitud (producto punto): {similitud_documentos_producto_punto}")
    similitud_documentos_coseno = documento_1.similitud_coseno(documento_2)
    print(f"      Similitud entre documentos (coseno): {similitud_documentos_coseno:.3f}")


if __name__ == "__main__":
    demo_operaciones_vectoriales()

Al ejecutar el código anterior obtenemos:

>  python vector.py
###################################
Ejemplos de Operaciones Vectoriales
###################################

=== Ejemplo 1: Preferencias de usuarios ===
   Cada usuario se corresponde con un vector que mapea sus preferencias en películas
   Vector([acción, comedia, drama])

   Usuario 0: Vector([4, 2, 5])
      Cálculos de similitud con el usuario 1
         Suma: preferencias combinadas: Vector([7, 6, 7])
         Similitud (producto punto): 30
         Similitud coseno: 0.830
      Cálculos de similitud con el usuario 2
         Suma: preferencias combinadas: Vector([13, 3, 7])
         Similitud (producto punto): 48
         Similitud coseno: 0.772
      Cálculos de similitud con el usuario 3
         Suma: preferencias combinadas: Vector([7, 10, 6])
         Similitud (producto punto): 33
         Similitud coseno: 0.572
      Cálculos de similitud con el usuario 4
         Suma: preferencias combinadas: Vector([5, 4, 14])
         Similitud (producto punto): 53
         Similitud coseno: 0.852
   Usuario 1: Vector([3, 4, 2])
      Cálculos de similitud con el usuario 2
         Suma: preferencias combinadas: Vector([12, 5, 4])
         Similitud (producto punto): 35
         Similitud coseno: 0.701
      Cálculos de similitud con el usuario 3
         Suma: preferencias combinadas: Vector([6, 12, 3])
         Similitud (producto punto): 43
         Similitud coseno: 0.928
      Cálculos de similitud con el usuario 4
         Suma: preferencias combinadas: Vector([4, 6, 11])
         Similitud (producto punto): 29
         Similitud coseno: 0.581
   Usuario 2: Vector([9, 1, 2])
      Cálculos de similitud con el usuario 3
         Suma: preferencias combinadas: Vector([12, 9, 3])
         Similitud (producto punto): 37
         Similitud coseno: 0.464
      Cálculos de similitud con el usuario 4
         Suma: preferencias combinadas: Vector([10, 3, 11])
         Similitud (producto punto): 29
         Similitud coseno: 0.337
   Usuario 3: Vector([3, 8, 1])
      Cálculos de similitud con el usuario 4
         Suma: preferencias combinadas: Vector([4, 10, 10])
         Similitud (producto punto): 28
         Similitud coseno: 0.351
   Usuario 4: Vector([1, 2, 9])

=== Ejemplo 2: Análisis de Documentos ===
   Cada documento se corresponde con un vector que mapea las frecuencias de las palabras que contiene
   Vector([frecuencia_palabra_1, frecuencia_palabra_2, frecuencia_palabra_3, frecuencia_palabra_4])
   Documento 1: Vector([2, 1, 0, 3])
   Documento 2: Vector([1, 2, 1, 2])
      Similitud (producto punto): 10
      Similitud entre documentos (coseno): 0.845

Matrices: transformaciones de datos
#

Si los vectores representan datos, las matrices representan transformaciones de esos datos. Una matriz es una tabla rectangular de números organizados en filas y columnas.

$$\mathbf{A} = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix}$$

En machine learning, las matrices son omnipresentes:

  • Dataset: Cada fila es un ejemplo, cada columna una característica
  • Pesos de red neuronal: Transforman entradas en salidas
  • Transformaciones: Rotación, escalado, proyección de datos

Multiplicación Matriz-Vector
#

Esta es la operación más común en ML. Transforma un vector usando una matriz:

$$\mathbf{A}\mathbf{v} = \begin{pmatrix} \sum_{i=1}^{n} a_{1i} v_i \\ \sum_{i=1}^{n} a_{2i} v_i \\ \sum_{i=1}^{n} a_{3i} v_i \\ \vdots \\ \sum_{i=1}^{n} a_{mi} v_i \\ \end{pmatrix}$$$$\mathbf{A}\mathbf{v} = \begin{pmatrix} a_{11} & a_{12} & a_{13} & \cdots & a_{1n} \\ a_{21} & a_{22} & a_{33} & \cdots & a_{2n} \\ a_{31} & a_{32} & a_{33} & \cdots & a_{3n} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & a_{m3} & \cdots & a_{mn} \\ \end{pmatrix} \cdot \begin{pmatrix} v_1 \\ v_2 \\ v_3 \\ \vdots \\ v_n \end{pmatrix}$$$$\mathbf{A}\mathbf{v} = \begin{pmatrix} a_{11} \cdot v_1 + a_{12} \cdot v_2 + a_{13} \cdot v_3 + \cdots + a_{1n} \cdot v_n \\ a_{21} \cdot v_1 + a_{22} \cdot v_2 + a_{23} \cdot v_3 + \cdots + a_{2n} \cdot v_n \\ a_{31} \cdot v_1 + a_{32} \cdot v_2 + a_{33} \cdot v_3 + \cdots + a_{3n} \cdot v_n \\ \vdots \\ a_{m1} \cdot v_1 + a_{m2} \cdot v_2 + a_{m3} \cdot v_3 + \cdots + a_{mn} \cdot v_n \\ \end{pmatrix}$$

Cuando se multiplica una matriz por un vector, es necesario que el número de elementos del vector coincida con el número de columnas de la matriz. Si no es así, la multiplicación no está definida.

Ejemplo práctico: En una red neuronal, cada capa aplica una transformación lineal:

salida = pesos × entrada + sesgo

Si aún no lo notaste, se puede establecer una conexión entre la multiplicación de una matriz por un vector y el producto punto entre vectores.

La conexión es directa: multiplicar una matriz por un vector es, en el fondo, hacer varios productos punto seguidos.

Si \(A\) es una matriz de \(m \times n\) y \(v\) es un vector de dimensión \(n\), el resultado de \(A \ v\) es un vector de dimensión \(m\) donde cada componente se obtiene haciendo el producto punto de una fila de la matriz con el vector.

$$(A \ v)_j = fila_j(A) \cdot v$$

Multiplicación Matriz-Matriz
#

Para que el producto de dos matrices \(A\) y \(B\) es decir, \(AB\) esté definido, la matriz \(A\) debe tener el mismo número de columnas que la matriz \(B\) tenga de filas. Si \(A\) es de tamaño \(m x n\) y \(B\) es de tamaño \(n x p\), entonces el resultado \(C = AB\) será una matriz de tamaño \(m x p\).

$$\mathbf{C} = \mathbf{A}\mathbf{B} = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ a_{31} & a_{32} & \cdots & a_{3n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \\ \end{pmatrix} \cdot \begin{pmatrix} b_{11} & b_{12} & \cdots & b_{1p} \\ b_{21} & b_{22} & \cdots & b_{2p} \\ b_{31} & b_{32} & \cdots & b_{3p} \\ \vdots & \vdots & \ddots & \vdots \\ b_{n1} & b_{n2} & \cdots & b_{np} \\ \end{pmatrix}$$$$\mathbf{C} = \mathbf{A}\mathbf{B} = \begin{pmatrix} \sum_{k=1}^{n} a_{1k} b_{k1} & \sum_{k=1}^{n} a_{1k} b_{k2} & \cdots & \sum_{k=1}^{n} a_{1k} b_{kp} \\ \sum_{k=1}^{n} a_{2k} b_{k1} & \sum_{k=1}^{n} a_{2k} b_{k2} & \cdots & \sum_{k=1}^{n} a_{2k} b_{kp} \\ \sum_{k=1}^{n} a_{3k} b_{k1} & \sum_{k=1}^{n} a_{3k} b_{k2} & \cdots & \sum_{k=1}^{n} a_{3k} b_{kp} \\ \vdots & \vdots & \ddots & \vdots \\ \sum_{k=1}^{n} a_{mk} b_{k1} & \sum_{k=1}^{n} a_{mk} b_{k2} & \cdots & \sum_{k=1}^{n} a_{mk} b_{kp} \\ \end{pmatrix}$$

Es decir, cada elemento \(ij\) de la matriz resultado (\(C\)) será:

$$\mathbf{C}_{ij} = (\mathbf{AB})_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}$$

Esta operación permite componer transformaciones lineales.

Nuevamente, la multiplicación de matrices está muy relacionada con el producto punto de vectores.

En la multiplicación entre las matrices \(A(m \times n)\) por \(B(n \times p)\), el elemento \(c_{ij}\) de la matriz resultado \(C\) se obtiene como:

$$\mathbf{C}_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}$$

Esto es exactamente el producto punto entre la fila \(i\) de \(A\) y la columna \(j\) de \(B\).

$$\mathbf{C}_{ij} = a_{i} \cdot b_{j}$$

Implementación desde cero: clase Matriz
#

import math
from typing import List
from vector import Vector


class Matriz:
    """
    Implementación básica de una matriz matemática.

    Esta clase nos ayuda a entender las operaciones matriciales
    fundamentales en Machine Learning.
    """

    def __init__(self, datos: List[List[float]]):
        """
        Inicializa una matriz con una lista de listas.

        Args:
            datos: Lista de filas, donde cada fila es una lista de números
        """
        if not datos or not datos[0]:
            raise ValueError("La matriz debe tener al menos un elemento")

        # Verificar que todas las filas tengan la misma longitud
        longitud_fila = len(datos[0])
        for fila in datos:
            if len(fila) != longitud_fila:
                raise ValueError("Todas las filas deben tener la misma longitud")

        self.datos = datos
        self.filas = len(datos)
        self.columnas = len(datos[0])
        self.forma = (self.filas, self.columnas)

    def __repr__(self):
        """Representación legible de la matriz."""
        filas = []
        for fila in self.datos:
            row_str = " ".join(f"{x:8.3f}" for x in fila)
            filas.append(f"[{row_str}]")
        return f"Matriz(\n  " + "\n  ".join(filas) + "\n)"

    def __getitem__(self, indices):
        """Permite acceso con matriz[i][j] o matriz[i, j]."""
        if isinstance(indices, tuple):
            fila, columna = indices
            return self.datos[fila][columna]
        else:
            return self.datos[indices]

    def __setitem__(self, indices, value):
        """Permite asignación con matriz[i][j] = value."""
        if isinstance(indices, tuple):
            fila, columna = indices
            self.datos[fila][columna] = value
        else:
            fila = indices
            self.datos[fila] = value

    def __add__(self, otra):
        """Suma de matrices (elemento por elemento)."""
        if self.forma != otra.shape:
            raise ValueError("Las matrices deben tener la misma forma")

        datos_resultado = [
            [self.datos[i][j] + otra.datos[i][j] for j in range(self.columnas)]
            for i in range(self.filas)
        ]
        return Matriz(datos_resultado)

    def trasponer(self):
        """
        Calcula la transpuesta de la matriz.

        La transpuesta intercambia filas por columnas.
        Es fundamental en álgebra lineal y en Machine Learning.

        Returns:
            Matriz: Nueva matriz transpuesta
        """
        datos_transpuestos = [
            [self.datos[fila][columna] for fila in range(self.filas)]
            for columna in range(self.columnas)
        ]
        return Matriz(datos_transpuestos)

    def multiplicar_por_escalar(self, escalar):
        """Multiplicación por escalar."""
        datos_resultado = [
            [escalar * self.datos[i][j] for j in range(self.columnas)]
            for i in range(self.filas)
        ]
        return Matriz(datos_resultado)

    def multiplicar_por_vector(self, vector: Vector):
        """
        Multiplica la matriz por un vector.

        Esta es la operación fundamental en redes neuronales:
        cada capa aplica una transformación lineal Ax + b.

        Args:
            vector: Vector a multiplicar

        Returns:
            Vector: Resultado de la multiplicación
        """
        if self.columnas != len(vector):
            raise ValueError(f"Dimensiones incompatibles: matriz {self.forma} * vector {len(vector)}")

        componentes_resultado = []
        for index_fila in range(self.filas):
            fila = [ self.datos[index_fila][columna] for columna in range(self.columnas) ]
            producto_punto = Vector(fila).producto_punto(vector)
            componentes_resultado.append(producto_punto)

        return Vector(componentes_resultado)

    def multiplicar_matrices(self, otra):
        """
        Multiplica dos matrices.

        La multiplicación de matrices permite componer transformaciones.
        En deep learning, representa la composición de capas.

        Args:
            otra: Otra matriz

        Returns:
            Matriz: Resultado de la multiplicación
        """
        if self.columnas != otra.filas:
            raise ValueError(f"Dimensiones incompatibles: {self.forma} * {otra.forma}")

        print(f"   Forma matriz A: {self.forma}")
        print(f"   Forma matriz B: {otra.forma}")

        datos_resultado = []
        for index_fila in range(self.filas):
            fila_i_matriz = [ self.datos[index_fila][columna] for columna in range(self.columnas) ]
            vector_fila_i_matriz = Vector(fila_i_matriz)
            fila_resultado = []
            for index_columna_otra in range(otra.columnas):
                columna_j_matriz_otra = [ otra.datos[fila][index_columna_otra] for fila in range(otra.filas) ]
                vector_columna_j_matriz_otra = Vector(columna_j_matriz_otra)
                fila_resultado.append(vector_fila_i_matriz.producto_punto(vector_columna_j_matriz_otra))
            datos_resultado.append(fila_resultado)

        return Matriz(datos_resultado)

    @staticmethod
    def identidad(tamano: int):
        """
        Crea una matriz identidad de tamaño size * size.

        La matriz identidad es el "1" de las matrices:
        A * I = I * A = A

        Args:
            tamano: Tamaño de la matriz cuadrada

        Returns:
            Matriz: Matriz identidad
        """
        datos = [
            [1.0 if i == j else 0.0 for j in range(tamano)]
            for i in range(tamano)
        ]
        return Matriz(datos)


def rotar_vector(vector: Vector, angulo: int):
    """Rotar vector"""
    angulo_radianes = angulo * math.pi / 180  # angulo en grados convertido a radianes
    matriz_transformacion = Matriz([
        [math.cos(angulo_radianes), -math.sin(angulo_radianes)],
        [math.sin(angulo_radianes),  math.cos(angulo_radianes)]
    ])
    return {
        "matriz_transformacion": matriz_transformacion,
        "vector_rotado": matriz_transformacion.multiplicar_por_vector(vector)
    }

# Ejemplos de uso
def demo_operaciones_matriciales():
    """
    Demuestra las operaciones matriciales con ejemplos de Machine Learning.
    """
    mensaje = "Ejemplos de Operaciones Matriciales"
    print("#" * len(mensaje))
    print(mensaje)
    print("#" * len(mensaje))
    print("\n")

    print("=== Ejemplo 1: Transponer ===")
    datos = Matriz([
        [1.0, 2.0, 3.0],
        [4.0, 5.0, 6.0],
        [7.0, 8.0, 9.0]
    ])
    print(datos)
    print("Transponer...")
    print(datos.trasponer())
    print("\n")

    print("=== Ejemplo 2: Multiplicación matriz por escalar ===")
    print(datos)
    escalar = 3
    print(f"Multiplicación por el escalar: {escalar}...")
    print(datos.multiplicar_por_escalar(escalar))
    print("\n")

    print("=== Ejemplo 3: Multiplicación matriz por vector ===")
    print(datos)
    vector = Vector([1.0, 2.0, 3.0])
    print(f"Multiplicación por el vector: {vector}...")
    print(datos.multiplicar_por_vector(vector))
    print("\n")

    print("=== Ejemplo 4: Multiplicación matriz por matriz ===")
    print(datos)
    otra = Matriz([
        [9.0, 8.0, 7.0],
        [6.0, 5.0, 4.0],
        [3.0, 2.0, 1.0]
    ])
    print(f"Multiplicación por la matriz: {otra}...")
    print(datos.multiplicar_matrices(otra))
    print("\n")

    print("=== Ejemplo 5: Rotación de un vector en 2D ===")
    vector_original = Vector([1.0, 0.0])
    print("\n")

    angulo = 45
    rotacion = rotar_vector(vector_original, angulo)
    print(f"Vector original en 2D: {vector_original}")
    print(f"Matriz de rotacion en {angulo} grados: {rotacion["matriz_transformacion"]}")
    print(f"Vector rotado en {angulo} grados: {rotacion["vector_rotado"]}")
    print("\n")

    angulo = 90
    rotacion = rotar_vector(vector_original, angulo)
    print(f"Vector original en 2D: {vector_original}")
    print(f"Matriz de rotacion en {angulo} grados: {rotacion["matriz_transformacion"]}")
    print(f"Vector rotado en {angulo} grados: {rotacion["vector_rotado"]}")
    print("\n")

    angulo = 180
    rotacion = rotar_vector(vector_original, angulo)
    print(f"Vector original en 2D: {vector_original}")
    print(f"Matriz de rotacion en {angulo} grados: {rotacion["matriz_transformacion"]}")
    print(f"Vector rotado en {angulo} grados: {rotacion["vector_rotado"]}")


if __name__ == "__main__":
    demo_operaciones_matriciales()

Al ejecutar el código anterior obtenemos:

>  python matriz.py
###################################
Ejemplos de Operaciones Matriciales
###################################

=== Ejemplo 1: Transponer ===
Matriz(
  [   1.000    2.000    3.000]
  [   4.000    5.000    6.000]
  [   7.000    8.000    9.000]
)
Transponer...
Matriz(
  [   1.000    4.000    7.000]
  [   2.000    5.000    8.000]
  [   3.000    6.000    9.000]
)

=== Ejemplo 2: Multiplicación matriz por escalar ===
Matriz(
  [   1.000    2.000    3.000]
  [   4.000    5.000    6.000]
  [   7.000    8.000    9.000]
)
Multiplicación por el escalar: 3...
Matriz(
  [   3.000    6.000    9.000]
  [  12.000   15.000   18.000]
  [  21.000   24.000   27.000]
)

=== Ejemplo 3: Multiplicación matriz por vector ===
Matriz(
  [   1.000    2.000    3.000]
  [   4.000    5.000    6.000]
  [   7.000    8.000    9.000]
)
Multiplicación por el vector: Vector([1.0, 2.0, 3.0])...
Vector([14.0, 32.0, 50.0])

=== Ejemplo 4: Multiplicación matriz por matriz ===
Matriz(
  [   1.000    2.000    3.000]
  [   4.000    5.000    6.000]
  [   7.000    8.000    9.000]
)
Multiplicación por la matriz: Matriz(
  [   9.000    8.000    7.000]
  [   6.000    5.000    4.000]
  [   3.000    2.000    1.000]
)...
   Forma matriz A: (3, 3)
   Forma matriz B: (3, 3)
Matriz(
  [  30.000   24.000   18.000]
  [  84.000   69.000   54.000]
  [ 138.000  114.000   90.000]
)

=== Ejemplo 5: Rotación de un vector en 2D ===

Vector original en 2D: Vector([1.0, 0.0])
Matriz de rotacion en 45 grados: Matriz(
  [   0.707   -0.707]
  [   0.707    0.707]
)
Vector rotado en 45 grados: Vector([0.7071067811865476, 0.7071067811865475])

Vector original en 2D: Vector([1.0, 0.0])
Matriz de rotacion en 90 grados: Matriz(
  [   0.000   -1.000]
  [   1.000    0.000]
)
Vector rotado en 90 grados: Vector([6.123233995736766e-17, 1.0])

Vector original en 2D: Vector([1.0, 0.0])
Matriz de rotacion en 180 grados: Matriz(
  [  -1.000   -0.000]
  [   0.000   -1.000]
)
Vector rotado en 180 grados: Vector([-1.0, 1.2246467991473532e-16])

Espacios Vectoriales y Transformaciones Lineales
#

Un espacio vectorial (o espacio lineal) es un conjunto no vacío de vectores, en el que se han definido dos operaciones: la suma de vectores y la multiplicación de un vector por un escalar (número real o complejo). Para que un conjunto sea considerado un espacio vectorial, debe cumplir con ciertos axiomas fundamentales.

  1. Conmutatividad: \(u + v = v + u\)

  2. Asociatividad: \((u + v) + w = u + (v + w)\)

  3. Existencia del vector nulo: \(\exists \ v_0 \in V \ \;|\; \ v_0 + u = u \ \forall \ u \in V \)

  4. Existencia del opuesto: \(\forall \ v_i \in V \ \exists \ -v_i \in V \ \;|\; \ v_i + (-v_i) = 0\)

  5. Distributividad del producto respecto a la suma vectorial: \(\alpha (u + v) = \alpha u + \alpha v\)

  6. Distributividad del producto respecto a la suma escalar: \((\alpha + \beta) u = \alpha u + \beta u\)

  7. Asociatividad del producto de escalares: \(\alpha (\beta u) = (\alpha \beta) u\)

  8. Elemento neutro: \(1 u = u \ \forall \ u \in V\)

Entre algunos ejemplos de espacios vectoriales podemos mencionar:

  • Vectores en el plano: Los vectores en \(\mathbb{R}^2\) son un ejemplo clásico de espacio vectorial, donde cada vector se representa como un par ordenado \((x,y)\)
  • Vectores en el espacio tridimensional: En \(\mathbb{R}^3\), un vector se puede escribir como \(V = \alpha i + \beta j + \gamma k \) donde \(i\), \(j\) y \(k\) son vectores base.

Los espacios vectoriales son fundamentales en diversas áreas, incluyendo matemáticas, física, ingeniería y ciencias de la computación, ya que permiten modelar y resolver problemas complejos mediante el uso de vectores y matrices.

¿Por qué importa en Machine Learning?

  • Características: Cada dataset define un espacio vectorial
  • Modelos: Los algoritmos de Machine Learning operan en estos espacios
  • Transformaciones: Cambiamos de un espacio a otro para facilitar el aprendizaje

Transformaciones lineales
#

Una transformación \(T: \mathbb{R}^n \rightarrow \mathbb{R}^m\) se define como una función que asigna a cada vector \(v\) en un espacio vectorial \(V\) un único vector \(w\) en otro espacio vectorial \(W\).

Para que \(T\) sea considerada lineal, debe cumplir dos condiciones fundamentales:

  • Adición: Para cualquier par de vectores \(u\) y \(v\) en \(V\), se cumple que: $$ T(u + v) = T(u) + T(v) $$
  • Homogeneidad: Para cualquier escalar \(c\) y cualquier vector \(v\) en \(V\) se cumple que: $$ T(c \ v) = c \ T(v) $$

Toda transformación lineal entre espacios vectoriales de dimensión finita puede representarse mediante una matriz, por ejemplo:

Imaginemos una transformación \(T: \mathbb{R}^2 \rightarrow \mathbb{R}^2\) definida por:

$$T(x,y) = (2x+y,3x-4y)$$

En base canónica:

  • \(T(1,0) = (2,3) \rightarrow \) primera columna \(\begin{pmatrix} 2 \\ 3 \end{pmatrix}\)
  • \(T(0,1) = (1,-4) \rightarrow \) segunda columna \(\begin{pmatrix} 1 \\ -4 \end{pmatrix}\)

La matriz asociada a la transformación \(T\) es:

$$|\mathbf{T}| = \begin{pmatrix} 2 & 1 \\ 3 & -4 \end{pmatrix}$$

La base canónica es un conjunto de vectores que forma una base ortonormal en un espacio vectorial. En el plano, la base canónica está compuesta por los vectores \(i\) y \(j\), que representan las direcciones de los ejes \(x\) e \(y\), respectivamente. Estos vectores se utilizan para expresar otros vectores como combinaciones lineales de la base canónica. Además, la base canónica es fundamental para entender la dimensión y la estructura de los espacios vectoriales.

Vectores y valores propios
#

Los valores propios o autovalores y los vectores propios o autovectores revelan las direcciones “especiales” de una transformación lineal.

Los vectores propios o autovectores de una transformación lineal son los vectores no nulos que, cuando son transformados, dan lugar a un múltiplo escalar de sí mismos, con lo que no cambian su dirección. Este escalar \(\lambda\) recibe el nombre de valor propio o autovalor. En muchos casos, una transformación queda completamente determinada por sus vectores propios y valores propios. Un espacio propio o autoespacio asociado al valor propio \(\lambda\) es el conjunto de vectores propios con un valor propio común.

Para una transformación lineal representada por la matriz \(\mathbf{A}\), un vector \(\mathbf{v}\) es un vector propio con valor propio \(\lambda\) si:

$$\mathbf{A}\mathbf{v} = \lambda \mathbf{v}$$

Interpretación: La transformación \(\mathbf{A}\) solo escala el vector \(\mathbf{v}\) por el factor \(\lambda\), sin cambiar su dirección.

Mona Lisa con auto vector
Imagen de J. Finkelstein y Vb on Wikimedia Commons, dominio público
En esta transformación de la Mona Lisa, la imagen se ha deformado de tal forma que su eje vertical no ha cambiado. El vector azul, representado por la flecha azul que va desde el pecho hasta el hombro, ha cambiado de dirección, mientras que el rojo, representado por la flecha roja, no ha cambiado. El vector rojo es entonces un vector propio o autovector de la transformación, mientras que el azul no lo es. Dado que el vector rojo no ha cambiado de longitud, su valor propio o autovalor es \(1\). Todos los vectores de esta misma dirección son vectores propios, con el mismo valor propio. Forman un subespacio del espacio propio de este valor propio.

Vector, valor y espacio propios. Wikipedia

Aplicaciones en Machine Learning
#

  1. PCA (Análisis de Componentes Principales): Los vectores propios de la matriz de covarianza muestran las direcciones de mayor varianza en los datos.

  2. Reducción de dimensionalidad: Proyectar datos en los vectores propios principales.

  3. Estabilidad de sistemas: Los valores propios indican si un sistema dinámico es estable.

Visualizando valores y vectores propios
#

Crea un archivo Python con el siguiente contenido:

def ver_valores_propios():
    """
    Visualiza conceptualmente los valores y los vectores propios.
    Esta es una simplificación para matrices 2x2.
    """
    import matplotlib.pyplot as plt
    import numpy as np

    # Matriz de ejemplo
    A = np.array([[3, 1], [0, 2]])

    # Calcular valores y vectores propios usando NumPy
    valores_propios, vectores_propios = np.linalg.eig(A)

    # Crear varios vectores para mostrar la transformación
    angles = np.linspace(0, 2*np.pi, 16)
    vectores_originales = np.array([[np.cos(a), np.sin(a)] for a in angles])
    vectores_transformados = np.array([A @ v for v in vectores_originales])

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

    # Vectores originales
    ax1.set_aspect('equal')
    for v in vectores_originales:
        ax1.arrow(0, 0, v[0], v[1], head_width=0.05, head_length=0.1,
                 fc='blue', ec='blue', alpha=0.6)

    # Vectores propios originales
    for i, (val, vec) in enumerate(zip(valores_propios, vectores_propios.T)):
        ax1.arrow(0, 0, vec[0], vec[1], head_width=0.1, head_length=0.15,
                 fc='red', ec='red', linewidth=3,
                 label=f'Vector propio {i+1}')

    ax1.set_xlim(-2, 2)
    ax1.set_ylim(-2, 2)
    ax1.set_title('Vectores Originales')
    ax1.grid(True)
    ax1.legend()

    # Vectores transformados
    ax2.set_aspect('equal')
    for v in vectores_transformados:
        ax2.arrow(0, 0, v[0], v[1], head_width=0.05, head_length=0.1,
                 fc='green', ec='green', alpha=0.6)

    # Vectores propios transformados (escalados por valor propio)
    for i, (val, vec) in enumerate(zip(valores_propios, vectores_propios.T)):
        vectores_propios_transformados = val * vec
        ax2.arrow(0, 0, vectores_propios_transformados[0], vectores_propios_transformados[1],
                 head_width=0.1, head_length=0.15, fc='red', ec='red',
                 linewidth=3, label=f{i+1}={val:.1f} × eigenvec{i+1}')

    ax2.set_xlim(-4, 4)
    ax2.set_ylim(-4, 4)
    ax2.set_title('Vectores Transformados por A')
    ax2.grid(True)
    ax2.legend()

    plt.tight_layout()
    plt.show()

    print(f"Valores propios: {valores_propios}")
    print(f"Vectores propios:\n{vectores_propios}")


ver_valores_propios()

Lo ejecutamos…

virtualenv venv
source venv/bin/activate
pip install numpy
pip install matplotlib
python valores_vectores_propios.py

y obtenemos:

Visualizando vectores propios con Python
Visualizando vectores propios con Python

Implementación práctica: un sistema de recomendaciones usando álgebra lineal
#

En el siguiente artículo de este módulo, vamos a actualizar nuestro sistema de recomendaciones para utilizar los conceptos de álgebra lineal que aprendimos hasta acá.

¡Nos vemos allí! 🚀


¡Gracias por haber llegado hasta acá!

Si te gustó el artículo, por favor ¡no olvides compartirlo con tu familia, amigos y colegas!

Y si puedes, envía tus comentarios, sugerencias, críticas a nuestro mail o por redes sociales, nos ayudarías a generar mejor contenido y sobretodo más relevante para vos.

Matemática para Machine Learning - Este artículo es parte de una serie.
Parte 1: Este artículo

Relacionados

Fundamentos de Inteligencia Artificial: Conclusión
Proyecto usando Machine Learning: Sistema de Recomendaciones
Machine Learning Ejemplo 2: Sistema de Clasificación
Machine Learning Ejemplo 1: Sistema Experto
Kubernetes Services

comments powered by Disqus