-
Notifications
You must be signed in to change notification settings - Fork 235
feat(v2): add tensorflow support #1064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ba1a4b6
978dfe4
712c950
fd88185
17fe3d5
b0fd980
c2e6ab0
cf6a0ef
194ab9f
6a2ecf1
056db70
6c35902
2abe113
beb340e
1817c44
a74daac
ab7d153
72744ad
dfdff10
28a7291
418ee37
217c870
fd7a8e5
26150b6
567b56a
665f408
68bdd77
fcd5b74
1e8e240
a5988ab
156a508
a29f5c1
eb6a53a
a2afa41
9cc04dd
bfffc2d
6e715b3
cc2e837
125f66c
d6506d1
73f8b0a
2d9162c
ef335ad
1e85f7a
ca8d1d1
1dd9c6e
f8d8426
269cf0f
9bcb816
dac79e2
8046e99
e325244
b7db1c8
9d1ef56
0467aca
a25dceb
52064d2
591cea1
5fc2721
7432123
1144d6f
25b9f42
20a2b3e
102e42a
b4b7e43
545438d
4905526
c2aa0b1
81a2540
838955a
09dd6a9
5daa511
6c36d25
85183ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,22 +209,22 @@ class MyMultiModalModel(nn.Module): | |
| self.text_encoder = TextEncoder() | ||
|
|
||
| def forward(self, text_1, text_2, image_1, image_2, audio_1, audio_2): | ||
| emnedding_text_1 = self.text_encoder(text_1) | ||
| emnedding_text_2 = self.text_encoder(text_2) | ||
| embedding_text_1 = self.text_encoder(text_1) | ||
| embedding_text_2 = self.text_encoder(text_2) | ||
|
|
||
| emnedding_image_1 = self.image_encoder(image_1) | ||
| emnedding_image_2 = self.image_encoder(image_2) | ||
| embedding_image_1 = self.image_encoder(image_1) | ||
| embedding_image_2 = self.image_encoder(image_2) | ||
|
|
||
| emnedding_audio_1 = self.image_encoder(audio_1) | ||
| emnedding_audio_2 = self.image_encoder(audio_2) | ||
| embedding_audio_1 = self.image_encoder(audio_1) | ||
| embedding_audio_2 = self.image_encoder(audio_2) | ||
|
|
||
| return ( | ||
| emnedding_text_1, | ||
| emnedding_text_2, | ||
| emnedding_image_1, | ||
| emnedding_image_2, | ||
| emnedding_audio_1, | ||
| emnedding_audio_2, | ||
| embedding_text_1, | ||
| embedding_text_2, | ||
| embedding_image_1, | ||
| embedding_image_2, | ||
| embedding_audio_1, | ||
| embedding_audio_2, | ||
| ) | ||
| ``` | ||
|
|
||
|
|
@@ -258,14 +258,14 @@ class MyPodcastModel(nn.Module): | |
| self.image_encoder = ImageEncoder() | ||
| self.text_encoder = TextEncoder() | ||
|
|
||
| def forward_podcast(da: DocumentArray[Podcast]) -> DocumentArray[Podcast]: | ||
| def forward_podcast(self, da: DocumentArray[Podcast]) -> DocumentArray[Podcast]: | ||
| da.audio.embedding = self.audio_encoder(da.audio.tensor) | ||
| da.text.embedding = self.text_encoder(da.text.tensor) | ||
| da.image.embedding = self.image_encoder(da.image.tensor) | ||
|
|
||
| return da | ||
|
|
||
| def forward(da: DocumentArray[PairPodcast]) -> DocumentArray[PairPodcast]: | ||
| def forward(self, da: DocumentArray[PairPodcast]) -> DocumentArray[PairPodcast]: | ||
| da.left = self.forward_podcast(da.left) | ||
| da.right = self.forward_podcast(da.right) | ||
|
|
||
|
|
@@ -277,6 +277,49 @@ You instantly win in code readability and maintainability. And for the same pric | |
| schema definition (see below). Everything handles in a pythonic manner by relying on type hints. | ||
|
|
||
|
|
||
| ## Coming from TensorFlow | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this part is too big IMO. We just need to show that there is a (tiny ?) difference and that you need to access tensor.tensor. No need to show the full example |
||
|
|
||
| Similar to the PyTorch approach, you can also use DocArray with TensorFlow to handle and represent multi-modal data inside your ML model. | ||
|
|
||
| First off, to use DocArray with TensorFlow we first need to install it as follows: | ||
| ``` | ||
| pip install tensorflow==2.11.0 | ||
| pip install protobuf==3.19.0 | ||
| ``` | ||
|
|
||
| Compared to using DocArray with PyTorch, there is one main difference when using it with TensorFlow:\ | ||
| While DocArray's `TorchTensor` is a subclass of `torch.Tensor`, this is not the case for the `TensorFlowTensor`: Due to technical limitations on `tf.Tensor`, docarray's `TensorFlowTensor` is not a subclass of `tf.Tensor` but instead stores a `tf.Tensor` in its `.tensor` attribute. | ||
|
|
||
| How does this effect you? Whenever you want to access the tensor data to e.g. do operations with it or hand it to your ML model, instead of handing over your `TensorFlowTensor` instance, you need to access its `.tensor` attribute. | ||
|
|
||
| This would look like the following: | ||
|
|
||
| ```python | ||
| from typing import Optional | ||
|
|
||
| from docarray import DocumentArray, BaseDocument | ||
|
|
||
| import tensorflow as tf | ||
|
|
||
|
|
||
| class Podcast(BaseDocument): | ||
| audio_tensor: Optional[AudioTensorFlowTensor] | ||
| embedding: Optional[AudioTensorFlowTensor] | ||
|
|
||
|
|
||
| class MyPodcastModel(tf.keras.Model): | ||
| def __init__(self): | ||
| super().__init__() | ||
| self.audio_encoder = AudioEncoder() | ||
|
|
||
| def call(self, inputs: DocumentArray[Podcast]) -> DocumentArray[Podcast]: | ||
| inputs.audio_tensor.embedding = self.audio_encoder( | ||
| inputs.audio_tensor.tensor | ||
| ) # access audio_tensor's .tensor attribute | ||
| return inputs | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## Coming from FastAPI | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,14 +27,22 @@ | |
| from pydantic.fields import ModelField | ||
|
|
||
| from docarray.proto import DocumentArrayStackedProto | ||
| from docarray.typing import TorchTensor | ||
| from docarray.typing.tensor.abstract_tensor import AbstractTensor | ||
|
|
||
| try: | ||
| from docarray.typing import TorchTensor | ||
| except ImportError: | ||
| TorchTensor = None # type: ignore | ||
|
|
||
| try: | ||
| import tensorflow as tf # type: ignore | ||
|
|
||
| from docarray.typing import TensorFlowTensor | ||
|
Comment on lines
+36
to
+39
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for torch i moved this thing to a helper in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, saw that and started doing this in the TF embedding/video/audio PR, so i'll do this refactor there if that's fine with you @JohannesMessner |
||
|
|
||
| tf_available = True | ||
| except (ImportError, TypeError): | ||
| TensorFlowTensor = None # type: ignore | ||
| tf_available = False | ||
|
|
||
| T = TypeVar('T', bound='DocumentArrayStacked') | ||
| IndexIterType = Union[slice, Iterable[int], Iterable[bool], None] | ||
|
|
||
|
|
@@ -163,7 +171,26 @@ def _create_columns( | |
| tensor_columns: Dict[str, AbstractTensor] = dict() | ||
|
|
||
| for field, type_ in column_schema.items(): | ||
| if issubclass(type_, AbstractTensor): | ||
| if tf_available and isinstance(getattr(docs[0], field), TensorFlowTensor): | ||
| # tf.Tensor does not allow item assignment, therefore the optimized way | ||
| # of initializing an empty array and assigning values to it iteratively | ||
| # does not work here, therefore handle separately. | ||
| tf_stack = [] | ||
anna-charlotte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for i, doc in enumerate(docs): | ||
| val = getattr(doc, field) | ||
| if val is None: | ||
| val = tensor_type.get_comp_backend().none_value() | ||
| tf_stack.append(val.tensor) | ||
| del val.tensor | ||
|
|
||
| stacked: tf.Tensor = tf.stack(tf_stack) | ||
| tensor_columns[field] = TensorFlowTensor(stacked) | ||
| for i, doc in enumerate(docs): | ||
| val = getattr(doc, field) | ||
| x = tensor_columns[field][i].tensor | ||
| val.tensor = x | ||
|
|
||
| elif issubclass(type_, AbstractTensor): | ||
| tensor = getattr(docs[0], field) | ||
| column_shape = ( | ||
| (len(docs), *tensor.shape) if tensor is not None else (len(docs),) | ||
|
|
@@ -190,7 +217,8 @@ def _create_columns( | |
| # We thus chose to convert the individual rank 0 tensors to rank 1 | ||
| # This does mean that stacking rank 0 tensors will transform them | ||
| # to rank 1 | ||
| if tensor_columns[field].ndim == 1: | ||
| tensor = tensor_columns[field] | ||
| if tensor.get_comp_backend().n_dim(tensor) == 1: | ||
| setattr(doc, field, tensor_columns[field][i : i + 1]) | ||
| else: | ||
| setattr(doc, field, tensor_columns[field][i]) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.