在运行卷积神经网络模型时,训练数据与测试数据的张量形状不一致问题

人工智能 2026-07-09

我在运行保存的CNN模型时遇到了一个问题。当我训练模型时,第一层线性层的输入张量大小被设定为展平并经过卷积层后用于训练数据的形状,一切正常。然而,当我对测试数据执行同样的操作并保存后重新加载模型时,就会出现错误。

错误:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[20], line 8
      4 with torch.no_grad():
      5     for images in X_test.unsqueeze(0):
      6         images = images.to(device)
      7 
----> 8         output = model(images)
      9         _, predictions = torch.max(output, 1)
     10 
     11         all_prediction.extend(predictions.cpu().numpy())

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\module.py:1778, in Module._wrapped_call_impl(self, *args, **kwargs)
   1776     return self._compiled_call_impl(*args, **kwargs)  # type: ignore[misc]
   1777 else:
-> 1778     return self._call_impl(*args, **kwargs)

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\module.py:1789, in Module._call_impl(self, *args, **kwargs)
   1784 # If we don't have any hooks, we want to skip the rest of the logic in
   1785 # this function, and just call forward.
   1786 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
   1787         or _global_backward_pre_hooks or _global_backward_hooks
   1788         or _global_forward_hooks or _global_forward_pre_hooks):
-> 1789     return forward_call(*args, **kwargs)
   1791 result = None
   1792 called_always_called_hooks = set()

Cell In[11], line 30, in GroceryObjectDetectorv2.forward(self, x)
     26     def forward(self, x):
     27         x = x.to(device)
     28         x = self.conv_layer(x)
     29         print(f"After conv layer: {x.shape}")  # Debug
---> 30         x = self.dense_layer(x)
     31         return x

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\module.py:1778, in Module._wrapped_call_impl(self, *args, **kwargs)
   1776     return self._compiled_call_impl(*args, **kwargs)  # type: ignore[misc]
   1777 else:
-> 1778     return self._call_impl(*args, **kwargs)

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\module.py:1789, in Module._call_impl(self, *args, **kwargs)
   1784 # If we don't have any hooks, we want to skip the rest of the logic in
   1785 # this function, and just call forward.
   1786 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
   1787         or _global_backward_pre_hooks or _global_backward_hooks
   1788         or _global_forward_hooks or _global_forward_pre_hooks):
-> 1789     return forward_call(*args, **kwargs)
   1791 result = None
   1792 called_always_called_hooks = set()

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\container.py:253, in Sequential.forward(self, input)
    249 """
    250 Runs the forward pass.
    251 """
    252 for module in self:
--> 253     input = module(input)
    254 return input

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\module.py:1778, in Module._wrapped_call_impl(self, *args, **kwargs)
   1776     return self._compiled_call_impl(*args, **kwargs)  # type: ignore[misc]
   1777 else:
-> 1778     return self._call_impl(*args, **kwargs)

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\module.py:1789, in Module._call_impl(self, *args, **kwargs)
   1784 # If we don't have any hooks, we want to skip the rest of the logic in
   1785 # this function, and just call forward.
   1786 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
   1787         or _global_backward_pre_hooks or _global_backward_hooks
   1788         or _global_forward_hooks or _global_forward_pre_hooks):
-> 1789     return forward_call(*args, **kwargs)
   1791 result = None
   1792 called_always_called_hooks = set()

File ~\Desktop\groceryClassifyer\torchGPU\Lib\site-packages\torch\nn\modules\linear.py:134, in Linear.forward(self, input)
    130 def forward(self, input: Tensor) -> Tensor:
    131     """
    132     Runs the forward pass.
    133     """
--> 134     return F.linear(input, self.weight, self.bias)

RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x784 and 100352x128)
-------------------------------------------------------------------------------

我不知道该如何修复,因为我曾尝试在类中加入一个参数,以便调整密集层的输入张量,但这让模型崩溃。

这是模型(不包含改变密集层输入张量值的参数):

class GroceryObjectDetectorv2(nn.Module):
   def __init__(self):
        super().__init__()
        self.conv_layer = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),

            nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),

            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        self.dense_layer = nn.Sequential(
            nn.Flatten(),
            nn.Linear(796, 128),
            nn.ReLU(),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, 43)# number of classes
        )

    def forward(self, x):
        x = self.conv_layer(x)
        print(f"After conv layer: {x.shape}")  # Debug
        x = self.dense_layer(x)
        return x

这是我用训练数据运行模型的代码:

