Como pasar varios gradientes a un tensor utilizado en multiples operaciones

Como se vio al final del notebook anterior, si un tensor participa en la creacion de más de un tensor, su gradiente no se acumula, simplemente sobreescribe el gradiente con el ultimo gradiente recibido por el tensor.

Para que un tensor pueda participar en la creacion de más de un tensor y mantener correctamente su gradiente es necesario añadir una nueva funcion y actualizar otras tres.

Primero que nada los gradientes tienen que poder ser acumulables, permitiendo que si un tensor es usado más de una vez, pueda recibir el gradiente de todos sus hijos (tensores que se originan a partir de el). Adicionalmente se debe crear un contador que permite saber el número de gradientes recibidos por cada uno de los hijos o tensores creados a partir de los iniciales. Con este conteo también se previene retropropagar el gradiente del mismo hijo dos veces.

También el método all_children_accounted_for() se utiiza para computar si un tensor recibió el gradiente de todos sus hijos en el grafo

import numpy as np

class Tensor(object):
    
    def __init__(self, data, 
                 autograd=False,
                 creators=None,
                 creation_op=None,
                 id=None):
        '''
        Inicializa un tensor utilizando numpy
        
        @data: una lista de numeros
        @creators: lista de tensores que participarion en la creacion de un nuevo tensor
        @creators_op: la operacion utilizada para combinar los tensores en el nuevo tensor
        '''
        self.data = np.array(data)
        self.creation_op = creation_op
        self.creators = creators
        self.grad = None
        self.autograd = autograd
        self.children = {}
        # se asigna un id al tensor
        if(id is None):
            id = np.random.randint(0,100000)
        self.id = id
        
        # se hace un seguimiento de cuantos hijos tiene un tensor
        # si los creadores no es none
        if (creators is not None):
            # para cada tensor padre
            for c in creators:
                # se verifica si el tensor padre posee el id del tensor hijo
                # en caso de no estar, agrega el id del tensor hijo al tensor padre
                if(self.id not in c.children):
                    c.children[self.id] = 1
                # si el tensor ya se encuentra entre los hijos del padre
                # y vuelve a aparece, se incrementa en uno
                # la cantidad de apariciones del tensor hijo
                else:
                    c.children[self.id] += 1
                    
    def all_children_grads_accounted_for(self, tab='', print_call=True):
        '''
        Verifica si un tensor ha recibido la cantidad 
        correcta de gradientes por cada uno de sus hijos
        '''
        # print('tensor id:', self.id)
        # print(tab+'all_children_grads_accounted_for({})'.format(self.id))
        for id, cnt in self.children.items():
            if (print_call) :
                print(tab+'Tensor actual:', self.id, 'hijo:', id, 'count', cnt)
            if(cnt != 0):
                return False
        return True
        
    def backward(self, grad, grad_origin=None, tab=''):
        '''
        Funcion que propaga recursivamente el gradiente a los creators o padres del tensor
        
        @grad: gradiente 
        @grad_orign
        '''
        # tab=tab
        print(tab+'backward({}, {}, {})'.format(self.id, grad, grad_origin))
        if(self.autograd):
            if(grad_origin is not None):
                print(tab+'El gradiente de',self.id,'proviene de (grad_origin):',grad_origin.id, 'count:', self.children[grad_origin.id])
                # Verifica para asegurar si se puede hacer retropropagacion
                if(self.children[grad_origin.id] == 0):
                    raise Exception("No se puede retropropagar mas de una vez")
                # o si se está esperando un gradiente, en dicho caso se decrementa
                else:
                    # el contador para ese hijo
                    self.children[grad_origin.id] -= 1
                    print(tab+'por tanto el contador de',self.id,'se reduce a', self.children[grad_origin.id], 'para su hijo', grad_origin.id)
        
        # acumula el gradiente de multiples hijos
        if(self.grad is None):
            self.grad = grad
        else:
            self.grad += grad
        
        
        print(tab+'Tensor', self.id, 'has creators?', self.creators is not None,
              '\n'+tab+'All children grads from', self.id,'accounted for is (cnt != 0)', self.all_children_grads_accounted_for(tab=tab, print_call=False),
              '\n'+tab+'Has grad origin?', grad_origin is None,
              '\n'+tab+'Has creators and (children grads accounted or grad no grad origin)',
              '\n'+tab, self.creators is not None, 'and', '(',self.all_children_grads_accounted_for(print_call=False) ,'or',grad_origin is None,') =>',
              self.creators is not None and (self.all_children_grads_accounted_for(print_call=False) or grad_origin is None)
             )
        if(self.creators is not None and
          (self.all_children_grads_accounted_for(print_call=False) or grad_origin is None)):
            
            if (self.creation_op == 'add'):
                # al recibir self.grad, empieza a realizar backprop
                print(tab + str(self.id), 'creators are:')
                print(tab+'creator', self.creators[0].id, ':', self.creators[0], 
                      'creator', self.creators[1].id, ':',self.creators[1])
                print(tab+'\tbackward call from creator[0]:', self.creators[0].id)
                self.creators[0].backward(self.grad, grad_origin=self, tab=tab+'\t')
                print()
                print(tab+'\tbackward call from creator[1]', self.creators[0].id)
                self.creators[1].backward(self.grad, grad_origin=self, tab=tab+'\t')
                
        
    def __add__(self, other):
        '''
        @other: un Tensor
        '''
        if(self.autograd and other.autograd):
            new_tensor = Tensor(self.data + other.data, 
                                autograd=True,
                                creators=[self, other],
                                creation_op='add')
            print('  new tensor id is', new_tensor.id)
            return new_tensor
        return Tensor(self.data + other.data)
    
    
    def __repr__(self):
        return str(self.data.__repr__())
    
    def __str__(self):
        return str(self.data.__str__())

