Esta página foi traduzida pela API Cloud Translation.
Switch to English

Tensores irregulares

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

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

Configuração

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 equivalente no TensorFlow das 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 ragged, 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 obter mais informações, consulte a seção sobre APIs do TensorFlow abaixo.

Assim como acontece com tensores normais, você pode usar a indexação do 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 elementwise. Para obter mais informações, consulte a seção 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 , você 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()
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 emparelhando 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

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 :

comprimento_da_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

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 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, consulte 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 & 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.10999689  0.10575606 -0.28228763  0.24919255]
 [-0.22778393  0.14891823  0.45986378 -0.3109092 ]
 [ 0.09017418 -0.02079154  0.23155184 -0.01072874]], shape=(3, 4), dtype=float32)

ragged_example

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 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, podemos 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.

sent_word_embed

Tensores irregulares podem ter múltiplas dimensões irregulares. Por exemplo, poderíamos 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 ou 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 vimos acima, a forma de um RaggedTensor 3-D 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, em que 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 sempre deve 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:

ragged_concat

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

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

sparse_concat

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 criar 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 as 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 kernel since it doesn't meet the cuDNN kernel criteria. It will use generic GPU kernel as fallback when running on GPU
Epoch 1/5

/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/framework/indexed_slices.py:432: UserWarning: Converting sparse IndexedSlices to a dense Tensor of unknown shape. This may consume a large amount of memory.
  "Converting sparse IndexedSlices to a dense Tensor of unknown shape. "

1/1 [==============================] - 0s 2ms/step - loss: 3.3884
Epoch 2/5
1/1 [==============================] - 0s 977us/step - loss: 2.0579
Epoch 3/5
1/1 [==============================] - 0s 1ms/step - loss: 2.0157
Epoch 4/5
1/1 [==============================] - 0s 988us/step - loss: 1.9858
Epoch 5/5
1/1 [==============================] - 0s 990us/step - loss: 1.9260
[[0.03027334]
 [0.01302399]
 [0.01826342]
 [0.00842744]]

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]} } } }''')]

Podemos 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, simplesmente usamos 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 numpy array 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

Tensores irregulares em conjuntos de dados também podem ser criados ou transformados 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 as 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 . Começando com TensorFlow 2.3 (e em tf-nightly ), tensores irregulares podem ser usados ​​de forma transparente com funções concretas.

# Preferred way to use ragged tensors with concrete functions (TF 2.3+):
try:
  @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))
except Exception as e:
  print(f"Not supported before TF 2.3: {type(e)}: {e}")
<tf.RaggedTensor [[2, 3], [4], [5, 6, 7]]>

Se você precisar usar tensores irregulares com funções concretas antes do TensorFlow 2.3, recomendamos decompor tensores irregulares em seus componentes ( values e row_splits ) e transmiti-los como argumentos separados.

# Backwards-compatible way to use ragged tensors with concrete functions:
@tf.function
def decomposed_ragged_increment(x_values, x_splits):
  x = tf.RaggedTensor.from_row_splits(x_values, x_splits)
  return x + 1

rt = tf.ragged.constant([[1, 2], [3], [4, 5, 6]])
cf = decomposed_ragged_increment.get_concrete_function(rt.values, rt.row_splits)
print(cf(rt.values, rt.row_splits))
<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:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: /tmp/tmpnlusyruh/assets

<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[0.03027334],
       [0.01302399],
       [0.01826342],
       [0.00842744]], 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, we must ensure that concrete functions are
# built for each input signature that we 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/tmphdjk87du/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]]>

Uma vez que 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, consulte a seção sobre Transmissão .

Tensores irregulares sobrecarregam o mesmo conjunto de operadores que os 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 multidimensionais. Os exemplos a seguir demonstram a indexação de tensor irregular com um tensor irregular 2-D e 3-D.

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 (2-D RaggedTensor)
<tf.RaggedTensor [[5], [], [6]]>

print(rt[3, 0])                     # First element of fourth row (1-D Tensor)
tf.Tensor([8 9], shape=(2,), dtype=int32)

print(rt[:, 1:3])                   # Items 1-3 of each row (3-D RaggedTensor)
<tf.RaggedTensor [[[4]], [[], [6]], [], [[10]]]>

print(rt[:, -1:])                   # Last item of each row (3-D RaggedTensor)
<tf.RaggedTensor [[[4]], [[6]], [[7]], [[10]]]>

RaggedTensor s suporta indexação e RaggedTensor multidimensionais, com uma restrição: indexar em uma dimensão irregular não é permitido. Este caso é problemático porque o valor indicado pode existir em algumas linhas, mas não em outras. Nesses casos, não é óbvio se devemos (1) aumentar um IndexError ; (2) usar um valor padrão; ou (3) pular esse valor e retornar um tensor com menos linhas do que começamos. Seguindo os princípios orientadores do Python ("Diante da ambigüidade, recuse a tentação de adivinhar"), atualmente não permitimos essa operação.

Conversão de tipo de tensor

A classe RaggedTensor define métodos que podem ser usados ​​para converter entre RaggedTensor s e 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. Decompõe-se o tensor de irregular nos seus componentes, utilizando os tf.RaggedTensor.values e tf.RaggedTensor.row_splits propriedades, ou fileiras paritioning métodos tais 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]

Transmissão

Broadcasting é o processo de fazer com que tensores com formatos diferentes tenham formatos compatíveis para operações elementar. Para obter mais informações sobre 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 tiverem o mesmo número de dimensões, adicione as 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, 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 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 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 nivelados são divididos em linhas.

ragged_encoding_2

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.

partição_encodings

Um número inteiro escalar nrows também pode ser incluído na codificação row_partition , para contabilizar linhas finais 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 ragged, 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 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.

ragged_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 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 os values subjacentes Tensor foram particionados (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).

uniform_inner

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 .

uniform_outer

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