1818
1919from tzrec .datasets .utils import BASE_DATA_GROUP , Batch
2020from tzrec .features .feature import BaseFeature
21+ from tzrec .metrics .relative_l1 import RelativeL1
2122from tzrec .metrics .unique_ratio import UniqueRatio
2223from tzrec .models .model import BaseModel
2324from tzrec .protos .model_pb2 import ModelConfig
@@ -40,9 +41,9 @@ class BaseSidModel(BaseModel):
4041
4142 Subclasses build their quantizer in ``__init__`` (after calling
4243 ``super().__init__``) and implement :meth:`predict` and :meth:`loss`.
43- They extend :meth:`init_metric` (via ``super()``) and implement
44- :meth:`update_metric` to populate the registered metrics
45- (:meth:`update_train_metric` defaults to a no-op).
44+ :meth:`predict` exposes the reconstruction under ``predictions["x_hat"]``
45+ (only when meaningful) so the shared :meth:`update_metric` can score it.
46+ (:meth:`update_train_metric` defaults to a no-op.)
4647
4748 Args:
4849 model_config (ModelConfig): an instance of ModelConfig.
@@ -69,8 +70,17 @@ def __init__(
6970 self ._input_dim = cfg .input_dim
7071 self ._normalize_residuals = cfg .normalize_residuals
7172
72- assert cfg .codebook , "codebook must be set, e.g. [256, 256, 256]"
73+ if not cfg .codebook :
74+ raise ValueError ("codebook must be set, e.g. [256, 256, 256]" )
7375 self ._n_embed_list = list (cfg .codebook )
76+ # Fail fast: a zero codebook entry / input_dim==0 only errors opaquely
77+ # deep inside faiss, after the whole training pass.
78+ if any (k < 1 for k in self ._n_embed_list ):
79+ raise ValueError (
80+ f"every codebook entry must be >= 1, got { self ._n_embed_list } "
81+ )
82+ if self ._input_dim < 1 :
83+ raise ValueError (f"input_dim must be >= 1, got { self ._input_dim } " )
7484 self ._n_layers = len (self ._n_embed_list )
7585
7686 def _extract_feature (
@@ -99,14 +109,48 @@ def init_loss(self) -> None:
99109 def init_metric (self ) -> None :
100110 """Initialize the eval metrics shared by all SID models.
101111
102- ``mse``: reconstruction error (input vs. quantized / decoded).
103- ``unique_sid_ratio``: mean per-batch unique-SID ratio (distinct rows /
104- batch size; a batch-size-sensitive diversity proxy, not global
105- coverage). Subclasses call ``super().init_metric()`` then add extras.
112+ - ``mse``: reconstruction error (input vs. quantized / decoded).
113+ - ``rel_loss``: symmetric relative-L1 reconstruction error
114+ (:class:`~tzrec.metrics.relative_l1.RelativeL1`); meaningful only with
115+ ``normalize_residuals=False`` (else the reconstruction and the input
116+ live on different scales).
117+ - ``unique_sid_ratio``: mean per-batch unique-SID ratio (distinct rows /
118+ batch size; a batch-size-sensitive diversity proxy, not global
119+ coverage).
120+
121+ Subclasses that add extras call ``super().init_metric()`` first.
106122 """
107123 self ._metric_modules ["mse" ] = torchmetrics .MeanSquaredError ()
124+ self ._metric_modules ["rel_loss" ] = RelativeL1 ()
108125 self ._metric_modules ["unique_sid_ratio" ] = UniqueRatio ()
109126
127+ def update_metric (
128+ self ,
129+ predictions : Dict [str , torch .Tensor ],
130+ batch : Batch ,
131+ losses : Optional [Dict [str , torch .Tensor ]] = None ,
132+ ) -> None :
133+ """Update eval metrics from the reconstruction + the re-extracted input.
134+
135+ ``predictions["x_hat"]`` is the model's reconstruction of the input
136+ embedding (the centroid sum for RQ-KMeans, the decoder output for
137+ RQ-VAE). Subclasses expose it only when it is meaningful, so a
138+ not-yet-fitted model omits it and this logs nothing. The target
139+ embedding is re-extracted from ``batch`` (it is an input, not an output).
140+
141+ Args:
142+ predictions (dict): a dict of predicted result.
143+ batch (Batch): input batch data.
144+ losses (dict, optional): a dict of loss.
145+ """
146+ if "x_hat" not in predictions :
147+ return
148+ recon = predictions ["x_hat" ]
149+ embedding = self ._extract_feature (batch )
150+ self ._metric_modules ["mse" ].update (recon , embedding )
151+ self ._metric_modules ["rel_loss" ].update (recon , embedding )
152+ self ._metric_modules ["unique_sid_ratio" ].update (predictions ["codes" ])
153+
110154 def update_train_metric (
111155 self ,
112156 predictions : Dict [str , torch .Tensor ],
0 commit comments