I haven’t checked the complete code, as it’s quite long, but if you are using your custom loss definition note that the operations are not differentiable and will thus detach the loss value from the computation graph:
x = torch.randn(10, requires_grad=True)
ratingOutput = torch.round(x).long()
categoryOutput = torch.argmax(x)
print(ratingOutput.grad_fn)
> None
print(categoryOutput.grad_fn)
> None

As you can see, the .grad_fn are None after the operations used in your loss() definition.