Cliente: NutriVision AI · Proyecto: FoodClassifier v2 (ResNet-50, 120 clases) · GPU: NVIDIA RTX 3090 24 GB
File "train_food_classifier.py", line 87, in forward logits = self.classifier(features) RuntimeError: mat1 and mat2 shapes cannot be multiplied (32x2048 and 512x120)
ResNet-50 con fc = nn.Identity() devuelve un tensor de [batch, 2048] tras el AdaptiveAvgPool2d. La capa nn.Linear estaba declarada con in_features=512, valor típico de ResNet-18/34 pero incorrecto para ResNet-50/101/152 que usan bloques Bottleneck con expansión ×4.
self.classifier = nn.Linear(512, num_classes) # in_features=512 → ResNet-18/34
self.classifier = nn.Linear(2048, num_classes) # in_features=2048 → ResNet-50 Bottleneck
Cambio quirúrgico de 1 número: 512 → 2048 en la declaración de nn.Linear. No se alteró la arquitectura. Verificado con torchsummary.summary(model, (3,224,224)).
File "train_food_classifier.py", line 134, in validate loss = criterion(outputs, labels) RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!
En el loop de validación, images se movía a GPU correctamente, pero labels permanecía en CPU. La función de pérdida CrossEntropyLoss necesita que outputs y labels estén en el mismo dispositivo.
for images, labels in loader: images = images.to(device) # labels permanece en CPU ✗ with torch.no_grad(): outputs = model(images) loss = criterion(outputs, labels)
for images, labels in loader: images = images.to(device) labels = labels.to(device) # ← añadir with torch.no_grad(): outputs = model(images) loss = criterion(outputs, labels)
Añadir 1 línea: labels = labels.to(device) inmediatamente después de mover las imágenes. Patrón recomendado: mover todos los tensores del batch al inicio del loop, antes de cualquier cómputo.
File "train_food_classifier.py", line 62, in forward x += self.dropout(residual) RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.cuda.FloatTensor [32, 256, 14, 14]], which is output 0 of ReluBackward0, is at version 2; expected version 1.
El operador += modifica el tensor x in-place. PyTorch Autograd almacena referencias a los tensores intermedios para el backward pass. Cuando un tensor intermedio se modifica in-place, el grafo computacional queda en estado inconsistente y el backward falla. Esto es especialmente problemático cuando el tensor proviene de una operación ReLU guardada para el backward.
def forward(self, x): features = self.backbone(x) # in-place: modifica 'x' sobre sí mismo x += self.dropout(features) return self.classifier(x)
def forward(self, x): features = self.backbone(x) # out-of-place: crea nuevo tensor x = features + self.dropout(features) return self.classifier(x)
Sustituir x += expr por x = features + expr. El operador + (out-of-place) crea un nuevo tensor y preserva el grafo computacional intacto para el backward. Regla general: nunca usar +=, -=, *=, /= sobre tensores que requieren gradiente.
File "train_food_classifier.py", line 201, in __iter__ batch = self.collate_fn(samples) RuntimeError: stack expects each tensor to be equal size, but got [3, 224, 224] at entry 0 and [3, 218, 231] at entry 3
El dataset recibe imágenes de tamaño variable desde disco (fotos tomadas con distintas resoluciones). Sin un transforms.Resize obligatorio antes de ToTensor, el default_collate intenta apilar tensores de formas distintas y falla. La solución es añadir Resize en el transform del dataset, no en el collate.
class FoodDataset(Dataset): def __getitem__(self, idx): img = self.load_image(idx) # sin resize → tamaños variables return transforms.ToTensor()(img), \ self.labels[idx]
class FoodDataset(Dataset): def __init__(self, ...): self.transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize( mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225]) ]) def __getitem__(self, idx): img = self.load_image(idx) return self.transform(img), self.labels[idx]
Añadir transforms.Resize((224, 224)) como primera transformación en el pipeline del dataset. Como bonus se añadió la normalización ImageNet estándar para alinear con los pesos preentrenados de ResNet-50 IMAGENET1K_V2.
| # | Archivo | Línea | Tipo de Error | Fix aplicado |
|---|---|---|---|---|
| [FIXED] | train_food_classifier.py | :87 | Shape Mismatch | Cambiar nn.Linear(512, 120) → nn.Linear(2048, 120) |
| [FIXED] | train_food_classifier.py | :134 | Device Mismatch | Añadir labels = labels.to(device) en loop de validación |
| [FIXED] | train_food_classifier.py | :62 | In-place Autograd | Reemplazar x += dropout(features) → x = features + dropout(features) |
| [FIXED] | train_food_classifier.py | :201 | DataLoader Collate | Añadir transforms.Resize((224, 224)) + normalización ImageNet en FoodDataset.__init__ |
Todos los errores de PyTorch/CUDA han sido diagnosticados y corregidos con cambios quirúrgicos mínimos.
El modelo FoodClassifier v2 de NutriVision AI puede reanudarse desde la época 0 sin pérdida de arquitectura.