Save_for_backward convertion to tuple

Hello,

I am a pytorch beginner and wanted to implement a really simple module (autograd and module extension) reprensenting an affine transformation.

I am however struggling with my code which returns the following error:
‘Variable’ object has no attribute ‘numpy’

It comes from the fact (I guess) that the type of input is tuple whereas I am expecting input to be a tuple.

I am pretty sure I am doing many stupid things and if somebody can help me / guide me it will be greatly appreciate. Here is the implementation I made (get prepared … sorry)

import torch
from torch.autograd import Function
from torch.autograd import Variable

import numpy as np
from scipy.signal import convolve2d, correlate2d
from torch.nn.modules.module import Module
from torch.nn.parameter import Parameter
##

## Inherit from Function
class RigidTransformationFunction(Function):
	# Both forward and backward are @staticmethods

	def forward(self, input, param):
		# Affine transformation
		# rotation parametrised by 3 rotation angles
		R_ = Rotation3D()
		R_.rot3D(param[0,0], param[0,1], param[0,2])
		t = torch.Tensor(3, 1)
		t[0,0] = param[0,3]
		t[1,0] = param[0,4]
		t[2,0] = param[0,5]
		r = torch.from_numpy(R_.R)
		xyz_ = r @ input.double() + t.double()
		self.save_for_backward(input)
		return torch.DoubleTensor(xyz_)

	def backward(self,param_grad):
		# derived wrt affine parameters
		input = self.saved_tensors
		# rotation parametrised by 3 rotation angles
		R_ = Rotation3D()
		R_.rot3D_grad_rx(param_grad[0,0], param_grad[0,1], param_grad[0,2])
		R_.rot3D_grad_ry(param_grad[0,0], param_grad[0,1], param_grad[0,2])
		R_.rot3D_grad_rz(param_grad[0,0], param_grad[0,1], param_grad[0,2])
		r = torch.from_numpy(R_.R_x)
		grad_rx = r @ input.double()
		r = torch.from_numpy(R_.R_y)			
		grad_ry = r @ input.double()
		r = torch.from_numpy(R_.R_z)			
		grad_rz = r @ input.double()
		grad_tx = 1
		grad_ty = 1
		grad_tz = 1
		return torch.FloatTensor(grad_rx), torch.FloatTensor(grad_ry), torch.FloatTensor(grad_rz), torch.FloatTensor(grad_tx), torch.FloatTensor(grad_ty), torch.FloatTensor(grad_tz)

class RigidTransformation(Module):
	def __init__(self):
		super(RigidTransformation, self).__init__()
		self.form = 'rotation_angle'
		self.param = Parameter(torch.randn(1, 6))

	def forward(self, input):
		return RigidTransformationFunction()(input, self.param)

class Rotation3D:
	def __init__(self):
		self.R = []
		self.R_x = []
		self.R_y = []
		self.R_z = []

	def rot3D(self,rx,ry,rz):
		Rx = np.array( [ [ 1 ,      0       ,     0         ] ,
						  [ 0 , np.cos(rx) , -np.sin( rx ) ] , 
						  [ 0 , np.sin( rx ) ,  np.cos( rx ) ] ] )

		Ry = np.array( [ [ np.cos( ry )  ,     0       ,     np.sin( ry ) ] ,
						  [ 	0 		  , 	1 		, 		  0 	   ] , 
						  [ -np.sin( ry ) , 	0 		,  	  np.cos( ry ) ] ] )

		Rz = np.array( [ [ np.cos( rz )  ,  -np.sin( rz )  ,   0 ] ,
						  [ np.sin( rz )  ,   np.cos( rz )  , 	0 ] , 
						  [ 	0 		  , 	 	0 		,   1 ] ] )

		self.R = Rz.dot(Ry.dot(Rx))

	def rot3D_grad_rx(self,rx,ry,rz):
		Rx = np.matrix( [ [ 0 ,      0        ,     0         ] ,
						  [ 0 , -np.sin( rx ) , -np.cos( rx ) ] , 
						  [ 0 ,  np.cos( rx ) , -np.sin( rx ) ] ] )
		Ry = np.matrix( [ [ np.cos( ry )  ,     0       ,     np.sin( ry ) ] ,
						  [ 	0 		  , 	1 		, 		  0 	   ] , 
						  [ -np.sin( ry ) , 	0 		,  	  np.cos( ry ) ] ] )
		Rz = np.matrix( [ [ np.cos( rz )  ,  -np.sin( rz )  ,   0 ] ,
						  [ np.sin( rz )  ,   np.cos( rz )  , 	0 ] , 
						  [ 	0 		  , 	 	0 		,   1 ] ] )
		self.R_x = Rz.dot(Ry.dot(Rx))

	def rot3D_grad_ry(self,rx,ry,rz):
		Rx = np.matrix( [ [ 1 ,      0       ,     0         ] ,
						  [ 0 , np.cos( rx ) , -np.sin( rx ) ] , 
						  [ 0 , np.sin( rx ) ,  np.cos( rx ) ] ] )
		Ry = np.matrix( [ [ -np.sin( ry )  ,     0       ,     np.cos( ry ) ] ,
						  [ 	0 		   , 	 0 		 , 		  0 	    ] , 
						  [ -np.cos( ry )  , 	 0 		 ,    -np.sin( ry ) ] ] )
		Rz = np.matrix( [ [ np.cos( rz )  ,  -np.sin( rz )  ,   0 ] ,
						  [ np.sin( rz )  ,   np.cos( rz )  , 	0 ] , 
						  [ 	0 		  , 	 	0 		,   1 ] ] )
		self.R_y = Rz.dot(Ry.dot(Rx))

	def rot3D_grad_rz(self,rx,ry,rz):
		Rx = np.matrix( [ [ 1 ,      0       ,     0         ] ,
						  [ 0 , np.cos( rx ) , -np.sin( rx ) ] , 
						  [ 0 , np.sin( rx ) ,  np.cos( rx ) ] ] )
		Ry = np.matrix( [ [ np.cos( ry )  ,     0       ,     np.sin( ry ) ] ,
						  [ 	0 		  , 	1 		, 		  0 	   ] , 
						  [ -np.sin( ry ) , 	0 		,  	  np.cos( ry ) ] ] )
		Rz = np.matrix( [ [ -np.sin( rz )  ,  -np.cos( rz )  ,   0 ] ,
						  [  np.cos( rz )  ,  -np.sin( rz )  , 	 0 ] , 
						  [ 	0 		   , 	 	0 		 ,   0 ] ] )
		self.R_z = Rz.dot(Ry.dot(Rx))

rT = RigidTransformation()
print(list(rT.parameters()))
input = Variable(torch.randn(3, 5), requires_grad=True)
print(input)
output = rT(input)
print(output)
print('______________')
output.backward(torch.randn(8, 8))
print(input.grad)
print('______________')

Because you want to go into numpy and back, the backward function might have to be marked with:

@staticmethod 
@once_differentiable

If you dont do once_differentiable, the input to backward is Variables and not Tensors.
Variables dont have .numpy() defined on them. If x is a Variable, then x.data.numpy() will exist instead.