PyTorch · Fundamentos
Tensores y datos: el vocabulario que ya usás sin pensarlo
Después de HF y fast.ai, ya construiste redes, entrenaste con LoRA, corriste inferencia con vLLM — todo sobre PyTorch, sin mirarlo de frente. Esta página fija el vocabulario oficial de la pieza más básica: el tensor y cómo entra la data al modelo.
1. El tensor: un ndarray que sabe usar GPU y derivarse
La documentación oficial es directa: un tensor es "una estructura de datos especializada, muy similar a
arrays y matrices" — básicamente un ndarray de NumPy con dos poderes extra: puede vivir en
GPU (o cualquier accelerator: CUDA, MPS, XPU) y está optimizado para diferenciación automática.
Se puede crear de varias formas equivalentes:
x_data = torch.tensor([[1, 2],[3, 4]]) # directo desde datos
x_np = torch.from_numpy(np_array) # desde un array de NumPy
x_ones = torch.ones_like(x_data) # conserva shape/dtype de otro tensor
rand_tensor = torch.rand((2,3)) # shape explícito
Tres atributos definen a cualquier tensor: .shape, .dtype y .device
(dónde vive físicamente — CPU por defecto). Mover un tensor a GPU es explícito y nunca automático:
if torch.accelerator.is_available():
tensor = tensor.to(torch.accelerator.current_accelerator())
La API cubre más de 1200 operaciones — indexado estilo NumPy, torch.cat para concatenar,
@/matmul para producto matricial, */mul para producto
elemento a elemento. Vale la pena memorizar un solo hecho: tensores en CPU y arrays de NumPy
pueden compartir la misma memoria subyacente — t.numpy() y
torch.from_numpy(n) no copian datos, así que modificar uno modifica el otro. Es la razón por
la que convertir entre ambos formatos es prácticamente gratis, y también una fuente clásica de bugs
sutiles cuando no se espera ese acoplamiento.
2. Operaciones in-place: el detalle que rompe autograd
Las operaciones que terminan en guion bajo (x.add_(5), x.copy_(y),
x.t_()) modifican el tensor en el lugar en vez de crear uno nuevo — ahorran memoria, pero la
documentación oficial es explícita: su uso está desaconsejado, porque pueden causar una
pérdida inmediata del historial necesario para calcular derivadas. Si un tensor participa en el grafo de
autograd y se sobreescribe in-place, PyTorch puede no tener forma de reconstruir el valor que necesitaba
para el backward pass — el error clásico es RuntimeError: a leaf Variable that requires grad is
being used in an in-place operation, o peor, un gradiente silenciosamente incorrecto.
3. Dataset: tres métodos, cualquier fuente de datos
torch.utils.data.Dataset es deliberadamente minimalista: cualquier clase que implemente
__init__, __len__ y __getitem__ ya es un Dataset válido, sin
heredar nada más complejo.
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
def __len__(self): return len(self.img_labels)
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = decode_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform: image = self.transform(image)
return image, label
El diseño es intencional: desacoplar el código de datos del código de entrenamiento. Un Dataset no sabe
nada de batches, GPUs, ni epochs — solo sabe cuántos ítems tiene y cómo devolver el ítem i.
Esta simplicidad es la razón por la que casi cualquier fuente de datos (un CSV, una carpeta de imágenes,
un dataset del Hub de HF, un stream remoto) puede envolverse en la misma interfaz de tres métodos.
4. DataLoader: batching, shuffling y paralelismo gratis
DataLoader envuelve un Dataset y resuelve todo lo que un training loop necesita
y un Dataset deliberadamente no sabe hacer: agrupar en mini-batches, barajar en cada epoch, y usar
multiprocessing para que la carga de datos no bloquee la GPU esperando el próximo batch.
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
train_features, train_labels = next(iter(train_dataloader))
# train_features.shape → [64, 1, 28, 28] — el batch ya armado
Iterar el DataLoader da tuplas (features, labels) ya agrupadas en el tamaño de
batch pedido — el mismo patrón exacto que ya usaste, sin necesariamente haberlo escrito a mano, en
Trainer de HF y en el Learner de fastai/miniai: ambos son, por dentro, un
DataLoader de PyTorch con una capa de conveniencia encima.
🔧 Es literalmente la misma pieza que ya viste tres veces
El TextDataLoaders/ImageDataLoaders de fastai (Parte 1 y 2), los
DataLoaders de miniai y los Dataset/DataLoader de
Hugging Face para tokenizar y batchear texto en el
Capítulo 5 de HF son, en su base,
exactamente esta misma pareja de clases de PyTorch — cada framework agrega su propia capa de
conveniencia arriba (integración con datasets del Hub, transforms automáticos, collation
específica), pero el contrato de fondo (__getitem__ + batching + shuffle) es idéntico en
los tres.
5. Resumen
- Un tensor es un ndarray con dos poderes extra: puede vivir en un accelerator y soporta diferenciación automática.
- Tensores en CPU y arrays de NumPy comparten memoria — convertir entre ambos es gratis, pero acopla los dos objetos.
- Las operaciones in-place (sufijo
_) ahorran memoria pero pueden romper el historial que necesita autograd — evitarlas quirúrgicamente, no por default. - Dataset son solo tres métodos (
__init__,__len__,__getitem__); DataLoader agrega batching, shuffle y paralelismo encima, sin que el Dataset sepa nada de eso. - fastai, miniai y HF Transformers construyen sus propias abstracciones de datos directamente sobre estas dos clases — no las reemplazan, las envuelven.