Tensores irregulares

Ver 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. Tensores esfarrapados são o equivalente TensorFlow de listas de comprimento variável aninhados. 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 esfarrapados são suportados por mais do que uma centena de operações TensorFlow, incluindo operações matemáticas (tais como tf.add e tf.reduce_mean ), operações de matriz (tais como tf.concat e tf.tile ), ops de manipulação de cadeia (como tf.substr ), o controlo de fluxo de operações (tais como tf.while_loop e tf.map_fn ), e muitos outros:

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

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

Tensores irregulares são suportados por muitos APIs TensorFlow, incluindo Keras , conjuntos de dados , tf.function , SavedModels e tf.Example . Para mais informações, consulte a seção sobre TensorFlow APIs abaixo.

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

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

E, assim como os tensores normais, você pode usar operadores aritméticos e de comparação do Python para realizar operações elementwise. Para mais informações, consulte a seção sobre operadores Overloaded 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ê precisa executar uma transformação elemento a elemento para os valores de uma RaggedTensor , você pode usar tf.ragged.map_flat_values , que leva uma função além de um ou mais argumentos, e aplica a função de transformar a RaggedTensor valores 's.

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 pode ser convertido para aninhada Python list s e NumPy array s:

digits.to_list()
[[3, 1, 4, 1], [], [5, 9, 2], [6], []]
digits.numpy()
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py: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 é irregular usando tf.ragged.constant , que constrói a RaggedTensor correspondente a um determinado Python aninhados list ou NumPy array :

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

Tensores irregulares também podem ser construídos por sorteio valores planas tensores com tensores de particionamento de linha indicando como esses valores devem ser divididos em linhas, usando classmethods fábrica tais 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 qual linha cada valor pertence, então você pode construir uma RaggedTensor usando um value_rowids tensor de particionamento de linha:

value_rowids tensor de particionamento de linha

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

tf.RaggedTensor.from_row_lengths

Se você sabe quanto tempo cada linha é, então você pode usar um row_lengths tensor row-particionamento:

row_lengths tensor de particionamento de linha

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

tf.RaggedTensor.from_row_splits

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

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

Veja a tf.RaggedTensor documentação da classe para uma lista completa de métodos de fábrica.

O que você pode armazenar em um tensor irregular

Tal como acontece com o normal Tensor s, os valores em um RaggedTensor devem ter todos o mesmo tipo; e os valores devem estar todos na mesma profundidade de nidificação (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 unigram e bigrama embeddings para um lote de consultas de comprimento variável, usando marcadores especiais para o início eo fim de cada frase. Para mais detalhes sobre as operações usadas neste exemplo, verifique a tf.ragged documentação do pacote.

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 áspera é uma dimensão cujas fatias pode ter diferentes comprimentos. Por exemplo, a dimensão interior (coluna) de rt=[[3, 1, 4, 1], [], [5, 9, 2], [6], []] é irregular, uma vez que as fatias de coluna ( rt[0, :] , ..., rt[4, :] ) possuem diferentes comprimentos. Dimensões cujas fatias têm todos o mesmo comprimento são chamados de dimensões uniformes.

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

Embeddings de palavras usando um tensor irregular

Tensores irregulares podem ter múltiplas 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 as dimensões irregulares).

Tal como acontece com tf.Tensor , a patente de um tensor áspera é o seu número total de dimensões (incluindo ambas as dimensões irregulares e uniformes). Um tensor potencialmente irregular é um valor que pode ser tanto um tf.Tensor ou um tf.RaggedTensor .

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

O RaggedTensor.shape atributo retorna um tf.TensorShape para um tensor irregular onde dimensões irregulares tem 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 apertado 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 pensado como um tipo de tensor escassa. Em particular, são tensores esparsos codificações eficientes para tf.Tensor que modelo os mesmos dados num formato compacto; mas tensor áspera é uma extensão para tf.Tensor que modela uma classe expandida de dados. Essa diferença é crucial ao definir as operações:

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

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

