Sesión 12: Tu primer modelo de Machine Learning en Python

Bloque 3 · Creación, Aplicaciones y Futuro
12 de 15
📅 21 de julio de 2026 ⏱ 40 minutos Ver código completo →

Introducción a la IA

Video: Tu primer modelo de Machine Learning en Python


Los videos y el texto de esta sesión son complementarios. Los videos amplían el contexto histórico y conceptual; el texto va a los mecanismos y te pone a interactuar con ellos. Encontrarás ideas en los videos que el texto no repite exactamente. ¡Disfruta de esta dinámica!

Introducción

En la sesión 3 describimos cómo funciona un algoritmo árbol de decisión: el modelo hace preguntas binarias [sí/no] sobre los datos hasta llegar a una respuesta.

Ahora vamos a hacer exactamente lo mismo, pero con Python 🐍. No es necesario memorizar el código, la idea es que puedas comprender qué hace cada línea y conectarla con conceptos que ya conoces.

Recuerda: sesión 3

Describimos el árbol de decisión (Random Forest) como el juego de las 20 preguntas: el modelo hace preguntas [sí = 1/no = 0] sobre los datos hasta llegar a una respuesta. Hoy, Python va a construir ese árbol automáticamente a partir de miles de ejemplos etiquetados.


Cinco pasos que todo modelo de Machine Learning recorre

Antes de pasar al código, vamos a familiarizarnos con el flujo de un modelo de machine learning (aprendizaje de máquinas). Cada sección del experimento corresponde a uno de estos pasos.

📦 Datos Los ejemplos etiquetados que el modelo usará para aprender.
✂️ Separar Dividir en dos grupos: uno para aprender (train = datos de entrenamiento), uno para evaluar (test = datos de prueba).
🏋️ Entrenar El modelo estudia el grupo de entrenamiento y construye reglas.
🔮 Predecir El modelo recibe ejemplos nuevos y decide qué cree que son, dado lo aprendido en el entrenamiento.
📊 Evaluar Comparamos las predicciones del modelo contra los datos etiquetados ("ground truth").

Estos cinco pasos son universales. Un modelo que detecta tumores en radiografías, uno que predice el precio de una casa o uno que reconoce tu voz siguen exactamente este mismo flujo.


Por qué dividimos los datos: una analogía sencilla

Imagina que estudias para un examen con una guía de estudio: cada pregunta tiene una respuesta. Si el día del examen el profesor usa exactamente las mismas preguntas, sacas diez — pero eso no significa que aprendiste. Solo memorizaste las respuestas.

Con los modelos de machine learning pasa exactamente lo mismo. Si entrenamos con las mismas imágenes que usamos para evaluar, el modelo puede “recordar” las respuestas correctas sin haber aprendido nada útil para imágenes nuevas.

La solución es siempre la misma: separar los datos antes de empezar.

Las 1,797 imágenes del dataset digits
Entrenamiento · 75% · 1,347 imágenes
Prueba · 25% · 450
El modelo puede ver estas imágenes muchas veces durante el entrenamiento.
Estas imágenes quedan bloqueadas hasta la evaluación final. El modelo nunca las ve antes.
La regla de oro

Nunca uses los datos de prueba durante el entrenamiento. La precisión que obtienes al final solo tiene sentido si el modelo no vio esas imágenes antes.


El experimento: reconocer dígitos escritos a mano

El conjunto de datos que usaremos se llama digits. Contiene 1,797 imágenes en escala de grises de dígitos escritos a mano (del 0 al 9), cada una de 8×8 píxeles. Es pequeño, claro y perfecto para un primer experimento.

Primero observa cómo se ven algunos de estos dígitos:

Ver el código de la galería
import os

os.environ["MPLCONFIGDIR"] = "/tmp/mplconfig"

import matplotlib.pyplot as plt
from sklearn.datasets import load_digits

digits = load_digits()

