如何拼接ResNet输出与原始图像尺寸?PyTorch可行性及实现方法
方案合理性判断
- 该方案适配你的数据集场景,具备可行性:预训练ResNet要求输入图像统一缩放至固定尺寸(通用配置为224×224),该预处理步骤会完全抹除原始图像的尺寸差异信息。你提到不同类别间平均尺寸差达10倍量级,原始尺寸本身就是和类别强关联的判别信号,作为补充特征加入可以降低卷积层的学习负担,理论上能带来分类精度提升。
- 实现时需要注意3个问题,避免引入反效果:
- 尺寸特征必须做归一化:禁止直接将原始宽高数值(如25、250)直接拼入特征向量,建议将宽、高分别除以训练集所有样本的最大宽度、最大高度,将数值压缩至0~1区间;如果尺寸跨度呈现量级差异,可以先对宽高做log变换再归一化,避免特征数值分布偏移干扰模型训练。
- 注意规避数据集偏差风险:如果尺寸差异来自采集偏差(比如某一类样本全部是小尺寸截图,而非目标本身固有尺寸属性),加入尺寸特征可能导致模型过拟合到数据集偏差、跨域泛化性下降,建议通过消融实验对比加特征前后的验证集精度,确认特征有效性。
- 尺寸特征维度不宜过多:一般使用「归一化宽度、归一化高度」2维特征即可,也可根据需求补充宽高比、图像面积特征,总维度控制在2~4维即可,冗余特征反而会干扰分类决策。
PyTorch实现步骤
整体实现逻辑分为3步:修改数据集类返回归一化尺寸特征、改造ResNet18结构适配拼接后的特征维度、调整训练循环的输入逻辑。
- 自定义数据集,在返回预处理后图像、标签的同时,返回归一化的原始尺寸特征:
from torch.utils.data import Dataset from torchvision import transforms from PIL import Image import torch class CustomImageDataset(Dataset): def __init__(self, file_paths, labels, max_w, max_h): self.file_paths = file_paths self.labels = labels # 提前统计训练集最大宽、高,用于尺寸特征归一化 self.max_w = max_w self.max_h = max_h # ResNet标准图像预处理流程 self.img_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 __len__(self): return len(self.labels) def __getitem__(self, idx): img = Image.open(self.file_paths[idx]).convert('RGB') # 记录原始图像尺寸(宽、高) orig_w, orig_h = img.size # 生成归一化尺寸特征 size_feat = torch.tensor([orig_w / self.max_w, orig_h / self.max_h], dtype=torch.float32) img_tensor = self.img_transform(img) label = self.labels[idx] return img_tensor, size_feat, label
- 改造ResNet18结构,截获卷积层输出的512维图像特征,与尺寸特征拼接后送入新的全连接层分类:
import torch import torch.nn as nn from torchvision import models class ResNet18WithSizeFeat(nn.Module): def __init__(self, num_classes=17, size_feat_dim=2): super().__init__() # 加载预训练ResNet18 backbone = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) # 拆分出特征提取部分(去掉原最后一层全连接层),输出为512维图像特征 self.feat_extractor = nn.Sequential(*list(backbone.children())[:-1]) # 新的全连接层,输入维度为图像特征维度+尺寸特征维度 self.fc = nn.Linear(in_features=512 + size_feat_dim, out_features=num_classes, bias=True) def forward(self, img, size_feat): # 提取图像特征,输出形状为(batch_size, 512, 1, 1) img_feat = self.feat_extractor(img) # 展平为(batch_size, 512) img_feat = img_feat.flatten(1) # 沿特征维度拼接图像特征和尺寸特征 combined_feat = torch.cat([img_feat, size_feat], dim=1) # 输出分类结果 out = self.fc(combined_feat) return out
- 训练时调整前向传播逻辑,同时传入图像和尺寸特征:
# 初始化模型 model = ResNet18WithSizeFeat(num_classes=17, size_feat_dim=2) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) epochs = 10 model.train() for epoch in range(epochs): for imgs, size_feats, labels in train_loader: imgs = imgs.to(device) size_feats = size_feats.to(device) labels = labels.to(device) optimizer.zero_grad() outputs = model(imgs, size_feats) loss = criterion(outputs, labels) loss.backward() optimizer.step()
调参建议:可以先冻结ResNet backbone的预训练权重,仅训练新的全连接层,验证尺寸特征带来的精度增益,再决定是否微调整个网络。
内容的提问来源于stack exchange,提问作者Dobiks
相关产品推荐
相关产品推荐

