Tensores irregulares

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Documentação da API:tf.RaggedTensor tf.ragged

Configurar

import math
import tensorflow as tf

Visão geral

Seus dados vêm em muitas formas; seus tensores também. Tensores irregulares são o TensorFlow equivalente a listas aninhadas de comprimento variável. Eles tornam mais fácil armazenar e processar dados com formas não uniformes, incluindo:

  • Recursos de comprimento variável, como o conjunto de atores em um filme.
  • Lotes de entradas sequenciais de comprimento variável, como frases ou videoclipes.
  • Entradas hierárquicas, como documentos de texto subdivididos em seções, parágrafos, frases e palavras.
  • Campos individuais em entradas estruturadas, como buffers de protocolo.

O que você pode fazer com um tensor irregular

Tensores irregulares são suportados por mais de uma centena de operações do TensorFlow, incluindo operações matemáticas (como tf.add e tf.reduce_mean ), operações de matriz (como tf.concat e tf.tile ), operações de manipulação de string (como tf.substr ), operações de fluxo de controle (como tf.while_loop e tf.map_fn ) e muitos outros:

digits = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
words = tf.ragged.constant([["So", "long"], ["thanks", "for", "all", "the", "fish"]])
print(tf.add(digits, 3))
print(tf.reduce_mean(digits, axis=1))
print(tf.concat([digits, [[5, 3]]], axis=0))
print(tf.tile(digits, [1, 2]))
print(tf.strings.substr(words, 0, 2))
print(tf.map_fn(tf.math.square, digits))
<tf.RaggedTensor [[6, 4, 7, 4], [], [8, 12, 5], [9], []]>
tf.Tensor([2.25              nan 5.33333333 6.                nan], shape=(5,), dtype=float64)
<tf.RaggedTensor [[3, 1, 4, 1], [], [5, 9, 2], [6], [], [5, 3]]>
<tf.RaggedTensor [[3, 1, 4, 1, 3, 1, 4, 1], [], [5, 9, 2, 5, 9, 2], [6, 6], []]>
<tf.RaggedTensor [[b'So', b'lo'], [b'th', b'fo', b'al', b'th', b'fi']]>
<tf.RaggedTensor [[9, 1, 16, 1], [], [25, 81, 4], [36], []]>

Existem também vários métodos e operações que são específicos para tensores irregulares, incluindo métodos de fábrica, métodos de conversão e operações de mapeamento de valor. Para obter uma lista de operações com suporte, consulte a documentação do pacote tf.ragged .

Os tensores irregulares são compatíveis com muitas APIs do TensorFlow, incluindo Keras , Datasets , tf.function , SavedModels e tf.Example . Para mais informações, verifique a seção sobre APIs do TensorFlow abaixo.

Assim como acontece com tensores normais, você pode usar a indexação no estilo Python para acessar fatias específicas de um tensor irregular. Para obter mais informações, consulte a seção sobre Indexação abaixo.

print(digits[0])       # First row
tf.Tensor([3 1 4 1], shape=(4,), dtype=int32)
print(digits[:, :2])   # First two values in each row.
<tf.RaggedTensor [[3, 1], [], [5, 9], [6], []]>
print(digits[:, -2:])  # Last two values in each row.
<tf.RaggedTensor [[4, 1], [], [9, 2], [6], []]>

E, assim como os tensores normais, você pode usar operadores aritméticos e de comparação do Python para realizar operações elementwise. Para obter mais informações, verifique a seção sobre Operadores sobrecarregados abaixo.

print(digits + 3)
<tf.RaggedTensor [[6, 4, 7, 4], [], [8, 12, 5], [9], []]>
print(digits + tf.ragged.constant([[1, 2, 3, 4], [], [5, 6, 7], [8], []]))
<tf.RaggedTensor [[4, 3, 7, 5], [], [10, 15, 9], [14], []]>

Se você precisar realizar uma transformação de elemento para os valores de um RaggedTensor , pode usar tf.ragged.map_flat_values , que recebe uma função mais um ou mais argumentos e aplica a função para transformar os RaggedTensor do RaggedTensor .

times_two_plus_one = lambda x: x * 2 + 1
print(tf.ragged.map_flat_values(times_two_plus_one, digits))
<tf.RaggedTensor [[7, 3, 9, 3], [], [11, 19, 5], [13], []]>

Tensores irregulares podem ser convertidos em list Python aninhadas e array NumPy s:

digits.to_list()
[[3, 1, 4, 1], [], [5, 9, 2], [6], []]
digits.numpy()
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py:2053: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  return np.array(rows)
array([array([3, 1, 4, 1], dtype=int32), array([], dtype=int32),
       array([5, 9, 2], dtype=int32), array([6], dtype=int32),
       array([], dtype=int32)], dtype=object)

Construindo um tensor irregular

A maneira mais simples de construir um tensor irregular é usando tf.ragged.constant , que constrói o RaggedTensor correspondente a uma determinada list Python aninhada ou array NumPy:

sentences = tf.ragged.constant([
    ["Let's", "build", "some", "ragged", "tensors", "!"],
    ["We", "can", "use", "tf.ragged.constant", "."]])
print(sentences)
<tf.RaggedTensor [[b"Let's", b'build', b'some', b'ragged', b'tensors', b'!'], [b'We', b'can', b'use', b'tf.ragged.constant', b'.']]>
paragraphs = tf.ragged.constant([
    [['I', 'have', 'a', 'cat'], ['His', 'name', 'is', 'Mat']],
    [['Do', 'you', 'want', 'to', 'come', 'visit'], ["I'm", 'free', 'tomorrow']],
])
print(paragraphs)
<tf.RaggedTensor [[[b'I', b'have', b'a', b'cat'], [b'His', b'name', b'is', b'Mat']], [[b'Do', b'you', b'want', b'to', b'come', b'visit'], [b"I'm", b'free', b'tomorrow']]]>

Os tensores irregulares também podem ser construídos pareando tensores de valores planos com tensores de particionamento de linha, indicando como esses valores devem ser divididos em linhas, usando tf.RaggedTensor.from_value_rowids tf.RaggedTensor.from_row_lengths fábrica, como tf.RaggedTensor.from_value_rowids , tf.RaggedTensor.from_row_lengths e tf.RaggedTensor.from_row_splits .

