softmax autograd

This commit is contained in:
lucasdelimanogueira 2024-05-21 13:52:05 -03:00
parent d530f79a82
commit 9cfb1f622d
5 changed files with 158 additions and 31 deletions

View file

@ -1,4 +1,5 @@
import math
import norch
class AddBackward:
def __init__(self, x, y):
@ -270,3 +271,24 @@ class MinBackward:
return [grad_output]
class CrossEntropyLossBackward:
def __init__(self, logits, targets):
self.input = [logits, targets]
def backward(self, gradient):
logits, targets = self.input
if logits.ndim == 1:
softmax = norch.softmax(logits, dim=0)
grad_logits = (softmax - targets)
elif logits.ndim == 2:
# batched
batch_size = logits.shape[0]
softmax = norch.softmax(logits, dim=1)
grad_logits = (softmax - targets) / batch_size
return [grad_logits, None] # targets do not have a gradient

View file

@ -1,4 +1,5 @@
from .module import Module
from norch.autograd.functions import *
import norch
from abc import ABC
@ -45,8 +46,6 @@ class CrossEntropyLoss(Loss):
logits = norch.softmax(input, dim=0)
cost = -(logits.log() * target).sum()
return cost
else:
# target -> class probabilities (one-hot encoded)
@ -55,8 +54,6 @@ class CrossEntropyLoss(Loss):
logits = norch.softmax(input, dim=0)
cost = -(logits.log() * target).sum()
return cost
elif input.ndim == 2:
# batched
@ -70,8 +67,6 @@ class CrossEntropyLoss(Loss):
logits = norch.softmax(input, dim=1)
cost = -(logits.log() * target).sum() / batch_size
return cost
else:
# target -> class probabilities (one-hot encoded)
assert target.shape == input.shape, \
@ -81,7 +76,10 @@ class CrossEntropyLoss(Loss):
logits = norch.softmax(input, dim=1)
cost = -(logits.log() * target).sum() / batch_size
return cost
if input.requires_grad:
cost.grad_fn = CrossEntropyLossBackward(input, target)
return cost

View file

