# Path: seya/layers/recurrent.py
# class Bidirectional(Recurrent):
#     def __init__(self, forward=None, backward=None, return_sequences=False,
#                  forward_conf=None, backward_conf=None):
#         assert forward is not None or forward_conf is not None, "Must provide a forward RNN or a forward configuration"
#         assert backward is not None or backward_conf is not None, "Must provide a backward RNN or a backward configuration"
#         super(Bidirectional, self).__init__()
#         if forward is not None:
#             self.forward = forward
#         else:
#             # Must import inside the function, because in order to support loading
#             # we must import this module inside layer_utils... ugly
#             from keras.utils.layer_utils import container_from_config
#             self.forward = container_from_config(forward_conf)
#         if backward is not None:
#             self.backward = backward
#         else:
#             from keras.utils.layer_utils import container_from_config
#             self.backward = container_from_config(backward_conf)
#         self.return_sequences = return_sequences
#         self.output_dim = self.forward.output_dim + self.backward.output_dim
# 
#         if not (self.return_sequences == self.forward.return_sequences == self.backward.return_sequences):
#             raise ValueError("Make sure 'return_sequences' is equal for self,"
#                              " forward and backward.")
# 
#     def build(self):
#         self.input = T.tensor3()
#         self.forward.input = self.input
#         self.backward.input = self.input
#         self.forward.build()
#         self.backward.build()
#         self.trainable_weights = self.forward.trainable_weights + self.backward.trainable_weights
# 
#     def set_previous(self, layer):
#         assert self.nb_input == layer.nb_output == 1, "Cannot connect layers: input count and output count should be 1."
#         if hasattr(self, 'input_ndim'):
#             assert self.input_ndim == len(layer.output_shape), "Incompatible shapes: layer expected input with ndim=" +\
#                 str(self.input_ndim) + " but previous layer has output_shape " + str(layer.output_shape)
#         self.forward.set_previous(layer)
#         self.backward.set_previous(layer)
#         self.backward.get_input = types.MethodType(_get_reversed_input, self.backward)
#         self.previous = layer
#         self.build()
# 
#     @property
#     def output_shape(self):
#         input_shape = self.input_shape
#         output_dim = self.output_dim
#         if self.return_sequences:
#             return (input_shape[0], input_shape[1], output_dim)
#         else:
#             return (input_shape[0], output_dim)
# 
#     def get_output(self, train=False):
#         Xf = self.forward.get_output(train)
#         Xb = self.backward.get_output(train)
#         Xb = Xb[::-1]
#         return T.concatenate([Xf, Xb], axis=-1)
# 
#     def get_config(self):
#         return {'name': self.__class__.__name__,
#                 'forward_conf': self.forward.get_config(),
#                 'backward_conf': self.backward.get_config(),
#                 'return_sequences': self.return_sequences}


import numpy as np
from keras.preprocessing import sequence
from keras.optimizers import SGD, RMSprop, Adagrad
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM, GRU
from keras.datasets import imdb
from seya.layers.recurrent import Bidirectional
    Notes:

    - RNNs are tricky. Choice of batch size is important,
    choice of loss and optimizer is critical, etc.
    Some configurations won't converge.

    - LSTM loss decrease patterns during training can be quite different
    from what you see with CNNs/MLPs/etc.

    GPU command:
        THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python imdb_lstm.py
