Repository: Flegyas/latentis (language: python)

Issue:
Add correspondence index



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index dc8c122..6febcdc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
 wandb
 multirun.yaml
 storage
-benchmark
+# ignore only top level benchmark folder
+/benchmark
 
 # ignore the _version.py file
 _version.py
diff --git a/benchmarks/eval_estimator/benchmark.json b/benchmarks/eval_estimator/benchmark.json
new file mode 100644
index 0000000..f6e363b
--- /dev/null
+++ b/benchmarks/eval_estimator/benchmark.json
@@ -0,0 +1,6322 @@
+[
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "svd",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "pca",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "relative",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "estimator": "cca",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "svd",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "pca",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "relative",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "l2",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "cosine",
+        "metric_type": "latent"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "accuracy",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "f1",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "precision",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "recall",
+        "metric_type": "downstream"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "accuracy",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "f1",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "precision",
+        "metric_type": "agreement"
+    },
+    {
+        "fit_correspondence": {
+            "__id": "40747931e14e4c2903a0de863c0d7ad629d9bddd9b2aa367316d2b277bf83337",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "train",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "fit_x_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "fit_y_space": {
+            "__id": "4c043398f82d58c26b301f697bbcdb80fcb8c32c9e8ae257dc22533b39f0b408",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "train"
+        },
+        "test_correspondence": {
+            "__id": "410c0776a7455ab7c653703210a133fcc20a5c8fb486933fc0234f867a6ae119",
+            "_type": "Correspondence",
+            "_version": 0,
+            "quality": 0.0,
+            "split": "test",
+            "x_dataset": "imdb",
+            "y_dataset": "imdb"
+        },
+        "test_x_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "test_y_space": {
+            "__id": "24981cb42ca29ec2db320a379507f3aa2287618c438e5831a9a3d1a0a7160c8e",
+            "_type": "LatentSpace",
+            "_vector_source": "TensorSource",
+            "_version": -42,
+            "dataset": "imdb",
+            "feature": "text",
+            "layer": 12,
+            "model/_type": "LatentisModule",
+            "model/_version": 0,
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "split": "test"
+        },
+        "estimator": "cca",
+        "metric": "spearman",
+        "metric_type": "agreement"
+    }
+]
diff --git a/benchmarks/eval_estimator/benchmark_config.yaml b/benchmarks/eval_estimator/benchmark_config.yaml
new file mode 100644
index 0000000..17833ee
--- /dev/null
+++ b/benchmarks/eval_estimator/benchmark_config.yaml
@@ -0,0 +1,65 @@
+name: "EstimatorBenchmark"
+
+data_root: null
+
+estimators:
+  - svd
+  - pca
+  - relative
+  - cca
+
+pairing_policy:
+  - estimators: ${estimators}
+    correspondence_constraints:
+      quality: 1
+    space_constraints:
+      pool: "cls"
+      layer: 12
+    fit:
+      split: "train"
+      correspondence_constraints:
+        x_dataset: "imdb"
+        y_dataset: "imdb"
+        quality: 0
+
+      x_space_constraints:
+        model/hf_name: "bert-base-uncased"
+
+      y_space_constraints:
+        model/hf_name: "bert-base-uncased"
+
+    tests:
+      - split: "train"
+        correspondence_constraints:
+          x_dataset: "imdb"
+          y_dataset: "imdb"
+          quality: 0
+
+        x_space_constraints:
+          model/hf_name: "bert-base-uncased"
+
+        y_space_constraints:
+          model/hf_name: "bert-base-uncased"
+
+      - split: "test"
+        correspondence_constraints:
+          x_dataset: "imdb"
+          y_dataset: "imdb"
+          quality: 0
+
+        x_space_constraints:
+          model/hf_name: "bert-base-uncased"
+
+        y_space_constraints:
+          model/hf_name: "bert-base-uncased"
+
+
+metrics:
+  latent: ["l2", "cosine"]
+  downstream: ["accuracy", "f1", "precision", "recall"]
+  agreement: ["accuracy", "f1", "precision", "spearman"]
+
+# metrics:
+  # latent: ["l2",]
+  # downstream: []
+  # agreement: []
diff --git a/setup.cfg b/setup.cfg
index 24ee433..7a8b2cf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -35,9 +35,12 @@ install_requires =
     backports.strenum
 
     python-dotenv
+    GitPython
     matplotlib
     faiss-cpu
     pandas
+    fire
+    lightning
 
 [options.packages.find]
 where=src
diff --git a/src/latentis/__init__.py b/src/latentis/__init__.py
index 4d500de..d702d22 100644
--- a/src/latentis/__init__.py
+++ b/src/latentis/__init__.py
@@ -15,8 +15,8 @@
 
 import git
 
-from .space import LatentSpace
-from latentis.utils import load_envs
+# from .space import LatentSpace
+from .utils import load_envs
 
 logger = logging.getLogger(__name__)
 
diff --git a/src/latentis/benchmark/__init__.py b/src/latentis/benchmark/__init__.py
new file mode 100644
index 0000000..bc5d93a
--- /dev/null
+++ b/src/latentis/benchmark/__init__.py
@@ -0,0 +1,5 @@
+from pathlib import Path
+
+from latentis import PROJECT_ROOT
+
+BENCHMARK_DIR: Path = PROJECT_ROOT / "benchmarks"
diff --git a/src/latentis/benchmark/resolver.py b/src/latentis/benchmark/resolver.py
new file mode 100644
index 0000000..ae60868
--- /dev/null
+++ b/src/latentis/benchmark/resolver.py
@@ -0,0 +1,181 @@
+from __future__ import annotations
+
+import itertools
+import json
+from pathlib import Path
+from typing import Optional
+
+from omegaconf import OmegaConf
+
+from latentis.benchmark import BENCHMARK_DIR
+from latentis.correspondence import correspondences_index
+from latentis.data import DATA_DIR
+from latentis.data.dataset import LatentisDataset
+
+BENCHMARK_DIR.mkdir(parents=True, exist_ok=True)
+
+
+def resolve_benchmark(benchmark_name: str) -> list[dict]:
+    config = OmegaConf.load(BENCHMARK_DIR / benchmark_name / "benchmark_config.yaml")
+    data_dir = Path(config.data_root) if config.data_root is not None else DATA_DIR
+
+    experiments = []
+
+    for pairing_policy in config.pairing_policy:
+        train_split = pairing_policy.fit.split
+        available_fit_correspondences = correspondences_index.get_items(
+            split=train_split,
+            **OmegaConf.merge(
+                pairing_policy.correspondence_constraints,
+                pairing_policy.fit.correspondence_constraints,
+            ),
+        )
+
+        for fit_corr_id, fit_corr_properties in available_fit_correspondences.items():
+            x_dataset_fit_name = fit_corr_properties["x_dataset"]
+            y_dataset_fit_name = fit_corr_properties["y_dataset"]
+
+            # TODO: how to access spaces properties without dataset loading?
+            x_dataset_fit = LatentisDataset.load_from_disk(data_dir / x_dataset_fit_name, load_hf_dataset=False)
+            y_dataset_fit = LatentisDataset.load_from_disk(data_dir / y_dataset_fit_name, load_hf_dataset=False)
+
+            x_spaces_fit = x_dataset_fit.encodings.get_items(
+                dataset=x_dataset_fit_name,
+                split=train_split,
+                **OmegaConf.merge(
+                    pairing_policy.space_constraints,
+                    pairing_policy.fit.x_space_constraints,
+                ),
+            )
+
+            y_spaces_fit = y_dataset_fit.encodings.get_items(
+                dataset=y_dataset_fit_name,
+                split=train_split,
+                **OmegaConf.merge(
+                    pairing_policy.space_constraints,
+                    pairing_policy.fit.y_space_constraints,
+                ),
+            )
+            for x_space_fit_key, y_space_fit_key in itertools.product(x_spaces_fit, y_spaces_fit):
+                for test_pairing_policy in pairing_policy.tests:
+                    test_split = test_pairing_policy.split
+
+                    available_test_correspondences = correspondences_index.get_items(
+                        split=test_split,
+                        **OmegaConf.merge(
+                            pairing_policy.correspondence_constraints,
+                            test_pairing_policy.correspondence_constraints,
+                        ),
+                    )
+                    for test_corr_id, test_corr_properties in available_test_correspondences.items():
+                        x_dataset_test_name = test_corr_properties["x_dataset"]
+                        y_dataset_test_name = test_corr_properties["y_dataset"]
+
+                        # TODO: how to access spaces properties without dataset loading?
+                        x_dataset_test = LatentisDataset.load_from_disk(
+                            data_dir / x_dataset_test_name, load_hf_dataset=False
+                        )
+                        y_dataset_test = LatentisDataset.load_from_disk(
+                            data_dir / y_dataset_test_name, load_hf_dataset=False
+                        )
+
+                        x_spaces_test = x_dataset_test.encodings.get_items(
+                            split=test_split,
+                            dataset=x_dataset_test_name,
+                            **OmegaConf.merge(
+                                pairing_policy.space_constraints,
+                                test_pairing_policy.x_space_constraints,
+                            ),
+                        )
+
+                        y_spaces_test = y_dataset_test.encodings.get_items(
+                            split=test_split,
+                            dataset=y_dataset_test_name,
+                            **OmegaConf.merge(
+                                pairing_policy.space_constraints,
+                                test_pairing_policy.y_space_constraints,
+                            ),
+                        )
+
+                        for x_space_test_key, y_space_test_key in itertools.product(x_spaces_test, y_spaces_test):
+                            for estimator_name in pairing_policy.estimators:
+                                for metric_type, metrics in config.metrics.items():
+                                    for metric_name in metrics:
+                                        experiments.append(
+                                            {
+                                                "fit_correspondence": {
+                                                    "__id": fit_corr_id,
+                                                    **fit_corr_properties,
+                                                },
+                                                "fit_x_space": {
+                                                    "__id": x_space_fit_key,
+                                                    **x_spaces_fit[x_space_fit_key],
+                                                },
+                                                "fit_y_space": {
+                                                    "__id": y_space_fit_key,
+                                                    **y_spaces_fit[y_space_fit_key],
+                                                },
+                                                "test_correspondence": {
+                                                    "__id": test_corr_id,
+                                                    **test_corr_properties,
+                                                },
+                                                "test_x_space": {
+                                                    "__id": x_space_test_key,
+                                                    **x_spaces_test[x_space_test_key],
+                                                },
+                                                "test_y_space": {
+                                                    "__id": y_space_test_key,
+                                                    **y_spaces_test[y_space_test_key],
+                                                },
+                                                "estimator": estimator_name,
+                                                "metric": metric_name,
+                                                "metric_type": metric_type,
+                                            }
+                                        )
+
+        (BENCHMARK_DIR / benchmark_name / "benchmark.json").write_text(f"{json.dumps(experiments, indent=4)}\n")
+        return experiments
+
+
+def experiments_summary(experiments: list[dict], benchmark_name: Optional[str] = None) -> str:
+    fit_correspondences = set()
+    fit_x_spaces = set()
+    fit_y_spaces = set()
+    test_correspondences = set()
+    test_x_spaces = set()
+    test_y_spaces = set()
+    estimators = set()
+    metrics = set()
+
+    for experiment in experiments:
+        fit_correspondences.add(repr(experiment["fit_correspondence"]))
+        fit_x_spaces.add(repr(experiment["fit_x_space"]))
+        fit_y_spaces.add(repr(experiment["fit_y_space"]))
+        test_correspondences.add(repr(experiment["test_correspondence"]))
+        test_x_spaces.add(repr(experiment["test_x_space"]))
+        test_y_spaces.add(repr(experiment["test_y_space"]))
+        estimators.add(repr(experiment["estimator"]))
+        metrics.add((repr(experiment["metric"]), repr(experiment["metric_type"])))
+
+    return f"""Benchmark {benchmark_name if benchmark_name is not None else '' } (n={len(experiments)}):
+    Fit correspondences: {len(fit_correspondences)}
+    Fit x spaces: {len(fit_x_spaces)}
+    Fit y spaces: {len(fit_y_spaces)}
+    Test correspondences: {len(test_correspondences)}
+    Test x spaces: {len(test_x_spaces)}
+    Test y spaces: {len(test_y_spaces)}
+    Estimators: {len(estimators)}
+    Metrics: {len(metrics)}
+    """
+
+
+def benchmark_summary(benchmark_name: str) -> str:
+    with (BENCHMARK_DIR / benchmark_name / "benchmark.json").open("r") as f:
+        experiments = json.load(f)
+    return experiments_summary(experiments=experiments, benchmark_name=benchmark_name)
+
+
+if __name__ == "__main__":
+    benchmark_name = "eval_estimator"
+    experiments = resolve_benchmark(benchmark_name)
+    print(experiments_summary(experiments))
diff --git a/src/latentis/benchmark/task.py b/src/latentis/benchmark/task.py
new file mode 100644
index 0000000..ce9aee0
--- /dev/null
+++ b/src/latentis/benchmark/task.py
@@ -0,0 +1,346 @@
+import itertools
+from dataclasses import dataclass
+from enum import auto
+from typing import Mapping, Optional, Sequence
+
+import pandas as pd
+import torch
+import torch.nn.functional as F
+from datasets.search import IndexableMixin
+from torch import nn
+
+from latentis.benchmark.correspondence import Correspondence, IdentityCorrespondence
+from latentis.data import DATA_DIR
+from latentis.data.dataset import LatentisDataset
+from latentis.measure import PairwiseMetric
+from latentis.nn import LatentisModule
+from latentis.space import LatentSpace
+from latentis.transform import Estimator
+from latentis.transform.translate.aligner import MatrixAligner
+from latentis.transform.translate.functional import svd_align_state
+from latentis.types import Properties, StrEnum
+
+
+class TaskProperty(StrEnum):
+    pass
+
+
+class TaskType(StrEnum):
+    CLASSIFICATION = auto()
+    AUTOENCODING = auto()
+
+
+class Accuracy(nn.Module):
+    def __init__(self):
+        super().__init__()
+
+    def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
+        return (x.argmax(dim=-1) == y).float().mean()
+
+    @property
+    def item_id(self):
+        return "Accuracy"
+
+    @property
+    def properties(self):
+        return {"name": self.item_id}
+
+
+class L2Similarity(nn.Module):
+    def __init__(self):
+        super().__init__()
+
+    def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
+        return (x - y).norm(p=2, dim=-1).mean()
+
+    @property
+    def item_id(self):
+        return "L1Similarity"
+
+    @property
+    def properties(self):
+        return {"name": self.item_id}
+
+
+class L1Similarity(nn.Module):
+    def __init__(self):
+        super().__init__()
+
+    def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
+        return (x - y).norm(p=1, dim=-1).mean()
+
+    @property
+    def item_id(self):
+        return "L1Similarity"
+
+    @property
+    def properties(self):
+        return {"name": self.item_id}
+
+
+class CosineSimilarity(nn.Module):
+    def __init__(self):
+        super().__init__()
+
+    def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
+        return F.cosine_similarity(x, y, dim=-1).mean()
+
+    @property
+    def item_id(self):
+        return "cosine"
+
+    @property
+    def properties(self):
+        return {"name": self.item_id}
+
+
+@dataclass(frozen=True)
+class ExperimentResult:
+    x_fit: str
+    y_fit: str
+    fit_correspondence: str
+    #
+    estimator: str
+    #
+    x_test: str
+    y_test: str
+    test_correspondence: str
+    #
+    metric: str
+    score: float
+    #
+    # y_fit_decoder: str
+    # y_gt: str
+    # downstream_metric: str
+    # downstream_score: float
+
+
+class Experiment(IndexableMixin):
+    @property
+    def properties(self) -> Properties:
+        return {
+            "x_fit": self.x_fit.properties,
+            "y_fit": self.y_fit.properties,
+            "fit_correspondence": self.fit_correspondence.properties,
+            #
+            "estimator": self.estimator.properties,
+            #
+            "x_test": self.x_test.properties,
+            "y_test": self.y_test.properties,
+            "test_correspondence": self.test_correspondence.properties,
+            #
+            "metric": {
+                metric_id: metric.properties
+                for metric_id, metric in itertools.chain(self.latent_metrics.items(), self.downstream_metrics.items())
+            },
+            #
+            # "y_fit_decoder": self.y_fit_decoder.properties if self.y_fit_decoder is not None else {},
+            # "y_gt": self.y_gt.properties if self.y_gt is not None else {},
+        }
+
+    def __init__(
+        self,
+        x_fit: LatentSpace,
+        y_fit: LatentSpace,
+        fit_correspondence: Correspondence,
+        #
+        estimator: Estimator,
+        #
+        x_test: LatentSpace,
+        y_test: LatentSpace,
+        test_correspondence: Correspondence,
+        #
+        latent_metrics: Sequence[PairwiseMetric],
+        #
+        y_fit_decoder: Optional[nn.Module] = None,
+        y_gt: Optional[torch.Tensor] = None,
+        downstream_metrics: Optional[Sequence[PairwiseMetric]] = None,
+    ):
+        if not ((y_fit_decoder is None) == (y_gt is None) == (downstream_metrics is None)):
+            raise ValueError("Either all or none of the downstream task arguments must be provided")
+
+        self.x_fit = x_fit
+        self.y_fit = y_fit
+        self.fit_correspondence = fit_correspondence
+
+        self.estimator = estimator
+
+        self.x_test = x_test
+        self.y_test = y_test
+        self.test_correspondence = test_correspondence
+
+        latent_metrics = [] if latent_metrics is None else latent_metrics
+        self.latent_metrics = {latent_metrics.item_id: latent_metrics for latent_metrics in latent_metrics}
+
+        self.y_fit_decoder = y_fit_decoder
+        self.y_gt = y_gt
+
+        downstream_metrics = [] if downstream_metrics is None else downstream_metrics
+        self.downstream_metrics = {
+            downstream_metric.item_id: downstream_metric for downstream_metric in downstream_metrics
+        }
+
+    def estimation_stage(self):
+        corresponding_x_fit: torch.Tensor = self.fit_correspondence.get_x_ids()
+        corresponding_y_fit: torch.Tensor = self.fit_correspondence.get_y_ids()
+
+        estimator = self.estimator.set_spaces(x_space=self.x_fit, y_space=self.y_fit)
+        estimator = estimator.fit(x=self.x_fit[corresponding_x_fit], y=self.y_fit[corresponding_y_fit])
+
+        return estimator
+
+    def transform_stage(self, estimator: Estimator):
+        corresponding_x_test: torch.Tensor = self.test_correspondence.get_x_ids()
+        corresponding_y_test: torch.Tensor = self.test_correspondence.get_y_ids()
+
+        x_test_transformed, y_test_transformed = estimator.transform(
+            x=self.x_test[corresponding_x_test], y=self.y_test[corresponding_y_test]
+        )
+        return x_test_transformed, y_test_transformed
+
+    def latent_evaluation_stage(self, x_test_transformed: torch.Tensor, y_test_transformed: torch.Tensor):
+        return {
+            metric_id: metric(x_test_transformed, y_test_transformed).item()
+            for metric_id, metric in self.latent_metrics.items()
+        }
+
+    def downstream_evaluation_stage(self, x_test_transformed: torch.Tensor):
+        if self.y_fit_decoder is not None:
+            y_fit_decoder: LatentisModule = self.y_fit.decoders.load_item(**self.y_fit_decoder)
+
+            return {
+                metric_id: metric(y_fit_decoder(x_test_transformed), self.y_gt).item()
+                for metric_id, metric in self.downstream_metrics.items()
+            }
+        else:
+            return {}
+
+    def export(self, results: Sequence[ExperimentResult], selection: Mapping[str, Sequence[str]]) -> pd.DataFrame:
+        experiment_properties = self.properties
+        export = {f"{k}/{v}": [] for k, values in selection.items() for v in values}
+        export.update(**{k: [] for k, values in selection.items() if len(values) == 0})
+
+        for result in results:
+            for selection_key, selection_values in selection.items():
+                for selection_value in selection_values:
+                    k = f"{selection_key}/{selection_value}"
+                    if selection_key != "metric":
+                        export[k].append(experiment_properties[selection_key][selection_value])
+                    else:
+                        export[k].append(
+                            experiment_properties[selection_key][getattr(result, selection_key)][selection_value]
+                        )
+                if len(selection_values) == 0:
+                    export[selection_key].append(getattr(result, selection_key))
+
+        return pd.DataFrame.from_dict(export)
+
+    def run(
+        self,
+        estimator: Optional[Estimator] = None,
+    ):
+        estimator = self.estimation_stage() if estimator is None else estimator
+
+        x_test_transformed, y_test_transformed = self.transform_stage(estimator=estimator)
+
+        latent_evaluation = self.latent_evaluation_stage(
+            x_test_transformed=x_test_transformed, y_test_transformed=y_test_transformed
+        )
+        downstream_evaluation = self.downstream_evaluation_stage(x_test_transformed=x_test_transformed)
+
+        for metric, score in itertools.chain(latent_evaluation.items(), downstream_evaluation.items()):
+            yield ExperimentResult(
+                x_fit=self.x_fit.item_id,
+                y_fit=self.y_fit.item_id,
+                fit_correspondence=self.fit_correspondence.item_id,
+                #
+                estimator=estimator.item_id,
+                #
+                x_test=self.x_test.name,
+                y_test=self.y_test.name,
+                test_correspondence=self.test_correspondence.item_id,
+                #
+                metric=metric,
+                score=score,
+                #
+                # y_fit_decoder=self.y_fit_decoder.name,
+                # y_gt=self.y_gt.name,
+                # downstream_metric=downstream_metric,
+                # downstream_score=downstream_score,
+            )
+
+
+if __name__ == "__main__":
+    dataset: LatentisDataset = LatentisDataset.load_from_disk(DATA_DIR / "trec")
+
+    print(dataset)
+    # correspondence_index = CorrespondenceIndex.load_from_disk(DATA_DIR / "correspondence")
+
+    x_fit = dataset.encodings.load_item(
+        **{
+            "split": "train",
+            "model/hf_name": "bert-base-cased",
+            "pool": "cls",
+            "layer": 12,
+        }
+    )
+    y_fit = dataset.encodings.load_item(
+        **{
+            "split": "train",
+            "model/hf_name": "bert-base-cased",
+            "pool": "cls",
+            "layer": 12,
+        }
+    )
+    x_test = dataset.encodings.load_item(
+        **{
+            "split": "test",
+            "model/hf_name": "bert-base-cased",
+            "pool": "cls",
+            "layer": 12,
+        }
+    )
+    y_test = dataset.encodings.load_item(
+        **{
+            "split": "test",
+            "model/hf_name": "bert-base-uncased",
+            "pool": "cls",
+            "layer": 12,
+        }
+    )
+
+    experiment = Experiment(
+        x_fit=x_fit,
+        y_fit=y_fit,
+        fit_correspondence=IdentityCorrespondence(
+            n_samples=len(dataset.hf_dataset["train"]),
+        ),  # .random_subset(factor=0.01, seed=42),
+        x_test=x_test,
+        y_test=y_test,  # .random_subset(factor=0.01, seed=42),
+        test_correspondence=IdentityCorrespondence(
+            n_samples=len(dataset.hf_dataset["test"]),
+        ),
+        estimator=MatrixAligner(name="svd", align_fn_state=svd_align_state),
+        latent_metrics=[L2Similarity(), L1Similarity(), CosineSimilarity()],
+        y_fit_decoder={},
+        y_gt=torch.as_tensor(dataset.hf_dataset["test"]["coarse_label"]),
+        downstream_metrics=[Accuracy()],
+    )
+
+    print(experiment.properties)
+
+    runs = list(experiment.run())
+    print(runs[0])
+
+    space_selection = ["dataset", "model/hf_name", "pool", "layer"]
+    selection = {
+        "x_fit": space_selection,
+        "y_fit": space_selection,
+        "estimator": ["name"],
+        "x_test": space_selection,
+        "y_test": space_selection,
+        "metric": ["name"],
+        "score": [],
+    }
+
+    experiment.export(results=runs, selection=selection).to_csv(DATA_DIR / "experiment.csv", index=False, sep="\t")
diff --git a/src/latentis/correspondence/__init__.py b/src/latentis/correspondence/__init__.py
new file mode 100644
index 0000000..de461a0
--- /dev/null
+++ b/src/latentis/correspondence/__init__.py
@@ -0,0 +1,6 @@
+from ._base import Correspondence, correspondences_index
+
+__all__ = [
+    "correspondences_index",
+    "Correspondence",
+]
diff --git a/src/latentis/correspondence/_base.py b/src/latentis/correspondence/_base.py
new file mode 100644
index 0000000..850e485
--- /dev/null
+++ b/src/latentis/correspondence/_base.py
@@ -0,0 +1,84 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from enum import auto
+from pathlib import Path
+
+import torch
+
+from latentis.data import DATA_DIR
+from latentis.serialize.disk_index import DiskIndex
+from latentis.serialize.io_utils import IndexableMixin, load_json, save_json
+from latentis.space import LatentSpace
+from latentis.types import Properties, StrEnum
+
+_CORRESPONDENCE_DIR = DATA_DIR / "correspondences"
+_PROPERTIES_FILE_NAME = "properties.json"
+
+
+class _CorrespondenceMetadata(StrEnum):
+    _VERSION = auto()
+    _TYPE = auto()
+
+
+class Correspondence(IndexableMixin):
+    def __init__(self, **properties):
+        properties = properties or {}
+        properties[_CorrespondenceMetadata._VERSION] = self.version
+        properties[_CorrespondenceMetadata._TYPE] = Correspondence.__name__
+        self._properties = properties.copy()
+
+    @property
+    def version(self) -> 0:
+        return 0
+
+    @property
+    def properties(self) -> Properties:
+        return self._properties
+
+    @classmethod
+    def load_properties(cls, correspondence_path: Path) -> Properties:
+        metadata = load_json(correspondence_path / _PROPERTIES_FILE_NAME)
+        return metadata
+
+    def save_to_disk(self, target_path: Path) -> None:
+        target_path.mkdir(parents=True, exist_ok=True)
+        save_json(self.properties, target_path / _PROPERTIES_FILE_NAME)
+        # TODO: serialize save correspondence
+
+    @classmethod
+    def load_from_disk(cls, path: Path) -> Correspondence:
+        # TODO: deserialize the correspondence
+        properties = cls.load_properties(path)
+        correspondence = cls.__new__(cls)
+        correspondence._properties = properties
+        return correspondence
+
+    @abstractmethod
+    def get_x_ids(self) -> torch.Tensor:
+        return self.x2y[:, 0]
+
+    def get_y_ids(self) -> torch.Tensor:
+        return self.x2y[:, 1]
+
+    @abstractmethod
+    def split(self):
+        pass
+
+    def add_noise(self, x: LatentSpace, y: LatentSpace):
+        pass
+
+    # def random_subset(self, factor: Union[int, float], seed: int):
+    #     n = int(len(self.x2y) * factor) if 0 < factor <= 1 else factor
+    #     x_ids = self.get_x_ids()
+    #     y_ids = self.get_y_ids()
+    #     subset = torch.randperm(x_ids.size(0), generator=torch.Generator().manual_seed(seed))[:n]
+
+    #     return TensorCorrespondence(x2y=torch.stack([x_ids[subset, :], y_ids[subset, :]], dim=-1))
+
+
+try:
+    correspondences_index = DiskIndex.load_from_disk(path=DATA_DIR / _CORRESPONDENCE_DIR)
+except FileNotFoundError:
+    correspondences_index = DiskIndex(root_path=DATA_DIR / _CORRESPONDENCE_DIR, item_class=Correspondence)
+    correspondences_index.save_to_disk()
diff --git a/src/latentis/correspondence/correspondence.py b/src/latentis/correspondence/correspondence.py
new file mode 100644
index 0000000..5c47dfc
--- /dev/null
+++ b/src/latentis/correspondence/correspondence.py
@@ -0,0 +1,44 @@
+from __future__ import annotations
+
+import torch
+from torch._tensor import Tensor
+
+from latentis.correspondence import Correspondence
+
+# WikimatrixCorrespondence(source_dataset="en", target_dataset="fr", source_id=0)
+
+
+# IdentityCorrespondence(dataset)(source_id=0)
+
+
+class TensorCorrespondence(Correspondence):
+    def __init__(self, x2y: torch.Tensor):
+        super().__init__()
+        if x2y.ndim != 2:
+            raise ValueError(f"Expected x2y to have ndim=2, got {x2y.ndim}")
+        if x2y.shape[1] != 2:
+            raise ValueError(f"Expected x2y to be (n, 2), got {x2y.shape}")
+
+        self.x2y: torch.Tensor = x2y
+
+    def get_x_ids(self) -> torch.Tensor:
+        return self.x2y[:, 0]
+
+    def get_y_ids(self) -> torch.Tensor:
+        return self.x2y[:, 1]
+
+
+class IdentityCorrespondence(Correspondence):
+    def __init__(self, n_samples: int):
+        super().__init__()
+        self.n_samples: int = n_samples
+
+    def get_x_ids(self) -> Tensor:
+        return torch.arange(self.n_samples)
+
+    def get_y_ids(self) -> Tensor:
+        return torch.arange(self.n_samples)
+
+
+if __name__ == "__main__":
+    pass
diff --git a/src/latentis/correspondence/gen_correspondences.py b/src/latentis/correspondence/gen_correspondences.py
new file mode 100644
index 0000000..efa1f10
--- /dev/null
+++ b/src/latentis/correspondence/gen_correspondences.py
@@ -0,0 +1,17 @@
+import torch
+
+from latentis.correspondence import correspondences_index
+from latentis.correspondence._base import Correspondence
+
+if __name__ == "__main__":
+    for x_dataset in ["trec", "imdb"]:
+        for y_dataset in ["trec", "imdb", "amazon"]:
+            for quality in torch.arange(0, 1, 0.1):
+                for split in ["train", "test"]:
+                    corr = Correspondence(
+                        x_dataset=x_dataset,
+                        y_dataset=y_dataset,
+                        quality=quality.item(),
+                        split=split,
+                    )
+                    correspondences_index.add_item(corr)
diff --git a/src/latentis/data/__init__.py b/src/latentis/data/__init__.py
index 5d952be..9fea193 100644
--- a/src/latentis/data/__init__.py
+++ b/src/latentis/data/__init__.py
@@ -2,4 +2,4 @@
 
 from latentis import PROJECT_ROOT
 
