Ejemplo n.º 1
0
 def build_dense_layers(self, dense_hid_layers):
     '''
     Builds all of the dense layers in the network and store in a Sequential model
     '''
     self.conv_out_dim = self.get_conv_output_size()
     dims = [self.conv_out_dim] + dense_hid_layers
     dense_model = net_util.build_sequential(dims, self.hid_layers_activation)
     return dense_model
Ejemplo n.º 2
0
 def build_dense_layers(self, dense_hid_layers):
     '''
     Builds all of the dense layers in the network and store in a Sequential model
     '''
     self.conv_out_dim = self.get_conv_output_size()
     dims = [self.conv_out_dim] + dense_hid_layers
     dense_model = net_util.build_sequential(dims, self.hid_layers_activation)
     return dense_model
Ejemplo n.º 3
0
 def build_fc_layers(self, fc_hid_layers):
     '''
     Builds all of the fc layers in the network and store in a Sequential model
     '''
     assert not ps.is_empty(fc_hid_layers)
     dims = [self.conv_out_dim] + fc_hid_layers
     fc_model = net_util.build_sequential(dims, self.hid_layers_activation)
     return fc_model
Ejemplo n.º 4
0
 def build_model_heads(self, in_dim):
     '''Build each model_head. These are stored as Sequential models in model_heads'''
     assert len(self.head_hid_layers) == len(in_dim), 'Hydra head hid_params inconsistent with number in dims'
     model_heads = nn.ModuleList()
     for in_d, hid_layers in zip(in_dim, self.head_hid_layers):
         dims = [in_d] + hid_layers
         model_head = net_util.build_sequential(dims, self.hid_layers_activation)
         model_heads.append(model_head)
     return model_heads