Concatenando tensores irregulares

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

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

Tensores esparsos de concatenação

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

Para outro exemplo de por que essa distinção é importante, considere a definição de “o valor médio de cada linha” para um op 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 é de TensorFlow API de alto nível para construir e treinar modelos de aprendizagem profunda. Tensores irregulares podem ser passados como entradas para um modelo Keras pela configuração ragged=True na tf.keras.Input ou tf.keras.layers.InputLayer . Tensores irregulares também podem ser passados ​​entre camadas Keras e retornados por modelos Keras. O exemplo a seguir mostra um modelo LSTM de brinquedo que é treinado usando tensores irregulares.

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

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

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

keras_model.compile(loss='binary_crossentropy', optimizer='rmsprop')
keras_model.fit(hashed_words, is_question, epochs=5)
print(keras_model.predict(hashed_words))
WARNING:tensorflow:Layer lstm will not use cuDNN kernels since it doesn't meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.
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 é um padrão Protobuf codificação para dados TensorFlow. Os dados codificados com tf.Example s muitas vezes inclui características de comprimento variável. Por exemplo, o seguinte código define um lote de quatro tf.Example mensagens com diferentes comprimentos de funcionalidades:

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 leva um tensor de cordas serializados e um dicionário de especificação de recurso e retorna um dicionário nomes de recursos de mapeamento para tensores. Para ler as características de comprimento variável em tensores irregulares, basta usar tf.io.RaggedFeature no dicionário especificação recurso:

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 características com múltiplas dimensões irregulares. Para mais detalhes, consulte a documentação da API .

Conjuntos de dados

tf.data é uma API que permite que você construa dutos de entrada complexos a partir de simples, peças reutilizáveis. A sua estrutura de dados de núcleo é tf.data.Dataset , que representa uma sequência de elementos, em que cada elemento é constituído de 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

Os conjuntos de dados pode ser construído a partir de tensores irregulares usando os mesmos métodos que são usados para construir os de tf.Tensor s ou NumPy array s, tais 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]

Colocação em lote e desencadeamento de conjuntos de dados com tensores irregulares

Conjuntos de dados com tensores irregulares podem ser agrupadas (que combina elementos n consecutivos num único elementos), utilizando o Dataset.batch método.

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 plano, utilizando 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ê tem um conjunto de dados que contém tensores não esfarrapadas, e tensores comprimentos variam entre os elementos, então você pode batch esses tensores não esfarrapados em tensores irregulares, aplicando o dense_to_ragged_batch transformação:

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

Transformando conjuntos de dados com tensores irregulares

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

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

tf.function

tf.function é um decorador que precomputes TensorFlow gráficos para funções Python, que pode melhorar substancialmente o desempenho do seu código TensorFlow. Tensores irregulares podem ser usados de forma transparente com @tf.function funções -decorated. 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)
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 , 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 encapsular gráficos traçados individuais que são construídas por tf.function . Tensores irregulares podem ser usados ​​de forma transparente com funções concretas.

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

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

SavedModels

Um SavedModel é um programa TensorFlow em série, incluindo ambos os pesos e cálculo. 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)
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

Os RaggedTensor sobrecargas classe dos aritméticas e comparação operadores Python padrão, tornando mais fácil para executar 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)
<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, verifique a seção sobre Radiodifusão.

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

Indexando

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

Exemplos de indexação: tensor irregular 2D

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

Exemplos de indexação: tensor 3D irregular

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

RaggedTensor apoio indexação s multidimensional e corte com uma restrição: indexação para 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. Em tais casos, não é óbvio se você deve (1) levantar um IndexError ; (2) use um valor padrão; ou (3) pule esse valor e retorne um tensor com menos linhas do que você começou. Seguindo os princípios orientadores do Python ( "Em face da ambigüidade, recuse a tentação de adivinhar"), esta operação é atualmente permitido.