Visualizacion de las llamadas de backward

Se agrego al metodo backward una serie de print()s que permiten ver las llamadas a medida que se propaga el gradiente por cada uno de los tensores que participaron en la creación del tensor final, es decir la salida.

x = Tensor([2,2,2,2], autograd=True)
print('y = x + x')
y = x + x
print('z = y + y')
z = y + y
print()

print('x id:',x.id)
for hijo, cnt in x.children.items():
    print(' hijo:', hijo, 'count', cnt)
    
print('y id:', y.id)
for hijo, cnt in y.children.items():
    print(' hijo:', hijo, 'count', cnt)
    
print('z id:', z.id, '\n')
z.backward(Tensor([1,1,1,1]))
print('\nx gradient data:',x.grad.data)
# z.backward(Tensor([1,1,1,1]))
y = x + x
  new tensor id is 36485
z = y + y
  new tensor id is 39106

x id: 35424
 hijo: 36485 count 2
y id: 36485
 hijo: 39106 count 2
z id: 39106 

backward(39106, [1 1 1 1], None)
Tensor 39106 has creators? True 
All children grads from 39106 accounted for is (cnt != 0) True 
Has grad origin? True 
Has creators and (children grads accounted or grad no grad origin) 
 True and ( True or True ) => True
39106 creators are:
creator 36485 : [4 4 4 4] creator 36485 : [4 4 4 4]
	backward call from creator[0]: 36485
	backward(36485, [1 1 1 1], [8 8 8 8])
	El gradiente de 36485 proviene de (grad_origin): 39106 count: 2
	por tanto el contador de 36485 se reduce a 1 para su hijo 39106
	Tensor 36485 has creators? True 
	All children grads from 36485 accounted for is (cnt != 0) False 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( False or False ) => False

	backward call from creator[1] 36485
	backward(36485, [1 1 1 1], [8 8 8 8])
	El gradiente de 36485 proviene de (grad_origin): 39106 count: 1
	por tanto el contador de 36485 se reduce a 0 para su hijo 39106
	Tensor 36485 has creators? True 
	All children grads from 36485 accounted for is (cnt != 0) True 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( True or False ) => True
	36485 creators are:
	creator 35424 : [2 2 2 2] creator 35424 : [2 2 2 2]
		backward call from creator[0]: 35424
		backward(35424, [2 2 2 2], [4 4 4 4])
		El gradiente de 35424 proviene de (grad_origin): 36485 count: 2
		por tanto el contador de 35424 se reduce a 1 para su hijo 36485
		Tensor 35424 has creators? False 
		All children grads from 35424 accounted for is (cnt != 0) False 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( False or False ) => False

		backward call from creator[1] 35424
		backward(35424, [2 2 2 2], [4 4 4 4])
		El gradiente de 35424 proviene de (grad_origin): 36485 count: 1
		por tanto el contador de 35424 se reduce a 0 para su hijo 36485
		Tensor 35424 has creators? False 
		All children grads from 35424 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False

x gradient data: [4 4 4 4]
a = Tensor([1,2,3,4,5], autograd=True)
b = Tensor([2,2,2,2,2], autograd=True)
c = Tensor([5,4,3,2,1], autograd=True)

print('d = a + b')
d = a + b
print('e = b + c')
e = b + c
print('f = d + e')
f = d + e
print()

print('a id:',a.id)
for hijo, cnt in a.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('b id:', b.id)
for hijo, cnt in b.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('c id:',c.id)
for hijo, cnt in c.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('d id:', d.id)
for hijo, cnt in d.children.items():
    print('\thijo:', hijo, 'count', cnt)

print('e id:', e.id)
for hijo, cnt in e.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('f id:', f.id, '\n')

f.backward(Tensor(np.array([1,1,1,1,1])))
print(b.grad.data == np.array([2,2,2,2,2]))
d = a + b
  new tensor id is 39565
e = b + c
  new tensor id is 30815
f = d + e
  new tensor id is 74828

a id: 65521
	hijo: 39565 count 1
b id: 59859
	hijo: 39565 count 1
	hijo: 30815 count 1
c id: 55699
	hijo: 30815 count 1
d id: 39565
	hijo: 74828 count 1
e id: 30815
	hijo: 74828 count 1
f id: 74828 

backward(74828, [1 1 1 1 1], None)
Tensor 74828 has creators? True 
All children grads from 74828 accounted for is (cnt != 0) True 
Has grad origin? True 
Has creators and (children grads accounted or grad no grad origin) 
 True and ( True or True ) => True
74828 creators are:
creator 39565 : [3 4 5 6 7] creator 30815 : [7 6 5 4 3]
	backward call from creator[0]: 39565
	backward(39565, [1 1 1 1 1], [10 10 10 10 10])
	El gradiente de 39565 proviene de (grad_origin): 74828 count: 1
	por tanto el contador de 39565 se reduce a 0 para su hijo 74828
	Tensor 39565 has creators? True 
	All children grads from 39565 accounted for is (cnt != 0) True 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( True or False ) => True
	39565 creators are:
	creator 65521 : [1 2 3 4 5] creator 59859 : [2 2 2 2 2]
		backward call from creator[0]: 65521
		backward(65521, [1 1 1 1 1], [3 4 5 6 7])
		El gradiente de 65521 proviene de (grad_origin): 39565 count: 1
		por tanto el contador de 65521 se reduce a 0 para su hijo 39565
		Tensor 65521 has creators? False 
		All children grads from 65521 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False

		backward call from creator[1] 65521
		backward(59859, [1 1 1 1 1], [3 4 5 6 7])
		El gradiente de 59859 proviene de (grad_origin): 39565 count: 1
		por tanto el contador de 59859 se reduce a 0 para su hijo 39565
		Tensor 59859 has creators? False 
		All children grads from 59859 accounted for is (cnt != 0) False 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( False or False ) => False

	backward call from creator[1] 39565
	backward(30815, [1 1 1 1 1], [10 10 10 10 10])
	El gradiente de 30815 proviene de (grad_origin): 74828 count: 1
	por tanto el contador de 30815 se reduce a 0 para su hijo 74828
	Tensor 30815 has creators? True 
	All children grads from 30815 accounted for is (cnt != 0) True 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( True or False ) => True
	30815 creators are:
	creator 59859 : [2 2 2 2 2] creator 55699 : [5 4 3 2 1]
		backward call from creator[0]: 59859
		backward(59859, [1 1 1 1 1], [7 6 5 4 3])
		El gradiente de 59859 proviene de (grad_origin): 30815 count: 1
		por tanto el contador de 59859 se reduce a 0 para su hijo 30815
		Tensor 59859 has creators? False 
		All children grads from 59859 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False

		backward call from creator[1] 59859
		backward(55699, [1 1 1 1 1], [7 6 5 4 3])
		El gradiente de 55699 proviene de (grad_origin): 30815 count: 1
		por tanto el contador de 55699 se reduce a 0 para su hijo 30815
		Tensor 55699 has creators? False 
		All children grads from 55699 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False
