如何在 Pytorch 中为优化器动态添加新参数?

Posted

技术标签:

【中文标题】如何在 Pytorch 中为优化器动态添加新参数?【英文标题】:How does one dynamically add new parameters to optimizers in Pytorch? 【发布时间】:2019-09-02 13:57:51 【问题描述】:

我在pytorch论坛上通过this post,我也想这样做。原始帖子删除并添加了图层,但我认为我的情况并没有那么不同。我还想添加层或更多过滤器或词嵌入。我的主要动机是 AI 代理并不预先知道整个词汇/词典,因为它很大。我强烈(目前)更喜欢不逐个字符地做 RNN。

所以对我来说会发生什么,当代理开始前向传递时,它可能会发现它从未见过的新单词,并且需要将它们添加到嵌入表中(或者可能在开始前向传递之前添加新过滤器)。

所以我要确定的是:

    嵌入被正确添加(在正确的时间,当创建一个新的计算图时),以便优化器可以更新它们 过去参数的存储信息没有问题,例如如果它使用某种动力

如何做到这一点?任何有效的示例代码?

【问题讨论】:

交叉发布:quora.com/unanswered/… 这是一个很好的问题。请不要关闭。 【参考方案1】:

只是为您的问题标题添加一个答案:“如何在 Pytorch 中动态地向优化器添加新参数?”

您可以随时将参数附加到优化器:

import torch
import torch.optim as optim

model = torch.nn.Linear(2, 2) 

# Initialize optimizer
optimizer = optim.Adam(model.parameters(), lr=0.001, momentum=0.9)

extra_params = torch.randn(2, 2)
optimizer.param_groups.append('params': extra_params )

#then you can print your `extra_params`
print("extra params", extra_params)
print("optimizer params", optimizer.param_groups)

【讨论】:

【参考方案2】:

这是一个棘手的问题,因为我认为答案是“取决于”,特别是取决于您希望如何处理优化器。

让我们从您的具体问题开始 - 嵌入。特别是,您询问如何添加嵌入以动态允许更大的词汇表。我的第一个建议是,如果您对词汇量的上限有很好的了解,请从一开始就使嵌入足够大以应对它,因为这样更有效,而且您最终还是需要内存。但这不是你问的。所以 - 要动态地改变你的嵌入,你需要用一个新的覆盖你的旧的,并通知你的优化器这个变化。每当您在旧嵌入中遇到异常时,您都可以在 try ... except 块中简单地执行此操作。这应该大致遵循这个思路:

# from within whichever module owns the embedding
# remember the already trained weights
old_embedding_weights = self.embedding.weight.data
# create a new embedding of the new size
self.embedding = nn.Embedding(new_vocab_size, embedding_dim)
# initialize the values for the new embedding. this does random, but you might want to use something like GloVe
new_weights = torch.randn(new_vocab_size, embedding_dim)
# as your old values may have been updated, you want to retrieve these updates values
new_weights[:old_vocab_size] = old_embedding_weights
self.embedding.weights.data.copy_(new_weights)

但是,您不应该对收到的每个新单词都执行此操作,因为这种复制需要时间(而且会占用大量内存,因为嵌入会在短时间内存在两次 - 如果您几乎没有内存,只需从一开始就让你的嵌入足够大)。所以改为一次动态地增加几百个槽的大小。

此外,这第一步已经提出了一些问题:

    我各自的nn.Module 如何知道新的嵌入参数? nn.Module__setattr__ 方法可以解决这个问题(参见 here) 其次,我为什么不简单地更改我的参数?这已经指向了更改优化器的一些问题:pytorch 在内部通过对象 ID 保留引用。这意味着如果您更改对象,所有这些引用都将指向一个可能不兼容的对象,因为它的属性已更改。所以我们应该简单地创建一个新参数。 其他不是嵌入的nn.Parametersnn.Modules 呢?你对待这些是一样的。您基本上只是实例化它们,并将它们附加到它们的父模块。 __setattr__ 方法将负责其余的工作。所以你可以完全动态地这样做......

