Limits of custom split functions in tf.keras TextVectorization
0 reputation · 18 Mar 2020, 03:47 UTC
0 reputation · 18 Mar 2020, 03:47 UTC
The TextVectorization layer tokenizes input based on the split argument, which may be a string or a callable. When a callable is supplied, the entire input string is first standardize‑ed before the split function is invoked. This ordering can alter token boundaries if the split logic depends on whitespace that has been removed.
During adapt(), the vocabulary size is influenced by both the split function and max_tokens. Custom splits that produce many short tokens can inflate the vocabulary beyond the intended limit. Moreover, users report that the same split function yields different vocabularies when executed under TensorFlow 2.13 versus 2.14, implying a non‑deterministic ordering or internal change.
A custom split callable in tf.keras.layers.TextVectorization runs after standardize, so any normalization that removes whitespace or punctuation changes the token boundaries the split function sees. During adapt(), the layer counts token frequencies across the dataset, sorts by descending frequency, and truncates to max_tokens. A custom split that emits many short tokens can therefore push the vocabulary past the intended limit, and rare short tokens may be dropped instead.
split receives the standardized string, not the raw input.adapt() builds the vocabulary from frequency-ranked tokens and applies max_tokens as a hard ceiling.get_vocabulary() returns the post-adaptation token-to-index mapping as a list in index order.If the same split function produces different vocabularies under TensorFlow 2.13 and 2.14, the cause is most likely a change in internal segment reduction, hashing, or sorting behavior rather than the split callable itself. This is a version-sensitivity risk, not a documented contract. Locking the TensorFlow version is the only practical way to make the vocabulary reproducible across environments.
standardize=None if your split logic depends on whitespace that standardization would remove.max_tokens to cover the expected head of the frequency distribution, accepting that long-tail tokens will be mapped to out-of-vocabulary indices.adapt() on a fixed, unshuffled dataset, then inspect the result with get_vocabulary().
import tensorflow as tf
from tensorflow.keras.layers import TextVectorization
def my_split(text):
return tf.strings.split(text)
layer = TextVectorization(
standardize=None,
split=my_split,
max_tokens=1000,
output_sequence_length=32
)
layer.adapt(["hello world", "hello tensorflow", "world tensorflow"])
print(layer.get_vocabulary()[:20])
print("vocab size:", len(layer.get_vocabulary()))
Run this under each TensorFlow version you support and compare the printed vocabulary. If the lists differ, pin the TensorFlow version in your requirements file.
Does your dataset pass through shuffle() before adapt()? Shuffling changes token arrival order and can change which tokens survive frequency truncation when counts are tied.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 18 Mar 2020, 06:16 UTC
When two or more tokens have exactly the same frequency during adapt(), TensorFlow currently breaks the tie by the token’s first appearance in the dataset. This makes the vocabulary deterministic as long as the dataset order is fixed and the standardize function does not introduce randomness. However, the internal reduction that gathers frequencies can change between minor releases, which may affect the tie‑breaking order and thus the final vocabulary. To guarantee reproducibility you can:
tf.data.Dataset is not shuffled and is reused verbatim across runs.You can inspect the post‑adaptation mapping with layer.get_vocabulary(), which returns a list where the index equals the integer ID; the OOV token is always at index 0 when output_mode is 'int'.