Cerramos el módulo 4 con una constatación: en todo lo que llevamos de curso, el conocimiento del problema lo hemos aportado nosotros a mano. Diseñamos las reglas de optimización del módulo 2, medimos los pesos del grafo del módulo 3, justificamos los invariantes y las heurísticas del módulo 4. Pero Rutalia acumula millones de registros históricos de entregas —hora, zona, peso, distancia, retraso, incidencias— que contienen patrones que nadie ha escrito nunca como regla. En esta lección damos el cambio de paradigma: en lugar de programar las reglas, dejaremos que los algoritmos las aprendan de los datos. Veremos qué significa exactamente "aprender", qué tipos de aprendizaje existen, definiremos el dataset canónico de Rutalia que usaremos en todo el módulo, recorreremos el flujo de trabajo completo (entrenar, evaluar, no engañarse) y construiremos nuestro primer modelo de principio a fin: un clasificador k-NN implementado a mano para predecir si una entrega llegará tarde.
Contenido
- Reglas escritas vs reglas aprendidas
- Tipos de aprendizaje automático
- El dataset canónico de Rutalia
- El flujo de trabajo: entrenar, evaluar, generalizar
- Sobreajuste e infraajuste: memorizar no es aprender
- Validación cruzada y métricas (adelanto)
- Preparación de datos: variables categóricas y escalado
- Primer modelo completo: k-NN a mano y con scikit-learn
Reglas escritas vs reglas aprendidas
Comparemos cómo hemos resuelto problemas hasta ahora con cómo los resolveremos en este módulo:
| Aspecto | Módulos 1-4 (algoritmia clásica) | Módulo 5 (aprendizaje automático) |
|---|---|---|
| Origen del conocimiento | El programador escribe las reglas | Las reglas se extraen de datos históricos |
| Entrada del algoritmo | Una instancia del problema (un grafo, una lista) | Un conjunto de ejemplos con su resultado |
| Salida | La solución de esa instancia | Un modelo: una función que predice sobre casos nuevos |
| Garantías | A menudo exactas (Dijkstra da EL camino mínimo) | Estadísticas: aciertos aproximados, con error medible |
| Cuándo brilla | El problema tiene estructura conocida y formalizable | La regla es desconocida, difusa o cambia con los datos |
| Ejemplo Rutalia | "El camino ALM→HOS más rápido según la matriz 9×9" | "¿Cuánto tardará de verdad esta entrega un viernes a las 18h?" |
La pregunta "¿cuánto tardará esta entrega?" ilustra el cambio. Con Dijkstra (03-03) obtenemos el tiempo de trayecto según los pesos que medimos. Pero el tiempo real depende de factores que no están en el grafo: la hora punta, el peso del paquete (buscar aparcamiento con 20 kg no es lo mismo que con 200 g), si es viernes, si la zona MER tiene mercado ese día... Nadie en Rutalia sabe escribir esa fórmula. Los datos históricos, en cambio, la contienen.
Formalmente, en aprendizaje supervisado buscamos una función f tal que f(x) ≈ y, donde x es un vector de características (features) de un ejemplo e y es la respuesta conocida. El "algoritmo de aprendizaje" es el procedimiento que, dado un conjunto de pares (x, y), construye esa f. Fíjate: seguimos en un curso de algoritmos. Un algoritmo de aprendizaje es un algoritmo como cualquier otro —con su complejidad, sus estructuras de datos y sus invariantes— cuya salida es otra función.
Tipos de aprendizaje automático
| Tipo | ¿Hay etiqueta y? |
Objetivo | Ejemplo en Rutalia | Dónde |
|---|---|---|---|---|
| Supervisado — clasificación | Sí, categórica | Predecir una clase | ¿Llegará tarde esta entrega? (sí/no) | 05-02 |
| Supervisado — regresión | Sí, numérica | Predecir un valor continuo | ¿Cuántos minutos tardará? | 05-03 |
| No supervisado — clustering | No | Descubrir grupos en los datos | ¿Qué zonas se comportan igual? | 05-05 |
| Por refuerzo | Recompensa diferida | Aprender una política de acciones | Un agente que aprende rutas por prueba y error | (fuera del curso) |
El aprendizaje por refuerzo —un agente que actúa, recibe recompensas y ajusta su comportamiento— es la base de los sistemas que juegan a Go o controlan robots, pero queda fuera de este curso; nos concentraremos en supervisado (05-01 a 05-04) y no supervisado (05-05).
El dataset canónico de Rutalia
Igual que el módulo 2 tuvo su instancia canónica del TSP (óptimo 35,22 km) y el módulo 3 su grafo de 9 zonas, este módulo tendrá su dataset canónico de entregas. Cada fila es una entrega histórica:
| Columna | Tipo | Descripción |
|---|---|---|
distancia_km |
numérica | Distancia de ruta desde el hub hasta el destino |
peso_kg |
numérica | Peso del paquete |
zona |
categórica | Zona de destino: una de las 9 del grafo del módulo 3 (ALM, MER, EST, UNI, RIO, CEN, IND, HOS, PAR) |
hora_salida |
numérica (8-20) | Hora a la que el repartidor sale del hub |
dia_semana |
numérica (0=lunes … 6=domingo) | Día de la semana |
minutos_entrega |
numérica | Objetivo de regresión: minutos reales hasta entregar |
retraso |
binaria (0/1) | Objetivo de clasificación: 1 si superó los 45 minutos comprometidos |
Unas filas de muestra (clientes identificados solo por un id ficticio):
| cliente | distancia_km | peso_kg | zona | hora_salida | dia_semana | minutos_entrega | retraso |
|---|---|---|---|---|---|---|---|
| C-1042 | 3.2 | 1.5 | CEN | 18 | 4 | 52.3 | 1 |
| C-2077 | 1.1 | 0.4 | UNI | 10 | 1 | 19.8 | 0 |
| C-1583 | 6.8 | 12.0 | IND | 8 | 2 | 41.5 | 0 |
| C-3316 | 4.5 | 2.2 | MER | 13 | 5 | 61.0 | 1 |
| C-0921 | 2.3 | 0.9 | PAR | 11 | 6 | 24.1 | 0 |
Como no podemos adjuntar millones de filas a una lección, usaremos un generador sintético con semilla fija: todo el módulo trabajará exactamente con los mismos datos, reproducibles en tu máquina. El generador esconde una "verdad" (la fórmula que produce los minutos) que nuestros modelos intentarán descubrir sin conocerla. Nosotros sí la conocemos porque la escribimos, y eso nos permitirá juzgar si un modelo aprende bien.
import numpy as np
ZONAS = ["ALM", "MER", "EST", "UNI", "RIO", "CEN", "IND", "HOS", "PAR"]
# Factor de congestión por zona: CEN y MER son densas; IND y PAR, fluidas
FACTOR_ZONA = {"ALM": 1.0, "MER": 1.3, "EST": 1.1, "UNI": 0.9, "RIO": 1.0,
"CEN": 1.4, "IND": 0.8, "HOS": 1.2, "PAR": 0.85}
def generar_dataset(n=2000, semilla=42):
"""Genera n entregas históricas sintéticas de Rutalia."""
rng = np.random.default_rng(semilla)
distancia = rng.uniform(0.5, 8.0, n) # km
peso = np.round(rng.exponential(3.0, n), 1) # kg: mayoría, paquetes ligeros
zona = rng.choice(ZONAS, n)
hora = rng.integers(8, 21, n) # salidas de 8h a 20h
dia = rng.integers(0, 7, n)
# "Verdad oculta" que los modelos deberán aprender:
base = 5 + 6.0 * distancia # ~6 min/km + 5 min fijos
congestion = np.array([FACTOR_ZONA[z] for z in zona])
punta = 1 + 0.35 * np.isin(hora, [13, 14, 18, 19]) # horas punta
finde = 1 - 0.10 * (dia >= 5) # fin de semana más fluido
pesado = 1 + 0.02 * peso # aparcar con carga cuesta
ruido = rng.normal(0, 4.0, n) # variabilidad no explicable
minutos = np.maximum(base * congestion * punta * finde * pesado + ruido, 5.0)
retraso = (minutos > 45).astype(int)
return {"distancia_km": distancia, "peso_kg": peso, "zona": zona,
"hora_salida": hora, "dia_semana": dia,
"minutos_entrega": np.round(minutos, 1), "retraso": retraso}
datos = generar_dataset()
print(f"Entregas: {len(datos['retraso'])}, con retraso: {datos['retraso'].mean():.1%}")Puntos importantes del generador:
- Semilla 42:
default_rng(42)garantiza que tú y esta lección veáis los mismos números. La reproducibilidad es una práctica central en ML. - La verdad oculta es multiplicativa y no lineal (congestión × hora punta × peso): ningún modelo lineal la capturará perfectamente, lo cual dará juego en 05-03 y 05-04.
- El
ruidogaussiano representa lo irreducible: dos entregas idénticas nunca tardan exactamente lo mismo. Ningún modelo debería (ni podrá) predecirlo — intentarlo es, precisamente, sobreajustar. - Los retrasos son minoría (en torno al 15-20%): las clases están desbalanceadas, algo que explotaremos en 05-02 al hablar de métricas.
El flujo de trabajo: entrenar, evaluar, generalizar
El error clásico del recién llegado es evaluar el modelo con los mismos datos con los que lo entrenó. Un modelo que memoriza el conjunto de entrenamiento saca un 100% en ese examen... porque ya conocía las respuestas. Lo que importa es cómo se comporta con entregas que nunca ha visto: a eso se le llama generalizar.
El protocolo mínimo:
flowchart LR
A[Datos históricos] -->|barajar y partir| B[Entrenamiento 80%]
A --> C[Test 20%]
B --> D[Entrenar modelo]
D --> E[Modelo f]
C --> F[Evaluar f con datos NUNCA vistos]
E --> F
F --> G[Estimación honesta del error real]
def train_test_split_manual(X, y, test_frac=0.2, semilla=42):
"""Baraja los índices y separa entrenamiento y test."""
rng = np.random.default_rng(semilla)
idx = rng.permutation(len(y)) # permutación aleatoria de 0..n-1
corte = int(len(y) * (1 - test_frac))
tr, te = idx[:corte], idx[corte:]
return X[tr], X[te], y[tr], y[te]Barajar es esencial: si los datos vinieran ordenados por fecha o por zona, el test contendría solo un tipo de entregas y la evaluación quedaría sesgada. Y el conjunto de test es sagrado: no se toca hasta el final, ni para elegir parámetros ni para "echar un vistazo". Cada vez que una decisión tuya depende del test, el test deja der ser una estimación honesta.
Sobreajuste e infraajuste: memorizar no es aprender
Todo modelo tiene una "capacidad": cuánta complejidad puede representar. El equilibrio es delicado:
- Infraajuste (underfitting): el modelo es demasiado simple para el patrón. Ejemplo: predecir los minutos de entrega con la media global (~35 min para todos). Falla en entrenamiento y en test.
- Sobreajuste (overfitting): el modelo es tan flexible que memoriza el ruido del entrenamiento. Ejemplo extremo: una tabla hash (01-04) que guarda cada entrega histórica y devuelve sus minutos exactos. Error cero en entrenamiento, desastre con entregas nuevas.
Podemos visualizarlo con k-NN, el modelo que construiremos al final. k-NN con k=1 responde copiando la entrega histórica más parecida: memoriza, ruido incluido. Con k=n (todas las entregas) responde siempre la mayoría global: infraajusta. El buen k está en medio:
Error ▲ │ ● ← k=1: test alto (sobreajuste) │ ● ●● │ ●● ●●● Error de TEST (curva en U) │ ●●● ●●●●●●●●● │ ●●●●●●●●●●●● ← zona buena │ │ ○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○ Error de ENTRENAMIENTO └────────────────────────────────────▶ k creciente = menos capacidad
Con k=1 el error de entrenamiento es 0 (tu vecino más cercano eres tú mismo) pero el de test es alto. Al crecer k ambos se acercan hasta que, pasado el punto óptimo, los dos suben: el modelo ya no distingue nada. Esta curva en U del error de test aparece en todos los modelos del módulo; en 05-03 la formalizaremos como el trade-off sesgo-varianza.
Validación cruzada y métricas (adelanto)
Si el test es sagrado, ¿con qué datos elegimos k? Reservar otra partición (validación) funciona, pero desperdicia datos. La solución estándar es la validación cruzada de K pliegues (K-fold): partir el entrenamiento en K trozos, entrenar K veces dejando cada vez un trozo fuera como validación, y promediar los K errores.
flowchart TB
subgraph CV ["5-fold CV sobre el 80% de entrenamiento"]
R1["Ronda 1: [VAL][tr][tr][tr][tr]"]
R2["Ronda 2: [tr][VAL][tr][tr][tr]"]
R3["... hasta la ronda 5: [tr][tr][tr][tr][VAL]"]
end
R1 --> M["Media de los 5 errores → estimación estable"]
R2 --> M
R3 --> M
Cada ejemplo se usa K−1 veces para entrenar y 1 vez para validar: aprovechamos todos los datos y la estimación es más estable que la de una sola partición. El coste: entrenar K veces — un trade-off tiempo/fiabilidad muy del estilo de este curso.
Sobre métricas, cada problema tiene las suyas y las desarrollaremos donde tocan: matriz de confusión, precisión/recall/F1 y ROC para clasificación (05-02); MSE, RMSE, MAE y R² para regresión (05-03); inercia y silueta para clustering (05-05). Por ahora usaremos la más simple, la exactitud (accuracy): fracción de aciertos. Y adelantamos ya su trampa: si el 85% de las entregas de Rutalia son puntuales, un "modelo" que siempre responde "puntual" acierta el 85% sin haber aprendido nada. En 05-02 la destriparemos.
Preparación de datos: variables categóricas y escalado
Los algoritmos de ML operan con vectores numéricos, y nuestro dataset tiene una columna categórica (zona) y columnas numéricas de escalas muy distintas. Dos transformaciones imprescindibles:
One-hot encoding
zona no es un número. Codificarla como ALM=0, MER=1, ..., PAR=8 inventaría un orden y una distancia falsos (¿MER está "entre" ALM y EST? ¿PAR está "8 veces más lejos" que ALM?). La solución es el one-hot: una columna binaria por categoría.
def one_hot(valores, categorias):
"""Convierte un array categórico en una matriz binaria n × len(categorias)."""
m = np.zeros((len(valores), len(categorias)))
for j, cat in enumerate(categorias):
m[:, j] = (valores == cat) # columna j: ¿el ejemplo es de la categoría j?
return m
Z = one_hot(datos["zona"], ZONAS) # matriz n × 9, una columna por zonaAsí, la distancia entre dos zonas distintas cualesquiera es siempre la misma, sin orden espurio.
Escalado (y por qué importa para algoritmos de distancia)
Nuestro primer modelo, k-NN, decide por distancia euclídea entre entregas. Observa las escalas: distancia_km va de 0.5 a 8, pero hora_salida va de 8 a 20. Una diferencia de 6 horas (¡mediodía vs noche, crucial para el retraso!) pesa en la distancia lo mismo que 6 km. Y si midiéramos el peso en gramos, esa columna aplastaría a todas las demás. La geometría del espacio dependería de las unidades — una decisión arbitraria que el modelo no debería heredar.
La estandarización lo corrige: a cada columna se le resta su media y se divide por su desviación típica, dejándolas todas con media 0 y desviación 1.
def estandarizar(X_train, X_test):
"""Ajusta media y desviación SOLO con train y aplica a ambos."""
mu = X_train.mean(axis=0)
sigma = X_train.std(axis=0)
sigma[sigma == 0] = 1.0 # evita división por cero
return (X_train - mu) / sigma, (X_test - mu) / sigmaDetalle crucial: la media y la desviación se calculan solo con el entrenamiento. Si usáramos también el test, información del test se "filtraría" al modelo (data leakage) y la evaluación dejaría de ser honesta. Es la versión sutil del pecado de evaluar con datos de entrenamiento.
Primer modelo completo: k-NN a mano y con scikit-learn
k vecinos más cercanos (k-NN) es el algoritmo perfecto para empezar porque es pura algoritmia de este curso: distancias + selección de los k menores. La idea: para predecir si una entrega nueva llegará tarde, busca las k entregas históricas más parecidas y vota lo que ellas hicieron.
No hay "entrenamiento" real: el modelo es el dataset. Todo el trabajo ocurre al predecir. Para seleccionar los k menores usamos un heap (01-04): heapq.nsmallest corre en O(n log k), mejor que ordenar todo en O(n log n).
import heapq
def predecir_knn(X_train, y_train, x_nuevo, k=15):
"""Clasifica x_nuevo por votación de sus k vecinos más cercanos."""
# 1. Distancia euclídea de x_nuevo a TODOS los ejemplos (vectorizado)
difs = X_train - x_nuevo # broadcasting: n × d
dists = np.sqrt((difs ** 2).sum(axis=1))
# 2. Los k índices con menor distancia, vía heap: O(n log k)
vecinos = heapq.nsmallest(k, range(len(dists)), key=lambda i: dists[i])
# 3. Votación mayoritaria de sus etiquetas
return int(y_train[vecinos].sum() * 2 > k) # 1 si más de la mitad votó "retraso"Montemos el pipeline completo — codificar, partir, escalar, evaluar:
# 1. Matriz de features: numéricas + one-hot de zona
X_num = np.column_stack([datos["distancia_km"], datos["peso_kg"],
datos["hora_salida"], datos["dia_semana"]])
X = np.column_stack([X_num, one_hot(datos["zona"], ZONAS)]) # n × 13
y = datos["retraso"]
# 2. Partir ANTES de escalar (el escalado solo puede ver train)
X_tr, X_te, y_tr, y_te = train_test_split_manual(X, y)
X_tr_e, X_te_e = estandarizar(X_tr, X_te)
# 3. Evaluar sobre el test
aciertos = sum(predecir_knn(X_tr_e, y_tr, x, k=15) == yv
for x, yv in zip(X_te_e, y_te))
print(f"Exactitud k-NN manual: {aciertos / len(y_te):.3f}")
# 4. Referencia obligada: el modelo trivial que siempre dice la clase mayoritaria
print(f"Exactitud del trivial: {max(y_te.mean(), 1 - y_te.mean()):.3f}")Compara siempre con el modelo trivial: si tu k-NN no lo supera con claridad, no ha aprendido nada útil. Con este dataset, k-NN debería superarlo con holgura, porque el retraso depende fuertemente de distancia, zona y hora — y los vecinos cercanos en ese espacio comparten destino.
Y la versión con scikit-learn, la biblioteca estándar, que hace lo mismo con validaciones y optimizaciones industriales:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
esc = StandardScaler().fit(X_tr) # fit SOLO con train...
X_tr_e, X_te_e = esc.transform(X_tr), esc.transform(X_te) # ...transform a ambos
knn = KNeighborsClassifier(n_neighbors=15).fit(X_tr_e, y_tr)
print(f"Exactitud k-NN sklearn: {knn.score(X_te_e, y_te):.3f}")Fíjate en el patrón fit/transform/predict: fit aprende de los datos (los parámetros del escalador, o el modelo), transform/predict aplican lo aprendido. Toda la API de scikit-learn sigue este contrato, lo que ayuda a evitar fugas de información.
En este módulo mantendremos siempre la doble vía: implementar a mano la mecánica esencial (esto es un curso de algoritmos) y luego usar scikit-learn como herramienta de trabajo, verificando que ambos coinciden.
Errores Comunes y Consejos
- Evaluar con los datos de entrenamiento. Es el error número uno. Un 99% de exactitud "en train" no dice nada; solo cuenta el rendimiento sobre datos nunca vistos.
- Escalar antes de partir. Si calculas media y desviación con todo el dataset, filtras información del test al modelo. Orden correcto: partir → ajustar transformaciones con train → aplicar a ambos.
- Codificar categorías como enteros ordenados. ALM=0...PAR=8 inventa distancias inexistentes y confunde a cualquier algoritmo basado en distancia. Usa one-hot.
- Olvidar el modelo trivial de referencia. Con clases desbalanceadas, una exactitud del 85% puede significar exactamente cero aprendizaje. Calcula siempre qué saca el "siempre mayoría".
- No fijar semillas. Sin
random_state/semilla, cada ejecución da resultados distintos y es imposible depurar o comparar. Fija semillas en particiones y generadores. - Consejo: desconfía de los resultados demasiado buenos. Una exactitud del 100% casi siempre delata una fuga de datos (una feature que "contiene" la respuesta, o un test contaminado), no un modelo genial.
Ejercicios
-
La curva en U de k. Con el dataset canónico (semilla 42), evalúa el k-NN manual para
k ∈ {1, 3, 7, 15, 31, 61, 121, 501}sobre el conjunto de test. Imprime la exactitud de cadaky localiza la zona buena. ¿Qué pasa conk=1? ¿Y conk=501? Explica ambos extremos en términos de sobreajuste/infraajuste. -
El desastre de las unidades. Repite la evaluación de k-NN (
k=15) con dos variantes: (a) sin estandarizar nada, y (b) sin estandarizar y conpeso_kgconvertido a gramos (multiplica esa columna por 1000). Compara las tres exactitudes (estandarizado, crudo, gramos) y explica por qué la versión en gramos se hunde hacia el modelo trivial. -
Validación cruzada manual. Implementa
cv_5fold(X, y, k)que parta el conjunto de entrenamiento en 5 pliegues, entrene/evalúe 5 veces el k-NN manual y devuelva la exactitud media. Úsala para elegir el mejorkde{5, 15, 45}sin tocar el test, y solo al final evalúa elkelegido sobre el test.
Soluciones
Ejercicio 1:
for k in [1, 3, 7, 15, 31, 61, 121, 501]:
acc = np.mean([predecir_knn(X_tr_e, y_tr, x, k) == yv
for x, yv in zip(X_te_e, y_te)])
print(f"k={k:4d} exactitud={acc:.3f}")Con k=1 la exactitud de test baja respecto a la zona buena: cada predicción copia a un único vecino, ruido incluido (sobreajuste; sobre el propio train sacaría 1.0). Con k=501 se promedia sobre un tercio del dataset y el modelo tiende al voto de la mayoría global (infraajuste). Los valores intermedios (≈7-61) forman el valle de la U.
Ejercicio 2:
# (a) crudo: partir sin estandarizar
Xa_tr, Xa_te, ya_tr, ya_te = train_test_split_manual(X, y)
acc_crudo = np.mean([predecir_knn(Xa_tr, ya_tr, x, 15) == yv
for x, yv in zip(Xa_te, ya_te)])
# (b) peso en gramos
Xg = X.copy(); Xg[:, 1] *= 1000
Xg_tr, Xg_te, yg_tr, yg_te = train_test_split_manual(Xg, y)
acc_gramos = np.mean([predecir_knn(Xg_tr, yg_tr, x, 15) == yv
for x, yv in zip(Xg_te, yg_te)])
print(acc_crudo, acc_gramos)En crudo, hora_salida (rango ~12) domina sobre las columnas one-hot (rango 1) y el resultado ya se degrada. En gramos, la columna de peso presenta diferencias de miles mientras las demás varían en unidades: la distancia euclídea se convierte, en la práctica, en "diferencia de peso"; y como el peso apenas determina el retraso, k-NN cae hacia el modelo trivial. Moral: las unidades son arbitrarias y no deben decidir la geometría — por eso se estandariza.
Ejercicio 3:
def cv_5fold(X, y, k):
idx = np.random.default_rng(0).permutation(len(y))
pliegues = np.array_split(idx, 5)
accs = []
for i in range(5):
val = pliegues[i]
tr = np.concatenate([pliegues[j] for j in range(5) if j != i])
Xtr_e, Xval_e = estandarizar(X[tr], X[val]) # ¡escalar dentro del pliegue!
acc = np.mean([predecir_knn(Xtr_e, y[tr], x, k) == yv
for x, yv in zip(Xval_e, y[val])])
accs.append(acc)
return np.mean(accs)
mejor_k = max([5, 15, 45], key=lambda k: cv_5fold(X_tr, y_tr, k))
print("Mejor k por CV:", mejor_k)
# Solo ahora, una única vez, se evalúa mejor_k sobre el test.Nota el detalle: la estandarización se ajusta dentro de cada pliegue con su parte de entrenamiento — el mismo principio anti-fugas de siempre, aplicado recursivamente. El test se usa una sola vez, con el k ya decidido.
Conclusión
Hemos cruzado la frontera del curso: de escribir reglas a aprenderlas. Ahora sabes qué es un modelo (una función aprendida de ejemplos), qué tipos de aprendizaje existen, y sobre todo conoces el protocolo que lo sostiene todo: separar train y test, escalar sin fugas, validar en cruz y desconfiar de la exactitud sin contexto. Definimos el dataset canónico de Rutalia —2000 entregas sintéticas con semilla 42— que nos acompañará todo el módulo, y construimos un primer clasificador completo, k-NN, que en el fondo es algoritmia pura: distancias euclídeas y un heap para los k menores. Pero k-NN tiene límites serios (paga O(n·d) por cada predicción y sufre en alta dimensión), y la clasificación da para mucho más. En la siguiente lección, 05-02, exploraremos el catálogo de clasificadores —árboles de decisión, random forest, Naive Bayes, regresión logística— y aprenderemos a medirlos en serio, porque con un 85% de entregas puntuales, la exactitud es una métrica que engaña.
Algoritmos Avanzados
Módulo 1: Introducción a los Algoritmos Avanzados
- Conceptos Básicos y Notación
- Análisis de Complejidad
- Recursión y Programación Dinámica
- Estructuras de Datos Avanzadas
Módulo 2: Algoritmos de Optimización
- Programación Lineal
- Algoritmos de Optimización Combinatoria
- Backtracking y Branch and Bound
- Algoritmos Genéticos
- Optimización de Colonia de Hormigas
Módulo 3: Algoritmos en Grafos
- Representación de Grafos
- Búsqueda en Grafos: BFS y DFS
- Algoritmos de Caminos Mínimos
- Árboles de Expansión Mínima
- Algoritmos de Flujo Máximo
- Algoritmos de Emparejamiento en Grafos
Módulo 4: Algoritmos de Búsqueda y Ordenación
Módulo 5: Algoritmos de Aprendizaje Automático
- Introducción al Aprendizaje Automático
- Algoritmos de Clasificación
- Algoritmos de Regresión
- Redes Neuronales y Deep Learning
- Algoritmos de Clustering
Módulo 6: Casos de Estudio y Aplicaciones
- Optimización en la Industria
- Aplicaciones de Grafos en Redes Sociales
- Búsqueda y Ordenación en Grandes Volúmenes de Datos
- Aplicaciones de Aprendizaje Automático en la Vida Real
