Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/ragged_tensor.ipynb
25115 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

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

Configuração

!pip install --pre -U tensorflow import math import tensorflow as tf

Visão geral

Seus dados têm vários formatos diferentes: os tensores devem ter também. Os tensores irregulares (ragged tensors) são o equivalente do TensorFlow às listas aninhadas de comprimento variável. Eles facilitam o armazenamento e o processamento de dados com formatos não uniformes, incluindo:

  • Características de comprimento variável, como o elenco 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 compatíveis com mais de cem operações do TensorFlow, incluindo operações matemáticas (como tf.add e tf.reduce_mean), de arrays (como tf.concat e tf.tile), de manipulação de strings (como tf.strings.substr), de fluxo de controle (como tf.while_loop e tf.map_fn) e várias outras:

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

Também há diversos métodos e operações específicos para tensores irregulares, incluindo factory methods, métodos de conversão e operações de mapeamento de valores. Para uma lista de ops compatíveis, veja a documentação do pacote tf.ragged.

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

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

print(digits[0]) # First row
print(digits[:, :2]) # First two values in each row.
print(digits[:, -2:]) # Last two values in each row.

Além disso, exatamente como tensores normais, você pode usar operadores aritméticos e de comparação em Python para realizar operações elemento a elemento. Para mais informações, clique na seção Operadores sobrecarregados abaixo.

print(digits + 3)
print(digits + tf.ragged.constant([[1, 2, 3, 4], [], [5, 6, 7], [8], []]))

Se você precisa realizar uma transformação elemento a elemento para os valores de um RaggedTensor, pode usar tf.ragged.map_flat_values, que pega uma função de um ou mais argumentos e a aplica 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))

Os tensores irregulares podem ser convertidos em lists aninhadas do Python e arrays do NumPy:

digits.to_list()
digits.numpy()

Construindo um tensor irregular

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

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

Os tensores irregulares também podem ser criados ao combinar tensores de valores flat com tensores de particionamento de linhas que indicam como esses valores devem ser divididos em linhas, usando factory classmethods como tf.RaggedTensor.from_value_rowids, tf.RaggedTensor.from_row_lengths e tf.RaggedTensor.from_row_splits.

tf.RaggedTensor.from_value_rowids

Se você sabe a qual linha cada valor pertence, então pode criar um RaggedTensor usando um tensor de particionamento de linhas value_rowids:

tensor de particionamento de linhas 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.from_row_lengths

Se você sabe o comprimento de cada linha, então pode usar um tensor de particionamento de linhas row_lengths:

tensor de particionamento de linhas row_lengths

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

tf.RaggedTensor.from_row_splits

Se você sabe o índice em que cada linha começa e termina, então pode usar um tensor de particionamento de linhas row_splits:

tensor de particionamento de linhas row_splits

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

Veja uma lista completa de factory methods na documentação da classe tf.RaggedTensor.

Observação: por padrão, esses factory methods adicionam asserções de que o tensor de partição de linhas está bem formado e consistente com o número de valores. O parâmetro validate=False pode ser usado para pular essas verificações se você conseguir garantir que as entradas estão bem formadas e consistentes.

O que você pode armazenar em um tensor irregular

Assim como em Tensors normais, todos os valores em um RaggedTensor precisam ter o mesmo tipo e, além disso, estar na mesma profundidade de aninhamento (o posto do tensor):

print(tf.ragged.constant([["Hi"], ["How", "are", "you"]])) # ok: type=string, rank=2
print(tf.ragged.constant([[[1, 2], [3]], [[4, 5]]])) # ok: type=int32, rank=3
try: tf.ragged.constant([["one", "two"], [3, 4]]) # bad: multiple types except ValueError as exception: print(exception)
try: tf.ragged.constant(["A", ["B", "C"]]) # bad: multiple nesting depths except ValueError as exception: print(exception)

Exemplo de caso de uso

O exemplo a seguir demonstra como RaggedTensors podem ser usados para construir e combinar embeddings de unigramas e bigramas para um lote de consultas de comprimento variável, usando marcadores especiais para o início e o final de cada frase. Para mais detalhes sobre as ops usadas neste exemplo, confira 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)

Exemplo de tensor irregular

Dimensões irregulares e uniformes

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

