Make automatic insertions of dnn_conv() use the optimal cuDNN code path (2)

Continuation of #2272 that has already been merged.

This also chooses between GpuDnnConv and GpuDnnConvGradW depending on what the original purpose of the convolution was in the graph. For me the gradient wrt. weights is faster than before, but still quite a bit slower than manually using GpuDnnConv. I cannot look into this any more today, but if anyone wants to have a try, please go ahead. The goal would be to find out if it's slower because of extra dimshuffles/copies, or because I did something wrong. Please don't merge before investigating this, and also don't merge before re-running tests.
This op and GpuConv need a \__setstate__ method to allow unpickling old graph:
```
    def __setstate__(self, d):
        self.__dict__.update(d)
        self.direction_hint = d.get('direction_hint', None)
```