Repository: OpenMined/PySyft

Issue:
Runtime Error asking all parameters to have requires_grad=True
**Describe the bug**
I'm trying to finetune a alexnet model and i've set the parameters except for the final layer of the model to requires_grad=False and have created a new classification layer with the desired outputs i want. However the .send() function keeps throwing a runtime error `RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

```
import syft
import torch
from torchvision import models
import torch.nn as nn

hook = syft.TorchHook(torch)
worker = syft.VirtualWorker(hook, id="worker")

model = models.alexnet(pretrained=True)
for param in model.parameters():
    param.requires_grad=False
model.classifier[6] = nn.Linear(model.classifier[6].in_features, 3)
model.send(worker)
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-15-a250859d9a13> in <module>
----> 1 model.send(worker)

~/implementation/PyGrid/gateway/src/syft/syft/frameworks/torch/hook/hook.py in module_send_(nn_self, force_send, *dest, **kwargs)
    608 
    609             if module_is_missing_grad(nn_self):
--> 610                 create_grad_objects(nn_self)
    611 
    612             for p in nn_self.parameters():

~/implementation/PyGrid/gateway/src/syft/syft/frameworks/torch/hook/hook.py in create_grad_objects(model)
    600             for p in model.parameters():
    601                 o = p.sum()
--> 602                 o.backward()
    603                 if p.grad is not None:
    604                     p.grad -= p.grad

~/implementation/PyGrid/gateway/src/syft/syft/generic/frameworks/hook/trace.py in trace_wrapper(*args, **kwargs)
     81                 syft.hook.trace.logs.append((command, response))
     82             else:
---> 83                 response = func(*args, **kwargs)
     84 
     85             return response

~/implementation/PyGrid/gateway/src/syft/syft/generic/frameworks/hook/hook.py in overloaded_native_method(self, *args, **kwargs)
    436                 except BaseException as e:
    437                     # we can make some errors more descriptive with this method
--> 438                     raise route_method_exception(e, self, args, kwargs)
    439 
    440             else:  # means that there is a wrapper to remove

~/implementation/PyGrid/gateway/src/syft/syft/generic/frameworks/hook/hook.py in overloaded_native_method(self, *args, **kwargs)
    432 
    433                 try:
--> 434                     response = method(*args, **kwargs)
    435 
    436                 except BaseException as e:

~/anaconda3/lib/python3.7/site-packages/torch/tensor.py in backward(self, gradient, retain_graph, create_graph)
    193                 products. Defaults to ``False``.
    194         """
--> 195         torch.autograd.backward(self, gradient, retain_graph, create_graph)
    196 
    197     def register_hook(self, hook):

~/anaconda3/lib/python3.7/site-packages/torch/autograd/__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables)
     97     Variable._execution_engine.run_backward(
     98         tensors, grad_tensors, retain_graph, create_graph,
---> 99         allow_unreachable=True)  # allow_unreachable flag
    100 
    101 

RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
 
```


Discussion:
@avinath1998 If you are trying to change the whole **model.classifier** block, then your input dimension (which you put 2) is wrong. The input dimension of **model.classifier** should be 256x6x6.
refer: https://github.com/pytorch/vision/blob/master/torchvision/models/alexnet.py

```
self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )
```
@imraniac Oh shoot, i only added this as an example and didnt see that. Regardless, the error still comes up, ive updated the issue accordingly
Have you still updated the issue with an example? Because it should be 256 * 6 * 6 and not 224
@imraniac updated accordingly, still doesn't work, the error occurs
I will check this.
@tudorcebere, assigned you

Resolving patch:
diff --git a/syft/frameworks/torch/hook/hook.py b/syft/frameworks/torch/hook/hook.py
--- a/syft/frameworks/torch/hook/hook.py
+++ b/syft/frameworks/torch/hook/hook.py
@@ -589,10 +589,11 @@ def module_is_missing_grad(model):
         def create_grad_objects(model):
             """Assigns gradient to model parameters if not assigned"""
             for p in model.parameters():
-                o = p.sum()
-                o.backward()
-                if p.grad is not None:
-                    p.grad -= p.grad
+                if p.requires_grad:  # check if the object requires a grad object
+                    o = p.sum()
+                    o.backward()
+                    if p.grad is not None:
+                        p.grad -= p.grad
 
         def module_send_(nn_self, *dest, force_send=False, **kwargs):
             """Overloads torch.nn instances so that they could be sent to other workers"""


Test patch:
diff --git a/test/torch/hook/test_hook.py b/test/torch/hook/test_hook.py
--- a/test/torch/hook/test_hook.py
+++ b/test/torch/hook/test_hook.py
@@ -1,8 +1,6 @@
 import pytest
 import torch
-
-
-# import syft
+import syft
 
 
 @pytest.mark.skipif(not torch.cuda.is_available(), reason="cuda not available")
@@ -60,3 +58,41 @@ def test_param_data():  # pragma: no cover
     param.data = data2
     assert (param.data == data2).all()
     assert param.is_cuda
+
+
+def test_send_frozen():
+    hook = syft.TorchHook(torch)
+    worker = syft.VirtualWorker(hook, id="worker")
+
+    d_in, h, d_out = 1000, 100, 10
+
+    model = torch.nn.Sequential(
+        torch.nn.Linear(d_in, h), torch.nn.ReLU(), torch.nn.Linear(h, d_out)
+    )
+
+    for param in model.parameters():
+        param.requires_grad = False
+
+    model.send(worker)
+
+
+def test_send_partially_frozen():
+    hook = syft.TorchHook(torch)
+    worker = syft.VirtualWorker(hook, id="worker")
+
+    d_in, h1, h2, d_out = 1000, 1000, 100, 10
+
+    model = torch.nn.Sequential(
+        torch.nn.Linear(d_in, h1),
+        torch.nn.ReLU(),
+        torch.nn.Linear(h1, h2),
+        torch.nn.ReLU(),
+        torch.nn.Linear(h2, d_out),
+    )
+
+    for layer_idx, param in enumerate(model.parameters()):
+        if layer_idx > 2:  # freezing the first two layers
+            pass
+        param.requires_grad = False
+
+    model.send(worker)