Phase 01 - Lesson 03

Transformaciones Matriciales

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Una matriz es una máquina que remodela el espacio. Aprende lo que hace con cada punto y entenderás la transformación completa.

Tipo: Construir Lenguajes: Python, Julia Requisitos previos: Fase 1, Lecciones 01-02 (Intuición de Álgebra Lineal, Operaciones de Vectores y Matrices) Tiempo: ~75 minutos

Objetivos de Aprendizaje

  • Construir matrices de rotación, escala, cizallamiento y reflexión y aplicarlas a puntos 2D y 3D
  • Componer múltiples transformaciones por multiplicación de matrices y verificar que el orden importa
  • Calcular autovalores y autovectores de matrices 2x2 a partir de la ecuación característica
  • Explicar por que los autovalores determinan las direcciones del PCA, la estabilidad de las RNN y el comportamiento del clustering espectral

El Problema

Lees sobre PCA y ves "encuentra los autovectores de la matriz de covarianza". Lees sobre estabilidad de modelos y ves "verifica si todos los autovalores tienen magnitud menor que 1". Lees sobre data augmentation y ves "aplica una rotación aleatoria". Nada de esto tiene sentido hasta que entiendes que hacen las matrices con el espacio geométricamente.

Las matrices no son solo cuadrículas de números. Son máquinas espaciales. Una matriz de rotación gira puntos. Una matriz de escala los estira. Una matriz de cizallamiento los inclina. Cada transformación que una red neuronal aplica a los datos es una de estas operaciones o una composición de ellas. Esta lección hace concretas esas operaciones.

El Concepto

Transformaciones como matrices

Toda transformación lineal en 2D puede escribirse como una matriz 2x2. La matriz te dice exactamente dónde terminan los vectores de la base [1, 0] y [0, 1]. Todo lo demás se deduce de ahi.

graph LR
    subgraph Before["Standard Basis"]
        e1["e1 = [1, 0] (along x)"]
        e2["e2 = [0, 1] (along y)"]
    end
    subgraph Transform["Matrix M"]
        M["M = columns are new basis vectors"]
    end
    subgraph After["After Transformation M"]
        e1p["e1' = new x-basis"]
        e2p["e2' = new y-basis"]
    end
    e1 --> M --> e1p
    e2 --> M --> e2p

Rotación

Una rotación 2D por el ángulo theta mantiene intactas distancias y ángulos. Mueve cada punto a lo largo de un arco circular.

graph LR
    subgraph Before["Before Rotation"]
        A["A(2, 1)"]
        B["B(0, 2)"]
    end
    subgraph Rot["Rotate 45 degrees"]
        R["R(θ) = [[cos θ, -sin θ], [sin θ, cos θ]]"]
    end
    subgraph After["After Rotation"]
        Ap["A'(0.71, 2.12)"]
        Bp["B'(-1.41, 1.41)"]
    end
    A --> R --> Ap
    B --> R --> Bp

En 3D, rotas alrededor de un eje. Cada eje tiene su propia matriz de rotación:

Rz(theta) = | cos  -sin  0 |     Rotate around z-axis
            | sin   cos  0 |     (x-y plane spins, z stays)
            |  0     0   1 |

Rx(theta) = | 1   0     0    |   Rotate around x-axis
            | 0  cos  -sin   |   (y-z plane spins, x stays)
            | 0  sin   cos   |

Ry(theta) = |  cos  0  sin |     Rotate around y-axis
            |   0   1   0  |     (x-z plane spins, y stays)
            | -sin  0  cos |

Escala

La escala estira o comprime a lo largo de cada eje de forma independiente.

graph LR
    subgraph Before["Before Scaling"]
        A["A(2, 1)"]
        B["B(0, 2)"]
    end
    subgraph Scale["Scale sx=2, sy=0.5"]
        S["S = [[2, 0], [0, 0.5]]"]
    end
    subgraph After["After Scaling"]
        Ap["A'(4, 0.5)"]
        Bp["B'(0, 1)"]
    end
    A --> S --> Ap
    B --> S --> Bp

Cizallamiento

El cizallamiento inclina un eje mientras mantiene fijo el otro. Convierte rectángulos en paralelogramos.

graph LR
    subgraph Before["Before Shear"]
        A["A(1, 0)"]
        B["B(0, 1)"]
    end
    subgraph Shear["Shear in x, k=1"]
        Sh["Shx = [[1, k], [0, 1]]"]
    end
    subgraph After["After Shear"]
        Ap["A(1, 0) unchanged"]
        Bp["B'(1, 1) shifted"]
    end
    A --> Sh --> Ap
    B --> Sh --> Bp

