Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Tensores irregulares

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Documentación de API: tf.RaggedTensor tf.Ragged

Preparar

import math
import tensorflow as tf

Visión general

Sus datos vienen en muchas formas; tus tensores también deberían hacerlo. Los tensores irregulares son el equivalente de TensorFlow de las listas anidadas de longitud variable. Facilitan el almacenamiento y el procesamiento de datos con formas no uniformes, que incluyen:

  • Funciones de duración variable, como el conjunto de actores de una película.
  • Lotes de entradas secuenciales de longitud variable, como frases o videoclips.
  • Entradas jerárquicas, como documentos de texto que se subdividen en secciones, párrafos, oraciones y palabras.
  • Campos individuales en entradas estructuradas, como búferes de protocolo.

Que puedes hacer con un tensor irregular

Los tensores irregulares son compatibles con más de cien operaciones de TensorFlow, incluidas operaciones matemáticas (como tf.add y tf.reduce_mean ), operaciones de matriz (como tf.concat y tf.tile ), operaciones de manipulación de cadenas (como tf.substr ), controlar las operaciones de flujo (como tf.while_loop y tf.map_fn ), y muchos otros:

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

También hay una serie de métodos y operaciones que son específicos para tensores irregulares, incluidos los métodos de fábrica, los métodos de conversión y las operaciones de mapeo de valores. Para obtener una lista de las operaciones admitidas, consulte la documentación del paquete tf.ragged .

Los tensores irregulares son compatibles con muchas API de TensorFlow, incluidas Keras , Datasets , tf.function , SavedModels y tf.Example . Para obtener más información, consulte la sección sobre las API de TensorFlow a continuación.

Al igual que con los tensores normales, puede usar la indexación al estilo de Python para acceder a segmentos específicos de un tensor irregular. Para obtener más información, consulte la sección sobre indexación a continuación.

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

Y al igual que los tensores normales, puede usar operadores aritméticos y de comparación de Python para realizar operaciones de elemento. Para obtener más información, consulte la sección sobre operadores sobrecargados a continuación.

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

Si necesita realizar una transformación de elemento a los valores de un RaggedTensor , puede usar tf.ragged.map_flat_values , que toma una función más uno o más argumentos, y aplica la función para transformar los valores de 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], []]>

Los tensores irregulares se pueden convertir en list Python anidadas y array numpy:

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)

Construyendo un tensor irregular

La forma más sencilla de construir un tensor irregular es usando tf.ragged.constant , que construye el RaggedTensor correspondiente a una list Python anidada dada o una 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']]]>

Los tensores irregulares también se pueden construir emparejando tensores de valores planos con tensores de división de filas que indiquen cómo se deben dividir esos valores en filas, usando métodos de clase de fábrica como tf.RaggedTensor.from_value_rowids , tf.RaggedTensor.from_row_lengths y tf.RaggedTensor.from_row_splits .

tf.RaggedTensor.from_value_rowids

Si sabe a qué fila pertenece cada valor, entonces puede construir un RaggedTensor usando un tensor de partición de filas 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

Si sabe cuánto mide cada fila, puede usar un tensor de partición de filas row_lengths :

row_lengths

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

tf.RaggedTensor.from_row_splits

Si conoce el índice donde comienza y termina cada fila, entonces puede usar un tensor de partición de filas 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 la documentación de la clase tf.RaggedTensor para obtener una lista completa de los métodos de fábrica.

Lo que puedes almacenar en un tensor irregular