-BENCHMARK_DIR: Path = PROJECT_ROOT / "benchmark"  # TODO: env var
+DATA_DIR: Path = PROJECT_ROOT / "data"  # TODO: env var
diff --git a/src/latentis/data/dataset.py b/src/latentis/data/dataset.py
index 63f1a89..437238c 100644
--- a/src/latentis/data/dataset.py
+++ b/src/latentis/data/dataset.py
@@ -1,21 +1,26 @@
-import json
+from __future__ import annotations
+
+import logging
 import shutil
 from dataclasses import dataclass, field
 from datetime import datetime
 from enum import auto
-from hashlib import sha256
 from pathlib import Path
-from typing import Any, Mapping, Sequence, Union
+from typing import Any, Mapping, Optional, Sequence, Union
 
-import pyarrow as pa
-import pyarrow.parquet as pq
 import torch
 from datasets import DatasetDict
 from torch import nn
+from torch.utils.data import DataLoader, Dataset
 
-from latentis.data import BENCHMARK_DIR
+from latentis.data import DATA_DIR
+from latentis.serialize.disk_index import DiskIndex
+from latentis.serialize.io_utils import MetadataMixin, SerializableMixin, load_json, save_json
+from latentis.space import LatentSpace
 from latentis.types import StrEnum
 
+pylogger = logging.getLogger(__name__)
+
 
 class DataType(StrEnum):
     TEXT = auto()
@@ -33,16 +38,14 @@ class DatasetProperty(StrEnum):
 
 
 # make it json serializable
-@dataclass(
-    frozen=True,
-)
+@dataclass(frozen=True)
 class Feature:
     col_name: str
     data_type: DataType
     properties: Mapping[FeatureProperty, str] = field(default_factory=lambda: {})
 
     def __hash__(self):
-        return hash((self.col_name, self.data_type, tuple(self.properties.items())))
+        return hash(self.col_name)
 
 
 @dataclass(frozen=True)
@@ -51,337 +54,248 @@ class FeatureMapping:
     target_col: str
 
 
-class LatentisDataset:
-    _METADATA_FILE_NAME: str = "metadata.json"
-
-    @classmethod
-    def get_key(cls, dataset_name: str, perc: float, metadata: Mapping[DatasetProperty, Any]) -> str:
-        hashcode = sha256()
+class DatasetView(torch.utils.data.Dataset):
+    def __init__(
+        self,
+        latentis_dataset: LatentisDataset,
+        encodings_key: Optional[Union[str, Sequence[str]]],
+        hf_x_keys: Optional[Union[str, Sequence[str]]],
+        hf_y_keys: Optional[Union[str, Sequence[str]]] = ("label",),
+    ):
+        super().__init__()
+        if isinstance(encodings_key, str):
+            encodings_key = [encodings_key]
+        if isinstance(hf_x_keys, str):
+            hf_x_keys = [hf_x_keys]
+        if isinstance(hf_y_keys, str):
+            hf_y_keys = [hf_y_keys]
+
+        self.spaces = [latentis_dataset.encodings.load_item(item_key=key) for key in encodings_key]
+        spaces_split = set(space.split for space in self.spaces)
+        if len(spaces_split) > 1:
+            raise ValueError(f"Spaces {encodings_key} are not all from the same split!")
+        self.split = spaces_split.pop()
+
+        self.data = latentis_dataset.hf_dataset[self.split]
+        self.encodings_key = encodings_key or []
+        self.hf_x_keys = hf_x_keys or []
+        self.hf_y_keys = hf_y_keys or []
+
+    def __getitem__(self, idx: int) -> Mapping[str, Any]:
+        sample = {
+            "encodings_key": [space[idx] for space in self.spaces],
+            "hf_x_keys": [self.data[idx][key] for key in self.hf_x_keys],
+            "hf_y_keys": [self.data[idx][key] for key in self.hf_y_keys],
+        }
+        return {key: value for key, value in sample.items() if value is not None and len(value) > 0}
 
-        hashcode.update(dataset_name.encode())
-        hashcode.update(str(perc).encode())
-        hashcode.update(json.dumps(metadata, sort_keys=True).encode())
+    def __len__(self) -> int:
+        return len(self.data)
 
-        return hashcode.hexdigest()[:8]
 
+class LatentisDataset(SerializableMixin, MetadataMixin):
     def __init__(
         self,
         name: str,
-        dataset: DatasetDict,
+        hf_dataset: DatasetDict,
         id_column: str,
         features: Sequence[Feature],
         perc: float = 1,
-        metadata: Mapping[str, Any] = {},
-        parent_dir: Path = BENCHMARK_DIR,
+        properties: Optional[Mapping[str, Any]] = None,
+        parent_dir: Path = DATA_DIR,
     ):
-        assert isinstance(dataset, DatasetDict), f"Expected {DatasetDict}, got {type(dataset)}"
+        super().__init__()
+        assert isinstance(hf_dataset, DatasetDict), f"Expected {DatasetDict}, got {type(hf_dataset)}"
         assert len(set(features)) == len(features), f"Features {features} contain duplicates!"
         assert len(features) > 0, f"Features {features} must not be empty!"
         assert all(
-            id_column in dataset[split].column_names for split in dataset.keys()
-        ), f"ID column {id_column} not in all splits of dataset {dataset}"
+            id_column in hf_dataset[split].column_names for split in hf_dataset.keys()
+        ), f"ID column {id_column} not in all splits of dataset {hf_dataset}"
         assert all(
-            feature.col_name in dataset[split].column_names for feature in features for split in dataset.keys()
-        ), f"Specified features not in all splits of dataset {dataset}"
+            feature.col_name in hf_dataset[split].column_names for feature in features for split in hf_dataset.keys()
+        ), f"Specified features not in all splits of dataset {hf_dataset}"
         assert 0 < perc <= 1, f"Percentage {perc} not in (0, 1]"
 
-        self.name: str = name
-        self.dataset = dataset
-        self.id_column: str = id_column
-        self.features = set(features)
-        self.perc = perc
-        self.metadata = metadata
-        self.parent_dir = parent_dir
+        self._name: str = name
+        self._hf_dataset: DatasetDict = hf_dataset
+        self._id_column: str = id_column
+        self._features: Sequence[Feature] = features
+        self._perc: float = perc
+        self._properties: Mapping[DatasetProperty, Any] = properties or {}
+        self._root_dir: Path = parent_dir / name
+        self.encodings = DiskIndex(self._root_dir / "encodings", item_class=LatentSpace)
+
+    def save_to_disk(self):
+        target_path = self._root_dir
+        if target_path.exists():
+            raise FileExistsError(f"Destination {self._root_dir} is not empty!")
 
-    def __repr__(self):
-        return f"{self.__class__.__name__}(features={self.features}, perc={self.perc}, metadata={self.metadata})"
-
-    #
-    # def load_encodings(self, encoding2splits: Mapping[str, Sequence[str]]) -> Mapping[str, Mapping[str, torch.Tensor]]:
-    #     result = {}
-
-    #     for encoding, splits in encoding2splits.items():
-    #         split2dataset = {}
-    #         for split in splits:
-    #             encodings_path = (
-    #                 self.parent_dir
-    #                 / self.name
-    #                 / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, properties=self.properties)
-    #                 / "encodings"
-    #                 / encoding
-    #                 / split
-    #             )
-
-    #             if not encodings_path.exists():
-    #                 raise FileNotFoundError
-
-    #             split2dataset[split] = Dataset.load_from_disk(encodings_path)
-
-    #         result[encoding] = DatasetDict(split2dataset)
-
-    #     return result
-
-    # DATASETS VERSION
-    # def with_encodings(self, encodings: Sequence[str]) -> "LatentisDataset":
-    #     result = self.dataset
-    #     for encoding in encodings:
-    #         encoding2split2data = self.load_encodings({encoding: self.dataset.keys()})
-
-    #         for encoding, split2data in encoding2split2data.items():
-    #             for split, split_data in split2data.items():
-    #                 result[split] = datasets.concatenate_datasets([result[split], split_data], axis=1)
-
-    #     return LatentisDataset(
-    #         name=self.name,
-    #         dataset=result,
-    #         id_column=self.id_column,
-    #         features=self.features,
-    #         perc=self.perc,
-    #         properties=self.properties,
-    #         parent_dir=self.parent_dir,
-    #     )
-
-    def load_encodings(self, encoding2splits: Mapping[str, Sequence[str]]) -> Mapping[str, Mapping[str, torch.Tensor]]:
-        result = {}
-
-        for encoding, splits in encoding2splits.items():
-            split2dataset = {}
-            for split in splits:
-                encodings_path = (
-                    self.parent_dir
-                    / self.name
-                    / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, metadata=self.metadata)
-                    / "encodings"
-                    / encoding
-                    / split
-                    / "encodings.parquet"
-                )
-
-                if not encodings_path.exists():
-                    raise FileNotFoundError
-
-                split2dataset[split] = pq.read_table(encodings_path)
-
-            result[encoding] = split2dataset
-
-        return result
-
-    # def with_encodings(self, encodings: Sequence[str]) -> "LatentisDataset":
-    #     # result = self.dataset
-
-    #     encoding2split2data = self.load_encodings({encoding: self.dataset.keys() for encoding in encodings})
-
-    #     split2all_data = {}
-
-    #     for encoding, split2data in encoding2split2data.items():
-    #         for split, data in split2data.items():
-    #             if split not in split2all_data:
-    #                 split2all_data[split] = data
-    #             else:
-    #                 split2all_data[split] = pa.concat_tables([split2all_data[split], data])
-
-    #     return LatentisDataset(
-    #         name=self.name,
-    #         dataset=result,
-    #         id_column=self.id_column,
-    #         features=self.features,
-    #         perc=self.perc,
-    #         properties=self.properties,
-    #         parent_dir=self.parent_dir,
-    #     )
+        self._hf_dataset.save_to_disk(target_path / "hf_dataset")
 
-    def add_decoder(self, encoding_key: str, decoder: nn.Module):
-        raise NotImplementedError
+        # Copy the encodings directory, if needed
+        if (target_path / "encodings").exists():
+            shutil.copy(self._root_dir / "encodings", target_path / "encodings")
 
-    def load_decoders(self, encodings: Sequence[str]) -> Mapping[str, nn.Module]:
-        raise NotImplementedError
+        save_json(self.metadata, target_path / self._METADATA_FILE_NAME, indent=4)
 
-    def get_available_encodings(
-        self, *features: Feature
-    ) -> Union[Mapping[str, Sequence[str]], Mapping[Feature, Mapping[str, Sequence[str]]]]:
-        """Scans the encodings directory and returns a mapping from feature to available encodings.
+        self.encodings.save_to_disk()
 
-        Returns:
-            Mapping[str, Sequence[str]]: Mapping from feature to available encodings.
+    @classmethod
+    def load_from_disk(
+        cls,
+        path: Path,
+        load_hf_dataset: bool = True,
+    ) -> "LatentisDataset":
+        assert (
+            path / cls._METADATA_FILE_NAME
+        ).exists(), f"Metadata file {path / cls._METADATA_FILE_NAME} does not exist! Are you sure about the parameters?"
 
-        """
-        raise NotImplementedError
-        # assert len(features) == 0 or all(
-        #     feature in self.features for feature in features
-        # ), f"Features {features} not available in dataset {self.name}!"
-        # features = features or self.features
-
-        # encodings_dir = (
-        #     self.parent_dir
-        #     / self.name
-        #     / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, metadata=self.metadata)
-        #     / "encodings"
-        # )
-        # if not encodings_dir.exists():
-        #     return {}
-
-        # available_features = [
-        #     feature
-        #     for feature in self.features
-        #     if (encodings_dir / feature.col_name).exists() and (encodings_dir / feature.col_name).is_dir()
-        # ]
-
-        # return {
-        #     encoding_path.name: list(encoding_path.iterdir())
-        #     for encoding_path in encoded_dir.iterdir()
-        #     if encoding_path.is_dir()
-        # }
-
-    # DATASETS VERSION
-    # def add_encoding(self, encoding_key: str, split: str, id2encoding: Mapping[str, torch.Tensor]):
-    #     encoding_dataset = Dataset.from_dict(
-    #         {
-    #             self.id_column: id2encoding.keys(),
-    #             encoding_key: [encoding.numpy() for encoding in id2encoding.values()],
-    #         }
-    #     )
-
-    #     target_path = (
-    #         self.parent_dir
-    #         / self.name
-    #         / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, properties=self.properties)
-    #         / "encodings"
-    #         / encoding_key
-    #         / split
-    #     )
-
-    #     if target_path.exists():
-    #         current_dataset = Dataset.load_from_disk(target_path)
-    #         encoding_dataset = datasets.concatenate_datasets([current_dataset, encoding_dataset], axis=0)
-
-    #     if target_path.exists():
-    #         # TODO: Highly inefficient
-    #         temp_path = target_path.parent / (target_path.name + "_temp")
-    #         encoding_dataset.save_to_disk(temp_path)
-    #         shutil.rmtree(target_path)
-    #         shutil.move(temp_path, target_path)
-    #     else:
-    #         encoding_dataset.save_to_disk(target_path)
-
-    # SQLITE VERSION
-    # def add_encoding(self, encoding_key: str, split: str, id2encoding: Mapping[str, torch.Tensor]):
-    #     ids, encodings = zip(*id2encoding.items())
-    #     data_list = [{self.id_column: sample_id, "encoding": encoding} for sample_id, encoding in zip(ids, encodings)]
-    #     encoding_size: int = encodings[0].shape[0]
-
-    #     target_path = (
-    #         self.parent_dir
-    #         / self.name
-    #         / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, properties=self.properties)
-    #         / "encodings"
-    #         / encoding_key
-    #         / split
-    #     )
-    #     target_path.mkdir(parents=True, exist_ok=True)
-
-    #     db = SQLiteDatabase(
-    #         path=str(target_path / "encodings.db"),
-    #         name="encodings",
-    #         schema={
-    #             self.id_column: int,
-    #             "encoding": dict(dtype=torch.float, size=(encoding_size,)),
-    #         },
-    #     )
-
-    #     db.multi_insert(indices=ids, data_list=data_list, log=True)
-
-    def add_encoding(self, feature: Feature, encoding_key: str, split: str, id2encoding: Mapping[str, torch.Tensor]):
-        ids, encodings = zip(*id2encoding.items())
-        data_list = [
-            {self.id_column: sample_id, "encoding": encoding.numpy()} for sample_id, encoding in zip(ids, encodings)
-        ]
-
-        table = pa.Table.from_pylist(data_list)
-
-        target_path = (
-            self.parent_dir
-            / self.name
-            / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, metadata=self.metadata)
-            / "encodings"
-            / feature.col_name
-            / split
-            / encoding_key
-            / "encodings.parquet"
-        )
+        metadata = load_json(path / cls._METADATA_FILE_NAME)
 
-        target_path.parent.mkdir(parents=True, exist_ok=True)
+        hf_dataset = None
+        if load_hf_dataset:
+            hf_dataset = DatasetDict.load_from_disk(path / "hf_dataset")
 
-        if target_path.exists():
-            current_table = pa.parquet.read_table(target_path)
-            table = pa.concat_tables([current_table, table])
+        properties = metadata["properties"]
+        features = [Feature(**feature) for feature in metadata["features"]]
+
+        dataset = LatentisDataset.__new__(cls)
 
-        pa.parquet.write_table(table, target_path)
+        dataset._name: str = metadata["name"]
+        dataset._hf_dataset: DatasetDict = hf_dataset
+        dataset._id_column: str = metadata["id_column"]
+        dataset._features: Sequence[Feature] = features
+        dataset._perc: float = metadata["perc"]
+        dataset._root_dir: Path = path
+        dataset._properties = properties
+        dataset.encodings = DiskIndex.load_from_disk(path=path / "encodings")
 
-    def save_to_disk(self, overwrite: bool = False):
-        dataset_path = self.parent_dir / self.name
-        instance_path = dataset_path / LatentisDataset.get_key(dataset_name=self.name, perc=self.perc, metadata={})
-        processed_path = instance_path / "processed"
+        return dataset
 
-        # path = parent_dir / (
-        #     dir_name or ProcessedDataset.get_key(hf_name=hf_name, perc=self.perc, properties=self.properties)
-        # )
+    @property
+    def name(self) -> str:
+        return self._name
 
-        if not overwrite and (processed_path / self._METADATA_FILE_NAME).exists():
-            raise FileExistsError
+    @property
+    def hf_dataset(self) -> DatasetDict:
+        return self._hf_dataset
 