A dimensão mais externa de um tensor irregular é sempre uniforme, já que consiste em uma única fatia (portanto, não há possibilidade de diferenciar comprimentos de fatias). As dimensões restantes podem ser irregulares ou uniformes. Por exemplo, você pode armazenar embeddings de palavras para cada palavra de um lote de frases usando um tensor irregular com o formato [num_sentences, (num_words), embedding_size], em que os parênteses em volta de (num_words) indicam que a dimensão é 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 estruturados usando um tensor com formato [num_documents, (num_paragraphs), (num_sentences), (num_words)] (novamente, os parênteses são usados para indicar dimensões irregulares).

Como no tf.Tensor, o posto de um tensor irregular é o número total de dimensões (incluindo irregulares e uniformes). Um tensor possivelmente irregular é um valor que pode ser um tf.Tensor ou tf.RaggedTensor.

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

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

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

O método tf.RaggedTensor.bounding_shape pode ser usado para encontrar um formato delimitador apertado para um determinado RaggedTensor:

print(tf.ragged.constant([["Hi"], ["How", "are", "you"]]).bounding_shape())

Irregular x esparso

Um tensor irregular não deve ser considerado como um tipo de tensor esparso. Particularmente, os tensores esparsos são codificações eficientes para tf.Tensor que modelam os mesmos dados em um formato compacto, enquanto os tensores irregulares são uma extensão do tf.Tensor que modelam uma classe expandida de dados. Essa diferença é fundamental ao definir operações:

  • Ao aplicar uma op a um tensor esparso ou denso, deve sempre dar o mesmo resultado.

  • Ao aplicar uma op a um tensor irregular ou esparso, os resultados podem ser diferentes.

Por exemplo, considere a maneira que as operações de arrays como concat, stack e tile são definidas para os tensores irregulares em comparação com os esparsos. A concatenação de tensores irregulares mescla cada linha, formando uma única linha de 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))

No entanto, a concatenação dos tensores esparsos equivale à concatenação dos tensores densos correspondentes, conforme ilustrado pelo exemplo a seguir (em que Ø indica valores ausentes):

Concatenating sparse tensors

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, ''))

Como outro exemplo da importância dessa distinção, considere a definição do "valor médio de cada linha" para uma op como tf.reduce_mean. Para um tensor irregular, o valor médio de uma linha é a soma dos valores dessa linha divididos pela largura dela. No entanto, para um tensor esparso, o valor médio de uma linha é a soma dos valores dessa linha divididos pela largura geral do tensor esparso (que é maior ou igual à largura da linha mais longa).

APIs TensorFlow

Keras

tf.keras é uma 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 do Keras ao configurar ragged=True no tf.keras.Input ou tf.keras.layers.InputLayer. Os tensores irregulares também podem ser passados entre camadas do Keras e retornados por modelos do Keras. O exemplo a seguir mostra um modelo LSTM de brinquedo 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))

tf.Example

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

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 processar esses dados codificados usando tf.io.parse_example, que pega um tensor de strings serializadas e um dicionário de especificações de características e retorna um dicionário que mapeia nomes de características a tensores. Para ler as características de comprimento variável nos tensores irregulares, basta usar tf.io.RaggedFeature no dicionário de especificações de características:

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

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

Datasets

tf.data é uma API que permite criar pipelines de entrada complexos a partir de partes simples e reutilizáveis. A estrutura de dados principal é tf.data.Dataset, que representa uma sequência de elementos, onde cada um 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))

Criando datasets com tensores irregulares

Os datasets podem ser criados a partir de tensores irregulares usando os mesmos métodos utilizados para a criação a partir de tf.Tensors ou arrays do NumPy, como Dataset.from_tensor_slices:

dataset = tf.data.Dataset.from_tensor_slices(feature_tensors) print_dictionary_dataset(dataset)

Observação: Dataset.from_generator ainda não é compatível com tensores irregulares, mas o suporte será adicionado em breve.

Criando e separando lotes de datasets com tensores irregulares

Os datasets com tensores irregulares podem ser divididos em lotes (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)

Por outro lado, um dataset em lotes pode ser transformado em um dataset simples usando Dataset.unbatch.

unbatched_dataset = batched_dataset.unbatch() print_dictionary_dataset(unbatched_dataset)

Dividindo datasets em lotes com tensores não irregulares de comprimento variável

