Tensores irregulares

Veja no TensorFlow.org Executar no Google Colab Ver fonte no GitHubBaixar 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 devem. Os tensores irregulares são o equivalente do TensorFlow de listas de comprimento variável aninhadas. Eles facilitam o armazenamento e o processamento de dados com formas não uniformes, incluindo:

  • Recursos de duração 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

Os 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 strings (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], []]>

Há também vários métodos e operações 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 suportadas, 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 obter mais informações, consulte a seção sobre APIs do TensorFlow abaixo.

Assim como 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 os operadores aritméticos e de comparação do Python para realizar operações elementares. Para obter mais informações, consulte 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 elementwise para os valores de um RaggedTensor , poderá usar tf.ragged.map_flat_values , que recebe uma função mais um ou mais argumentos e aplica a função para transformar os valores 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], []]>

Os tensores irregulares podem ser convertidos em list Python aninhadas e array NumPy:

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:2063: 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 ragged é 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 emparelhando tensores de valores planos com tensores de particionamento de linha indicando como esses valores devem ser divididos em linhas, usando métodos de classe de 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 :

tensor de particionamento de linha value_rowids

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 :

tensor de particionamento de linha row_lengths

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ê conhece o índice onde cada linha começa e termina, então 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 classe tf.RaggedTensor para obter uma lista completa de métodos de fábrica.

O que você pode armazenar em um tensor irregular

Assim como acontece com os Tensor s normais, os valores em um RaggedTensor devem ser todos do 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 as operações usadas 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.14285272  0.02908629 -0.16327512 -0.14529026]
 [-0.4479212  -0.35615516  0.17110227  0.2522229 ]
 [-0.1987868  -0.13152348 -0.0325102   0.02125177]], 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, pois as fatias de coluna ( rt[0, :] , ..., rt[4, :] ) têm comprimentos diferentes. 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, pois 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 as incorporações de palavras 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.

Incorporações 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).

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

Ao descrever a forma de um RaggedTensor, as dimensões irregulares são convencionalmente indicadas colocando-as entre parênteses. Por exemplo, como você viu acima, a forma de um 3D RaggedTensor que armazena embeddings de 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 ragged onde as dimensões ragged 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 delimitadora apertada para um determinado RaggedTensor :

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

Esfarrapado vs esparso

Um tensor irregular não deve ser pensado como 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 do 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 exemplo ilustrativo, considere como as operações de matriz como concat , stack e tile são definidas para tensores irregulares versus 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):

Concatenando tensores esparsos

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 operação 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 do TensorFlow

Keras

tf.keras é a API de alto nível do TensorFlow para criar e treinar modelos de aprendizado profundo. Os tensores irregulares podem ser passados ​​como entradas para um modelo Keras definindo ragged=True em tf.keras.Input ou tf.keras.layers.InputLayer . Os 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.
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: 3.1269
Epoch 2/5
1/1 [==============================] - 0s 18ms/step - loss: 2.1197
Epoch 3/5
1/1 [==============================] - 0s 19ms/step - loss: 2.0196
Epoch 4/5
1/1 [==============================] - 0s 20ms/step - loss: 1.9371
Epoch 5/5
1/1 [==============================] - 0s 18ms/step - loss: 1.8857
[[0.02800461]
 [0.00945962]
 [0.02283431]
 [0.00252927]]

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 recurso:

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 recebe um tensor de strings serializadas e um dicionário de especificação de recursos e retorna um dicionário mapeando 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 criar pipelines de entrada complexos a partir de peças simples e reutilizáveis. Sua estrutura de dados central é tf.data.Dataset , que representa uma sequência de elementos, na qual 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))

Como criar conjuntos de dados com tensores irregulares

Conjuntos de dados podem ser construídos a partir de tensores irregulares usando os mesmos métodos que são usados ​​para construí-los de tf.Tensor s ou array s NumPy, 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]

