На самом деле я думаю, что вам не нужно обновлять optimizer
. Parameters
, переданный optimizer
- это просто ссылки.
Поэтому, когда вы измените флаг requires_grad
, он будет немедленно обновлен.
Но даже если по какой-то причине это не так - как только вы установите флаг requires_grad
равным False
, вы больше не сможете вычислять градиенты для любых новых градиентов ( см. внизу None
и нулевой градиент) для этого веса, поэтому градиент больше не изменится, и если вы используете optimizer.zero_grad()
, он просто останется zero
.
Так что, если градиента нет, то нет необходимости исключать их из optimizer
. Потому что без градиента optimizer
просто ничего не сделает, независимо от того, какую скорость обучения вы используете.
Вот небольшой пример, демонстрирующий это поведение:
import torch
import torch.nn as nn
import torch.optim as optim
n_dim = 5
p1 = nn.Linear(n_dim, 1)
p2 = nn.Linear(n_dim, 1)
optimizer = optim.Adam(list(p1.parameters())+list(p2.parameters()))
p2.weight.requires_grad = False
for i in range(4):
dummy_loss = (p1(torch.rand(n_dim)) + p2(torch.rand(n_dim))).squeeze()
optimizer.zero_grad()
dummy_loss.backward()
optimizer.step()
print('p1: requires_grad =', p1.weight.requires_grad, ', gradient:', p1.weight.grad)
print('p2: requires_grad =', p2.weight.requires_grad, ', gradient:', p2.weight.grad)
print()
if i == 1:
p1.weight.requires_grad = False
p2.weight.requires_grad = True
Выход:
p1: requires_grad = True , gradient: tensor([[0.8522, 0.0020, 0.1092, 0.8167, 0.2144]])
p2: requires_grad = False , gradient: None
p1: requires_grad = True , gradient: tensor([[0.7635, 0.0652, 0.0902, 0.8549, 0.6273]])
p2: requires_grad = False , gradient: None
p1: requires_grad = False , gradient: tensor([[0., 0., 0., 0., 0.]])
p2: requires_grad = True , gradient: tensor([[0.1343, 0.1323, 0.9590, 0.9937, 0.2270]])
p1: requires_grad = False , gradient: tensor([[0., 0., 0., 0., 0.]])
p2: requires_grad = True , gradient: tensor([[0.0100, 0.0123, 0.8054, 0.9976, 0.6397]])
Здесь видно, что градиенты не рассчитываются. Возможно, вы заметили, что градиент для p2
в начале равен None
, а позже он будет tensor([[0., 0., 0., 0., 0.]])
для p1
вместо None
после деактивации градиентов.
Это так, потому что p1.weight.grad
- это просто переменная, которая изменяется на backward()
и optimizer.zero_grad()
.
Таким образом, в начале p1.weight.grad
просто инициализируется с None
, после того как градиенты записаны или накоплены в эту переменную, они не будут очищаться автоматически. Но поскольку вызывается optimizer.zero_grad()
, они устанавливаются в ноль и остаются такими, поскольку backward()
больше не может вычислять новые градиенты с requires_grad=False
.
Вы также можете изменить код в if
заявлении на:
if i == 1:
p1.weight.requires_grad = False
p1.weight.grad = None
p2.weight.requires_grad = True
Таким образом, после сброса на None
они остаются нетронутыми и остаются None
:
p1: requires_grad = True , gradient: tensor([[0.2375, 0.7528, 0.1501, 0.3516, 0.3470]])
p2: requires_grad = False , gradient: None
p1: requires_grad = True , gradient: tensor([[0.5181, 0.5178, 0.6590, 0.6950, 0.2743]])
p2: requires_grad = False , gradient: None
p1: requires_grad = False , gradient: None
p2: requires_grad = True , gradient: tensor([[0.4797, 0.7203, 0.2284, 0.9045, 0.6671]])
p1: requires_grad = False , gradient: None
p2: requires_grad = True , gradient: tensor([[0.8344, 0.1245, 0.0295, 0.2968, 0.8816]])
Надеюсь, это имеет смысл для вас!