-        if processed_path.exists():
-            shutil.rmtree(processed_path)
+    @property
+    def perc(self) -> float:
+        return self._perc
 
-        self.dataset.save_to_disk(processed_path)
+    @property
+    def id_column(self) -> str:
+        return self._id_column
 
-        data = {
-            "name": self.name,
-            "id_column": self.id_column,
-            "features": tuple(self.features),
-            "perc": self.perc,
-            "metadata": self.metadata,
+    @property
+    def features(self) -> Sequence[Feature]:
+        return self._features
+
+    @property
+    def root_dir(self) -> Path:
+        return self._root_dir
+
+    def get_feature(self, col_name: str) -> Optional[Feature]:
+        for feature in self._features:
+            if feature.col_name == col_name:
+                return feature
+
+        return None
+
+    @property
+    def metadata(self) -> Mapping[str, Any]:
+        return {
+            "name": self._name,
+            "id_column": self._id_column,
+            "features": tuple(self._features),
+            "perc": self._perc,
             "timestamp": str(datetime.now()),
             "timestamp_ms": int(datetime.now().timestamp()),
+            "properties": self._properties,
         }
 
-        with open(processed_path / self._METADATA_FILE_NAME, "w") as f:
-            json.dump(data, f, indent=4, sort_keys=True, default=lambda x: x.__dict__)
+    def __repr__(self):
+        return f"{self.__class__.__name__}(features={self._features}, perc={self._perc}, metadata={self.metadata})"
 
-    @classmethod
-    def load_from_disk(
-        cls,
-        dataset_name: str,
-        perc: float,
-        metadata: Mapping[DatasetProperty, Any] = {},
-        parent_dir: Path = BENCHMARK_DIR,
-    ) -> "LatentisDataset":
-        path: Path = (
-            parent_dir
-            / dataset_name
-            / LatentisDataset.get_key(dataset_name=dataset_name, perc=perc, metadata=metadata)
-            / "processed"
-        )
+    def add_decoder(self, encoding_key: str, decoder: nn.Module):
+        raise NotImplementedError
 
-        assert (
-            path / cls._METADATA_FILE_NAME
-        ).exists(), f"Metadata file {path / cls._METADATA_FILE_NAME} does not exist! Are you sure about the parameters?"
+    def load_decoders(self, encodings: Sequence[str]) -> Mapping[str, nn.Module]:
+        raise NotImplementedError
+
+    def add_encoding(
+        self,
+        item: LatentSpace,
+        save_source_model: bool,
+    ) -> LatentSpace:
+        # TODO: add consistency check to make sure that the item is compatible with the dataset
+        try:
+            existing_space = self.encodings.load_item(**item.properties)
+            existing_space.add_vectors(vectors=item.vectors, keys=item.keys)
+
+            # TODO: This is a hack, we are bypassing the index
+            target_path = self.encodings.get_item_path(**item.properties)
+            existing_space.save_to_disk(
+                target_path,
+                save_vector_source=True,
+                save_properties=False,
+                save_source_model=save_source_model,
+            )
+        except KeyError:
+            return self.encodings.add_item(
+                item=item,
+                save_args={
+                    "save_vector_source": True,
+                    "save_properties": True,
+                    "save_source_model": save_source_model,
+                },
+            )
+
+    def add_encodings(
+        self,
+        items: Sequence[LatentSpace],
+        save_source_model: bool,
+    ) -> Sequence[str]:
+        for space in items:
+            self.add_encoding(item=space, save_source_model=save_source_model)
 
-        dataset = DatasetDict.load_from_disk(path)
-        with open(path / cls._METADATA_FILE_NAME, "r") as f:
-            data = json.load(f)
-
-        features = [Feature(**feature) for feature in data["features"]]
-        perc = data["perc"]
-        metadata = data["metadata"]
-        id_column = data["id_column"]
-
-        processed_dataset = LatentisDataset(
-            name=dataset_name,
-            dataset=dataset,
-            id_column=id_column,
-            features=features,
-            perc=perc,
-            metadata=metadata,
+    def get_dataset_view(
+        self,
+        encodings_key: Sequence[str],
+        hf_x_keys: Sequence[str],
+        hf_y_keys: Sequence[str],
+    ) -> Dataset:
+        return DatasetView(
+            latentis_dataset=self,
+            encodings_key=encodings_key,
+            hf_x_keys=hf_x_keys,
+            hf_y_keys=hf_y_keys,
+        )
+
+    def get_dataloader(
+        self,
+        space_id: Sequence[str],
+        hf_x_keys: Sequence[str],
+        hf_y_keys: Sequence[str],
+        batch_size: int,
+        shuffle: bool,
+        num_workers: int = 0,
+        **kwargs,
+    ):
+        return DataLoader(
+            self.get_dataset_view(
+                encodings_key=space_id,
+                hf_x_keys=hf_x_keys,
+                hf_y_keys=hf_y_keys,
+            ),
+            batch_size=batch_size,
+            shuffle=shuffle,
+            num_workers=num_workers,
+            **kwargs,
         )
 
-        return processed_dataset
+
+if __name__ == "__main__":
+    dataset = LatentisDataset.load_from_disk(DATA_DIR / "imdb")
+    print(dataset.encodings.get_items_df())
diff --git a/src/latentis/data/encode.py b/src/latentis/data/encode.py
index e6d131c..f8af9ed 100644
--- a/src/latentis/data/encode.py
+++ b/src/latentis/data/encode.py
@@ -1,17 +1,21 @@
 import functools
+import itertools
 import logging
-from collections import defaultdict
-from typing import Mapping, Sequence, Tuple
+from typing import Sequence
 
 import torch
+from torch import nn
 from torch.utils.data import DataLoader
 from tqdm import tqdm
-from transformers import AutoFeatureExtractor, AutoModel, AutoTokenizer, PreTrainedModel, PreTrainedTokenizer
-from transformers.feature_extraction_utils import PreTrainedFeatureExtractor
 
-from latentis.data.dataset import DataType, Feature
+from latentis.data import DATA_DIR
+from latentis.data.dataset import Feature
 from latentis.data.processor import LatentisDataset
-from latentis.data.text_encoding import EncodeMode, batch_encode
+from latentis.data.text_encoding import HFPooler, cls_pool
+from latentis.data.utils import default_collate
+from latentis.nn import LatentisModule
+from latentis.nn.encoders import TextHFEncoder
+from latentis.space import LatentSpace
 
 pylogger = logging.getLogger(__name__)
 
@@ -40,233 +44,112 @@
 }
 
 
-def load_text_encoder(model_name: str) -> Tuple[PreTrainedModel, PreTrainedTokenizer]:
-    encoder = AutoModel.from_pretrained(model_name, output_hidden_states=True, return_dict=True)
-    encoder.requires_grad_(False).eval()
-    return encoder, AutoTokenizer.from_pretrained(model_name)
+# encoding_module = encoder.vision_model if encoder_name.startswith("openai/clip") else encoder
+# for batch in tqdm(loader, desc=f"Embedding samples"):
+#     embeddings.extend(encoding_module(batch["image"].to(DEVICE)).cpu().tolist())
 
-
-def load_image_encoder(model_name: str) -> Tuple[PreTrainedModel, PreTrainedFeatureExtractor]:
-    encoder = AutoModel.from_pretrained(model_name, output_hidden_states=True, return_dict=True)
-    encoder.requires_grad_(False).eval()
-    return encoder, AutoFeatureExtractor.from_pretrained(model_name)
-
-
-@torch.no_grad()
-def collate_fn_text(batch, tokenizer, max_length: int, data_key: str, encoder_name: str, id_column: str):
-    is_clip: bool = encoder_name.startswith("openai/clip")
-    tokenizer_result = tokenizer(
-        [sample[data_key] for sample in batch],
-        return_special_tokens_mask=True,
-        return_token_type_ids=not is_clip,
-        return_tensors="pt",
-        truncation=True,
-        max_length=max_length,
-        padding=True,
-    )
-
-    sample_ids = [sample[id_column] for sample in batch]
-
-    return {
-        "tokenizer_result": tokenizer_result,
-        id_column: sample_ids,
-    }
-
-
-@torch.no_grad()
-def collate_fn_image(batch, processor, data_key: str, encoder_name: str, id_column: str):
-    pass
-
-
-@torch.no_grad()
-def encode_image(dataset, transform, encoder, encoder_name: str, batch_size=64):
-    raise NotImplementedError
-    # embeddings = []
-    # loader = DataLoader(
-    #     dataset,
-    #     batch_size=batch_size,
-    #     pin_memory=True,
-    #     shuffle=False,
-    #     num_workers=4,
-    #     collate_fn=functools.partial(
-    #         clip_image_encode if encoder_name.startswith("openai/clip") else image_encode,
-    #         transform=transform,
-    #     ),
-    # )
-
-    # encoding_module = encoder.vision_model if encoder_name.startswith("openai/clip") else encoder
-    # for batch in tqdm(loader, desc=f"Embedding samples"):
-    #     embeddings.extend(encoding_module(batch["image"].to(DEVICE)).cpu().tolist())
-
-    # return embeddings
+# return embeddings
 
 
 def encode_feature(
     dataset: LatentisDataset,
-    feature: Feature,
+    feature: str,
+    model: LatentisModule,
     collate_fn: callable,
-    encode_fn: callable,
-    encoder_name: str,
-    encoder: PreTrainedModel,
     encoding_batch_size: int,
     store_every: int,
     num_workers: int,
     device: torch.device,
+    save_source_model: bool = False,
+    poolers: Sequence[nn.Module] = None,
 ):
-    for split, split_data in dataset.dataset.items():
+    """Encode the dataset using the specified encoders.
+
+    Args:
+        dataset (LatentisDataset): Dataset to encode
+        feature (str): Name of the feature to encode
+        model (LatentisModule): Model to use for encoding
+        collate_fn (callable): Collate function to use for the dataset
+        encoding_batch_size (int): Batch size for encoding
+        store_every (int): Store encodings every `store_every` batches
+        num_workers (int): Number of workers to use for encoding
+        device (torch.device): Device to use for encoding
+        save_source_model (bool, optional): Save the source model. Defaults to False.
+        poolers (Sequence[nn.Module], optional): Poolers to use for encoding. Defaults to None.
+    """
+    feature: Feature = dataset.get_feature(feature)
+
+    pylogger.info(f"Encoding {feature} for dataset {dataset._name}")
+
+    model = model.to(device)
+
+    for split, split_data in dataset.hf_dataset.items():
         loader = DataLoader(
             split_data,
             batch_size=encoding_batch_size,
             pin_memory=device != torch.device("cpu"),
             shuffle=False,
             num_workers=num_workers,
-            collate_fn=collate_fn,
+            collate_fn=functools.partial(collate_fn, model=model, feature=feature.col_name),
         )
 
-        result = defaultdict(list)
-        for batch_index, batch in tqdm(
-            enumerate(loader), desc=f"Encoding samples for feature {feature} using {encoder_name}"
+        for batch in tqdm(
+            loader, desc=f"Encoding `{split}` samples for feature {feature.col_name} using {model.item_id[:8]}"
         ):
-            batch_encoding = encode_fn(batch=batch, encoder=encoder, encoder_name=encoder_name)
-
-            for k, v in batch_encoding.items():
-                result[k].append(v)
-
-            result[dataset.id_column].extend(batch[dataset.id_column])
-
-            if (batch_index + 1) % store_every == 0 or batch_index == len(loader) - 1:
-                for encoder_name, encoding in result.items():
-                    if encoder_name == dataset.id_column:
-                        continue
-                    encoding = torch.cat(encoding, dim=0).cpu()
-
-                    dataset.add_encoding(
-                        feature=feature,
-                        encoding_key=encoder_name,
-                        split=split,
-                        id2encoding=dict(zip(result[dataset.id_column], encoding)),
-                    )
-
-                result = defaultdict(list)
-
-        assert len(result) == 0
-
-
-def encode_dataset(
-    dataset: LatentisDataset,
-    encoding_batch_size: int,
-    store_every: int,
-    num_workers: int,
-    force_recompute: bool,
-    device: torch.device,
-    features: Sequence[Feature] = None,
-    data_type2encoders: Mapping[str, Sequence[str]] = DEFAULT_ENCODERS,
-    # modality2collate_fn: Mapping[str, callable] = DEFAULT_COLLATES,
-):
-    """Encode the dataset using the specified encoders.
-
-    Args:
-        dataset (LatentisDataset): Dataset to encode.
-        encoding_batch_size (int): Batch size for the encoding.
-        store_every (int): Store the encodings every `store_every` batches.
-        num_workers (int): Number of workers for the encoding.
-        force_recompute (bool): Whether to recompute the encodings even if they are already present.
-        device (torch.device): Device to use for the encoding.
-        features (Sequence[Feature], optional): Features to encode. Defaults to None. If None, all the features are encoded.
-        data_type2encoders (Mapping[str, Sequence[str]], optional): Encoders to use for each modality. Defaults to DEFAULT_ENCODERS.
-    """
-    if features is None:
-        features = dataset.features
-    else:
-        assert all(feature in dataset.features for feature in features)
-
-    pylogger.info(f"Encoding {len(features)} features for dataset {dataset.name}: {features}")
-    print(features)
-    for feature in features:
-        print(feature)
-        feature_type = feature.data_type
-        if feature_type not in data_type2encoders:
-            pylogger.warning(f"Missing encoders for data type `{feature_type}`. Skipping feature `{feature}`.")
-            continue
-
-        missing_encoders = [
-            encoder
-            for encoder in data_type2encoders[feature_type]
-            if force_recompute or encoder not in dataset.get_available_encodings(features=feature)
-        ]
-
-        for encoder_name in tqdm(missing_encoders, desc=f"{missing_encoders}"):
-            if feature_type == DataType.TEXT:
-                encoder, tokenizer = load_text_encoder(encoder_name)
-                encoder = encoder.to(device)
-                collate_fn = functools.partial(
-                    collate_fn_text,
-                    tokenizer=tokenizer,
-                    max_length=encoder.config.max_length,
-                    data_key=feature.col_name,
-                    encoder_name=encoder_name,
-                    id_column=dataset.id_column,
-                )
-                encode_fn = functools.partial(
-                    batch_encode,
-                    encoder=encoder,
-                    encoder_name=encoder_name,
-                    modes=[EncodeMode.MEAN],
-                    only_last=True,
-                )
-
-            elif feature_type == DataType.IMAGE:
-                encoder, processor = load_image_encoder(encoder_name)
-                collate_fn = functools.partial(
-                    collate_fn_image,
-                    processor=processor,
-                    data_key=feature.col_name,
-                    encoder_name=encoder_name,
-                    id_column=dataset.id_column,
-                )
-                encode_fn = functools.partial(
-                    encode_image,
-                    transform=processor,
-                    encoder=encoder,
-                    encoder_name=encoder_name,
+            raw_encoding = model.encode(batch.to(device))
+
+            if not poolers:
+                assert isinstance(raw_encoding, torch.Tensor)
+
+                dataset.add_encoding(
+                    item=LatentSpace(
+                        vector_source=(raw_encoding.detach().cpu(), batch[dataset._id_column].cpu().tolist()),
+                        properties={
+                            **{f"model/{key}": value for key, value in model.properties.items()},
+                            "feature": feature.col_name,
+                            "split": split,
+                            "dataset": dataset.name,
+                        },
+                    ),
+                    save_source_model=save_source_model,
                 )
             else:
-                raise ValueError(f"Unknown feature modality {feature_type}")
-
-            encoder.to(device)
-
-            encode_feature(
-                dataset=dataset,
-                feature=feature,
-                collate_fn=collate_fn,
-                encode_fn=encode_fn,
-                encoder_name=encoder_name,
-                encoder=encoder,
-                encoding_batch_size=encoding_batch_size,
-                store_every=store_every,
-                num_workers=num_workers,
-                device=device,
-            )
-
-            encoder.cpu()
+                for pooler in poolers:
+                    encoding2pooler_properties = pooler(**raw_encoding)
+
+                    for encoding, pooler_properties in encoding2pooler_properties:
+                        dataset.add_encoding(
+                            item=LatentSpace(
+                                vector_source=(encoding.detach().cpu(), batch[dataset._id_column].cpu().tolist()),
+                                properties={
+                                    **{f"model/{key}": value for key, value in model.properties.items()},
+                                    "feature": feature.col_name,
+                                    "split": split,
+                                    "dataset": dataset.name,
+                                    **pooler_properties,
+                                },
+                            ),
+                            save_source_model=save_source_model,
+                        )
+
+    model.cpu()
 
 
 if __name__ == "__main__":
-    dataset = LatentisDataset.load_from_disk("imdb", perc=0.01)
-    print(dataset.dataset)
-
-    encode_dataset(
-        dataset=dataset,
-        encoding_batch_size=32,
-        store_every=10,
-        num_workers=4,
-        force_recompute=True,
-        device=torch.device("cpu"),
-        data_type2encoders={DataType.TEXT: ["bert-base-cased"]},
-    )
-
-# if __name__ == "__main__":
-#     dataset = LatentisDataset.load_from_disk("imdb", 0.01)
-#     print("original dataset", dataset.dataset)
-#     dataset = dataset.with_encodings(["bert-base-cased_mean_encoding"])
-#     print(dataset)
+    for dataset, hf_encoder in itertools.product(["trec"], ["bert-base-cased", "bert-base-uncased"]):
+        dataset = LatentisDataset.load_from_disk(DATA_DIR / dataset)
+
+        encode_feature(
+            dataset=dataset,
+            feature="text",
+            model=TextHFEncoder(hf_encoder),
+            collate_fn=default_collate,
+            encoding_batch_size=256,
+            store_every=10,
+            num_workers=0,
+            save_source_model=False,
+            poolers=[
+                HFPooler(layers=[11, 12], pooling_fn=cls_pool),
+            ],
+            device=torch.device("cpu"),
+        )
diff --git a/src/latentis/data/image_encoding.py b/src/latentis/data/image_encoding.py
deleted file mode 100644
index ffccd9f..0000000
--- a/src/latentis/data/image_encoding.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from typing import List, Sequence
-
-import torch
-from PIL.Image import Image
-
-
-@torch.no_grad()
-def image_encode(
-    images: Sequence[Image],
-    transform,
-):
-    images: List[torch.Tensor] = [transform(image["image"].convert("RGB")) for image in images]
-    images: torch.Tensor = torch.stack(images, dim=0)
-
-    return {"image": images}
-
-
-@torch.no_grad()
-def clip_image_encode(
-    images: Sequence[Image],
-    transform,
-):
-    images = [image["image"].convert("RGB") for image in images]
-    images = transform(images=images, return_tensors="pt")
-    return {"image": images}
diff --git a/src/latentis/data/processor.py b/src/latentis/data/processor.py
index 534e97f..317f77b 100644
--- a/src/latentis/data/processor.py
+++ b/src/latentis/data/processor.py
@@ -1,10 +1,11 @@
 import logging
 from abc import abstractmethod
+from pathlib import Path
 from typing import Any, Mapping, Sequence
 
 from datasets import ClassLabel, Dataset, DatasetDict, load_dataset
-from torchvision.transforms import Compose, ToTensor
 
+from latentis.data import DATA_DIR
 from latentis.data.dataset import DataType, Feature, FeatureMapping, FeatureProperty, LatentisDataset
 
 pylogger = logging.getLogger(__name__)
@@ -32,18 +33,17 @@ def map_features(dataset: Dataset, *feature_mappings: FeatureMapping):
 
 
 _RANDOM_SEED: int = 42
+_ID_COLUMN: str = "sample_id"
 
 
 class DataProcessor:
     def __init__(
         self,
-        dataset_name: str,
         load_dataset_params: Mapping[str, Any],
         features: Sequence[Feature],
         metadata={},
-        id_column: str = "sample_id",
+        id_column: str = _ID_COLUMN,
     ):
-        self.dataset_name: str = dataset_name
         self.load_dataset_params = load_dataset_params
         self.features = features
         self.metadata = metadata
@@ -53,38 +53,40 @@ def __init__(
     def _process(self, dataset: DatasetDict) -> DatasetDict:
         raise NotImplementedError
 
-    def process(self, perc: float = 1) -> LatentisDataset:
-        dataset: DatasetDict = load_dataset(**self.load_dataset_params)
+    def process(self, dataset_name: str = None, perc: float = 1, parent_dir: Path = DATA_DIR) -> LatentisDataset:
+        hf_dataset: DatasetDict = load_dataset(**self.load_dataset_params)
+        dataset_name: str = dataset_name or list(hf_dataset.values())[0].info.dataset_name
 
         # Select a random subset, if needed
         if perc != 1:
-            dataset = DatasetDict(
+            hf_dataset = DatasetDict(
                 {
-                    split: dataset[split]
+                    split: hf_dataset[split]
                     .shuffle(seed=_RANDOM_SEED)
-                    .select(list(range(int(len(dataset[split]) * perc))))
-                    for split in dataset.keys()
+                    .select(list(range(int(len(hf_dataset[split]) * perc))))
+                    for split in hf_dataset.keys()
                 }
             )
-        start_columns = {col for cols in dataset.column_names.values() for col in cols}
+
+        start_columns = {col for cols in hf_dataset.column_names.values() for col in cols}
         core_columns = {feature.col_name for feature in self.features}
 
-        dataset: DatasetDict = self._process(dataset=dataset)
+        hf_dataset: DatasetDict = self._process(dataset=hf_dataset)
 
-        dataset = dataset.remove_columns([col for col in start_columns if col not in core_columns])
+        hf_dataset = hf_dataset.remove_columns([col for col in start_columns if col not in core_columns])
 
-        dataset = dataset.map(
+        hf_dataset = hf_dataset.map(
             lambda _, index: {self.id_column: index},
             with_indices=True,
         )
 
         processed_dataset = LatentisDataset(
-            dataset=dataset,
-            name=self.dataset_name,
+            name=dataset_name,
+            perc=perc,
+            hf_dataset=hf_dataset,
             id_column=self.id_column,
             features=self.features,
-            perc=perc,
-            metadata=self.metadata,
+            parent_dir=parent_dir,
         )
 
         return processed_dataset
@@ -93,7 +95,6 @@ def process(self, perc: float = 1) -> LatentisDataset:
 class DBPedia14(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="dbpedia_14",
             load_dataset_params=dict(path="dbpedia_14"),
             features=[
                 Feature(col_name="x", data_type=DataType.TEXT, properties={FeatureProperty.LANGUAGE: "en"}),
@@ -117,7 +118,6 @@ def _process(self, dataset: DatasetDict) -> LatentisDataset:
 class TREC(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="trec",
             load_dataset_params=dict(path="trec"),
             features=[
                 Feature(col_name="text", data_type=DataType.TEXT, properties={FeatureProperty.LANGUAGE: "en"}),
@@ -141,7 +141,6 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
 class AGNews(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="ag_news",
             load_dataset_params=dict(path="ag_news"),
             features=[
                 Feature(col_name="text", data_type=DataType.TEXT, properties={FeatureProperty.LANGUAGE: "en"}),
@@ -164,7 +163,6 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
 class IMDB(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="imdb",
             load_dataset_params=dict(path="imdb"),
             features=[
                 Feature(col_name="text", data_type=DataType.TEXT, properties={FeatureProperty.LANGUAGE: "en"}),
@@ -192,7 +190,6 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
 class MNIST(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="mnist",
             load_dataset_params=dict(path="mnist"),
             features=[
                 Feature(col_name="image", data_type=DataType.IMAGE),
@@ -201,18 +198,18 @@ def __init__(self):
         )
 
     def _process(self, dataset: DatasetDict) -> DatasetDict:
-        transforms = Compose(
-            [
-                ToTensor(),
-            ]
-        )
-
-        dataset = dataset.map(
-            lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
-            input_columns=["image"],
-            batched=True,
-        )
-        dataset = dataset.with_format("torch", columns=["image"])
+        # transforms = Compose(
+        #     [
+        #         ToTensor(),
+        #     ]
+        # )
+
+        # dataset = dataset.map(
+        #     lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
+        #     input_columns=["image"],
+        #     batched=True,
+        # )
+        # dataset = dataset.with_format("torch", columns=["image"])
 
         dataset = dataset.cast_column(
             "label",
@@ -228,7 +225,6 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
 class CIFAR10(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="cifar10",
             load_dataset_params=dict(path="cifar10"),
             features=[
                 Feature(col_name="image", data_type=DataType.IMAGE),
@@ -237,18 +233,19 @@ def __init__(self):
         )
 
     def _process(self, dataset: DatasetDict) -> DatasetDict:
-        transforms = Compose(
-            [
-                ToTensor(),
-            ]
-        )
-
-        dataset = dataset.map(
-            lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
-            input_columns=["img"],
-            batched=True,
-        )
-        dataset = dataset.with_format("torch", columns=["image"])
+        # transforms = Compose(
+        #     [
+        #         ToTensor(),
+        #     ]
+        # )
+
+        # dataset = dataset.map(
+        #     lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
+        #     input_columns=["img"],
+        #     batched=True,
+        # )
+        # dataset = dataset.with_format("torch", columns=["image"])
+        dataset = dataset.rename_column("img", "image")
 
         dataset = dataset.cast_column(
             "label",
@@ -264,7 +261,6 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
 class CIFAR100(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="cifar100",
             load_dataset_params=dict(path="cifar100"),
             features=[
                 Feature(col_name="image", data_type=DataType.IMAGE),
@@ -282,18 +278,19 @@ def __init__(self):
         )
 
     def _process(self, dataset: DatasetDict) -> DatasetDict:
-        transforms = Compose(
-            [
-                ToTensor(),
-            ]
-        )
-
-        dataset = dataset.map(
-            lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
-            input_columns=["img"],
-            batched=True,
-        )
-        dataset = dataset.with_format("torch", columns=["image"])
+        # transforms = Compose(
+        #     [
+        #         ToTensor(),
+        #     ]
+        # )
+
+        # dataset = dataset.map(
+        #     lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
+        #     input_columns=["img"],
+        #     batched=True,
+        # )
+        # dataset = dataset.with_format("torch", columns=["image"])
+        dataset = dataset.rename_column("img", "image")
 
         for label in ("coarse_label", "fine_label"):
             dataset = dataset.cast_column(
@@ -310,7 +307,6 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
 class FashionMNIST(DataProcessor):
     def __init__(self):
         super().__init__(
-            dataset_name="fashion_mnist",
             load_dataset_params=dict(path="fashion_mnist"),
             features=[
                 Feature(col_name="image", data_type=DataType.IMAGE),
@@ -319,18 +315,19 @@ def __init__(self):
         )
 
     def _process(self, dataset: DatasetDict) -> DatasetDict:
-        transforms = Compose(
-            [
-                ToTensor(),
-            ]
-        )
+        # transforms = Compose(
+        #     [
+        #         ToTensor(),
+        #     ]
+        # )
 
-        dataset = dataset.map(
-            lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
-            input_columns=["image"],
-            batched=True,
-        )
-        dataset = dataset.with_format("torch", columns=["image"])
+        # dataset = dataset.map(
+        #     lambda images: {"image": [transforms(image.convert("RGB")) for image in images]},
+        #     input_columns=["image"],
+        #     batched=True,
+        # )
+
+        # dataset = dataset.with_format("torch", columns=["image"])
 
         dataset = dataset.cast_column(
             "label",
@@ -341,3 +338,11 @@ def _process(self, dataset: DatasetDict) -> DatasetDict:
         )
 
         return dataset
+
+
+if __name__ == "__main__":
+    latentis_dataset = IMDB().process(
+        perc=0.05,
+    )
+
+    latentis_dataset.save_to_disk()
diff --git a/src/latentis/data/text_encoding.py b/src/latentis/data/text_encoding.py
index d8c380a..50a1cb7 100644
--- a/src/latentis/data/text_encoding.py
+++ b/src/latentis/data/text_encoding.py
@@ -1,249 +1,102 @@
 import logging
-from enum import auto
-from typing import Sequence
+from abc import abstractmethod
+from typing import Optional, Sequence, Tuple
 
 import torch
-from transformers import PreTrainedModel, PreTrainedTokenizer
-
-from latentis.types import StrEnum
+from torch import nn
 
 pylogger = logging.getLogger(__name__)
 
 
-class EncodeMode(StrEnum):
-    MEAN = auto()
-    TOKEN = auto()
-    RAW = auto()
-    SUM = auto()
-    CLS = auto()
-
-
-@torch.no_grad()
-def batch_encode(
-    batch,
-    encoder: PreTrainedModel,
-    encoder_name: str,
-    modes: Sequence[EncodeMode] = EncodeMode.MEAN,
-    only_last: bool = False,
-    return_tensors: str = "pt",
-):
-    batch = batch["tokenizer_result"]
-    modes = set(modes)
-    mask = batch["attention_mask"] * batch["special_tokens_mask"].bool().logical_not()
-    del batch["special_tokens_mask"]
-
-    batch = batch.to(encoder.device)
-
-    if encoder_name.startswith("openai/clip"):
-        encodings = [encoder.text_model(**batch, return_dict=True)["last_hidden_state"]]
-    else:
-        encodings = encoder(**batch)["hidden_states"]
-
-    raw_encodings = {f"raw_encoding_{i_layer}": layer_encoding for i_layer, layer_encoding in enumerate(encodings)}
-
-    result = {} if EncodeMode.RAW not in modes else raw_encodings
-    if EncodeMode.TOKEN in modes:
-        token_encodings = {
-            f"token_encoding_{i_layer}": [
-                sample_encoding[sample_mask].cpu().numpy() for sample_encoding, sample_mask in zip(layer_encoding, mask)
-            ]
-            for i_layer, layer_encoding in enumerate(encodings)
-        }
-        result.update(**token_encodings)
-
-    if EncodeMode.MEAN in modes:
-        pooled_encodings = {
-            f"mean_encoding_{i_layer}": (
-                torch.stack(
-                    [
-                        sample_encoding[sample_mask].mean(dim=0)
-                        for sample_encoding, sample_mask in zip(layer_encoding, mask)
-                    ],
-                    dim=0,
-                )
-            )
-            for i_layer, layer_encoding in enumerate(raw_encodings.values())
-        }
-        if only_last:
-            pooled_encodings = {
-                "mean_encoding": list(pooled_encodings.values())[-1].clone()
-            }  # the standard encoding is set to be the one from the last layer
-        else:
-            pooled_encodings["mean_encoding"] = list(pooled_encodings.values())[
-                -1
-            ].clone()  # the standard encoding is set to be the one from the last layer
-
-        result.update(**pooled_encodings)
-
-    if EncodeMode.SUM in modes:
-        pooled_encodings = {
-            f"sum_encoding_{i_layer}": (
-                torch.stack(
-                    [
-                        sample_encoding[sample_mask].sum(dim=0)
-                        for sample_encoding, sample_mask in zip(layer_encoding, mask)
-                    ],
-                    dim=0,
-                )
-            )
-            for i_layer, layer_encoding in enumerate(raw_encodings.values())
-        }
-
-        if only_last:
-            pooled_encodings = {
-                "sum_encoding": list(pooled_encodings.values())[-1].clone()
-            }  # the standard encoding is set to be the one from the last layer
-        else:
-            pooled_encodings["sum_encoding"] = list(pooled_encodings.values())[
-                -1
-            ].clone()  # the standard encoding is set to be the one from the last layer
-
-        result.update(**pooled_encodings)
-
-    if EncodeMode.CLS in modes:
-        pooled_encodings = {
-            f"cls_encoding_{i_layer}": layer_encoding[:, 0, :]  # TODO: adapt to encoders without CLS as first token
-            for i_layer, layer_encoding in enumerate(raw_encodings.values())
-        }
-
-        if only_last:
-            pooled_encodings = {
-                "cls_encoding": list(pooled_encodings.values())[-1].clone()
-            }  # the standard encoding is set to be the one from the last layer
-        else:
-            pooled_encodings["cls_encoding"] = list(pooled_encodings.values())[
-                -1
-            ].clone()  # the standard encoding is set to be the one from the last layer
-
-        result.update(**pooled_encodings)
-
-    if return_tensors == "numpy":
-        result = {k: v.cpu().numpy() if isinstance(v, torch.Tensor) else v for k, v in result.items()}
-    elif return_tensors == "pt":
-        pass
-    else:
-        raise NotImplementedError
+class Pooler(nn.Module):
+    def __init__(self, name: str):
+        super().__init__()
+        self.name: str = name
 
-    return result
-
-
-@torch.no_grad()
-def sample_encode(
-    batch,
-    tokenizer: PreTrainedTokenizer,
-    encoder: PreTrainedModel,
-    prefix: str,
-    modes: Sequence[EncodeMode] = EncodeMode.MEAN,
-    only_last: bool = False,
-    return_tensors: str = "pt",
-    data_key: str = "data",
-):
-    modes = set(modes)
-
-    encoding = tokenizer(
-        batch[data_key],
-        return_special_tokens_mask=True,
-        return_token_type_ids=True,
-        return_tensors="pt",
-        truncation=True,
-        max_length=encoder.config.max_length,
-        padding=True,
-    ).to(encoder.device)
-
-    mask = encoding["attention_mask"] * encoding["special_tokens_mask"].bool().logical_not()
-    del encoding["special_tokens_mask"]
-
-    encoding = encoding.to(encoder.device)
-
-    encodings = encoder(**encoding)["hidden_states"]
-
-    raw_encodings = {
-        f"{prefix}_raw_encoding_{i_layer}": layer_encoding for i_layer, layer_encoding in enumerate(encodings)
-    }
-
-    result = {} if EncodeMode.RAW not in modes else raw_encodings
-    if EncodeMode.TOKEN in modes:
-        token_encodings = {
-            f"{prefix}_token_encoding_{i_layer}": [
-                sample_encoding[sample_mask].cpu().numpy() for sample_encoding, sample_mask in zip(layer_encoding, mask)
-            ]
-            for i_layer, layer_encoding in enumerate(encodings)
-        }
-        result.update(**token_encodings)
-
-    if EncodeMode.MEAN in modes:
-        pooled_encodings = {
-            f"{prefix}_mean_encoding_{i_layer}": (
-                torch.stack(
-                    [
-                        sample_encoding[sample_mask].mean(dim=0)
-                        for sample_encoding, sample_mask in zip(layer_encoding, mask)
-                    ],
-                    dim=0,
-                )
-            )
-            for i_layer, layer_encoding in enumerate(raw_encodings.values())
-        }
-        if only_last:
-            pooled_encodings = {
-                f"{prefix}_mean_encoding": list(pooled_encodings.values())[-1].clone()
-            }  # the standard encoding is set to be the one from the last layer
-        else:
-            pooled_encodings[f"{prefix}_mean_encoding"] = list(pooled_encodings.values())[
-                -1
-            ].clone()  # the standard encoding is set to be the one from the last layer
-
-        result.update(**pooled_encodings)
-
-    if EncodeMode.SUM in modes:
-        pooled_encodings = {
-            f"{prefix}_sum_encoding_{i_layer}": (
-                torch.stack(
-                    [
-                        sample_encoding[sample_mask].sum(dim=0)
-                        for sample_encoding, sample_mask in zip(layer_encoding, mask)
-                    ],
-                    dim=0,
-                )
-            )
-            for i_layer, layer_encoding in enumerate(raw_encodings.values())
-        }
-
-        if only_last:
-            pooled_encodings = {
-                f"{prefix}_sum_encoding": list(pooled_encodings.values())[-1].clone()
-            }  # the standard encoding is set to be the one from the last layer
-        else:
-            pooled_encodings[f"{prefix}_sum_encoding"] = list(pooled_encodings.values())[
-                -1
-            ].clone()  # the standard encoding is set to be the one from the last layer
-
-        result.update(**pooled_encodings)
-
-    if EncodeMode.CLS in modes:
-        pooled_encodings = {
-            f"{prefix}_cls_encoding_{i_layer}": layer_encoding[
-                :, 0, :
-            ]  # TODO: adapt to encoders without CLS as first token
-            for i_layer, layer_encoding in enumerate(raw_encodings.values())
-        }
-
-        if only_last:
-            pooled_encodings = {
-                f"{prefix}_cls_encoding": list(pooled_encodings.values())[-1].clone()
-            }  # the standard encoding is set to be the one from the last layer
-        else:
-            pooled_encodings[f"{prefix}_cls_encoding"] = list(pooled_encodings.values())[
-                -1
-            ].clone()  # the standard encoding is set to be the one from the last layer
-
-        result.update(**pooled_encodings)
-
-    if return_tensors == "numpy":
-        result = {k: v.cpu().numpy() if isinstance(v, torch.Tensor) else v for k, v in result.items()}
-    elif return_tensors == "pt":
-        pass
-    else:
+    @abstractmethod
+    def forward(self, x: torch.Tensor, *args, **kwargs):
         raise NotImplementedError
 
-    return result
+
+class HFPooler(Pooler):
+    def __init__(self, pooling_fn: callable, layers: Optional[Sequence[int]] = None):
+        assert all(isinstance(layer, int) and layer >= 0 for layer in layers)
+        super().__init__(name=f"{pooling_fn.__name__}_{layers}")
+        self.pooling_fn = pooling_fn
+        self.layers = layers
+
+    def forward(self, x: torch.Tensor, mask: torch.Tensor):
+        return self.pooling_fn(encodings=x, mask=mask, layers=self.layers)
+
+
+def token_pool(encodings: Tuple[torch.Tensor], mask: torch.Tensor, layers: Optional[Sequence[int]] = None):
+    assert all(isinstance(layer, int) and layer >= 0 for layer in layers)
+    layers = list(range(len(encodings))) if not layers else set(layers)
+
+    token_encodings = [
+        (
+            [sample_encoding[sample_mask].cpu().numpy() for sample_encoding, sample_mask in zip(layer_encoding, mask)],
+            {"pool": "token", "layer": i_layer},
+        )
+        for i_layer, layer_encoding in enumerate(encodings)
+        if i_layer in layers
+    ]
+
+    return token_encodings
+
+
+def mean_pool(encodings: Tuple[torch.Tensor], mask: torch.Tensor, layers: Optional[Sequence[int]] = None):
+    assert all(isinstance(layer, int) and layer >= 0 for layer in layers)
+    layers = list(range(len(encodings))) if not layers else set(layers)
+
+    pooled_encodings = [
+        (
+            torch.stack(
+                [
+                    sample_encoding[sample_mask].mean(dim=0)
+                    for sample_encoding, sample_mask in zip(layer_encoding, mask)
+                ],
+                dim=0,
+            ),
+            {"pool": "mean", "layer": i_layer},
+        )
+        for i_layer, layer_encoding in enumerate(encodings)
+        if i_layer in layers
+    ]
+
+    return pooled_encodings
+
+
+def sum_pool(encodings: Tuple[torch.Tensor], mask: torch.Tensor, layers: Optional[Sequence[int]] = None):
+    assert all(isinstance(layer, int) and layer >= 0 for layer in layers)
+    layers = list(range(len(encodings))) if not layers else set(layers)
+
+    pooled_encodings = [
+        (
+            torch.stack(
+                [sample_encoding[sample_mask].sum(dim=0) for sample_encoding, sample_mask in zip(layer_encoding, mask)],
+                dim=0,
+            ),
+            {"pool": "sum", "layer": i_layer},
+        )
+        for i_layer, layer_encoding in enumerate(encodings)
+        if i_layer in layers
+    ]
+
+    return pooled_encodings
+
+
+def cls_pool(encodings: Tuple[torch.Tensor], layers: Optional[Sequence[int]] = None, **kwargs):
+    assert all(isinstance(layer, int) and layer >= 0 for layer in layers)
+    layers = list(range(len(encodings))) if not layers else set(layers)
+
+    pooled_encodings = [
+        (
+            layer_encoding[:, 0, :],
+            {"pool": "cls", "layer": i_layer},
+        )  # TODO: adapt to encoders without CLS as first token
+        for i_layer, layer_encoding in enumerate(encodings)
+        if i_layer in layers
+    ]
+
+    return pooled_encodings
diff --git a/src/latentis/data/train.py b/src/latentis/data/train.py
new file mode 100644
index 0000000..3da4e3b
--- /dev/null
+++ b/src/latentis/data/train.py
@@ -0,0 +1,133 @@
+import itertools
+from typing import Callable, Dict, Sequence
+
+import torch
+from torch import nn
+
+from latentis.data import DATA_DIR
+from latentis.data.dataset import LatentisDataset
+from latentis.nn import LatentisModule
+from latentis.nn.decoders import Classifier
+
+
+def fit_model(
+    model: LatentisModule,
+    dataset: LatentisDataset,
+    train_space_id: str,
+    test_space_id: str,
+    y_gt_key: str,
+    batch_size: int,
+    num_workers: int,
+    device: torch.device,
+    **trainer_params,
+):
+    model = model.to(device)
+
+    train_dataloader = dataset.get_dataloader(
+        space_id=train_space_id,
+        hf_x_keys=None,
+        hf_y_keys=(y_gt_key,),
+        batch_size=batch_size,
+        shuffle=True,
+        num_workers=num_workers,
+    )
+
+    test_dataloader = dataset.get_dataloader(
+        space_id=test_space_id,
+        hf_x_keys=None,
+        hf_y_keys=(y_gt_key,),
+        batch_size=batch_size,
+        shuffle=True,
+        num_workers=num_workers,
+    )
+
+    model.fit(train_dataloader=train_dataloader, **trainer_params)
+
+    return model.score(dataloader=test_dataloader)
+
+
+def attach_decoder(
+    dataset: LatentisDataset,
+    train_space_id: str,
+    test_space_id: str,
+    y_gt_key: str,
+    model_builder: Callable[[str], LatentisModule],
+    train_model: bool,
+    batch_size: int,
+    num_workers: int,
+    device: torch.device,
+    exists_ok: bool = False,
+) -> Sequence[Dict[str, float]]:
+    model = model_builder().to(device)
+    model_perfs = None
+    if train_model:
+        model_perfs = fit_model(
+            model=model,
+            dataset=dataset,
+            train_space_id=train_space_id,
+            test_space_id=test_space_id,
+            y_gt_key=y_gt_key,
+            batch_size=batch_size,
+            num_workers=num_workers,
+            device=device,
+        )
+        print(model_perfs)
+
+    train_space = dataset.encodings.load_item(item_key=train_space_id)
+
+    try:
+        train_space.decoders.add_item(item=model)
+    except FileExistsError as e:
+        if not exists_ok:
+            raise e
+
+
+if __name__ == "__main__":
+    for (dataset_name, label_feature), hf_encoder_name in itertools.product(
+        [("trec", "coarse_label"), ("imdb", "label"), ("ag_news", "label")],
+        ["bert-base-cased", "bert-base-uncased", "roberta-base"],
+    ):
+        dataset = LatentisDataset.load_from_disk(DATA_DIR / dataset_name)
+
+        res = attach_decoder(
+            dataset=dataset,
+            train_space_id=(
+                space_key := dataset.encodings.get_item_key(
+                    split="train", layer=12, **{"model/hf_name": hf_encoder_name}
+                )
+            ),
+            test_space_id=dataset.encodings.get_item_key(split="test", layer=12, **{"model/hf_name": hf_encoder_name}),
+            y_gt_key=label_feature,
+            model_builder=lambda: Classifier(
+                input_dim=dataset.encodings.load_item(item_key=space_key).shape[1],  # TODO add this a space property
+                num_classes=len(dataset.hf_dataset["train"].features[label_feature].names),
+                deep=True,
+                bias=True,
+                x_feature="encodings_key",
+                y_feature="hf_y_keys",
+                first_activation=nn.Tanh(),
+                second_activation=nn.ReLU(),
+                first_projection_dim=None,
+            ),
+            train_model=True,
+            batch_size=128,
+            num_workers=0,
+            device=torch.device("cpu"),
+            exists_ok=True,  # TODO: careful here
+        )
+
+        # space = dataset.encodings.load_item(split="train", layer=12, **{"model/hf_name": "bert-base-uncased"})
+        # decoder = space.decoders.load_item()
+
+        # dataloader = dataset.get_dataloader(
+        #     space_id=dataset.encodings.get_item_key(
+        #         split="test", layer=12, **{"model/hf_name": "bert-base-uncased"}
+        #     ),
+        #     hf_x_keys=None,
+        #     hf_y_keys=(label_feature,),
+        #     batch_size=128,
+        #     shuffle=True,
+        #     num_workers=0,
+        # )
+
+        # print(decoder.score(dataloader))
diff --git a/src/latentis/data/utils.py b/src/latentis/data/utils.py
new file mode 100644
index 0000000..c9e4e52
--- /dev/null
+++ b/src/latentis/data/utils.py
@@ -0,0 +1,79 @@
+from pathlib import Path
+from typing import Dict, Sequence
+
+import pandas as pd
+import torch
+from transformers import BatchEncoding
+
+from latentis.nn import LatentisModule
+from latentis.serialize.io_utils import SerializableMixin
+
+
+class BiMap(SerializableMixin):
+    def __init__(self, x: Sequence[str], y: Sequence[int]):
+        assert len(x) == len(y), "x and y must have the same length"
+        self._x2y: Dict[str, int] = {k: v for k, v in zip(x, y)}
+        self._y2x: Dict[int, str] = {v: k for k, v in self._x2y.items()}
+
+    def contains_x(self, x: str) -> bool:
+        return x in self._x2y
+
+    def contains_y(self, y: int) -> bool:
+        return y in self._y2x
+
+    def get_x(self, y: int) -> str:
+        return self._y2x[y]
+
+    def get_y(self, x: str) -> int:
+        return self._x2y[x]
+
+    def add(self, x: str, y: int):
+        assert x not in self._x2y, f"X `{x}` already exists"
+        assert y not in self._y2x, f"Y `{y}` already exists"
+
+        self._x2y[x] = y
+        self._y2x[y] = x
+
+    def add_all(self, x: Sequence[str], y: Sequence[int]):
+        assert len(x) == len(y), "x and y must have the same length"
+        for x_i, y_i in zip(x, y):
+            self.add(x_i, y_i)
+
+    def __len__(self) -> int:
+        assert len(self._x2y) == len(self._y2x)
+        return len(self._x2y)
+
+    def save_to_disk(self, target_path: Path):
+        df = pd.DataFrame({"x": list(self._x2y.keys()), "y": list(self._x2y.values())})
+        df.to_csv(target_path, sep="\t", index=False)
+
+    @classmethod
+    def load_from_disk(cls, path: Path) -> "BiMap":
+        mapping = pd.read_csv(path, sep="\t")
+        return cls(x=mapping["x"].tolist(), y=mapping["y"].tolist())
+
+    @property
+    def x(self):
+        return self._x2y.keys()
+
+    @property
+    def y(self):
+        return self._y2x.keys()
+
+    def __repr__(self) -> str:
+        return repr(self._x2y)
+
+
+def default_collate(
+    samples: Sequence,
+    feature: str,
+    model: LatentisModule,
+    id_column: str = None,
+) -> BatchEncoding:
+    from latentis.data.processor import _ID_COLUMN
+
+    id_column = id_column or _ID_COLUMN
+    batch = model.pre_encode(samples, feature=feature)
+    batch[id_column] = torch.as_tensor([sample[id_column] for sample in samples])
+
+    return BatchEncoding(batch)
diff --git a/src/latentis/measure/_abstract.py b/src/latentis/measure/_abstract.py
index 67f1d68..fa2fe1e 100644
--- a/src/latentis/measure/_abstract.py
+++ b/src/latentis/measure/_abstract.py
@@ -1,18 +1,19 @@
 from __future__ import annotations
 
 from abc import abstractmethod
-from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence
+from typing import TYPE_CHECKING, Any, Callable, Mapping, Tuple
 
 import torch
 from torch import nn
 
-import latentis
+from latentis.serialize.io_utils import IndexableMixin
+from latentis.space import LatentSpace
 
 if TYPE_CHECKING:
     from latentis.types import Space
 
 
-class PairwiseMetric(nn.Module):
+class Metric(nn.Module, IndexableMixin):
     def __init__(self, name: str) -> None:
         super().__init__()
         self._name: str = name
@@ -25,7 +26,16 @@ def name(self) -> str:
         return self._name
 
     @abstractmethod
-    def forward(self, x: Space, y: Space) -> Sequence[Mapping[str, Any]]:
+    def forward(self, *spaces: Space) -> Mapping[Tuple[str], Mapping[str, Any]]:
+        raise NotImplementedError
+
+
+class PairwiseMetric(Metric):
+    def __init__(self, name: str) -> None:
+        super().__init__(name=name)
+
+    @abstractmethod
+    def forward(self, x: Space, y: Space) -> Mapping[str, Any]:
         raise NotImplementedError
 
 
@@ -36,10 +46,10 @@ def __init__(self, key: str, fn: Callable([Space, Space], torch.Tensor)) -> None
         self.fn = fn
 
     def forward(self, x: Space, y: Space) -> Mapping[str, Any]:
-        if isinstance(x, latentis.LatentSpace):
+        if isinstance(x, LatentSpace):
             x = x.vectors
 
-        if isinstance(y, latentis.LatentSpace):
+        if isinstance(y, LatentSpace):
             y = y.vectors
 
         return {self.key: self.fn(x, y)}
diff --git a/src/latentis/measure/functional.py b/src/latentis/measure/functional.py
index b9e654d..f4b0ec0 100644
--- a/src/latentis/measure/functional.py
+++ b/src/latentis/measure/functional.py
@@ -3,7 +3,7 @@
 
 import torch
 
-import latentis
+from latentis.space import LatentSpace
 
 
 def linear_cka(x: torch.Tensor, y: torch.Tensor):
@@ -15,10 +15,10 @@ def rbf_cka(x: torch.Tensor, y: torch.Tensor, *, sigma: float = None):
 
 
 def cka(x: torch.Tensor, y: torch.Tensor, *, hsic: callable, tolerance=1e-6):
-    if isinstance(x, latentis.LatentSpace):
+    if isinstance(x, LatentSpace):
         x = x.vectors
 
-    if isinstance(y, latentis.LatentSpace):
+    if isinstance(y, LatentSpace):
         y = y.vectors
 
     assert x.shape[0] == y.shape[0], "X and Y must have the same number of samples."
diff --git a/src/latentis/nn/__init__.py b/src/latentis/nn/__init__.py
new file mode 100644
index 0000000..5deb6dd
--- /dev/null
+++ b/src/latentis/nn/__init__.py
@@ -0,0 +1,5 @@
+from ._base import LatentisModule
+
+__all__ = [
+    "LatentisModule",
+]
diff --git a/src/latentis/nn/_base.py b/src/latentis/nn/_base.py
new file mode 100644
index 0000000..351d1d7
--- /dev/null
+++ b/src/latentis/nn/_base.py
@@ -0,0 +1,157 @@
+from __future__ import annotations
+
+import copy
+from abc import abstractmethod
+from enum import auto
+
+try:
+    # be ready for 3.10 when it drops
+    from enum import StrEnum as PythonStrEnum
+except ImportError:
+    from backports.strenum import StrEnum as PythonStrEnum
+
+from pathlib import Path
+from typing import Mapping, Optional, Sequence
+
+from lightning import LightningModule
+from torch import nn
+from torch.utils.data import DataLoader, default_collate
+
+from latentis.serialize.io_utils import IndexableMixin, load_json, load_model, save_json, save_model
+from latentis.types import Properties
+
+
+class _LatentisModuleMetadata(PythonStrEnum):
+    _VERSION = auto()
+    _TYPE = auto()
+
+
+_PROPERTIES_FILE_NAME = "properties.json"
+
+
+class LatentisModule(LightningModule, IndexableMixin):
+    def __init__(self, properties: Optional[Properties] = None):
+        super().__init__()
+
+        properties = properties or {}
+        # metadata[SpaceMetadata._NAME] = self._name
+        properties[_LatentisModuleMetadata._VERSION] = self.version
+
+        # TODO: store also the module to use for deserialization,
+        # removing this info from the index
+        properties[_LatentisModuleMetadata._TYPE] = LatentisModule.__name__
+
+        self._properties = properties.copy()
+
+    def save_to_disk(self, target_path: Path) -> None:
+        target_path.mkdir(parents=True, exist_ok=True)
+        save_json(self.properties, target_path / _PROPERTIES_FILE_NAME)
+        save_model(model=self, target_path=target_path / "model.pt", version=self.version)
+
+    @classmethod
+    def load_from_disk(cls, path: Path) -> LatentisModule:
+        properties = cls.load_properties(path)
+        # TODO: if the save is changed, the properties should be injected here
+        return load_model(path / "model.pt", version=properties[_LatentisModuleMetadata._VERSION])
+
+    @property
+    def version(cls) -> int:
+        return 0
+
+    @classmethod
+    def load_properties(cls, space_path: Path) -> Properties:
+        metadata = load_json(space_path / _PROPERTIES_FILE_NAME)
+        return metadata
+
+    @property
+    def properties(self) -> Properties:
+        return copy.deepcopy(self._properties)
+
+    @abstractmethod
+    def fit(self, train_dataloader: DataLoader) -> LatentisModule:
+        raise NotImplementedError
+
+    @abstractmethod
+    def score(self, test_dataloader: DataLoader) -> Mapping[str, float]:
+        raise NotImplementedError
+
+    def pre_encode(self, samples: Sequence, feature: str):
+        return default_collate({feature: [sample[feature] for sample in samples]})
+
+    @abstractmethod
+    def encode(self, *args, **kwargs):
+        raise NotImplementedError
+
+    @abstractmethod
+    def post_encode(self, *args, **kwargs):
+        raise NotImplementedError
+
+    @abstractmethod
+    def pre_decode(self, *args, **kwargs):
+        raise NotImplementedError
+
+    @abstractmethod
+    def decode(self, *args, **kwargs):
+        raise NotImplementedError
+
+
+class WrappedModule(LatentisModule):
+    def __init__(
+        self,
+        model: nn.Module,
+        encode_fn: Optional[str] = None,
+        decode_fn: Optional[str] = None,
+        properties: Optional[Properties] = None,
+    ):
+        super().__init__(properties=properties)
+        self.model = model
+        self.encode_fn = encode_fn
+        self.decode_fn = decode_fn
+
+    def encode(self, *args, **kwargs):
+        if self.encode_fn is None:
+            raise NotImplementedError
+        return getattr(self.model, self.encode_fn)(*args, **kwargs)
+
+    def decode(self, *args, **kwargs):
+        if self.decode_fn is None:
+            raise NotImplementedError
+        return getattr(self.model, self.decode_fn)(*args, **kwargs)
+
+
+class PooledModel(WrappedModule):
+    def __init__(
+        self,
+        model: nn.Module,
+        pooler: nn.Module,
+        encode_fn: str = "encode",
+        decode_fn: str = "decode",
+        properties: Optional[Properties] = None,
+    ):
+        super().__init__(
+            model=model,
+            encode_fn=encode_fn,
+            decode_fn=decode_fn,
+            properties={
+                **properties,
+                **{"pooler": self.pooler.name if hasattr(self.pooler, "name") else self.pooler.__class__.__name__},
+            },
+        )
+        self.pooler = pooler
+
+    def encode(self, *args, **kwargs):
+        return self.pooler(super().encode(*args, **kwargs))
+
+
+# TODO: this must be a LatentisModule
+class StitchedModel(nn.Module):
+    def __init__(self, encoding_model: LatentisModule, decoding_model: LatentisModule):
+        super().__init__()
+        self.encoding_model = encoding_model
+        self.decoding_model = decoding_model
+
+    def encode(self, *args, **kwargs):
+        return self.encoding_model.encode(*args, **kwargs)
+
+    def decode(self, *args, **kwargs):
+        return self.decoding_model.decode(*args, **kwargs)
diff --git a/src/latentis/nn/decoders.py b/src/latentis/nn/decoders.py
new file mode 100644
index 0000000..947539b
--- /dev/null
+++ b/src/latentis/nn/decoders.py
@@ -0,0 +1,158 @@
+import logging
+from typing import Any, Mapping, Optional
+
+import torch
+import torch.nn.functional as F
+from lightning import Trainer
+from torch import nn
+from torch.utils.data import DataLoader
+from torchmetrics import Accuracy, FBetaScore, MetricCollection
+
+from latentis.nn._base import LatentisModule
+
+pylogger = logging.getLogger(__name__)
+
+
+class SVCModel(LatentisModule):
+    def __init__(self, model):
+        super().__init__()
+        self.model = model
+
+    def forward(self, x):
+        return F.one_hot(torch.as_tensor(self.model.predict(x.cpu().numpy()))).to(x.device)
+
+
+class LambdaModule(LatentisModule):
+    def __init__(self, lambda_func) -> None:
+        super().__init__()
+        self.lambda_func = lambda_func
+
+    def forward(self, x: torch.Tensor):
+        return self.lambda_func(x)
+
+
+class Classifier(LatentisModule):
+    def __init__(
+        self,
+        input_dim: int,
+        num_classes: int,
+        deep: bool,
+        bias: bool = True,
+        x_feature: str = "x",
+        y_feature: str = "y",
+        first_activation: nn.Module = nn.Tanh(),
+        second_activation: nn.Module = nn.ReLU(),
+        first_projection_dim: Optional[int] = None,
+        trainer_params: Mapping[str, Any] = None,
+        lr: float = 1e-3,
+    ):
+        super().__init__(
+            properties={
+                "name": "classifier",
+                "input_dim": input_dim,
+                "num_classes": num_classes,
+                "deep": deep,
+                "bias": bias,
+                "x_feature": x_feature,
+                "y_feature": y_feature,
+                "first_activation": first_activation.__class__.__name__,
+                "second_activation": second_activation.__class__.__name__,
+                "first_projection_dim": first_projection_dim,
+                # "trainer_params": trainer_params,
+                "lr": lr,
+            }
+        )
+        self.latentis_trainer_params = dict(
+            accelerator="auto",
+            devices=1,
+            max_epochs=5,
+            logger=False,
+            # callbacks=[RichProgressBar()],
+            enable_progress_bar=False,
+            enable_checkpointing=False,
+        )
+        self.latentis_trainer_params.update(trainer_params or {})
+        self.lr = lr
+
+        if not isinstance(deep, bool):
+            raise ValueError(f"deep must be bool, got {deep} of type {type(deep)}")
+
+        if not deep and (first_activation is None or second_activation is None or first_projection_dim is None):
+            pylogger.warning(
+                "If deep is False, first_activation, second_activation and first_projection_dim are not used!"
+            )
+
+        if callable(first_activation) and getattr(first_activation, "__name__", None) == "<lambda>":
+            first_activation = LambdaModule(first_activation)
+
+        if callable(second_activation) and getattr(second_activation, "__name__", None) == "<lambda>":
+            second_activation = LambdaModule(second_activation)
+
+        first_projection_dim = input_dim if first_projection_dim is None else first_projection_dim
+
+        self.class_proj = (
+            nn.Sequential(
+                #
+                nn.Linear(input_dim, first_projection_dim, bias=bias),
+                first_activation,
+                #
+                nn.Linear(first_projection_dim, first_projection_dim // 2, bias=bias),
+                second_activation,
+                #
+                nn.Linear(first_projection_dim // 2, num_classes, bias=bias),
+            )
+            if deep
+            else nn.Sequential(nn.Linear(input_dim, num_classes))
+        )
+
+        self.train_metrics = MetricCollection(
+            {
+                "accuracy": Accuracy(task="multiclass", num_classes=num_classes),
+                "f1": FBetaScore(task="multiclass", num_classes=num_classes),
+            }
+        )
+        # self.val_metrics = self.train_metrics.clone()
+        self.test_metrics = self.train_metrics.clone()
+
+        self.x_feature: str = x_feature
+        self.y_feature: str = y_feature
+        self.latentis_trainer: Optional[Trainer] = None
+
+    def forward(self, x):
+        x = self.class_proj(x)
+        return F.log_softmax(x, dim=1)
+
+    def _step(self, batch, split: str):
+        x = batch[self.x_feature][0]
+        y = batch[self.y_feature][0]
+
+        logits = self(x)
+        loss = F.cross_entropy(logits, y)
+        preds = torch.argmax(logits, dim=1)
+        metrics = getattr(self, f"{split}_metrics")
+        metrics.update(preds, y)
+
+        self.log(f"{split}_loss", loss, prog_bar=True)
+        self.log_dict(metrics, prog_bar=True)
+
+        return loss
+
+    def training_step(self, batch, batch_idx):
+        return self._step(batch=batch, split="train")
+
+    def test_step(self, batch, batch_idx):
+        return self._step(batch=batch, split="test")
+
+    def configure_optimizers(self):
+        optimizer = torch.optim.Adam(self.parameters(), lr=self.lr)
+        return optimizer
+
+    def fit(self, train_dataloader: DataLoader) -> LatentisModule:
+        self.latentis_trainer = Trainer(**self.latentis_trainer_params)
+        self.latentis_trainer.fit(self, train_dataloaders=train_dataloader)
+        return self.eval()
+
+    def score(self, dataloader: DataLoader):
+        if self.latentis_trainer is None:
+            raise RuntimeError("Model has not been fitted yet!")
+        return self.latentis_trainer.test(model=self, dataloaders=dataloader)
diff --git a/src/latentis/nn/encoders.py b/src/latentis/nn/encoders.py
new file mode 100644
index 0000000..a16f59a
--- /dev/null
+++ b/src/latentis/nn/encoders.py
@@ -0,0 +1,102 @@
+from typing import Optional, Sequence
+
+import torch
+from transformers import AutoImageProcessor, AutoModel, AutoTokenizer, BatchEncoding, PreTrainedModel
+
+from latentis.nn._base import WrappedModule
+from latentis.types import Properties
+
+
+class HFEncoder(WrappedModule):
+    def __init__(
+        self,
+        hf_name: str,
+        requires_grad: bool,
+        encode_fn: Optional[str] = None,
+        decode_fn: Optional[str] = None,
+        properties: Optional[Properties] = None,
+    ):
+        hf_model: PreTrainedModel = (
+            AutoModel.from_pretrained(hf_name, output_hidden_states=True, return_dict=True)
+            .eval()
+            .requires_grad_(requires_grad)
+        )
+        self.hf_name = hf_name
+        super().__init__(
+            model=hf_model,
+            encode_fn=encode_fn,
+            decode_fn=decode_fn,
+            properties={**(properties or {}), "hf_name": hf_name},
+        )
+
+
+class TextHFEncoder(HFEncoder):
+    def __init__(
+        self,
+        hf_name: str,
+        requires_grad: bool = False,
+        truncation: bool = True,
+        padding: bool = True,
+        max_length: Optional[int] = None,
+        properties: Optional[Properties] = None,
+        **kwargs,
+    ):
+        super().__init__(hf_name, requires_grad, encode_fn=None, decode_fn=None, properties=properties)
+        self.tokenizer = AutoTokenizer.from_pretrained(hf_name)
+
+        max_length = max_length or self.model.config.max_length
+
+        self.pre_encode_kwargs = {
+            "truncation": truncation,
+            "padding": padding,
+            "max_length": max_length,
+            **kwargs,
+        }
+
+    @torch.no_grad()
+    def pre_encode(self, samples: Sequence, feature: str) -> BatchEncoding:
+        is_clip: bool = self.hf_name.startswith("openai/clip")
+
+        tok_out: BatchEncoding = self.tokenizer(
+            [sample[feature] for sample in samples],
+            return_special_tokens_mask=True,
+            return_token_type_ids=not is_clip,
+            return_tensors="pt",
+            **self.pre_encode_kwargs,
+        )
+
+        return BatchEncoding(dict(tok_out=tok_out))
+
+    def encode(self, x: BatchEncoding):
+        tok_out = x["tok_out"]
+
+        mask = tok_out["attention_mask"] * tok_out["special_tokens_mask"].bool().logical_not()
+        del tok_out["special_tokens_mask"]
+
+        if self.hf_name.startswith("openai/clip"):
+            # TODO: fix this
+            encodings = [self.model.text_model(**tok_out, return_dict=True)["last_hidden_state"]]
+        else:
+            encodings = self.model(**tok_out)["hidden_states"]
+
+        return {"x": encodings, "mask": mask}
+
+
+class ImageHFEncoder(HFEncoder):
+    def __init__(self, hf_name: str, requires_grad: bool = False, properties: Optional[Properties] = None):
+        super().__init__(hf_name, requires_grad, properties=properties)
+        self.processor = AutoImageProcessor.from_pretrained(self.hf_name)
+
+    @torch.no_grad()
+    def pre_encode(self, samples: Sequence, feature: str, **kwargs):
+        images = [sample[feature].convert("RGB") for sample in samples]
+        # images = [sample[feature] for sample in samples]
+        images = self.processor(images=images, return_tensors="pt")
+
+        return {"proc_out": images}
+
+    @torch.no_grad()
+    def encode(self, x: BatchEncoding):
+        x = x["proc_out"]
+        outputs = self.model(**x)
+        return outputs.last_hidden_state[:, 0, :]
diff --git a/src/latentis/sample/sampling.py b/src/latentis/sample/sampling.py
index 3e2339a..e982ede 100644
--- a/src/latentis/sample/sampling.py
+++ b/src/latentis/sample/sampling.py
@@ -53,7 +53,6 @@ def forward(self, *spaces: Space, n: int) -> Union[Sequence[Space], Space]:
             out = tuple(
                 LatentSpace(
                     vector_source=space.vectors[ids],
-                    name=f"{space.name}{self.suffix}",
                     # features={
                     #     SpaceProperty.SAMPLING_IDS: ids,
                     #     **{key: values[ids] for key, values in space.features.items()},
diff --git a/src/latentis/serialize/__init__.py b/src/latentis/serialize/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/latentis/serialize/disk_index.py b/src/latentis/serialize/disk_index.py
new file mode 100644
index 0000000..5805905
--- /dev/null
+++ b/src/latentis/serialize/disk_index.py
@@ -0,0 +1,177 @@
+import importlib
+import shutil
+from pathlib import Path
+from typing import Any, Mapping, Optional, Sequence, Type
+
+import pandas as pd
+
+from latentis.serialize.io_utils import IndexableMixin, SerializableMixin, load_json, save_json
+from latentis.types import Properties
+
+
+class DiskIndex(SerializableMixin):
+    def __init__(self, root_path: Path, item_class: Type[IndexableMixin]):
+        self.root_path = root_path
+        self._item_class = item_class
+        self._index: Mapping[str, Mapping[str, Any]] = {}
+
+    @property
+    def version(self) -> str:
+        return -42
+
+    def save_to_disk(self):
+        self.root_path.mkdir(exist_ok=True)
+
+        info = {
+            "item_class": self._item_class.__name__,
+            "item_class_module": self._item_class.__module__,
+            "version": self.version,
+        }
+
+        # TODO see if the disk index should implement the indexable mixin
+        save_json(info, self.root_path / "info.json")
+
+    @classmethod
+    def _read_index(cls, path: Path, item_class: Type[IndexableMixin]) -> Mapping[str, Mapping[str, Any]]:
+        item_dirs = [item_dir for item_dir in path.iterdir() if item_dir.is_dir()]
+        index = {}
+        for item_dir in item_dirs:
+            item_key = item_dir.name
+            index[item_key] = item_class.load_properties(item_dir)
+            assert item_key == item_class.id_from_properties(index[item_key])
+
+        return index
+
+    @classmethod
+    def load_from_disk(cls, path: Path, *args, **kwargs) -> IndexableMixin:
+        info = load_json(path / "info.json")
+        module = importlib.import_module(info["item_class_module"])
+        item_class = getattr(module, info["item_class"])
+
+        instance = cls.__new__(cls)
+        instance._index = cls._read_index(path, item_class=item_class)
+        instance.root_path = path
+        instance._item_class = item_class
+
+        return instance
+
+    def _resolve_items(self, item_key: Optional[str] = None, **properties: Any) -> Sequence[str]:
+        candidates_items = list(self._index.keys())
+
+        if item_key is not None:
+            candidates_items = [x for x in self._index.keys() if x.startswith(item_key)]
+            if len(candidates_items) == 0:
+                raise KeyError(f"No items with key prefix '{item_key}' found")
+
+        return [
+            key for key in candidates_items if all(self._index[key].get(p, None) == v for p, v in properties.items())
+        ]
+
+    def _resolve_item(self, item_key: Optional[str] = None, **properties: Any) -> str:
+        items = self._resolve_items(item_key=item_key, **properties)
+        if len(items) == 0:
+            raise KeyError(f"No items with key prefix '{item_key}' matching {properties} found")
+        elif len(items) > 1:
+            raise ValueError(f"Multiple items with key prefix '{item_key}' matching {properties} found: {items}")
+        return items[0]
+
+    def _remove_item_by_key(self, item_key: str) -> None:
+        if item_key not in self._index:
+            raise KeyError(f"Key {item_key} does not exist in index")
+
+        del self._index[item_key]
+        shutil.rmtree(self.root_path / item_key)
+        self.save_to_disk()
+
+    def add_item(
+        self,
+        item: IndexableMixin,
+        save_args: Mapping[str, Any] = None,
+    ) -> str:
+        item_key = item.item_id
+
+        if item_key in self._index:
+            raise FileExistsError(f"Key {item_key} already exists in index: {self._index[item_key]}")
+
+        primary_keys = item.properties
+        if len(primary_keys) == 0:
+            raise ValueError("Item does not have any properties")
+
+        self._index[item_key] = item.properties
+
+        item.save_to_disk(self.root_path / item_key, **(save_args or {}))
+        self.save_to_disk()
+        return item_key
+
+    def add_items(self, items: Sequence[IndexableMixin], save_args: Mapping[str, Any] = None) -> Sequence[str]:
+        item_keys = [item.item_id for item in items]
+
+        # Avoid adding any of the items if any of the keys already exist
+        if any(item_key in self._index for item_key in item_keys):
+            raise FileExistsError("One of the keys already exists in index")
+
+        if any(len(item.properties) == 0 for item in items):
+            raise ValueError("One of the items does not have any properties")
+
+        for item, item_key in zip(items, item_keys):
+            self._index[item_key] = item.properties
+            item.save_to_disk(self.root_path / item_key, **(save_args or {}))
+
+        self.save_to_disk()
+        return item_keys
+
+    def remove_item(self, item_key: Optional[str] = None, **properties: Any) -> str:
+        item_to_remove = self._resolve_item(item_key=item_key, **properties)
+        self._remove_item_by_key(item_to_remove)
+        return item_to_remove
+
+    def remove_items(self, item_key: Optional[str] = None, **properties: Any) -> Sequence[str]:
+        items_to_remove = self._resolve_items(item_key=item_key, **properties)
+        for item in items_to_remove:
+            self._remove_item_by_key(item)
+        return items_to_remove
+
+    def load_item(self, item_key: Optional[str] = None, **properties: Any) -> IndexableMixin:
+        item_to_load = self._resolve_item(item_key=item_key, **properties)
+        return self._item_class.load_from_disk(self.root_path / item_to_load)
+
+    def load_items(self, item_key: Optional[str] = None, **properties: Any) -> Mapping[str, IndexableMixin]:
+        items_to_load = self._resolve_items(item_key=item_key, **properties)
+        return {item: self._item_class.load_from_disk(self.root_path / item) for item in items_to_load}
+
+    def get_item_path(self, item_key: Optional[str] = None, **properties: Any) -> Path:
+        item_to_load = self._resolve_item(item_key=item_key, **properties)
+        return self.root_path / item_to_load
+
+    def get_items_path(self, item_key: Optional[str] = None, **properties: Any) -> Mapping[str, Path]:
+        items_to_load = self._resolve_items(item_key=item_key, **properties)
+        return {item: self.root_path / item for item in items_to_load}
+
+    def get_item(self, item_key: Optional[str] = None, **properties: Any) -> Mapping[str, Properties]:
+        item_to_get = self._resolve_item(item_key=item_key, **properties)
+        return {item_to_get: self._index[item_to_get]}
+
+    def get_items(self, item_key: Optional[str] = None, **properties: Any) -> Mapping[str, Properties]:
+        items_to_get = self._resolve_items(item_key=item_key, **properties)
+        return {item: self._index[item] for item in items_to_get}
+
+    def get_items_df(self, item_key: Optional[str] = None, **properties: Any) -> pd.DataFrame:
+        items = self.get_items(item_key=item_key, **properties)
+        return pd.DataFrame.from_dict(items, orient="index").reset_index(names="item_key")
+
+    def get_item_key(self, item_key: Optional[str] = None, **properties: Any) -> str:
+        item_to_load = self._resolve_item(item_key=item_key, **properties)
+        return item_to_load
+
+    def get_items_key(self, item_key: Optional[str] = None, **properties: Any) -> str:
+        item_to_load = self._resolve_items(item_key=item_key, **properties)
+        return item_to_load
+
+    def clear(self):
+        self._index = {}
+        shutil.rmtree(self.root_path)
+        self.root_path.mkdir()
+        self.save_to_disk()
+
+    def __len__(self):
+        return len(self._index)
diff --git a/src/latentis/serialize/io_utils.py b/src/latentis/serialize/io_utils.py
new file mode 100644
index 0000000..7724eff
--- /dev/null
+++ b/src/latentis/serialize/io_utils.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+import hashlib
+import json
+from abc import abstractmethod
+from pathlib import Path
+from typing import Any, Callable, Dict, Optional
+
+import torch
+from torch import nn
+
+from latentis.types import Properties
+
+
+# TODO: Handle versioning
+def save_model(model: nn.Module, target_path: Path, version: int):
+    torch.save(model, target_path)
+
+
+def load_model(model_path: Path, version: int) -> nn.Module:
+    return torch.load(model_path)
+
+
+def _default_json(o):
+    return o.__dict__
+
+
+def save_json(
+    obj: object,
+    path: Path,
+    indent: Optional[int] = 4,
+    sort_keys: bool = True,
+    default: Optional[Callable] = _default_json,
+):
+    with open(path, "w", encoding="utf-8") as fw:
+        json.dump(obj, fw, indent=indent, sort_keys=sort_keys, default=default)
+
+
+def load_json(path: Path):
+    with open(path, "r", encoding="utf-8") as fr:
+        return json.load(fr)
+
+
+class SerializableMixin:
+    @abstractmethod
+    def save_to_disk(self, parent_dir: Path, *args, **kwargs):
+        raise NotImplementedError
+
+    @classmethod
+    @abstractmethod
+    def load_from_disk(cls, path: Path, *args, **kwargs) -> SerializableMixin:
+        raise NotImplementedError
+
+    @property
+    def version(self) -> int:
+        raise NotImplementedError
+
+
+class IndexableMixin(SerializableMixin):
+    @classmethod
+    def id_from_properties(cls, properties: Properties) -> str:
+        hash_object = hashlib.sha256(
+            json.dumps(properties, default=lambda o: o.__dict__, sort_keys=True).encode(encoding="utf-8")
+        )
+        return hash_object.hexdigest()
+
+    @property
+    def item_id(self) -> str:
+        return IndexableMixin.id_from_properties(self.properties)
+
+    @classmethod
+    @abstractmethod
+    def load_properties(cls, path: Path) -> Properties:
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def properties(self) -> Dict[str, Any]:
+        raise NotImplementedError
+
+    def __repr__(self) -> str:
+        public_properties = {k: v for k, v in self.properties.items() if not k.startswith("_")}
+        return f"{self.__class__.__name__}(id={self.item_id[:5]}, properties={public_properties})"
+
+
+class MetadataMixin:
+    _METADATA_FILE_NAME: str = "metadata.json"
+
+    @property
+    @abstractmethod
+    def metadata(self) -> Dict[str, Any]:
+        raise NotImplementedError
diff --git a/src/latentis/space.py b/src/latentis/space.py
deleted file mode 100644
index aa088d1..0000000
--- a/src/latentis/space.py
+++ /dev/null
@@ -1,203 +0,0 @@
-from __future__ import annotations
-
-import copy
-import json
-import logging
-from enum import auto
-from pathlib import Path
-from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Optional, Sequence, Union
-
-from latentis.search import SearchIndex, SearchMetric
-from latentis.vector_source import InMemorySource, VectorSource
-
-if TYPE_CHECKING:
-    from latentis.sample import Sampler
-    from latentis.types import Space
-    from latentis.translate import LatentTranslator
-    from latentis.measure import MetricFn
-
-import torch
-
-try:
-    # be ready for 3.10 when it drops
-    from enum import StrEnum
-except ImportError:
-    from backports.strenum import StrEnum
-
-pylogger = logging.getLogger(__name__)
-
-
-class SpaceProperty(StrEnum):
-    VECTOR_SOURCE = auto()
-
-
-class LatentSpace:
-    def __init__(
-        self,
-        vector_source: Union[torch.Tensor, VectorSource],
-        metadata: Optional[Dict[str, Any]] = None,
-        name: str = "space",
-    ):
-        assert isinstance(
-            vector_source, (torch.Tensor, VectorSource)
-        ), f"Expected {torch.Tensor} or {VectorSource}, got {type(vector_source)}"
-        assert name is not None and len(name.strip()) > 0, "Name must be a non-empty string."
-
-        self.name: str = name
-        if metadata is None:
-            metadata = {}
-        self._vector_source: torch.Tensor = (
-            InMemorySource(vector_source) if torch.is_tensor(vector_source) else vector_source
-        )
-        if SpaceProperty.VECTOR_SOURCE not in metadata:
-            metadata[SpaceProperty.VECTOR_SOURCE] = type(self._vector_source).__name__
-
-        self.metadata = metadata
-
-    def save_to_disk(self, parent_dir: Path, name: Optional[str] = None):
-        if name is None:
-            name = self.name
-
-        path = parent_dir / name
-        path.mkdir(parents=True, exist_ok=False)
-
-        # save VectorSource
-        self._vector_source.save_to_disk(path / "vectors")
-
-        # save metadata
-        with open(parent_dir / "metadata.json", "w") as fw:
-            json.dump(self.metadata, fw, indent=4, default=lambda o: o.__dict__)
-
-    @classmethod
-    def load_from_disk(cls, path: Path) -> LatentSpace:
-        # load VectorSource
-        vector_source = InMemorySource.load_from_disk(path / "vectors")
-
-        # load metadata
-        with open(path / "metadata.json", "r") as fr:
-            metadata = json.load(fr)
-
-        return LatentSpace(vector_source=vector_source, metadata=metadata)
-
-    @property
-    def vectors(self) -> torch.Tensor:
-        return self._vector_source.as_tensor()
-
-    def to_memory(self) -> "LatentSpace":
-        if isinstance(self._vector_source, torch.Tensor):
-            pylogger.info("Already in memory, skipping.")
-            return self
-
-        return LatentSpace.like(
-            space=self,
-            vector_source=self.as_tensor(),
-        )
-
-    @classmethod
-    def like(
-        cls,
-        space: LatentSpace,
-        name: Optional[str] = None,
-        vector_source: Optional[VectorSource] = None,
-        metadata: Optional[Dict[str, Any]] = None,
-        deepcopy: bool = False,
-    ):
-        """Create a new space with the arguments not provided taken from the given space.
-
-        There is no copy of the vectors, so changes to the vectors of the new space will also affect the vectors of the given space.
-
-        Args:
-            space (LatentSpace): The space to copy.
-            name (Optional[str]): The name of the new space.
-            vector_source (Optional[torch.Tensor], optional): The vectors of the new space.
-            metadata (Optional[Dict[str, Any]], optional): The metadata of the new space.
-
-        Returns:
-            LatentSpace: The new space, with the arguments not provided taken from the given space.
-        """
-        if name is None:
-            name = space.name
-        if vector_source is None:
-            vector_source = space.vector_source if not deepcopy else copy.deepcopy(space.vector_source)
-        if metadata is None:
-            metadata = space.metadata if not deepcopy else copy.deepcopy(space.metadata)
-        # TODO: test deepcopy
-        return LatentSpace(name=name, vector_source=vector_source, metadata=metadata)
-
-    @property
-    def shape(self) -> torch.Size:
-        return self.vectors.shape
-
-    def __getitem__(self, index: int) -> Mapping[str, torch.Tensor]:
-        return self._vector_source[index]
-
-    def __len__(self) -> int:
-        return len(self._vector_source)
-
-    def __repr__(self) -> str:
-        return f"{self.__class__.__name__}(name={self.name}, vectors={self.vectors.shape}, metadata={self.metadata})"
-
-    def __eq__(self, __value: object) -> bool:
-        return (
-            self.name == __value.name
-            and self.metadata == __value.metadata
-            and self._vector_source == __value._vector_source
-        )
-
-    def sample(self, sampler: Sampler, n: int) -> "LatentSpace":
-        """Sample n vectors from this space using the given sampler.
-
-        Args:
-            sampler (Sampler): The sampler to use.
-            n (int): The number of vectors to sample.
-
-        Returns:
-            LatentSpace: The sampled space.
-
-        """
-        return sampler(self, n=n)
-
-    def compare(
-        self, *others: Space, metrics: Mapping[str, Union[SearchMetric, Callable[[Space, Space], torch.Tensor]]]
-    ):
-        """Compare this space with another space using the given metrics.
-
-        Args:
-            other (Space): The space to compare with.
-            metrics (Mapping[str, Metric]): The metrics to use.
-
-        Returns:
-            Dict[str, Any]: The results of the comparison.
-        """
-        metrics = {
-            key: metric if isinstance(metric, SearchMetric) else MetricFn(key, metric)
-            for key, metric in (metrics.items())
-        }
-        metrics_results = {metric_name: metric(self, *others) for metric_name, metric in metrics.items()}
-        return metrics_results
-
-    def translate(
-        self,
-        translator: LatentTranslator,
-    ):
-        return translator(x=self)
-
-    def to_index(
-        self,
-        metric_fn: SearchMetric,
-        keys: Optional[Sequence[str]] = None,
-        transform: Optional[Callable[[torch.Tensor], torch.Tensor]] = None,
-    ) -> SearchIndex:
-        index: SearchIndex = SearchIndex.create(
-            metric_fn=metric_fn,
-            num_dimensions=self.vectors.size(dim=1),
-            name=self.name,
-            transform=transform,
-        )
-
-        index.add_items(
-            vectors=self.vectors.cpu(),
-            keys=keys,
-        )
-
-        return index
diff --git a/src/latentis/space/__init__.py b/src/latentis/space/__init__.py
new file mode 100644
index 0000000..0ad07dc
--- /dev/null
+++ b/src/latentis/space/__init__.py
@@ -0,0 +1,3 @@
+from ._base import LatentSpace
+
+__all__ = ["LatentSpace"]
diff --git a/src/latentis/space/_base.py b/src/latentis/space/_base.py
new file mode 100644
index 0000000..1d76bfe
--- /dev/null
+++ b/src/latentis/space/_base.py
@@ -0,0 +1,331 @@
+from __future__ import annotations
+
+import copy
+import logging
+from enum import auto
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, Union
+
+from latentis.nn import LatentisModule
+from latentis.serialize.disk_index import DiskIndex
+from latentis.serialize.io_utils import IndexableMixin, load_json, load_model, save_json, save_model
+from latentis.space.search import SearchIndex, SearchMetric
+from latentis.space.vector_source import TensorSource, VectorSource
+from latentis.transform import Transform
+from latentis.types import Properties, StrEnum
+
+if TYPE_CHECKING:
+    from latentis.sample import Sampler
+    from latentis.types import Space
+    from latentis.measure import MetricFn
+    from latentis.transform.translate import Translator
+
+import torch
+
+pylogger = logging.getLogger(__name__)
+
+
+class _SpaceMetadata(StrEnum):
+    _VERSION = auto()
+    _VECTOR_SOURCE = auto()
+    _TYPE = auto()
+
+
+_PROPERTIES_FILE_NAME = "properties.json"
+
+
+class LatentSpace(IndexableMixin):
+    def __init__(
+        self,
+        vector_source: Optional[Union[torch.Tensor, Tuple[torch.Tensor, Sequence[str]], VectorSource]],
+        source_model: Optional[LatentisModule] = None,
+        properties: Optional[Properties] = None,
+        root_path: Optional[Path] = None,
+    ):
+        super().__init__()
+        self.root_path = root_path
+        if vector_source is None:
+            vector_source = torch.empty(0)
+
+        # add "keys" as second element of tuple if not present
+        if isinstance(vector_source, torch.Tensor):
+            vector_source = (vector_source, None)
+
+        assert isinstance(
+            vector_source, (torch.Tensor, Tuple, VectorSource)
+        ), f"Expected {torch.Tensor} or {VectorSource} or {Tuple[torch.Tensor, Sequence[str]]}, got {type(vector_source)}"
+
+        self._vector_source: torch.Tensor = (
+            TensorSource(vectors=vector_source[0], keys=vector_source[1])
+            if isinstance(vector_source, tuple)
+            else vector_source
+        )
+        self._source_model = source_model
+
+        self._decoders = None
+        if root_path is not None:
+            self._decoders: Dict[str, LatentisModule] = DiskIndex(
+                root_path=root_path / "decoders", item_class=LatentisModule
+            )
+            self._decoders.save_to_disk()
+
+        properties = properties or {}
+        # metadata[SpaceMetadata._NAME] = self._name
+        properties[_SpaceMetadata._VERSION] = self.version
+
+        # TODO: store also the module to use for deserialization,
+        # removing this properties from the index
+        properties[_SpaceMetadata._TYPE] = LatentSpace.__name__
+        properties[_SpaceMetadata._VECTOR_SOURCE] = type(self._vector_source).__name__
+
+        self._properties = properties.copy()
+
+    @property
+    def properties(self) -> Properties:
+        return copy.deepcopy(self._properties)
+
+    @property
+    def split(self):
+        return self.properties["split"]
+
+    @property
+    def name(self) -> str:
+        return self.properties.get("name", "space")
+
+    @property
+    def decoders(self) -> Dict[str, LatentisModule]:
+        if self.root_path is None:
+            raise ValueError("Cannot store or access decoders if root_path is None")
+        return self._decoders
+
+    @property
+    def version(cls) -> str:
+        return -42
+
+    @property
+    def source_model(self) -> Optional[LatentisModule]:
+        return self._source_model
+
+    def get_vector_by_key(self, key: str) -> torch.Tensor:
+        return self._vector_source.get_vector_by_key(key=key)
+
+    def save_to_disk(
+        self,
+        target_path: Path,
+        save_vector_source=True,
+        save_properties=True,
+        save_source_model=True,
+    ):
+        target_path.mkdir(parents=True, exist_ok=True)
+
+        # save VectorSource
+        if save_vector_source:
+            vector_path = target_path / "vectors"
+            vector_path.mkdir(parents=True, exist_ok=True)
+            self._vector_source.save_to_disk(vector_path)
+
+        # save metadata
+        if save_properties:
+            save_json(self.properties, target_path / _PROPERTIES_FILE_NAME)
+
+        # save model
+        if save_source_model:
+            if self.source_model is not None:
+                save_model(model=self.source_model, target_path=target_path / "model.pt", version=self.version)
+
+        # TODO: remove save to disk from disk index
+        if self._decoders is not None:
+            self._decoders.save_to_disk()
+
+    @classmethod
+    def load_properties(cls, space_path: Path) -> Dict[str, Any]:
+        metadata = load_json(space_path / _PROPERTIES_FILE_NAME)
+
+        return metadata
+
+    @classmethod
+    def load_from_disk(cls, path: Path, load_source_model: bool = False) -> LatentSpace:
+        # load VectorSource
+        vector_source = TensorSource.load_from_disk(path / "vectors")
+
+        # load metadata
+        properties = cls.load_properties(path)
+
+        # load model
+        model_path = path
+        model = (
+            load_model(model_path, version=properties[_SpaceMetadata._VERSION])
+            if (model_path.exists() and load_source_model)
+            else None
+        )
+
+        space = LatentSpace.__new__(cls)
+        space._properties = properties
+        space._vector_source = vector_source
+        space._source_model = model
+        space.root_path: Path = path
+
+        try:
+            space._decoders = DiskIndex.load_from_disk(path=path / "decoders")
+        except FileNotFoundError:
+            space._decoders = DiskIndex(root_path=path / "decoders", item_class=LatentisModule)
+        return space
+
+    @property
+    def keys(self) -> Sequence[str]:
+        return self._vector_source.keys
+
+    @property
+    def vectors(self) -> torch.Tensor:
+        return self._vector_source.as_tensor()
+
+    def to_memory(self) -> "LatentSpace":
+        if isinstance(self._vector_source, torch.Tensor):
+            pylogger.info("Already in memory, skipping.")
+            return self
+
+        return LatentSpace.like(
+            space=self,
+            vector_source=self.as_tensor(),
+        )
+
+    @classmethod
+    def like(
+        cls,
+        #
+        space: LatentSpace,
+        vector_source: Optional[Union[torch.Tensor, Tuple[torch.Tensor, Sequence[str]], VectorSource]] = None,
+        # decoders: Optional[Dict[str, LatentisModule]] = None,
+        properties: Optional[Mapping[str, Any]] = None,
+        #
+        deepcopy: bool = False,
+    ):
+        """Create a new space with the arguments not provided taken from the given space.
+
+        There is no copy of the vectors, so changes to the vectors of the new space will also affect the vectors of the given space.
+
+        Args:
+            space (LatentSpace): The space to copy.
+            name (Optional[str]): The name of the new space.
+            vector_source (Optional[torch.Tensor], optional): The vectors of the new space.
+            metadata (Optional[Dict[str, Any]], optional): The metadata of the new space.
+
+        Returns:
+            LatentSpace: The new space, with the arguments not provided taken from the given space.
+        """
+        if vector_source is None:
+            vector_source = space.vector_source if not deepcopy else copy.deepcopy(space.vector_source)
+
+        # if decoders is None:
+        #     decoders = space.decoders if not deepcopy else copy.deepcopy(space.decoders)
+        # if source_model is None:
+        #     source_model = space.source_model if not deepcopy else copy.deepcopy(space.source_model)
+
+        if properties is None:
+            properties = space.properties if not deepcopy else copy.deepcopy(space.properties)
+
+        # TODO: test deepcopy
+        return LatentSpace(
+            vector_source=vector_source,
+            # decoders=decoders,
+            properties=properties,
+        )
+
+    @property
+    def shape(self) -> torch.Size:
+        return self._vector_source.shape
+
+    def __getitem__(self, index: Union[int, Sequence[int], slice]) -> Mapping[str, torch.Tensor]:
+        return self._vector_source[index]
+
+    def __iter__(self):
+        return iter(self._vector_source)
+
+    def __len__(self) -> int:
+        return len(self._vector_source)
+
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(vectors={self.vectors.shape}, metadata={self.properties})"
+
+    def __eq__(self, __value: object) -> bool:
+        return self.properties == __value.properties and self.vector_source == __value.vector_source
+
+    def add_vectors(self, vectors: torch.Tensor, keys: Optional[Sequence[str]] = None) -> None:
+        """Add vectors to this space.
+
+        Args:
+            vectors (torch.Tensor): The vectors to add.
+            keys (Optional[Sequence[str]], optional): The keys of the vectors. Defaults to None.
+
+        Returns:
+            LatentSpace: The new space.
+        """
+        self._vector_source.add_vectors_(vectors=vectors, keys=keys)
+
+    def sample(self, sampler: Sampler, n: int) -> "LatentSpace":
+        """Sample n vectors from this space using the given sampler.
+
+        Args:
+            sampler (Sampler): The sampler to use.
+            n (int): The number of vectors to sample.
+
+        Returns:
+            LatentSpace: The sampled space.
+
+        """
+        return sampler(self, n=n)
+
+    def compare(
+        self, *others: Space, metrics: Mapping[str, Union[SearchMetric, Callable[[Space, Space], torch.Tensor]]]
+    ):
+        """Compare this space with another space using the given metrics.
+
+        Args:
+            other (Space): The space to compare with.
+            metrics (Mapping[str, Metric]): The metrics to use.
+
+        Returns:
+            Dict[str, Any]: The results of the comparison.
+        """
+        metrics = {
+            key: metric if isinstance(metric, SearchMetric) else MetricFn(key, metric)
+            for key, metric in (metrics.items())
+        }
+        metrics_results = {metric_name: metric(self, *others) for metric_name, metric in metrics.items()}
+        return metrics_results
+
+    def to_index(
+        self,
+        metric_fn: SearchMetric,
+        keys: Optional[Sequence[str]] = None,
+        transform: Optional[Callable[[torch.Tensor], torch.Tensor]] = None,
+    ) -> SearchIndex:
+        """Create a SearchIndex from this space.
+
+        Args:
+            metric_fn (SearchMetric): The metric to use.
+            keys (Optional[Sequence[str]], optional): The keys of the vectors. Defaults to None.
+            transform (Optional[Callable[[torch.Tensor], torch.Tensor]], optional): A transform to apply to the vectors. Defaults to None.
+        """
+        index: SearchIndex = SearchIndex.create(
+            metric_fn=metric_fn,
+            num_dimensions=self.vectors.size(dim=1),
+            name=self.name,
+            transform=transform,
+        )
+
+        index.add_items(
+            vectors=self.vectors.cpu(),
+            keys=keys,
+        )
+
+        return index
+
+    def transform(self, transform: Union[Transform, Translator]) -> "LatentSpace":
+        return LatentSpace.like(
+            space=self,
+            vector_source=transform(x=self.vectors),
+        )
+
+    def to_hf_dataset(self, name: str) -> Any:
+        raise NotImplementedError
diff --git a/src/latentis/search.py b/src/latentis/space/search.py
similarity index 100%
rename from src/latentis/search.py
rename to src/latentis/space/search.py
diff --git a/src/latentis/space/vector_source.py b/src/latentis/space/vector_source.py
new file mode 100644
index 0000000..423d60e
--- /dev/null
+++ b/src/latentis/space/vector_source.py
@@ -0,0 +1,124 @@
+from __future__ import annotations
+
+import logging
+from abc import abstractmethod
+from pathlib import Path
+from typing import Optional, Sequence, Union
+
+import torch
+
+from latentis.data.utils import BiMap
+from latentis.serialize.io_utils import SerializableMixin
+
+pylogger = logging.getLogger(__name__)
+
+
+_VECTOR_SOURCES = {}
+
+
+class VectorSourceMeta(type):
+    def __new__(mcs, name, bases, attrs):
+        cls = super().__new__(mcs, name, bases, attrs)
+        key = cls.__module__ + "." + cls.__name__
+
+        assert key not in _VECTOR_SOURCES, f"Duplicated vector source with key {key}"
+
+        _VECTOR_SOURCES[key] = cls
+        return cls
+
+
+class VectorSource(metaclass=VectorSourceMeta):
+    @property
+    @abstractmethod
+    def shape(self) -> torch.Size:
+        raise NotImplementedError
+
+    @abstractmethod
+    def __getitem__(self, index: Union[int, Sequence[int], slice]) -> torch.Tensor:
+        raise NotImplementedError
+
+    @abstractmethod
+    def __len__(self) -> int:
+        raise NotImplementedError
+
+    @abstractmethod
+    def as_tensor(self) -> torch.Tensor:
+        raise NotImplementedError
+
+    @abstractmethod
+    def __eq__(self, __value: VectorSource) -> bool:
+        raise NotImplementedError
+
+    @abstractmethod
+    def add_vectors_(self, vectors: torch.Tensor, keys: Optional[Sequence[str]] = None) -> VectorSource:
+        raise NotImplementedError
+
+    @abstractmethod
+    def get_vector_by_key(self, key: str) -> torch.Tensor:
+        raise NotImplementedError
+
+    @property
+    @abstractmethod
+    def keys(self) -> Sequence[str]:
+        raise NotImplementedError
+
+
+class TensorSource(VectorSource, SerializableMixin):
+    def __init__(self, vectors: torch.Tensor, keys: Optional[Sequence[str]] = None):
+        assert (
+            keys is None or len(keys) == 0 or len(keys) == vectors.size(0)
+        ), "Keys must be None, empty, or have the same length as vectors"
+        self._vectors = vectors
+        keys = keys or []
+        self._keys2offset = BiMap(x=keys, y=range(len(keys)))
+
+    @property
+    def shape(self) -> torch.Size:
+        return self._vectors.shape
+
+    def __getitem__(self, index: Union[int, Sequence[int], slice]) -> torch.Tensor:
+        return self._vectors[index]
+
+    def __len__(self) -> int:
+        return self._vectors.size(0)
+
+    def __eq__(self, __value: TensorSource) -> bool:
+        assert isinstance(__value, TensorSource), f"Expected {TensorSource}, got {type(__value)}"
+        return torch.allclose(self._vectors, __value._vectors)
+
+    def as_tensor(self) -> torch.Tensor:
+        return self._vectors
+
+    def save_to_disk(self, parent_dir: Path):
+        torch.save(self._vectors, parent_dir / "vectors.pt")
+        self._keys2offset.save_to_disk(parent_dir / "mapping.tsv")
+
+    @classmethod
+    def load_from_disk(cls, path: Path) -> TensorSource:
+        vectors = torch.load(path / "vectors.pt")
+        keys = BiMap.load_from_disk(path / "mapping.tsv")
+        result = cls(vectors=vectors)
+        result._keys2offset = keys  # TODO: ugly
+        return result
+
+    def add_vectors_(self, vectors: torch.Tensor, keys: Optional[Sequence[str]] = None) -> TensorSource:
+        assert (keys is None) == (
+            len(self._keys2offset) == 0
+        ), "Keys must be provided only if the source already has keys"
+        if keys is not None:
+            assert len(keys) == vectors.size(0), "Keys must have the same length as vectors"
+            self._keys2offset.add_all(x=keys, y=range(len(self._keys2offset), len(self._keys2offset) + len(keys)))
+
+        self._vectors = torch.cat([self._vectors, vectors], dim=0)
+        return self
+
+    def get_vector_by_key(self, key: str) -> torch.Tensor:
+        assert len(self._keys2offset) > 0, "This source does not have keys enabled"
+        try:
+            return self[self._keys2offset.get_y(key)]
+        except KeyError:
+            raise KeyError(f"Key {key} not found in {self._keys2offset}")
+
+    @property
+    def keys(self) -> Sequence[str]:
+        return self._keys2offset.x
diff --git a/src/latentis/transform/_abstract.py b/src/latentis/transform/_abstract.py
index df3698a..7ceb330 100644
--- a/src/latentis/transform/_abstract.py
+++ b/src/latentis/transform/_abstract.py
@@ -1,21 +1,32 @@
 from __future__ import annotations
 
+from abc import abstractmethod
 from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence, Union
 
 import torch
 from torch import nn
 
+from latentis.serialize.io_utils import IndexableMixin
 from latentis.transform.functional import InverseFn, State, StateFn, TransformFn
+from latentis.types import Properties
 
 if TYPE_CHECKING:
     from latentis.types import Space
 
 
-class Transform(nn.Module):
+class Transform(nn.Module, IndexableMixin):
     _STATE_PREFIX: str = "latentis_state_"
 
+    @property
+    def properties(self) -> Properties:
+        return {
+            "name": self.name,
+            # "invertible": self.invertible,
+        }
+
     def __init__(
         self,
+        *,
         name: Optional[str] = None,
         invertible: bool = False,
     ) -> None:
@@ -23,6 +34,10 @@ def __init__(
         self._name: Optional[str] = name
         self._invertible: bool = invertible
 
+    @abstractmethod
+    def fit_stats():
+        raise NotImplementedError
+
     @property
     def name(self) -> str:
         return self._name
@@ -44,14 +59,22 @@ def get_state(self, *keys: str) -> Union[torch.Tensor, Mapping[str, torch.Tensor
             if k.startswith(Transform._STATE_PREFIX)
         }
 
-    def fit(self, x: torch.Tensor, y=None) -> "Transform":
+    def fit(self, x: torch.Tensor, y: torch.Tensor = None) -> "Transform":
         return self
 
-    def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
+    def transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
         raise NotImplementedError
 
+    def fit_transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
+        return self.fit(x=x, y=y).transform(x=x, y=y)
+
     def forward(self, x: torch.Tensor, y=None, inverse: bool = False) -> torch.Tensor:
-        return self.transform(x=x, y=y) if not inverse else self.inverse_transform(x=x, y=y)
+        x, y = self.transform(x=x, y=y) if not inverse else self.inverse_transform(x=x, y=y)
+
+        return {
+            "x": x,
+            "y": y,
+        }
 
     @property
     def invertible(self) -> bool:
@@ -116,12 +139,15 @@ def fit(self, x: torch.Tensor, y=None) -> "Transform":
     def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
         if not self._fitted and self._state_fn is not None:
             raise RuntimeError("Transform not fitted.")
-        return self._transform_fn(x=x, **self._transform_params, **self.get_state())
+
+        return self._transform_fn(x=x, **self._transform_params, **self.get_state()), y
 
     def inverse_transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
         if not self._fitted and self._state_fn is not None:
             raise RuntimeError("Transform not fitted.")
-        return self._inverse_fn(x=x, **self._inverse_params, **self.get_state())
+
+        # return x, self._inverse_fn(x=y, **self._inverse_params, **self.get_state())
+        return self._inverse_fn(x=x, **self._inverse_params, **self.get_state()), y
 
 
 class Identity(SimpleTransform):
@@ -138,30 +164,46 @@ def __init__(self, transforms: Sequence[Transform]):
         super().__init__()
         self.transforms = transforms
 
-    def fit(self, x: Space) -> "TransformSequence":
+    def fit(self, x: Space, y=None) -> "TransformSequence":
         for transform in self.transforms:
-            transform.fit(x)
-            x = transform.transform(x)
+            x, y = transform.fit_transform(x=x, y=y)
 
         return self
 
-    def transform(self, x: Space) -> torch.Tensor:
+    def transform(self, x: Space, y=None) -> torch.Tensor:
         for transform in self.transforms:
-            x = transform.transform(x)
+            x, y = transform.transform(x=x, y=y)
 
-        return x
+        return x, y
 
     @property
     def invertible(self) -> bool:
         return all(transform.invertible for transform in self.transforms)
 
-    def inverse_transform(self, x: Space) -> torch.Tensor:
+    def inverse_transform(self, x: Space, y=None) -> torch.Tensor:
         assert self.invertible, "Not all transforms in the sequence are invertible."
         for transform in reversed(self.transforms):
-            x = transform.inverse_transform(x)
+            x, y = transform.inverse_transform(x=x, y=y)
 
-        return x
+        return x, y
 
 
 class Estimator(Transform):
-    pass
+    def __init__(
+        self,
+        *,
+        name: Optional[str] = None,
+        invertible: bool = False,
+        x_space: Optional[torch.Tensor] = None,
+        y_space: Optional[torch.Tensor] = None,
+    ) -> None:
+        super().__init__(name=name, invertible=invertible)
+
+        self._x_space = x_space
+        self._y_space = y_space
+
+    def set_spaces(self, x_space: torch.Tensor, y_space: torch.Tensor) -> "Estimator":
+        # TODO: decide if we want to keep the shuffling or trust the users
+        self._x_space = x_space[torch.randperm(x_space.shape[0])]
+        self._y_space = y_space
+        return self
diff --git a/src/latentis/transform/base.py b/src/latentis/transform/base.py
index dfdab38..2a350c2 100644
--- a/src/latentis/transform/base.py
+++ b/src/latentis/transform/base.py
@@ -1,8 +1,10 @@
+from typing import Any, Mapping
+
 import torch
 import torch.nn.functional as F
 
 import latentis.transform.functional as FL
-from latentis.transform._abstract import SimpleTransform, Transform
+from latentis.transform import SimpleTransform, Transform
 
 
 class Centering(SimpleTransform):
@@ -42,8 +44,8 @@ def __init__(self, p: int = 2):
         )
         self.p = p
 
-    def transform(self, x: torch.Tensor) -> torch.Tensor:
-        return FL.lp_normalize_transform(x=x, p=self.p)
+    def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
+        return FL.lp_normalize_transform(x=x, p=self.p), y
 
 
 class MeanLPNorm(Transform):
@@ -52,39 +54,39 @@ def __init__(self, p: int = 2, dim: int = -1):
         self.p = p
         self.dim = dim
 
-    def fit(self, x: torch.Tensor) -> "MeanLPNorm":
+    def fit(self, x: torch.Tensor, y=None) -> "MeanLPNorm":
         mean_norm: torch.Tensor = x.norm(p=self.p, dim=-1).mean()
         self._register_state(dict(mean_norm=mean_norm))
         return self
 
     def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
         # TODO: decide if we want to use the state or not
-        return F.normalize(x, p=self.p, dim=-1)
+        return F.normalize(x, p=self.p, dim=-1), y
 
     def inverse_transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
-        return x * self.get_state("mean_norm")
+        return x * self.get_state("mean_norm"), y
 
 
 class IsotropicScaling(Transform):
     def __init__(self, scale: float):
-        super().__init__(name="isotropic_scaling")
+        super().__init__(name="isotropic_scaling", invertible=True)
 
         self.scale: float = scale
 
-    def fit(self, x: torch.Tensor) -> "IsotropicScaling":
+    def fit(self, x: torch.Tensor, y=None) -> "IsotropicScaling":
         self._register_state({"scale": torch.tensor(self.scale, dtype=x.dtype, device=x.device)})
         return self
 
-    def transform(self, x: torch.Tensor) -> torch.Tensor:
-        return FL.isotropic_scaling_transform(x=x, **self.get_state())
+    def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
+        return FL.isotropic_scaling_transform(x=x, **self.get_state()), y
 
-    def inverse_transform(self, x: torch.Tensor) -> torch.Tensor:
-        return FL.isotropic_scaling_inverse(x=x, **self.get_state())
+    def inverse_transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
+        return FL.isotropic_scaling_inverse(x=x, **self.get_state()), y
 
 
 class RandomIsometry(Transform):
     def __init__(self, random_seed: int):
-        super().__init__(name="random_isometry")
+        super().__init__(name="random_isometry", invertible=True)
         self.random_seed: int = random_seed
 
     def fit(self, x: torch.Tensor, y=None) -> "RandomIsometry":
@@ -92,15 +94,15 @@ def fit(self, x: torch.Tensor, y=None) -> "RandomIsometry":
         return self
 
     def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
-        return FL.isometry_transform(x=x, **self.get_state())
+        return FL.isometry_transform(x=x, **self.get_state()), y
 
     def inverse_transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
-        return FL.isometry_inverse(x=x, **self.get_state())
+        return FL.isometry_inverse(x=x, **self.get_state()), y
 
 
 class RandomDimensionPermutation(Transform):
     def __init__(self, random_seed: int):
-        super().__init__(name="random_dimension_permutation")
+        super().__init__(name="random_dimension_permutation", invertible=True)
         self.random_seed: int = random_seed
 
     def fit(self, x: torch.Tensor, y=None) -> "RandomDimensionPermutation":
@@ -109,7 +111,25 @@ def fit(self, x: torch.Tensor, y=None) -> "RandomDimensionPermutation":
         return self
 
     def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
-        return FL.dimension_permutation_transform(x=x, **self.get_state())
+        return FL.dimension_permutation_transform(x=x, **self.get_state()), y
 
     def inverse_transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
-        return FL.dimension_permutation_inverse(x=x, **self.get_state())
+        return FL.dimension_permutation_inverse(x=x, **self.get_state()), y
+
+
+class InverseTransform(Transform):
+    def __init__(self, transform: Transform) -> None:
+        super().__init__(name=f"reversed_{transform.name}", invertible=True)
+        if not transform.invertible:
+            raise ValueError(f"Transform {transform.name} is not invertible.")
+
+        self._transform = transform
+
+    def fit(self, x: torch.Tensor, y: torch.Tensor = None) -> Mapping[str, Any]:
+        return self
+
+    def transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
+        return self._transform.inverse_transform(x=x, y=y)
+
+    def inverse_transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
+        return self._transform.transform(x=x, y=y)
diff --git a/src/latentis/transform/dim_matcher.py b/src/latentis/transform/dim_matcher.py
index e743981..337f5a6 100644
--- a/src/latentis/transform/dim_matcher.py
+++ b/src/latentis/transform/dim_matcher.py
@@ -36,50 +36,38 @@ def transform(
         x: Optional[torch.Tensor] = None,
         y: Optional[torch.Tensor] = None,
     ) -> Dict[str, torch.Tensor]:
-        assert sum((x is not None, y is not None)) == 1, "Either x or y must be provided."
-
-        if x is not None:
-            if self.get_state("transform_x"):
-                assert x.ndim == 2, "The source tensor must be 2D."
-                x = torch.nn.functional.pad(
-                    x,
-                    (0, self.get_state("x_pad")),
-                    mode="constant",
-                    value=0,
-                )
-
-            return x
-
-        if y is not None:
-            if self.get_state("transform_y"):
-                assert y.ndim == 2, "The target tensor must be 2D."
-                y = torch.nn.functional.pad(
-                    y,
-                    (0, self.get_state("y_pad")),
-                    mode="constant",
-                    value=0,
-                )
-
-            return y
+        if x is not None and self.get_state("transform_x"):
+            assert x.ndim == 2, "The source tensor must be 2D."
+            x = torch.nn.functional.pad(
+                x,
+                (0, self.get_state("x_pad")),
+                mode="constant",
+                value=0,
+            )
+
+        if y is not None and self.get_state("transform_y"):
+            assert y.ndim == 2, "The target tensor must be 2D."
+            y = torch.nn.functional.pad(
+                y,
+                (0, self.get_state("y_pad")),
+                mode="constant",
+                value=0,
+            )
+
+        return x, y
 
     def inverse_transform(
         self,
         x: Optional[torch.Tensor] = None,
         y: Optional[torch.Tensor] = None,
     ) -> Dict[str, torch.Tensor]:
-        assert sum((x is not None, y is not None)) == 1, "Either x or y must be provided."
+        if x is not None and self.get_state("transform_x"):
+            x = x[..., : -self.get_state("x_pad")]
 
-        if x is not None:
-            if self.get_state("transform_x"):
-                x = x[..., : -self.get_state("x_pad")]
+        if y is not None and self.get_state("transform_y"):
+            y = y[..., : -self.get_state("y_pad")]
 
-            return x
-
-        if y is not None:
-            if self.get_state("transform_y"):
-                y = y[..., : -self.get_state("y_pad")]
-
-            return y
+        return x, y
 
 
 # class PCATruncation(DimMatcher):
diff --git a/src/latentis/transform/projection.py b/src/latentis/transform/projection.py
index 42b00c8..c8877cd 100644
--- a/src/latentis/transform/projection.py
+++ b/src/latentis/transform/projection.py
@@ -6,7 +6,7 @@
 import torch.nn.functional as F
 
 from latentis.space import LatentSpace
-from latentis.transform._abstract import Identity, Transform, TransformSequence
+from latentis.transform._abstract import Identity, Transform
 from latentis.transform.functional import TransformFn
 
 if TYPE_CHECKING:
@@ -72,7 +72,7 @@ def angular_proj(
     anchors = F.normalize(anchors, p=2, dim=-1)
 
     x = (x @ anchors.mT).clamp(-1.0, 1.0)
-    x = 1 - torch.arccos(x)
+    x = torch.arccos(x) / torch.pi
 
     return x
 
@@ -157,8 +157,8 @@ class RelativeProjection(Transform):
     def __init__(
         self,
         projection_fn: TransformFn,
-        abs_transform: Optional[TransformSequence] = None,
-        rel_transform: Optional[TransformSequence] = None,
+        abs_transform: Optional[Transform] = None,
+        rel_transform: Optional[Transform] = None,
     ):
         super().__init__()
         self.projection_fn = projection_fn
@@ -166,17 +166,16 @@ def __init__(
         self.rel_transform = rel_transform or Identity()
 
     def fit(self, x: Space, **kwargs) -> "RelativeProjection":
-        self.abs_transform.fit(x)
-        x = self.abs_transform.transform(x)
+        x, _ = self.abs_transform.fit_transform(x)
         self._register_state(dict(anchors=x))
         rel_x = relative_projection(x, anchors=x, projection_fn=self.projection_fn)
         self.rel_transform.fit(rel_x)
 
         return self
 
-    def transform(self, x: Space) -> torch.Tensor:
-        x = self.abs_transform.transform(x)
+    def transform(self, x: Space, y=None) -> torch.Tensor:
+        x, _ = self.abs_transform.transform(x=x, y=y)
         rel_x = relative_projection(x, **self.get_state(), projection_fn=self.projection_fn)
-        rel_x = self.rel_transform.transform(rel_x)
+        rel_x, _ = self.rel_transform.transform(x=rel_x, y=y)
 
-        return rel_x
+        return rel_x, y
diff --git a/src/latentis/transform/translate/__init__.py b/src/latentis/transform/translate/__init__.py
index dab1ef3..c6aca35 100644
--- a/src/latentis/transform/translate/__init__.py
+++ b/src/latentis/transform/translate/__init__.py
@@ -1,7 +1,6 @@
-from .aligner import Aligner, MatrixAligner, Procrustes, Translator
+from .aligner import MatrixAligner, Procrustes, Translator
 
 __all__ = [
-    "Aligner",
     "Translator",
     "MatrixAligner",
     "Procrustes",
diff --git a/src/latentis/transform/translate/aligner.py b/src/latentis/transform/translate/aligner.py
index 73d6159..6c68e98 100644
--- a/src/latentis/transform/translate/aligner.py
+++ b/src/latentis/transform/translate/aligner.py
@@ -3,18 +3,12 @@
 import torch
 from torch import nn
 
-from latentis.transform import Estimator, Identity, Transform, TransformSequence
+from latentis.transform import Estimator, Identity, Transform
 from latentis.transform.base import StandardScaling
 from latentis.transform.dim_matcher import DimMatcher, ZeroPadding
 from latentis.transform.translate.functional import sgd_affine_align_state, svd_align_state
 
 
-class Aligner(Estimator):
-    def __init__(self, name: Optional[str] = None, dim_matcher: Optional[DimMatcher] = None) -> None:
-        super().__init__(name=name)
-        self.dim_matcher = dim_matcher
-
-
 class Translator(Estimator):
     def __init__(
         self,
@@ -22,75 +16,77 @@ def __init__(
         name: Optional[str] = None,
         x_transform: Optional[Transform] = None,
         y_transform: Optional[Transform] = None,
+        dim_matcher: Optional[DimMatcher] = None,
     ) -> None:
         super().__init__(name=name)
         self.x_transform = x_transform or Identity()
         self.y_transform = y_transform or Identity()
         self.aligner = aligner
+        self.dim_matcher: DimMatcher = dim_matcher or Identity()
+
+        self._fitted = False
 
     def fit(self, x: torch.Tensor, y: torch.Tensor) -> Mapping[str, Any]:
         self.x_transform.fit(x)
-        x = self.x_transform.transform(x)
+        x, _ = self.x_transform.transform(x=x, y=None)
 
         self.y_transform.fit(y)
-        y = self.y_transform.transform(y)
+        y, _ = self.y_transform.transform(x=y, y=None)
+
+        x, y = self.dim_matcher.fit_transform(x=x, y=y)
 
         self.aligner.fit(x, y)
 
+        self._fitted = True
+
         return self
 
     def transform(self, x: torch.Tensor, y=None) -> torch.Tensor:
-        x = self.x_transform.transform(x)
-        x = self.aligner.transform(x=x)
+        assert self._fitted, "The transform should be fitted before being applied."
 
-        return self.y_transform.inverse_transform(x=x)
+        x, _ = self.x_transform.transform(x=x, y=None)
+        x, y = self.dim_matcher.transform(x=x, y=y)
+        x, y = self.aligner.transform(x=x, y=y)
+        _, x = self.dim_matcher.inverse_transform(x=None, y=x)
+        x, _ = self.y_transform.inverse_transform(x=x, y=None)
 
-    def inverse_transform(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
-        assert x is None, "The inverse transform should be applied on the target space (y)"
+        return x, y
 
-        y = self.y_transform.transform(y)
-        y = self.aligner.inverse_transform(y)
+    # def inverse_transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
+    #     # assert x is None, "The inverse transform should be applied on the target space (y)"
+    #     assert self._fitted, "The transform should be fitted before being applied."
 
-        return self.x_transform.inverse_transform(y)
+    #     y = self.y_transform.transform(y)
+    #     y = self.aligner.inverse_transform(y)
 
+    #     return self.x_transform.inverse_transform(y), y
 
-class MatrixAligner(Aligner):
+
+class MatrixAligner(Estimator):
     def __init__(
         self,
         name: str,
         align_fn_state: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
-        dim_matcher: Optional[DimMatcher] = None,
     ) -> None:
-        super().__init__(name=name, dim_matcher=dim_matcher)
+        super().__init__(name=name)
         self.align_fn_state = align_fn_state
 
     def fit(self, x: torch.Tensor, y: torch.Tensor) -> Mapping[str, Any]:
-        if self.dim_matcher is not None:
-            self.dim_matcher.fit(x=x, y=y)
-            x = self.dim_matcher.transform(x=x, y=None)
-            y = self.dim_matcher.transform(x=None, y=y)
-
         state = self.align_fn_state(x=x, y=y)
         self._register_state(state=state)
 
         return self
 
     def transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
-        if self.dim_matcher is not None:
-            x = self.dim_matcher.transform(x=x, y=None)
-
         x = x @ self.get_state("matrix")
 
-        if self.dim_matcher is not None:
-            x = self.dim_matcher.inverse_transform(x=None, y=x)
-
-        return x
+        return x, y
 
     def inverse_transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
         raise NotImplementedError
 
 
-class SGDAffineAligner(Aligner):
+class SGDAffineAligner(Estimator):
     def __init__(
         self,
         num_steps: int,
@@ -113,19 +109,15 @@ def fit(self, x: torch.Tensor, y: torch.Tensor) -> Mapping[str, Any]:
         return self
 
     def transform(self, x: torch.Tensor, y: torch.Tensor = None) -> torch.Tensor:
-        return self.translation(x)
+        return self.translation(x), y
 
 
 class Procrustes(Translator):
     def __init__(self) -> None:
         super().__init__(
             name="procrustes",
-            aligner=TransformSequence(
-                [
-                    ZeroPadding(),
-                    MatrixAligner(name="svd_aligner", align_fn_state=svd_align_state),
-                ]
-            ),
+            aligner=MatrixAligner(name="svd_aligner", align_fn_state=svd_align_state),
             x_transform=StandardScaling(),
             y_transform=StandardScaling(),
+            dim_matcher=ZeroPadding(),
         )
diff --git a/src/latentis/transform/translate/functional.py b/src/latentis/transform/translate/functional.py
index 8c085bf..102a156 100644
--- a/src/latentis/transform/translate/functional.py
+++ b/src/latentis/transform/translate/functional.py
@@ -23,14 +23,14 @@ def svd_align_state(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
 
 def svd_align(x: torch.Tensor, y: torch.Tensor, dim_matcher: Optional[DimMatcher] = None) -> torch.Tensor:
     if dim_matcher is not None:
-        dim_matcher.fit(x, y)
-        x = dim_matcher.transform(x=x, y=None)
-        y = dim_matcher.transform(x=None, y=y)
+        x, y = dim_matcher.fit_transform(x=x, y=y)
 
     state = svd_align_state(x, y)
 
     x = x @ state["matrix"]
-    x = dim_matcher.inverse_transform(x=None, y=x)
+
+    if dim_matcher is not None:
+        _, x = dim_matcher.inverse_transform(x=None, y=x)
 
     return x
 
diff --git a/src/latentis/types.py b/src/latentis/types.py
index 94c933a..6cc8a7c 100644
--- a/src/latentis/types.py
+++ b/src/latentis/types.py
@@ -1,7 +1,5 @@
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, Union
-
 import torch
 
 try:
@@ -10,9 +8,14 @@
 except ImportError:
     from backports.strenum import StrEnum as PythonStrEnum
 
+from typing import TYPE_CHECKING, Any, Mapping, Union
+
 if TYPE_CHECKING:
     from latentis.space import LatentSpace
 
     Space = Union[LatentSpace, torch.Tensor]
 
 StrEnum = PythonStrEnum
+
+
+Properties = Mapping[str, Any]
diff --git a/src/latentis/vector_source.py b/src/latentis/vector_source.py
deleted file mode 100644
index f0a26dd..0000000
--- a/src/latentis/vector_source.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from __future__ import annotations
-
-import logging
-from abc import abstractmethod
-
-import torch
-
-pylogger = logging.getLogger(__name__)
-
-
-_VECTOR_SOURCES = {}
-
-
-class VectorSourceMeta(type):
-    def __new__(mcs, name, bases, attrs):
-        cls = super().__new__(mcs, name, bases, attrs)
-        key = cls.__module__ + "." + cls.__name__
-
-        assert key not in _VECTOR_SOURCES, f"Duplicated vector source with key {key}"
-
-        _VECTOR_SOURCES[key] = cls
-        return cls
-
-
-class VectorSource(metaclass=VectorSourceMeta):
-    @abstractmethod
-    def shape(self) -> torch.Size:
-        raise NotImplementedError
-
-    @abstractmethod
-    def __getitem__(self, index: int) -> torch.Tensor:
-        raise NotImplementedError
-
-    @abstractmethod
-    def __len__(self) -> int:
-        raise NotImplementedError
-
-    @abstractmethod
-    def as_tensor(self) -> torch.Tensor:
-        raise NotImplementedError
-
-    @abstractmethod
-    def __eq__(self, __value: VectorSource) -> bool:
-        raise NotImplementedError
-
-
-class InMemorySource(VectorSource):
-    def __init__(self, vectors: torch.Tensor):
-        self._vectors = vectors
-
-    def shape(self) -> torch.Size:
-        return self._vectors.shape
-
-    def __getitem__(self, index: int) -> torch.Tensor:
-        return self._vectors[index]
-
-    def __len__(self) -> int:
-        return self._vectors.size(0)
-
-    def __eq__(self, __value: InMemorySource) -> bool:
-        assert isinstance(__value, InMemorySource), f"Expected {InMemorySource}, got {type(__value)}"
-        return torch.allclose(self._vectors, __value._vectors)
-
-    def as_tensor(self) -> torch.Tensor:
-        return self._vectors


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6909401..f0b38a6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,7 +6,7 @@
 import torch
 from torch import Tensor
 
-from latentis import LatentSpace
+from latentis.space import LatentSpace
 from latentis.utils import seed_everything
 
 if TYPE_CHECKING:
@@ -18,7 +18,6 @@ class Space1Params(object):
     instances = [
         LatentSpace(
             vector_source=torch.randn(1000, 128, dtype=torch.double),
-            name="space1",
         ),
         torch.randn(1000, 128, dtype=torch.double),
     ]
@@ -34,7 +33,6 @@ class Space2Params(object):
     instances = [
         LatentSpace(
             vector_source=torch.randn(53, 250, dtype=torch.double),
-            name="space2",
         ),
         torch.randn(53, 250, dtype=torch.double),
     ]
@@ -50,11 +48,9 @@ class ParallelSpaces(object):
         (
             LatentSpace(
                 vector_source=torch.randn(space1_n, space_1_dim, dtype=torch.double),
-                name="space1",
             ),
             LatentSpace(
                 vector_source=torch.randn(space2_n, space2_dim, dtype=torch.double),
-                name="space2",
             ),
         )
         for (space1_n, space_1_dim), (space2_n, space2_dim) in [
diff --git a/tests/measure/conftest.py b/tests/measure/conftest.py
index 70b9db6..2f1999c 100644
--- a/tests/measure/conftest.py
+++ b/tests/measure/conftest.py
@@ -5,8 +5,8 @@
 import pytest
 import torch
 
-from latentis import LatentSpace
 from latentis.measure.base import CKAMode
+from latentis.space import LatentSpace
 
 if TYPE_CHECKING:
     from latentis.types import Space
@@ -16,24 +16,22 @@ class SameShapeSpaces(object):
     instances = [
         (
             LatentSpace(
-                vector_source=torch.randn(x_n, space_1_dim, dtype=torch.double),
-                name="space1",
+                vector_source=torch.randn(x_n, x_dim, dtype=torch.double),
             ),
             LatentSpace(
                 vector_source=torch.randn(y_n, y_dim, dtype=torch.double),
-                name="y",
             ),
         )
-        for (x_n, space_1_dim), (y_n, y_dim) in [
+        for (x_n, x_dim), (y_n, y_dim) in [
             ((50, 250), (50, 250)),
             ((20, 300), (20, 300)),
         ]
     ] + [
         (
-            torch.randn(x_n, space_1_dim, dtype=torch.double),
+            torch.randn(x_n, x_dim, dtype=torch.double),
             torch.randn(y_n, y_dim, dtype=torch.double),
         )
-        for (x_n, space_1_dim), (y_n, y_dim) in [
+        for (x_n, x_dim), (y_n, y_dim) in [
             ((50, 250), (50, 250)),
             ((20, 300), (20, 300)),
         ]
@@ -45,11 +43,9 @@ class DifferentDimSpaces(object):
         (
             LatentSpace(
                 vector_source=torch.randn(x_n, space_1_dim, dtype=torch.double),
-                name="x",
             ),
             LatentSpace(
                 vector_source=torch.randn(y_n, y_dim, dtype=torch.double),
-                name="y",
             ),
         )
         for (x_n, space_1_dim), (y_n, y_dim) in [
diff --git a/tests/project/conftest.py b/tests/project/conftest.py
index c343a3e..97625ce 100644
--- a/tests/project/conftest.py
+++ b/tests/project/conftest.py
@@ -5,7 +5,7 @@
 import pytest
 import torch
 
-from latentis import LatentSpace
+from latentis.space import LatentSpace
 from latentis.utils import seed_everything
 
 if TYPE_CHECKING:
@@ -21,7 +21,6 @@ class Anchor1Params(object):
     instances = [
         LatentSpace(
             vector_source=torch.randn(NUM_ANCHORS, LATENT_DIM, dtype=torch.double),
-            name="space1",
         ),
         torch.randn(NUM_ANCHORS, LATENT_DIM, dtype=torch.double),
     ]
@@ -47,7 +46,6 @@ class X1Params(object):
     instances = [
         LatentSpace(
             vector_source=torch.randn(BATCH_DIM, LATENT_DIM, dtype=torch.double),
-            name="space1",
         ),
         torch.randn(BATCH_DIM, LATENT_DIM, dtype=torch.double),
     ]
diff --git a/tests/project/test_projection.py b/tests/project/test_projection.py
index 77d1ccb..c2a0288 100644
--- a/tests/project/test_projection.py
+++ b/tests/project/test_projection.py
@@ -9,7 +9,7 @@
 
 from tests.project.conftest import LATENT_DIM
 
-from latentis import LatentSpace
+from latentis.space import LatentSpace
 from latentis.transform import Identity, TransformSequence
 from latentis.transform.base import Centering
 from latentis.transform.projection import (
@@ -221,10 +221,10 @@ def test_invariances(
         rel_transform=rel_transforms,
     )
     pipeline.fit(x_anchors.vectors if isinstance(x_anchors, LatentSpace) else x_anchors)
-    x_projected = pipeline.transform(x if isinstance(x, torch.Tensor) else x.vectors)
+    x_projected, _ = pipeline.transform(x if isinstance(x, torch.Tensor) else x.vectors)
 
     pipeline.fit(y_anchors.vectors if isinstance(y_anchors, LatentSpace) else y_anchors)
-    y_projected = pipeline.transform(y if isinstance(y, torch.Tensor) else y.vectors)
+    y_projected, _ = pipeline.transform(y if isinstance(y, torch.Tensor) else y.vectors)
 
     assert not invariant or torch.allclose(x_projected, y_projected)
 
diff --git a/tests/sample/test_sampling.py b/tests/sample/test_sampling.py
index 81ef23c..9500766 100644
--- a/tests/sample/test_sampling.py
+++ b/tests/sample/test_sampling.py
@@ -5,7 +5,6 @@
 import pytest
 
 from latentis.sample import Uniform
-from latentis.space import LatentSpace
 
 if TYPE_CHECKING:
     from latentis.types import Space
@@ -26,10 +25,10 @@ def test_uniform_sampler(space1: Space, space2: Space):
     # Test suffix
     uniform = Uniform(random_seed=0, suffix="_custom")
     subspace1 = uniform(space1, n=10)
-    if isinstance(space1, LatentSpace):
-        assert subspace1.name.startswith(space1.name)
-        assert subspace1.name.endswith("_custom")
-        assert subspace1.name == space1.name + "_custom"
+    # if isinstance(space1, LatentSpace):
+    # assert subspace1.name.startswith(space1._name)
+    # assert subspace1.name.endswith("_custom")
+    # assert subspace1.name == space1._name + "_custom"
 
     # TODO: restore id comparisons
 
diff --git a/tests/test_index.py b/tests/test_index.py
new file mode 100644
index 0000000..a58e55e
--- /dev/null
+++ b/tests/test_index.py
@@ -0,0 +1,89 @@
+from pathlib import Path
+from typing import Mapping
+
+import pytest
+import torch
+
+from latentis.serialize.disk_index import DiskIndex
+from latentis.space import LatentSpace
+from latentis.utils import seed_everything
+
+
+def test_index(tmp_path: Path):
+    seed_everything(42)
+
+    index = DiskIndex(tmp_path / "test_index", item_class=LatentSpace)
+    n_items: int = 10
+
+    random_properties = [{"p1": str(torch.randn(1, 1).item()), "p2": "value2"} for _ in range(n_items)]
+
+    for x, properties in zip(range(n_items), random_properties):
+        fake_item = LatentSpace(
+            torch.randn(1, 1),
+            properties=properties,
+        )
+
+        index.add_item(item=fake_item)
+
+    # get item by properties with multiple matches
+    items = index.get_items(p2="value2")
+    assert len(index) == n_items == len(items)
+
+    # add item with same key
+    new_item_key = index.add_item(item=LatentSpace(torch.randn(1, 2), properties={"a": 1, "b": 2}))
+    assert len(index) == n_items + 1
+
+    with pytest.raises(FileExistsError):
+        index.add_item(item=LatentSpace(torch.randn(1, 2), properties=properties))
+
+    index.remove_item(item_key=new_item_key)
+    assert len(index) == n_items
+
+    index.remove_item(**properties)
+    new_item_key = index.add_item(item=LatentSpace(torch.randn(1, 2), properties=properties))
+    assert len(index) == n_items
+
+    # get item by key
+    item = index.get_item(new_item_key)
+    assert isinstance(item, Mapping)
+    assert len(index) == n_items
+
+    index.remove_item(**index.load_item(new_item_key).properties)
+    assert len(index) == n_items - 1
+
+    # get item by properties
+    item = index.load_item(p1=random_properties[1]["p1"])
+    assert isinstance(item, LatentSpace)
+
+    # get item by properties with multiple matches
+    with pytest.raises(ValueError):
+        index.get_item(p2="value2")
+
+    # get item by properties with no matches
+    with pytest.raises(KeyError):
+        assert index.get_item(p2="value3")
+
+    # get items by empty properties matches all items
+    assert len(index.get_items()) == len(index)
+
+    # also the load
+    assert len(index.load_items()) == len(index)
+
+    index_restored = DiskIndex.load_from_disk(tmp_path / "test_index")
+    assert len(index_restored) == len(index)
+
+    # also removing them all D:
+    index.remove_items()
+    assert len(index) == 0
+    assert len(list(tmp_path.iterdir())) == 1
+
+    # clear is now a no-op
+    index.clear()
+    assert len(index) == 0
+    assert len(list(tmp_path.iterdir())) == 1
+
+    # but it works indeed
+    index.add_item(item=LatentSpace(torch.randn(1, 2), properties=properties))
+    index.clear()
+    assert len(index) == 0
+    assert len(list(tmp_path.iterdir())) == 1
diff --git a/tests/test_search.py b/tests/test_search.py
index 94cbd2a..ef6b87c 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -6,8 +6,8 @@
 import torch
 import torch.nn.functional as F
 
-from latentis import LatentSpace
-from latentis.search import SearchIndex, SearchMetric
+from latentis.space import LatentSpace
+from latentis.space.search import SearchIndex, SearchMetric
 from latentis.utils import seed_everything
 
 
@@ -35,7 +35,6 @@ def test_index(
 
     space = LatentSpace(
         vector_source=torch.randn(num_vectors, num_dimensions, dtype=torch.double),
-        name="space1",
     )
     index = space.to_index(metric_fn=metric_fn, keys=[str(i) for i in range(num_vectors)])
 
@@ -136,7 +135,6 @@ def test_transform(num_vectors: int, num_dimensions: int):
 
     space = LatentSpace(
         vector_source=torch.randn(num_vectors, num_dimensions, dtype=torch.double),
-        name="space1",
     )
     index1 = space.to_index(metric_fn=SearchMetric.COSINE)
     index2 = space.to_index(metric_fn=SearchMetric.INNER_PRODUCT, transform=lambda x: F.normalize(x, p=2, dim=1))
@@ -152,7 +150,6 @@ def test_transform(num_vectors: int, num_dimensions: int):
 
     space = LatentSpace(
         vector_source=vectors,
-        name="space1",
     )
     index = space.to_index(metric_fn=SearchMetric.COSINE)
     assert index.transform is not None
@@ -189,7 +186,6 @@ def test_range_search(num_vectors: int, num_dimensions: int, search_metric2radiu
     vectors = torch.randn(num_vectors, num_dimensions, dtype=torch.double) * 10
     space = LatentSpace(
         vector_source=vectors,
-        name="space1",
     )
 
     index: SearchIndex = space.to_index(metric_fn=search_metric)
@@ -210,7 +206,6 @@ def test_keys(
 
     space = LatentSpace(
         vector_source=torch.randn(num_vectors, 100, dtype=torch.float32),
-        name="space1",
     )
 
     keys = ["".join([chr(ord("a") + np.random.randint(0, 26)) for _ in range(8)]) for _ in range(num_vectors)]
@@ -253,7 +248,6 @@ def test_get_vectors(num_vectors: int, num_dimensions: int):
     vectors = torch.randn(num_vectors, num_dimensions, dtype=torch.float32)
     space = LatentSpace(
         vector_source=vectors,
-        name="space1",
     )
 
     index = space.to_index(metric_fn=SearchMetric.EUCLIDEAN)
diff --git a/tests/test_space.py b/tests/test_space.py
index 9a70191..5c3567a 100644
--- a/tests/test_space.py
+++ b/tests/test_space.py
@@ -27,5 +27,5 @@ def test_space_get(space1: Space):
 def test_space_sample_hook(space1: Space):
     if isinstance(space1, LatentSpace):
         subspace = space1.sample(Uniform(), n=50)
-        assert subspace.name == space1.name + "_sampled"
+        # assert subspace._name == space1._name + "_sampled"
         assert len(subspace) == 50
diff --git a/tests/transform/test_transforms.py b/tests/transform/test_transforms.py
index b05ff50..42154ee 100644
--- a/tests/transform/test_transforms.py
+++ b/tests/transform/test_transforms.py
@@ -7,6 +7,7 @@
 from latentis.transform._abstract import SimpleTransform, Transform
 from latentis.transform.base import (
     Centering,
+    InverseTransform,
     IsotropicScaling,
     LPNorm,
     RandomDimensionPermutation,
@@ -89,19 +90,23 @@ def test_functional_transforms(
         inverse_params=inverse_params,
     )
     simple_transform.fit(x=space)
-    space_out2 = simple_transform.transform(x=space)
+    space_out2, _ = simple_transform.transform(x=space)
 
     transform = transform().fit(x=space)
-    space_out3 = transform.transform(x=space)
+    space_out3, _ = transform.transform(x=space)
 
     assert torch.allclose(space_out1, space_out2)
     assert torch.allclose(space_out1, space_out3)
 
     if simple_transform.invertible:
-        rev_out1 = simple_transform.inverse_transform(x=space_out1)
+        rev_out1, _ = simple_transform.inverse_transform(x=space_out1)
         rev_out2 = inverse_fn(x=space_out1, **inverse_params, **state)
-        rev_out3 = transform.inverse_transform(x=space_out1)
+        rev_out3, _ = transform.inverse_transform(x=space_out1)
+        inverse_transform = InverseTransform(transform=transform)
+
+        rev_out4, _ = inverse_transform.transform(x=space_out1, y=None)
 
         assert torch.allclose(space, rev_out1)
         assert torch.allclose(rev_out1, rev_out2)
         assert torch.allclose(rev_out2, rev_out3)
+        assert torch.allclose(rev_out3, rev_out4)
diff --git a/tests/translate/test_manual_translate.py b/tests/translate/test_manual_translate.py
index 4e8ea9d..164265d 100644
--- a/tests/translate/test_manual_translate.py
+++ b/tests/translate/test_manual_translate.py
@@ -11,8 +11,9 @@
 from latentis.transform import TransformSequence
 from latentis.transform.base import Centering, MeanLPNorm, StandardScaling, STDScaling
 from latentis.transform.dim_matcher import ZeroPadding
+from latentis.transform.functional import std_scaling_state, std_scaling_transform
 from latentis.transform.translate import MatrixAligner
-from latentis.transform.translate.aligner import SGDAffineAligner, Translator
+from latentis.transform.translate.aligner import Procrustes, SGDAffineAligner, Translator
 from latentis.transform.translate.functional import lstsq_align_state, lstsq_ortho_align_state, svd_align_state
 from latentis.utils import seed_everything
 
@@ -136,6 +137,8 @@ def fit(self, x: torch.Tensor, y: torch.Tensor) -> None:
 
         self.translation = lambda x: x @ self.translation_matrix
 
+        return self
+
     def transform(self, X: torch.Tensor, compute_info: bool = True) -> torch.Tensor:
         if self.method == "absolute":
             return {"source": X, "target": X, "info": {}}
@@ -174,7 +177,13 @@ def transform(self, X: torch.Tensor, compute_info: bool = True) -> torch.Tensor:
 @pytest.mark.parametrize(
     "manual_method, aligner_factory",
     [
-        ("svd", lambda: MatrixAligner("svd", align_fn_state=svd_align_state, dim_matcher=ZeroPadding())),
+        (
+            "svd",
+            lambda: Translator(
+                aligner=MatrixAligner("svd", align_fn_state=svd_align_state),
+                dim_matcher=ZeroPadding(),
+            ),
+        ),
         ("lstsq", lambda: MatrixAligner("lstsq", align_fn_state=lstsq_align_state)),
         ("lstsq+ortho", lambda: MatrixAligner("lstsq+ortho", align_fn_state=lstsq_ortho_align_state)),
         ("linear", lambda: SGDAffineAligner(num_steps=20, lr=1e-3, random_seed=0)),
@@ -245,7 +254,7 @@ def test_manual_translation(
     translator.fit(x=A, y=B)
 
     manual_output = manual_translator.transform(A)["target"]
-    latentis_output = translator.transform(x=A)
+    latentis_output, _ = translator.transform(x=A, y=B)
 
     assert torch.allclose(
         manual_output, latentis_output.vectors if isinstance(latentis_output, LatentSpace) else latentis_output
@@ -256,3 +265,24 @@ def test_manual_translation(
             manual_output,
             A.translate(translator=translator).vectors,
         )
+
+
+def test_procrustes(
+    parallel_spaces: Tuple[Space, Space],
+):
+    x, y = parallel_spaces
+    x = x.vectors if isinstance(x, LatentSpace) else x
+    y = y.vectors if isinstance(y, LatentSpace) else y
+
+    x = std_scaling_transform(x, **std_scaling_state(x))
+    y = std_scaling_transform(y, **std_scaling_state(y))
+
+    x1, _ = Procrustes().fit_transform(x, y)
+    manual_result = (
+        ManualLatentTranslation(seed=42, centering=True, std_correction=True, l2_norm=False, method="svd")
+        .fit(x=x, y=y)
+        .transform(x)
+    )
+    _, y2 = manual_result["source"], manual_result["target"]
+
+    assert torch.allclose(x1, y2)