Se você tiver um dataset com tensores não irregulares, e o comprimento dos tensores variar entre os elementos, então você pode dividir esses tensores não irregulares em lotes de tensores irregulares ao aplicar 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)

Transformando datasets 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)

tf.function

tf.function é um decorador que pré-computa os grafos do TensorFlow para as funções em Python, o que pode melhorar significativamente o desempenho do seu código do TensorFlow. Os tensores irregulares podem ser usados de maneira transparente com funções @tf.function decoradas. Por exemplo, a função a seguir funciona tanto com tensores irregulares quanto 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)
make_palindrome(tf.ragged.constant([[1, 2], [3], [4, 5, 6]]), axis=1)

Se você quiser especificar input_signature explicitamente para tf.function, 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]]))

Funções concretas

As funções concretas encapsulam os grafos traçados individuais que são criados por tf.function. Os tensores irregulares podem ser usados de maneira 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))

SavedModels

Um SavedModel é um programa do TensorFlow serializado, incluindo tanto os pesos quanto a computação. Ele pode ser criado a partir de um modelo do Keras ou de um modelo personalizado. Nos dois casos, os tensores irregulares podem ser usados de maneira transparente com as funções e os métodos definidos por um SavedModel.

Exemplo: salvando um modelo do 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)

Exemplo: salvando 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]]))

Observação: as assinaturas do SavedModel são funções concretas. Conforme discutido na seção "Funções concretas" acima, os tensores irregulares só são tratados corretamente pelas funções concretas a partir do TensorFlow 2.3. Se você precisar usar assinaturas do SavedModel em uma versão anterior do TensorFlow, é recomendável decompor o tensor irregular nos seus tensores componentes.

Operadores sobrecarregados

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

x = tf.ragged.constant([[1, 2], [3], [4, 5, 6]]) y = tf.ragged.constant([[1, 1], [2], [3, 3, 3]]) print(x + y)

Como os operadores sobrecarregados realizam computações elemento a elemento, é preciso que as entradas de todas as operações binárias tenham o mesmo formato ou possam fazer o broadcasting para o mesmo formato. No caso de broadcasting mais simples, um único escalar é combinado elemento a elemento com cada valor de um tensor irregular:

x = tf.ragged.constant([[1, 2], [3], [4, 5, 6]]) print(x + 3)

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

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

Indexação

Os tensores irregulares são compatíveis com a indexação de estilo Python, incluindo a indexação e o fatiamento multidimensional. Os exemplos a seguir demonstram a indexação de tensores irregulares 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
print(queries[1, 2]) # A single word
print(queries[1:]) # Everything but the first row
print(queries[:, :3]) # The first 3 words of each query
print(queries[:, -2:]) # The last 2 words of each query

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)
print(rt[3, 0]) # First element of fourth row (1D Tensor)
print(rt[:, 1:3]) # Items 1-3 of each row (3D RaggedTensor)
print(rt[:, -1:]) # Last item of each row (3D RaggedTensor)

RaggedTensors são compatíveis com a indexação e o fatiamento multidimensional com uma restrição: a indexação em uma dimensão irregular não é permitida. Esse caso é problemático porque o valor indicado pode existir em algumas linhas, mas não em outras. Nesses casos, não é evidente se você deve (1) gerar um IndexError; (2) usar um valor padrão; ou (3) pular esse valor e retornar um tensor com menos linhas que antes. Seguindo os princípios norteadores do Python ("Diante da ambiguidade, resista à tentação de adivinhar"), essa operação foi proibida.

Conversão de tipo de tensor

A classe RaggedTensor define métodos que podem ser usados para a conversão entre RaggedTensors e tf.Tensors 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]))
# Tensor -> RaggedTensor x = [[1, 3, -1, -1], [2, -1, -1, -1], [4, 5, 8, 9]] print(tf.RaggedTensor.from_tensor(x, padding=-1))
#RaggedTensor -> SparseTensor print(ragged_sentences.to_sparse())
# 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))

Avaliando tensores irregulares

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

  1. Usar tf.RaggedTensor.to_list para converter o tensor irregular em uma lista Python aninhada.

  2. Usar tf.RaggedTensor.numpy para converter o tensor irregular em um array do NumPy com valores que são arrays do NumPy aninhados.

  3. Decompor o tensor irregular nos seus componentes, usando as propriedades tf.RaggedTensor.values e tf.RaggedTensor.row_splits ou métodos de particionamento de linhas, como tf.RaggedTensor.row_lengths e tf.RaggedTensor.value_rowids.

  4. Usar 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())