fig, axes = plt.subplots(2, 5, figsize=(8, 4))
for ax, image, label in zip(axes.flat, digits.images[:10], digits.target[:10]):
    ax.imshow(image, cmap="gray_r")
    ax.set_title(f"Etiqueta: {label}", fontsize=10)
    ax.axis("off")

plt.tight_layout()
plt.show()
Figure 1: Diez imágenes del dataset digits. Cada una tiene 8×8 píxeles y una etiqueta que indica qué número es.

Cada imagen es una cuadrícula de 8×8 = 64 valores numéricos entre 0 (blanco) y 16 (negro). Eso es lo que el árbol de decisión va a analizar para decidir qué dígito es cada imagen.


Google Colab — tu laboratorio

Para escribir y ejecutar Python no necesitas instalar nada. Google Colab es un cuaderno de código que corre directo en el navegador.

Google Colab mi_modelo.ipynb ● Conectado
+ Código + Texto Archivo Entorno de ejecución Ver

Sesión 12: vamos a construir un árbol de decisión que reconoce dígitos escritos a mano.

# Cargar datos y entrenar el árbol
from sklearn.datasets import load_digits
from sklearn.tree import DecisionTreeClassifier
print("Listo para reconocer números")
Listo para reconocer números
+ Agregar celda de código + Agregar celda de texto
Abrir Colab

colab.research.google.com → Necesitas una cuenta de Google. Crea un cuaderno nuevo desde “Archivo → Nuevo notebook” y copia el código de esta sesión.


Introducción breve a Python

No necesitas conocer todo el lenguage de Python para entender este experimento. Estas cuatro piezas son suficientes.

01 · Variables

Guardar algo con nombre

Una 'variable' guarda un valor para reutilizarlo después. digits, modelo y score son todas variables.

modelo = DecisionTreeClassifier()
02 · Funciones y métodos

Pedir que algo haga una tarea

Los paréntesis () indican que se ejecuta una acción. load_digits() carga datos. modelo.fit() entrena el árbol.

modelo.fit(X_train, y_train)
03 · Índices

Elegir un ejemplo concreto

X_test[12] significa: dame el ejemplo número 12 del grupo de prueba. Los corchetes [ ] sirven para elegir elementos.

imagen = X_test[12]
04 · print()

Mostrar un resultado en pantalla

Todo lo que quieras ver pasa por print(). Úsalo para inspeccionar la precisión, la predicción o cualquier variable.

print(score)

Del árbol de decisión al código: cada línea explicada

Con los cinco pasos del flujo de ML en mente y el Python mínimo claro, ya puedes leer el experimento completo. Haz clic en cualquier línea para ver qué hace exactamente.

mi_modelo.py ← haz clic en una línea para entenderla
1 # ── Importar herramientas ──────────────────────────────
2 from sklearn.datasets import load_digits
3 from sklearn.tree import DecisionTreeClassifier
4 from sklearn.model_selection import train_test_split
5
6 # ── Paso 1: Cargar los datos ───────────────────────────
7 digits = load_digits()
8
9 # ── Paso 2: Separar entrenamiento y prueba ─────────────
10 X_train, X_test, y_train, y_test = train_test_split(
11 digits.data, digits.target, test_size=0.25, random_state=42
12 )
13
14 # ── Paso 3: Crear el árbol de decisión ────────────────
15 modelo = DecisionTreeClassifier(max_depth=10, random_state=42)
16
17 # ── Paso 4: Entrenar (aquí ocurre el aprendizaje) ─────
18 modelo.fit(X_train, y_train)
19
20 # ── Paso 5: Predecir y evaluar ────────────────────────
21 prediccion = modelo.predict([X_test[12]])[0]
22 score = modelo.score(X_test, y_test)
23 print(f"Precisión: {score:.3f}")
Output » Precisión: 0.853
👆 Haz clic en cualquier línea para ver su explicación.

El experimento completo: el árbol en acción

Tres ventanas para ver el modelo funcionar: primero una predicción concreta, luego la estructura interna del árbol, y finalmente el rendimiento global.

1 · Una predicción

Ver el código del experimento completo
import os

