PyTorch自定义CNN模块出现额外1792个参数的问题咨询
问题描述
单独统计第一个卷积块的参数量时,得到结果为36928,与预期值完全一致。但在另一个继承自nn.Module的类中调用该块搭建模型时,模型多出了1792个额外参数,无法定位参数来源。
复现代码
卷积块定义
class Conv2dBlock(torch.nn.Module): def __init__(self, in_filters, out_filters, kernel_size=3): super(Conv2dBlock, self).__init__() self.conv2d_seq = torch.nn.Sequential() for k in range(2): self.conv2d_seq.append(torch.nn.Conv2d(in_channels=in_filters, out_channels=out_filters, kernel_size=kernel_size, padding='same')) self.conv2d_seq.append(torch.nn.ReLU()) in_filters = out_filters def forward(self, input): out = self.conv2d_seq(input) return out
编码器块定义
class EncoderBlock(torch.nn.Module): def __init__(self): super(EncoderBlock, self).__init__() self.conv2d = Conv2dBlock(3, 64) self.maxpool = torch.nn.MaxPool2d(kernel_size=2) def forward(self, input): x = self.conv2d(input) p = self.maxpool(x) out = torch.nn.functional.dropout(p, 0.3) return x, out
模型定义与参数量统计
class UNet_model(torch.nn.Module): def __init__(self): super(UNet_model, self).__init__() self.encoder_block1 = EncoderBlock() def forward(self, input): p1 = self.encoder_block1(input) # 已省略无关代码 return p1 model = UNet_model() summary(model, (3,128,128))
问题现象
summary工具统计得到UNet_model总参数量为38720,比预期的36928多1792,差值刚好对应一个输入通道3、输出通道64、3×3卷积核的卷积层参数量(3*64*3*3 + 64 = 1792),相当于对输入多应用了一次卷积,找不到问题成因。
问题原因与排查方案
你的代码里不存在“额外新增的卷积层”,嵌套模型统计得到的38720是当前定义下的正确参数量,之前单独统计得到36928是统计失误导致的:
- 按你当前给出的
Conv2dBlock定义,传入in_filters=3, out_filters=64时,块内包含两个卷积层:- 第一层为3输入通道、64输出通道的3×3卷积(带偏置),参数量为
64*(3*3*3 + 1) = 1792 - 第二层为64输入通道、64输出通道的3×3卷积(带偏置),参数量为
64*(3*3*64 +1) = 36928
两个卷积层总参数量为1792+36928=38720,和你嵌套模型的统计结果完全一致。
- 第一层为3输入通道、64输出通道的3×3卷积(带偏置),参数量为
- 代码中
MaxPool2d是无参数层,torch.nn.functional.dropout是函数式操作、无注册参数,二者都不会增加可学习参数量。 - 之前单独统计卷积块得到36928,通常是以下原因导致:
- 单独测试时实例化的是旧版本类:比如之前
Conv2dBlock的循环只执行1次、输入通道设为64,刚好得到单个64->64卷积的参数量36928,修改类代码为循环2次、输入通道3后,没有重新运行类定义代码块,导致单独测试用的是内存中缓存的旧类,搭建嵌套模型时才加载了新的类定义 - 单独统计参数量的方法有误,只统计到了第二个卷积层的参数,漏统计了第一个卷积层
- 单独测试时实例化参数写错,传入的输入通道不是3,导致第一个卷积层参数量和预期不符
- 单独测试时实例化的是旧版本类:比如之前
你可以执行以下代码直接打印所有参数的名称和归属,验证参数来源:
for name, param in model.named_parameters(): print(f"参数名:{name},参数量:{param.numel()}")
执行后你会看到所有参数都归属Conv2dBlock内的两个卷积层,不存在任何不明来源的参数。
内容的提问来源于stack exchange,提问作者ThOpaque
相关产品推荐
相关产品推荐

