Inteligencia artificial / Material de consulta
Red neuronal para imágenes
PyTorch · CPU, GPU opcional
Consulte las explicaciones, el código y las salidas de referencia en esta página.
Volver al aula →Proyecto II · Una CNN pequeña con PyTorch
Overtone Education · Curso de inteligencia artificial
Objetivo
Entrenar y evaluar una red convolucional sobre digits (imágenes 8×8, scikit-learn). Corre en CPU; GPU opcional. Separar entrenamiento, validación y prueba. Los datos no incluyen aquí una partición por escritor: la prueba aleatoria es didáctica y no demuestra generalización entre personas.
Preparación
Ejecute de arriba abajo en Jupyter con Python 3.12 (entorno validado). Entorno externo al aula. Instale en un entorno virtual:
python -m pip install numpy scikit-learn torch ipykernelLas versiones comprobadas se distribuyen en requirements-ia.txt. No necesita claves de API. Los ejemplos son educativos y no validan aplicaciones de impacto alto.
Pasos
1. Datos, semillas y tres particiones
Las intensidades originales van de 0 a 16; dividir por 16 es una transformación fija, no una estadística aprendida con la prueba.
Código de referencia
import copy
import numpy as np
import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
torch.manual_seed(7)
np.random.seed(7)
torch.set_num_threads(2)
datos = load_digits()
X = datos.images.astype("float32")[:,None,:,:]/16.0
y = datos.target.astype("int64")
X_dev,X_test,y_dev,y_test = train_test_split(X,y,test_size=.25,random_state=7,stratify=y)
X_train,X_valid,y_train,y_valid = train_test_split(X_dev,y_dev,test_size=.2,random_state=7,stratify=y_dev)
print({"train":len(y_train),"valid":len(y_valid),"test":len(y_test),"torch":torch.__version__})
train = DataLoader(TensorDataset(torch.from_numpy(X_train),torch.from_numpy(y_train)),batch_size=64,shuffle=True,generator=torch.Generator().manual_seed(7))Salida comprobada
{'train': 1077, 'valid': 270, 'test': 450, 'torch': '2.14.1'}
2. Arquitectura y contrato
Entrada: lote × 1 × 8 × 8. Una convolución, activación y pooling producen 8 × 4 × 4 valores por imagen. CrossEntropyLoss recibe logits sin softmax previo.
Código de referencia
modelo = nn.Sequential(nn.Conv2d(1,8,3,padding=1),nn.ReLU(),nn.MaxPool2d(2),nn.Flatten(),nn.Linear(8*4*4,32),nn.ReLU(),nn.Linear(32,10))
opt = torch.optim.Adam(modelo.parameters(),lr=.01)
perdida = nn.CrossEntropyLoss()
print("Parámetros:",sum(p.numel() for p in modelo.parameters()))
assert modelo(torch.zeros(2,1,8,8)).shape == (2,10)Salida comprobada
Parámetros: 4538
3. Entrenar y seleccionar con validación
Seleccione el mejor estado usando validación. La prueba se consulta una vez al finalizar. Este ejemplo usa un máximo fijo de 10 épocas para mantener el cómputo pequeño.
Código de referencia
mejor_score = -1.0
mejor_estado = None
for epoca in range(10):
modelo.train()
suma = 0.0
for xb,yb in train:
opt.zero_grad()
loss = perdida(modelo(xb),yb)
loss.backward()
opt.step()
suma += float(loss.detach())*len(yb)
modelo.eval()
with torch.no_grad():
score = float((modelo(torch.from_numpy(X_valid)).argmax(1)==torch.from_numpy(y_valid)).float().mean())
if score>mejor_score:
mejor_score = score
mejor_estado = copy.deepcopy(modelo.state_dict())
print(f"Época {epoca+1:02d} | pérdida train={suma/len(y_train):.4f} | exactitud valid={score:.3f}")Salida comprobada
Época 01 | pérdida train=2.0789 | exactitud valid=0.715 Época 02 | pérdida train=0.9887 | exactitud valid=0.874 Época 03 | pérdida train=0.3992 | exactitud valid=0.896 Época 04 | pérdida train=0.2464 | exactitud valid=0.930 Época 05 | pérdida train=0.1913 | exactitud valid=0.926 Época 06 | pérdida train=0.1694 | exactitud valid=0.956 Época 07 | pérdida train=0.1351 | exactitud valid=0.952 Época 08 | pérdida train=0.1058 | exactitud valid=0.959
Salida comprobada
Época 09 | pérdida train=0.1191 | exactitud valid=0.970 Época 10 | pérdida train=0.0799 | exactitud valid=0.967
4. Evaluar el artefacto seleccionado
eval() cambia el comportamiento de capas que lo necesitan; no_grad() evita construir el grafo de derivadas durante evaluación.
Código de referencia
modelo.load_state_dict(mejor_estado)
modelo.eval()
with torch.no_grad():
pred = modelo(torch.from_numpy(X_test)).argmax(1).numpy()
exactitud = float(np.mean(pred==y_test))
print("Exactitud de prueba:",round(exactitud,3))
print("Matriz de confusión (filas reales):")
print(confusion_matrix(y_test,pred))
indices_fallo = np.flatnonzero(pred!=y_test)
print("Primeros errores (índice, real, pred):",[(int(i),int(y_test[i]),int(pred[i])) for i in indices_fallo[:8]])Salida comprobada
Exactitud de prueba: 0.953 Matriz de confusión (filas reales): [[45 0 0 0 0 0 0 0 0 0] [ 0 43 0 0 2 0 1 0 0 0] [ 0 1 43 0 0 0 0 0 0 0] [ 0 0 2 40 0 2 0 0 2 0] [ 0 0 0 0 44 0 1 0 0 0] [ 0 0 0 0 1 44 0 0 0 1] [ 0 1 0 0 0 0 44 0 0 0] [ 0 0 0 0 1 0 0 44 0 0] [ 0 0 1 0 1 1 1 0 39 0] [ 0 0 0 0 0 2 0 0 0 43]] Primeros errores (índice, real, pred): [(1, 5, 4), (8, 1, 4), (19, 7, 4), (23, 1, 4), (31, 3, 2), (54, 8, 6), (108, 3, 5), (124, 3, 8)]
Comprobaciones
Compruebe tamaños, salidas finitas y calidad mínima para detectar una implementación rota. No convierta esta condición en garantía de uso.
Código de referencia
assert len(y_train)+len(y_valid)+len(y_test)==1797
assert np.isfinite(exactitud) and exactitud>.8
assert pred.shape==y_test.shape
assert all(torch.isfinite(p).all() for p in modelo.parameters())
print("Comprobaciones aprobadas")Salida comprobada
Comprobaciones aprobadas
Siguientes pasos
- Compare contra el SVC del aula usando exactamente las mismas particiones.
- Repita con otra semilla y mida tiempo y memoria.
- Para transferencia, utilice un backbone con licencia y preprocesamiento documentados. Congele primero el backbone; luego compare ajuste parcial.
- Use particiones por sujeto o fuente cuando exista esa dependencia.
Referencias: https://pytorch.org/tutorials/beginner/basics/intro.html y https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_digits.html