为何修改PyTorch网络__init__中层定义顺序会改变模型准确率?
问题描述
我搭建了如下继承自nn.Module的RNN神经网络模型,代码实现如下:
class RNN(nn.Module): def __init__(self, input_size, hidden_size, num_layers1, num_layers2, num_layers3, num_classes): super(RNN, self).__init__() self.num_layers1 = num_layers1 self.num_layers2 = num_layers2 self.num_layers3 = num_layers3 self.hidden_size = hidden_size self.transformer = nn.TransformerEncoderLayer(d_model=100, nhead=20, layer_norm_eps=1e-04, dropout=0.3, dim_feedforward=2048, norm_first=True, activation= "relu", batch_first=True) self.fc1_bn = nn.BatchNorm1d(125) self.conv1 = torch.nn.ConvTranspose1d(in_channels=125, out_channels=125, kernel_size=6) self.layerNorm = torch.nn.LayerNorm(7) self.drop2 = torch.nn.Dropout(DROPOUT2) self.conv2 = torch.nn.ConvTranspose1d(in_channels=125, out_channels=125, kernel_size=5) self.layerNorm2 = torch.nn.LayerNorm(512) self.maxpool1d = nn.LPPool1d( 1,1) self.fc2_bn= nn.BatchNorm1d(125) self.lstm2 = nn.LSTM(200, hidden_size, num_layers2, batch_first=True ,dropout = .2 ) # #self.maxpool1d2 = nn.LPPool1d( 1,1) self.lstm3 = nn.LSTM(hidden_size, hidden_size, num_layers3, batch_first=True ) self.drop3 = torch.nn.Dropout(DROPOUT3) self.lstm4 = nn.LSTM(200, 200, num_layers3, batch_first=True ) self.fc = nn.Linear(hidden_size, num_classes) self.fc2 = nn.Linear(100, 200) self.fc3 = nn.Linear(205, 200) self.fc4 = nn.Linear(512, 512) self.fc5 = nn.Linear(3, 100) def forward(self, x): h02 = torch.zeros(self.num_layers2, x.size(0), self.hidden_size).to(device) c02 = torch.zeros(self.num_layers2, x.size(0), self.hidden_size).to(device) h03 = torch.zeros(self.num_layers3, x.size(0), self.hidden_size).to(device) c03 = torch.zeros(self.num_layers3, x.size(0), self.hidden_size).to(device) #out = self.conv1(x) out = F.relu(self.fc5(x)) out = self.transformer(out) out = F.relu(self.fc2(out)) #out = self.fc1_bn(out) out = self.conv1(out) out = F.relu(self.fc3(out)) out = self.maxpool1d(out) out, _ = self.lstm2(out, (h02,c02)) out = F.relu(self.fc4(out)) #out = self.fc1_bn(out) out, _ = self.lstm3(out, (h03,c03)) out = out[:, -1, :] out = self.fc(out) return out
实验过程中我发现,若修改__init__方法内任意模块的定义顺序,模型准确率会出现大幅变化;若保持原有定义顺序不变,准确率则不会出现异常波动。我原以为__init__方法仅用于声明神经网络的各个组成模块,并不会实际参与前向传播计算,为什么仅调整模块的定义顺序就会影响模型的最终输出结果?
原因说明
- 核心原因是PyTorch会在实例化
nn.Module子类时,按照__init__中模块的定义顺序依次初始化每个模块的权重参数,权重初始化需要调用随机数生成器,调整模块顺序会直接改变随机数的消耗顺序,最终每个模块拿到的初始权重完全不同。
举例:原本先定义Transformer层再定义BN层,初始化时随机数序列会先分配一段值给Transformer的权重,再分配下一段给BN层;如果调换两者顺序,BN层会先占用原本分给Transformer的随机值,Transformer只能取后面的随机值做初始化,所有模块的初始权重从根源上发生了变化。 - 如果固定了全局随机种子来保证实验可复现,模块定义顺序就是决定初始权重取值的关键变量之一。初始权重不同,模型训练的收敛路径、最终收敛到的局部最优点都会变化,准确率自然会出现大幅波动。
- 上述代码中定义了
conv2、drop2、layerNorm、layerNorm2、fc2_bn、lstm4多个模块,但这些模块根本没有在forward方法中被调用。这些未使用的模块在实例化时同样会消耗随机数,哪怕它们不参与前向传播,只要定义顺序变了,一样会打乱后续实际参与计算的模块的初始权重取值。 - 容易忽略的细节:PyTorch中
nn.Module的parameters()、children()、named_modules()等接口遍历模块时,严格按照__init__里的模块注册顺序返回结果。如果训练时用到了参数遍历逻辑(比如分层设置学习率、权重衰减、不同层使用不同优化器),模块顺序变化会导致训练策略的实际生效对象错位,进一步影响最终训练结果。
内容的提问来源于stack exchange,提问作者samuel fipps
相关产品推荐
相关产品推荐