os.environ["MPLCONFIGDIR"] = "/tmp/mplconfig"

import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier

digits = load_digits()

X_train, X_test, y_train, y_test = train_test_split(
    digits.data,
    digits.target,
    test_size=0.25,
    random_state=42,
    stratify=digits.target,
)

modelo = DecisionTreeClassifier(max_depth=10, random_state=42)
modelo.fit(X_train, y_train)
score = modelo.score(X_test, y_test)

indice = 12
real = y_test[indice]
prediccion = modelo.predict([X_test[indice]])[0]
resultado = "✓ Acertó" if prediccion == real else f"✗ Se equivocó (predijo {prediccion})"

fig, ax = plt.subplots(figsize=(3.5, 3.5))
ax.imshow(X_test[indice].reshape(8, 8), cmap="gray_r")
ax.set_title(f"Dígito real: {real}\n{resultado}", fontsize=11, pad=10)
ax.axis("off")
plt.tight_layout()
plt.show()

print(f"Precisión global: {score:.3f}  ({int(score * 450)}/450 imágenes de prueba)")
Figure 2: Una imagen del grupo de prueba con la predicción del árbol. El modelo puede acertar o equivocarse.
Precisión global: 0.836  (376/450 imágenes de prueba)

Una sola predicción dice poco: el árbol acierta el ~85% de las veces y se equivoca en el resto. Lo importante es entender el patrón de errores, que veremos a continuación con una matriz de confusión. Pero primero, visualicemos árbol.


2 · El árbol de decisión: las preguntas que hace el modelo

Aquí está lo que construyó Python. Cada nodo hace una pregunta sobre el valor de un píxel: si la respuesta es sí (≤ umbral), el árbol va a la izquierda; si no, va a la derecha. El árbol completo tiene 10 niveles — aquí mostramos los primeros 3 para que sean legibles.

Ver el código de la visualización del árbol
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np

# ── Estructura del árbol ─────────────────────────────────────────────────
tree_ = modelo.tree_

def node_info(nid):
    feat = int(tree_.feature[nid])
    thr  = tree_.threshold[nid]
    cl   = int(np.argmax(tree_.value[nid][0]))
    return cl, f"pix[{feat//8},{feat%8}] ≤ {thr:.0f}?", f"clase: {cl}"

n0   = 0
n1L  = tree_.children_left[n0];   n1R  = tree_.children_right[n0]
n2LL = tree_.children_left[n1L];  n2LR = tree_.children_right[n1L]
n2RL = tree_.children_left[n1R];  n2RR = tree_.children_right[n1R]

_, q0,  _    = node_info(n0)
_, q1L, _    = node_info(n1L)
_, q1R, _    = node_info(n1R)
_, _, c2LL   = node_info(n2LL)
_, _, c2LR   = node_info(n2LR)
_, _, c2RL   = node_info(n2RL)
_, _, c2RR   = node_info(n2RR)

# ── Colores ──────────────────────────────────────────────────────────────
CB  = "#6495ED"   # cornflower blue — bordes y texto
GRN = "#228B22"   # forest green   — ✓ Sí
AMB = "#F59E0B"   # amber          — ✗ No

# ── Posiciones ───────────────────────────────────────────────────────────
L0X, L0Y = 0.50, 0.86
L1X, L1Y = [0.25, 0.75], 0.52
L2X, L2Y = [0.12, 0.38, 0.62, 0.88], 0.13
DW, DH   = 0.27, 0.17   # nodo decisión
LW, LH   = 0.21, 0.16   # nodo hoja

# ── Figura — tamaño controlado por fig-width / fig-height del chunk ──────
fig, ax = plt.subplots()
ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")

def box(cx, cy, w, h, text, fs=9.5):
    ax.add_patch(mpatches.FancyBboxPatch(
        (cx-w/2, cy-h/2), w, h, boxstyle="round,pad=0.02",
        facecolor="white", edgecolor=CB, linewidth=2,
        transform=ax.transAxes, zorder=3))
    ax.text(cx, cy, text, ha="center", va="center", fontsize=fs,
            color=CB, transform=ax.transAxes, zorder=4, fontweight="bold")