Ejemplo n.º 5
0
Archivo: mlp.py Proyecto: tttor/SLM-Lab
    def __init__(self, net_spec, algorithm, in_dim, out_dim):
        nn.Module.__init__(self)
        Net.__init__(self, net_spec, algorithm, in_dim, out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                clip_grad=False,
                clip_grad_val=1.0,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_decay='no_decay',
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'hid_layers',
            'hid_layers_activation',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'lr_anneal_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])

        # Guard against inappropriate algorithms and environments
        assert net_util.is_q_learning(algorithm)
        # Build model body
        dims = [self.in_dim] + self.hid_layers
        self.model_body = net_util.build_sequential(dims,
                                                    self.hid_layers_activation)
        # output layers
        self.v = nn.Linear(dims[-1], 1)  # state value
        self.adv = nn.Linear(dims[-1],
                             out_dim)  # action dependent raw advantage
        net_util.init_layers(self.modules())
        if torch.cuda.is_available() and self.gpu:
            for module in self.modules():
                module.cuda()
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 6
0
 def build_model_tails(self, out_dim):
     '''Build each model_tail. These are stored as Sequential models in model_tails'''
     model_tails = nn.ModuleList()
     if ps.is_empty(self.tail_hid_layers):
         for out_d in out_dim:
             model_tails.append(nn.Linear(self.body_hid_layers[-1], out_d))
     else:
         assert len(self.tail_hid_layers) == len(out_dim), 'Hydra tail hid_params inconsistent with number out dims'
         for out_d, hid_layers in zip(out_dim, self.tail_hid_layers):
             dims = hid_layers
             model_tail = net_util.build_sequential(dims, self.hid_layers_activation)
             model_tail.add_module(str(len(model_tail)), nn.Linear(dims[-1], out_d))
             model_tails.append(model_tail)
     return model_tails
Ejemplo n.º 7
0
    def __init__(self, net_spec, in_dim, out_dim):
        nn.Module.__init__(self)
        Net.__init__(self, net_spec, in_dim, out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                init_fn=None,
                clip_grad_val=None,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_scheduler_spec=None,
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'shared',
            'hid_layers',
            'hid_layers_activation',
            'init_fn',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_scheduler_spec',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])

        # Guard against inappropriate algorithms and environments
        # Build model body
        dims = [self.in_dim] + self.hid_layers
        self.model_body = net_util.build_sequential(dims,
                                                    self.hid_layers_activation)
        # output layers
        self.v = nn.Linear(dims[-1], 1)  # state value
        self.adv = nn.Linear(dims[-1],
                             out_dim)  # action dependent raw advantage
        net_util.init_layers(self, self.init_fn)
        for module in self.modules():
            module.to(self.device)
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_scheduler = net_util.get_lr_scheduler(self,
                                                      self.lr_scheduler_spec)
Ejemplo n.º 8
0
Archivo: mlp.py Proyecto: tttor/SLM-Lab
    def __init__(self, net_spec, algorithm, in_dim, out_dim):
        nn.Module.__init__(self)
        Net.__init__(self, net_spec, algorithm, in_dim, out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                clip_grad=False,
                clip_grad_val=1.0,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_decay='no_decay',
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'hid_layers',
            'hid_layers_activation',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'lr_anneal_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])

        dims = [self.in_dim] + self.hid_layers
        self.model_body = net_util.build_sequential(dims,
                                                    self.hid_layers_activation)
        # multi-tail output layer with mean and std
        self.model_tails = nn.ModuleList(
            [nn.Linear(dims[-1], out_d) for out_d in out_dim])

        net_util.init_layers(self.modules())
        if torch.cuda.is_available() and self.gpu:
            for module in self.modules():
                module.cuda()
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 9
0
    def __init__(self, net_spec, algorithm, in_dim, out_dim):
        '''
        Multi state processing heads, single shared body, and multi action tails.
        There is one state and action head per body/environment
        Example:

          env 1 state       env 2 state
         _______|______    _______|______
        |    head 1    |  |    head 2    |
        |______________|  |______________|
                |                  |
                |__________________|
         ________________|_______________
        |          Shared body           |
        |________________________________|
                         |
                 ________|_______
                |                |
         _______|______    ______|_______
        |    tail 1    |  |    tail 2    |
        |______________|  |______________|
                |                |
           env 1 action      env 2 action
        '''
        nn.Module.__init__(self)
        super(HydraMLPNet, self).__init__(net_spec, algorithm, in_dim, out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                clip_grad=False,
                clip_grad_val=1.0,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_decay='no_decay',
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'hid_layers',
            'hid_layers_activation',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])
        assert len(
            self.hid_layers
        ) == 3, 'Your hidden layers must specify [*heads], [body], [*tails]. If not, use MLPHeterogenousTails'
        assert isinstance(self.in_dim,
                          list), 'Hydra network needs in_dim as list'
        assert isinstance(self.out_dim,
                          list), 'Hydra network needs out_dim as list'
        self.head_hid_layers = self.hid_layers[0]
        self.body_hid_layers = self.hid_layers[1]
        self.tail_hid_layers = self.hid_layers[2]
        if len(self.head_hid_layers) == 1:
            self.head_hid_layers = self.head_hid_layers * len(self.in_dim)
        if len(self.tail_hid_layers) == 1:
            self.tail_hid_layers = self.tail_hid_layers * len(self.out_dim)

        self.model_heads = self.build_model_heads(in_dim)
        heads_out_dim = np.sum(
            [head_hid_layers[-1] for head_hid_layers in self.head_hid_layers])
        dims = [heads_out_dim] + self.body_hid_layers
        self.model_body = net_util.build_sequential(dims,
                                                    self.hid_layers_activation)
        self.model_tails = self.build_model_tails(out_dim)

        net_util.init_layers(self.modules())
        if torch.cuda.is_available() and self.gpu:
            for module in self.modules():
                module.cuda()
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 10
0
    def __init__(self, net_spec, algorithm, in_dim, out_dim):
        '''
        net_spec:
        hid_layers: list containing dimensions of the hidden layers
        hid_layers_activation: activation function for the hidden layers
        clip_grad: whether to clip the gradient
        clip_grad_val: the clip value
        loss_spec: measure of error between model predictions and correct outputs
        optim_spec: parameters for initializing the optimizer
        lr_decay: function to decay learning rate
        lr_decay_frequency: how many total timesteps per decay
        lr_decay_min_timestep: minimum amount of total timesteps before starting decay
        update_type: method to update network weights: 'replace' or 'polyak'
        update_frequency: how many total timesteps per update
        polyak_coef: ratio of polyak weight update
        gpu: whether to train using a GPU. Note this will only work if a GPU is available, othewise setting gpu=True does nothing

        e.g. net_spec
        "net": {
            "type": "MLPNet",
            "hid_layers": [32],
            "hid_layers_activation": "relu",
            "clip_grad": false,
            "clip_grad_val": 1.0,
            "loss_spec": {
              "name": "MSELoss"
            },
            "optim_spec": {
              "name": "Adam",
              "lr": 0.02
            },
            "lr_decay": "rate_decay",
            "lr_decay_frequency": 500,
            "lr_decay_min_timestep": 1000,
            "update_type": "replace",
            "update_frequency": 1,
            "polyak_coef": 0.9,
            "gpu": true
        }
        '''
        nn.Module.__init__(self)
        super(MLPNet, self).__init__(net_spec, algorithm, in_dim, out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                clip_grad=False,
                clip_grad_val=1.0,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_decay='no_decay',
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'hid_layers',
            'hid_layers_activation',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])

        dims = [self.in_dim] + self.hid_layers
        self.model = net_util.build_sequential(dims,
                                               self.hid_layers_activation)
        # add last layer with no activation
        self.model.add_module(str(len(self.model)),
                              nn.Linear(dims[-1], self.out_dim))

        net_util.init_layers(self.modules())
        if torch.cuda.is_available() and self.gpu:
            for module in self.modules():
                module.cuda()
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 11
0
    def __init__(self, net_spec, in_dim, out_dim):
        '''
        net_spec:
        hid_layers: list containing dimensions of the hidden layers
        hid_layers_activation: activation function for the hidden layers
        init_fn: weight initialization function
        clip_grad_val: clip gradient norm if value is not None
        loss_spec: measure of error between model predictions and correct outputs
        optim_spec: parameters for initializing the optimizer
        lr_scheduler_spec: Pytorch optim.lr_scheduler
        update_type: method to update network weights: 'replace' or 'polyak'
        update_frequency: how many total timesteps per update
        polyak_coef: ratio of polyak weight update
        gpu: whether to train using a GPU. Note this will only work if a GPU is available, othewise setting gpu=True does nothing
        '''
        nn.Module.__init__(self)
        super(MLPNet, self).__init__(net_spec, in_dim, out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                init_fn=None,
                clip_grad_val=None,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_scheduler_spec=None,
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'shared',
            'hid_layers',
            'hid_layers_activation',
            'init_fn',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_scheduler_spec',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])

        dims = [self.in_dim] + self.hid_layers
        self.model = net_util.build_sequential(dims,
                                               self.hid_layers_activation)
        # add last layer with no activation
        # tails. avoid list for single-tail for compute speed
        if ps.is_integer(self.out_dim):
            self.model_tail = nn.Linear(dims[-1], self.out_dim)
        else:
            self.model_tails = nn.ModuleList(
                [nn.Linear(dims[-1], out_d) for out_d in self.out_dim])

        net_util.init_layers(self, self.init_fn)
        for module in self.modules():
            module.to(self.device)
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_scheduler = net_util.get_lr_scheduler(self,
                                                      self.lr_scheduler_spec)
Ejemplo n.º 12
0
    def __init__(self, net_spec, algorithm, in_dim, out_dim):
        '''
        net_spec:
        hid_layers: list containing dimensions of the hidden layers. The last element of the list is should be the dimension of the hidden state for the recurrent layer. The other elements in the list are the dimensions of the MLP (if desired) which is to transform the state space.
        hid_layers_activation: activation function for the state_proc hidden layers
        rnn_hidden_size: rnn hidden_size
        rnn_num_layers: number of recurrent layers
        seq_len: length of the history of being passed to the net
        clip_grad: whether to clip the gradient
        clip_grad_val: the clip value
        loss_spec: measure of error between model predictions and correct outputs
        optim_spec: parameters for initializing the optimizer
        lr_decay: function to decay learning rate
        lr_decay_frequency: how many total timesteps per decay
        lr_decay_min_timestep: minimum amount of total timesteps before starting decay
        update_type: method to update network weights: 'replace' or 'polyak'
        update_frequency: how many total timesteps per update
        polyak_coef: ratio of polyak weight update
        gpu: whether to train using a GPU. Note this will only work if a GPU is available, othewise setting gpu=True does nothing
        '''
        # use generic multi-output for RNN
        out_dim = np.reshape(out_dim, -1).tolist()
        nn.Module.__init__(self)
        super(RecurrentNet, self).__init__(net_spec, algorithm, in_dim,
                                           out_dim)
        # set default
        util.set_attr(
            self,
            dict(
                rnn_num_layers=1,
                clip_grad=False,
                clip_grad_val=1.0,
                loss_spec={'name': 'MSELoss'},
                optim_spec={'name': 'Adam'},
                lr_decay='no_decay',
                update_type='replace',
                update_frequency=1,
                polyak_coef=0.0,
                gpu=False,
            ))
        util.set_attr(self, self.net_spec, [
            'hid_layers',
            'hid_layers_activation',
            'rnn_hidden_size',
            'rnn_num_layers',
            'seq_len',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])
        # state processing model
        state_proc_dims = [self.in_dim] + self.hid_layers
        self.state_proc_model = net_util.build_sequential(
            state_proc_dims, self.hid_layers_activation)

        # RNN model
        self.rnn_input_dim = state_proc_dims[-1]
        self.rnn_model = nn.GRU(input_size=self.rnn_input_dim,
                                hidden_size=self.rnn_hidden_size,
                                num_layers=self.rnn_num_layers,
                                batch_first=True)

        # tails
        self.model_tails = nn.ModuleList(
            [nn.Linear(self.rnn_hidden_size, out_d) for out_d in self.out_dim])

        net_util.init_layers(self.modules())
        if torch.cuda.is_available() and self.gpu:
            for module in self.modules():
                module.cuda()
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 13
0
    def __init__(self, net_spec, algorithm, in_dim, out_dim):
        '''
        net_spec:
        hid_layers: list containing dimensions of the hidden layers. The last element of the list is should be the dimension of the hidden state for the recurrent layer. The other elements in the list are the dimensions of the MLP (if desired) which is to transform the state space.
        hid_layers_activation: activation function for the state_proc hidden layers
        rnn_hidden_size: rnn hidden_size
        rnn_num_layers: number of recurrent layers
        seq_len: length of the history of being passed to the net
        clip_grad: whether to clip the gradient
        clip_grad_val: the clip value
        loss_spec: measure of error between model predictions and correct outputs
        optim_spec: parameters for initializing the optimizer
        lr_decay: function to decay learning rate
        lr_decay_frequency: how many total timesteps per decay
        lr_decay_min_timestep: minimum amount of total timesteps before starting decay
        lr_anneal_timestep: timestep to anneal lr decay
        update_type: method to update network weights: 'replace' or 'polyak'
        update_frequency: how many total timesteps per update
        polyak_coef: ratio of polyak weight update
        gpu: whether to train using a GPU. Note this will only work if a GPU is available, othewise setting gpu=True does nothing
        '''
        # use generic multi-output for RNN
        out_dim = np.reshape(out_dim, -1).tolist()
        nn.Module.__init__(self)
        super(RecurrentNet, self).__init__(net_spec, algorithm, in_dim, out_dim)
        # set default
        util.set_attr(self, dict(
            rnn_num_layers=1,
            clip_grad=False,
            clip_grad_val=1.0,
            loss_spec={'name': 'MSELoss'},
            optim_spec={'name': 'Adam'},
            lr_decay='no_decay',
            update_type='replace',
            update_frequency=1,
            polyak_coef=0.0,
            gpu=False,
        ))
        util.set_attr(self, self.net_spec, [
            'hid_layers',
            'hid_layers_activation',
            'rnn_hidden_size',
            'rnn_num_layers',
            'seq_len',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'lr_anneal_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])
        # state processing model
        state_proc_dims = [self.in_dim] + self.hid_layers
        self.state_proc_model = net_util.build_sequential(state_proc_dims, self.hid_layers_activation)

        # RNN model
        self.rnn_input_dim = state_proc_dims[-1]
        self.rnn_model = nn.GRU(
            input_size=self.rnn_input_dim,
            hidden_size=self.rnn_hidden_size,
            num_layers=self.rnn_num_layers,
            batch_first=True)

        # tails
        self.model_tails = nn.ModuleList([nn.Linear(self.rnn_hidden_size, out_d) for out_d in self.out_dim])

        net_util.init_layers(self.modules())
        if torch.cuda.is_available() and self.gpu:
            for module in self.modules():
                module.cuda()
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 14
0
    def __init__(self, net_spec, in_dim, out_dim):
        '''
        net_spec:
        hid_layers: list containing dimensions of the hidden layers
        hid_layers_activation: activation function for the hidden layers
        init_fn: weight initialization function
        clip_grad: whether to clip the gradient
        clip_grad_val: the clip value
        loss_spec: measure of error between model predictions and correct outputs
        optim_spec: parameters for initializing the optimizer
        lr_decay: function to decay learning rate
        lr_decay_frequency: how many total timesteps per decay
        lr_decay_min_timestep: minimum amount of total timesteps before starting decay
        lr_anneal_timestep: timestep to anneal lr decay
        update_type: method to update network weights: 'replace' or 'polyak'
        update_frequency: how many total timesteps per update
        polyak_coef: ratio of polyak weight update
        gpu: whether to train using a GPU. Note this will only work if a GPU is available, othewise setting gpu=True does nothing
        '''
        nn.Module.__init__(self)
        super(MLPNet, self).__init__(net_spec, in_dim, out_dim)
        # set default
        util.set_attr(self, dict(
            init_fn='xavier_uniform_',
            clip_grad=False,
            clip_grad_val=1.0,
            loss_spec={'name': 'MSELoss'},
            optim_spec={'name': 'Adam'},
            lr_decay='no_decay',
            update_type='replace',
            update_frequency=1,
            polyak_coef=0.0,
            gpu=False,
        ))
        util.set_attr(self, self.net_spec, [
            'separate',
            'hid_layers',
            'hid_layers_activation',
            'init_fn',
            'clip_grad',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_decay',
            'lr_decay_frequency',
            'lr_decay_min_timestep',
            'lr_anneal_timestep',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])

        dims = [self.in_dim] + self.hid_layers
        self.model = net_util.build_sequential(dims, self.hid_layers_activation)
        # add last layer with no activation
        if ps.is_integer(self.out_dim):
            self.model.add_module(str(len(self.model)), nn.Linear(dims[-1], self.out_dim))
        else:  # if more than 1 output, add last layer as tails separate from main model
            self.model_tails = nn.ModuleList([nn.Linear(dims[-1], out_d) for out_d in self.out_dim])

        net_util.init_layers(self, self.init_fn)
        for module in self.modules():
            module.to(self.device)
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_decay = getattr(net_util, self.lr_decay)
Ejemplo n.º 15
0
    def __init__(self, net_spec, in_dim, out_dim):
        '''
        net_spec:
        cell_type: any of RNN, LSTM, GRU
        fc_hid_layers: list of fc layers preceeding the RNN layers
        hid_layers_activation: activation function for the fc hidden layers
        rnn_hidden_size: rnn hidden_size
        rnn_num_layers: number of recurrent layers
        bidirectional: if RNN should be bidirectional
        seq_len: length of the history of being passed to the net
        init_fn: weight initialization function
        clip_grad_val: clip gradient norm if value is not None
        loss_spec: measure of error between model predictions and correct outputs
        optim_spec: parameters for initializing the optimizer
        lr_scheduler_spec: Pytorch optim.lr_scheduler
        update_type: method to update network weights: 'replace' or 'polyak'
        update_frequency: how many total timesteps per update
        polyak_coef: ratio of polyak weight update
        gpu: whether to train using a GPU. Note this will only work if a GPU is available, othewise setting gpu=True does nothing
        '''
        nn.Module.__init__(self)
        super(RecurrentNet, self).__init__(net_spec, in_dim, out_dim)
        # set default
        util.set_attr(self, dict(
            cell_type='GRU',
            rnn_num_layers=1,
            bidirectional=False,
            init_fn=None,
            clip_grad_val=None,
            loss_spec={'name': 'MSELoss'},
            optim_spec={'name': 'Adam'},
            lr_scheduler_spec=None,
            update_type='replace',
            update_frequency=1,
            polyak_coef=0.0,
            gpu=False,
        ))
        util.set_attr(self, self.net_spec, [
            'cell_type',
            'fc_hid_layers',
            'hid_layers_activation',
            'rnn_hidden_size',
            'rnn_num_layers',
            'bidirectional',
            'seq_len',
            'init_fn',
            'clip_grad_val',
            'loss_spec',
            'optim_spec',
            'lr_scheduler_spec',
            'update_type',
            'update_frequency',
            'polyak_coef',
            'gpu',
        ])
        # fc layer: state processing model
        if not ps.is_empty(self.fc_hid_layers):
            fc_dims = [self.in_dim] + self.fc_hid_layers
            self.fc_model = net_util.build_sequential(fc_dims, self.hid_layers_activation)
            self.rnn_input_dim = fc_dims[-1]
        else:
            self.rnn_input_dim = self.in_dim

        # RNN model
        self.rnn_model = getattr(nn, self.cell_type)(
            input_size=self.rnn_input_dim,
            hidden_size=self.rnn_hidden_size,
            num_layers=self.rnn_num_layers,
            batch_first=True, bidirectional=self.bidirectional)

        # tails. avoid list for single-tail for compute speed
        if ps.is_integer(self.out_dim):
            self.model_tail = nn.Linear(self.rnn_hidden_size, self.out_dim)
        else:
            self.model_tails = nn.ModuleList([nn.Linear(self.rnn_hidden_size, out_d) for out_d in self.out_dim])

        net_util.init_layers(self, self.init_fn)
        for module in self.modules():
            module.to(self.device)
        self.loss_fn = net_util.get_loss_fn(self, self.loss_spec)
        self.optim = net_util.get_optim(self, self.optim_spec)
        self.lr_scheduler = net_util.get_lr_scheduler(self, self.lr_scheduler_spec)