Search code examples
pythonpytorchneural-networkfast-ai

Wrong shape at fully connected layer: mat1 and mat2 shapes cannot be multiplied


I have the following model. It is training well. The shapes of my splits are:

  • X_train (98, 1, 40, 844)
  • X_val (21, 1, 40, 844)
  • X_test (21, 1, 40, 844)

However, I am getting the following error at x = F.relu(self.fc1(x)) in forward. When I attempt to interpret the model on the validation set.

# Create a DataLoader for the validation set
valid_dl = learn.dls.test_dl(X_val, y_val)

# Get predictions and interpret them on the validation set
interp = ClassificationInterpretation.from_learner(learn, dl=valid_dl) 

RuntimeError: mat1 and mat2 shapes cannot be multiplied (32x2110 and 67520x128)

I have checked dozens of similar questions but I am unable to find a solution. Here is the code.

from fastai.vision.all import *
import librosa
import numpy as np
from sklearn.model_selection import train_test_split
import torch
import torch.nn as nn
from torchsummary import summary

[...] #labels in y can be [0,1,2,3]

# Split the data
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.3, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)

# Reshape data for CNN input (add channel dimension)
X_train = X_train[:, np.newaxis, :, :]
X_val = X_val[:, np.newaxis, :, :]
X_test = X_test[:, np.newaxis, :, :]

#X_train.shape, X_val.shape, X_test.shape
#((98, 1, 40, 844), (21, 1, 40, 844), (21, 1, 40, 844))

class DraftCNN(nn.Module):
    def __init__(self):
        super(DraftCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)
        
        # Calculate flattened size based on input dimensions
        with torch.no_grad():
            dummy_input = torch.zeros(1, 1, 40, 844)  # shape of one input sample
            dummy_output = self.pool(self.conv2(self.pool(F.relu(self.conv1(dummy_input)))))
            self.flattened_size = dummy_output.view(dummy_output.size(0), -1).size(1)
        
        self.fc1 = nn.Linear(self.flattened_size, 128)
        self.fc2 = nn.Linear(128, 4)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(x.size(0), -1)  # Flatten the output of convolutions
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x


# Initialize the model and the Learner
model = AudioCNN()
learn = Learner(dls, model, loss_func=CrossEntropyLossFlat(), metrics=[accuracy, Precision(average='macro'),  Recall(average='macro'), F1Score(average='macro')])

# Train the model
learn.fit_one_cycle(8)

print(summary(model, (1, 40, 844)))

# Create a DataLoader for the validation set
valid_dl = learn.dls.test_dl(X_val, y_val)

# Get predictions and interpret them on the validation set
interp = ClassificationInterpretation.from_learner(learn, dl=valid_dl)
interp.plot_confusion_matrix()
interp.plot_top_losses(5)

I tried changing the forward function and the shapes of the layers but I keep getting the same error.

Edit. Upon request, I have added more code.


