Embeddings design
Evovest
Embeddings map each raw column into a representation the backbone consumes. Numerical embeddings act column-wise. A temporal embedding reserves one time column; EmbeddingLayer concatenates that branch last. Piecewise-linear and temporal embeddings need training data at build time (needs_x_train).
Setup
Every block below uses the same scalar
raw —
basis — intermediate
, when the map has one embedding — coordinates
Expanding embeddings use d_embedding=4 and activation=:identity so the geometry is visible; Linear and Periodic default to :relu in the constructor.
e_titles (generic function with 1 method)
Numerical embeddings
Each numerical config is applied independently to every non-time column. Expanding types emit
LinearEmbeddings
The simplest expansion: each embedding coordinate is an independent affine map of the same scalar.
One number :relu would zero the negative half of each coordinate.
conf_linear = LinearEmbeddings(; d_embedding, activation=:identity)
layer_linear, ps_linear, st_linear = setup_embedding(conf_linear)
y_linear = embed_grid(layer_linear, ps_linear, st_linear, xgrid)
embed_point(layer_linear, ps_linear, st_linear, x1)4-element Vector{Float32}:
-0.7141968
0.40148556
0.3199348
-0.26569
PeriodicEmbeddings
Periodic embedding first lifts
The frequencies lite=true shares
K = 2
conf_periodic = PeriodicEmbeddings(;
d_embedding, frequencies=K, frequencies_init_scale=0.4f0, activation=:identity, lite=false
)
layer_periodic, ps_periodic, st_periodic = setup_embedding(conf_periodic)
h_periodic = basis_grid(layer_periodic, ps_periodic, st_periodic, xgrid, :periodic)
y_periodic = embed_grid(layer_periodic, ps_periodic, st_periodic, xgrid)
embed_point(layer_periodic, ps_periodic, st_periodic, x1)4-element Vector{Float32}:
0.68084216
0.6917712
-0.07447859
0.2205313Basis —

Projected embedding:

PiecewiseLinearEmbeddings
Bin edges come from training quantiles. Each bin
is :A (plotted) is that map. Version :B adds a residual linear path :B looks like LinearEmbeddings. This config requires x_train.
conf_piecewise = PiecewiseLinearEmbeddings(; d_embedding, bins=4, activation=:identity, version=:A)
bins = Embeddings.compute_bins(x_train; bins=4)[1]
layer_piecewise, ps_piecewise, st_piecewise = setup_embedding(conf_piecewise; x_train)
h_piecewise = basis_grid(layer_piecewise, ps_piecewise, st_piecewise, xgrid, :encoding)
y_piecewise = embed_grid(layer_piecewise, ps_piecewise, st_piecewise, xgrid)
(; bins, e_x1=embed_point(layer_piecewise, ps_piecewise, st_piecewise, x1))(bins = Float32[-2.0, -1.0, 0.0, 1.0, 2.0], e_x1 = Float32[-0.6851754, 0.52889746, 0.77274066, -0.12734753])Basis — one ramp per bin:

Projected embedding:

BatchNormEmbeddings
Width stays 1: the feature is standardized, then optionally rescaled.
On this figure
conf_batchnorm = BatchNormEmbeddings()
layer_batchnorm, ps_batchnorm, st_batchnorm = setup_embedding(conf_batchnorm)
y_batchnorm = embed_grid(layer_batchnorm, ps_batchnorm, st_batchnorm, xgrid)
embed_point(layer_batchnorm, ps_batchnorm, st_batchnorm, x1)1-element Vector{Float32}:
0.0
IdentityEmbedding
The default numerical embedding: the backbone sees the raw column.
The curve matches the raw figure above; only the series color (embedding purple) changes.
conf_identity = IdentityEmbedding()
layer_identity, ps_identity, st_identity = setup_embedding(conf_identity)
y_identity = embed_grid(layer_identity, ps_identity, st_identity, xgrid)
embed_point(layer_identity, ps_identity, st_identity, x1)1-element Vector{Float32}:
0.5
Temporal embeddings
Temporal embedding is a separate branch on one time column, not a column-wise numerical map. It is always attached through EmbeddingLayer, which routes that column to temp and concatenates the result after the numerical features.
TemporalEmbeddings
Fixed harmonics (not learned frequencies) of periods
Defaults assume a POSIX-seconds column (year, month, week, day). The figure uses short periods so the same EmbeddingLayer with x_train (for trend=true; the last coordinate is the trend.
order = [2, 1]
periods = Float32[2, 4]
conf_temporal = EmbeddingLayer(; temp=TemporalEmbeddings(; index=1, order, periods, trend=true, d_embedding))
layer_temporal, ps_temporal, st_temporal = setup_embedding(conf_temporal; x_train)
h_temporal = temporal_basis(layer_temporal, ps_temporal, st_temporal, xgrid)
y_temporal = embed_grid(layer_temporal, ps_temporal, st_temporal, xgrid)
n_harm = sum(order)
embed_point(layer_temporal, ps_temporal, st_temporal, x1)5-element Vector{Float32}:
0.0
0.0
0.3734188
0.0
0.4313913Basis —

Projected embedding —

EmbeddingLayer
The rest of this report built a numerical config for a single column. A real table has several numeric columns and, sometimes, one time column. EmbeddingLayer is the wrapper that says how those two kinds of columns are treated.
input columns: [ x1 x2 x3 t ]
└─────────┘ │
num temp (temp.index = 4)
│ │
└─ vcat ┘ → backbonenum— applied to every column except the time column. Default:IdentityEmbedding()(pass-through).temp— applied to one column, whose position istemp.index(1-based in the feature list). Default:nothing(no time branch).
You pass this object as embedding_config when fitting; fit then builds the chain. The snippets below call build_embedding_chain only to show widths.
Numerical only (equivalent to passing LinearEmbeddings directly, as earlier sections did):
EmbeddingLayer(; num=LinearEmbeddings(; d_embedding, activation=:identity))EmbeddingLayer{LinearEmbeddings, Nothing}(LinearEmbeddings(4, :identity), nothing)Temporal only — numeric columns pass through; column index is the timestamp:
EmbeddingLayer(; temp=TemporalEmbeddings(; index=1, d_embedding))EmbeddingLayer{IdentityEmbedding, TemporalEmbeddings}(IdentityEmbedding(), TemporalEmbeddings(1, [4, 1, 7, 0], Float32[3.15576f7, 2.6298f6, 604800.0, 86400.0], true, 4))Both — two columns, x1 then t. index=2 means “the second column is time”, not embedding width:
nfeats = 2 # columns in the table
time_column = 2 # t is column 2
conf_combo = EmbeddingLayer(;
num=LinearEmbeddings(; d_embedding, activation=:identity),
temp=TemporalEmbeddings(; index=time_column, order=[2], periods=Float32[4], trend=true, d_embedding),
)
x_train2 = hcat(collect(xgrid), collect(xgrid)) # (n_samples, nfeats): [x1 | t]
layer_combo = Embeddings.build_embedding_chain(conf_combo, nfeats; x_train=x_train2)
# widths: one numeric feature → d_embedding; time → d_embedding projection + 1 trend
n_numeric = nfeats - 1
width_num = n_numeric * d_embedding
width_temp = d_embedding + 1
batch = randn(Float32, nfeats, 4) # (nfeats, batch) dummy input
(;
width_num,
width_temp,
concatenated=width_num + width_temp,
measured=Embeddings.embedding_width(layer_combo, batch, Xoshiro(seed)),
)(width_num = 4, width_temp = 5, concatenated = 9, measured = 9)The second argument of build_embedding_chain is nfeats (how many columns the matrix has), not temp.index. x_train is (n_samples, nfeats).
For hyperparameter search, a Dict is accepted: :embedding_type selects num (:linear, :periodic, :piecewise, :batchnorm, :layernorm, :identity), and :temporal => Dict(:index => 2, ...) builds temp. Unknown keys are ignored.