def accuracy_fn(y_true, y_pred):
        """Calculates accuracy between truth labels and predictions.

       Args:
            y_true (torch.Tensor): Truth labels for predictions.
            y_pred (torch.Tensor): Predictions to be compared to predictions.

       Returns:
            [torch.float]: Accuracy value between y_true and y_pred, e.g. 78.45
        """
        correct = torch.eq(y_true, y_pred).sum().item()
        acc = (correct / len(y_pred)) * 100
        return acc

    epochs = 100
    train_losses = []
    train_accs = []
    val_losses = []
    val_accs = []

    for epoch in range(epochs):
        model.train()

        # running_loss keeps track of the loss in each batch
        train_running_loss = 0.0

        train_running_acc = 0.0

        for X_batch, y_batch in train_loader:
            X_batch = X_batch.to(device)
            y_batch = y_batch.to(device)

            optimizer.zero_grad()

            #insert a batch to the model
            train_y_pred = model(X_batch).to(device)

            #calculate the loss of the batch
            loss = loss_fn(train_y_pred, y_batch)

            #calculate accuracy of the batch
            train_y_pred_class = torch.argmax(train_y_pred, dim=1)
            train_acc = accuracy_fn(y_batch, train_y_pred_class)

            loss.backward()

            optimizer.step()

            train_running_loss += loss.item()
            train_running_acc += train_acc

        average_train_loss =  train_running_loss / len(train_loader)
        train_losses.append(average_train_loss)

        average_train_acc = train_running_acc / len(train_loader)
        train_accs.append(average_train_acc)

        model.eval()

        # running_loss keeps track of the loss in each batch
        val_running_loss = 0.0

        val_running_acc = 0.0
        with torch.no_grad():

            for X_val, y_val in val_loader:

                 #insert a batch to the model
                val_y_pred = model(X_val).to(device)

                #calculate the loss of the batch
                val_loss = loss_fn(val_y_pred, y_val)

                #calculate accuracy of the batch
                val_y_pred_class = torch.argmax(val_y_pred, dim=1)
                val_acc = accuracy_fn(y_batch, val_y_pred_class)

                val_running_loss += val_loss.item()
                val_running_acc += val_acc

            average_val_loss = val_running_loss / len(val_loader)
            val_losses.append(average_val_loss)

        average_acc = val_running_acc / len(val_loader)
        val_accs.append(average_acc)

        print(f"Epoch: {epoch + 1} Train:  Acc: {train_running_acc / len(train_loader):.2f}%           Loss: {train_running_loss / len(train_loader):.4f} | Validation: Acc: {val_running_acc / len(val_loader):.2f}%  Loss: {val_running_loss / len(val_loader)

这是用测试数据拟合模型时的代码:

model.to(device)

model.eval()
folder = r"C:/Users/FiercePC/Desktop/groceryClassifyer/"

images = []
labels = []

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor()
])

for i in range(len(test_df)):
    path = test_df["path"].iloc[i]
    label = test_df["label"].iloc[i]   # change "label" if your column has a different name

    img = Image.open(folder + "data/img/" + path).convert("RGB")
    img = transform(img)

    images.append(img)
    labels.append(label)

test_image_tensor = torch.stack(images)
test_label_tensor = torch.tensor(labels, dtype=torch.long)

test_loader = DataLoader(test_image_tensor, batch_size=32, shuffle=False)

X_test = next(iter(test_image_tensor)).to(device)
y_test = test_label_tensor.to(device)

all_prediction = []
model.eval()

with torch.no_grad():
    for images in X_test.unsqueeze(0):
        images = images.to(device)

        output = model(images)
        _, predictions = torch.max(output, 1)

        all_prediction.extend(predictions.cpu().numpy())
print(all_prediction)

解决方案

错误信息中的 100352 出现的源头如下:

输出形状 解释
输入 (batch_size, 32, 224, 224)
nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1) (batch_size, 32, 224, 224) padding=1 保持空间维度(224x224),通道从 3 变为 32
nn.ReLU() (batch_size, 32, 224, 224) 逐元素激活,形状保持不变
nn.MaxPool2d(2, 2) (batch_size, 32, 112, 112) 以因子2 下采样
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) (batch_size, 64, 112, 112) 空间维度保持不变;通道从 32 增加到 64
nn.ReLU() (batch_size, 64, 112, 112) 形状保持不变
nn.MaxPool2d(2, 2) (batch_size, 64, 56, 56) 高度和宽度减半
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) (batch_size, 128, 56, 56) 空间维度保持不变;通道从 64 增加到 128
nn.ReLU() (batch_size, 128, 56, 56) 形状保持不变
nn.MaxPool2d(2, 2) (batch_size, 128, 28, 28) 以因子2 下采样
nn.Flatten() (batch_size, 100352) 128x28x28=100352

第一层 nn.Linear 的输入维度应该是 100352,而不是 796

由于权重矩阵在矩阵乘法的右侧,出现在 forwardnn.Linear 中(请参阅源码 此处此处),错误信息显示你实际上使用的是正确的代码(即 nn.Linear(100352, 128) 而不是 nn.Linear(796, 128))。


真正的问题出在测试代码里。你创建了一个 DataLoader

test_loader = DataLoader(test_image_tensor, batch_size=32, shuffle=False)

但你从未使用它。相反,你做的是:

X_test = next(iter(test_image_tensor)).to(device)  # shape: (num_images,3,224,224) -> (3,224,224)

这会从 test_image_tensor 提取一张图像,而不是从 test_loader 提取一个批次。

并且这里:

for images in X_test.unsqueeze(0):
    model(images)

unsqueeze(0) 生成一个形状为 (1,3,224,224) 的张量,但for循环移除了未挤压的维度,产生一个形状为 (3,224,224)images 张量。这导致 nn.Flatten() 层的输入和输出成为形状分别为 (128,28,28)(128,784) 的张量,而不是 (1,128,28,28)(1,100352)。这解释了错误信息中的 128x784

解决方法是改用你创建的 test_loader

for images in test_loader:
    images = images.to(device)
    output = model(images)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章