Skip to content

Reuse a PyTorch GRU or LSTM

Wrap an existing torch.nn.GRUCell or torch.nn.LSTMCell to use it in a ResDAG graph. Its weights and gate equations are preserved; the wrapper adds the normal sequence, state, training and forecasting interfaces.

from torch import nn
from resdag import TorchRecurrentLayer

native = nn.LSTMCell(input_size=3, hidden_size=32)
reservoir = TorchRecurrentLayer(native, feedback_size=2, input_size=1)
# reservoir(feedback, driver): (batch, time, 32)

The native cell's input size must equal feedback_size + input_size. Feedback comes first in the concatenated input. input_size=None means feedback only. The wrapper copies the supplied cell so freezing it does not unexpectedly freeze the caller's original module. It inherits the original dtype/device.

Weights are frozen by default, making this useful for readout experiments on existing recurrent features. Pass trainable=True to train the recurrent weights with a PyTorch optimizer. Freezing a random GRU/LSTM does not guarantee a useful reservoir; compare its validation performance with a plain ESN.

GRU state has width hidden_size. LSTM state packs [hidden | cell] with width 2 * hidden_size; the readout sees only the hidden part. If you use explicit states, keep that packed width. The input projection is computed over the full sequence before the recurrence loop, and the gate updates match the native PyTorch cells.

import torch

feedback = torch.randn(4, 20, 2)
driver = torch.randn(4, 20, 1)
initial = reservoir.cell.init_state(4, feedback.device, feedback.dtype)
features, state = reservoir.forward_stateless([feedback, driver], initial)

Use the standard composition workflow to attach readouts and embed the complete trained graph in a larger model. See the API reference for state methods and the official GRUCell and LSTMCell documentation for native gate definitions.