Equimo 0.2.4__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/PKG-INFO +79 -1
  2. {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/requires.txt +9 -0
  3. {equimo-0.2.4 → equimo-0.3.0}/PKG-INFO +79 -1
  4. {equimo-0.2.4 → equimo-0.3.0}/README.md +71 -0
  5. equimo-0.3.0/equimo/__init__.py +1 -0
  6. {equimo-0.2.4 → equimo-0.3.0}/equimo/io.py +54 -1
  7. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/attention.py +12 -1
  8. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/ffn.py +8 -0
  9. {equimo-0.2.4 → equimo-0.3.0}/equimo/utils.py +78 -0
  10. {equimo-0.2.4 → equimo-0.3.0}/pyproject.toml +13 -1
  11. {equimo-0.2.4 → equimo-0.3.0}/tests/test_models.py +5 -3
  12. equimo-0.2.4/equimo/__init__.py +0 -1
  13. {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/SOURCES.txt +0 -0
  14. {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/dependency_links.txt +0 -0
  15. {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/top_level.txt +0 -0
  16. {equimo-0.2.4 → equimo-0.3.0}/LICENSE.md +0 -0
  17. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/__init__.py +0 -0
  18. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/activation.py +0 -0
  19. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/convolution.py +0 -0
  20. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/downsample.py +0 -0
  21. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/dropout.py +0 -0
  22. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/generic.py +0 -0
  23. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/mamba.py +0 -0
  24. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/norm.py +0 -0
  25. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/patch.py +0 -0
  26. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/posemb.py +0 -0
  27. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/sharing.py +0 -0
  28. {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/squeeze_excite.py +0 -0
  29. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/__init__.py +0 -0
  30. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/emamodel.py +0 -0
  31. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/fastervit.py +0 -0
  32. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/mlla.py +0 -0
  33. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/partialformer.py +0 -0
  34. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/shvit.py +0 -0
  35. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/vit.py +0 -0
  36. {equimo-0.2.4 → equimo-0.3.0}/equimo/models/vssd.py +0 -0
  37. {equimo-0.2.4 → equimo-0.3.0}/equimo/ops/scan.py +0 -0
  38. {equimo-0.2.4 → equimo-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Equimo
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -24,6 +24,13 @@ Requires-Dist: semver>=3.0.4
24
24
  Provides-Extra: torch
25
25
  Requires-Dist: torch>=2.6.0; extra == "torch"
26
26
  Requires-Dist: timm>=1.0.15; extra == "torch"
27
+ Provides-Extra: text
28
+ Requires-Dist: tensorflow>=2.18.1; extra == "text"
29
+ Requires-Dist: tensorflow-text>=2.18.1; extra == "text"
30
+ Provides-Extra: extras
31
+ Requires-Dist: matplotlib>=3.10.1; extra == "extras"
32
+ Requires-Dist: pillow>=11.1.0; extra == "extras"
33
+ Requires-Dist: scikit-learn>=1.6.1; extra == "extras"
27
34
 
28
35
  # Equimo: Modern Vision Models in JAX/Equinox
29
36
 
@@ -37,6 +44,7 @@ Equimo (Equinox Image Models) provides JAX/Equinox implementations of recent com
37
44
  - Focus on recent architectures (2023-2024 papers)
38
45
  - Modular design for easy experimentation
39
46
  - Extensive documentation and type hints
47
+ - **Experimental** support for text embedding
40
48
 
41
49
  ## Installation
42
50
 
@@ -98,6 +106,76 @@ x = jax.random.normal(key, (3, 224, 224))
98
106
  output = model(x, enable_dropout=False, key=key)
99
107
  ```
100
108
 
109
+ ## Working with text embeddings
110
+
111
+ **Warning: this is experimental, it can break or change at any time**
112
+
113
+ `equimo.experimental.text` has been added since v0.3.0. It allows working with both text and images. It is especially
114
+ useful for models like SigLIP or TIPS, although only TIPS is currently supported.
115
+
116
+ Currently, text tokenization relies on tensorflow_text, install equimo with the `text` group such as
117
+ `uv add equimo[text]`.
118
+
119
+ Here is a very simple example of a 0-shot classification based on the comparison between text and image embeddings:
120
+
121
+ ```python
122
+ import jax
123
+ from einops import rearrange
124
+
125
+ from equimo.experimental.text import Tokenizer
126
+ from equimo.io import load_image, load_model
127
+ from equimo.utils import PCAVisualizer, normalize, plot_image_and_feature_map
128
+
129
+ # Random demo inputs
130
+ key = jax.random.PRNGKey(42)
131
+ image = load_image("./demo.jpg", size=448)
132
+ text = [
133
+ "A baby discovering happiness",
134
+ "A computer",
135
+ ]
136
+
137
+ # Loading pretrained models
138
+ image_encoder = load_model("vit", "tips_vits14_hr")
139
+ text_encoder = load_model("experimental.textencoder", "tips_vits14_hr_text")
140
+
141
+ # Encoding text and image
142
+ ids, paddings = Tokenizer(identifier="sentencepiece_tips").tokenize(text, max_len=64)
143
+
144
+ text_embedding = normalize(
145
+ jax.vmap(text_encoder, in_axes=(0, 0, None))(ids, paddings, key)
146
+ )
147
+ image_embedding = jax.vmap(image_encoder.norm)(image_encoder.features(image, key))
148
+ cls_token = normalize(image_embedding[0])
149
+ spatial_features = rearrange(
150
+ image_embedding[2:], "(h w) d -> h w d", h=int(448 / 14), w=int(448 / 14)
151
+ )
152
+
153
+ # Getting probabilities based on Cosine Similarity
154
+ cos_sim = jax.nn.softmax(
155
+ ((cls_token[None, :] @ text_embedding.T) / text_encoder.temperature), axis=-1
156
+ )
157
+
158
+ # Plot the results
159
+ label_idxs = jax.numpy.argmax(cos_sim, axis=-1)
160
+ cos_sim_max = jax.numpy.max(cos_sim, axis=-1)
161
+ label_predicted = text[label_idxs[0]]
162
+ similarity = cos_sim_max[0]
163
+ pca_obj = PCAVisualizer(spatial_features)
164
+ image_pca = pca_obj(spatial_features)
165
+
166
+ plot_image_and_feature_map(
167
+ image.transpose(1, 2, 0),
168
+ image_pca,
169
+ "./out.png",
170
+ "Input Image",
171
+ f"{label_predicted}, prob: {similarity * 100:.2f}%",
172
+ )
173
+ ```
174
+
175
+ Resulting in such a wonderful result:
176
+
177
+ ![Output of TIPS 0-shot classification](./docs/assets/0-shot.png)
178
+
101
179
  ## Saving and Loading Models
102
180
 
103
181
  Equimo provides utilities for saving models locally and loading pre-trained models from the
@@ -7,6 +7,15 @@ lz4>=4.4.3
7
7
  requests>=2.32.3
8
8
  semver>=3.0.4
9
9
 
10
+ [extras]
11
+ matplotlib>=3.10.1
12
+ pillow>=11.1.0
13
+ scikit-learn>=1.6.1
14
+
15
+ [text]
16
+ tensorflow>=2.18.1
17
+ tensorflow-text>=2.18.1
18
+
10
19
  [torch]
11
20
  torch>=2.6.0
12
21
  timm>=1.0.15
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: Equimo
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Implementation of popular vision models in Jax
5
5
  Classifier: Development Status :: 4 - Beta
6
6
  Classifier: Intended Audience :: Developers
@@ -24,6 +24,13 @@ Requires-Dist: semver>=3.0.4
24
24
  Provides-Extra: torch
25
25
  Requires-Dist: torch>=2.6.0; extra == "torch"
26
26
  Requires-Dist: timm>=1.0.15; extra == "torch"
27
+ Provides-Extra: text
28
+ Requires-Dist: tensorflow>=2.18.1; extra == "text"
29
+ Requires-Dist: tensorflow-text>=2.18.1; extra == "text"
30
+ Provides-Extra: extras
31
+ Requires-Dist: matplotlib>=3.10.1; extra == "extras"
32
+ Requires-Dist: pillow>=11.1.0; extra == "extras"
33
+ Requires-Dist: scikit-learn>=1.6.1; extra == "extras"
27
34
 
28
35
  # Equimo: Modern Vision Models in JAX/Equinox
29
36
 
@@ -37,6 +44,7 @@ Equimo (Equinox Image Models) provides JAX/Equinox implementations of recent com
37
44
  - Focus on recent architectures (2023-2024 papers)
38
45
  - Modular design for easy experimentation
39
46
  - Extensive documentation and type hints
47
+ - **Experimental** support for text embedding
40
48
 
41
49
  ## Installation
42
50
 
@@ -98,6 +106,76 @@ x = jax.random.normal(key, (3, 224, 224))
98
106
  output = model(x, enable_dropout=False, key=key)
99
107
  ```
100
108
 
109
+ ## Working with text embeddings
110
+
111
+ **Warning: this is experimental, it can break or change at any time**
112
+
113
+ `equimo.experimental.text` has been added since v0.3.0. It allows working with both text and images. It is especially
114
+ useful for models like SigLIP or TIPS, although only TIPS is currently supported.
115
+
116
+ Currently, text tokenization relies on tensorflow_text, install equimo with the `text` group such as
117
+ `uv add equimo[text]`.
118
+
119
+ Here is a very simple example of a 0-shot classification based on the comparison between text and image embeddings:
120
+
121
+ ```python
122
+ import jax
123
+ from einops import rearrange
124
+
125
+ from equimo.experimental.text import Tokenizer
126
+ from equimo.io import load_image, load_model
127
+ from equimo.utils import PCAVisualizer, normalize, plot_image_and_feature_map
128
+
129
+ # Random demo inputs
130
+ key = jax.random.PRNGKey(42)
131
+ image = load_image("./demo.jpg", size=448)
132
+ text = [
133
+ "A baby discovering happiness",
134
+ "A computer",
135
+ ]
136
+
137
+ # Loading pretrained models
138
+ image_encoder = load_model("vit", "tips_vits14_hr")
139
+ text_encoder = load_model("experimental.textencoder", "tips_vits14_hr_text")
140
+
141
+ # Encoding text and image
142
+ ids, paddings = Tokenizer(identifier="sentencepiece_tips").tokenize(text, max_len=64)
143
+
144
+ text_embedding = normalize(
145
+ jax.vmap(text_encoder, in_axes=(0, 0, None))(ids, paddings, key)
146
+ )
147
+ image_embedding = jax.vmap(image_encoder.norm)(image_encoder.features(image, key))
148
+ cls_token = normalize(image_embedding[0])
149
+ spatial_features = rearrange(
150
+ image_embedding[2:], "(h w) d -> h w d", h=int(448 / 14), w=int(448 / 14)
151
+ )
152
+
153
+ # Getting probabilities based on Cosine Similarity
154
+ cos_sim = jax.nn.softmax(
155
+ ((cls_token[None, :] @ text_embedding.T) / text_encoder.temperature), axis=-1
156
+ )
157
+
158
+ # Plot the results
159
+ label_idxs = jax.numpy.argmax(cos_sim, axis=-1)
160
+ cos_sim_max = jax.numpy.max(cos_sim, axis=-1)
161
+ label_predicted = text[label_idxs[0]]
162
+ similarity = cos_sim_max[0]
163
+ pca_obj = PCAVisualizer(spatial_features)
164
+ image_pca = pca_obj(spatial_features)
165
+
166
+ plot_image_and_feature_map(
167
+ image.transpose(1, 2, 0),
168
+ image_pca,
169
+ "./out.png",
170
+ "Input Image",
171
+ f"{label_predicted}, prob: {similarity * 100:.2f}%",
172
+ )
173
+ ```
174
+
175
+ Resulting in such a wonderful result:
176
+
177
+ ![Output of TIPS 0-shot classification](./docs/assets/0-shot.png)
178
+
101
179
  ## Saving and Loading Models
102
180
 
103
181
  Equimo provides utilities for saving models locally and loading pre-trained models from the
@@ -10,6 +10,7 @@ Equimo (Equinox Image Models) provides JAX/Equinox implementations of recent com
10
10
  - Focus on recent architectures (2023-2024 papers)
11
11
  - Modular design for easy experimentation
12
12
  - Extensive documentation and type hints
13
+ - **Experimental** support for text embedding
13
14
 
14
15
  ## Installation
15
16
 
@@ -71,6 +72,76 @@ x = jax.random.normal(key, (3, 224, 224))
71
72
  output = model(x, enable_dropout=False, key=key)
72
73
  ```
73
74
 
75
+ ## Working with text embeddings
76
+
77
+ **Warning: this is experimental, it can break or change at any time**
78
+
79
+ `equimo.experimental.text` has been added since v0.3.0. It allows working with both text and images. It is especially
80
+ useful for models like SigLIP or TIPS, although only TIPS is currently supported.
81
+
82
+ Currently, text tokenization relies on tensorflow_text, install equimo with the `text` group such as
83
+ `uv add equimo[text]`.
84
+
85
+ Here is a very simple example of a 0-shot classification based on the comparison between text and image embeddings:
86
+
87
+ ```python
88
+ import jax
89
+ from einops import rearrange
90
+
91
+ from equimo.experimental.text import Tokenizer
92
+ from equimo.io import load_image, load_model
93
+ from equimo.utils import PCAVisualizer, normalize, plot_image_and_feature_map
94
+
95
+ # Random demo inputs
96
+ key = jax.random.PRNGKey(42)
97
+ image = load_image("./demo.jpg", size=448)
98
+ text = [
99
+ "A baby discovering happiness",
100
+ "A computer",
101
+ ]
102
+
103
+ # Loading pretrained models
104
+ image_encoder = load_model("vit", "tips_vits14_hr")
105
+ text_encoder = load_model("experimental.textencoder", "tips_vits14_hr_text")
106
+
107
+ # Encoding text and image
108
+ ids, paddings = Tokenizer(identifier="sentencepiece_tips").tokenize(text, max_len=64)
109
+
110
+ text_embedding = normalize(
111
+ jax.vmap(text_encoder, in_axes=(0, 0, None))(ids, paddings, key)
112
+ )
113
+ image_embedding = jax.vmap(image_encoder.norm)(image_encoder.features(image, key))
114
+ cls_token = normalize(image_embedding[0])
115
+ spatial_features = rearrange(
116
+ image_embedding[2:], "(h w) d -> h w d", h=int(448 / 14), w=int(448 / 14)
117
+ )
118
+
119
+ # Getting probabilities based on Cosine Similarity
120
+ cos_sim = jax.nn.softmax(
121
+ ((cls_token[None, :] @ text_embedding.T) / text_encoder.temperature), axis=-1
122
+ )
123
+
124
+ # Plot the results
125
+ label_idxs = jax.numpy.argmax(cos_sim, axis=-1)
126
+ cos_sim_max = jax.numpy.max(cos_sim, axis=-1)
127
+ label_predicted = text[label_idxs[0]]
128
+ similarity = cos_sim_max[0]
129
+ pca_obj = PCAVisualizer(spatial_features)
130
+ image_pca = pca_obj(spatial_features)
131
+
132
+ plot_image_and_feature_map(
133
+ image.transpose(1, 2, 0),
134
+ image_pca,
135
+ "./out.png",
136
+ "Input Image",
137
+ f"{label_predicted}, prob: {similarity * 100:.2f}%",
138
+ )
139
+ ```
140
+
141
+ Resulting in such a wonderful result:
142
+
143
+ ![Output of TIPS 0-shot classification](./docs/assets/0-shot.png)
144
+
74
145
  ## Saving and Loading Models
75
146
 
76
147
  Equimo provides utilities for saving models locally and loading pre-trained models from the
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -1,10 +1,13 @@
1
1
  import json
2
+ import io
2
3
  import tarfile
3
4
  import tempfile
4
5
  from pathlib import Path
6
+ from typing import Optional
5
7
 
6
8
  import equinox as eqx
7
9
  import jax
10
+ import jax.numpy as jnp
8
11
  import lz4.frame
9
12
  import requests
10
13
  from loguru import logger
@@ -131,6 +134,7 @@ def load_model(
131
134
  identifier: str | None = None,
132
135
  path: Path | None = None,
133
136
  repository: str = DEFAULT_REPOSITORY_URL,
137
+ inference_mode: bool = True,
134
138
  **model_kwargs,
135
139
  ) -> eqx.Module:
136
140
  """Load an Equinox model from either a local path or remote repository.
@@ -144,6 +148,8 @@ def load_model(
144
148
  Mutually exclusive with identifier. Defaults to None.
145
149
  repository (str, optional): Base URL for model download.
146
150
  Defaults to DEFAULT_REPOSITORY_URL.
151
+ inference_mode (bool): Disables dropouts if True.
152
+ Defaults to True.
147
153
  model_kwargs: kwargs passed to model instanciation. Overrides metadatas.
148
154
 
149
155
  Returns:
@@ -219,6 +225,10 @@ def load_model(
219
225
  model_cls = em.FasterViT
220
226
  case "partialformer":
221
227
  model_cls = em.PartialFormer
228
+ case "experimental.textencoder":
229
+ from equimo.experimental.text import TextEncoder
230
+
231
+ model_cls = TextEncoder
222
232
  case _:
223
233
  raise ValueError(f"Unknown model class: {cls}")
224
234
 
@@ -226,9 +236,52 @@ def load_model(
226
236
  kwargs = metadata["model_config"] | model_kwargs
227
237
  model = model_cls(**kwargs, key=jax.random.PRNGKey(42))
228
238
 
229
- # Load weights
239
+ # Load weights and set inference mode
230
240
  model = eqx.tree_deserialise_leaves(load_path / "weights.eqx", model)
241
+ model = eqx.nn.inference_mode(model, inference_mode)
231
242
 
232
243
  logger.info("Model loaded successfully.")
233
244
 
234
245
  return model
246
+
247
+
248
+ def load_image(
249
+ path: str,
250
+ mean: Optional[list[float]] = None,
251
+ std: Optional[list[float]] = None,
252
+ size: Optional[int] = None,
253
+ ):
254
+ """Load an image and perform minor preprocessing.
255
+
256
+ Args:
257
+ path (str): Path of the image.
258
+ mean (list, optional): Channel mean for normalization.
259
+ std (list, optional): Channel std for normalization.
260
+ size (int, optional): Size to which resize the image.
261
+
262
+ Returns:
263
+ jnp.array: The loaded image.
264
+
265
+ Raises:
266
+ ImportError: If PIL can't be loaded.
267
+ """
268
+ try:
269
+ from PIL import Image
270
+ except ImportError:
271
+ raise ImportError("PIL is needed to be able to load images.")
272
+
273
+ with open(path, "rb") as fd:
274
+ image_bytes = io.BytesIO(fd.read())
275
+ pil_image = Image.open(image_bytes)
276
+
277
+ array = jnp.array(pil_image).astype(jnp.float32) / 255.0
278
+ if size is not None:
279
+ array = jax.image.resize(array, (size, size, 3), method="bilinear")
280
+
281
+ if mean is not None and std is not None:
282
+ mean = jnp.array(mean)[None, None, :]
283
+ std = jnp.array(std)[None, None, :]
284
+
285
+ array = (array - mean) / std
286
+
287
+ return array.transpose(2, 0, 1)
@@ -1,5 +1,4 @@
1
1
  from typing import Callable, List, Optional, Tuple
2
-
3
2
  import equinox as eqx
4
3
  import jax
5
4
  import jax.numpy as jnp
@@ -74,6 +73,7 @@ class Attention(eqx.Module):
74
73
  x: Float[Array, "seqlen dim"],
75
74
  key: PRNGKeyArray,
76
75
  inference: Optional[bool] = None,
76
+ mask: Optional[Float[Array, ""]] = None,
77
77
  ) -> Float[Array, "seqlen dim"]:
78
78
  key1, key2 = jr.split(key, 2)
79
79
 
@@ -90,6 +90,10 @@ class Attention(eqx.Module):
90
90
  k = jax.vmap(jax.vmap(self.k_norm))(k)
91
91
 
92
92
  attn = jnp.einsum("hqd,hkd->hqk", q, k) / jnp.sqrt(self.head_dim)
93
+
94
+ if mask is not None:
95
+ attn = jnp.where(mask == 0, jnp.finfo(attn.dtype).min, attn)
96
+
93
97
  attn = jax.nn.softmax(attn, axis=-1)
94
98
  attn = self.attn_drop(attn, inference=inference, key=key1)
95
99
 
@@ -312,9 +316,14 @@ class AttentionBlock(eqx.Module):
312
316
  x: Float[Array, "seqlen dim"],
313
317
  key: PRNGKeyArray,
314
318
  inference: Optional[bool] = None,
319
+ mask: Optional[Float[Array, ""]] = None,
315
320
  ) -> Float[Array, "seqlen dim"]:
316
321
  key_attn, key_mlp, key_dr1, key_dr2 = jr.split(key, 4)
317
322
 
323
+ # I chose to define extra args here rather than passing mask directly
324
+ # because not all attention mechanisms support masks as args
325
+ extra_kwargs = {"mask": mask} if mask is not None else {}
326
+
318
327
  x = self.drop_path1(
319
328
  x,
320
329
  self.ls1(
@@ -323,6 +332,7 @@ class AttentionBlock(eqx.Module):
323
332
  jax.vmap(self.prenorm)(x),
324
333
  inference=inference,
325
334
  key=key_attn,
335
+ **extra_kwargs,
326
336
  )
327
337
  )
328
338
  ),
@@ -336,6 +346,7 @@ class AttentionBlock(eqx.Module):
336
346
  jax.vmap(self.norm)(x),
337
347
  inference=inference,
338
348
  key=key_mlp,
349
+ **extra_kwargs,
339
350
  )
340
351
  ),
341
352
  inference=inference,
@@ -210,6 +210,7 @@ class Mlp(eqx.Module):
210
210
  self,
211
211
  x: Float[Array, "seqlen dim"],
212
212
  key: PRNGKeyArray,
213
+ mask: Optional[Float[Array, ""]] = None,
213
214
  inference: Optional[bool] = None,
214
215
  ) -> Float[Array, "seqlen dim"]:
215
216
  key_dr1, key_dr2 = jr.split(key, 2)
@@ -219,12 +220,19 @@ class Mlp(eqx.Module):
219
220
  inference=inference,
220
221
  key=key_dr1,
221
222
  )
223
+
224
+ if mask is not None:
225
+ x *= mask
226
+
222
227
  x = self.drop2(
223
228
  jax.vmap(self.fc2)(x),
224
229
  inference=inference,
225
230
  key=key_dr2,
226
231
  )
227
232
 
233
+ if mask is not None:
234
+ x *= mask
235
+
228
236
  return x
229
237
 
230
238
 
@@ -4,6 +4,84 @@ import jax
4
4
  import jax.numpy as jnp
5
5
  from jaxtyping import Array, Float
6
6
 
7
+ import typing as t
8
+ import numpy as np
9
+
10
+
11
+ _ArrayLike = t.Union[np.ndarray, jnp.ndarray]
12
+
13
+
14
+ def normalize(x, order: int = 2):
15
+ return x / np.linalg.norm(x, ord=order, axis=-1, keepdims=True).clip(min=1e-3)
16
+
17
+
18
+ class PCAVisualizer:
19
+ """PCA visualizer.
20
+
21
+ Taken as-is from https://github.com/google-deepmind/tips/blob/72820c1841f973c9543d9c95c5ff2262ec621955/scenic/utils/feature_viz.py#L32
22
+ """
23
+
24
+ def __init__(
25
+ self, features: _ArrayLike, n_samples: int = 100000, n_components: int = 3
26
+ ) -> None:
27
+ """Creates a PCA object for visualizing features of shape [..., F]."""
28
+ try:
29
+ from sklearn import decomposition
30
+ except ImportError:
31
+ raise ImportError(
32
+ "You need sklearn to use the PCAVisualizer, install it using `uv add equimo[viz]`"
33
+ )
34
+
35
+ features = np.array(features)
36
+ pca_object = decomposition.PCA(n_components=n_components)
37
+ features = features.reshape([-1, features.shape[-1]])
38
+ features = features[np.random.randint(0, features.shape[0], n_samples), :]
39
+ pca_object.fit(features)
40
+ self.pca_object = pca_object
41
+ self.n_components = n_components
42
+
43
+ def __call__(self, features: _ArrayLike) -> np.ndarray:
44
+ """Apply PCA to features of shape [..., F]."""
45
+ features = np.array(features)
46
+ features_pca = self.pca_object.transform(
47
+ features.reshape([-1, features.shape[-1]])
48
+ ).reshape(features.shape[:-1] + (self.n_components,))
49
+ return normalize(features_pca) * 0.5 + 0.5
50
+
51
+
52
+ def plot_image_and_feature_map(
53
+ image,
54
+ feature_map,
55
+ save_path,
56
+ image_title="Image",
57
+ map_title="Feature Map",
58
+ fontsize=16,
59
+ ):
60
+ """
61
+ Plots an image and its feature map side by side and saves the figure.
62
+ """
63
+ try:
64
+ import matplotlib.pyplot as plt
65
+ except ImportError:
66
+ raise ImportError("Matplotlib is required to plot an image")
67
+
68
+ feature_map_normalized = (feature_map - feature_map.min()) / (
69
+ feature_map.max() - feature_map.min()
70
+ )
71
+
72
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
73
+ ax1.imshow(image)
74
+ ax1.set_title(image_title, fontsize=fontsize)
75
+ ax1.axis("off")
76
+
77
+ ax2.imshow(feature_map_normalized, cmap="gray")
78
+ ax2.set_title(map_title, fontsize=fontsize)
79
+ ax2.axis("off")
80
+
81
+ plt.tight_layout()
82
+ plt.savefig(save_path)
83
+ plt.close()
84
+
7
85
 
8
86
  def make_2tuple(x):
9
87
  """Convert input into a 2-tuple.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Equimo"
3
- version = "0.2.4"
3
+ version = "0.3.0"
4
4
  description = "Implementation of popular vision models in Jax"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -31,10 +31,22 @@ classifiers = [
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
34
+ # Useful to convert models from PyTorch
34
35
  torch = [
35
36
  "torch>=2.6.0",
36
37
  "timm>=1.0.15",
37
38
  ]
39
+ # Required for equimo.experimental.text (text tokenizer)
40
+ text = [
41
+ "tensorflow>=2.18.1",
42
+ "tensorflow-text>=2.18.1",
43
+ ]
44
+ # Required for some external utils
45
+ extras = [
46
+ "matplotlib>=3.10.1",
47
+ "pillow>=11.1.0",
48
+ "scikit-learn>=1.6.1",
49
+ ]
38
50
 
39
51
  [dependency-groups]
40
52
  dev = [
@@ -75,7 +75,9 @@ def test_save_load_model_compressed():
75
75
  save_model(save_path, model, model_config, torch_hub_cfg, compression=True)
76
76
 
77
77
  # Load model
78
- loaded_model = load_model(cls="vit", path=save_path.with_suffix(".tar.lz4"))
78
+ loaded_model = load_model(
79
+ cls="vit", path=save_path.with_suffix(".tar.lz4"), dynamic_img_size=True
80
+ )
79
81
 
80
82
  # Test loaded model
81
83
  loaded_output = loaded_model.features(x, key=key)
@@ -116,7 +118,7 @@ def test_save_load_model_uncompressed():
116
118
 
117
119
  save_model(save_path, model, model_config, torch_hub_cfg, compression=False)
118
120
 
119
- loaded_model = load_model(cls="vit", path=save_path)
121
+ loaded_model = load_model(cls="vit", path=save_path, dynamic_img_size=True)
120
122
  loaded_output = loaded_model.features(x, key=key)
121
123
 
122
124
  assert jnp.allclose(original_output, loaded_output, atol=1e-5)
@@ -125,7 +127,7 @@ def test_save_load_model_uncompressed():
125
127
  def test_load_pretrained_model():
126
128
  """Test loading a pretrained model from the repository."""
127
129
  key = jr.PRNGKey(42)
128
- model = load_model(cls="vit", identifier="dinov2_vits14_reg")
130
+ model = load_model(cls="vit", identifier="dinov2_vits14_reg", dynamic_img_size=True)
129
131
 
130
132
  # Test inference
131
133
  x = jr.normal(key, (3, 224, 224))
@@ -1 +0,0 @@
1
- __version__ = "0.2.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes