What’s the difference between the grad_inputs from register_backward_hook() and the gradients from [x.grad.data for x in network.parameters()]?
class Feedforward(nn.Module):
	def __init__(self, topology):
		super(Feedforward, self).__init__()
		self.input_layer  = nn.Linear(topology['features'], topology['hidden_dim'])
		self.hidden_layer = nn.Linear(topology['hidden_dim'], topology['hidden_dim'])
		self.output_layer = nn.Linear(topology['hidden_dim'], topology['output_dim'])
		self.num_hidden   = topology['hidden_layers']


	def forward(self, x):
		hidden = self.input_layer(x).clamp(min=0)

		for _ in range(self.num_hidden):
			hidden = self.hidden_layer(hidden).clamp(min=0)
			
		return self.output_layer(hidden)


   class Train(object):
	def __init__(self, topology, training):
		self.topology   = topology
		self.training   = training
		self.network    = Feedforward(topology)


	def extract_grads(self):
		return [p.grad.data.numpy()*2 for p in list(self.network.parameters())]


	def update(self, module, grad_in, grad_out):
		grads = self.extract_grads()
		grad_out = grads


	def train(self):
		dh = DataHandler(self.training['data'])

		losses = []
		valid_acc = []
		loss_fn = torch.nn.MSELoss(size_average=False)
		optimizer = torch.optim.Adam(self.network.parameters(), lr=self.training['lr'])

		# training loop
		for x in range(self.training['iterations']):
			batch = dh.get_batch(self.training['batch_size'])
			x = Variable(torch.from_numpy(batch[0]), requires_grad=False)
			y = Variable(torch.from_numpy(batch[1]), requires_grad=False)

			optimizer.zero_grad()
			cost_fn = nn.MSELoss()
			cost = cost_fn(self.network(x), y)
			
			print 'before'
			print list(self.network.parameters())

			self.network.register_backward_hook(self.update)

			cost.backward()

			print 'after'
			print list(self.network.parameters())

			optimizer.step()
			break