Feature Definitions
Torch-RecHub provides three core feature classes for different data types.

DenseFeature
Numeric features (e.g., age, income).
from torch_rechub.basic.features import DenseFeature
dense_feature = DenseFeature(name="age", embed_dim=1)Parameters: name, embed_dim. Scalar inputs normally use 1; if the field is already a vector, set it to the actual vector width.
SparseFeature
Categorical features (e.g., city, gender).
from torch_rechub.basic.features import SparseFeature
sparse_feature = SparseFeature(
name="city",
vocab_size=100,
embed_dim=16,
shared_with=None, # share embeddings with another feature if needed
)Parameters: name, vocab_size, embed_dim (auto if None), shared_with, padding_idx, initializer.
SequenceFeature
Sequence or multi-hot features (e.g., behavior history, tags).
from torch_rechub.basic.features import SequenceFeature
sequence_feature = SequenceFeature(
name="user_history",
vocab_size=10000,
embed_dim=32,
pooling="mean", # mean, sum, concat
padding_idx=0, # use 0 for sequence padding
)Parameters: name, vocab_size, embed_dim (auto if None), pooling (mean/sum/concat), shared_with, padding_idx, initializer. mean and sum reduce (batch_size, seq_len, embed_dim) to (batch_size, embed_dim); concat preserves the sequence dimension.
Model Input Contract
Feature objects describe how a model interprets fields; they do not encode raw categories or pad sequences for you. Input dictionaries normally follow this contract:
| Feature type | Typical shape | Value contract |
|---|---|---|
DenseFeature | (batch_size,) or (batch_size, embed_dim) | Convertible to floating point |
SparseFeature | (batch_size,) | Integer indices in [0, vocab_size) |
SequenceFeature | (batch_size, seq_len) | Integer indices padded to a common length before model input |
When sequences are padded with 0, set padding_idx=0 explicitly. Otherwise, the default mask treats -1, not 0, as padding.
With shared_with="item_id", the same EmbeddingLayer feature list must also contain a feature named item_id that creates its own embedding. The shared table determines the effective vocabulary size and embedding dimension.
Embedding Initialization
initializer accepts a callable initializer instance. The built-ins are RandomNormal, RandomUniform, XavierNormal, XavierUniform, and Pretrained; SparseFeature and SequenceFeature default to RandomNormal(0, 0.0001).
import torch
from torch_rechub.basic.features import SparseFeature
from torch_rechub.basic.initializers import Pretrained, XavierUniform
random_feature = SparseFeature(
name="item_id",
vocab_size=1000,
embed_dim=32,
padding_idx=0,
initializer=XavierUniform(gain=1.0),
)
weights = torch.randn(1000, 32)
pretrained_feature = SparseFeature(
name="pretrained_item_id",
vocab_size=1000,
embed_dim=32,
padding_idx=0,
initializer=Pretrained(weights, freeze=False),
)Pretrained requires its weight shape to match vocab_size and embed_dim exactly. freeze=True is the default, so that embedding is not updated during training. The built-in random and Xavier initializers zero the padding_idx row when one is configured.
Feature Instances and Embedding Ownership
Warning:
SparseFeatureandSequenceFeaturecache thenn.Embeddingcreated byget_embedding_layer(). If the same Feature instance is passed to multiple models, those models use the same embedding parameters. Training or loading weights into one model therefore changes the embedding observed by the others.
The two EmbeddingLayer instances below unintentionally share the city embedding:
from torch_rechub.basic.features import SparseFeature
from torch_rechub.basic.layers import EmbeddingLayer
features = [SparseFeature(name="city", vocab_size=100, embed_dim=16)]
embedding_a = EmbeddingLayer(features)
embedding_b = EmbeddingLayer(features)
assert embedding_a.embed_dict["city"] is embedding_b.embed_dict["city"]For independent model comparisons, cross-validation, or ensemble training, create new Feature instances for every model:
def build_features():
return [SparseFeature(name="city", vocab_size=100, embed_dim=16)]
embedding_a = EmbeddingLayer(build_features())
embedding_b = EmbeddingLayer(build_features())
assert embedding_a.embed_dict["city"] is not embedding_b.embed_dict["city"]Copying only the list does not help: features.copy() still contains the original Feature instances. This accidental cross-model sharing is different from explicitly using shared_with to share an embedding inside one model; the latter is intentional.
Usage Example
from torch_rechub.basic.features import DenseFeature, SparseFeature, SequenceFeature
dense_features = [
DenseFeature(name="age", embed_dim=1),
DenseFeature(name="income", embed_dim=1),
]
sparse_features = [
SparseFeature(name="city", vocab_size=100, embed_dim=16),
SparseFeature(name="gender", vocab_size=3, embed_dim=8),
]
sequence_features = [
SequenceFeature(
name="user_history",
vocab_size=10000,
embed_dim=32,
pooling="mean",
padding_idx=0,
),
]
all_features = dense_features + sparse_features + sequence_features