Matrices de cizallamiento:

  • Shx = [[1, k], [0, 1]] desplaza x por k * y
  • Shy = [[1, 0], [k, 1]] desplaza y por k * x

Reflexión

La reflexión refleja puntos a través de un eje o línea.

graph LR
    subgraph Before["Before Reflection"]
        A["A(2, 1)"]
    end
    subgraph Reflect["Reflect across y-axis"]
        R["[[-1, 0], [0, 1]]"]
    end
    subgraph After["After Reflection"]
        Ap["A'(-2, 1)"]
    end
    A --> R --> Ap

Matrices de reflexión:

  • Reflejar a través del eje y: [[-1, 0], [0, 1]]
  • Reflejar a través del eje x: [[1, 0], [0, -1]]

Composición: encadenar transformaciones

Aplicar la transformación A y luego B es lo mismo que multiplicar sus matrices: result = B @ A @ point. El orden importa. Rotar y luego escalar da resultados distintos de escalar y luego rotar.

graph LR
    subgraph Path1["Rotate 90 then Scale (2, 0.5)"]
        P1["(1, 0)"] -->|"Rotate 90"| P2["(0, 1)"] -->|"Scale"| P3["(0, 0.5)"]
    end

Compuesta: S @ R = [[0, -2], [0.5, 0]]

graph LR
    subgraph Path2["Scale (2, 0.5) then Rotate 90"]
        Q1["(1, 0)"] -->|"Scale"| Q2["(2, 0)"] -->|"Rotate 90"| Q3["(0, 2)"]
    end

Compuesta: R @ S = [[0, -0.5], [2, 0]]

Resultados distintos. La multiplicación de matrices no es conmutativa.

Autovalores y autovectores

La mayoría de los vectores cambian de dirección cuando una matriz los golpea. Los autovectores son especiales: la matriz solo los escala, nunca los rota. El factor de escala es el autovalor.

A @ v = lambda * v

v is the eigenvector (direction that survives)
lambda is the eigenvalue (how much it stretches)

Example: A = | 2  1 |
             | 1  2 |

Eigenvector [1, 1] with eigenvalue 3:
  A @ [1,1] = [3, 3] = 3 * [1, 1]     (same direction, scaled by 3)

Eigenvector [1, -1] with eigenvalue 1:
  A @ [1,-1] = [1, -1] = 1 * [1, -1]  (same direction, unchanged)

La matriz estira el espacio 3x a lo largo de [1, 1] y mantiene [1, -1] sin cambios. Cualquier otra dirección es una mezcla de estas dos.

Autodescomposición

Si una matriz tiene n autovectores linealmente independientes, puede descomponerse:

A = V @ D @ V^(-1)

V = matrix whose columns are eigenvectors
D = diagonal matrix of eigenvalues
V^(-1) = inverse of V

This says: rotate into eigenvector coordinates, scale along each axis, rotate back.

Por qué importan los autovalores

PCA. Los autovectores de la matriz de covarianza son las componentes principales. Los autovalores te dicen cuánta varianza captura cada componente. Ordena por autovalor, conserva los k mayores y tienes reducción de dimensionalidad.

Estabilidad. En redes recurrentes y sistemas dinámicos, autovalores con magnitud > 1 hacen que las salidas exploten. Magnitud < 1 hace que se desvanezcan. Este es el problema del gradiente que se desvanece/explota expresado en una frase.

Métodos espectrales. Las redes neuronales de grafos usan autovalores de la matriz de adyacencia. El clustering espectral usa autovalores del Laplaciano. Los autovectores revelan la estructura del grafo.

Determinante como factor de escala de volumen

El determinante de una matriz de transformación te dice cuánto escala área (2D) o volumen (3D).

det = 1:   area preserved (rotation)
det = 2:   area doubled
det = 0:   space crushed to lower dimension (singular)
det = -1:  area preserved but orientation flipped (reflection)

| det(Rotation) | = 1        (always)
| det(Scale sx, sy) | = sx * sy
| det(Shear) | = 1           (area preserved)
| det(Reflection) | = -1     (orientation flipped)

Construye

Paso 1: Matrices de transformación desde cero (Python)

import math

def rotation_2d(theta):
    c, s = math.cos(theta), math.sin(theta)
    return [[c, -s], [s, c]]

def scaling_2d(sx, sy):
    return [[sx, 0], [0, sy]]

def shearing_2d(kx, ky):
    return [[1, kx], [ky, 1]]

def reflection_x():
    return [[1, 0], [0, -1]]

def reflection_y():
    return [[-1, 0], [0, 1]]

