You are incorrectly trying taking the output from the last cell by indexing the tensor at the end using lstm_out[-1]. Since lstm_out has the shape (batch, seq_len, num_directions * hidden_size), that means you need to index the second dimension of this tensor and not the first one.
So, the correct indexing should be lstm_out[:,-1,:]. After passing this through linear and relu, you will get a 2D tensor and so you need to artificially add a new dimension by calling y_pred[:,-1,:].unsqueeze(1) to add a new dimension at the second position. So, now your y_pred will have shape (batch, 1, num_directions * hidden_size).
As a rule of thumb, whenever you slice a tensor using a single value index like lstm[:,-1,:] and not a range of indices like lstm[:,:-1,:] (so I am grabbing all tensors from the second dimension except the last one as opposed to in the first one where I am only grabbing the last tensor at the second dimension), you should always remember the dimension of the lstm will decrease by 1 (you lose the second dimension) in the first case and will remain the same in the second case.