Al igual que con los Tensor normales, los valores en un RaggedTensor deben tener el mismo tipo; y todos los valores deben estar a la misma profundidad de anidación (el rango del 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 ejemplo

El siguiente ejemplo demuestra cómo se pueden usar RaggedTensor s para construir y combinar incrustaciones de unigrama y bigrama para un lote de consultas de longitud variable, usando marcadores especiales para el principio y el final de cada oración. Para obtener más detalles sobre las operaciones utilizadas en este ejemplo, consulte la documentación del paquete 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

Dimensiones irregulares y uniformes

Una dimensión irregular es una dimensión cuyos cortes pueden tener diferentes longitudes. Por ejemplo, la dimensión interna (columna) de rt=[[3, 1, 4, 1], [], [5, 9, 2], [6], []] es desigual, ya que las secciones de columna ( rt[0, :] , ..., rt[4, :] ) tienen diferentes longitudes. Las dimensiones cuyas rebanadas tienen todas la misma longitud se denominan dimensiones uniformes .

La dimensión más externa de un tensor irregular es siempre uniforme, ya que consta de un solo corte (por lo que no hay posibilidad de diferentes longitudes de corte). Las dimensiones restantes pueden ser irregulares o uniformes. Por ejemplo, podríamos almacenar las incrustaciones de palabras para cada palabra en un lote de oraciones usando un tensor irregular con forma [num_sentences, (num_words), embedding_size] , donde los paréntesis alrededor de (num_words) indican que la dimensión es irregular.

sent_word_embed

Los tensores irregulares pueden tener múltiples dimensiones irregulares. Por ejemplo, podríamos almacenar un lote de documentos de texto estructurado usando un tensor con forma [num_documents, (num_paragraphs), (num_sentences), (num_words)] (donde nuevamente se usan paréntesis para indicar dimensiones irregulares).

Al igual que con tf.Tensor , el rango de un tensor irregular es su número total de dimensiones (incluidas las dimensiones irregulares y uniformes). Un tensor potencialmente irregular es un valor que puede ser un tf.Tensor o un tf.RaggedTensor .

Al describir la forma de un RaggedTensor, las dimensiones irregulares se indican convencionalmente encerrándolas entre paréntesis. Por ejemplo, como vimos anteriormente, la forma de un RaggedTensor 3-D que almacena incrustaciones de palabras para cada palabra en un lote de oraciones se puede escribir como [num_sentences, (num_words), embedding_size] .

El atributo RaggedTensor.shape devuelve un tf.TensorShape para un tensor irregular, donde las dimensiones irregulares tienen un tamaño None :

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

El método tf.RaggedTensor.bounding_shape se puede usar para encontrar una forma delimitadora ajustada para un RaggedTensor dado:

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

Harapiento vs escaso

Un tensor irregular no debe considerarse como un tipo de tensor disperso. En particular, los tensores dispersos son codificaciones eficientes para tf.Tensor , que modelan los mismos datos en un formato compacto; pero el tensor irregular es una extensión de tf.Tensor , que modela una clase expandida de datos. Esta diferencia es crucial a la hora de definir operaciones:

  • La aplicación de una op a un tensor denso o escaso siempre debería dar el mismo resultado.
  • La aplicación de una op a un tensor desigual o escaso puede dar resultados diferentes.

Como ejemplo ilustrativo, considere cómo las operaciones de matriz como concat , stack y tile se definen para tensores irregulares frente a dispersos. La concatenación de tensores irregulares une cada fila para formar una sola fila con la longitud combinada:

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

Pero concatenar tensores dispersos es equivalente a concatenar los tensores densos correspondientes, como se ilustra en el siguiente ejemplo (donde Ø indica valores faltantes):

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 ver otro ejemplo de por qué es importante esta distinción, considere la definición de "el valor medio de cada fila" para una tf.reduce_mean como tf.reduce_mean . Para un tensor irregular, el valor medio de una fila es la suma de los valores de la fila dividida por el ancho de la fila. Pero para un tensor disperso, el valor medio de una fila es la suma de los valores de la fila divididos por el ancho total del tensor disperso (que es mayor o igual que el ancho de la fila más larga).

API de TensorFlow

Keras

tf.keras es la API de alto nivel de TensorFlow para crear y entrenar modelos de aprendizaje profundo. Los tensores irregulares se pueden pasar como entradas a un modelo de Keras estableciendo ragged=True en tf.keras.Input o tf.keras.layers.InputLayer . Los tensores irregulares también se pueden pasar entre las capas de Keras y los modelos de Keras pueden devolverlos. El siguiente ejemplo muestra un modelo de juguete LSTM que se entrena con 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.Ejemplo

tf.Example es una codificación protobuf estándar para datos de TensorFlow. Los datos codificados con tf.Example s a menudo incluyen características de longitud variable. Por ejemplo, el siguiente código define un lote de cuatro mensajes de tf.Example con diferentes longitudes 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]} } } }''')]

Podemos analizar estos datos codificados usando tf.io.parse_example , que toma un tensor de cadenas serializadas y un diccionario de especificación de características, y devuelve un diccionario que asigna los nombres de las características a los tensores. Para leer las características de longitud variable en tensores irregulares, simplemente usamos tf.io.RaggedFeature en el diccionario de especificación 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))
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 también se puede utilizar para leer tf.io.RaggedFeature con múltiples dimensiones irregulares. Para obtener más detalles, consulte la documentación de la API .

Conjuntos de datos

tf.data es una API que le permite crear tuberías de entrada complejas a partir de piezas simples y reutilizables. Su estructura de datos central es tf.data.Dataset , que representa una secuencia de elementos, en la que cada elemento consta de uno o más 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))

Creación de conjuntos de datos con tensores irregulares

Los conjuntos de datos se pueden construir a partir de tensores irregulares usando los mismos métodos que se usan para construirlos a partir de tf.Tensor s o array numpy, como Dataset.from_tensor_slices :

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

Lote y desagregación de conjuntos de datos con tensores irregulares

Los conjuntos de datos con tensores irregulares se pueden agrupar (que combina n elementos consecutivos en un solo elemento) utilizando el 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 el contrario, un conjunto de datos por Dataset.unbatch se puede transformar en un conjunto de datos plano mediante 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]

Lote de conjuntos de datos con tensores no irregulares de longitud variable

Si tiene un conjunto de datos que contiene tensores no irregulares y las longitudes de los tensores varían entre los elementos, entonces puede agrupar esos tensores no irregulares en tensores irregulares aplicando la transformación 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]]>

Transformar conjuntos de datos con tensores irregulares

Los tensores irregulares en los conjuntos de datos también se pueden crear o transformar utilizando 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 es un decorador que calcula previamente los gráficos de TensorFlow para las funciones de Python, lo que puede mejorar sustancialmente el rendimiento de su código de TensorFlow. Los tensores irregulares se pueden utilizar de forma transparente con las funciones @tf.function con @tf.function . Por ejemplo, la siguiente función funciona con tensores irregulares y no 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]]>

Si desea especificar explícitamente input_signature para tf.function , puede hacerlo 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)>)

Funciones concretas

Las funciones concretas encapsulan gráficos trazados individuales que son construidos por tf.function . A partir de TensorFlow 2.3 (y en tf-nightly ), los tensores irregulares se pueden usar de forma transparente con funciones 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]]>

Si necesita utilizar tensores irregulares con funciones concretas antes de TensorFlow 2.3, recomendamos descomponer los tensores irregulares en sus componentes ( values y row_splits ) y pasarlos 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]]>

Modelos guardados

Un modelo guardado es un programa de TensorFlow serializado, que incluye tanto los pesos como el cálculo. Puede construirse a partir de un modelo Keras o de un modelo personalizado. En cualquier caso, los tensores irregulares se pueden usar de forma transparente con las funciones y métodos definidos por un modelo guardado.

Ejemplo: guardar un modelo de 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)>

Ejemplo: guardar un 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 sobrecargados

La clase RaggedTensor sobrecarga los operadores aritméticos y de comparación estándar de Python, lo que facilita la realización de operaciones matemáticas básicas por elementos:

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

Dado que los operadores sobrecargados realizan cálculos por elementos, las entradas a todas las operaciones binarias deben tener la misma forma o ser ampliables a la misma forma. En el caso de transmisión más simple, un solo escalar se combina por elementos con cada valor en un tensor irregular:

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

Para una discusión de casos más avanzados, consulte la sección sobre difusión .

Los tensores irregulares sobrecargan el mismo conjunto de operadores que los Tensor normales: los operadores unarios - , ~ y abs() ; y los operadores binarios + , - , * , / , // , % , ** , & , | , ^ , == , < , <= , > y >= .

Indexación

Los tensores irregulares admiten la indexación al estilo Python, incluida la indexación y el corte multidimensionales. Los siguientes ejemplos demuestran la indexación de tensor irregular con un tensor irregular 2-D y 3-D.

Ejemplos de indexación: tensor desigual 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']]>

Ejemplos de indexación 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 admite la indexación y la RaggedTensor multidimensionales, con una restricción: no se permite la indexación en una dimensión irregular. Este caso es problemático porque el valor indicado puede existir en algunas filas pero no en otras. En tales casos, no es obvio si deberíamos (1) generar un IndexError ; (2) use un valor predeterminado; o (3) omitir ese valor y devolver un tensor con menos filas de las que comenzamos. Siguiendo los principios rectores de Python ("Ante la ambigüedad, rechace la tentación de adivinar"), actualmente rechazamos esta operación.

Conversión de tipo de tensor

La clase RaggedTensor define métodos que se pueden usar para convertir entre RaggedTensor sy tf.Tensor 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']]>

Evaluación de tensores irregulares

Para acceder a los valores en un tensor irregular, puede:

  1. Utilice tf.RaggedTensor.to_list() para convertir el tensor irregular en una lista de Python anidada.
  2. Utilice tf.RaggedTensor.numpy() para convertir el tensor irregular en una matriz numpy cuyos valores son matrices numpy anidadas.
  3. Descomponga el tensor irregular en sus componentes, utilizando las propiedades tf.RaggedTensor.values y tf.RaggedTensor.row_splits , o métodos de tf.RaggedTensor.row_lengths() como tf.RaggedTensor.row_lengths() y tf.RaggedTensor.value_rowids() .
  4. Utilice la indexación de Python para seleccionar valores del 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]

Radiodifusión

La difusión es el proceso de hacer que los tensores con diferentes formas tengan formas compatibles para operaciones de elementos. Para obtener más información sobre la transmisión, consulte:

Los pasos básicos para la difusión de dos entradas x y y tener formas compatibles son:

  1. Si x y y no tienen el mismo número de dimensiones, a continuación, añadir dimensiones exteriores (con tamaño 1) hasta que lo hagan.

  2. Para cada dimensión donde x y y tienen diferentes tamaños:

    • Si x o y tienen un tamaño de 1 en la dimensión d , y luego repetir sus valores a través de dimensión d para que coincida con el tamaño de la otra entrada.

    • De lo contrario, lanzar una excepción ( x , y y no se transmiten compatibles).

Donde el tamaño de un tensor en una dimensión uniforme es un solo número (el tamaño de los cortes en esa dimensión); y el tamaño de un tensor en una dimensión irregular es una lista de longitudes de corte (para todos los cortes en esa dimensión).

Ejemplos de transmisión

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

A continuación, se muestran algunos ejemplos de formas que no se transmiten:

# 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

Codificación RaggedTensor

Los tensores irregulares se codifican mediante la clase RaggedTensor . Internamente, cada RaggedTensor consta de:

  • Un tensor de values , que concatena las filas de longitud variable en una lista plana.
  • Una row_partition , que indica cómo esos valores aplanados se dividen en filas.

codificación_ragged_2

La row_partition se puede almacenar usando cuatro codificaciones diferentes:

  • row_splits es un vector entero que especifica los puntos de división entre filas.
  • value_rowids es un vector entero que especifica el índice de fila para cada valor.
  • row_lengths es un vector entero que especifica la longitud de cada fila.
  • uniform_row_length es un escalar entero que especifica una longitud única para todas las filas.

particiones_encodificaciones

También se puede nrows un nrows escalares enteros en la codificación row_partition , para tener en cuenta las filas finales vacías con value_rowids , o filas vacías con 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]]>

La elección de qué codificación usar para las particiones de fila se gestiona internamente mediante tensores irregulares, para mejorar la eficiencia en algunos contextos. En particular, algunas de las ventajas y desventajas de los diferentes esquemas de partición de filas son:

  • Indexación eficiente : la codificación row_splits permite la indexación y el corte en tiempo constante en tensores irregulares.

  • Concatenación eficiente : la codificación row_lengths es más eficiente cuando se concatenan tensores irregulares, ya que las longitudes de las filas no cambian cuando dos tensores se concatenan juntos.

  • Tamaño de codificación pequeño : la codificación value_rowids es más eficiente cuando se almacenan tensores irregulares que tienen una gran cantidad de filas vacías, ya que el tamaño del tensor depende solo del número total de valores. Por otro lado, las codificaciones row_splits y row_lengths son más eficientes cuando se almacenan tensores irregulares con filas más largas, ya que solo requieren un valor escalar para cada fila.

  • Compatibilidad : el esquema value_rowids coincide con el formato de segmentación utilizado por operaciones como tf.segment_sum . El esquema row_limits coincide con el formato utilizado por operaciones como tf.sequence_mask .

  • Dimensiones uniformes : como se analiza a continuación, la codificación uniform_row_length se utiliza para codificar tensores irregulares con dimensiones uniformes.

Múltiples dimensiones irregulares

Un tensor irregular con múltiples dimensiones irregulares se codifica utilizando un RaggedTensor anidado para el tensor de values . Cada RaggedTensor anidado agrega una única dimensión 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

La función de fábrica tf.RaggedTensor.from_nested_row_splits se puede utilizar para construir un RaggedTensor con múltiples dimensiones irregulares directamente, proporcionando una 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]]]>

Rango irregular y valores planos

El rango irregular de un tensor irregular es el número de veces que los values subyacentes Tensor se han dividido (es decir, la profundidad de RaggedTensor objetos RaggedTensor ). El tensor de values más interno se conoce como flat_values . En el siguiente ejemplo, conversations tiene ragged_rank = 3, y su flat_values es un Tensor 1D con 24 cadenas:

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

Dimensiones interiores uniformes

Los tensores irregulares con dimensiones internas uniformes se codifican utilizando un tf.Tensor multidimensional para flat_values ​​(es decir, los values más internos).

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

Dimensiones no internas uniformes

Los tensores irregulares con dimensiones no internas uniformes se codifican dividiendo filas con 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