SAINT: self-attention over features and over rows.
Implements the architecture of Somepalli et al. (2021), SAINT: Improved Neural Networks for Tabular Data via Row Attention and Contrastive Pre-Training. The distinguishing mechanism is intersample attention: after attending across the features of a row, the network attends across the rows of the batch, so a prediction can borrow evidence from neighbouring samples. Without that second stage SAINT would be FT-Transformer.
PyTorch is an optional dependency -- pip install 'tuiml[torch]'. Nothing in this module imports torch until fit is called.
Classes
class algorithms.tabular_deep.saint.SAINTClassifier(_SAINTCore, _DeepTabularClassifierMixin, Classifier)
SAINT: attention across features and across rows of the batch.
Overview
-
Tokenize every feature into a
d_tokenembedding and prepend
[CLS] (the same tokenizer FT-Transformer uses).
- Feature attention: a pre-norm Transformer block over the token
- Intersample attention: flatten each row's tokens into a single
n_tokens * d_token, treat the batch as a sequence of those vectors, and attend over it -- relating whole rows to each other. Reshape back.
-
Repeat for
n_blocksstages and read the[CLS]token.
Theory
Write T^{(i)} \in \mathbb{R}^{t \times d} for the tokens of row i in a batch of size b. Feature attention acts within a row,
while intersample attention acts on the flattened matrix Z \in \mathbb{R}^{b \times td} whose rows are \mathrm{vec}(T^{(i)}):
The two stages differ only in which axis the softmax runs over, and that difference is observable: permuting the other rows of a batch changes a given row's representation under intersample attention, and cannot change it under feature attention alone.
Parameters
d_token
n_heads. The default is tuned for sub-second fits on toy data; real problems want 32-64 (intersample attention makes SAINT more expensive than FT-Transformer at equal width).
n_blocks
n_heads
dropout
learning_rate
weight_decay
batch_size
n_epochs
early_stopping
validation_fraction
early_stopping is enabled.
patience
categorical_features
device
"auto" trades reproducibility for speed.
random_state
Attributes
classes_
fit.
network_
feature_mean_, feature_scale_
loss_curve_
n_iter_
n_features_in_
fit.
Notes
Requires PyTorch. Install with pip install 'tuiml[torch]'. The class constructs and introspects without torch; fit raises ImportError naming the install command.
Predictions depend on the batch. Intersample attention is not a per-row function, so a row's prediction depends on which rows share its batch. Predictions are deterministic for a given X and batch_size because inference batches follow input order, but scoring the same row inside a different set of rows can move it. This is inherent to the architecture, not an implementation artifact.
Complexity. Per epoch, O(n m^2 d + n b m d) -- the second term is intersample attention, quadratic in the batch size b. Memory is O(b^2 + b m^2).
When to use. SAINT pays off when rows are informative about each other -- semi-supervised settings, or data with cluster structure the label respects. When rows are genuinely i.i.d. given the features, FTTransformerClassifier gives similar accuracy for less compute.
References
See Also
Constructing and inspecting a model needs no torch:
>>> from tuiml.algorithms.tabular_deep import SAINTClassifier
>>> model = SAINTClassifier(d_token=8, n_blocks=2, random_state=0)
>>> model.n_blocks
2
>>> "batch_size" in SAINTClassifier.get_parameter_schema()
True
Fitting requires pip install 'tuiml[torch]'; the example below is a
no-op on an install without it:
>>> import numpy as np
>>> from tuiml.utils.torch_backend import has_torch
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(300, 4))
>>> y = ((X[:, 0] > 0) ^ (X[:, 1] > 0)).astype(int)
>>> if has_torch():
... model = SAINTClassifier(n_epochs=150, random_state=0).fit(X, y)
... print(float(model.score(X, y)) > 0.85)
... else:
... print(True)
True
class algorithms.tabular_deep.saint.SAINTRegressor(_SAINTCore, _DeepTabularRegressorMixin, Regressor)
SAINT for regression: feature attention plus intersample attention.
SAINTClassifier. The stack is identical -- tokenizer, alternating feature and row attention, [CLS] readout -- and only the objective changes: one output unit trained with mean squared error against a standardised target.Overview
- Standardise the target.
-
Tokenize the features and prepend
[CLS]. - Alternate feature attention (within a row) and intersample attention
n_blocks stages.
-
Read
[CLS]through a linear head, then undo the standardisation.
Theory
With Z \in \mathbb{R}^{b \times td} the flattened batch of tokenized rows, intersample attention makes the prediction for row i a weighted combination of every row in the batch:
which is a learned kernel regression over the batch, trained jointly with the representation it attends over. The loss is mean squared error on the standardised target (y - \mu_y)/\sigma_y.
Parameters
d_token
n_heads. Real problems want 32-64.
n_blocks
n_heads
dropout
learning_rate
weight_decay
batch_size
n_epochs
early_stopping
validation_fraction
early_stopping is enabled.
patience
categorical_features
device
"auto" trades reproducibility for speed.
random_state
Attributes
network_
target_mean_, target_scale_
feature_mean_, feature_scale_
loss_curve_
n_iter_
n_features_in_
fit.
Notes
Requires PyTorch. Install with pip install 'tuiml[torch]'.
Predictions depend on the batch, because intersample attention is not a per-row function. See the classifier's notes.
Complexity. O(n m^2 d + n b m d) per epoch, with the second term quadratic in batch_size.
When to use. Choose SAINT over FTTransformerRegressor when neighbouring rows carry information about the target -- clustered or grouped data -- and accept the extra compute for it.
References
See Also
>>> from tuiml.algorithms.tabular_deep import SAINTRegressor
>>> model = SAINTRegressor(d_token=8, n_heads=2)
>>> model.d_token
8
>>> "regression" in SAINTRegressor.get_capabilities()
True
Fitting requires pip install 'tuiml[torch]'; the example below is a
no-op on an install without it:
>>> import numpy as np
>>> from tuiml.utils.torch_backend import has_torch
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(300, 4))
>>> y = np.sin(X[:, 0]) * X[:, 1]
>>> if has_torch():
... model = SAINTRegressor(n_epochs=300, random_state=0).fit(X, y)
... print(float(model.score(X, y)) > 0.8)
... else:
... print(True)
True