# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Andrew Hendel. Part of mojolearn, https://doi.org/10.5281/zenodo.22068632
"""Recurrent sequence models of the sequence expansion lane: RNN, LSTM and GRU
layers under a linear head, trained by backpropagation through time on the
GPU (`_mojolearn_x_sequence`) or on the CPU host binding, the same arithmetic
(`sequence/recurrent.mojo`).
The layers are PyTorch's (`nn.RNN`, `nn.LSTM`, `nn.GRU`, batch_first=True):
the same parameter names, shapes, gate orders and initialisation rule
(U(-1/sqrt(H), 1/sqrt(H)) for every weight and bias, the head as
`nn.Linear`), so `state_dict()` loads into a torch module and back. The head
reads the top layer's last hidden state. Regression minimises the mean squared
error, classification the softmax cross-entropy.
Refused by name (sequence/NOT_IMPLEMENTED.tsv): bidirectional, dropout
between layers, proj_size, initial hidden state, packed / variable-length
sequences, float64.
"""
from ._optional_numpy import require_numpy
np = require_numpy('_x_sequence_rnn')
from . import _backend
from ._labels import argmax_rows, unique_inverse
_BINDING = "_mojolearn_x_sequence"
CELLS = {"rnn_tanh": 0, "rnn_relu": 1, "lstm": 2, "gru": 3}
GATES = {0: 1, 1: 1, 2: 4, 3: 3}
OPTIMIZERS = {"sgd": 0, "adam": 1, "adamw": 2, "rmsprop": 3, "adagrad": 4, "lion": 7, "adamax": 8, "nadam": 9}
_OPT_DEFAULTS = {
"sgd": dict(momentum=0.0, dampening=0.0, nesterov=False, weight_decay=0.0),
"adam": dict(betas=(0.9, 0.999), eps=1e-8, weight_decay=0.0),
"adamw": dict(betas=(0.9, 0.999), eps=1e-8, weight_decay=1e-2),
"rmsprop": dict(alpha=0.99, eps=1e-8, weight_decay=0.0, momentum=0.0, centered=False),
"adagrad": dict(lr_decay=0.0, eps=1e-10, weight_decay=0.0, initial_accumulator_value=0.0),
"lion": dict(betas=(0.9, 0.99), weight_decay=0.0),
"adamax": dict(betas=(0.9, 0.999), eps=1e-8, weight_decay=0.0),
"nadam": dict(betas=(0.9, 0.999), eps=1e-8, weight_decay=0.0, momentum_decay=4e-3,
decoupled_weight_decay=False),
}
def binding(numeric_mode=None):
return _backend.binding("_mojolearn_x_sequence", numeric_mode)
def optimizer_arguments(name, options):
"""(kind, flags, [f1, f2, eps, weight_decay, f7, initial_accumulator])
for the binding's optimizer slots, PyTorch's defaults under `options`."""
if name not in OPTIMIZERS:
raise ValueError(f"optimizer must be one of {sorted(OPTIMIZERS)}, got {name!r}")
o = dict(_OPT_DEFAULTS[name])
unknown = set(options or {}) - set(o)
if unknown:
raise ValueError(f"optimizer {name!r} takes no option(s) {sorted(unknown)}; it takes {sorted(o)}")
o.update(options or {})
kind = OPTIMIZERS[name]
if name == "sgd":
if o["nesterov"] and (o["momentum"] <= 0 or o["dampening"] != 0):
raise ValueError("sgd: nesterov needs momentum > 0 and dampening 0 (torch.optim.SGD)")
return kind, int(bool(o["nesterov"])), [o["momentum"], 0.0, 0.0, o["weight_decay"], o["dampening"], 0.0]
if name in ("adam", "adamw", "adamax"):
b1, b2 = o["betas"]
if not (0.0 <= b1 < 1.0 and 0.0 <= b2 < 1.0):
raise ValueError(f"{name}: betas must lie in [0, 1)")
return kind, 0, [b1, b2, o["eps"], o["weight_decay"], 0.0, 0.0]
if name == "nadam":
b1, b2 = o["betas"]
if o["momentum_decay"] < 0:
raise ValueError("nadam: momentum_decay must be >= 0")
return kind, int(bool(o["decoupled_weight_decay"])), [b1, b2, o["eps"], o["weight_decay"],
o["momentum_decay"], 0.0]
if name == "lion":
b1, b2 = o["betas"]
return kind, 0, [b1, b2, 0.0, o["weight_decay"], 0.0, 0.0]
if name == "rmsprop":
return kind, int(bool(o["centered"])), [0.0, o["alpha"], o["eps"], o["weight_decay"], o["momentum"], 0.0]
return kind, 0, [o["lr_decay"], 0.0, o["eps"], o["weight_decay"], 0.0, o["initial_accumulator_value"]]
def _f32(a, name):
a = np.asarray(a)
if a.dtype == np.float64:
raise TypeError(f"{name}: float64 is refused; pass float32 (no float64 on the device)")
return np.ascontiguousarray(a, dtype=np.float32)
class _RecurrentBase:
_CELL = "lstm"
_TASK = 0
def __init__(self, hidden_size=16, num_layers=1, optimizer="adam", learning_rate=1e-3,
batch_size=32, max_epochs=10, shuffle=True, random_state=0,
optimizer_options=None, nonlinearity="tanh", numeric_mode=None,
predict_chunk=4096, lr_schedule=None):
self.hidden_size = hidden_size
self.num_layers = num_layers
self.optimizer = optimizer
self.learning_rate = learning_rate
self.batch_size = batch_size
self.max_epochs = max_epochs
self.shuffle = shuffle
self.random_state = random_state
self.optimizer_options = optimizer_options
self.nonlinearity = nonlinearity
self.numeric_mode = numeric_mode
self.predict_chunk = predict_chunk
self.lr_schedule = lr_schedule
# ------------------------------------------------------------ shape
def _cell(self):
if self._CELL == "rnn":
if self.nonlinearity not in ("tanh", "relu"):
raise ValueError("nonlinearity must be 'tanh' or 'relu' (nn.RNN)")
return CELLS["rnn_" + self.nonlinearity]
return CELLS[self._CELL]
def _ints(self):
return [self._cell(), self.n_features_in_, int(self.hidden_size), int(self.num_layers), self.n_outputs_]
def _layout(self):
"""[(torch name, shape)] in the binding's flat order."""
G, H = GATES[self._cell()], int(self.hidden_size)
out = []
for l in range(int(self.num_layers)): # glue: one entry per recurrent layer
din = self.n_features_in_ if l == 0 else H
out += [(f"weight_ih_l{l}", (G * H, din)), (f"weight_hh_l{l}", (G * H, H)),
(f"bias_ih_l{l}", (G * H,)), (f"bias_hh_l{l}", (G * H,))]
return out + [("head.weight", (self.n_outputs_, H)), ("head.bias", (self.n_outputs_,))]
def _init_params(self, rng):
"""Every parameter U(-1/sqrt(H), 1/sqrt(H)) in layout order, one flat
float32 block drawn in Mojo from the seeded stream (`_buffer.InitStream`;
lane cgr4-py-compute: it was numpy's Generator)."""
k = 1.0 / float(np.sqrt(float(self.hidden_size))) # glue: scalar init bound from hidden_size
total = sum(int(np.prod(s)) for _, s in self._layout()) # glue: parameter tensor sizes of the layout
out = np.empty(total, dtype=np.float32)
rng.fill_uniform(out.ctypes.data, total, -k, k)
return out
def state_dict(self):
"""{torch name: array}: `nn.LSTM`/`nn.GRU`/`nn.RNN` names for the
recurrent layers, `head.weight` and `head.bias` for the `nn.Linear`."""
out, off = {}, 0
for name, shape in self._layout(): # glue: one view per parameter tensor
n = int(np.prod(shape)) # glue: product of the tensor shape dims
out[name] = self.params_[off:off + n].reshape(shape).copy()
off += n
return out
def load_state_dict(self, state):
parts = []
for name, shape in self._layout(): # glue: one check per parameter tensor
a = _f32(state[name], name)
if a.shape != tuple(shape):
raise ValueError(f"{name}: shape {a.shape}, expected {tuple(shape)}")
parts.append(a.ravel())
self.params_ = np.ascontiguousarray(np.concatenate(parts), dtype=np.float32) # glue: packs user weight tensors into the flat buffer
return self
# ------------------------------------------------------------ data
def _check_X(self, X, fitting):
X = _f32(X, "X")
if X.ndim != 3:
raise ValueError("X must be 3-D: (n_samples, n_timesteps, n_features)")
if fitting:
self.n_features_in_ = int(X.shape[2])
elif X.shape[2] != self.n_features_in_:
raise ValueError(f"X has {X.shape[2]} features, the model was fitted on {self.n_features_in_}")
return X
def _schedule(self, n, rng):
"""(epochs, bs, shuffle, seed): the minibatch schedule's scalars.
Every epoch's row order is built on the executor by `rnn_fit` (lane
cpu4-python: `op_mlp_perm`, the MLP's Feistel permutation keyed by
the epoch, or the identity without shuffle; the host Fisher-Yates
`epoch_schedule` is gone); step k of epoch e covers rows
[j bs, j bs + min(bs, n - j bs)) of it, j = k mod ceil(n / bs)."""
bs = max(1, min(int(self.batch_size), n))
epochs = int(self.max_epochs)
if epochs * n >= 2 ** 31 - 1:
raise ValueError(f"max_epochs * n_samples must be below 2^31 - 1, got {epochs} * {n}")
seed = rng.child_seed() if hasattr(rng, "child_seed") else int(rng.integers(0, 2 ** 63, dtype=np.int64))
return epochs, bs, bool(self.shuffle), seed
def _lrs(self, n_steps):
"""The learning rate of every optimizer step: `lr_schedule.lr_at(t)`
(t one-based) when a schedule is set, else `learning_rate`."""
if self.lr_schedule is not None:
table = getattr(self.lr_schedule, "lr_values", None)
got = table(n_steps) if table is not None else None
if got is not None:
return np.frombuffer(got, dtype=np.float32).copy()
return np.asarray([self.lr_schedule.lr_at(t) for t in range(1, n_steps + 1)], dtype=np.float32) # cpu-route: a user schedule object's own lr_at, the explicit input step
return np.full(n_steps, self.learning_rate, dtype=np.float32)
def _fit(self, X, target):
X = self._check_X(X, True)
n, T = int(X.shape[0]), int(X.shape[1])
if n < 1 or T < 1:
raise ValueError("X needs at least one sample and one time step")
from ._buffer import InitStream
rng = InitStream(None if self.random_state is None else int(self.random_state))
self.params_ = self._init_params(rng)
epochs, bs, shuffle, seed = self._schedule(n, rng)
n_steps = epochs * (-(-n // bs))
kind, flags, fp = optimizer_arguments(self.optimizer, self.optimizer_options)
lrs = self._lrs(n_steps)
losses = np.zeros(n_steps, dtype=np.float32)
ip = self._ints() + [self._TASK, n, T, epochs, bs, kind, flags,
int(shuffle), seed & 0xFFFFFFFF, seed >> 32]
b = binding(self.numeric_mode)
b.rnn_fit([X.ctypes.data, target.ctypes.data,
self.params_.ctypes.data, losses.ctypes.data, lrs.ctypes.data],
ip, [float(v) for v in fp]) # glue: the optimizer float arguments
self.loss_curve_ = losses
self.n_iter_ = n_steps
self.n_timesteps_ = T
return self
def _run(self, X, want_seq=False):
X = self._check_X(X, False)
n, T = int(X.shape[0]), int(X.shape[1])
out = np.zeros((n, self.n_outputs_), dtype=np.float32)
seq = np.zeros((n, T, int(self.hidden_size)) if want_seq else (1,), dtype=np.float32)
ip = self._ints() + [self._TASK, n, T, int(self.predict_chunk), int(want_seq)]
binding(self.numeric_mode).rnn_predict(
[X.ctypes.data, self.params_.ctypes.data, out.ctypes.data, seq.ctypes.data], ip)
return out, seq
def hidden_sequence(self, X):
"""The top layer's hidden states h_1..h_T, (n_samples, n_timesteps,
hidden_size): `nn.LSTM(..., batch_first=True)(X)[0]`."""
return self._run(X, want_seq=True)[1]
class _RecurrentRegressor(_RecurrentBase):
_TASK = 0
def fit(self, X, y):
y = _f32(y, "y")
self._y_1d = y.ndim == 1
Y = y.reshape(len(y), -1)
self.n_outputs_ = int(Y.shape[1])
return self._fit(X, np.ascontiguousarray(Y))
def predict(self, X):
out = self._run(X)[0]
return out[:, 0].copy() if self._y_1d else out
class _RecurrentClassifier(_RecurrentBase):
_TASK = 1
def fit(self, X, y):
y = np.asarray(y)
# sorted classes and inverse codes on the device (_labels.unique_inverse)
cls, inv = unique_inverse(y)
self.classes_ = np.asarray(cls).astype(y.dtype, copy=False) if y.dtype.kind in "biuf" else np.asarray(cls)
inv = np.asarray(inv)
if len(self.classes_) < 2:
raise ValueError("classification needs at least two classes")
self.n_outputs_ = int(len(self.classes_))
return self._fit(X, np.ascontiguousarray(inv.astype(np.float32)))
def predict_proba(self, X):
return self._run(X)[0]
def predict(self, X):
# the first-max-wins row argmax in Mojo (`argmax_rows`)
return self.classes_[np.asarray(argmax_rows(np.ascontiguousarray(self.predict_proba(X))))]
[docs]
class LSTMRegressor(_RecurrentRegressor):
"""LSTM sequence regressor: recurrent layers under a linear head.
The recurrent layers are PyTorch's `nn.LSTM` with ``batch_first=True``: the same
parameter names, shapes, gate order and initialization (every weight and bias drawn
from U(-1/sqrt(hidden_size), 1/sqrt(hidden_size)), the head as `nn.Linear`), so
`state_dict()` loads into a torch module and back. A linear head reads the top
layer's last hidden state, and training minimizes the mean squared error by backpropagation
through time.
`X` has shape (n_samples, n_timesteps, n_features) and must be float32 (float64
raises `TypeError`). `y` is (n_samples,) or (n_samples, n_outputs).
Not supported: bidirectional layers, dropout between layers, `proj_size`, an initial
hidden state, and packed or variable-length sequences.
Parameters
----------
hidden_size : int, default 16
Size of the hidden state of each recurrent layer.
num_layers : int, default 1
Number of stacked recurrent layers.
optimizer : {'adam', 'adamw', 'sgd', 'rmsprop', 'adagrad', 'lion', 'adamax', 'nadam'}, default 'adam'
Optimizer, with PyTorch's update rule and defaults.
learning_rate : float, default 1e-3
Learning rate, used when `lr_schedule` is None.
batch_size : int, default 32
Minibatch size (capped at n_samples).
max_epochs : int, default 10
Number of passes over the data. ``max_epochs * n_samples`` must be below 2**31 - 1.
shuffle : bool, default True
Reshuffle the rows every epoch.
random_state : int or None, default 0
Seed of the weight initialization and the shuffle.
optimizer_options : dict or None, default None
Extra optimizer arguments under their PyTorch names (for example
``{'betas': (0.9, 0.99), 'weight_decay': 0.01}``). Unknown names raise `ValueError`.
nonlinearity : str, default 'tanh'
Unused by this model; accepted for a common signature.
numeric_mode : {'identical', 'deterministic', 'fast'} or None, default None
Numeric mode. None uses the library default.
predict_chunk : int, default 4096
Rows processed per chunk at prediction time.
lr_schedule : object or None, default None
A schedule such as `StepLR`, `ExponentialLR` or `OneCycleLR` (anything with a
one-based `lr_at(t)`); it sets the learning rate of every optimizer step.
Attributes
----------
n_features_in_ : int
Number of input features.
n_outputs_ : int
Number of head outputs.
n_timesteps_ : int
Sequence length seen in `fit`.
n_iter_ : int
Number of optimizer steps taken.
loss_curve_ : ndarray
Training loss at every optimizer step.
params_ : ndarray
All parameters as one flat float32 array.
Methods
-------
fit(X, y)
Train the model; returns self.
predict(X)
Predicted values.
hidden_sequence(X)
Top layer's hidden states, (n_samples, n_timesteps, hidden_size), as
``nn.LSTM(..., batch_first=True)(X)[0]``.
state_dict()
Parameters as a dict of arrays under PyTorch names.
load_state_dict(state)
Load parameters from such a dict; returns self.
Examples
--------
>>> import numpy as np, mojolearn
>>> rng = np.random.default_rng(0)
>>> X = rng.random((256, 20, 4), dtype=np.float32)
>>> y = X[:, :, 0].sum(axis=1)
>>> model = mojolearn.LSTMRegressor(hidden_size=32, max_epochs=5).fit(X, y)
>>> pred = model.predict(X)
"""
_CELL = "lstm"
[docs]
class LSTMClassifier(_RecurrentClassifier):
"""LSTM sequence classifier: recurrent layers under a linear head.
The recurrent layers are PyTorch's `nn.LSTM` with ``batch_first=True``: the same
parameter names, shapes, gate order and initialization (every weight and bias drawn
from U(-1/sqrt(hidden_size), 1/sqrt(hidden_size)), the head as `nn.Linear`), so
`state_dict()` loads into a torch module and back. A linear head reads the top
layer's last hidden state, and training minimizes the softmax cross-entropy by backpropagation
through time.
`X` has shape (n_samples, n_timesteps, n_features) and must be float32 (float64
raises `TypeError`). `y` holds class labels (at least two classes).
Not supported: bidirectional layers, dropout between layers, `proj_size`, an initial
hidden state, and packed or variable-length sequences.
Parameters
----------
hidden_size : int, default 16
Size of the hidden state of each recurrent layer.
num_layers : int, default 1
Number of stacked recurrent layers.
optimizer : {'adam', 'adamw', 'sgd', 'rmsprop', 'adagrad', 'lion', 'adamax', 'nadam'}, default 'adam'
Optimizer, with PyTorch's update rule and defaults.
learning_rate : float, default 1e-3
Learning rate, used when `lr_schedule` is None.
batch_size : int, default 32
Minibatch size (capped at n_samples).
max_epochs : int, default 10
Number of passes over the data. ``max_epochs * n_samples`` must be below 2**31 - 1.
shuffle : bool, default True
Reshuffle the rows every epoch.
random_state : int or None, default 0
Seed of the weight initialization and the shuffle.
optimizer_options : dict or None, default None
Extra optimizer arguments under their PyTorch names (for example
``{'betas': (0.9, 0.99), 'weight_decay': 0.01}``). Unknown names raise `ValueError`.
nonlinearity : str, default 'tanh'
Unused by this model; accepted for a common signature.
numeric_mode : {'identical', 'deterministic', 'fast'} or None, default None
Numeric mode. None uses the library default.
predict_chunk : int, default 4096
Rows processed per chunk at prediction time.
lr_schedule : object or None, default None
A schedule such as `StepLR`, `ExponentialLR` or `OneCycleLR` (anything with a
one-based `lr_at(t)`); it sets the learning rate of every optimizer step.
Attributes
----------
classes_ : ndarray
Sorted class labels.
n_features_in_ : int
Number of input features.
n_outputs_ : int
Number of head outputs.
n_timesteps_ : int
Sequence length seen in `fit`.
n_iter_ : int
Number of optimizer steps taken.
loss_curve_ : ndarray
Training loss at every optimizer step.
params_ : ndarray
All parameters as one flat float32 array.
Methods
-------
fit(X, y)
Train the model; returns self.
predict(X)
Predicted class labels.
predict_proba(X)
Class probabilities, (n_samples, n_classes).
hidden_sequence(X)
Top layer's hidden states, (n_samples, n_timesteps, hidden_size), as
``nn.LSTM(..., batch_first=True)(X)[0]``.
state_dict()
Parameters as a dict of arrays under PyTorch names.
load_state_dict(state)
Load parameters from such a dict; returns self.
Examples
--------
>>> import numpy as np, mojolearn
>>> rng = np.random.default_rng(0)
>>> X = rng.random((256, 20, 4), dtype=np.float32)
>>> y = (X[:, -1, 0] > 0.5).astype(np.int64)
>>> model = mojolearn.LSTMClassifier(hidden_size=32, max_epochs=5).fit(X, y)
>>> pred = model.predict(X)
"""
_CELL = "lstm"
[docs]
class GRURegressor(_RecurrentRegressor):
"""GRU sequence regressor: recurrent layers under a linear head.
The GRU gates are r, z, n with ``h' = n + z * (h - n)``.
The recurrent layers are PyTorch's `nn.GRU` with ``batch_first=True``: the same
parameter names, shapes, gate order and initialization (every weight and bias drawn
from U(-1/sqrt(hidden_size), 1/sqrt(hidden_size)), the head as `nn.Linear`), so
`state_dict()` loads into a torch module and back. A linear head reads the top
layer's last hidden state, and training minimizes the mean squared error by backpropagation
through time.
`X` has shape (n_samples, n_timesteps, n_features) and must be float32 (float64
raises `TypeError`). `y` is (n_samples,) or (n_samples, n_outputs).
Not supported: bidirectional layers, dropout between layers, `proj_size`, an initial
hidden state, and packed or variable-length sequences.
Parameters
----------
hidden_size : int, default 16
Size of the hidden state of each recurrent layer.
num_layers : int, default 1
Number of stacked recurrent layers.
optimizer : {'adam', 'adamw', 'sgd', 'rmsprop', 'adagrad', 'lion', 'adamax', 'nadam'}, default 'adam'
Optimizer, with PyTorch's update rule and defaults.
learning_rate : float, default 1e-3
Learning rate, used when `lr_schedule` is None.
batch_size : int, default 32
Minibatch size (capped at n_samples).
max_epochs : int, default 10
Number of passes over the data. ``max_epochs * n_samples`` must be below 2**31 - 1.
shuffle : bool, default True
Reshuffle the rows every epoch.
random_state : int or None, default 0
Seed of the weight initialization and the shuffle.
optimizer_options : dict or None, default None
Extra optimizer arguments under their PyTorch names (for example
``{'betas': (0.9, 0.99), 'weight_decay': 0.01}``). Unknown names raise `ValueError`.
nonlinearity : str, default 'tanh'
Unused by this model; accepted for a common signature.
numeric_mode : {'identical', 'deterministic', 'fast'} or None, default None
Numeric mode. None uses the library default.
predict_chunk : int, default 4096
Rows processed per chunk at prediction time.
lr_schedule : object or None, default None
A schedule such as `StepLR`, `ExponentialLR` or `OneCycleLR` (anything with a
one-based `lr_at(t)`); it sets the learning rate of every optimizer step.
Attributes
----------
n_features_in_ : int
Number of input features.
n_outputs_ : int
Number of head outputs.
n_timesteps_ : int
Sequence length seen in `fit`.
n_iter_ : int
Number of optimizer steps taken.
loss_curve_ : ndarray
Training loss at every optimizer step.
params_ : ndarray
All parameters as one flat float32 array.
Methods
-------
fit(X, y)
Train the model; returns self.
predict(X)
Predicted values.
hidden_sequence(X)
Top layer's hidden states, (n_samples, n_timesteps, hidden_size), as
``nn.GRU(..., batch_first=True)(X)[0]``.
state_dict()
Parameters as a dict of arrays under PyTorch names.
load_state_dict(state)
Load parameters from such a dict; returns self.
Examples
--------
>>> import numpy as np, mojolearn
>>> rng = np.random.default_rng(0)
>>> X = rng.random((256, 20, 4), dtype=np.float32)
>>> y = X[:, :, 0].sum(axis=1)
>>> model = mojolearn.GRURegressor(hidden_size=32, max_epochs=5).fit(X, y)
>>> pred = model.predict(X)
"""
_CELL = "gru"
[docs]
class GRUClassifier(_RecurrentClassifier):
"""GRU sequence classifier: recurrent layers under a linear head.
The recurrent layers are PyTorch's `nn.GRU` with ``batch_first=True``: the same
parameter names, shapes, gate order and initialization (every weight and bias drawn
from U(-1/sqrt(hidden_size), 1/sqrt(hidden_size)), the head as `nn.Linear`), so
`state_dict()` loads into a torch module and back. A linear head reads the top
layer's last hidden state, and training minimizes the softmax cross-entropy by backpropagation
through time.
`X` has shape (n_samples, n_timesteps, n_features) and must be float32 (float64
raises `TypeError`). `y` holds class labels (at least two classes).
Not supported: bidirectional layers, dropout between layers, `proj_size`, an initial
hidden state, and packed or variable-length sequences.
Parameters
----------
hidden_size : int, default 16
Size of the hidden state of each recurrent layer.
num_layers : int, default 1
Number of stacked recurrent layers.
optimizer : {'adam', 'adamw', 'sgd', 'rmsprop', 'adagrad', 'lion', 'adamax', 'nadam'}, default 'adam'
Optimizer, with PyTorch's update rule and defaults.
learning_rate : float, default 1e-3
Learning rate, used when `lr_schedule` is None.
batch_size : int, default 32
Minibatch size (capped at n_samples).
max_epochs : int, default 10
Number of passes over the data. ``max_epochs * n_samples`` must be below 2**31 - 1.
shuffle : bool, default True
Reshuffle the rows every epoch.
random_state : int or None, default 0
Seed of the weight initialization and the shuffle.
optimizer_options : dict or None, default None
Extra optimizer arguments under their PyTorch names (for example
``{'betas': (0.9, 0.99), 'weight_decay': 0.01}``). Unknown names raise `ValueError`.
nonlinearity : str, default 'tanh'
Unused by this model; accepted for a common signature.
numeric_mode : {'identical', 'deterministic', 'fast'} or None, default None
Numeric mode. None uses the library default.
predict_chunk : int, default 4096
Rows processed per chunk at prediction time.
lr_schedule : object or None, default None
A schedule such as `StepLR`, `ExponentialLR` or `OneCycleLR` (anything with a
one-based `lr_at(t)`); it sets the learning rate of every optimizer step.
Attributes
----------
classes_ : ndarray
Sorted class labels.
n_features_in_ : int
Number of input features.
n_outputs_ : int
Number of head outputs.
n_timesteps_ : int
Sequence length seen in `fit`.
n_iter_ : int
Number of optimizer steps taken.
loss_curve_ : ndarray
Training loss at every optimizer step.
params_ : ndarray
All parameters as one flat float32 array.
Methods
-------
fit(X, y)
Train the model; returns self.
predict(X)
Predicted class labels.
predict_proba(X)
Class probabilities, (n_samples, n_classes).
hidden_sequence(X)
Top layer's hidden states, (n_samples, n_timesteps, hidden_size), as
``nn.GRU(..., batch_first=True)(X)[0]``.
state_dict()
Parameters as a dict of arrays under PyTorch names.
load_state_dict(state)
Load parameters from such a dict; returns self.
Examples
--------
>>> import numpy as np, mojolearn
>>> rng = np.random.default_rng(0)
>>> X = rng.random((256, 20, 4), dtype=np.float32)
>>> y = (X[:, -1, 0] > 0.5).astype(np.int64)
>>> model = mojolearn.GRUClassifier(hidden_size=32, max_epochs=5).fit(X, y)
>>> pred = model.predict(X)
"""
_CELL = "gru"
[docs]
class RNNRegressor(_RecurrentRegressor):
"""Elman RNN sequence regressor: recurrent layers under a linear head.
The nonlinearity is 'tanh' or 'relu'.
The recurrent layers are PyTorch's `nn.RNN` with ``batch_first=True``: the same
parameter names, shapes, gate order and initialization (every weight and bias drawn
from U(-1/sqrt(hidden_size), 1/sqrt(hidden_size)), the head as `nn.Linear`), so
`state_dict()` loads into a torch module and back. A linear head reads the top
layer's last hidden state, and training minimizes the mean squared error by backpropagation
through time.
`X` has shape (n_samples, n_timesteps, n_features) and must be float32 (float64
raises `TypeError`). `y` is (n_samples,) or (n_samples, n_outputs).
Not supported: bidirectional layers, dropout between layers, `proj_size`, an initial
hidden state, and packed or variable-length sequences.
Parameters
----------
hidden_size : int, default 16
Size of the hidden state of each recurrent layer.
num_layers : int, default 1
Number of stacked recurrent layers.
optimizer : {'adam', 'adamw', 'sgd', 'rmsprop', 'adagrad', 'lion', 'adamax', 'nadam'}, default 'adam'
Optimizer, with PyTorch's update rule and defaults.
learning_rate : float, default 1e-3
Learning rate, used when `lr_schedule` is None.
batch_size : int, default 32
Minibatch size (capped at n_samples).
max_epochs : int, default 10
Number of passes over the data. ``max_epochs * n_samples`` must be below 2**31 - 1.
shuffle : bool, default True
Reshuffle the rows every epoch.
random_state : int or None, default 0
Seed of the weight initialization and the shuffle.
optimizer_options : dict or None, default None
Extra optimizer arguments under their PyTorch names (for example
``{'betas': (0.9, 0.99), 'weight_decay': 0.01}``). Unknown names raise `ValueError`.
nonlinearity : {'tanh', 'relu'}, default 'tanh'
Activation of the recurrence, as `nn.RNN`.
numeric_mode : {'identical', 'deterministic', 'fast'} or None, default None
Numeric mode. None uses the library default.
predict_chunk : int, default 4096
Rows processed per chunk at prediction time.
lr_schedule : object or None, default None
A schedule such as `StepLR`, `ExponentialLR` or `OneCycleLR` (anything with a
one-based `lr_at(t)`); it sets the learning rate of every optimizer step.
Attributes
----------
n_features_in_ : int
Number of input features.
n_outputs_ : int
Number of head outputs.
n_timesteps_ : int
Sequence length seen in `fit`.
n_iter_ : int
Number of optimizer steps taken.
loss_curve_ : ndarray
Training loss at every optimizer step.
params_ : ndarray
All parameters as one flat float32 array.
Methods
-------
fit(X, y)
Train the model; returns self.
predict(X)
Predicted values.
hidden_sequence(X)
Top layer's hidden states, (n_samples, n_timesteps, hidden_size), as
``nn.RNN(..., batch_first=True)(X)[0]``.
state_dict()
Parameters as a dict of arrays under PyTorch names.
load_state_dict(state)
Load parameters from such a dict; returns self.
Examples
--------
>>> import numpy as np, mojolearn
>>> rng = np.random.default_rng(0)
>>> X = rng.random((256, 20, 4), dtype=np.float32)
>>> y = X[:, :, 0].sum(axis=1)
>>> model = mojolearn.RNNRegressor(hidden_size=32, max_epochs=5).fit(X, y)
>>> pred = model.predict(X)
"""
_CELL = "rnn"
[docs]
class RNNClassifier(_RecurrentClassifier):
"""Elman RNN sequence classifier: recurrent layers under a linear head.
The nonlinearity is 'tanh' or 'relu'.
The recurrent layers are PyTorch's `nn.RNN` with ``batch_first=True``: the same
parameter names, shapes, gate order and initialization (every weight and bias drawn
from U(-1/sqrt(hidden_size), 1/sqrt(hidden_size)), the head as `nn.Linear`), so
`state_dict()` loads into a torch module and back. A linear head reads the top
layer's last hidden state, and training minimizes the softmax cross-entropy by backpropagation
through time.
`X` has shape (n_samples, n_timesteps, n_features) and must be float32 (float64
raises `TypeError`). `y` holds class labels (at least two classes).
Not supported: bidirectional layers, dropout between layers, `proj_size`, an initial
hidden state, and packed or variable-length sequences.
Parameters
----------
hidden_size : int, default 16
Size of the hidden state of each recurrent layer.
num_layers : int, default 1
Number of stacked recurrent layers.
optimizer : {'adam', 'adamw', 'sgd', 'rmsprop', 'adagrad', 'lion', 'adamax', 'nadam'}, default 'adam'
Optimizer, with PyTorch's update rule and defaults.
learning_rate : float, default 1e-3
Learning rate, used when `lr_schedule` is None.
batch_size : int, default 32
Minibatch size (capped at n_samples).
max_epochs : int, default 10
Number of passes over the data. ``max_epochs * n_samples`` must be below 2**31 - 1.
shuffle : bool, default True
Reshuffle the rows every epoch.
random_state : int or None, default 0
Seed of the weight initialization and the shuffle.
optimizer_options : dict or None, default None
Extra optimizer arguments under their PyTorch names (for example
``{'betas': (0.9, 0.99), 'weight_decay': 0.01}``). Unknown names raise `ValueError`.
nonlinearity : {'tanh', 'relu'}, default 'tanh'
Activation of the recurrence, as `nn.RNN`.
numeric_mode : {'identical', 'deterministic', 'fast'} or None, default None
Numeric mode. None uses the library default.
predict_chunk : int, default 4096
Rows processed per chunk at prediction time.
lr_schedule : object or None, default None
A schedule such as `StepLR`, `ExponentialLR` or `OneCycleLR` (anything with a
one-based `lr_at(t)`); it sets the learning rate of every optimizer step.
Attributes
----------
classes_ : ndarray
Sorted class labels.
n_features_in_ : int
Number of input features.
n_outputs_ : int
Number of head outputs.
n_timesteps_ : int
Sequence length seen in `fit`.
n_iter_ : int
Number of optimizer steps taken.
loss_curve_ : ndarray
Training loss at every optimizer step.
params_ : ndarray
All parameters as one flat float32 array.
Methods
-------
fit(X, y)
Train the model; returns self.
predict(X)
Predicted class labels.
predict_proba(X)
Class probabilities, (n_samples, n_classes).
hidden_sequence(X)
Top layer's hidden states, (n_samples, n_timesteps, hidden_size), as
``nn.RNN(..., batch_first=True)(X)[0]``.
state_dict()
Parameters as a dict of arrays under PyTorch names.
load_state_dict(state)
Load parameters from such a dict; returns self.
Examples
--------
>>> import numpy as np, mojolearn
>>> rng = np.random.default_rng(0)
>>> X = rng.random((256, 20, 4), dtype=np.float32)
>>> y = (X[:, -1, 0] > 0.5).astype(np.int64)
>>> model = mojolearn.RNNClassifier(hidden_size=32, max_epochs=5).fit(X, y)
>>> pred = model.predict(X)
"""
_CELL = "rnn"