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.
- {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/PKG-INFO +79 -1
- {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/requires.txt +9 -0
- {equimo-0.2.4 → equimo-0.3.0}/PKG-INFO +79 -1
- {equimo-0.2.4 → equimo-0.3.0}/README.md +71 -0
- equimo-0.3.0/equimo/__init__.py +1 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/io.py +54 -1
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/attention.py +12 -1
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/ffn.py +8 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/utils.py +78 -0
- {equimo-0.2.4 → equimo-0.3.0}/pyproject.toml +13 -1
- {equimo-0.2.4 → equimo-0.3.0}/tests/test_models.py +5 -3
- equimo-0.2.4/equimo/__init__.py +0 -1
- {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/SOURCES.txt +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/dependency_links.txt +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/Equimo.egg-info/top_level.txt +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/LICENSE.md +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/__init__.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/activation.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/convolution.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/downsample.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/dropout.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/generic.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/mamba.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/norm.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/patch.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/posemb.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/sharing.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/layers/squeeze_excite.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/__init__.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/emamodel.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/fastervit.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/mlla.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/partialformer.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/shvit.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/vit.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/models/vssd.py +0 -0
- {equimo-0.2.4 → equimo-0.3.0}/equimo/ops/scan.py +0 -0
- {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.
|
|
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
|
+

|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: Equimo
|
|
3
|
-
Version: 0.
|
|
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
|
+

|
|
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
|
+

|
|
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.
|
|
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(
|
|
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))
|
equimo-0.2.4/equimo/__init__.py
DELETED
|
@@ -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
|
|
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
|