PyTorch loss decreases even if requires_grad = False for all variables
When I create a neural network with PyTorch, using the torch.nn.Sequential method for defining layers, it seems that the parameters have requires_grad = False by default. However, when I train this network, the loss decreases. How is this possible if the layers are not being updated via gradients?

For example, this is the code for defining my network:

class Network(torch.nn.Module):

def __init__(self):
    super(Network, self).__init__()
    self.layers = torch.nn.Sequential(
        torch.nn.Linear(10, 5),
        torch.nn.Linear(5, 2)
    )
    print('Network Parameters:')
    model_dict = self.state_dict()
    for param_name in model_dict:
        param = model_dict[param_name]
        print('Name: ' + str(param_name))
        print('\tRequires Grad: ' + str(param.requires_grad))

def forward(self, input):
    prediction = self.layers(input)
    return prediction


And this prints out:

Network Parameters:
Name: layers.0.weight
    Requires Grad: False
Name: layers.0.bias
    Requires Grad: False
Name: layers.1.weight
    Requires Grad: False
Name: layers.1.bias
    Requires Grad: False


Then this is the code to train my network:

network = Network()
network.train()
optimiser = torch.optim.SGD(network.parameters(), lr=0.001)
criterion = torch.nn.MSELoss()
inputs = np.random.random([100, 10]).astype(np.float32)
inputs = torch.from_numpy(inputs)
labels = np.random.random([100, 2]).astype(np.float32)
labels = torch.from_numpy(labels)


while True:
    prediction = network.forward(inputs)
    loss = criterion(prediction, labels)
    print('loss = ' + str(loss.item()))
    optimiser.zero_grad()
    loss.backward()
    optimiser.step()


And this prints out:

loss = 0.284633219242
loss = 0.278225809336
loss = 0.271959483624
loss = 0.265835255384
loss = 0.259853869677
loss = 0.254015892744
loss = 0.248321473598
loss = 0.242770522833
loss = 0.237362638116
loss = 0.232097044587
loss = 0.226972639561
loss = 0.221987977624
loss = 0.217141270638
loss = 0.212430402637
loss = 0.207852959633
loss = 0.203406244516
loss = 0.199087426066
loss = 0.19489350915
loss = 0.190821439028
loss = 0.186868071556
loss = 0.183030322194
loss = 0.179305106401
loss = 0.175689414144
loss = 0.172180294991
loss = 0.168774917722
loss = 0.165470585227
loss = 0.162264674902
loss = 0.159154698253


Why is the loss decreasing if all of the parameters have requires_grad = False?

This is interesting -- there seems to be a difference between state_dict() and parameters():

class Network(torch.nn.Module):
    def __init__(self):
        super(Network, self).__init__()
        self.layers = torch.nn.Sequential(
            torch.nn.Linear(10, 5),
            torch.nn.Linear(5, 2)
        )
        print(self.layers[0].weight.requires_grad) # True
        print(self.state_dict()['layers.0.weight'].requires_grad) # False
        print(list(self.parameters())[0].requires_grad) # True

    def forward(self, input):
        prediction = self.layers(input)
        return prediction


So it appears your loss is decreasing because the net is in fact learning, because requires_grad is True. (In general for debugging, I prefer querying the actual objects (self.layers[0]...).

[EDIT] Ahah - found the problem: there's a keep_vars boolean option that you can pass into state_dict that does the following (among other things): (https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/module.py#L665)

for name, param in self._parameters.items():
    if param is not None:
        destination[prefix + name] = param if keep_vars else param.data


so, if you want the actual param, use keep_vars=True -- if you want just the data, use the default keep_vars=False. 

So:

print(self.layers[0].weight.requires_grad) # True
print(self.state_dict(keep_vars=True)['layers.0.weight'].requires_grad) # True
print(list(self.parameters())[0].requires_grad) # True