tf.RaggedTensor.from_value_rowids

Se você souber a qual linha cada valor pertence, poderá construir um RaggedTensor usando um tensor de particionamento de linha value_rowids :

value_rowids tensor de particionamento de linha

print(tf.RaggedTensor.from_value_rowids(
    values=[3, 1, 4, 1, 5, 9, 2],
    value_rowids=[0, 0, 0, 0, 2, 2, 3]))
<tf.RaggedTensor [[3, 1, 4, 1], [], [5, 9], [2]]>

tf.RaggedTensor.from_row_lengths

Se você souber o comprimento de cada linha, poderá usar um tensor de particionamento de linha row_lengths :

row_lengths tensor de particionamento de linha

print(tf.RaggedTensor.from_row_lengths(
    values=[3, 1, 4, 1, 5, 9, 2],
    row_lengths=[4, 0, 2, 1]))
<tf.RaggedTensor [[3, 1, 4, 1], [], [5, 9], [2]]>

tf.RaggedTensor.from_row_splits

Se você souber o índice onde cada linha começa e termina, você pode usar um tensor de particionamento de linha row_splits :

row_splits tensor de particionamento de linha

print(tf.RaggedTensor.from_row_splits(
    values=[3, 1, 4, 1, 5, 9, 2],
    row_splits=[0, 4, 4, 6, 7]))
<tf.RaggedTensor [[3, 1, 4, 1], [], [5, 9], [2]]>

Consulte a documentação da classetf.RaggedTensor para uma lista completa dos métodos de fábrica.

O que você pode armazenar em um tensor irregular

Como acontece com os Tensor normais, os valores em um RaggedTensor devem ter todos o mesmo tipo; e os valores devem estar todos na mesma profundidade de aninhamento (a classificação do tensor):

print(tf.ragged.constant([["Hi"], ["How", "are", "you"]]))  # ok: type=string, rank=2
<tf.RaggedTensor [[b'Hi'], [b'How', b'are', b'you']]>
print(tf.ragged.constant([[[1, 2], [3]], [[4, 5]]]))        # ok: type=int32, rank=3
<tf.RaggedTensor [[[1, 2], [3]], [[4, 5]]]>
try:
  tf.ragged.constant([["one", "two"], [3, 4]])              # bad: multiple types
except ValueError as exception:
  print(exception)
Can't convert Python sequence with mixed types to Tensor.
try:
  tf.ragged.constant(["A", ["B", "C"]])                     # bad: multiple nesting depths
except ValueError as exception:
  print(exception)
all scalar values must have the same nesting depth

Caso de uso de exemplo

O exemplo a seguir demonstra como RaggedTensor s pode ser usado para construir e combinar embeddings de unigrama e bigrama para um lote de consultas de comprimento variável, usando marcadores especiais para o início e o final de cada frase. Para obter mais detalhes sobre os ops usados ​​neste exemplo, verifique a documentação do pacote tf.ragged .

queries = tf.ragged.constant([['Who', 'is', 'Dan', 'Smith'],
                              ['Pause'],
                              ['Will', 'it', 'rain', 'later', 'today']])

# Create an embedding table.
num_buckets = 1024
embedding_size = 4
embedding_table = tf.Variable(
    tf.random.truncated_normal([num_buckets, embedding_size],
                       stddev=1.0 / math.sqrt(embedding_size)))

# Look up the embedding for each word.
word_buckets = tf.strings.to_hash_bucket_fast(queries, num_buckets)
word_embeddings = tf.nn.embedding_lookup(embedding_table, word_buckets)     # ①

# Add markers to the beginning and end of each sentence.
marker = tf.fill([queries.nrows(), 1], '#')
padded = tf.concat([marker, queries, marker], axis=1)                       # ②

# Build word bigrams and look up embeddings.
bigrams = tf.strings.join([padded[:, :-1], padded[:, 1:]], separator='+')   # ③

bigram_buckets = tf.strings.to_hash_bucket_fast(bigrams, num_buckets)
bigram_embeddings = tf.nn.embedding_lookup(embedding_table, bigram_buckets) # ④

# Find the average embedding for each sentence
all_embeddings = tf.concat([word_embeddings, bigram_embeddings], axis=1)    # ⑤
avg_embedding = tf.reduce_mean(all_embeddings, axis=1)                      # ⑥
print(avg_embedding)
tf.Tensor(
[[-0.23997106 -0.14016564 -0.2052617  -0.14209671]
 [ 0.17473209 -0.43044376 -0.33512878 -0.38062695]
 [ 0.16858886 -0.04922291  0.04416551  0.15116404]], shape=(3, 4), dtype=float32)

Exemplo de tensor irregular

Dimensões irregulares e uniformes

Uma dimensão irregular é uma dimensão cujas fatias podem ter comprimentos diferentes. Por exemplo, a dimensão interna (coluna) de rt=[[3, 1, 4, 1], [], [5, 9, 2], [6], []] é irregular, uma vez que as fatias da coluna ( rt[0, :] , ..., rt[4, :] ) têm comprimentos diferentes. As dimensões cujas fatias têm o mesmo comprimento são chamadas de dimensões uniformes .

A dimensão mais externa de um tensor irregular é sempre uniforme, uma vez que consiste em uma única fatia (e, portanto, não há possibilidade de diferentes comprimentos de fatia). As dimensões restantes podem ser irregulares ou uniformes. Por exemplo, você pode armazenar a palavra embeddings para cada palavra em um lote de frases usando um tensor irregular com forma [num_sentences, (num_words), embedding_size] , onde os parênteses ao redor (num_words) indicam que a dimensão está irregular.

Embeddings de palavras usando um tensor irregular

Os tensores irregulares podem ter várias dimensões irregulares. Por exemplo, você pode armazenar um lote de documentos de texto estruturado usando um tensor com forma [num_documents, (num_paragraphs), (num_sentences), (num_words)] (onde novamente os parênteses são usados ​​para indicar dimensões irregulares).

Tal como acontece com tf.Tensor , a classificação de um tensor irregular é o seu número total de dimensões (incluindo dimensões irregulares e uniformes). Um tensor potencialmente irregular é um valor que pode ser tf.Tensor outf.RaggedTensor .