Conjuntos de dados em lote e desmontagem com tensores irregulares

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 irregulares e os comprimentos dos tensores variam entre os elementos, você pode agrupar esses tensores não irregulares em tensores irregulares 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 Datasets 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.função

tf.function é um decorador que pré-computa gráficos do TensorFlow para funções do Python, o que pode melhorar substancialmente o desempenho do seu código do TensorFlow. Os tensores irregulares podem ser usados ​​de forma transparente com funções @tf.function -decorated. Por exemplo, a função a seguir 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)
2021-09-22 20:36:51.018367: W tensorflow/core/grappler/optimizers/loop_optimizer.cc:907] Skipping loop optimization for Merge node with control input: RaggedConcat/assert_equal_1/Assert/AssertGuard/branch_executed/_9
<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 , pode fazê-lo 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

As funções concretas encapsulam gráficos rastreados individuais que são construídos por tf.function . Os 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]]>

Modelos salvos

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 irregulares podem ser usados ​​de forma transparente 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)
2021-09-22 20:36:52.069689: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
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.
INFO:tensorflow:Assets written to: /tmp/tmp114axtt7/assets
INFO:tensorflow:Assets written to: /tmp/tmp114axtt7/assets
<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[0.02800461],
       [0.00945962],
       [0.02283431],
       [0.00252927]], 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/tmpnn4u8dy5/assets
INFO:tensorflow:Assets written to: /tmp/tmpnn4u8dy5/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 execuçã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 elementares, 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, consulte a seção sobre Transmissão .

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

Indexação

Os tensores irregulares suportam indexação no estilo Python, incluindo indexação e fatiamento 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 irregular 3D

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

O RaggedTensor suporta indexação e fatiamento multidimensional com uma restrição: a indexação 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) gerar um IndexError ; (2) usar 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 ("Em face da ambiguidade, 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 se 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 um array NumPy cujos valores são arrays NumPy aninhados.
  3. Decomponha o tensor irregular em seus componentes, usando as propriedades tf.RaggedTensor.values e tf.RaggedTensor.row_splits , ou métodos de particionamento de linha, como tf.RaggedTensor.row_lengths e tf.RaggedTensor.value_rowids .
  4. Use a indexação do 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:2063: 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 tensores com formas diferentes terem formas compatíveis para operações elementares. Para mais informações sobre transmissão, consulte:

As etapas básicas para transmitir duas entradas x e y para que tenham formas compatíveis são:

  1. Se x e y não tiverem o mesmo número de dimensões, adicione dimensões externas (com tamanho 1) até que tenham.

  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, lance uma exceção ( x e y não são compatíveis com broadcast).

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 fatias (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 irregulares 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

A row_partition pode ser armazenada 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 de 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 comprimento único para todas as linhas.

codificações de partição_linha

Um inteiro escalar nrows também pode ser incluído na codificação row_partition para contabilizar linhas vazias à direita 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 linhas são:

  • Indexação eficiente : A codificação row_splits permite indexação em tempo constante e fatiamento em tensores irregulares.
  • Concatenação eficiente : A codificação row_lengths é mais eficiente ao concatenar tensores irregulares, pois os comprimentos das linhas 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, pois o tamanho do tensor depende apenas do número total de valores. Por outro lado, as codificações row_splits e row_lengths são mais eficientes ao armazenar tensores irregulares com linhas mais longas, pois requerem 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 operações como tf.sequence_mask .
  • Dimensões uniformes : Conforme discutido abaixo, a codificação uniform_row_length é usada para codificar tensores irregulares com dimensões uniformes.

Várias 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 várias dimensões irregulares (rank 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 várias dimensões ragged 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 planos

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 aninhamento de objetos RaggedTensor ). O tensor de values mais internos é conhecido como flat_values . No exemplo a seguir, as conversations têm ragged_rank=3 e seus flat_values são 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

Os tensores irregulares com dimensões internas uniformes são codificados usando um tf.Tensor multidimensional para os 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

Os tensores irregulares com dimensões não internas uniformes são codificados por linhas de particionamento 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