def arrow(x1, y1, x2, y2, col):
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
        xycoords="axes fraction", textcoords="axes fraction",
        arrowprops=dict(arrowstyle="-|>", color=col, lw=1.8))

# ── Nodos ────────────────────────────────────────────────────────────────
box(L0X,    L0Y, DW, DH, q0,   fs=10.5)
box(L1X[0], L1Y, DW, DH, q1L,  fs=9.5)
box(L1X[1], L1Y, DW, DH, q1R,  fs=9.5)
for xi, lbl in zip(L2X, [c2LL, c2LR, c2RL, c2RR]):
    box(xi, L2Y, LW, LH, lbl, fs=11)

# ── Flechas ──────────────────────────────────────────────────────────────
arrow(L0X,    L0Y-DH/2, L1X[0], L1Y+DH/2+0.01, GRN)
arrow(L0X,    L0Y-DH/2, L1X[1], L1Y+DH/2+0.01, AMB)
arrow(L1X[0], L1Y-DH/2, L2X[0], L2Y+LH/2+0.01, GRN)
arrow(L1X[0], L1Y-DH/2, L2X[1], L2Y+LH/2+0.01, AMB)
arrow(L1X[1], L1Y-DH/2, L2X[2], L2Y+LH/2+0.01, GRN)
arrow(L1X[1], L1Y-DH/2, L2X[3], L2Y+LH/2+0.01, AMB)

# ── Etiquetas Sí / No ────────────────────────────────────────────────────
kw = dict(transform=ax.transAxes, ha="center", fontsize=9, fontweight="bold")
mid01 = (L0Y + L1Y) / 2 + 0.02
ax.text(L0X - 0.15, mid01, "✓ Sí", color=GRN, **kw)
ax.text(L0X + 0.15, mid01, "✗ No", color=AMB, **kw)

mid12 = (L1Y + L2Y) / 2 + 0.02
ax.text((L1X[0]+L2X[0])/2 - 0.02, mid12, "✓ Sí", color=GRN, **kw)
ax.text((L1X[0]+L2X[1])/2 + 0.02, mid12, "✗ No", color=AMB, **kw)
ax.text((L1X[1]+L2X[2])/2 - 0.02, mid12, "✓ Sí", color=GRN, **kw)
ax.text((L1X[1]+L2X[3])/2 + 0.02, mid12, "✗ No", color=AMB, **kw)

ax.set_title("Árbol de decisión: niveles 0–2 de 10", fontsize=11, pad=12)
plt.tight_layout()
plt.show()
Figure 3: Primeros 3 niveles del árbol de decisión. Cada nodo hace una pregunta sobre el valor de un píxel; según la respuesta (✓ Sí / ✗ No) el árbol toma una rama. Los nodos del fondo muestran la clase predicha en ese punto. El árbol completo tiene 10 niveles.
Cómo leer este diagrama

Cada recuadro azul es un punto de decisión. El árbol revisa el valor de un píxel específico de la imagen (en una cuadrícula de 8×8) y hace una pregunta binaria:

  • Flecha verde → Sí: el valor de ese píxel es menor o igual al umbral → el árbol sigue por la rama izquierda.
  • Flecha naranja → No: el valor es mayor → el árbol sigue por la rama derecha.

Los nodos del fondo son el resultado de seguir esas dos ramas dos veces: muestran el dígito (clase 0–9) que el modelo predice si una imagen llega hasta ese punto.

Este diagrama solo muestra los primeros 3 niveles. El árbol real tiene 10 en total — después de esas 10 preguntas, el modelo da su predicción final.

Esto es el árbol de decisión que describimos en la sesión 3, construido automáticamente por Python a partir de 1,347 ejemplos. Cada umbral (el número después de ≤) fue elegido por el algoritmo para separar mejor los dígitos durante el entrenamiento.


3 · ¿Dónde acierta y dónde se confunde? La matriz de confusión

