在PyTorch中对模型重新参数化

0 投票
1 回答
33 浏览
提问于 2025-04-13 16:55

我正在尝试优化一个简单模型的参数,这个模型是用PyTorch库实现的。为了优化,我想用一种不同的方式来表示这些参数,而不是模型类中指定的那种方式。具体来说,我想把我的参数表示成一个单一的向量,而不是像这个例子中那样用两个向量。

我可以通过使用torch.nn.utils.convert_parameters中的parameters_to_vectormodel.parameters()(这是一个Iterable)转换成我想要的向量表示。不过,当我试图把这个向量标记为“叶子节点”(使用detachrequires_grad_),并用vector_to_parameters把它放回模型的原始参数时,似乎计算图并不知道发生了什么。

#!/usr/bin/python3
import torch
import torch.nn as nn
from torch.nn.utils.convert_parameters import *

class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.linear = nn.Linear(10, 1)

    def forward(self, x):
        return self.linear(x)

model = SimpleModel()

for name, param in model.named_parameters():
    print(name, param.size())
# this prints:
## linear.weight torch.Size([1, 10])
## linear.bias torch.Size([1])

loss_function = nn.MSELoss()

vparams = parameters_to_vector(model.parameters()).detach().clone().requires_grad_(True)

# populate model.parameters() from vparams
vector_to_parameters(vparams, model.parameters())

input_data = torch.randn(1, 10)
output = model(input_data)
target = torch.randn(1, 1)

loss = loss_function(output, target)
# loss.backward()  ## if we do this, then vparams.grad is None

## this one works, but we wanted to use vparams:
# vgrads = torch.autograd.grad(loss, model.linear.weight)[0]   

## this gives an error:
vgrads = torch.autograd.grad(loss, vparams)[0]
## "One of the differentiated Tensors appears to not have been used in the graph."

我也尝试手动进行向量切片,但这并没有解决错误。例如:

model.linear.weight.data.copy_(vparams[0:10].view_as(model.linear.weight.data))

或者

model.linear.weight = nn.Parameter(vparams[0:10].view_as(model.linear.weight.data))

我对PyTorch还比较陌生,但我听说在PyTorch中可以通过切片计算梯度,所以我觉得我尝试的应该是可行的。

我是不是对PyTorch模型使用的torch.nn.Parameter类有什么误解?这个类的成员是否必须在计算图中是“叶子节点”?下面是一个更小的例子,它省略了nn.Module子类,只是尝试从一个“切片”创建一个nn.Parameter对象:

import torch
import torch.nn as nn

a = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)

#b = nn.Parameter(a[1]) # "RuntimeError: One of the differentiated Tensors appears to not have been used in the graph."
#b = torch.Tensor(a[1]) # "IndexError: slice() cannot be applied to a 0-dim tensor."
b = a[1] # works

g = torch.autograd.grad(b, a)[0]

从错误信息来看,似乎PyTorch无法通过nn.Parameter的初始化来进行区分。有没有办法解决这个问题?

1 个回答

0

我觉得你可能误解了 vector_to_parameters 的工作原理。看看源代码:

def vector_to_parameters(vec: torch.Tensor, parameters: Iterable[torch.Tensor]) -> None:
    r"""Convert one vector to the parameters

    Args:
        vec (Tensor): a single vector represents the parameters of a model.
        parameters (Iterable[Tensor]): an iterator of Tensors that are the
            parameters of a model.
    """
    # Ensure vec of type Tensor
    if not isinstance(vec, torch.Tensor):
        raise TypeError('expected torch.Tensor, but got: {}'
                        .format(torch.typename(vec)))
    # Flag for the device where the parameter is located
    param_device = None

    # Pointer for slicing the vector for each parameter
    pointer = 0
    for param in parameters:
        # Ensure the parameters are located in the same device
        param_device = _check_param_device(param, param_device)

        # The length of the parameter
        num_param = param.numel()
        # Slice the vector, reshape it, and replace the old data of the parameter
        param.data = vec[pointer:pointer + num_param].view_as(param).data

        # Increment the pointer
        pointer += num_param

vector_to_parameters 是把 vec 中的值赋给 parameters 中的参数。这会更新 parameters 中的 data 值,但 vec 和更新后的参数之间并没有自动梯度链接。

当你进行前向传播时,你使用的是模型状态字典中的参数,梯度会流回这些参数。

举个例子,当你运行以下代码时,你会在 model.linear.weight.grad 中看到梯度,因为这是计算的一个叶子参数。vparams 在计算中没有参与,所以没有梯度链回到 vparams

vector_to_parameters(vparams, model.parameters())

input_data = torch.randn(1, 10)
output = model(input_data)
target = torch.randn(1, 1)

loss = loss_function(output, target)
loss.backward()

print(model.linear.weight.grad)

撰写回答