Ejercicios — Inteligencia Artificial

Mezclá teoría (entender qué hace cada algoritmo) con práctica (correrlos en Python con scikit-learn / numpy).


Cap. 1 — Búsqueda y heurística

1.1 — BFS vs DFS (básico)

Para el grafo:

A - B - D
|   |
C - E - F

a) Listá el orden de visita con BFS desde A. b) Listá el orden de visita con DFS desde A.

✅ Solución

a) BFS: A, B, C, D, E, F.

b) DFS: A, B, D, E, C, F (depende del orden de los vecinos en la lista; este es uno válido).

1.2 — Heurística admisible (intermedio)

A* es óptimo si la heurística h(n)h(n) es admisible: h(n)h(n)h(n) \leq h^(n) para todo nn, donde hh^ es el costo real al objetivo. ¿Cuál de estas heurísticas es admisible para encontrar el camino más corto en una cuadrícula con movimientos de 1 paso a 4 vecinos?

a) h1h_1 = distancia Manhattan. b) h2h_2 = distancia Euclidiana. c) h3h_3 = 22 \cdot distancia Manhattan.

✅ Solución
  • a) Manhattan: admisible y consistente. Es el mínimo de pasos posibles si solo se mueve en 4 direcciones.
  • b) Euclidiana: admisible (siempre \leq Manhattan), pero subestima demasiado — guía peor que Manhattan en grillas con 4 movimientos.
  • c) 2×2 \times Manhattan: NO admisible. Sobreestima → A* puede dar caminos no óptimos.

Lección: admisibilidad garantiza optimalidad pero sobrestimar rompe la garantía.


Cap. 2 — Aprendizaje supervisado

2.1 — Train/test split (básico)

Cargá el dataset Iris de scikit-learn, partilo 80/20 con stratify, entrená un KNN (k=5k=5) y reportá accuracy.

✅ Solución
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target,
    test_size=0.20, stratify=iris.target, random_state=42
)
clf = KNeighborsClassifier(n_neighbors=5).fit(X_train, y_train)
print(clf.score(X_test, y_test))   # ~0.97 típico

stratify=y mantiene la proporción de clases. Sin él, podrías quedarte con 0 ejemplos de una clase en test.

2.2 — Identificar overfitting (intermedio)

Tu modelo da 99% de accuracy en train y 70% en test. ¿Qué está pasando? ¿Qué hacés?

✅ Solución

Diagnóstico: overfitting. El modelo memorizó train, no generaliza.

Soluciones (en orden de costo):

  1. Más datos (siempre la mejor opción cuando se puede).
  2. Modelo más simple (menos profundidad en árbol, kk más alto en KNN, regularización en regresión).
  3. Validación cruzada para detectarlo antes.
  4. Data augmentation (en visión/NLP).
  5. Regularización (L1, L2, dropout).
  6. Early stopping (en redes).

2.3 — Validación cruzada (avanzado)

Implementá k-fold cross-validation a mano (sin cross_val_score) sobre Iris con un árbol de decisión.

✅ Solución
import numpy as np
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import KFold

iris = load_iris()
X, y = iris.data, iris.target

kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = []
for train_idx, test_idx in kf.split(X):
    clf = DecisionTreeClassifier(random_state=42)
    clf.fit(X[train_idx], y[train_idx])
    scores.append(clf.score(X[test_idx], y[test_idx]))

print(f"Media: {np.mean(scores):.3f}, std: {np.std(scores):.3f}")

Reportar media ± std es más informativo que un solo número.


Cap. 3 — Redes neuronales

3.1 — Forward pass a mano (intermedio)

Una red 2-2-1 con activación sigmoide y pesos:

Calculá la salida para input x=(1,2)x = (1, 2).

✅ Solución

Capa oculta: h=σ(W1Tx+b1)h = \sigma(W_1^T x + b_1).

W1Tx=(0.51+0.82, 0.31+0.12)=(2.1, 0.1)W_1^T x = (0.5 \cdot 1 + 0.8 \cdot 2,\ -0.3 \cdot 1 + 0.1 \cdot 2) = (2.1,\ -0.1).

h=(σ(2.1), σ(0.1))(0.891, 0.475)h = (\sigma(2.1),\ \sigma(-0.1)) \approx (0.891,\ 0.475).

Salida: y=σ(W2Th)=σ(1.20.8910.70.475)=σ(1.0690.333)=σ(0.737)0.676y = \sigma(W_2^T h) = \sigma(1.2 \cdot 0.891 - 0.7 \cdot 0.475) = \sigma(1.069 - 0.333) = \sigma(0.737) \approx 0.676.

3.2 — Curva de pérdida (básico)

¿Qué problema sugiere cada curva durante el entrenamiento?

a) Pérdida train baja, pérdida val baja. b) Pérdida train baja, pérdida val alta. c) Pérdida train alta, pérdida val alta. d) Pérdida train baja, val baja al inicio, después val sube.

✅ Solución