Ver el código de la matriz de confusión
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay

fig, ax = plt.subplots()
ConfusionMatrixDisplay.from_estimator(
    modelo,
    X_test,
    y_test,
    ax=ax,
    colorbar=False,
    cmap="Blues",
)
ax.set_title("Matriz de confusión")
plt.tight_layout()
plt.show()
Figure 4: Cada fila es el dígito real; cada columna es lo que predijo el modelo. La diagonal brillante son aciertos. Los cuadros fuera de la diagonal son errores.
Cómo leer la matriz — celda por celda

La matriz tiene 10 filas y 10 columnas (una por cada dígito, del 0 al 9). Para leer cualquier celda usa esta regla:

Fila = el dígito que realmente era la imagen escrita a mano.

Columna = lo que el modelo predijo que era.

Número en la celda = cuántas veces ocurrió esa combinación exacta.


Los aciertos: la diagonal

Todas las celdas donde fila = columna son aciertos. El modelo predijo el dígito correcto.

  • Celda [fila 0, columna 0]: el modelo vio imágenes del dígito 0 y predijo 0 ✓. Si el número es 41, eso pasó 41 veces.
  • Celda [fila 1, columna 1]: vio un 1 y predijo 1 ✓.
  • Cuanto más brillante (más azul) es la diagonal, mejor está funcionando el modelo en ese dígito.

Los errores: todo lo que está fuera de la diagonal

Cada celda fuera de la diagonal es un tipo de error específico.

  • Celda [fila 1, columna 0]: el modelo vio una imagen del dígito 1 pero predijo 0 ✗. Si el número es 0, ese error nunca ocurrió.
  • Celda [fila 3, columna 8] (por ejemplo): vio un 3, pero lo confundió con un 8 ✗.
  • Cuanto más brillante sea una celda fuera de la diagonal, más frecuente es ese error.

Cómo encontrar el error más común del modelo:

  1. Ignora la diagonal brillante.
  2. Busca la celda más intensa (más azul) que esté fuera de esa diagonal.
  3. Lee su fila (dígito real) y su columna (dígito que predijo el modelo).
  4. Pregúntate: ¿se parecen visualmente esos dos dígitos escritos a mano? ¿Por qué crees que el árbol los confundió?

Actividad: tres experimentos

Vamos a intentar mejorar nuestro modelo. Queremos que acierte más del ~84% de las veces. Lo que intentaremos hacer es cambiar los parámetros del modelo. A cada modificación le denominaremos experimento. Cada experimento tiene un objetivo claro — no hace falta dominar todo el código completo para aprender algo valioso.


Experimento A · ¿Cuántas preguntas necesita el árbol?

El parámetro max_depth controla el número máximo de preguntas que puede hacer el árbol antes de dar una respuesta. Busca la línea DecisionTreeClassifier(max_depth=10, ...) y prueba estos valores uno por uno, corriendo el código completo cada vez:

Valor Qué significa Precisión esperada
max_depth=2 Solo 2 preguntas — árbol muy simple ~50–65 %
max_depth=5 5 preguntas — árbol moderado ~75–80 %
max_depth=10 El original ~85 %
max_depth=None Sin límite — el árbol crece todo lo que pueda ¿sube o baja?
Pista: el peligro de un árbol sin límite

Un árbol sin max_depth puede memorizar cada imagen de entrenamiento en vez de aprender reglas generales — como estudiar copiando las respuestas de la guía sin entender nada. En entrenamiento funciona perfectamente; con imágenes nuevas, falla. Eso se llama sobreajuste.

Pregunta: ¿Por qué un árbol con solo 2 preguntas acierta tan poco? ¿Y por qué quitarle el límite del todo tampoco mejora mucho?


Experimento B · Dale nombre a un error de la matriz

