That the validation loss goes up again at some point is pretty normal and is one of your main indicator that your models starts to overfit. So typically you keep track of the model state yielding the lowest validation loss and perform the testing on the best state.
I don’t think you can truly avoid overfitting, at least not with virtually unlimited amount of training data. For most practical cases, there will be differences in the distributions of the training data and validation/test data.
However, even your training loss seem to only go down to half before converging. The only thing I’m not sure about is the max_pool part in your forward() method. What is the semantic intuition here.
Can you try it without it and simple give the last hidden state to the Dropout and Linear Layer? Just curious how the losses will develop using this more standard approach. Since you don’t use a bidirectional LSTM, it should look like this:
out, (h, c) = self.rnn(out, (h0, c0))
out = self.dropout(h[-1])
out = self.fc(out)