TypeError with optimizer.step()

Hey guys !

I am currently trying to make an implementation of a CNN model with PyTorch and PySyft. But I have an issue that does appear with the optimizer.step() :
“TypeError: add_() takes 1 positional argument but 2 were given”

Here’s my model and the way I launch it :

class Net(Module):   
    def __init__(self):
        super(Net, self).__init__()

        self.cnn_layers = Sequential(
            # Defining a 2D convolution layer
            Conv2d(3, 4, kernel_size=3, stride=1, padding=1),
            BatchNorm2d(4),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2),
            # Defining another 2D convolution layer
            Conv2d(4, 4, kernel_size=4, stride=1, padding=1),
            BatchNorm2d(4),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2),
        )

        self.linear_layers = Sequential(
            Linear(4 * 49 * 49, 1)
        )

    # Defining the forward pass    
    def forward(self, x):
        x = self.cnn_layers(x)
        print("X prev: ", x.shape)
        x = x.view(-1, 4*49*49)
        print("X after: ", x.shape)
        x = self.linear_layers(x)
        return x
        
# defining the model
model = Net()

# defining the optimizer
optimizer = Adam(model.parameters(), lr=0.07)
# defining the loss function
criterion = BCEWithLogitsLoss()
# checking if GPU is available
#if torch.cuda.is_available():
#    model = model.to(device)
#    criterion = criterion.to(device)
#    print("Model & Criterion on GPU")
    
from torchsummary import summary

#vgg = models.vgg16()
#summary(model.cuda(), (3, 200, 200))
print(model)

def trains(epoch):
    model.train()
    for batch_idx, (data,target) in enumerate(train_loader):
        worker = data.location
        model.send(worker)
        #if torch.cuda.is_available():
        #    data = data.to(device)
        #   target = target.to(device)
            
        optimizer.zero_grad()
        # update the model
        output_train = model(data)
        loss = criterion(output_train, target.unsqueeze(1).type_as(output_train))
        loss.backward()
        optimizer.step()
        model.get()
     
        if batch_idx % args.log_interval == 0:
            loss = loss.get()
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * data.shape[0], len(train_loader),
                       100. * batch_idx / len(train_loader), loss.item()))
import time
t = time.time()
for epoch in range(1, args.epochs + 1):
    trains(epoch)

    
total_time = time.time() - t
print('Total', round(total_time, 20), 's')

Are you using the torch.optim.Adam optimizer or a custom implementation?
It seems the error points to the internal update step of the optimizer.

I am using the torch.optim.Adam. Maybe another optimizer would be more appropriate ?

No, you shouldn’t change the optimizer, because of this error message.
I’ve tested your model without PySyft and it’s working fine.

Could you post the complete stack trace, please?

Here it is :

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-df22c32abf0a> in <module>
      2 t = time.time()
      3 for epoch in range(1, args.epochs + 1):
----> 4     trains(epoch)
      5 
      6 

<ipython-input-17-5a23fe3794d1> in trains(epoch)
     13         loss = criterion(output_train, target.unsqueeze(1).type_as(output_train))
     14         loss.backward()
---> 15         optimizer.step()
     16         model.get()
     17 

/opt/conda/lib/python3.6/site-packages/torch/optim/adam.py in step(self, closure)
     93 
     94                 # Decay the first and second moment running average coefficient
---> 95                 exp_avg.mul_(beta1).add_(1 - beta1, grad)
     96                 exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)
     97                 if amsgrad:

/opt/conda/lib/python3.6/site-packages/syft/generic/frameworks/hook/hook.py in overloaded_native_method(self, *args, **kwargs)
    208                 # Send the new command to the appropriate class and get the response
    209                 method = getattr(new_self, method_name)
--> 210                 response = method(*new_args, **new_kwargs)
    211 
    212                 # For inplace methods, just directly return self

/opt/conda/lib/python3.6/site-packages/syft/generic/frameworks/hook/pointers.py in overloaded_pointer_method(self, *args, **kwargs)
     82 
     83             # Send the command
---> 84             response = owner.send_command(location, attr, self, args, kwargs)
     85 
     86             # For inplace methods, just directly return self

/opt/conda/lib/python3.6/site-packages/syft/workers/base.py in send_command(self, recipient, cmd_name, target, args_, kwargs_, return_ids, return_value)
    624                 cmd_name, target, args_, kwargs_, return_ids, return_value
    625             )