Ao descrever a forma de um RaggedTensor, as dimensões irregulares são convencionalmente indicadas entre parênteses. Por exemplo, como você viu acima, a forma de um 3D RaggedTensor que armazena embeddings de palavras para cada palavra em um lote de frases pode ser escrita como [num_sentences, (num_words), embedding_size] .

O atributo RaggedTensor.shape retorna um tf.TensorShape para um tensor irregular onde as dimensões irregulares têm tamanho None :

tf.ragged.constant([["Hi"], ["How", "are", "you"]]).shape
TensorShape([2, None])

O método tf.RaggedTensor.bounding_shape pode ser usado para encontrar uma forma de limite estreito para um determinado RaggedTensor :

print(tf.ragged.constant([["Hi"], ["How", "are", "you"]]).bounding_shape())
tf.Tensor([2 3], shape=(2,), dtype=int64)

Ragged vs sparse

Um tensor irregular não deve ser considerado um tipo de tensor esparso. Em particular, tensores esparsos são codificações eficientes para tf.Tensor que modelam os mesmos dados em um formato compacto; mas o tensor irregular é uma extensão de tf.Tensor que modela uma classe expandida de dados. Essa diferença é crucial ao definir as operações:

  • Aplicar um op a um tensor esparso ou denso deve sempre dar o mesmo resultado.
  • Aplicar um op a um tensor irregular ou esparso pode dar resultados diferentes.

Como um exemplo ilustrativo, considere como as operações de array, como concat , stack e tile são definidas para tensores irregulares vs. tensores esparsos. A concatenação de tensores irregulares une cada linha para formar uma única linha com o comprimento combinado:

Concatenando tensores irregulares

ragged_x = tf.ragged.constant([["John"], ["a", "big", "dog"], ["my", "cat"]])
ragged_y = tf.ragged.constant([["fell", "asleep"], ["barked"], ["is", "fuzzy"]])
print(tf.concat([ragged_x, ragged_y], axis=1))
<tf.RaggedTensor [[b'John', b'fell', b'asleep'], [b'a', b'big', b'dog', b'barked'], [b'my', b'cat', b'is', b'fuzzy']]>

No entanto, concatenar tensores esparsos é equivalente a concatenar os tensores densos correspondentes, conforme ilustrado pelo exemplo a seguir (onde Ø indica valores ausentes):

Tensores esparsos de concatenação

sparse_x = ragged_x.to_sparse()
sparse_y = ragged_y.to_sparse()
sparse_result = tf.sparse.concat(sp_inputs=[sparse_x, sparse_y], axis=1)
print(tf.sparse.to_dense(sparse_result, ''))
tf.Tensor(
[[b'John' b'' b'' b'fell' b'asleep']
 [b'a' b'big' b'dog' b'barked' b'']
 [b'my' b'cat' b'' b'is' b'fuzzy']], shape=(3, 5), dtype=string)

Para outro exemplo de por que essa distinção é importante, considere a definição de “o valor médio de cada linha” para uma tf.reduce_mean como tf.reduce_mean . Para um tensor irregular, o valor médio de uma linha é a soma dos valores da linha dividida pela largura da linha. Mas para um tensor esparso, o valor médio de uma linha é a soma dos valores da linha dividida pela largura geral do tensor esparso (que é maior ou igual à largura da linha mais longa).

APIs TensorFlow

Keras

tf.keras é a API de alto nível do TensorFlow para construir e treinar modelos de aprendizado profundo. Tensores Ragged podem ser passados ​​como entradas para um modelo Keras definindo ragged=True em tf.keras.Input ou tf.keras.layers.InputLayer . Tensores irregulares também podem ser passados ​​entre camadas Keras e retornados por modelos Keras. O exemplo a seguir mostra um modelo LSTM de brinquedo que é treinado usando tensores irregulares.

# Task: predict whether each sentence is a question or not.
sentences = tf.constant(
    ['What makes you think she is a witch?',
     'She turned me into a newt.',
     'A newt?',
     'Well, I got better.'])
is_question = tf.constant([True, False, True, False])

# Preprocess the input strings.
hash_buckets = 1000
words = tf.strings.split(sentences, ' ')
hashed_words = tf.strings.to_hash_bucket_fast(words, hash_buckets)

# Build the Keras model.
keras_model = tf.keras.Sequential([
    tf.keras.layers.Input(shape=[None], dtype=tf.int64, ragged=True),
    tf.keras.layers.Embedding(hash_buckets, 16),
    tf.keras.layers.LSTM(32, use_bias=False),
    tf.keras.layers.Dense(32),
    tf.keras.layers.Activation(tf.nn.relu),
    tf.keras.layers.Dense(1)
])

keras_model.compile(loss='binary_crossentropy', optimizer='rmsprop')
keras_model.fit(hashed_words, is_question, epochs=5)
print(keras_model.predict(hashed_words))
WARNING:tensorflow:Layer lstm will not use cuDNN kernels since it doesn't meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
Epoch 1/5
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/indexed_slices.py:449: UserWarning: Converting sparse IndexedSlices(IndexedSlices(indices=Tensor("gradient_tape/sequential/lstm/RaggedToTensor/boolean_mask_1/GatherV2:0", shape=(None,), dtype=int32), values=Tensor("gradient_tape/sequential/lstm/RaggedToTensor/boolean_mask/GatherV2:0", shape=(None, 16), dtype=float32), dense_shape=Tensor("gradient_tape/sequential/lstm/RaggedToTensor/Shape:0", shape=(2,), dtype=int32))) to a dense Tensor of unknown shape. This may consume a large amount of memory.
  "shape. This may consume a large amount of memory." % value)
1/1 [==============================] - 2s 2s/step - loss: 7.7128
Epoch 2/5
1/1 [==============================] - 0s 18ms/step - loss: 7.7125
Epoch 3/5
1/1 [==============================] - 0s 16ms/step - loss: 7.7125
Epoch 4/5
1/1 [==============================] - 0s 17ms/step - loss: 7.7125
Epoch 5/5
1/1 [==============================] - 0s 15ms/step - loss: 7.7125
[[-0.00928741]
 [-0.01342061]
 [-0.00900517]
 [-0.01094673]]

tf.Exemplo