a) Modelo bueno. Generaliza bien. b) Overfitting. Memoriza train. c) Underfitting. Modelo demasiado simple (o LR mal configurado). d) Comienzo de overfitting. Activá early stopping (parar cuando val deja de bajar).


Cap. 5 — Ética y aplicaciones

5.1 — Auditoría de sesgo (intermedio)

Tu modelo de aprobación de créditos tiene 85 % accuracy global. Por subgrupo:

¿Hay sesgo? ¿Qué métricas adicionales pedirías para confirmarlo?

✅ Solución

Sí, evidencia clara de sesgo demográfico. Pero accuracy sola es insuficiente.

Métricas adicionales:

  1. Demographic parity: ¿la tasa de aprobación es similar entre grupos? Pueden tener accuracy similar pero aprobar mucho menos a un grupo.
  2. Equal opportunity: ¿el true positive rate (dado que merecen crédito, qué porcentaje aprueba) es similar?
  3. Equalized odds: TPR y FPR similares.

Decidir cuál métrica priorizar es una decisión ética + de producto, no solo técnica. Cada definición de fairness puede ser incompatible con otra (Chouldechova 2017).


Cap. 6 — LLMs y transformers

6.1 — Tokenización (básico)

Usá tiktoken (o el tokenizer del LLM que tengas a mano) para contar los tokens de:

a) "pupusa con queso y curtido" b) "Programación Orientada a Objetos en El Salvador" c) Un párrafo de 100 palabras de tu propio texto en español.

✅ Solución
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o-mini")

textos = [
    "pupusa con queso y curtido",
    "Programación Orientada a Objetos en El Salvador",
]
for t in textos:
    print(t, "→", len(enc.encode(t)), "tokens")

Típicamente: ~1.3 tokens por palabra en español. Lenguajes con menos representación gastan más tokens. Si tu app facturara por token, pasar de inglés a español duplica el costo aproximadamente.

6.2 — Prompt engineering (intermedio)

Un usuario te pide "extraer el nombre, edad y email" de un texto libre. Diseñá:

a) Zero-shot prompt. b) Few-shot con 2 ejemplos. c) Con chain-of-thought. d) Con output estructurado (JSON).

✅ Solución
zero_shot = """Extraé nombre, edad y email del texto.

Texto: {texto}"""

few_shot = """Extraé nombre, edad y email del texto.

Ejemplo 1:
Texto: "Hola, soy Ana, tengo 25 años. Mi correo es ana@ejemplo.com"
Salida: nombre=Ana, edad=25, email=ana@ejemplo.com

Ejemplo 2:
Texto: "Bryan (32) – bryan@x.com"
Salida: nombre=Bryan, edad=32, email=bryan@x.com

Texto: {texto}"""

cot = """Extraé nombre, edad y email del texto.
Pensá paso a paso:
1. Identificá el nombre (suele estar al principio).
2. Buscá un número que represente edad (cerca de "años" o entre paréntesis).
3. Buscá patrón email (algo@algo.com).

Texto: {texto}"""

structured = """Extraé información del texto y devolvé SOLO un JSON válido.

Esquema:
{
  "nombre": string,
  "edad": int,
  "email": string
}

Texto: {texto}"""

Tip: para producción, usá la API de structured outputs del proveedor (OpenAI/Anthropic) en lugar del prompt-only. Garantiza JSON válido.


Reto integrador

R.1 — Modelo + auditoría + RAG

Construí un sistema completo en un Jupyter notebook:

  1. Dataset: UCI Adult (predecir > 50k USD/año) o COMPAS (riesgo reincidencia). Ambos son los datasets clásicos de auditoría de fairness.
  2. Modelo base: Random Forest con cross-validation. Reportar accuracy, F1.
  3. Auditoría: demographic parity y equal opportunity por subgrupo (raza, género, edad). Mostrar disparidad numérica.
  4. Mitigación: aplicá una técnica (reweighting, threshold per group, o adversarial debiasing). Comparar antes/después.
  5. Explicabilidad: SHAP sobre 5 predicciones individuales — qué features pesan más.
  6. Card del modelo: estilo Hugging Face — datasource, intended use, limitations, fairness considerations.
  7. (Bonus) RAG: buscador semántico sobre la documentación del modelo (las cards de las features).
💡 Pista de arquitectura

Librerías:

  • pandas, scikit-learn, numpy para modelo.
  • aif360 o fairlearn para auditoría/mitigación.
  • shap para explicabilidad.
  • sentence-transformers + chromadb para RAG.

Estructura del notebook: 1 sección por punto, cada una con visualización y comentario.

✅ Criterio de evaluación

Un revisor sin background técnico debería leer tu notebook (especialmente la "card") y entender:

  • Qué predice el modelo.
  • Cuándo confiar.
  • Cuándo NO confiar.
  • Quién paga el costo si el modelo se equivoca.

Es portafolio fuerte para roles de IA aplicada y data science responsable.


Para más práctica: Kaggle competitions con kernels públicos, fast.ai cursos.