This “trick” won’t work as you are breaking the computation graph:
    # a trick to avoid the RuntimeError: Trying to backward through the graph a second 
    # time ...
    y_pred1 = torch.tensor(y_pred1, requires_grad=True)
    loss1 = Loss(y_pred1, Y)
    l1.append(loss1)
    loss1.backward()
    optimizer1.step()

The RuntimeError is real and you could:

accumulate the losses and call backward() once
call backward() on each loss separately and then optimizer.step()

use backward(retain_graph=True) in the first backward call and make sure the forward activations are not stale to compute the gradients for loss1. I would consider this use case an advanced method and would not recommend to use it unless you know exactly that you want this behavior.

In your current code snippet you re avoiding the RuntimeError by detaching y_pred1 from the computation graph by recreating a new tensor with requires_grad=True. While this won’t raise any errors, no gradients will be created in the model parameters since y_pred1 is a new leaf tensor without any gradient history.