tf.Example é uma codificação protobuf padrão para dados do TensorFlow. Os dados codificados com tf.Example s geralmente incluem recursos de comprimento variável. Por exemplo, o código a seguir define um lote de quatro mensagens tf.Example com diferentes comprimentos de recursos:

import google.protobuf.text_format as pbtext

def build_tf_example(s):
  return pbtext.Merge(s, tf.train.Example()).SerializeToString()

example_batch = [
  build_tf_example(r'''
    features {
      feature {key: "colors" value {bytes_list {value: ["red", "blue"]} } }
      feature {key: "lengths" value {int64_list {value: [7]} } } }'''),
  build_tf_example(r'''
    features {
      feature {key: "colors" value {bytes_list {value: ["orange"]} } }
      feature {key: "lengths" value {int64_list {value: []} } } }'''),
  build_tf_example(r'''
    features {
      feature {key: "colors" value {bytes_list {value: ["black", "yellow"]} } }
      feature {key: "lengths" value {int64_list {value: [1, 3]} } } }'''),
  build_tf_example(r'''
    features {
      feature {key: "colors" value {bytes_list {value: ["green"]} } }
      feature {key: "lengths" value {int64_list {value: [3, 5, 2]} } } }''')]

Você pode analisar esses dados codificados usando tf.io.parse_example , que pega um tensor de strings serializadas e um dicionário de especificação de recurso, e retorna um dicionário de mapeamento de nomes de recursos para tensores. Para ler os recursos de comprimento variável em tensores irregulares, basta usar tf.io.RaggedFeature no dicionário de especificação de recursos:

feature_specification = {
    'colors': tf.io.RaggedFeature(tf.string),
    'lengths': tf.io.RaggedFeature(tf.int64),
}
feature_tensors = tf.io.parse_example(example_batch, feature_specification)
for name, value in feature_tensors.items():
  print("{}={}".format(name, value))
colors=<tf.RaggedTensor [[b'red', b'blue'], [b'orange'], [b'black', b'yellow'], [b'green']]>
lengths=<tf.RaggedTensor [[7], [], [1, 3], [3, 5, 2]]>

tf.io.RaggedFeature também pode ser usado para ler recursos com várias dimensões irregulares. Para obter detalhes, consulte a documentação da API .

Conjuntos de dados

tf.data é uma API que permite construir pipelines de entrada complexos a partir de peças simples e reutilizáveis. Sua estrutura de dados principal étf.data.Dataset , que representa uma sequência de elementos, em que cada elemento consiste em um ou mais componentes.

# Helper function used to print datasets in the examples below.
def print_dictionary_dataset(dataset):
  for i, element in enumerate(dataset):
    print("Element {}:".format(i))
    for (feature_name, feature_value) in element.items():
      print('{:>14} = {}'.format(feature_name, feature_value))

Construindo conjuntos de dados com tensores irregulares

Conjuntos de dados podem ser construídos a partir de tensores irregulares usando os mesmos métodos usados ​​para construí-los a partir de tf.Tensor s ou array NumPy s, como Dataset.from_tensor_slices :

dataset = tf.data.Dataset.from_tensor_slices(feature_tensors)
print_dictionary_dataset(dataset)
Element 0:
        colors = [b'red' b'blue']
       lengths = [7]
Element 1:
        colors = [b'orange']
       lengths = []
Element 2:
        colors = [b'black' b'yellow']
       lengths = [1 3]
Element 3:
        colors = [b'green']
       lengths = [3 5 2]

Batching and unbatching Datasets with ragged tensors

Conjuntos de dados com tensores irregulares podem ser agrupados (o que combina n elementos consecutivos em um único elemento) usando o método Dataset.batch .

batched_dataset = dataset.batch(2)
print_dictionary_dataset(batched_dataset)
Element 0:
        colors = <tf.RaggedTensor [[b'red', b'blue'], [b'orange']]>
       lengths = <tf.RaggedTensor [[7], []]>
Element 1:
        colors = <tf.RaggedTensor [[b'black', b'yellow'], [b'green']]>
       lengths = <tf.RaggedTensor [[1, 3], [3, 5, 2]]>

Por outro lado, um conjunto de dados em lote pode ser transformado em um conjunto de dados simples usando Dataset.unbatch .

unbatched_dataset = batched_dataset.unbatch()
print_dictionary_dataset(unbatched_dataset)
Element 0:
        colors = [b'red' b'blue']
       lengths = [7]
Element 1:
        colors = [b'orange']
       lengths = []
Element 2:
        colors = [b'black' b'yellow']
       lengths = [1 3]
Element 3:
        colors = [b'green']
       lengths = [3 5 2]

Conjuntos de dados em lote com tensores não irregulares de comprimento variável

Se você tiver um conjunto de dados que contém tensores não ragged e os comprimentos dos tensores variam entre os elementos, você pode agrupar esses tensores não ragged em tensores ragged aplicando a transformação dense_to_ragged_batch :

non_ragged_dataset = tf.data.Dataset.from_tensor_slices([1, 5, 3, 2, 8])
non_ragged_dataset = non_ragged_dataset.map(tf.range)
batched_non_ragged_dataset = non_ragged_dataset.apply(
    tf.data.experimental.dense_to_ragged_batch(2))
for element in batched_non_ragged_dataset:
  print(element)
<tf.RaggedTensor [[0], [0, 1, 2, 3, 4]]>
<tf.RaggedTensor [[0, 1, 2], [0, 1]]>
<tf.RaggedTensor [[0, 1, 2, 3, 4, 5, 6, 7]]>

Transformando conjuntos de dados com tensores irregulares

Você também pode criar ou transformar tensores irregulares em conjuntos de dados usando Dataset.map :

def transform_lengths(features):
  return {
      'mean_length': tf.math.reduce_mean(features['lengths']),
      'length_ranges': tf.ragged.range(features['lengths'])}
transformed_dataset = dataset.map(transform_lengths)
print_dictionary_dataset(transformed_dataset)
Element 0:
   mean_length = 7
 length_ranges = <tf.RaggedTensor [[0, 1, 2, 3, 4, 5, 6]]>
Element 1:
   mean_length = 0
 length_ranges = <tf.RaggedTensor []>
Element 2:
   mean_length = 2
 length_ranges = <tf.RaggedTensor [[0], [0, 1, 2]]>
