Algoritmo Page Rank de Google
Implementación del algoritmo PageRank de Google en Rust, demostrando como las cadenas de Markov y el modelo de teletransporte permiten clasificar páginas web según su relevancia.
stack tecnológico
Rust
Contexto y propósito
El contexto empezó al ver el video “Visitiuim cadena de Markov” donde se explica cómo las Cadenas de Markov permitieron a Google convertirse en el mejor buscador del mundo (PageRank): Ver video en YouTube.
A partir de esa inspiración, este proyecto nació como un ejercicio práctico para:
- Aplicar Cadenas de Markov a generación de texto y entender sus limitaciones/fortalezas.
- Consolidar conceptos de álgebra lineal (matrices estocásticas, distribución estacionaria) y el modelo de teletransporte (base de PageRank).
- Disponer de un binario sencillo que entrena una cadena de Markov y genera nuevas secuencias, además de utilidades para experimentar con matrices de transición.
Desafíos Técnicos y Soluciones de Ingeniería
1) Tokenización y normalización robustas para español
- Retos:
- Limpiar puntuación y unificar a minúsculas para evitar explosión del vocabulario.
- Garantizar un orden determinista del vocabulario para reproducibilidad.
- Solución:
- La función
tokenizeconvierte a minúsculas, filtra caracteres no alfabéticos (reemplazándolos por espacios) y separa porsplit_whitespace. - El vocabulario se construye con
BTreeSety se vuelca aVec, lo que asegura orden y, por tanto, índices reproducibles.
- La función
- Código clave:
2) Construcción segura de matrices de transición
- Retos:
- Evitar estados de matriz inválidos (vacía, no-cuadrada, tamaño 0) y filas con suma 0.
- Normalizar recuentos a probabilidades fila-a-fila.
- Solución:
SquareMatrix::newvalida tamaño y forma;SquareMatrix::zeros(n)rechazan=0.- Tras contar bigramas, cada fila se normaliza dividiendo por su suma (si > 0).
- Código clave:
3) Estrategias de generación: “greedy” no-reflexiva y muestreo ponderado
- Retos:
- Evitar bucles triviales (quedarse en la misma palabra).
- Conservar naturalidad alternando entre determinismo y aleatoriedad.
- Solución:
next_greedy_nonselfelige el siguiente estado con mayor probabilidad excluyendo transiciones a sí mismo; en empate, desempata por índice (determinismo).generaterealiza muestreo aleatorio ponderado (conrand) prohibiendo auto-transición; si no hay alternativa, permite cualquiera como “fallback”.
- Código clave:
4) PageRank y modelo de teletransporte (eigenvector estacionario)
- Retos:
- Ilustrar el modelo de teletransporte con factor
α(e.g., 0.85) y obtener la distribución estacionaria.
- Ilustrar el modelo de teletransporte con factor
- Solución:
- Módulo
instructionscon operaciones de matrices y funciones de ayuda para construir el modelo y calcular el eigenvector dominante. - Ejemplo rápido (comentado) en
main.rsque muestra: construir matriz, aplicar teletransporte y calcular eigenvector.
- Módulo
- Código clave:
- src/instructions/matrix_operations.rs
- src/instructions/markov_chain.rs
- Demostración en: src/main.rs (bloque comentado al inicio)
Organización del Proyecto
- Raíz
- Cargo.toml (Rust 2024,
rand0.9.x) - Cargo.lock
- .gitignore
- README.md
- Cargo.toml (Rust 2024,
- assets/
- Corpus de ejemplo (p.ej.
quijote.txt) para entrenamiento.
- Corpus de ejemplo (p.ej.
- src/
- main.rs: binario de demostración (generación de texto; ejemplo PageRank comentado).
- lib.rs: expone módulos de librería.
- collections/
- mod.rs
- square_matrix.rs: tipo
SquareMatrixy validaciones. - markov_chain.rs: entrenamiento y generación.
- instructions/
- docs/
- Material auxiliar (si procede).
- tests/
- Espacio para pruebas (WIP).
- .github/
- Carpeta reservada para flujos de CI/CD (si se añaden).
API de librería (ejemplo de uso)
use markov_chain::collections::MarkovChain;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let texto = "hola hola adios mundo adios hola mundo";
let mut mc = MarkovChain::new();
mc.fit(texto)?;
let greedy = mc.generate_greedy_nonself("hola", 10).unwrap();
let random = mc.generate("hola", 10).unwrap();
println!("Greedy: {}", greedy);
println!("Random: {}", random);
Ok(())
}
Puntos clave:
fit(&str): entrena con un corpus en texto plano.generate_greedy_nonself(start, pasos): elige siempre la transición más probable (excluyendo auto-transiciones).generate(start, pasos): muestreo aleatorio ponderado; evita auto-transiciones si es posible.
Cómo funciona (alto nivel)
- Tokenización:
- Minúsculas, filtrado de símbolos no alfabéticos y partición por espacios.
- Vocabulario e índices:
- Con
BTreeSetpara orden determinista; mapeo palabra→índice conBTreeMap.
- Con
- Conteo y normalización:
- Bigrama
(w_i → w_{i+1})incrementaM[i][j]. - Cada fila se normaliza para que sume 1, formando una matriz estocástica fila.
- Bigrama
- Generación:
- Greedy no-reflexiva: evita quedarse en el mismo estado; en empate, el más “temprano” por índice.
- Aleatoria: muestreo proporcional a
M[i][*](conrand); “fallback” si la fila efectiva queda sin masa.
- PageRank (demo):
- Construye
M_α = αM + (1-α)U(mezcla con distribución uniforme) y obtiene la distribución estacionaria por eigenvector.
- Construye
Limitaciones y mejoras futuras
- Vocabulario a nivel de palabra únicamente (no n-gramas > 2).
- No se preserva puntuación ni mayúsculas originales.
- No se establecen semillas RNG; añadir opción para reproducibilidad total.
- Suavizado (Laplace/Kneser-Ney) para manejar mejor palabras raras.
- Serialización del modelo (guardar/cargar) y exposición como crate en crates.io.
- Tests adicionales en
tests/y benchmarks de rendimiento.