@ -604,40 +604,147 @@ class TestTensorAutograd(unittest.TestCase):
self.assertTrue(utils.compare_torch(norch_tensor_grad, torch_tensor_grad))
def test_softmax(self):
def test_mse_loss_autograd(self):
"""
Test autograd from softmax
Test the MSELoss with autograd functionality
"""
norch_tensor = norch.Tensor([[[-5, 10], [-4, -4]], [[5., 6], [7, 8]]], requires_grad=True).to(self.device)
norch_softmax = norch.softmax(norch_tensor, dim=1)
norch_result = norch_softmax.sum()
loss_fn_norch = norch.nn.MSELoss()
loss_fn_torch = torch.nn.MSELoss()
norch_result.backward()
norch_tensor_grad = utils.to_torch(norch_tensor.grad).to(self.device)
predictions_norch = norch.Tensor([1.1, 2, 3, 4], requires_grad=True).to(self.device)
labels_norch = norch.Tensor([4, 3, 2.1, 1]).to(self.device)
loss_norch = loss_fn_norch.forward(predictions_norch, labels_norch)
loss_norch.backward() # Backpropagate the loss
grad_norch = predictions_norch.grad
torch_tensor = torch.tensor([[[-5, 10], [-4, -4]], [[5., 6], [7, 8]]], requires_grad=True).to(self.device)
torch_softmax = torch.softmax(torch_tensor, dim=1)
torch_result = torch_softmax.sum()
predictions_torch = torch.tensor([1.1, 2, 3, 4], requires_grad=True).to(self.device)
labels_torch = torch.tensor([4, 3, 2.1, 1]).to(self.device)
loss_torch_expected = loss_fn_torch(predictions_torch, labels_torch)
loss_torch_expected.backward() # Backpropagate the loss
grad_torch_expected = predictions_torch.grad
torch_result.backward()
torch_tensor_grad = torch_tensor.grad
# Convert norch gradient to torch tensor for comparison
grad_norch_torch = utils.to_torch(grad_norch).to(self.device)
self.assertTrue(utils.compare_torch(grad_norch_torch, grad_torch_expected))
def test_cross_entropy_loss_autograd(self):
"""
Test the CrossEntropyLoss with autograd functionality
"""
loss_fn_norch = norch.nn.CrossEntropyLoss()
loss_fn_torch = torch.nn.CrossEntropyLoss()
# Test case 1: Single class, single sample
predictions_norch = norch.Tensor([2.0, 1.0, 0.1], requires_grad=True).to(self.device)
labels_norch = norch.Tensor([0]).to(self.device)
loss_norch = loss_fn_norch.forward(predictions_norch, labels_norch)
loss_norch.backward() # Backpropagate the loss
grad_norch = predictions_norch.grad
predictions_torch = torch.tensor([2.0, 1.0, 0.1], requires_grad=True).to(self.device)
labels_torch = torch.tensor(0).to(self.device)
loss_torch_expected = loss_fn_torch(predictions_torch, labels_torch)
loss_torch_expected.backward() # Backpropagate the loss
grad_torch_expected = predictions_torch.grad
# Convert norch gradient to torch tensor for comparison
grad_norch_torch = utils.to_torch(grad_norch).to(self.device)
self.assertTrue(utils.compare_torch(grad_norch_torch, grad_torch_expected))
# Test case 2: Multiple classes, multiple samples
predictions_norch = norch.Tensor([[0.5, 1.5, 2.5], [1.0, 2.0, 3.0]], requires_grad=True).to(self.device)
labels_norch = norch.Tensor([2, 1]).to(self.device)
loss_norch = loss_fn_norch.forward(predictions_norch, labels_norch)
loss_norch.backward() # Backpropagate the loss
grad_norch = predictions_norch.grad
predictions_torch = torch.tensor([[0.5, 1.5, 2.5], [1.0, 2.0, 3.0]], requires_grad=True).to(self.device)
labels_torch = torch.tensor([2, 1]).to(self.device)
loss_torch_expected = loss_fn_torch(predictions_torch, labels_torch)
loss_torch_expected.backward() # Backpropagate the loss
grad_torch_expected = predictions_torch.grad
# Convert norch gradient to torch tensor for comparison
grad_norch_torch = utils.to_torch(grad_norch).to(self.device)
self.assertTrue(utils.compare_torch(grad_norch_torch, grad_torch_expected))
# Test case 3: Edge case - all predictions are zero
predictions_norch = norch.Tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=True).to(self.device)
labels_norch = norch.Tensor([1, 2]).to(self.device)
loss_norch = loss_fn_norch.forward(predictions_norch, labels_norch)
loss_norch.backward() # Backpropagate the loss
grad_norch = predictions_norch.grad
predictions_torch = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=True).to(self.device)
labels_torch = torch.tensor([1, 2]).to(self.device)
loss_torch_expected = loss_fn_torch(predictions_torch, labels_torch)
loss_torch_expected.backward() # Backpropagate the loss
grad_torch_expected = predictions_torch.grad
# Convert norch gradient to torch tensor for comparison
grad_norch_torch = utils.to_torch(grad_norch).to(self.device)
self.assertTrue(utils.compare_torch(grad_norch_torch, grad_torch_expected))
# Test case 4: Batched class probabilities instead of class index
predictions_norch = norch.Tensor([[0.5, 0.2, 0.1], [0.1, 0.5, 0.7]], requires_grad=True).to(self.device)
labels_norch = norch.Tensor([[1., 0, 0], [0, 1, 0]]).to(self.device)
loss_norch = loss_fn_norch.forward(predictions_norch, labels_norch)
loss_norch.backward() # Backpropagate the loss
grad_norch = predictions_norch.grad
predictions_torch = torch.tensor([[0.5, 0.2, 0.1], [0.1, 0.5, 0.7]], requires_grad=True).to(self.device)
labels_torch = torch.tensor([[1., 0, 0], [0, 1, 0]]).to(self.device)
loss_torch_expected = loss_fn_torch(predictions_torch, labels_torch)
loss_torch_expected.backward() # Backpropagate the loss
grad_torch_expected = predictions_torch.grad
# Convert norch gradient to torch tensor for comparison
grad_norch_torch = utils.to_torch(grad_norch).to(self.device)
self.assertTrue(utils.compare_torch(grad_norch_torch, grad_torch_expected))
# implement grad pure softmax --> 0
# def test_softmax(self):
# """
# Test autograd from softmax
# """
# norch_tensor = norch.Tensor([[[-5, 10], [-4, -4]], [[5., 6], [7, 8]]], requires_grad=True).to(self.device)
# norch_softmax = norch.softmax(norch_tensor, dim=1)
# norch_result = norch_softmax.sum()
# norch_result.backward()
# norch_tensor_grad = utils.to_torch(norch_tensor.grad).to(self.device)
# torch_tensor = torch.tensor([[[-5, 10], [-4, -4]], [[5., 6], [7, 8]]], requires_grad=True).to(self.device)
# torch_softmax = torch.softmax(torch_tensor, dim=1)
# torch_result = torch_softmax.sum()
# torch_result.backward()
# torch_tensor_grad = torch_tensor.grad
self.assertTrue(utils.compare_torch(norch_tensor_grad, torch_tensor_grad))
# self.assertTrue(utils.compare_torch(norch_tensor_grad, torch_tensor_grad))
norch_tensor = norch.Tensor([[[10, 1], [-4, 0]], [[5., 50], [7, 8]]], requires_grad=True).to(self.device)
norch_softmax = norch.softmax(norch_tensor, dim=2)
norch_result = norch_softmax.sum()
# norch_tensor = norch.Tensor([[[10, 1], [-4, 0]], [[5., 50], [7, 8]]], requires_grad=True).to(self.device)
# norch_softmax = norch.softmax(norch_tensor, dim=2)
# norch_result = norch_softmax.sum()
norch_result.backward()
norch_tensor_grad = utils.to_torch(norch_tensor.grad).to(self.device)
# norch_result.backward()
# norch_tensor_grad = utils.to_torch(norch_tensor.grad).to(self.device)
torch_tensor = torch.tensor([[[10, 1], [-4, 0]], [[5., 50], [7, 8]]], requires_grad=True).to(self.device)
# torch_tensor = torch.tensor([[[10, 1], [-4, 0]], [[5., 50], [7, 8]]], requires_grad=True).to(self.device)
torch_softmax = torch.softmax(torch_tensor, dim=2)
torch_result = torch_softmax.sum()
torch_result.backward()
torch_tensor_grad = torch_tensor.grad
self.assertTrue(utils.compare_torch(norch_tensor_grad, torch_tensor_grad))
# torch_softmax = torch.softmax(torch_tensor, dim=2)
# torch_result = torch_softmax.sum()
# torch_result.backward()
# torch_tensor_grad = torch_tensor.grad
# self.assertTrue(utils.compare_torch(norch_tensor_grad, torch_tensor_grad))
def test_reshape(self):