Element 3:
   mean_length = 3
 length_ranges = <tf.RaggedTensor [[0, 1, 2], [0, 1, 2, 3, 4], [0, 1]]>

tf.function

tf.function é um decorador que pré-calcula os gráficos do TensorFlow para funções Python, o que pode melhorar substancialmente o desempenho do código do TensorFlow. Tensores irregulares podem ser usados ​​transparentemente com funções @tf.function com @tf.function . Por exemplo, a seguinte função funciona com tensores irregulares e não irregulares:

@tf.function
def make_palindrome(x, axis):
  return tf.concat([x, tf.reverse(x, [axis])], axis)
make_palindrome(tf.constant([[1, 2], [3, 4], [5, 6]]), axis=1)
<tf.Tensor: shape=(3, 4), dtype=int32, numpy=
array([[1, 2, 2, 1],
       [3, 4, 4, 3],
       [5, 6, 6, 5]], dtype=int32)>
make_palindrome(tf.ragged.constant([[1, 2], [3], [4, 5, 6]]), axis=1)
<tf.RaggedTensor [[1, 2, 2, 1], [3, 3], [4, 5, 6, 6, 5, 4]]>

Se você deseja especificar explicitamente o input_signature para o tf.function , então você pode fazer isso usando tf.RaggedTensorSpec .

@tf.function(
    input_signature=[tf.RaggedTensorSpec(shape=[None, None], dtype=tf.int32)])
def max_and_min(rt):
  return (tf.math.reduce_max(rt, axis=-1), tf.math.reduce_min(rt, axis=-1))

max_and_min(tf.ragged.constant([[1, 2], [3], [4, 5, 6]]))
(<tf.Tensor: shape=(3,), dtype=int32, numpy=array([2, 3, 6], dtype=int32)>,
 <tf.Tensor: shape=(3,), dtype=int32, numpy=array([1, 3, 4], dtype=int32)>)

Funções concretas

Funções concretas encapsulam gráficos traçados individuais que são construídos por tf.function . Tensores irregulares podem ser usados ​​de forma transparente com funções concretas.

@tf.function
def increment(x):
  return x + 1

rt = tf.ragged.constant([[1, 2], [3], [4, 5, 6]])
cf = increment.get_concrete_function(rt)
print(cf(rt))
<tf.RaggedTensor [[2, 3], [4], [5, 6, 7]]>

SavedModels

Um SavedModel é um programa TensorFlow serializado, incluindo pesos e computação. Ele pode ser construído a partir de um modelo Keras ou de um modelo personalizado. Em ambos os casos, tensores ragged podem ser usados ​​transparentemente com as funções e métodos definidos por um SavedModel.

Exemplo: salvar um modelo Keras

import tempfile

keras_module_path = tempfile.mkdtemp()
tf.saved_model.save(keras_model, keras_module_path)
imported_model = tf.saved_model.load(keras_module_path)
imported_model(hashed_words)
WARNING:absl:Function `_wrapped_model` contains input name(s) args_0 with unsupported characters which will be renamed to args_0_1 in the SavedModel.
WARNING:tensorflow:FOR KERAS USERS: The object that you are saving contains one or more Keras models or layers. If you are loading the SavedModel with `tf.keras.models.load_model`, continue reading (otherwise, you may ignore the following instructions). Please change your code to save with `tf.keras.models.save_model` or `model.save`, and confirm that the file "keras.metadata" exists in the export directory. In the future, Keras will only load the SavedModels that have this file. In other words, `tf.saved_model.save` will no longer write SavedModels that can be recovered as Keras models (this will apply in TF 2.5).

FOR DEVS: If you are overwriting _tracking_metadata in your class, this property has been used to save metadata in the SavedModel. The metadta field will be deprecated soon, so please move the metadata to a different file.
WARNING:tensorflow:FOR KERAS USERS: The object that you are saving contains one or more Keras models or layers. If you are loading the SavedModel with `tf.keras.models.load_model`, continue reading (otherwise, you may ignore the following instructions). Please change your code to save with `tf.keras.models.save_model` or `model.save`, and confirm that the file "keras.metadata" exists in the export directory. In the future, Keras will only load the SavedModels that have this file. In other words, `tf.saved_model.save` will no longer write SavedModels that can be recovered as Keras models (this will apply in TF 2.5).

FOR DEVS: If you are overwriting _tracking_metadata in your class, this property has been used to save metadata in the SavedModel. The metadta field will be deprecated soon, so please move the metadata to a different file.
INFO:tensorflow:Assets written to: /tmp/tmp087u1gje/assets
INFO:tensorflow:Assets written to: /tmp/tmp087u1gje/assets
<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[-0.00928741],
       [-0.01342061],
       [-0.00900517],
       [-0.01094673]], dtype=float32)>

Exemplo: salvar um modelo personalizado

class CustomModule(tf.Module):
  def __init__(self, variable_value):
    super(CustomModule, self).__init__()
    self.v = tf.Variable(variable_value)

  @tf.function
  def grow(self, x):
    return x * self.v

module = CustomModule(100.0)

# Before saving a custom model, you must ensure that concrete functions are
# built for each input signature that you will need.
module.grow.get_concrete_function(tf.RaggedTensorSpec(shape=[None, None],
                                                      dtype=tf.float32))

custom_module_path = tempfile.mkdtemp()
tf.saved_model.save(module, custom_module_path)
imported_model = tf.saved_model.load(custom_module_path)
imported_model.grow(tf.ragged.constant([[1.0, 4.0, 3.0], [2.0]]))
INFO:tensorflow:Assets written to: /tmp/tmpm2n_d_rz/assets
INFO:tensorflow:Assets written to: /tmp/tmpm2n_d_rz/assets
<tf.RaggedTensor [[100.0, 400.0, 300.0], [200.0]]>

Operadores sobrecarregados

A classe RaggedTensor sobrecarrega os operadores aritméticos e de comparação padrão do Python, facilitando a realização de matemática elementar básica:

x = tf.ragged.constant([[1, 2], [3], [4, 5, 6]])
y = tf.ragged.constant([[1, 1], [2], [3, 3, 3]])
print(x + y)
<tf.RaggedTensor [[2, 3], [5], [7, 8, 9]]>

