Search code examples
pythonmachine-learningpytorchneural-network

PyTorch throws batch size error with any value but 1


I made a neural network model with PyTorch, it kind of resembles the vgg19 model. Every time i enter a value for batch size i got the error:

ValueError: Expected input batch_size (1) to match target batch_size (16).

This is not happening with the batch size value of 1, it starts training without any complication.

I simply want to alter the batch size without getting any error.

I could not find (or understand) a solution for this issue by myself. I provide the model script below.

Note : In the train() method of the model, a defult value of batch size is given as 16. With this configuration it throws error as i showed above, i set the batch size value to 1 when i call this method and it works fine.

import os
import glob

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader

from torchvision import datasets, transforms

class ICModel(nn.Module):

    def __init__(self):
        super().__init__()
        # CNN
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2)
        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=2)
        self.conv3 = nn.Conv2d(128, 256, kernel_size=3, stride=2)
        self.conv4 = nn.Conv2d(256, 512, kernel_size=3, stride=2)

        # FULLY CONNECTED LAYERS
        self.fc1 = nn.Linear(61952, 256)
        self.fc2 = nn.Linear(256, 64)
        self.out = nn.Linear(64, 2)
    def forward(self, x):
        # CONV - 1
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, kernel_size=3, stride=1)
        # CONV - 2
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, kernel_size=3, stride=1)
        # CONV - 3
        x = F.relu(self.conv3(x))
        x = F.max_pool2d(x, kernel_size=3, stride=1)
        # CONV - 4
        x = F.relu(self.conv4(x))

        flattened_size = x.shape[0] * x.shape[1] * x.shape[2] * x.shape[3]

        x = x.view(-1, flattened_size)
        # FULLY CONNECTED LAYERS
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.out(x)

        return F.log_softmax(x, dim=1)

    def train(self, dataset_dir='', epochs=5, batch_size=16, seed=35, learning_rate=0.001, model_weights_path=''):
        if dataset_dir == '':
            raise Exception("Please enter a valid dataset directory path!")

        train_correct = []
        train_losses = []

        torch.manual_seed(seed)

        # CRITERION AND OPTIMIZER SETUP
        criterion = nn.CrossEntropyLoss()
        optimizer = torch.optim.Adam(self.parameters(), lr=learning_rate)

        optim_width, optim_height = 224, 224

        data_transforms = transforms.Compose([
            transforms.Resize((optim_width, optim_height)),  # Resize images to average dimensions
            transforms.ToTensor(),  # Convert images to PyTorch tensors
            transforms.Normalize(mean=[0.456, 0.456, 0.456], std=[0.456, 0.456, 0.456])  # Normalize images
        ])

        dataset = datasets.ImageFolder(root=dataset_dir, transform=data_transforms)
        train_loader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)

        for epoch in range(epochs):
            trn_corr = 0

            for b, (X_train, y_train) in enumerate(train_loader):
                b += 1
                y_pred = self(X_train)
                loss = criterion(y_pred, y_train)

                predicted = torch.max(y_pred, dim=1)[1]
                batch_corr = (predicted == y_train).sum()

                trn_corr += batch_corr.item()

                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

                if b % 4 == 0:
                    print(f'Epoch: {epoch}  Batch: {b}  Loss: {loss.item()}')

        train_losses.append(loss)
        train_correct.append(trn_corr)

        if (model_weights_path != '') & os.path.exists(model_weights_path) & os.path.isdir(model_weights_path):
            torch.save({
                'model_state_dict': self.state_dict(),
                'optimizer_state_dict': optimizer.state_dict()
            }, model_weights_path)

    def test(self, dataset_dir='', batch_size=16):
        if dataset_dir == '':
            raise Exception("Please enter a valid dataset directory path!")

        optim_width, optim_height = 224, 224
        test_losses = []
        tst_crr = 0

        criterion = nn.CrossEntropyLoss()

        data_transforms = transforms.Compose([
            transforms.Resize((optim_width, optim_height)),  # Resize images to average dimensions
            transforms.Grayscale(),
            transforms.ToTensor(),  # Convert images to PyTorch tensors
            transforms.Normalize(mean=[0.456], std=[0.456])  # Normalize images
        ])

        dataset = datasets.ImageFolder(root=dataset_dir, transform=data_transforms)
        test_loader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=True)

        with torch.no_grad():
            for b, (X_test, y_test) in enumerate(test_loader):
                y_val = self(X_test)
                predicted = torch.max(y_val.data, dim=1)[1]
                tst_crr += (predicted == y_test).sum()

            loss = criterion(y_val, y_test)
            test_losses.append(loss.item())

            test_results = {
                'true_positive': tst_crr,
                'false_positive': len(dataset.imgs) - tst_crr
            }

        return test_results, test_losses


And this is the full traceback of the error :

Traceback (most recent call last):
  File "/Users/eaidy/Repos/ML/inclination-classification-pytorch/src/main.py", line 12, in <module>
    ic_model.train(dataset_dir=train_dataset_absolute_path, epochs=1, batch_size=16, learning_rate=1e-5)
  File "/Users/eaidy/Repos/ML/inclination-classification-pytorch/src/models/cnn_model.py", line 94, in train
    loss = criterion(y_pred, y_train)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/eaidy/Repos/ML/inclination-classification-pytorch/.venv/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/eaidy/Repos/ML/inclination-classification-pytorch/.venv/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
    return forward_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/eaidy/Repos/ML/inclination-classification-pytorch/.venv/lib/python3.11/site-packages/torch/nn/modules/loss.py", line 1179, in forward
    return F.cross_entropy(input, target, weight=self.weight,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/eaidy/Repos/ML/inclination-classification-pytorch/.venv/lib/python3.11/site-packages/torch/nn/functional.py", line 3059, in cross_entropy
    return torch._C._nn.cross_entropy_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index, label_smoothing)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: Expected input batch_size (1) to match target batch_size (16).

Solution

  • The problem is here

    flattened_size = x.shape[0] * x.shape[1] * x.shape[2] * x.shape[3]
    
    x = x.view(-1, flattened_size)
    

    x is of shape (batch_size, channels, height, width). You compute flattened_size = batch_size * channels * height * width - the error is including the batch dimension. This means when you compute x = x.view(-1, flattened_size), the output x will always have size (1, flattened_size).

    In your loss calculation, you attempt to compute loss(x,y) where x has shape (1, flattened_size) and y has shape (bs,), causing the error.

    You should preserve the batch dimension when flattening:

    x = x.view(x.shape[0], -1)
    

    You will also need to resize the input to the first fully connected layer.