Formatos irregulares

O formato de um tensor especifica o tamanho de cada eixo. Por exemplo, o formato de [[1, 2], [3, 4], [5, 6]] é [3, 2], já que há 3 linhas e 2 colunas. O TensorFlow tem duas maneiras separadas mas relacionadas de descrever formatos:

  • formato estático: são informações sobre os tamanhos dos eixos conhecidas estaticamente (por exemplo, ao traçar uma tf.function). Pode ser parcialmente especificado.

  • formato dinâmico: são informações de runtime sobre os tamanhos dos eixos.

Formato estático

O formato estático de um tensor contém informações sobre os tamanhos dos eixos que são conhecidas no momento de construção do grafo. Para ambos o tf.Tensor e o tf.RaggedTensor, ele está disponível usando a propriedade .shape e é codificado usando tf.TensorShape:

x = tf.constant([[1, 2], [3, 4], [5, 6]]) x.shape # shape of a tf.tensor
rt = tf.ragged.constant([[1], [2, 3], [], [4]]) rt.shape # shape of a tf.RaggedTensor

O formato estático de uma dimensão irregular é sempre None (ou seja, não especificada). No entanto, o inverso não é verdade — se uma dimensão TensorShape for None, então isso pode indicar que a dimensão é irregular ou que ela é uniforme, mas o tamanho dela não é conhecido estatisticamente.

Formato dinâmico

O formato dinâmico de um tensor contém informações sobre os tamanhos dos eixos que são conhecidas quando o grafo é executado. É criado usando a operação tf.shape. Para tf.Tensor, tf.shape retorna o formato como um Tensor de número inteiro 1D, em que tf.shape(x)[i] é o tamanho do eixo i.

x = tf.constant([['a', 'b'], ['c', 'd'], ['e', 'f']]) tf.shape(x)

No entanto, um Tensor 1D não é expressivo o suficiente para descrever o formato de um tf.RaggedTensor. Em vez disso, o formato dinâmico para tensores irregulares é codificado usando um tipo dedicado, tf.experimental.DynamicRaggedShape. No exemplo a seguir, o DynamicRaggedShape retornado por tf.shape(rt) indica que o tensor irregular tem 4 linhas, com comprimentos 1, 3, 0 e 2:

rt = tf.ragged.constant([[1], [2, 3, 4], [], [5, 6]]) rt_shape = tf.shape(rt) print(rt_shape)

Formato dinâmico: operações

DynamicRaggedShapes podem ser usados com a maioria das ops do TensorFlow que esperam formatos, incluindo tf.reshape, tf.zeros, tf.ones. tf.fill, tf.broadcast_dynamic_shape e tf.broadcast_to.

print(f"tf.reshape(x, rt_shape) = {tf.reshape(x, rt_shape)}") print(f"tf.zeros(rt_shape) = {tf.zeros(rt_shape)}") print(f"tf.ones(rt_shape) = {tf.ones(rt_shape)}") print(f"tf.fill(rt_shape, 9) = {tf.fill(rt_shape, 'x')}")

Formato dinâmico: indexação e fatiamento

DynamicRaggedShape também pode ser indexado para obter os tamanhos das dimensões uniformes. Por exemplo, podemos encontrar o número de linhas em um raggedtensor usando tf.shape(rt)[0] (como faríamos para um tensor não irregular):

rt_shape[0]

No entanto, é um erro usar a indexação para tentar recuperar o tamanho de uma dimensão irregular, já que ela não tem um único tamanho. (Como o RaggedTensor rastreia quais eixos são irregulares, esse erro só é gerado durante a eager execution ou ao traçar uma tf.function. Ele nunca será gerado ao executar uma função concreta.)

try: rt_shape[1] except ValueError as e: print("Got expected ValueError:", e)

DynamicRaggedShapes também podem ser fatiados, desde que a fatia comece com o eixo 0 ou contenha apenas dimensões densas.

rt_shape[:1]

Formato dinâmico: codificação