Como os operadores sobrecarregados realizam cálculos de elemento a elemento, as entradas para todas as operações binárias devem ter a mesma forma ou ser transmitidas para a mesma forma. No caso de transmissão mais simples, um único escalar é combinado elemento a elemento com cada valor em um tensor irregular:

x = tf.ragged.constant([[1, 2], [3], [4, 5, 6]])
print(x + 3)
<tf.RaggedTensor [[4, 5], [6], [7, 8, 9]]>

Para uma discussão de casos mais avançados, verifique a seção sobre Transmissão .

Tensores irregulares sobrecarregam o mesmo conjunto de operadores que Tensor normais: os operadores unários - , ~ e abs() ; e os operadores binários + , - , * , / , // , % , ** , & , | , ^ , == , < , <= , > e >= .

Indexando

Os tensores irregulares oferecem suporte à indexação no estilo Python, incluindo indexação e divisão multidimensional. Os exemplos a seguir demonstram a indexação de tensor irregular com um tensor irregular 2D e 3D.

Exemplos de indexação: tensor irregular 2D

queries = tf.ragged.constant(
    [['Who', 'is', 'George', 'Washington'],
     ['What', 'is', 'the', 'weather', 'tomorrow'],
     ['Goodnight']])
print(queries[1])                   # A single query
tf.Tensor([b'What' b'is' b'the' b'weather' b'tomorrow'], shape=(5,), dtype=string)
print(queries[1, 2])                # A single word
tf.Tensor(b'the', shape=(), dtype=string)
print(queries[1:])                  # Everything but the first row
<tf.RaggedTensor [[b'What', b'is', b'the', b'weather', b'tomorrow'], [b'Goodnight']]>
print(queries[:, :3])               # The first 3 words of each query
<tf.RaggedTensor [[b'Who', b'is', b'George'], [b'What', b'is', b'the'], [b'Goodnight']]>
print(queries[:, -2:])              # The last 2 words of each query
<tf.RaggedTensor [[b'George', b'Washington'], [b'weather', b'tomorrow'], [b'Goodnight']]>

Exemplos de indexação: tensor 3D irregular

rt = tf.ragged.constant([[[1, 2, 3], [4]],
                         [[5], [], [6]],
                         [[7]],
                         [[8, 9], [10]]])
print(rt[1])                        # Second row (2D RaggedTensor)
<tf.RaggedTensor [[5], [], [6]]>
print(rt[3, 0])                     # First element of fourth row (1D Tensor)
tf.Tensor([8 9], shape=(2,), dtype=int32)
print(rt[:, 1:3])                   # Items 1-3 of each row (3D RaggedTensor)
<tf.RaggedTensor [[[4]], [[], [6]], [], [[10]]]>
print(rt[:, -1:])                   # Last item of each row (3D RaggedTensor)
<tf.RaggedTensor [[[4]], [[6]], [[7]], [[10]]]>

RaggedTensor suporte à indexação e RaggedTensor multidimensional com uma restrição: indexar em uma dimensão irregular não é permitida. Este caso é problemático porque o valor indicado pode existir em algumas linhas, mas não em outras. Nesses casos, não é óbvio se você deve (1) aumentar um IndexError ; (2) use um valor padrão; ou (3) pule esse valor e retorne um tensor com menos linhas do que você começou. Seguindo os princípios orientadores do Python ("Diante da ambigüidade, recuse a tentação de adivinhar"), essa operação não é permitida no momento.

Conversão de tipo de tensor

A classe RaggedTensor define métodos que podem ser usados ​​para converter entre RaggedTensor tf.Tensor s ou tf.SparseTensors :

ragged_sentences = tf.ragged.constant([
    ['Hi'], ['Welcome', 'to', 'the', 'fair'], ['Have', 'fun']])
# RaggedTensor -> Tensor
print(ragged_sentences.to_tensor(default_value='', shape=[None, 10]))
tf.Tensor(
[[b'Hi' b'' b'' b'' b'' b'' b'' b'' b'' b'']
 [b'Welcome' b'to' b'the' b'fair' b'' b'' b'' b'' b'' b'']
 [b'Have' b'fun' b'' b'' b'' b'' b'' b'' b'' b'']], shape=(3, 10), dtype=string)
# Tensor -> RaggedTensor
x = [[1, 3, -1, -1], [2, -1, -1, -1], [4, 5, 8, 9]]
print(tf.RaggedTensor.from_tensor(x, padding=-1))
<tf.RaggedTensor [[1, 3], [2], [4, 5, 8, 9]]>
#RaggedTensor -> SparseTensor
print(ragged_sentences.to_sparse())
SparseTensor(indices=tf.Tensor(
[[0 0]
 [1 0]
 [1 1]
 [1 2]
 [1 3]
 [2 0]
 [2 1]], shape=(7, 2), dtype=int64), values=tf.Tensor([b'Hi' b'Welcome' b'to' b'the' b'fair' b'Have' b'fun'], shape=(7,), dtype=string), dense_shape=tf.Tensor([3 4], shape=(2,), dtype=int64))
# SparseTensor -> RaggedTensor
st = tf.SparseTensor(indices=[[0, 0], [2, 0], [2, 1]],
                     values=['a', 'b', 'c'],
                     dense_shape=[3, 3])
print(tf.RaggedTensor.from_sparse(st))
<tf.RaggedTensor [[b'a'], [], [b'b', b'c']]>

Avaliando tensores irregulares

Para acessar os valores em um tensor irregular, você pode:

  1. Use tf.RaggedTensor.to_list para converter o tensor irregular em uma lista Python aninhada.
  2. Use tf.RaggedTensor.numpy para converter o tensor irregular em uma matriz NumPy cujos valores são matrizes NumPy aninhadas.
  3. Decompõe-se o tensor de irregular nos seus componentes, utilizando os tf.RaggedTensor.values e tf.RaggedTensor.row_splits propriedades, ou por meio de paritioning fileira como tf.RaggedTensor.row_lengths e tf.RaggedTensor.value_rowids .
  4. Use a indexação Python para selecionar valores do tensor irregular.