Solution

  • I was able to pass the data to the fastai.Learner to train the model and get results from plot_confusion_matrix. My conclusion that fastai is not designed to work with custom Datasets and DataLoaders and expecting you to use their API for loading the data. I think that in your case it might be worth to switch to TabularDataLoaders and load the data using TabularDataLoaders.from_df. Or alternatively use ImageBlock if you are working with images.

    Basically to give the most optimal solution to the question it is important to know what data are you using. Are you working with images? Are images stored in files? What type of files? Or the input data are simple arrays?

    Also function plot_top_losses doesn't work well if you have numpy dataset as the input. Function plots worst_k examples, and most probably it works only with data that is loaded using ImageBlocks and etc.

    Given current inputs there is two options how to fix the code:

    1. Assume numpy inputs. Create custom dataloader using fastai API.
    2. Assume numpy inputs. Create custom dataloaders using pytorch API.

    Solution 1: Building custom numpy dataloader for fastai Learner using fastai DataBlock API:

    from fastai.vision.all import *
    from fastai.data.all import *
    
    def make_dataloaders_from_numpy_data(image, label, loader=False):
        def pass_index(idx):
            return idx
    
        def get_x(i):
            val = image[i]
            return torch.Tensor(val)
    
        def get_y(i):
            # val = [label[i]]
            # res = torch.Tensor(val).to(torch.int64)
            return label[i]
    
        dblock = DataBlock(
            blocks=(DataBlock, CategoryBlock),
            get_items=pass_index,
            get_x=get_x,
            get_y=get_y)
    
        # pass in a list of index
        num_images = image.shape[0]
    
        source = list(range(num_images))
    
        if not loader:
            ds = dblock.datasets(source)    
            return ds
        
        return dblock.dataloaders(source, batch_size = 1)    
    
    train_ds = make_dataloaders_from_numpy_data(X_train, y_train)
    test_ds = make_dataloaders_from_numpy_data(X_test, y_test)
    
    
    train_ld = DataLoader(train_ds, batch_size=64)
    test_ld = DataLoader(test_ds, batch_size=64)
    dls = DataLoaders(train_ld, test_ld)
    
    
    dls_val = make_dataloaders_from_numpy_data(X_val, y_val,loader=True)
    
    
    
    # Initialize the model and the Learner
    model = DraftCNN()
    learn = Learner(dls, 
                    model, 
                    loss_func=CrossEntropyLossFlat(), 
                    metrics=[accuracy, Precision(average='macro'),  Recall(average='macro'), F1Score(average='macro')])
    
    # # # # Train the model
    learn.fit_one_cycle(1)
    
    
    # Get predictions and interpret them on the validation set
    interp = ClassificationInterpretation.from_learner(learn, dl=dls_val)
    interp.plot_confusion_matrix()
    plt.show()
    
    

    Solution 2

    Building custom numpy dataloader for fastai Learner using pytorch API for Dataset and DataLoader

    from torch.utils.data import Dataset
    from fastai.data.core import DataLoaders
    
    class CustomDataclass(Dataset):
        def __init__(self, X: np.ndarray, y: np.ndarray):
            """
            Will iterate over the dataset
            """
            self.data = X
            self.labels = y
    
            # Not clear what is self.vocab for
            # However part of this attribute is used for plotting labels in `ClassificationInterpretation`
            self.vocab = (None,
                          ['class_0', 'class_1', 'class_2', 'class_3'])
    
        def __len__(self):
            return self.data.shape[0]
    
        def __getitem__(self, idx: int):
          data = self.data[idx,...]
    
          # labels can't be single values and must be converted to a list
          labels = [self.labels[idx]]
    
          return  (torch.Tensor(data),  
                   torch.Tensor(labels).to(torch.int64) # labels must be integers
                   )
    
    
    train_ds = CustomDataclass(X_train, y_train)
    test_ds = CustomDataclass(X_test, y_test)
    val_ds = CustomDataclass(X_val, y_val)
    
    
    
    from torch.utils.data import DataLoader
    bs = 64
    train_loader = DataLoader(train_ds, batch_size = bs)
    test_loader = DataLoader(test_ds, batch_size = bs)
    
    # Val dataset used in interpretation phase where pytorch dataloaders doesn't work
    from fastai.data.core import DataLoader
    val_loader = DataLoader(val_ds, batch_size = bs)
    
    
    
    dls = DataLoaders(train_loader, test_loader)
    
    # Initialize the model and the Learner
    model = DraftCNN()
    learn = Learner(dls, 
                    model, 
                    loss_func=CrossEntropyLossFlat(), 
                    metrics=[accuracy, Precision(average='macro'),  Recall(average='macro'), F1Score(average='macro')])
    
    # # # # Train the model
    learn.fit_one_cycle(4)
    
    
    
    # Get predictions and interpret them on the validation set
    interp = ClassificationInterpretation.from_learner(learn, dl=val_loader)
    interp.plot_confusion_matrix()
    plt.show()
    
    

    Other errors that are fixed by my code:

    1. dataset must return labels in lists:
    learn.fit_one_cycle(4)
    ...
    return torch.stack(batch, 0, out=out)
    
    RuntimeError: stack expects each tensor to be equal size, but got [3] at entry 0 and [0] at entry 1
    
    1. Added vocab attribute to pytorch DataClass:
    interp = ClassificationInterpretation.from_learner(learn, dl=val_loader)
    ...
      File "/Users/ivanpetrov/.pyenv/versions/3.11.6/envs/stack_overflow_env/lib/python3.11/site-packages/fastcore/basics.py", line 507, in __getattr__
        if attr is not None: return getattr(attr,k)
                                    ^^^^^^^^^^^^^^^
    AttributeError: 'CustomDataclass' object has no attribute 'vocab'