Algoritmo de Matrices Estocásticas en Python
Introducción
Una matriz estocástica (también llamada matriz de probabilidad de transición) es una matriz cuadrada cuyas filas son vectores de probabilidad: todos sus elementos son no negativos y la suma de cada fila es 1. Estas matrices son la base matemática de cadenas de Markov, algoritmos de PageRank, procesos de decisión de Markov y muchos modelos de machine learning.
En este artículo aprenderás:
- Definiciones y propiedades clave.
- Cómo generar y validar una matriz estocástica mediante un algoritmo robusto.
- Ejemplos prácticos en Python usando
numpyyscipy. - Comparativas con matrices no estocásticas y mejores prácticas de rendimiento.
Definición formal y propiedades esenciales
Sea \(P \in \mathbb{R}^{n\times n}\). Decimos que \(P\) es una matriz estocástica de fila si:
- \(p_{ij} \ge 0\) para todo \(i,j\).
- \(\sum_{j=1}^{n} p_{ij}=1\) para cada fila \(i\).
Propiedades que se derivan directamente:
- Conservación de probabilidad: el vector de distribución de probabilidad \(\pi\) satisface \(\pi P = \pi\).
- Espectro limitado: el valor propio dominante siempre es \(1\) y está asociado a un autovector no negativo.
- Irreducibilidad y aperiodicidad: garantizan convergencia a una distribución estacionaria única.
Algoritmo para generar y validar matrices estocásticas
El algoritmo siguiente asegura que la matriz resultante cumpla ambas condiciones (no‑negatividad y suma de fila = 1) y permite incluir restricciones opcionales como sparsidad o diagonal dominante.
import numpy as np
def generar_matriz_estocastica(n, sparsity=0.0, seed=None):
"""Genera una matriz estocástica de fila de tamaño *n*.
Parámetros
----------
n : int
Dimensión de la matriz cuadrada.
sparsity : float, opcional (default=0.0)
Fracción de ceros a introducir (0 = densa, 1 = totalmente cero).
seed : int, opcional
Semilla para reproducibilidad.
"""
rng = np.random.default_rng(seed)
# Paso 1: generar valores positivos aleatorios
mat = rng.random((n, n))
# Paso 2: aplicar sparsity (opcional)
if sparsity > 0:
mask = rng.random((n, n)) < sparsity
mat[mask] = 0.0
# Paso 3: normalizar filas para que sumen 1
row_sums = mat.sum(axis=1, keepdims=True)
# Evitar división por cero (filas totalmente cero)
row_sums[row_sums == 0] = 1.0
mat = mat / row_sums
return mat
def es_estocastica(P, atol=1e-8):
"""Valida que *P* sea una matriz estocástica de fila.
Devuelve ``True`` si cumple:
1) todos los elementos >= 0
2) la suma de cada fila ≈ 1 (tolerancia *atol*)
"""
if np.any(P < -atol):
return False
row_sums = P.sum(axis=1)
return np.all(np.abs(row_sums - 1) <= atol)
El algoritmo está pensado para escalar a matrices de varios miles de filas, aprovechando numpy y evitando bucles Python.
Comparativa: Matrices Estocásticas vs. Matrices Arbitrarias
Matriz Estocástica
- Filas suman 1 → conserva probabilidad.
- Valores no negativos → interpretación como probabilidades.
- Valor propio dominante = 1 → garantiza existencia de distribución estacionaria.
- Uso típico: cadenas de Markov, PageRank, modelado de procesos estocásticos.
Matriz Arbitraria
- Sin restricción de suma de filas → no representa transiciones probabilísticas.
- Puede contener valores negativos → no interpretable como probabilidad.
- Valores propios arbitrarios → no hay garantía de convergencia.
- Aplicaciones genéricas: transformaciones lineales, sistemas de ecuaciones, etc.
Ejemplo 1: Cadena de Markov de 3 estados
Supongamos un proceso que modela el clima: Soleado (S), Nublado (N) y Lluvioso (L). La matriz de transición es:
P = np.array([
[0.6, 0.3, 0.1], # S -> S,N,L
[0.2, 0.5, 0.3], # N -> S,N,L
[0.1, 0.4, 0.5] # L -> S,N,L
])
Calculemos la distribución estacionaria resolviendo \(\pi P = \pi\):
import scipy.linalg as la
# Resolución del sistema (P.T - I) * pi = 0 con condición suma(pi)=1
A = np.vstack([P.T - np.eye(3), np.ones(3)])
b = np.append(np.zeros(3), 1)
pi = la.lstsq(A, b)[0]
print("Distribución estacionaria:", pi)
Resultado típico:
Distribución estacionaria: [0.357, 0.357, 0.286]
Esto indica que, a largo plazo, el 35.7 % de los días será soleado, 35.7 % nublado y 28.6 % lluvioso.
Ejemplo 2: Algoritmo PageRank simplificado
PageRank se basa en una matriz estocástica de transición de un grafo dirigido. A continuación, una versión mínima con 4 páginas:
import numpy as np
def pagerank(P, alpha=0.85, tol=1e-8, max_iter=100):
n = P.shape[0]
# Matriz de teletransportación
E = np.ones((n, n)) / n
M = alpha * P + (1 - alpha) * E
r = np.ones(n) / n # vector de ranking inicial
for _ in range(max_iter):
r_next = M.T @ r
if np.linalg.norm(r_next - r, 1) < tol:
break
r = r_next
return r
# Matriz de enlaces (filas = página origen, columnas = destino)
L = np.array([
[0, 1, 1, 0], # Página A enlaza a B y C
[1, 0, 0, 1], # Página B enlaza a A y D
[1, 0, 0, 1], # Página C enlaza a A y D
[0, 1, 0, 0] # Página D enlaza a B
], dtype=float)
# Normalizar filas para que sumen 1 (manejo de dangling nodes opcional)
P = L / L.sum(axis=1, keepdims=True)
rank = pagerank(P)
print("PageRank:", rank)
El vector rank muestra la importancia relativa de cada página según el modelo de salto aleatorio.
Buenas prácticas, seguridad y optimización
- Validación de entrada: siempre aplicar
es_estocasticaantes de usar la matriz en algoritmos críticos. - Sparsity: para grafos grandes, almacena la matriz en formato CSR/CSC usando
scipy.sparsey evita conversiones a densa. - Precisión numérica: en matrices muy grandes, la suma de fila puede desviarse ligeramente de 1 por error de punto flotante; usa tolerancias (
atol=1e-12) y renormaliza si es necesario. - Parallelismo: operaciones de multiplicación matriz‑vector (
M.T @ r) pueden paralelizarse connumbaocupypara GPUs. - Seguridad: si la matriz proviene de fuentes externas (p. ej., archivos CSV), sanitiza los valores para evitar NaN, inf o negativos que romperían la lógica de probabilidad.
Resolución de problemas comunes
| Problema | Causa típica | Solución recomendada |
|---|---|---|
| Filas que no suman 1 | División por cero al normalizar filas vacías | Reemplazar filas cero por distribución uniforme antes de la normalización. |
| Valores negativos | Lectura de datos corruptos o uso de funciones que generan números aleatorios con distribución normal | Aplicar np.clip(P, 0, None) y volver a normalizar. |
| Convergencia lenta en PageRank | Valor de alpha demasiado cercano a 1 o matriz muy esparsa | Reducir alpha (p. ej., 0.85 → 0.80) o usar aceleración de potencia (extrapolación de Anderson). |
| Desbordamiento de memoria | Matrices densas de > 10⁴ × 10⁴ | Utilizar scipy.sparse.csr_matrix y operaciones esparsas. |
Conclusión
Las matrices estocásticas son una herramienta esencial para modelar procesos probabilísticos. Con el algoritmo presentado puedes generar, validar y emplear estas matrices de forma segura y eficiente en Python, abriendo la puerta a aplicaciones que van desde la simulación de cadenas de Markov hasta algoritmos de ranking web y modelos de aprendizaje automático.
Recuerda siempre validar los datos de entrada, aprovechar estructuras esparsas para escalabilidad y considerar la tolerancia numérica para evitar desviaciones sutiles pero críticas.
Algoritmo de Matrices Estocásticas en Python: Conceptos, Implementación y Casos Prácticos