DynamicRaggedShape é codificado usando dois campos:

  • inner_shape: um vetor de número inteiro que dá o formato de um tf.Tensor denso.

  • row_partitions: uma lista de objetos tf.experimental.RowPartition, que descreve como a dimensão mais externa desse formato interno deve ser particionada para adicionar eixos irregulares.

Para mais informações sobre as partições de linhas, confira a seção "Codificação do RaggedTensor" abaixo e a documentação da API para tf.experimental.RowPartition.

Formato dinâmico: construção

DynamicRaggedShape é geralmente construído ao aplicar tf.shape a um RaggedTensor, mas ele também pode ser construído diretamente:

tf.experimental.DynamicRaggedShape( row_partitions=[tf.experimental.RowPartition.from_row_lengths([5, 3, 2])], inner_shape=[10, 8])

Se os comprimentos de todas as linhas são conhecidos estaticamente, DynamicRaggedShape.from_lengths também pode ser usado para construir um formato irregular dinâmico (Isso é útil principalmente para teste e código de demonstração, já que é raro para os comprimentos das dimensões irregulares serem conhecidos estaticamente).

tf.experimental.DynamicRaggedShape.from_lengths([4, (2, 1, 0, 8), 12])

Broadcasting

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

Os passos básicos de broadcasting de duas entradas x e y para que tenham formatos compatíveis são:

  1. Se x e y não têm 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 tem o tamanho 1 na dimensão d, então repita os valores dele na dimensão d para combinar com o tamanho da outra entrada.

  • Caso contrário, gere uma exceção (x e y não são compatíveis com o broadcasting).

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 broadcasting

# 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)
# 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)
# 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)
# 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)

Aqui estão alguns exemplos de formatos que não realizam o broadcasting:

# 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)
# 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)
# 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)

Codificação do RaggedTensor

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

  • Um tensor values, que concatena as linhas de comprimento variável em uma lista simples.

  • Uma row_partition, que indica como esses valores simples são divididos em linhas.

Codificação do RaggedTensor

A row_partition pode ser armazenada usando quatro codificações diferentes:

  • row_splits é um vetor de número inteiro que especifica os pontos de divisão entre as linhas.

  • value_rowids é um vetor de número inteiro que especifica o índice da linha para cada valor.

  • row_lengths é um vetor de número inteiro que especifica o comprimento de cada linha.

  • uniform_row_length é um escalar de número inteiro que especifica um único comprimento para todas as linhas.

Codificações da row_partition

Um nrows escalar de número inteiro também pode ser incluído na codificação de row_partition para compensar as linhas de espaços em branco à direita com value_rowids ou as 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)

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

  • Indexação eficiente: a codificação row_splits permite a indexação de tempo constante e o fatiamento em tensores irregulares.

  • Concatenação eficiente: a codificação row_lengths é mais eficiente ao concatenar tensores irregulares, já que os comprimentos de linhas não mudam quando dois tensores são concatenados.

  • Codificação de tamanho pequeno: a codificação value_rowids é mais eficiente ao armazenar tensores irregulares com um grande número de linhas vazias, já que 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, porque exigem apenas um valor escalar para cada linha.

  • Compatibilidade: o esquema value_rowids se ajusta ao formato de segmentação usado pelas operações, como tf.segment_sum. O esquema row_limits se ajusta ao formato usado por ops 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 ao usar um RaggedTensor aninhado para o tensor values. Cada RaggedTensor aninhado adiciona uma única dimensão irregular.

Codificação de um tensor irregular com várias dimensões irregulares (posto 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))

A factory function tf.RaggedTensor.from_nested_row_splits pode ser usada para construir um RaggedTensor com várias dimensões irregulares diretamente ao fornecer 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)

Posto irregular e valores simples

O posto irregular de um tensor irregular é o número de vezes que o tensor values subjacente foi particionado (ou seja, a profundidade de aninhamento dos objetos RaggedTensor). O tensor values mais interno é conhecido como seus flat_values. No exemplo a seguir, conversations tem ragged_rank=3, e seus 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
assert conversations.ragged_rank == len(conversations.nested_row_splits) conversations.ragged_rank # Number of partitioned dimensions.
conversations.flat_values.numpy()

Dimensões internas uniformes

Os tensores irregulares com dimensões internas uniformes são codificados ao usar 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))

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