'''

max_features = 20000
maxlen = 100  # cut texts after this number of words (among top max_features most common words)
batch_size = 32

print("Loading data...")
(X_train, y_train), (X_test, y_test) = imdb.load_data(nb_words=max_features, test_split=0.2)
print(len(X_train), 'train sequences')
print(len(X_test), 'test sequences')

print("Pad sequences (samples x time)")
X_train = sequence.pad_sequences(X_train, maxlen=maxlen)
X_test = sequence.pad_sequences(X_test, maxlen=maxlen)
print('X_train shape:', X_train.shape)
print('X_test shape:', X_test.shape)

lstm = LSTM(output_dim=64)
gru = GRU(output_dim=64)  # original examples was 128, we divide by 2 because results will be concatenated

# Path: seya/layers/recurrent.py
# class Bidirectional(Recurrent):
#     def __init__(self, forward=None, backward=None, return_sequences=False,
#                  forward_conf=None, backward_conf=None):
#         assert forward is not None or forward_conf is not None, "Must provide a forward RNN or a forward configuration"
#         assert backward is not None or backward_conf is not None, "Must provide a backward RNN or a backward configuration"
#         super(Bidirectional, self).__init__()
#         if forward is not None:
#             self.forward = forward
#         else:
#             # Must import inside the function, because in order to support loading
#             # we must import this module inside layer_utils... ugly
#             from keras.utils.layer_utils import container_from_config
#             self.forward = container_from_config(forward_conf)
#         if backward is not None:
#             self.backward = backward
#         else:
#             from keras.utils.layer_utils import container_from_config
#             self.backward = container_from_config(backward_conf)
#         self.return_sequences = return_sequences
#         self.output_dim = self.forward.output_dim + self.backward.output_dim
# 
#         if not (self.return_sequences == self.forward.return_sequences == self.backward.return_sequences):
#             raise ValueError("Make sure 'return_sequences' is equal for self,"
#                              " forward and backward.")
# 
#     def build(self):
#         self.input = T.tensor3()
#         self.forward.input = self.input
#         self.backward.input = self.input
#         self.forward.build()
#         self.backward.build()
#         self.trainable_weights = self.forward.trainable_weights + self.backward.trainable_weights
# 
#     def set_previous(self, layer):
#         assert self.nb_input == layer.nb_output == 1, "Cannot connect layers: input count and output count should be 1."
#         if hasattr(self, 'input_ndim'):
#             assert self.input_ndim == len(layer.output_shape), "Incompatible shapes: layer expected input with ndim=" +\
#                 str(self.input_ndim) + " but previous layer has output_shape " + str(layer.output_shape)
#         self.forward.set_previous(layer)
#         self.backward.set_previous(layer)
#         self.backward.get_input = types.MethodType(_get_reversed_input, self.backward)
#         self.previous = layer
#         self.build()
# 
#     @property
#     def output_shape(self):
#         input_shape = self.input_shape
#         output_dim = self.output_dim
#         if self.return_sequences:
#             return (input_shape[0], input_shape[1], output_dim)
#         else:
#             return (input_shape[0], output_dim)
# 
#     def get_output(self, train=False):
#         Xf = self.forward.get_output(train)
#         Xb = self.backward.get_output(train)
#         Xb = Xb[::-1]
#         return T.concatenate([Xf, Xb], axis=-1)
# 
#     def get_config(self):
#         return {'name': self.__class__.__name__,
#                 'forward_conf': self.forward.get_config(),
#                 'backward_conf': self.backward.get_config(),
#                 'return_sequences': self.return_sequences}


# Path: examples/imdb_brnn.py
import numpy as np
from keras.preprocessing import sequence
from keras.optimizers import SGD, RMSprop, Adagrad
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM, GRU
from keras.datasets import imdb
from seya.layers.recurrent import Bidirectional

    Notes:

    - RNNs are tricky. Choice of batch size is important,
    choice of loss and optimizer is critical, etc.
    Some configurations won't converge.

    - LSTM loss decrease patterns during training can be quite different
    from what you see with CNNs/MLPs/etc.

    GPU command:
        THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python imdb_lstm.py
'''

max_features = 20000
maxlen = 100  # cut texts after this number of words (among top max_features most common words)
batch_size = 32

print("Loading data...")
(X_train, y_train), (X_test, y_test) = imdb.load_data(nb_words=max_features, test_split=0.2)
print(len(X_train), 'train sequences')
print(len(X_test), 'test sequences')

print("Pad sequences (samples x time)")
X_train = sequence.pad_sequences(X_train, maxlen=maxlen)
X_test = sequence.pad_sequences(X_test, maxlen=maxlen)
print('X_train shape:', X_train.shape)
print('X_test shape:', X_test.shape)

lstm = LSTM(output_dim=64)
gru = GRU(output_dim=64)  # original examples was 128, we divide by 2 because results will be concatenated

brnn = Bidirectional(forward=lstm, backward=gru)