def mat_vec_mul(matrix, vector):
    return [
        sum(matrix[i][j] * vector[j] for j in range(len(vector)))
        for i in range(len(matrix))
    ]

def mat_mul(a, b):
    rows_a, cols_b = len(a), len(b[0])
    cols_a = len(a[0])
    return [
        [sum(a[i][k] * b[k][j] for k in range(cols_a)) for j in range(cols_b)]
        for i in range(rows_a)
    ]

point = [1.0, 0.0]
angle = math.pi / 4

rotated = mat_vec_mul(rotation_2d(angle), point)
print(f"Rotate (1,0) by 45 deg: ({rotated[0]:.4f}, {rotated[1]:.4f})")

scaled = mat_vec_mul(scaling_2d(2, 3), [1.0, 1.0])
print(f"Scale (1,1) by (2,3): ({scaled[0]:.1f}, {scaled[1]:.1f})")

sheared = mat_vec_mul(shearing_2d(1, 0), [1.0, 1.0])
print(f"Shear (1,1) kx=1: ({sheared[0]:.1f}, {sheared[1]:.1f})")

reflected = mat_vec_mul(reflection_y(), [2.0, 1.0])
print(f"Reflect (2,1) across y: ({reflected[0]:.1f}, {reflected[1]:.1f})")

Paso 2: Composición de transformaciones

R = rotation_2d(math.pi / 2)
S = scaling_2d(2, 0.5)

rotate_then_scale = mat_mul(S, R)
scale_then_rotate = mat_mul(R, S)

point = [1.0, 0.0]
result1 = mat_vec_mul(rotate_then_scale, point)
result2 = mat_vec_mul(scale_then_rotate, point)

print(f"Rotate 90 then scale: ({result1[0]:.2f}, {result1[1]:.2f})")
print(f"Scale then rotate 90: ({result2[0]:.2f}, {result2[1]:.2f})")
print(f"Same? {result1 == result2}")

Paso 3: Autovalores desde cero (2x2)

Para una matriz 2x2 [[a, b], [c, d]], los autovalores resuelven la ecuación característica: lambda^2 - (a+d)*lambda + (ad - bc) = 0.

def eigenvalues_2x2(matrix):
    a, b = matrix[0]
    c, d = matrix[1]
    trace = a + d
    det = a * d - b * c
    discriminant = trace ** 2 - 4 * det
    if discriminant < 0:
        real = trace / 2
        imag = (-discriminant) ** 0.5 / 2
        return (complex(real, imag), complex(real, -imag))
    sqrt_disc = discriminant ** 0.5
    return ((trace + sqrt_disc) / 2, (trace - sqrt_disc) / 2)

def eigenvector_2x2(matrix, eigenvalue):
    a, b = matrix[0]
    c, d = matrix[1]
    if abs(b) > 1e-10:
        v = [b, eigenvalue - a]
    elif abs(c) > 1e-10:
        v = [eigenvalue - d, c]
    else:
        if abs(a - eigenvalue) < 1e-10:
            v = [1, 0]
        else:
            v = [0, 1]
    mag = (v[0] ** 2 + v[1] ** 2) ** 0.5
    return [v[0] / mag, v[1] / mag]

A = [[2, 1], [1, 2]]
vals = eigenvalues_2x2(A)
print(f"Matrix: {A}")
print(f"Eigenvalues: {vals[0]:.4f}, {vals[1]:.4f}")

for val in vals:
    vec = eigenvector_2x2(A, val)
    result = mat_vec_mul(A, vec)
    scaled = [val * vec[0], val * vec[1]]
    print(f"  lambda={val:.1f}, v={[round(x,4) for x in vec]}")
    print(f"    A@v = {[round(x,4) for x in result]}")
    print(f"    l*v = {[round(x,4) for x in scaled]}")

Paso 4: Determinante como factor de escala de volumen

def det_2x2(matrix):
    return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]

print(f"det(rotation 45) = {det_2x2(rotation_2d(math.pi/4)):.4f}")
print(f"det(scale 2,3)   = {det_2x2(scaling_2d(2, 3)):.1f}")
print(f"det(shear kx=1)  = {det_2x2(shearing_2d(1, 0)):.1f}")
print(f"det(reflect y)   = {det_2x2(reflection_y()):.1f}")

singular = [[1, 2], [2, 4]]
print(f"det(singular)     = {det_2x2(singular):.1f}")
print("Singular: columns are proportional, space collapses to a line.")

Usalo

NumPy maneja todo esto con rutinas optimizadas.

import numpy as np

