nn.Parameter无法更新,求问当前用法是否正确及正确实现方式
问题与解决方案
问题
定义了两个requires_grad=True的nn.Parameter变量,在类的__init__方法中声明,通过类内其他函数调用使用,但这两个参数始终无法更新,打印模型的named_parameters()也找不到它们。
原代码示例
class LG(BaseNetwork): def __init__(self, opt): super().__init__() self.opt = opt self.lparam = nn.Parameter(torch.zeros(1), requires_grad=True).cuda(device=opt.gpu_ids[0]) self.rparam = nn.Parameter(torch.zeros(1), requires_grad=True).cuda(device=opt.gpu_ids[0]) def foo(self, a, b, k=1.0, lparam=0, rparam=0): t = bar(a, b, k=k, lparam=lparam, rparam=rparam) return t def forward(self, a, b): x = self.foo(a, b, k=self.opt.k, lparam=self.lparam, rparam=self.rparam) return x
BaseNetwork继承自nn.Module,仅包含基础初始化逻辑
def bar(a, b, k=1.0, lparam=0, rparam=0): return n(a) * (b.std() * (k * lparam)) + (b.mean() * (k * rparam))
错误原因
问题出在nn.Parameter的初始化方式:直接链式调用.cuda()会返回一个新的普通张量,而非修改原nn.Parameter对象。原本PyTorch会自动将nn.Parameter实例注册到模型的参数列表中,但替换成普通张量后,模型无法追踪到这两个变量,自然不会更新它们的梯度,也不会出现在named_parameters()的输出里。
修正方案
有三种规范的实现方式,任选其一即可:
方式1:先创建Parameter,再单独移动到设备
class LG(BaseNetwork): def __init__(self, opt): super().__init__() self.opt = opt # 先创建Parameter实例,PyTorch自动注册到模型 self.lparam = nn.Parameter(torch.zeros(1), requires_grad=True) self.rparam = nn.Parameter(torch.zeros(1), requires_grad=True) # 再将参数移动到指定GPU self.lparam = self.lparam.cuda(device=opt.gpu_ids[0]) self.rparam = self.rparam.cuda(device=opt.gpu_ids[0]) def foo(self, a, b, k=1.0, lparam=0, rparam=0): t = bar(a, b, k=k, lparam=lparam, rparam=rparam) return t def forward(self, a, b): x = self.foo(a, b, k=self.opt.k, lparam=self.lparam, rparam=self.rparam) return x
方式2:直接在目标设备创建张量后转为Parameter
class LG(BaseNetwork): def __init__(self, opt): super().__init__() self.opt = opt # 先指定设备,再创建张量并转为Parameter device = torch.device(f'cuda:{opt.gpu_ids[0]}') self.lparam = nn.Parameter(torch.zeros(1, device=device), requires_grad=True) self.rparam = nn.Parameter(torch.zeros(1, device=device), requires_grad=True) def foo(self, a, b, k=1.0, lparam=0, rparam=0): t = bar(a, b, k=k, lparam=lparam, rparam=rparam) return t def forward(self, a, b): x = self.foo(a, b, k=self.opt.k, lparam=self.lparam, rparam=self.rparam) return x
方式3:统一调用self.to()移动所有参数(推荐)
这种方式更简洁,尤其当模型有多个参数时,无需逐个移动:
class LG(BaseNetwork): def __init__(self, opt): super().__init__() self.opt = opt # 创建Parameter实例 self.lparam = nn.Parameter(torch.zeros(1), requires_grad=True) self.rparam = nn.Parameter(torch.zeros(1), requires_grad=True) # 统一将模型所有参数移动到目标设备 self.to(opt.gpu_ids[0]) def foo(self, a, b, k=1.0, lparam=0, rparam=0): t = bar(a, b, k=k, lparam=lparam, rparam=rparam) return t def forward(self, a, b): x = self.foo(a, b, k=self.opt.k, lparam=self.lparam, rparam=self.rparam) return x
验证方法
创建模型实例后,执行以下代码:
model = LG(opt) for name, param in model.named_parameters(): print(name, param.requires_grad)
此时会输出lparam True和rparam True,训练时这两个参数也会正常更新梯度。
内容的提问来源于stack exchange,提问作者Mahmood Hussain
相关产品推荐
相关产品推荐

