Algoritmo de Recomendaciones con SVD: Guía Práctica en Python
Introducción
Los sistemas de recomendación son una pieza clave en plataformas de streaming, e‑commerce y redes sociales. Singular Value Decomposition (SVD) es uno de los algoritmos más robustos para collaborative filtering porque permite reducir la dimensionalidad de una matriz de usuarios‑productos y capturar relaciones latentes.
En este artículo encontrarás:
- Fundamentos teóricos de SVD aplicados a recomendación.
- Implementación completa en Python (NumPy, SciPy y
surprise). - Comparativa con algoritmos alternativos (ALS, NMF, Deep Learning).
- Mejores prácticas de pre‑procesado, evaluación, optimización y despliegue.
- Guía de troubleshooting, seguridad y escalabilidad.
1. Fundamentos de SVD para Sistemas de Recomendación
Partimos de una matriz R de dimensiones m × n (usuarios × ítems) donde cada celda contiene una valoración (por ejemplo, de 1 a 5). La descomposición SVD se expresa como:
R ≈ U Σ Vᵀ
donde:
U (m × k)– vectores latentes de usuarios.Σ (k × k)– valores singulares (importancia de cada factor).Vᵀ (k × n)– vectores latentes de ítems.
Al truncar a k factores (usualmente entre 20 y 200) reducimos el ruido y obtenemos predicciones mediante:
Ŕ = U Σ Vᵀ
Las entradas de Ŕ son estimaciones de la valoración que un usuario daría a un ítem que aún no ha evaluado.
2. Preparación de los Datos
Los datos reales son extremadamente escasos. Algunas tareas esenciales:
- Filtrado de usuarios/ítems con muy pocas interacciones.
- Normalización. Restar la media del usuario o del ítem para centrar la distribución.
- División de conjuntos.
train_test_split(ej. 80/20) usandosurprise.model_selection.
Ejemplo de pre‑procesado con pandas:
import pandas as pd
# Cargar dataset (formato: userId, movieId, rating, timestamp)
ratings = pd.read_csv('ml‑100k/u.data', sep='\t',
names=['user','item','rating','ts'])
# Filtrar usuarios con < 20 ratings y items con < 20 ratings
user_counts = ratings['user'].value_counts()
item_counts = ratings['item'].value_counts()
ratings = ratings[ratings['user'].isin(user_counts[user_counts>=20].index)]
ratings = ratings[ratings['item'].isin(item_counts[item_counts>=20].index)]
# Centering por usuario
ratings['rating_centered'] = ratings.groupby('user')['rating'].transform(lambda x: x - x.mean())
3. Implementación Básica de SVD con NumPy / SciPy
Esta versión es útil para comprender el algoritmo, aunque no es la más eficiente para matrices muy grandes.
import numpy as np
from scipy.sparse.linalg import svds
# Construir matriz de usuarios‑items (rellena ceros donde no hay rating)
m = ratings['user'].nunique()
n = ratings['item'].nunique()
R = np.zeros((m, n))
for row in ratings.itertuples():
R[row.user-1, row.item-1] = row.rating_centered
# Número de factores latentes
k = 50
# svds devuelve los k valores singulares más grandes
U, sigma, VT = svds(R, k=k)
Sigma = np.diag(sigma)
# Predicción
pred = np.dot(np.dot(U, Sigma), VT)
# Des‑centering (añadir la media del usuario)
user_means = ratings.groupby('user')['rating'].mean().values
pred += user_means[:, np.newaxis]
Para obtener las 10 mejores recomendaciones para el usuario 5:
user_id = 5 - 1 # índice 0‑based
user_pred = pred[user_id]
# Excluir ítems ya valorados
already_rated = ratings[ratings['user']==5]['item'].values - 1
user_pred[already_rated] = -np.inf
top_items = np.argsort(user_pred)[::-1][:10] + 1 # volver a 1‑based
print('Recomendaciones para usuario 5:', top_items)
4. Implementación con surprise (Recomendado para Producción)
La librería Surprise abstrae la mayor parte del pipeline y permite entrenar SVD con regularización y bias de forma eficiente.
from surprise import Dataset, Reader, SVD, accuracy
from surprise.model_selection import train_test_split
# Formato: user, item, rating
reader = Reader(line_format='user item rating', sep='\t', rating_scale=(1, 5))
data = Dataset.load_from_df(ratings[['user','item','rating']], reader)
trainset, testset = train_test_split(data, test_size=0.2, random_state=42)
# Entrenamiento con bias y regularización
svd = SVD(n_factors=100, n_epochs=30, biased=True, lr_all=0.005, reg_all=0.02)
svd.fit(trainset)
# Predicciones y métricas
predictions = svd.test(testset)
rmse = accuracy.rmse(predictions)
mae = accuracy.mae(predictions)
print(f'RMSE: {rmse:.4f} | MAE: {mae:.4f}')
# Función de recomendación
from collections import defaultdict
def get_top_n(predictions, n=10):
top_n = defaultdict(list)
for uid, iid, true_r, est, _ in predictions:
top_n[uid].append((iid, est))
# Ordenar y cortar
for uid, user_ratings in top_n.items():
user_ratings.sort(key=lambda x: x[1], reverse=True)
top_n[uid] = user_ratings[:n]
return top_n
top10 = get_top_n(predictions, n=10)
print('Top‑10 para el usuario 5:', top10[5])
Ventajas de surprise.SVD:
- Optimiza automáticamente los hiperparámetros mediante
GridSearchCV. - Implementación en Cython, mucho más rápida que NumPy puro.
- Manejo interno de bias de usuarios e ítems.
5. Comparativa: SVD vs ALS vs NMF vs Deep Learning
SVD (Factorización de Matriz)
- Ventajas: Interpretabilidad, buen desempeño en datasets medianos, bajo coste computacional para
k<200. - Desventajas: No escala bien a millones de usuarios sin paralelismo.
ALS (Alternating Least Squares)
- Ventajas: Fácil de paralelizar (Spark MLlib), maneja datos implícitos.
- Desventajas: Requiere más iteraciones y memoria que SVD.
NMF (Non‑Negative Matrix Factorization)
- Ventajas: Factores no negativos → interpretables como “preferencias positivas”.
- Desventajas: Convergencia más lenta y menos robusta a valores atípicos.
Deep Learning (Autoencoders, Neural CF)
- Ventajas: Captura relaciones no lineales, fácilmente extensible con side‑information.
- Desventajas: Necesita gran cantidad de datos, GPU y mayor tiempo de entrenamiento.
6. Métricas de Evaluación y Validación
Además de RMSE y MAE, en entornos de recomendación se usan métricas de ranking:
- Precision@K / Recall@K: Cuántos de los K ítems recomendados son relevantes.
- MAP (Mean Average Precision).
- NDCG (Normalized Discounted Cumulative Gain): Penaliza ítems relevantes en posiciones bajas.
Ejemplo de cálculo de Precision@10 con surprise:
from sklearn.metrics import precision_score
def precision_at_k(predictions, k=10):
user_est_true = defaultdict(list)
for uid, iid, true_r, est, _ in predictions:
user_est_true[uid].append((est, true_r))
precisions = []
for uid, ratings in user_est_true.items():
ratings.sort(key=lambda x: x[0], reverse=True)
top_k = ratings[:k]
# Consideramos rating >=4 como relevante
relevant = sum(1 for _, r in top_k if r >= 4)
precisions.append(relevant / k)
return np.mean(precisions)
print('Precision@10:', precision_at_k(predictions, k=10))
7. Buenas Prácticas, Optimización y Escalabilidad
Regularización y Bias
Siempre incluya bias_user y bias_item (p.e., surprise.SVD(biased=True)) para evitar sobre‑ajuste.
Selección de k
Realice una búsqueda de hiperparámetros (GridSearchCV de Surprise) con valores típicos: 20, 50, 100, 150.
Paralelismo
Para datasets > 10⁶ interacciones, considere:
- Implementar SVD en Spark (
spark.ml.recommendation.ALS) y usar la varianteimplicitPrefssi trabaja con clicks. - Utilizar
faissoAnnoypara búsqueda aproximada de vecinos cuando se necesita generar recomendaciones en tiempo real.
Persistencia del Modelo
Guarde los factores U, Σ y V en un almacén columnar (Parquet) o en Redis para consultas de baja latencia.
8. Seguridad, Privacidad y Consideraciones Éticas
- Anonimización: Elimine cualquier dato identificable antes de entrenar (hash de IDs).
- Protección contra ataques de inferencia: Limite la exposición de puntuaciones predichas mediante rate‑limiting y differential privacy si el modelo se expone como API.
- Bias de género/raza: Evalúe métricas de equidad (por ejemplo, disparity‑ratio) y ajuste los factores o añada regularización de fairness.
9. Despliegue en Producción
Un flujo típico:
- Ingesta de eventos (Kafka) → tabla de interacciones.
- Batch nightly: re‑entrenamiento del modelo SVD con Spark o con un script Python programado (Airflow).
- Persistencia de factores en un servicio de vectores (RedisVector, Milvus).
- API REST (FastAPI) que recibe
user_idy devuelve top‑N ítems usandonp.dotsobre los factores cargados.
Ejemplo de endpoint rápido con FastAPI:
from fastapi import FastAPI
import numpy as np, redis
app = FastAPI()
r = redis.Redis(host='localhost', port=6379, db=0)
U = np.load('U.npy') # shape (users, k)
V = np.load('V.npy') # shape (items, k)
@app.get('/recommend/{user_id}')
def recommend(user_id: int, n: int = 10):
user_vec = U[user_id]
scores = V @ user_vec # dot product
top_idx = np.argpartition(-scores, n)[:n]
return {'user': user_id, 'recommendations': top_idx.tolist()}
10. Futuro y Tendencias
Si bien SVD sigue siendo la referencia por su simplicidad y rendimiento, la integración con:
- Graph Neural Networks (GNN) para capturar relaciones de usuarios‑usuarios.
- Large Language Models (LLM) como “retrieval‑augmented recommendation”.
- Federated Learning para entrenar modelos sin centralizar datos sensibles.
Estas combinaciones prometen superar los límites de precisión de los enfoques puramente lineales.
Construye un Sistema de Recomendaciones con SVD en Python: Guía Completa y Práctica