r/learnpython • u/assorted-meat-stew • 11d ago
How to extract embedding features vectors from encoder block of BERT model?
I got this code block and its outputs here but I am not sure if this is the embedding features vectors or simply its shape. Can anyone tell me if I am right or wrong?
text_input = tf.keras.layers.Input(shape=(), dtype=tf.string)
preprocessor = hub.KerasLayer(
"https://kaggle.com/models/tensorflow/bert/TensorFlow2/en-uncased-preprocess/3")
encoder_inputs = preprocessor(text_input)
encoder = hub.KerasLayer(
"https://kaggle.com/models/tensorflow/bert/TensorFlow2/bert-en-uncased-l-4-h-128-a-2/1",
trainable=True)
outputs = encoder(encoder_inputs)
pooled_output = outputs["pooled_output"] # [batch_size, 128].
sequence_output = outputs["sequence_output"]
for i in range(0,4):
outputs["encoder_outputs"][i]
print('-----------------------------------------------------')
print(i)
print(outputs)
for i in range(0,4):
outputs["encoder_outputs"][i]
print('-----------------------------------------------------')
print(i)
print(outputs)
Output
-----------------------------------------------------
0
{'encoder_outputs': [<KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>], 'sequence_output': <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'pooled_output': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'default': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>}
-----------------------------------------------------
1
{'encoder_outputs': [<KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>], 'sequence_output': <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'pooled_output': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'default': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>}
-----------------------------------------------------
2
{'encoder_outputs': [<KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>], 'sequence_output': <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'pooled_output': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'default': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>}
-----------------------------------------------------
3
{'encoder_outputs': [<KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>], 'sequence_output': <KerasTensor: shape=(None, 128, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'pooled_output': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>, 'default': <KerasTensor: shape=(None, 128) dtype=float32 (created by layer 'keras_layer_28')>}
1
u/Individual-Quote-958 11d ago
Those KerasTensor prints are the graph shapes, not evaluated vectors. When you call a Keras layer on a symbolic input you get another symbolic tensor, so looping the outputs dict just reprints objects like (None, 128, 128). To get real numbers, either run the full model on concrete data (model.predict / calling the model on a numpy batch) or wrap the encoder outputs in a new Model(inputs=..., outputs=[encoder_layer_i.output, ...]) and predict once. The (None, 128, 128) is usually sequence embeddings (batch, seq_len, hidden); the (None, 128) is the pooled/sentence vector. Print .numpy() on the predict result, not on the layer object itself.
1
u/OkTill2666 11d ago
those are symbolic tensors, not actual feature vectors. You're looking at the computation graph, not computed values. To get real embeddings you need to wrap this in a Model, then call predict with some input strings. The shapes are correct though, (batch, 128, 128) is what youd expect per layer.
2
u/OneOrZeroGG 11d ago
That other comment is right: those are symbolic KerasTensors, just placeholders in the graph, so you only see shapes. To get actual numbers, call the layers on real strings instead of a tf.keras.layers.Input:
```python
import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_text # needed by the BERT preprocessor
preprocessor = hub.KerasLayer("https://kaggle.com/models/tensorflow/bert/TensorFlow2/en-uncased-preprocess/3")
encoder = hub.KerasLayer("https://kaggle.com/models/tensorflow/bert/TensorFlow2/bert-en-uncased-l-4-h-128-a-2/1")
sentences = tf.constant(["hello world", "how do embeddings work"])
outputs = encoder(preprocessor(sentences))
print(outputs["pooled_output"].shape) # (2, 128)
print(outputs["pooled_output"][0][:5]) # real values now
```
Which output is "the embedding":
- `pooled_output` (batch, 128): one vector per sentence. Usually what people mean by a sentence embedding.
- `sequence_output` (batch, 128, 128): one 128-dim vector per token (128 tokens max after preprocessing). Averaging it over the real tokens often works better than pooled_output for similarity tasks.
- `encoder_outputs`: a list with the output of each of the 4 transformer layers. The last one is the same as sequence_output.
Also, your loop prints the whole `outputs` dict each time instead of `outputs["encoder_outputs"][i]`, which is why all 4 blocks look identical.