24. Evaluación de modelos de visión por computadora

24.1 Introducción

Entrenar un modelo no basta. Después de entrenarlo, necesitamos responder una pregunta fundamental: ¿qué tan bueno es realmente? Esa pregunta nos lleva al tema de la evaluación.

En visión por computadora, evaluar un modelo significa medir su desempeño de una manera que sea útil, honesta y alineada con el problema real. No alcanza con mirar si la pérdida baja durante el entrenamiento. Tampoco alcanza, muchas veces, con una sola métrica global.

En este tema veremos cómo evaluar clasificadores de imágenes, qué métricas son más comunes, cuándo pueden engañarnos y cómo interpretar los resultados de manera más seria.

24.2 ¿Por qué la evaluación es tan importante?

Porque un modelo puede parecer bueno durante entrenamiento y sin embargo comportarse mal en datos no vistos. También puede tener una accuracy aceptable pero fallar justo en la clase más importante del problema.

Evaluar bien sirve para:

  • Estimar capacidad de generalización.
  • Comparar modelos o configuraciones.
  • Detectar sesgos o debilidades.
  • Entender si el sistema es útil en la práctica.
Una evaluación pobre puede hacer que un modelo mediocre parezca bueno. Una evaluación seria revela tanto fortalezas como debilidades reales.

24.3 Entrenamiento, validación y prueba

Para evaluar correctamente, conviene distinguir tres subconjuntos:

  • Train: se usa para ajustar los pesos.
  • Validation: se usa para tomar decisiones durante desarrollo.
  • Test: se reserva para la evaluación final.

Esta separación evita que terminemos optimizando el modelo sobre los mismos datos con los que luego pretendemos evaluarlo.

24.4 Qué mide una métrica

Una métrica es una forma numérica de resumir el desempeño del modelo. Pero cada métrica destaca un aspecto distinto del comportamiento.

Por eso no conviene pensar que existe una única cifra mágica que diga toda la verdad. Una métrica puede ser útil para un problema y poco informativa para otro.

24.5 Accuracy

La métrica más simple y conocida en clasificación es la accuracy. Mide qué fracción de ejemplos fue clasificada correctamente.

Su fórmula conceptual es:

accuracy = aciertos / total

Si de 100 imágenes el modelo acierta 87, la accuracy es 0.87 o 87%.

24.6 Ventajas de accuracy

La accuracy es útil porque:

  • Es fácil de entender.
  • Es fácil de calcular.
  • Sirve como primera referencia general.

En problemas balanceados y relativamente simples, puede ser una métrica bastante razonable.

24.7 Limitaciones de accuracy

El problema es que la accuracy puede resultar engañosa cuando las clases están desbalanceadas. Imaginemos un dataset donde el 95% de las imágenes pertenecen a la misma clase. Un modelo que predice siempre esa clase tendrá 95% de accuracy y, sin embargo, será prácticamente inútil para detectar las clases minoritarias.

Por eso, especialmente en problemas reales, conviene complementar accuracy con otras métricas.

24.8 Matriz de confusión

Una herramienta muy valiosa es la matriz de confusión. Esta matriz muestra cuántos ejemplos de cada clase real fueron predichos como cada clase posible.

En vez de resumir todo en una sola cifra, la matriz permite ver dónde se equivoca el modelo.

Esto es especialmente útil cuando queremos responder preguntas como:

  • ¿Qué clases confunde con más frecuencia?
  • ¿Hay una clase que casi nunca reconoce?
  • ¿El error está concentrado en pocas categorías?

24.9 Interpretación de la matriz de confusión

En una matriz de confusión, normalmente:

  • Las filas representan la clase real.
  • Las columnas representan la clase predicha.

La diagonal principal contiene los aciertos. Los valores fuera de la diagonal muestran confusiones entre clases.

Cuanto más concentrada esté la matriz en la diagonal, mejor estará funcionando el modelo.

24.10 Precisión y recall

Cuando una clase es especialmente importante, suelen aparecer dos métricas clave: precision y recall.

En un contexto binario:

  • Precision: de los ejemplos que el modelo marcó como positivos, cuántos eran realmente positivos.
  • Recall: de los positivos reales, cuántos fueron detectados por el modelo.

Estas dos métricas capturan errores distintos y por eso conviene distinguirlas.

24.11 Intuición de precision

La precision responde a la pregunta: “cuando el modelo dice que sí, ¿cuánto puedo confiar en eso?”.

Si la precision es baja, significa que el modelo está produciendo muchos falsos positivos.

Esto es importante en problemas donde una alarma incorrecta tiene costo alto.

24.12 Intuición de recall

El recall responde a la pregunta: “de todo lo que realmente debía detectar, ¿cuánto logró encontrar?”.

Si el recall es bajo, significa que el modelo deja escapar muchos positivos reales.

Esto es crucial en problemas donde omitir un caso importante tiene consecuencias graves.

24.13 F1-score

El F1-score combina precision y recall en una sola medida. No reemplaza su interpretación individual, pero puede servir como resumen cuando queremos equilibrar ambos aspectos.

Un F1 alto sugiere que el modelo no solo detecta bastante, sino que además comete relativamente pocos falsos positivos.

24.14 Evaluación multiclase

En clasificación multiclase, accuracy sigue siendo útil, pero precision, recall y F1 también pueden calcularse por clase. Esto permite estudiar el comportamiento del modelo de forma más detallada.

Por ejemplo, puede ocurrir que el modelo funcione muy bien en tres clases y muy mal en una cuarta. Una única accuracy global podría ocultar ese problema.

24.15 Macro, micro y weighted

Cuando se calculan métricas multiclase, suelen aparecer distintas formas de agregación:

  • Macro: promedia todas las clases por igual.
  • Micro: combina todas las decisiones globalmente.
  • Weighted: pondera según la cantidad de ejemplos por clase.

La elección depende del problema y del nivel de importancia que queramos dar a las clases minoritarias.

24.16 La pérdida también importa

Aunque solemos mirar accuracy y otras métricas, la pérdida sigue siendo una señal importante. Dos modelos pueden tener accuracy parecida pero pérdidas diferentes. Eso puede reflejar diferencias en la confianza de sus predicciones.

Por eso conviene observar tanto la pérdida como las métricas finales de clasificación.

24.17 Validación durante entrenamiento versus evaluación final

Durante el desarrollo del modelo solemos mirar la validación al final de cada época. Eso sirve para tomar decisiones: cambiar hiperparámetros, aplicar early stopping, guardar el mejor modelo, etc.

Pero la evaluación final ideal se hace sobre el conjunto de test, que debe permanecer lo más “intacto” posible durante el proceso de diseño.

24.18 ¿Qué significa evaluar de forma honesta?

Evaluar de forma honesta significa medir el modelo sobre datos que no hayan influido en las decisiones de desarrollo. Si usamos una y otra vez el conjunto de test para ajustar el sistema, ya no estamos midiendo generalización real, sino adaptación indirecta a ese test.

El test no debe convertirse en otra fuente de ajuste. Su valor está en ser una referencia final lo más imparcial posible.

24.19 Curvas y seguimiento temporal

Además de métricas finales, es útil mirar la evolución de entrenamiento y validación a lo largo de las épocas. Eso ayuda a detectar:

  • Aprendizaje progresivo.
  • Estancamiento.
  • Overfitting.
  • Problemas de optimización.

La evaluación, entonces, no es solo una foto final: también es una observación del proceso.

24.20 Un ejemplo práctico de evaluación

En lugar de usar datos sintéticos, en este tema conviene trabajar con un problema clásico y real: MNIST, el dataset de dígitos manuscritos.

La idea será entrenar una CNN pequeña y luego calcular:

  • Loss de test.
  • Accuracy.
  • Matriz de confusión.
  • Reporte de clasificación con precision, recall y F1.

Así podremos ver cómo se implementa una evaluación más seria que simplemente imprimir un porcentaje de aciertos, pero ahora sobre imágenes reales de los números del 0 al 9.

24.21 Herramientas útiles para evaluar

En Python, una combinación muy práctica es:

  • PyTorch para ejecutar el modelo.
  • scikit-learn para matriz de confusión y reporte de clasificación.

Por ejemplo, funciones como confusion_matrix y classification_report facilitan mucho el análisis.

24.22 Ejemplo completo de evaluación

Este ejemplo descarga MNIST, entrena brevemente una CNN pequeña y luego la evalúa sobre el conjunto de test:

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from sklearn.metrics import confusion_matrix, classification_report


class CNNPequena(nn.Module):
    def __init__(self, num_clases=10):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, num_clases)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = torch.flatten(x, start_dim=1)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
transform = transforms.ToTensor()

train_dataset = datasets.MNIST(
    root="./data",
    train=True,
    download=True,
    transform=transform
)

test_dataset = datasets.MNIST(
    root="./data",
    train=False,
    download=True,
    transform=transform
)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

model = CNNPequena(num_clases=10).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# Entrenamiento breve
for epoch in range(2):
    model.train()
    for images, labels in train_loader:
        images = images.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

# Evaluación
model.eval()
test_loss = 0.0
correct = 0
total = 0

y_true = []
y_pred = []

with torch.no_grad():
    for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)
        loss = criterion(outputs, labels)

        test_loss += loss.item() * images.size(0)

        preds = outputs.argmax(dim=1)
        correct += (preds == labels).sum().item()
        total += labels.size(0)

        y_true.extend(labels.cpu().numpy())
        y_pred.extend(preds.cpu().numpy())

avg_test_loss = test_loss / total
test_acc = correct / total

print(f"Test loss: {avg_test_loss:.4f}")
print(f"Test accuracy: {test_acc:.4f}")

print("\nMatriz de confusion:")
print(confusion_matrix(y_true, y_pred))

