You might want to have a look at  previous post of mine 37, where I had a similar question.
In general, you need to double-check with the documentation. According to that:

hidden.shape = (num_layers*num_directions, batch, hidden_size)
layers can be separated using h_n.view(num_layers, num_directions, batch, hidden_size)


So after view() the direction dimension is at index 1. So I would say it should look like this:
hidden = hidden.view(num_layers, 2, batch, hidden_size) # 2 for bidirectional
print("\nForward Hidden: \n", hidden[:, 0, :, :])
print("\nBackward Hidden: \n", hidden[:, 1, :, :])

Similar for the output. According to the documentation:

output.shape = (seq_len, batch, num_directions*hidden_size)
directions can be separated using output.view(seq_len, batch, num_directions, hidden_size)


Here the direction dimension is at index 2, yielding the following:
output = hidden.view(seq_len, batch, 2, hidden_size) # 2 for bidirectional
print("\nForward Outputs: \n", out[:, :, 0, :])
print("\nBackward Outpus: \n", out[:, :, 1, :])

Your current approach for output might be correct, but first splitting num_directions and hidden_size seems cleaner and easier to understand. Your approach for hidden, however, might be wrong since num_directions is not (part of) the last dimensions of hidden.
I hope that gives at least some pointers.