rt = tf.ragged.constant([[1, 2], [3, 4, 5], [6], [], [7]])
print("Python list:", rt.to_list())
print("NumPy array:", rt.numpy())
print("Values:", rt.values.numpy())
print("Splits:", rt.row_splits.numpy())
print("Indexed value:", rt[1].numpy())
Python list: [[1, 2], [3, 4, 5], [6], [], [7]]
NumPy array: [array([1, 2], dtype=int32) array([3, 4, 5], dtype=int32)
 array([6], dtype=int32) array([], dtype=int32) array([7], dtype=int32)]
Values: [1 2 3 4 5 6 7]
Splits: [0 2 5 6 6 7]
Indexed value: [3 4 5]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py:2053: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  return np.array(rows)

Transmissão

Broadcasting é o processo de fazer com que tensores com formatos diferentes tenham formatos compatíveis para operações de elemento a elemento. Para obter mais informações sobre a transmissão, consulte:

Os passos básicos para transmitir duas entradas x e y ter formas compatíveis são:

  1. Se x e y não têm o mesmo número de dimensões, em seguida, adicionar dimensões exteriores (com tamanho 1) até que eles fazem.

  2. Para cada dimensão em que x e y têm tamanhos diferentes:

  • Se x ou y tiverem tamanho 1 na dimensão d , repita seus valores na dimensão d para corresponder ao tamanho da outra entrada.
  • Caso contrário, gere uma exceção ( x e y não são compatíveis com transmissão).

Onde o tamanho de um tensor em uma dimensão uniforme é um único número (o tamanho das fatias nessa dimensão); e o tamanho de um tensor em uma dimensão irregular é uma lista de comprimentos de fatia (para todas as fatias nessa dimensão).

Exemplos de transmissão

# x       (2D ragged):  2 x (num_rows)
# y       (scalar)
# result  (2D ragged):  2 x (num_rows)
x = tf.ragged.constant([[1, 2], [3]])
y = 3
print(x + y)
<tf.RaggedTensor [[4, 5], [6]]>
# x         (2d ragged):  3 x (num_rows)
# y         (2d tensor):  3 x          1
# Result    (2d ragged):  3 x (num_rows)
x = tf.ragged.constant(
   [[10, 87, 12],
    [19, 53],
    [12, 32]])
y = [[1000], [2000], [3000]]
print(x + y)
<tf.RaggedTensor [[1010, 1087, 1012], [2019, 2053], [3012, 3032]]>
# x      (3d ragged):  2 x (r1) x 2
# y      (2d ragged):         1 x 1
# Result (3d ragged):  2 x (r1) x 2
x = tf.ragged.constant(
    [[[1, 2], [3, 4], [5, 6]],
     [[7, 8]]],
    ragged_rank=1)
y = tf.constant([[10]])
print(x + y)
<tf.RaggedTensor [[[11, 12], [13, 14], [15, 16]], [[17, 18]]]>
# x      (3d ragged):  2 x (r1) x (r2) x 1
# y      (1d tensor):                    3
# Result (3d ragged):  2 x (r1) x (r2) x 3
x = tf.ragged.constant(
    [
        [
            [[1], [2]],
            [],
            [[3]],
            [[4]],
        ],
        [
            [[5], [6]],
            [[7]]
        ]
    ],
    ragged_rank=2)
y = tf.constant([10, 20, 30])
print(x + y)
<tf.RaggedTensor [[[[11, 21, 31], [12, 22, 32]], [], [[13, 23, 33]], [[14, 24, 34]]], [[[15, 25, 35], [16, 26, 36]], [[17, 27, 37]]]]>

Aqui estão alguns exemplos de formas que não transmitem:

# x      (2d ragged): 3 x (r1)
# y      (2d tensor): 3 x    4  # trailing dimensions do not match
x = tf.ragged.constant([[1, 2], [3, 4, 5, 6], [7]])
y = tf.constant([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
try:
  x + y
except tf.errors.InvalidArgumentError as exception:
  print(exception)
Expected 'tf.Tensor(False, shape=(), dtype=bool)' to be true. Summarized data: b'Unable to broadcast: dimension size mismatch in dimension'
1
b'lengths='
4
b'dim_size='
2, 4, 1
# x      (2d ragged): 3 x (r1)
# y      (2d ragged): 3 x (r2)  # ragged dimensions do not match.
x = tf.ragged.constant([[1, 2, 3], [4], [5, 6]])
y = tf.ragged.constant([[10, 20], [30, 40], [50]])
try:
  x + y
except tf.errors.InvalidArgumentError as exception:
  print(exception)
Expected 'tf.Tensor(False, shape=(), dtype=bool)' to be true. Summarized data: b'Unable to broadcast: dimension size mismatch in dimension'
1
b'lengths='
2, 2, 1
b'dim_size='
3, 1, 2
# x      (3d ragged): 3 x (r1) x 2
# y      (3d ragged): 3 x (r1) x 3  # trailing dimensions do not match
x = tf.ragged.constant([[[1, 2], [3, 4], [5, 6]],
                        [[7, 8], [9, 10]]])
y = tf.ragged.constant([[[1, 2, 0], [3, 4, 0], [5, 6, 0]],
                        [[7, 8, 0], [9, 10, 0]]])
try:
  x + y
except tf.errors.InvalidArgumentError as exception:
  print(exception)
Expected 'tf.Tensor(False, shape=(), dtype=bool)' to be true. Summarized data: b'Unable to broadcast: dimension size mismatch in dimension'
2
b'lengths='
3, 3, 3, 3, 3
b'dim_size='
2, 2, 2, 2, 2

Codificação RaggedTensor

Os tensores Ragged são codificados usando a classe RaggedTensor . Internamente, cada RaggedTensor consiste em:

  • Um tensor de values , que concatena as linhas de comprimento variável em uma lista achatada.
  • Um row_partition , que indica como esses valores achatados são divididos em linhas.

Codificação RaggedTensor

O row_partition pode ser armazenado usando quatro codificações diferentes:

  • row_splits é um vetor inteiro que especifica os pontos de divisão entre as linhas.
  • value_rowids é um vetor inteiro que especifica o índice da linha para cada valor.
  • row_lengths é um vetor inteiro que especifica o comprimento de cada linha.
  • uniform_row_length é um escalar inteiro que especifica um único comprimento para todas as linhas.

codificações row_partition

Um inteiro escalares nrows também pode ser incluído no row_partition codificação de conta para as linhas de arrasto da vazias com value_rowids ou linhas vazias com uniform_row_length .

rt = tf.RaggedTensor.from_row_splits(
    values=[3, 1, 4, 1, 5, 9, 2],
    row_splits=[0, 4, 4, 6, 7])
print(rt)
<tf.RaggedTensor [[3, 1, 4, 1], [], [5, 9], [2]]>

A escolha de qual codificação usar para partições de linha é gerenciada internamente por tensores irregulares para melhorar a eficiência em alguns contextos. Em particular, algumas das vantagens e desvantagens dos diferentes esquemas de particionamento de linha são:

  • Indexação eficiente : a codificação row_splits permite indexação em tempo constante e row_splits em tensores irregulares.
  • Concatenação eficiente : a codificação de row_lengths é mais eficiente ao concatenar tensores irregulares, uma vez que os comprimentos de linha não mudam quando dois tensores são concatenados juntos.
  • Tamanho de codificação pequeno : a codificação value_rowids é mais eficiente ao armazenar tensores irregulares que possuem um grande número de linhas vazias, uma vez que o tamanho do tensor depende apenas do número total de valores. Por outro lado, os row_splits e row_lengths codificações são mais eficientes quando armazenar tensores irregulares com linhas mais longas, uma vez que exigem apenas um valor escalar para cada linha.
  • Compatibilidade : o esquema value_rowids corresponde ao formato de segmentação usado por operações, como tf.segment_sum . O esquema row_limits corresponde ao formato usado por ops, como tf.sequence_mask .
  • Dimensões uniformes : Como discutido abaixo, a codificação uniform_row_length é usada para codificar tensores irregulares com dimensões uniformes.

Múltiplas dimensões irregulares

Um tensor irregular com várias dimensões irregulares é codificado usando um RaggedTensor aninhado para o tensor de values . Cada RaggedTensor aninhado adiciona uma única dimensão irregular.

Codificação de um tensor irregular com múltiplas dimensões irregulares (classificação 2)

rt = tf.RaggedTensor.from_row_splits(
    values=tf.RaggedTensor.from_row_splits(
        values=[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
        row_splits=[0, 3, 3, 5, 9, 10]),
    row_splits=[0, 1, 1, 5])
print(rt)
print("Shape: {}".format(rt.shape))
print("Number of partitioned dimensions: {}".format(rt.ragged_rank))
<tf.RaggedTensor [[[10, 11, 12]], [], [[], [13, 14], [15, 16, 17, 18], [19]]]>
Shape: (3, None, None)
Number of partitioned dimensions: 2

A função de fábrica tf.RaggedTensor.from_nested_row_splits pode ser usada para construir um RaggedTensor com múltiplas dimensões irregulares diretamente, fornecendo uma lista de tensores row_splits :

rt = tf.RaggedTensor.from_nested_row_splits(
    flat_values=[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
    nested_row_splits=([0, 1, 1, 5], [0, 3, 3, 5, 9, 10]))
print(rt)
<tf.RaggedTensor [[[10, 11, 12]], [], [[], [13, 14], [15, 16, 17, 18], [19]]]>

Classificação irregular e valores fixos

A classificação irregular de um tensor irregular é o número de vezes que o tensor de values subjacentes foi particionado (ou seja, a profundidade de RaggedTensor objetos RaggedTensor ). O tensor de values mais internos é conhecido como flat_values . No exemplo a seguir, conversations tem ragged_rank = 3 e seu flat_values é um Tensor 1D com 24 strings:

# shape = [batch, (paragraph), (sentence), (word)]
conversations = tf.ragged.constant(
    [[[["I", "like", "ragged", "tensors."]],
      [["Oh", "yeah?"], ["What", "can", "you", "use", "them", "for?"]],
      [["Processing", "variable", "length", "data!"]]],
     [[["I", "like", "cheese."], ["Do", "you?"]],
      [["Yes."], ["I", "do."]]]])
conversations.shape
TensorShape([2, None, None, None])
assert conversations.ragged_rank == len(conversations.nested_row_splits)
conversations.ragged_rank  # Number of partitioned dimensions.
3
conversations.flat_values.numpy()
array([b'I', b'like', b'ragged', b'tensors.', b'Oh', b'yeah?', b'What',
       b'can', b'you', b'use', b'them', b'for?', b'Processing',
       b'variable', b'length', b'data!', b'I', b'like', b'cheese.', b'Do',
       b'you?', b'Yes.', b'I', b'do.'], dtype=object)

Dimensões internas uniformes

Tensores irregulares com dimensões internas uniformes são codificados usando um tf.Tensor multidimensional para flat_values ​​(ou seja, os values mais internos).

Codificação de tensores irregulares com dimensões internas uniformes

rt = tf.RaggedTensor.from_row_splits(
    values=[[1, 3], [0, 0], [1, 3], [5, 3], [3, 3], [1, 2]],
    row_splits=[0, 3, 4, 6])
print(rt)
print("Shape: {}".format(rt.shape))
print("Number of partitioned dimensions: {}".format(rt.ragged_rank))
print("Flat values shape: {}".format(rt.flat_values.shape))
print("Flat values:\n{}".format(rt.flat_values))
<tf.RaggedTensor [[[1, 3], [0, 0], [1, 3]], [[5, 3]], [[3, 3], [1, 2]]]>
Shape: (3, None, 2)
Number of partitioned dimensions: 1
Flat values shape: (6, 2)
Flat values:
[[1 3]
 [0 0]
 [1 3]
 [5 3]
 [3 3]
 [1 2]]

Dimensões não internas uniformes

Tensores irregulares com dimensões não internas uniformes são codificados particionando linhas com uniform_row_length .

Codificação de tensores irregulares com dimensões não internas uniformes

rt = tf.RaggedTensor.from_uniform_row_length(
    values=tf.RaggedTensor.from_row_splits(
        values=[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
        row_splits=[0, 3, 5, 9, 10]),
    uniform_row_length=2)
print(rt)
print("Shape: {}".format(rt.shape))
print("Number of partitioned dimensions: {}".format(rt.ragged_rank))
<tf.RaggedTensor [[[10, 11, 12], [13, 14]], [[15, 16, 17, 18], [19]]]>
Shape: (2, 2, None)
Number of partitioned dimensions: 2