[ True  True  True  True  True]
x1 = Tensor([1,1,1,1], autograd=True)
x2 = Tensor([1,1,1,1], autograd=True)

print('x3 = x1 + x2')
x3 = x1 + x2
print('x4 = x1 + x2')
x4 = x1 + x2
print('x5 = x1+ x2 + x3 + x4')
x5 = x1+ x2 + x3 + x4
print()

print('x1 id:',x1.id)
for hijo, cnt in x1.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('x2 id:', x2.id)
for hijo, cnt in x2.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('x3 id:',x3.id)
for hijo, cnt in x3.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('x4 id:', x4.id)
for hijo, cnt in x4.children.items():
    print('\thijo:', hijo, 'count', cnt)
    
print('x5 id:', x5.id, '\n')

x5.backward(Tensor([1,1,1,1]))
x3 = x1 + x2
  new tensor id is 7181
x4 = x1 + x2
  new tensor id is 56634
x5 = x1+ x2 + x3 + x4
  new tensor id is 28334
  new tensor id is 45990
  new tensor id is 92166

x1 id: 54166
	hijo: 7181 count 1
	hijo: 56634 count 1
	hijo: 28334 count 1
x2 id: 50546
	hijo: 7181 count 1
	hijo: 56634 count 1
	hijo: 28334 count 1
x3 id: 7181
	hijo: 45990 count 1
x4 id: 56634
	hijo: 92166 count 1
x5 id: 92166 

backward(92166, [1 1 1 1], None)
Tensor 92166 has creators? True 
All children grads from 92166 accounted for is (cnt != 0) True 
Has grad origin? True 
Has creators and (children grads accounted or grad no grad origin) 
 True and ( True or True ) => True