La matriz de confusión te dice qué pares de dígitos confunde el modelo. Ahora vas a encontrar ese error en una imagen :

  1. Mira la matriz y localiza la celda más brillante fuera de la diagonal. Anota: ¿qué dígito (fila) se confunde con qué predicción (columna)?
  2. Busca la línea indice = 12 y cámbiala por números entre 0 y 449 hasta que el modelo cometa ese error exacto — que prediga el dígito incorrecto que encontraste en el paso 1.
  3. Observa la imagen: ¿puedes ver a simple vista por qué el árbol se confundió?

Pregunta: ¿Esos dos dígitos se parecen visualmente cuando alguien los escribe a mano? ¿Crees que un humano también los confundiría?


Experimento C · ¿Qué pasa si el árbol aprende con menos datos?

Busca la línea test_size=0.25 y cámbiala por test_size=0.5. Esto mueve el 50 % de las imágenes al grupo de prueba — el árbol aprende solo con las 898 imágenes restantes en vez de las 1,347 originales.

Pregunta: ¿Sube o baja la precisión? ¿Por qué crees que tener menos imágenes de entrenamiento cambia el resultado?


IA como copiloto del experimento

Úsalo ya

Explícame este bloque

Pega unas pocas líneas y pide que te las traduzca a lenguaje natural. Más útil que buscar en Google.

"Explícame qué hacen estas líneas como si nunca hubiera programado: modelo = DecisionTreeClassifier(max_depth=10), modelo.fit(X_train, y_train), score = modelo.score(X_test, y_test)"
Úsalo ya

Tengo un error

Pega el mensaje exacto del error y tu código. La IA puede identificar qué salió mal mucho más rápido que intentarlo a ciegas.

"Me salió este error en Python: NameError: name 'X_train' is not defined. Aquí está mi código completo. ¿Qué estoy haciendo mal?"
Úsalo con criterio

¿Qué pasa si…?

Cuando ya entiendes el experimento, puedes preguntarle a la IA qué efecto tendrá cambiar un parámetro antes de correrlo.

"En mi árbol de decisión, si cambio max_depth de 10 a 2, ¿qué efecto esperas en la precisión y por qué? Explícalo en términos simples."

Qué aprendiste de ML y qué aprendiste de Python

De machine learning

  • Un modelo aprende de ejemplos etiquetados — nadie le programó las reglas a mano.
  • La separación entrenamiento/prueba es la condición mínima para que la precisión tenga significado. Sin ella, no sabes si el modelo aprendió o memorizó.
  • La profundidad del árbol es un un arma de doble filo: muy baja = no aprende lo suficiente, sin límite = memoriza en vez de generalizar. El punto óptimo está en el medio.
  • La matriz de confusión te dice qué errores comete el modelo, no solo cuántos — es más útil que la precisión sola.

De Python

  • Con seis líneas de sklearn puedes entrenar un clasificador que reconoce el ~85 % de imágenes de dígitos que nunca ha visto.
  • Un solo parámetro (max_depth, test_size) puede cambiar el resultado por completo.
  • Modificar un experimento existente ya es programar, no hace falta empezar desde cero.

Recursos para explorar más sobre el tema

🧪

Google Colab

Google

Cuadernos de Python en el navegador, sin instalar nada. El lugar más fácil para empezar a experimentar.

Gratis En el navegador
🐍

Kaggle Learn: Python

Kaggle

Lecciones cortas con ejercicios interactivos en el navegador. El mejor punto de partida para aprender Python desde cero.

Gratis Principiante
🤖

Kaggle Learn: Intro to ML

Kaggle

Aprende más sobre modelos de ML. Extiende exactamente lo que hicimos hoy en esta sesión.

Gratis Principiante
🎓

CS50P — Python

Harvard / edX

Curso completo de Python para principiantes, estructurado y riguroso. Para cuando quieras ir más allá de los experimentos.

Gratis

La idea central de esta sesión

Idea central · Sesión 12

Tu primer modelo en Python no aprendió porque alguien le escribió reglas a mano, sino porque encontró patrones en ejemplos etiquetados. Lo importante de esta sesión no fue memorizar código, sino entender que entrenar, probar y ajustar un modelo también es una forma de pensar.