theta = np.pi / 4
R = np.array([[np.cos(theta), -np.sin(theta)],
              [np.sin(theta),  np.cos(theta)]])

point = np.array([1.0, 0.0])
print(f"Rotate (1,0) by 45 deg: {R @ point}")

S = np.diag([2.0, 3.0])
composed = S @ R
print(f"Scale(2,3) after Rotate(45): {composed @ point}")

A = np.array([[2, 1], [1, 2]], dtype=float)
eigenvalues, eigenvectors = np.linalg.eig(A)
print(f"\nEigenvalues: {eigenvalues}")
print(f"Eigenvectors (columns):\n{eigenvectors}")

for i in range(len(eigenvalues)):
    v = eigenvectors[:, i]
    lam = eigenvalues[i]
    print(f"  A @ v{i} = {A @ v}, lambda * v{i} = {lam * v}")

print(f"\ndet(R) = {np.linalg.det(R):.4f}")
print(f"det(S) = {np.linalg.det(S):.1f}")

B = np.array([[3, 1], [0, 2]], dtype=float)
vals, vecs = np.linalg.eig(B)
D = np.diag(vals)
V = vecs
reconstructed = V @ D @ np.linalg.inv(V)
print(f"\nEigendecomposition A = V @ D @ V^-1:")
print(f"Original:\n{B}")
print(f"Reconstructed:\n{reconstructed}")

Rotaciones 3D con NumPy

def rotation_3d_z(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])

def rotation_3d_x(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([[1, 0, 0], [0, c, -s], [0, s, c]])

point_3d = np.array([1.0, 0.0, 0.0])
rotated_z = rotation_3d_z(np.pi / 2) @ point_3d
rotated_x = rotation_3d_x(np.pi / 2) @ point_3d

print(f"\n3D point: {point_3d}")
print(f"Rotate 90 around z: {np.round(rotated_z, 4)}")
print(f"Rotate 90 around x: {np.round(rotated_x, 4)}")

Entrégalo

Esta lección construye la base geométrica para el PCA (Fase 2) y el análisis de pesos de redes neuronales. El código de autovalor/autovector construido aquí es el mismo algoritmo que impulsa la reducción de dimensionalidad, el clustering espectral y el análisis de estabilidad en sistemas de ML en produccion.

Ejercicios

  1. Aplica rotación, escala y cizallamiento a un cuadrado unitario (esquinas en [0,0], [1,0], [1,1], [0,1]). Imprime las esquinas transformadas de cada uno. Verifica que la rotación preserva las distancias entre las esquinas.

  2. Encuentra los autovalores de la matriz [[4, 2], [1, 3]] a mano usando la ecuación característica. Luego verifica con tu función hecha desde cero y con NumPy.

  3. Crea una composición de tres transformaciones (rotar 30 grados, escalar por [1.5, 0.8], cizallar con kx=0.3) y aplícala a 8 puntos dispuestos en un círculo. Imprime las coordenadas antes y después. Calcula el determinante de la matriz compuesta y verifica que sea igual al producto de los determinantes individuales.

Términos Clave

Término Lo que dice la gente Lo que realmente significa
Matriz de rotación "Gira cosas" Una matriz ortogonal que mueve puntos a lo largo de arcos circulares preservando distancias y ángulos. El determinante siempre es 1.
Matriz de escala "Agranda las cosas" Una matriz diagonal que estira o comprime de forma independiente a lo largo de cada eje. El determinante es el producto de los factores de escala.
Matriz de cizallamiento "Inclina las cosas" Una matriz que desplaza una coordenada proporcionalmente a otra, convirtiendo rectángulos en paralelogramos. El determinante es 1.
Reflexión "Refleja las cosas" Una matriz que invierte el espacio a través de un eje o plano. El determinante es -1.
Composición "Hacer dos cosas" Multiplicar matrices de transformación para encadenar operaciones. El orden importa: B @ A significa aplicar A primero, luego B.
Autovector "Dirección especial" Una dirección que la matriz solo escala, nunca rota. La huella digital de la transformación.
Autovalor "Cuánto estira" El factor escalar por el cual la matriz escala su autovector. Puede ser negativo (invierte) o complejo (rotación).
Autodescomposición "Descomponer la matriz" Escribir una matriz como V @ D @ V^(-1), separándola en sus direcciones y magnitudes fundamentales de escala.
Determinante "Un solo número de una matriz" El factor por el cual la transformación escala área (2D) o volumen (3D). Cero significa que la transformación es irreversible.
Ecuación característica "De dónde vienen los autovalores" det(A - lambda * I) = 0. El polinomio cuyas raíces son los autovalores.

Lectura Adicional

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).