print("\nReporte de clasificacion:")
print(classification_report(y_true, y_pred, digits=4, zero_division=0))

Este ejemplo permite observar no solo una accuracy final, sino también cómo se distribuyen los errores entre los dígitos del 0 al 9.

24.23 Cómo interpretar un resultado como este

Si al ejecutar el programa aparece una salida como esta:

Test loss: 1.3795
Test accuracy: 0.9720

Matriz de confusion:
[[ 970    0    1    0    0    1    3    1    4    0]
 [   0 1128    2    1    0    1    1    0    2    0]
 [   6    2 1001    4    2    0    3    8    6    0]
 [   1    0    8  972    0    9    0    6    9    5]
 [   1    0    2    0  947    0    6    1    2   23]
 [   4    1    0   12    1  857    7    1    7    2]
 [   7    2    0    0    4    7  935    0    3    0]
 [   1    5   12    2    1    0    0  992    2   13]
 [   6    0    3    7    3    5    4    3  938    5]
 [   5    4    1    5   12    5    1    7    4  965]]

En un caso como este, la accuracy de 0.9720 nos dice que el modelo acertó el 97.20% de las imágenes del conjunto de test. Es una métrica global útil, pero no alcanza por sí sola para entender dónde se equivoca.

La matriz de confusión agrega justamente esa información. Cada fila representa la clase real y cada columna la clase predicha. Lo ideal es que casi todos los valores queden sobre la diagonal principal, porque esa diagonal contiene los aciertos.

Por ejemplo:

  • El valor 970 en la primera fila y primera columna indica que 970 imágenes del dígito 0 fueron reconocidas correctamente como 0.
  • En la fila del dígito 4 aparece un 23 en la última columna, lo que indica que varios cuatros fueron confundidos con nueves.
  • En la fila del dígito 8 aparecen algunos valores fuera de la diagonal, lo que muestra que ciertos ochos fueron confundidos con 0, 3, 5 o 9.

Eso nos permite identificar qué pares de clases se parecen más para el modelo. En MNIST es normal que aparezcan confusiones entre dígitos con formas visuales parecidas, como 4 y 9, 3 y 5, o 7 y 9.

La accuracy resume el rendimiento total. La matriz de confusión muestra exactamente en qué clases se concentra el error.

El classification_report profundiza todavía más el análisis. Allí veremos, para cada dígito, si el modelo fue preciso al predecirlo, si logró recuperarlo cuando realmente estaba presente y qué equilibrio hubo entre ambos aspectos.

24.24 Qué leer del reporte de clasificación

El classification_report suele mostrar, para cada clase:

  • Precision.
  • Recall.
  • F1-score.
  • Support, es decir, cantidad de ejemplos reales.

Esto ayuda mucho a detectar clases problemáticas que una accuracy global podría ocultar.

24.25 Qué limitaciones tiene este ejemplo

Aunque MNIST es un dataset real y muy útil para aprender, sigue siendo un problema relativamente simple comparado con tareas modernas de visión por computadora.

Las imágenes son pequeñas, en escala de grises y con una sola cifra centrada. En problemas reales suele haber más ruido, fondos complejos, variaciones de iluminación, múltiples objetos y clases mucho más difíciles de separar.

Por eso, una buena evaluación en MNIST no garantiza por sí sola que el mismo enfoque funcione igual de bien en un problema visual más exigente.

24.26 Errores comunes al evaluar

Algunos errores frecuentes son:

  • Evaluar sobre datos usados para ajustar el modelo.
  • Mirar solo accuracy en problemas desbalanceados.
  • No usar model.eval().
  • No usar torch.no_grad() durante inferencia.
  • Interpretar una métrica global sin revisar clases individuales.
  • Confundir mejora en entrenamiento con mejora real en generalización.

24.27 Qué debes recordar de este tema

  • Evaluar un modelo significa medir su desempeño sobre datos no usados directamente para ajustar sus pesos.
  • La accuracy es útil, pero no siempre suficiente.
  • La matriz de confusión muestra dónde se equivoca el modelo.
  • Precision, recall y F1 ayudan a entender mejor el comportamiento por clase.
  • El conjunto de test debe reservarse para la evaluación final.
  • Una evaluación seria combina métricas, contexto del problema y lectura crítica de errores.

24.28 Conclusión

La evaluación es la etapa que transforma un entrenamiento en una conclusión confiable. Sin una buena evaluación, no sabemos si el modelo realmente aprendió algo útil o si solo se adaptó a las particularidades del conjunto de desarrollo.

Aprender a mirar accuracy, matriz de confusión, precision, recall y F1 no es un detalle accesorio: es parte esencial del trabajo en visión por computadora. Estas herramientas permiten pasar de una impresión superficial del rendimiento a una comprensión más profunda del comportamiento del sistema.

En el próximo tema avanzaremos hacia una estrategia muy importante en problemas reales: el transfer learning en visión por computadora, donde aprovecharemos modelos ya entrenados para acelerar y mejorar nuevos proyectos.