--> 626             ret_val = self.send_msg(message, location=recipient)
    627         except ResponseSignatureError as e:
    628             ret_val = None

/opt/conda/lib/python3.6/site-packages/syft/workers/base.py in send_msg(self, message, location)
    272 
    273         # Step 2: send the message and wait for a response
--> 274         bin_response = self._send_msg(bin_message, location)
    275 
    276         # Step 3: deserialize the response

/opt/conda/lib/python3.6/site-packages/syft/workers/virtual.py in _send_msg(self, message, location)
     14             sleep(self.message_pending_time)
     15 
---> 16         return location._recv_msg(message)
     17 
     18     def _recv_msg(self, message: bin) -> bin:

/opt/conda/lib/python3.6/site-packages/syft/workers/virtual.py in _recv_msg(self, message)
     18     def _recv_msg(self, message: bin) -> bin:
     19         """receive message"""
---> 20         return self.recv_msg(message)
     21 
     22     # For backwards compatibility with Udacity course

/opt/conda/lib/python3.6/site-packages/syft/workers/base.py in recv_msg(self, bin_message)
    308 
    309         # Step 2: route message to appropriate function
--> 310         response = self._message_router[type(msg)](msg)
    311 
    312         # Step 3: Serialize the message to simple python objects

/opt/conda/lib/python3.6/site-packages/syft/workers/base.py in execute_tensor_command(self, cmd)
    449     def execute_tensor_command(self, cmd: TensorCommandMessage) -> PointerTensor:
    450         if isinstance(cmd.action, ComputationAction):
--> 451             return self.execute_computation_action(cmd.action)
    452         else:
    453             return self.execute_communication_action(cmd.action)

/opt/conda/lib/python3.6/site-packages/syft/workers/base.py in execute_computation_action(self, action)
    487                 # TODO[jvmancuso]: figure out a good way to generalize the
    488                 # above check (#2530)
--> 489                 getattr(_self, op_name)(*args_, **kwargs_)
    490                 return
    491             else:

/opt/conda/lib/python3.6/site-packages/syft/generic/frameworks/hook/hook.py in overloaded_native_method(self, *args, **kwargs)
    168                 except BaseException as e:
    169                     # we can make some errors more descriptive with this method
--> 170                     raise route_method_exception(e, self, args, kwargs)
    171 
    172             else:  # means that there is a wrapper to remove

/opt/conda/lib/python3.6/site-packages/syft/generic/frameworks/hook/hook.py in overloaded_native_method(self, *args, **kwargs)
    164 
    165                 try:
--> 166                     response = method(*args, **kwargs)
    167 
    168                 except BaseException as e:

TypeError: add_() takes 1 positional argument but 2 were given

Which PyTorch version are you using? Could you create a new virtual environment and install the latest stable version (or nightly)?

I am running PyTorch 1.4 because it is needed for PySyft. So I think if I run the latest stable version on another venv, it won’t make PySyft run

Could you run this code snippet without importing PySyft to check, if PyTorch itself is raising this error:

import torch
import torch.nn as nn

class Net(nn.Module):   
    def __init__(self):
        super(Net, self).__init__()

        self.cnn_layers = nn.Sequential(
            # Defining a 2D convolution layer
            nn.Conv2d(3, 4, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(4),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),
            # Defining another 2D convolution layer
            nn.Conv2d(4, 4, kernel_size=4, stride=1, padding=1),
            nn.BatchNorm2d(4),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),
        )

        self.linear_layers = nn.Sequential(
            nn.Linear(4 * 49 * 49, 1)
        )

    # Defining the forward pass    
    def forward(self, x):
        x = self.cnn_layers(x)
        print("X prev: ", x.shape)
        x = x.view(-1, 4*49*49)
        print("X after: ", x.shape)
        x = self.linear_layers(x)
        return x
        
# defining the model
model = Net()

# defining the optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=0.07)
# defining the loss function
criterion = nn.BCEWithLogitsLoss()

output_train = model(torch.randn(1, 3, 200, 200))
loss = criterion(output_train, torch.zeros(1, 1))
loss.backward()
optimizer.step()

The code is running fine, no error raising.

EDIT : Looks like the solution is to provide one optimizer to each worker in PySyft. An example is given here, in the PySyft tutorials.

Thanks for your help !