我正在使用 PyTorch 训练神经网络模型,称为“top_model”。下面,我概述了我的设置,然后详细描述了我面临的挑战。
class torch_top_model(nn.Module):
def __init__(self):
super(torch_top_model, self).__init__()
self.input_layer = nn.Linear(25, 320)
self.hidden_layer = nn.Linear(320, 64)
self.output_layer = nn.Linear(64, 10)
def forward(self, x):
x = F.relu(self.input_layer(x))
x = F.relu(self.hidden_layer(x))
x = self.output_layer(x)
return x
top_model = torch_top_model()
top_model.train()
我有一个大小为 (30, 25) 的输入张量。对于我的输入生成,我将 25 个特征分为 5 组,每组 5 个特征。
model_inputs = torch.rand(30, 25)
model_labels = torch.randint(0, 10, (30,))
top_model_inputs = {}
for i in range(5): # 4 groups (5 columns each)
top_model_inputs[i] = model_inputs[:, i*5:(i+1)*5]
在下一步中,我将生成输入数据的版本,其中一组特征(对应于每个键)被清零,而其他特征保持不变。
for key in range(1, len(top_model_inputs.keys())):
temp = []
for k in top_model_inputs:
if k == key:
temp.append(torch.zeros_like(top_model_inputs[k]))
else:
temp.append(top_model_inputs[k])
temp_tensor = torch.cat(temp, dim=1)
inputs[key] = temp_tensor
接下来我开始模型训练:
weights_and_biases = {i:top_model.state_dict() for i in range(1,5)}
optimizer = {i:torch.optim.Adam(top_model.parameters(), lr=0.01) for i in range(1,5)}
criterion = nn.CrossEntropyLoss()
top_models = {i:copy.deepcopy(top_model) for i in range(1,5)}
epochs = 3
for i in range(epochs):
for key in range(1, len(top_model_inputs.keys())):
top_models[key].load_state_dict(weights_and_biases[key])
outputs = top_models[key](inputs[key])
loss = criterion(outputs, model_labels)
optimizer[key].zero_grad()
loss.backward()
if i == 2:
w2 = weights_and_biases[2]['input_layer.weight'].clone().detach()
optimizer[key].step()
weights_and_biases[key] = top_models[key].state_dict()
if i == 1:
w1 = weights_and_biases[1]['input_layer.weight'].clone().detach()
print("w1-w2: ", torch.norm(w1-w2))
我的问题:对于输入的每个版本,当我在历元中运行算法时,我只希望更新相应输入的权重和偏差。例如,在 epoch1 中,我希望模型仅更新输入[1]的weights_and_biases[1],输入[2]的weights_and_biases[2]等。本质上,我希望在每个时期内针对每个输入版本单独更新 top_model 的参数(权重和偏差),然后在下一个时期使用这些更新的参数。
我面临的问题:权重被其他输入版本的weight_and_biases 重用。例如,输入[2]正在使用weight_and_biases[1]。因此,我在不同输入版本的更新前后得到零范数差异。
如何跨epoch独立更新不同输入版本的模型参数?如果无需多次深度复制模型即可实现这一点,这将是一个更有效的解决方案。
我认为您对这里的一些非常基本的编程方面感到困惑。让我们逐步完成一些事情。
您创建模型:
top_model = torch_top_model()
此时您只有一个模型。现在你创建这个字典:
weights_and_biases = {i:top_model.state_dict() for i in range(1,5)}
此时您仍然有一个模型。
weights_and_biases
中的条目都引用相同的模型状态字典。字典值都是指向完全相同对象的指针。
您创建优化器字典
optimizer = {i:torch.optim.Adam(top_model.parameters(), lr=0.01) for i in range(1,5)}
此时您仍然有一个模型。每个字典值都是不同的优化器对象,但它们都引用相同的权重对象。
您创建模型副本字典
top_models = {i:copy.deepcopy(top_model) for i in range(1,5)}
现在您已经创建了四个新模型。您总共有五个模型 -
top_models
中的四个加上您克隆的原始模型。
现在你的火车环路了
top_models[key].load_state_dict(weights_and_biases[key])
outputs = top_models[key](inputs[key])
loss = criterion(outputs, model_labels)
optimizer[key].zero_grad()
loss.backward()
如果您理解所编写的代码,您就可以清楚地看到为什么这不起作用。您可以将
top_models[key]
中的模型与优化器 optimizer[key]
一起使用。 top_models[key]
指向您创建的模型副本之一。 optimizer[key]
指向原始模型的权重。他们引用不同的对象。通过 top_models[key].load_state_dict(weights_and_biases[key])
加载状态字典不会改变这一点,因为它将数据值从 weights_and_biases[key]
复制到 top_models[key].load_state_dict
中的张量对象中。
您看到权重没有变化的原因是您没有更新它们。您使用
top_models[key]
中的权重计算损失,但优化器引用原始 top_model
中的权重。两者之间没有任何联系。如果您实际检查优化器中参数的梯度值 (optimizer[key].param_groups[0]['params'][0].grad
),您会发现它是 None
,因为没有计算梯度。优化器中的参数与您用于计算损失的参数无关。
我认为您也对自己想要实现的目标感到困惑。当您说
I would like the model to update only weights_and_biases[1] for inputs[1], weights_and_biases[2] for inputs[2]
时,这意味着您想要在不同的输入上训练单独的模型。当您说 If this can be achieved without deep copying the model
时,这意味着您想要一个模型。
您可以在所有版本的输入上训练单个模型,也可以在单独的输入上训练模型的单独实例。