92166 creators are:
creator 45990 : [4 4 4 4] creator 56634 : [2 2 2 2]
	backward call from creator[0]: 45990
	backward(45990, [1 1 1 1], [6 6 6 6])
	El gradiente de 45990 proviene de (grad_origin): 92166 count: 1
	por tanto el contador de 45990 se reduce a 0 para su hijo 92166
	Tensor 45990 has creators? True 
	All children grads from 45990 accounted for is (cnt != 0) True 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( True or False ) => True
	45990 creators are:
	creator 28334 : [2 2 2 2] creator 7181 : [2 2 2 2]
		backward call from creator[0]: 28334
		backward(28334, [1 1 1 1], [4 4 4 4])
		El gradiente de 28334 proviene de (grad_origin): 45990 count: 1
		por tanto el contador de 28334 se reduce a 0 para su hijo 45990
		Tensor 28334 has creators? True 
		All children grads from 28334 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 True and ( True or False ) => True
		28334 creators are:
		creator 54166 : [1 1 1 1] creator 50546 : [1 1 1 1]
			backward call from creator[0]: 54166
			backward(54166, [1 1 1 1], [2 2 2 2])
			El gradiente de 54166 proviene de (grad_origin): 28334 count: 1
			por tanto el contador de 54166 se reduce a 0 para su hijo 28334
			Tensor 54166 has creators? False 
			All children grads from 54166 accounted for is (cnt != 0) False 
			Has grad origin? False 
			Has creators and (children grads accounted or grad no grad origin) 
			 False and ( False or False ) => False

			backward call from creator[1] 54166
			backward(50546, [1 1 1 1], [2 2 2 2])
			El gradiente de 50546 proviene de (grad_origin): 28334 count: 1
			por tanto el contador de 50546 se reduce a 0 para su hijo 28334
			Tensor 50546 has creators? False 
			All children grads from 50546 accounted for is (cnt != 0) False 
			Has grad origin? False 
			Has creators and (children grads accounted or grad no grad origin) 
			 False and ( False or False ) => False

		backward call from creator[1] 28334
		backward(7181, [1 1 1 1], [4 4 4 4])
		El gradiente de 7181 proviene de (grad_origin): 45990 count: 1
		por tanto el contador de 7181 se reduce a 0 para su hijo 45990
		Tensor 7181 has creators? True 
		All children grads from 7181 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 True and ( True or False ) => True
		7181 creators are:
		creator 54166 : [1 1 1 1] creator 50546 : [1 1 1 1]
			backward call from creator[0]: 54166
			backward(54166, [1 1 1 1], [2 2 2 2])
			El gradiente de 54166 proviene de (grad_origin): 7181 count: 1
			por tanto el contador de 54166 se reduce a 0 para su hijo 7181
			Tensor 54166 has creators? False 
			All children grads from 54166 accounted for is (cnt != 0) False 
			Has grad origin? False 
			Has creators and (children grads accounted or grad no grad origin) 
			 False and ( False or False ) => False

			backward call from creator[1] 54166
			backward(50546, [1 1 1 1], [2 2 2 2])
			El gradiente de 50546 proviene de (grad_origin): 7181 count: 1
			por tanto el contador de 50546 se reduce a 0 para su hijo 7181
			Tensor 50546 has creators? False 
			All children grads from 50546 accounted for is (cnt != 0) False 
			Has grad origin? False 
			Has creators and (children grads accounted or grad no grad origin) 
			 False and ( False or False ) => False

	backward call from creator[1] 45990
	backward(56634, [1 1 1 1], [6 6 6 6])
	El gradiente de 56634 proviene de (grad_origin): 92166 count: 1
	por tanto el contador de 56634 se reduce a 0 para su hijo 92166
	Tensor 56634 has creators? True 
	All children grads from 56634 accounted for is (cnt != 0) True 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( True or False ) => True
	56634 creators are:
	creator 54166 : [1 1 1 1] creator 50546 : [1 1 1 1]
		backward call from creator[0]: 54166
		backward(54166, [1 1 1 1], [2 2 2 2])
		El gradiente de 54166 proviene de (grad_origin): 56634 count: 1
		por tanto el contador de 54166 se reduce a 0 para su hijo 56634
		Tensor 54166 has creators? False 
		All children grads from 54166 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False

		backward call from creator[1] 54166
		backward(50546, [1 1 1 1], [2 2 2 2])
		El gradiente de 50546 proviene de (grad_origin): 56634 count: 1
		por tanto el contador de 50546 se reduce a 0 para su hijo 56634
		Tensor 50546 has creators? False 
		All children grads from 50546 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False
x = Tensor([2,2,2,2], autograd=True)

print('y = x + x + x + x')
y = x + x + x + x
print('z = y + y')
z = y + y
print()

print('x id:',x.id)
for hijo, cnt in x.children.items():
    print(' hijo:', hijo, 'count', cnt)
    
print('y id:', y.id)
for hijo, cnt in y.children.items():
    print(' hijo:', hijo, 'count', cnt)
    
print('z id:', z.id, '\n')

z.backward(Tensor([1,1,1,1]))
print('\nx gradient data:',x.grad.data)
y = x + x + x + x
  new tensor id is 88879
  new tensor id is 57091
  new tensor id is 62010
z = y + y
  new tensor id is 83512

x id: 81129
 hijo: 88879 count 2
 hijo: 57091 count 1
 hijo: 62010 count 1
y id: 62010
 hijo: 83512 count 2
z id: 83512 

backward(83512, [1 1 1 1], None)
Tensor 83512 has creators? True 
All children grads from 83512 accounted for is (cnt != 0) True 
Has grad origin? True 
Has creators and (children grads accounted or grad no grad origin) 
 True and ( True or True ) => True