当然,优化器除外。除了主模型模块之外,优化器是唯一“了解”您的参数的东西。所以你需要让优化器知道任何变化。

这很棘手,如果您想更复杂一点,如果您不关心保持优化器状态,​​这很容易。但是,即使您想对其进行复杂化,也有一个很好的理由说明您可能无论如何都不应该这样做。更多关于以下内容。

不管怎样,如果你不在乎,一个简单的

# simply overwrite your old optimizer
optimizer = optim.SGD(model.parameters(), lr=0.001)

会的。但是,如果您想转移旧状态,可以使用与存储相同的方式,然后从磁盘加载参数和优化器状态:使用.state_dict().load_state_dict() 方法。但是,这仅适用于扭曲:

# extract the state dict from your old optimizer
old_state_dict = optimizer.state_dict()
# create a new optimizer
optimizer = optim.SGD(model.parameters())
new_state_dict = optimizer.state_dict()
# the old state dict will have references to the old parameters, in state_dict['param_groups'][xyz]['params'] and in state_dict['state']
# you now need to find the parameter mismatches between the old and new statedicts
# if your optimizer has multiple param groups, you need to loop over them, too (I use xyz as a placeholder here. mostly, you'll only have 1 anyways, so just replace xyz with 0
new_pars = [p for p in new_state_dict['param_groups'][xyz]['params'] if not p in old_state_dict['param_groups'][xyz]['params']]
old_pars = [p for p in old_state_dict['param_groups'][xyz]['params'] if not p in new_state_dict['param_groups'][xyz]['params']]
# then you remove all the outdated ones from the state dict
for pid in old_pars:
    old_state_dict['state'].pop(pid)
# and add a new state for each new parameter to the state:
for pid in new_pars:
    old_state_dict['param_groups'][xyz]['params'].append(pid)
    old_state_dict['state'][pid] =  ...   # your new state def here, depending on your optimizer

然而,这就是为什么您应该永远像这样更新您的优化器,而应该从头开始重新初始化,并接受状态信息丢失的原因:当您更改计算图时,您更改计算路径上所有参数的前向和后向计算(如果您没有分支架构,则此路径将是您的整个图)。这更具体地说意味着,如果您更改之前应用的某些功能 (=layer/nn.Module),您的功能 (=layer/nn.Module) 的输入将有所不同,并且如果您更改某些功能 (= layer/nn.Module) 稍后应用。这反过来又使优化器的整个状态无效。因此,如果您保留优化器的状态,它将是为不同的计算图计算的状态,并且如果您尝试将其应用于新的计算图,则可能会导致优化器的一部分出现灾难性行为。 (我去过那里......)

所以 - 总结一下:我真的建议尽量保持简单,并且尽可能保守地更改参数,不要碰优化器。

【讨论】:

简单地覆盖优化器虽然也需要你覆盖你的 lr_scheduler。 'prosti' 的答案阻止了这一点。【参考方案3】:

如果你想自定义初始参数:

from itertools import chain

l1 = nn.Linear(3,3)
l2 = nn.Linear(2,3)
optimizer = optim.SGD(chain(l1.parameters(), l2.parameters()), lr=0.01, momentum=0.9)

关键是构造函数的第一个参数接收迭代器。

【讨论】:

以上是关于如何在 Pytorch 中为优化器动态添加新参数?的主要内容,如果未能解决你的问题,请参考以下文章

pytorch入门Pytorch神经网络工具

Pytorch框架学习---优化器Optimizer

Pytorch ValueError:优化器得到一个空的参数列表

以optim.SGD为例介绍pytorch优化器

Pytorch中adam优化器的参数问题

Pytorch 模型 查看网络参数的梯度以及参数更新是否正确,优化器学习率的分层设置