Conversão de tipo de tensor

Os RaggedTensor define classe métodos que podem ser utilizados 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 a uma lista Python aninhados.
  2. Uso tf.RaggedTensor.numpy para converter o tensor irregular para uma matriz NumPy cujos valores estão aninhados matrizes Numpy.
  3. Decompõe-se o tensor de irregular nos seus componentes, utilizando os tf.RaggedTensor.values e tf.RaggedTensor.row_splits propriedades, ou por meio de paritioning fileira como tf.RaggedTensor.row_lengths e tf.RaggedTensor.value_rowids .
  4. Use a indexação Python para selecionar valores do tensor irregular.
rt = tf.ragged.constant([[1, 2], [3, 4, 5], [6], [], [7]])
print("Python list:", rt.to_list())
print("NumPy array:", rt.numpy())
print("Values:", rt.values.numpy())
print("Splits:", rt.row_splits.numpy())
print("Indexed value:", rt[1].numpy())
Python list: [[1, 2], [3, 4, 5], [6], [], [7]]
NumPy array: [array([1, 2], dtype=int32) array([3, 4, 5], dtype=int32)
 array([6], dtype=int32) array([], dtype=int32) array([7], dtype=int32)]
Values: [1 2 3 4 5 6 7]
Splits: [0 2 5 6 6 7]
Indexed value: [3 4 5]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py: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 com que tensores com formatos diferentes tenham formatos compatíveis para operações de elemento a elemento. Para obter mais informações sobre a transmissão, consulte:

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

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

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

  • Se x ou y tem tamanho 1 na dimensão d , em seguida, repita os seus valores através de dimensão d para coincidir com o tamanho da outra entrada.
  • Caso contrário, levantar uma excepção ( x e y não são transmitidos compatível).

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

Exemplos de transmissão

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

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

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

Codificação RaggedTensor

Tensores irregulares são codificados usando o RaggedTensor classe. Internamente, cada RaggedTensor consiste de:

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

Codificação RaggedTensor

O row_partition podem ser armazenados usando quatro codificações diferentes:

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

codificações row_partition

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

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

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

  • Indexação eficiente: A row_splits codificação permite a indexação de tempo constante e que corta em tensores irregulares.
  • Concatenação eficientes: Os row_lengths codificando é mais eficiente quando concatenando tensores irregulares, desde comprimentos de linha não mudam quando dois tensores são concatenados em conjunto.
  • Tamanho pequeno codificação: Os value_rowids codificando é mais eficiente quando armazenar tensores irregulares que têm um grande número de linhas vazios, 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 value_rowids esquema corresponde à segmentação formato usado por operações, como tf.segment_sum . O row_limits esquema corresponde ao formato usado por ops como tf.sequence_mask .
  • Dimensões uniformes: Como discutido abaixo, o uniform_row_length de codificação é utilizada para codificar irregular tensores com dimensões uniformes.

Múltiplas dimensões irregulares

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

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

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

A função fábrica tf.RaggedTensor.from_nested_row_splits pode ser utilizado para construir um RaggedTensor com múltiplas dimensões irregulares directamente por meio de uma lista de row_splits tensores:

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

Terminou irregular de um tensor áspera é o número de vezes que os subjacentes values tensor foi particionado (ou seja, a profundidade de sobreposição de RaggedTensor objetos). A mais interna values tensor é conhecida como o seu flat_values. No exemplo a seguir, conversations tem ragged_rank = 3, e os seus flat_values é um 1D Tensor com 24 cordas:

# 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 áspero com dimensões internas uniformes são codificados usando um multidimensional tf.Tensor para os flat_values (isto é, os mais profundos values ).

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

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

Dimensões não internas uniformes

Tensores áspero com dimensões internas não-uniformes são codificados por particionamento linhas com uniform_row_length .

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

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