在图像识别任务中,多标签分类(multi-label classification)是一种常见的场景,即一张图像可能同时包含多个独立的类别标签(例如,一张艺术品图像可能同时被标记为“印象派”、“风景画”和“莫奈”)。为了实现这类任务,通常会采用多头(multi-head)模型架构,即在共享的特征提取器之后,为每个分类任务设置独立的分类头。
在PyTorch中构建自定义模型时,尤其是在卷积层和全连接层之间进行张量形变(flattening)时,很容易出现张量尺寸计算错误,导致模型输入批次与输出批次不一致的问题。这会直接导致训练循环中计算损失时出现ValueError: Expected input batch_size (...) to match target batch_size (...)的错误。
本教程将以一个具体的案例来阐述这一问题。用户尝试为一个Wikiart数据集构建一个多标签分类模型,需要同时预测艺术家(artist)、风格(style)和流派(genre)三个标签。
最初,用户尝试基于Hugging Face的ResNetForImageClassification修改其分类头,以适应多标签任务。然而,直接修改model.classifier属性并不能让模型在forward方法中自动包含新增的多个分类头,torchinfo的摘要也证实了这一点,模型结构仍然是单分类输出。
# 初始尝试:修改预训练模型的分类头 (不适用多头输出) # model2.classifier_artist = torch.nn.Sequential(...) # model2.classifier_style = torch.nn.Sequential(...) # model2.classifier_genre = torch.nn.Sequential(...)
由于预训练模型修改的复杂性,用户转向了构建一个自定义的PyTorch模型WikiartModel。该模型包含共享的卷积层用于特征提取,然后分叉出三个独立的线性分类头。
import torch import torch.nn as nn import torch.nn.functional as F class WikiartModel(nn.Module): def __init__(self, num_artists, num_genres, num_styles): super(WikiartModel, self).__init__() # 共享卷积层 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding =1) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(128, 256, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(2, 2) # 最大池化层 # 艺术家分类分支 self.fc_artist1 = nn.Linear(256 * 16 * 16, 512) # 错误:输入特征维度计算有误 self.fc_artist2 = nn.Linear(512, num_artists) # 流派分类分支 self.fc_genre1 = nn.Linear(256 * 16 * 16, 512) # 错误:输入特征维度计算有误 self.fc_genre2 = nn.Linear(512, num_genres) # 风格分类分支 self.fc_style1 = nn.Linear(256 * 16 * 16, 512) # 错误:输入特征维度计算有误 self.fc_style2 = nn.Linear(512, num_styles) def forward(self, x): # 共享卷积层处理 x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) # 张量形变:将多维特征图展平为一维向量 x = x.view(-1, 256 * 16 * 16) # 错误:展平后的维度计算有误,且-1可能导致意外行为 # 艺术家分类分支 artists_out = F.relu(self.fc_artist1(x)) artists_out = self.fc_artist2(artists_out) # 流派分类分支 genre_out = F.relu(self.fc_genre1(x)) genre_out = self.fc_genre2(genre_out) # 风格分类分支 style_out = F.relu(self.fc_style1(x)) style_out = self.fc_style2(style_out) return artists_out, genre_out, style_out # 设置类别数量 num_artists = 129 num_genres = 11 num_styles = 27
当输入数据批次大小为32(即输入张量形状为[32, 3, 224, 224])时,torchinfo显示的模型输出批次大小为98,而不是预期的32,这导致了训练循环中损失计算的ValueError。
问题的核心在于卷积层输出的特征图尺寸与全连接层nn.Linear的in_features参数不匹配,以及forward方法中x.view操作的错误。
让我们逐步分析输入张量[32, 3, 224, 224]经过卷积和池化层后的尺寸变化:
因此,在进入全连接层之前,特征图的尺寸应该是 [Batch_Size, 256, 28, 28]。 当将其展平为一维向量时,除了批次大小之外的维度都应相乘:256 * 28 * 28 = 200704。
然而,原始代码中nn.Linear的in_features参数被错误地设置为256 * 16 * 16,这显然与实际的256 * 28 * 28不符。 同时,x.view(-1, 256 * 16 * 16)中的-1表示PyTorch会自动推断该维度,但由于其后指定的维度256 * 16 * 16与实际的展平尺寸不匹配,导致PyTorch在尝试展平时,不得不调整批次大小以满足总元素数量,从而产生了98这个错误的批次大小。
要解决此问题,需要进行两处关键修改:
以下是修正后的WikiartModel代码:
import torch import torch.nn as nn import torch.nn.functional as F class WikiartModel(nn.Module): def __init__(self, num_artists, num_genres, num_styles): super(WikiartModel, self).__init__() # 共享卷积层 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding =1) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(128, 256, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(2, 2) # 计算卷积层最终输出的特征图尺寸(例如,对于224x224输入,经过三次conv+pool后为28x28) # 建议在模型初始化时或通过一个小的dummy_input计算得出 # 确保这里的尺寸与实际计算结果一致 self.feature_map_size = 28 # 经过三次池化后,224 -> 112 -> 56 -> 28 self.flattened_features = 256 * self.feature_map_size * self.feature_map_size # 256 * 28 * 28 = 200704 # 艺术家分类分支 self.fc_artist1 = nn.Linear(self.flattened_features, 512) # 修正此处输入特征维度 self.fc_artist2 = nn.Linear(512, num_artists) # 流派分类分支 self.fc_genre1 = nn.Linear(self.flattened_features, 512) # 修正此处输入特征维度 self.fc_genre2 = nn.Linear(512, num_genres) # 风格分类分支 self.fc_style1 = nn.Linear(self.flattened_features, 512) # 修正此处输入特征维度 self.fc_style2 = nn.Linear(512, num_styles) def forward(self, x): # 共享卷积层处理 x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) # 张量形变:展平张量,保留批次大小 # x.size(0) 获取当前批次大小,-1让PyTorch自动计算剩余维度 x = x.view(x.size(0), -1) # 艺术家分类分支 artists_out = F.relu(self.fc_artist1(x)) artists_out = self.fc_artist2(artists_out) # 流派分类分支 genre_out = F.relu(self.fc_genre1(x)) genre_out = self.fc_genre2(genre_out) # 风格分类分支 style_out = F.relu(self.fc_style1(x)) style_out = self.fc_style2(style_out) return artists_out, genre_out, style_out # 设置类别数量 num_artists = 129 num_genres = 11 num_styles = 27 # 实例化模型并进行测试 (示例) model = WikiartModel(num_artists, num_genres, num_styles) dummy_input = torch.randn(32, 3, 224, 224) # 批次大小为32的模拟输入 artist_output, genre_output, style_output = model(dummy_input) print(f"Artist Output Shape: {artist_output.shape}") # 预期: [32, 129] print(f"Genre Output Shape: {genre_output.shape}") # 预期: [32, 11] print(f"Style Output Shape: {style_output.shape}") # 预期: [32, 27] # 此时,torchinfo的输出也将显示正确的批次大小 # from torchinfo import summary # summary(model, input_size=(32, 3, 224, 224))
在PyTorch中构建自定义神经网络时,管理张量尺寸是至关重要的一环。批次大小不一致的问题通常源于卷积层输出与全连接层输入之间的尺寸不匹配,以及view操作的误用。通过精确计算卷积层输出的特征图尺寸,并采用x.view(x.size(0), -1)这种健壮的展平方式,可以有效解决这类问题,确保数据在网络中顺畅流动,并避免训练过程中的ValueError。养成良好的张量尺寸追踪习惯,将大大提高模型开发的效率和准确性。
以上就是解决PyTorch多标签分类中批次大小不一致问题:模型架构与张量形变管理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号