在运行卷积神经网络模型时,训练数据与测试数据的张量形状不一致问题
我在运行保存的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。
由于权重矩阵在矩阵乘法的右侧,出现在 forward 的 nn.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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。