83512 creators are:
creator 62010 : [8 8 8 8] creator 62010 : [8 8 8 8]
	backward call from creator[0]: 62010
	backward(62010, [1 1 1 1], [16 16 16 16])
	El gradiente de 62010 proviene de (grad_origin): 83512 count: 2
	por tanto el contador de 62010 se reduce a 1 para su hijo 83512
	Tensor 62010 has creators? True 
	All children grads from 62010 accounted for is (cnt != 0) False 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( False or False ) => False

	backward call from creator[1] 62010
	backward(62010, [1 1 1 1], [16 16 16 16])
	El gradiente de 62010 proviene de (grad_origin): 83512 count: 1
	por tanto el contador de 62010 se reduce a 0 para su hijo 83512
	Tensor 62010 has creators? True 
	All children grads from 62010 accounted for is (cnt != 0) True 
	Has grad origin? False 
	Has creators and (children grads accounted or grad no grad origin) 
	 True and ( True or False ) => True
	62010 creators are:
	creator 57091 : [6 6 6 6] creator 81129 : [2 2 2 2]
		backward call from creator[0]: 57091
		backward(57091, [2 2 2 2], [8 8 8 8])
		El gradiente de 57091 proviene de (grad_origin): 62010 count: 1
		por tanto el contador de 57091 se reduce a 0 para su hijo 62010
		Tensor 57091 has creators? True 
		All children grads from 57091 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 True and ( True or False ) => True
		57091 creators are:
		creator 88879 : [4 4 4 4] creator 81129 : [2 2 2 2]
			backward call from creator[0]: 88879
			backward(88879, [2 2 2 2], [6 6 6 6])
			El gradiente de 88879 proviene de (grad_origin): 57091 count: 1
			por tanto el contador de 88879 se reduce a 0 para su hijo 57091
			Tensor 88879 has creators? True 
			All children grads from 88879 accounted for is (cnt != 0) True 
			Has grad origin? False 
			Has creators and (children grads accounted or grad no grad origin) 
			 True and ( True or False ) => True
			88879 creators are:
			creator 81129 : [2 2 2 2] creator 81129 : [2 2 2 2]
				backward call from creator[0]: 81129
				backward(81129, [2 2 2 2], [4 4 4 4])
				El gradiente de 81129 proviene de (grad_origin): 88879 count: 2
				por tanto el contador de 81129 se reduce a 1 para su hijo 88879
				Tensor 81129 has creators? False 
				All children grads from 81129 accounted for is (cnt != 0) False 
				Has grad origin? False 
				Has creators and (children grads accounted or grad no grad origin) 
				 False and ( False or False ) => False

				backward call from creator[1] 81129
				backward(81129, [2 2 2 2], [4 4 4 4])
				El gradiente de 81129 proviene de (grad_origin): 88879 count: 1
				por tanto el contador de 81129 se reduce a 0 para su hijo 88879
				Tensor 81129 has creators? False 
				All children grads from 81129 accounted for is (cnt != 0) False 
				Has grad origin? False 
				Has creators and (children grads accounted or grad no grad origin) 
				 False and ( False or False ) => False

			backward call from creator[1] 88879
			backward(81129, [2 2 2 2], [6 6 6 6])
			El gradiente de 81129 proviene de (grad_origin): 57091 count: 1
			por tanto el contador de 81129 se reduce a 0 para su hijo 57091
			Tensor 81129 has creators? False 
			All children grads from 81129 accounted for is (cnt != 0) False 
			Has grad origin? False 
			Has creators and (children grads accounted or grad no grad origin) 
			 False and ( False or False ) => False

		backward call from creator[1] 57091
		backward(81129, [2 2 2 2], [8 8 8 8])
		El gradiente de 81129 proviene de (grad_origin): 62010 count: 1
		por tanto el contador de 81129 se reduce a 0 para su hijo 62010
		Tensor 81129 has creators? False 
		All children grads from 81129 accounted for is (cnt != 0) True 
		Has grad origin? False 
		Has creators and (children grads accounted or grad no grad origin) 
		 False and ( True or False ) => False

x gradient data: [8 8 8 8]