![]() | ![]() | ![]() | ![]() |
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
números:
digits.to_list()
[[3, 1, 4, 1], [], [5, 9, 2], [6], []]
digits.numpy()
/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py:2012: 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)
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
anidada de Python 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 indican cómo esos valores deben dividirse en filas, utilizando 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
:
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
:
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
:
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 clasetf.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
0b79e0320 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 bigramas para un lote de consultas de longitud variable, usando marcadores especiales para el comienzo 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.07226902 -0.03359559 -0.03866251 -0.04422581] [-0.10786405 -0.38157812 -0.11010554 -0.07145723] [ 0.18454719 -0.05851733 -0.13204828 0.14964654]], shape=(3, 4), dtype=float32)
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 consiste en 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.
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 untf.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_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_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 los devuelven. 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:437: 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: 2.7506 Epoch 2/5 1/1 [==============================] - 0s 16ms/step - loss: 1.9755 Epoch 3/5 1/1 [==============================] - 0s 15ms/step - loss: 1.8869 Epoch 4/5 1/1 [==============================] - 0s 16ms/step - loss: 1.8256 Epoch 5/5 1/1 [==============================] - 0s 19ms/step - loss: 1.7605 [[0.03648993] [0.00550611] [0.02936834] [0.0077349 ]]
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 estf.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, 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
. Los tensores irregulares se pueden utilizar de forma transparente con funciones concretas.
@tf.function
def increment(x):
return x + 1
rt = tf.ragged.constant([[1, 2], [3], [4, 5, 6]])
cf = increment.get_concrete_function(rt)
print(cf(rt))
<tf.RaggedTensor [[2, 3], [4], [5, 6, 7]]>
Modelos 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 utilizar 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)
INFO:tensorflow:Assets written to: /tmp/tmpyr1dlj_r/assets <tf.Tensor: shape=(4, 1), dtype=float32, numpy= array([[0.03648993], [0.00550611], [0.02936834], [0.0077349 ]], 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/tmpen_1gfeg/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 de 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 el corte 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:
- Utilice
tf.RaggedTensor.to_list()
para convertir el tensor irregular en una lista de Python anidada. - Utilice
tf.RaggedTensor.numpy()
para convertir el tensor irregular en una matriz numpy cuyos valores son matrices numpy anidadas. - Descomponga el tensor irregular en sus componentes, utilizando las propiedades
tf.RaggedTensor.values
ytf.RaggedTensor.row_splits
, o métodos detf.RaggedTensor.row_lengths()
comotf.RaggedTensor.row_lengths()
ytf.RaggedTensor.value_rowids()
. - 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] /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py:2012: 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)
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:
Si
x
yy
no tienen el mismo número de dimensiones, a continuación, añadir dimensiones exteriores (con tamaño 1) hasta que lo hagan.Para cada dimensión donde
x
yy
tienen diferentes tamaños:Si
x
oy
tienen un tamaño de1
en la dimensiónd
, y luego repetir sus valores a través de dimensiónd
para que coincida con el tamaño de la otra entrada.De lo contrario, lanzar una excepción (
x
, yy
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.
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.
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 utilizar 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 codificacionesrow_splits
yrow_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 comotf.segment_sum
. El esquemarow_limits
coincide con el formato utilizado por operaciones comotf.sequence_mask
.Dimensiones uniformes : como se explica 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.
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).
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
.
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