不规则张量

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看源代码 下载笔记本

API 文档: tf.RaggedTensor tf.ragged

设置

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

概述

您的数据有多种形状;您的张量也应该如此。不规则张量是 TensorFlow 中嵌套可变长度列表的等效项。它们使存储和处理具有非统一形状的数据变得容易,包括

  • 可变长度特征,例如电影中的演员列表。
  • 可变长度顺序输入的批次,例如句子或视频片段。
  • 分层输入,例如细分为部分、段落、句子和单词的文本文档。
  • 结构化输入中的各个字段,例如协议缓冲区。

您可以使用不规则张量做什么

不规则张量受一百多个 TensorFlow 操作的支持,包括数学操作(例如 tf.addtf.reduce_mean)、数组操作(例如 tf.concattf.tile)、字符串操作(例如 tf.strings.substr)、控制流操作(例如 tf.while_looptf.map_fn)等等

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.ragged 包文档

不规则张量受许多 TensorFlow API 支持,包括 Keras数据集tf.functionSavedModelstf.Example。有关更多信息,请查看下面的 TensorFlow API 部分。

与普通张量一样,您可以使用 Python 风格的索引来访问不规则张量的特定切片。有关更多信息,请参阅下面的 **索引** 部分。

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

与普通张量一样,您可以使用 Python 算术和比较运算符执行逐元素运算。有关更多信息,请查看下面的 **重载运算符** 部分。

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

如果您需要对 RaggedTensor 的值执行逐元素转换,您可以使用 tf.ragged.map_flat_values,它接受一个函数和一个或多个参数,并应用该函数来转换 RaggedTensor 的值。

times_two_plus_one = lambda x: x * 2 + 1
print(tf.ragged.map_flat_values(times_two_plus_one, digits))

不规则张量可以转换为嵌套的 Python list 和 NumPy array

digits.to_list()
digits.numpy()

构造不规则张量

构造不规则张量的最简单方法是使用 tf.ragged.constant,它构建对应于给定嵌套 Python list 或 NumPy arrayRaggedTensor

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

不规则张量也可以通过将扁平的 *值* 张量与 *行分区* 张量配对来构造,这些张量指示这些值应该如何被划分为行,使用工厂类方法,例如 tf.RaggedTensor.from_value_rowidstf.RaggedTensor.from_row_lengthstf.RaggedTensor.from_row_splits

tf.RaggedTensor.from_value_rowids

如果您知道每个值属于哪一行,那么您可以使用 value_rowids 行分区张量构建 RaggedTensor

value_rowids row-partitioning tensor

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

tf.RaggedTensor.from_row_lengths

如果您知道每行的长度,那么您可以使用 row_lengths 行分区张量。

row_lengths row-partitioning tensor

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

tf.RaggedTensor.from_row_splits

如果您知道每行开始和结束的索引,那么您可以使用 row_splits 行分区张量。

row_splits row-partitioning tensor

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

有关工厂方法的完整列表,请参阅 tf.RaggedTensor 类文档。

您可以在不规则张量中存储什么

与普通 Tensor 一样,RaggedTensor 中的值必须都具有相同的类型;并且值必须都位于相同的嵌套深度(张量的 *秩*)。

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

示例用例

以下示例演示了如何使用 RaggedTensor 为一批可变长度查询构建和组合一元语法和二元语法嵌入,使用特殊标记来表示每个句子的开头和结尾。有关此示例中使用的操作的更多详细信息,请查看 tf.ragged 包文档。

queries = tf.ragged.constant([['Who', 'is', 'Dan', 'Smith'],
                              ['Pause'],
                              ['Will', 'it', 'rain', 'later', 'today']])

# Create an embedding table.
num_buckets = 1024
embedding_size = 4
embedding_table = tf.Variable(
    tf.random.truncated_normal([num_buckets, embedding_size],
                       stddev=1.0 / math.sqrt(embedding_size)))

# Look up the embedding for each word.
word_buckets = tf.strings.to_hash_bucket_fast(queries, num_buckets)
word_embeddings = tf.nn.embedding_lookup(embedding_table, word_buckets)     # ①

# Add markers to the beginning and end of each sentence.
marker = tf.fill([queries.nrows(), 1], '#')
padded = tf.concat([marker, queries, marker], axis=1)                       # ②

# Build word bigrams and look up embeddings.
bigrams = tf.strings.join([padded[:, :-1], padded[:, 1:]], separator='+')   # ③

bigram_buckets = tf.strings.to_hash_bucket_fast(bigrams, num_buckets)
bigram_embeddings = tf.nn.embedding_lookup(embedding_table, bigram_buckets) # ④

# Find the average embedding for each sentence
all_embeddings = tf.concat([word_embeddings, bigram_embeddings], axis=1)    # ⑤
avg_embedding = tf.reduce_mean(all_embeddings, axis=1)                      # ⑥
print(avg_embedding)

Ragged tensor example

不规则维度和统一维度

**不规则维度** 是一个维度,其切片可能具有不同的长度。例如,rt=[[3, 1, 4, 1], [], [5, 9, 2], [6], []] 的内部(列)维度是不规则的,因为列切片 (rt[0, :], ..., rt[4, :]) 具有不同的长度。切片都具有相同长度的维度称为 *统一维度*。

不规则张量的最外层维度始终是统一的,因为它包含单个切片(因此,不存在切片长度不同的可能性)。其余维度可以是不规则的或统一的。例如,您可以使用形状为 [num_sentences, (num_words), embedding_size] 的不规则张量来存储一批句子中每个单词的词嵌入,其中 (num_words) 周围的括号表示该维度是不规则的。

Word embeddings using a ragged tensor

不规则张量可能具有多个不规则维度。例如,您可以使用形状为 [num_documents, (num_paragraphs), (num_sentences), (num_words)] 的张量来存储一批结构化文本文档(其中括号再次用于表示不规则维度)。

tf.Tensor 一样,不规则张量的 **** 是其总维度数(包括不规则维度和统一维度)。**潜在的不规则张量** 是一个值,它可能是一个 tf.Tensor 或一个 tf.RaggedTensor

在描述不规则张量的形状时,不规则维度通常用括号括起来。例如,如您在上面看到的,存储一批句子中每个单词的词嵌入的 3D 不规则张量的形状可以写成 [num_sentences, (num_words), embedding_size]

RaggedTensor.shape 属性为不规则张量返回一个 tf.TensorShape,其中不规则维度的尺寸为 None

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

方法 tf.RaggedTensor.bounding_shape 可用于为给定的 RaggedTensor 找到一个紧密的边界形状。

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

不规则与稀疏

不规则张量不应被视为稀疏张量的一种类型。特别是,稀疏张量是 *用于 tf.Tensor 的高效编码*,以紧凑的格式对相同数据进行建模;但稀疏张量是 *对 tf.Tensor 的扩展*,它对一类扩展的数据进行建模。在定义操作时,这种差异至关重要。

  • 将操作应用于稀疏或密集张量始终应该给出相同的结果。
  • 将操作应用于不规则或稀疏张量可能会给出不同的结果。

作为一个说明性的例子,考虑数组操作(如 concatstacktile)是如何为不规则张量和稀疏张量定义的。连接不规则张量会将每行连接起来,形成一个具有组合长度的单行。

Concatenating ragged tensors

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

但是,连接稀疏张量等同于连接相应的密集张量,如下面的示例所示(其中 Ø 表示缺失值)。

Concatenating sparse tensors

sparse_x = ragged_x.to_sparse()
sparse_y = ragged_y.to_sparse()
sparse_result = tf.sparse.concat(sp_inputs=[sparse_x, sparse_y], axis=1)
print(tf.sparse.to_dense(sparse_result, ''))

作为另一个说明这种区别为何重要的例子,请考虑操作(如 tf.reduce_mean)中“每行的平均值”的定义。对于不规则张量,行的平均值是行值的总和除以行的宽度。但是对于稀疏张量,行的平均值是行值的总和除以稀疏张量的总宽度(大于或等于最长行的宽度)。

TensorFlow API

Keras

tf.keras 是 TensorFlow 用于构建和训练深度学习模型的高级 API。通过在 tf.keras.Inputtf.keras.layers.InputLayer 上设置 ragged=True,可以将不规则张量作为输入传递给 Keras 模型。不规则张量也可以在 Keras 层之间传递,并由 Keras 模型返回。以下示例展示了一个使用不规则张量训练的玩具 LSTM 模型。

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

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

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

keras_model.compile(loss='binary_crossentropy', optimizer='rmsprop')
keras_model.fit(hashed_words, is_question, epochs=5)
print(keras_model.predict(hashed_words))

tf.Example

tf.Example 是 TensorFlow 数据的标准 protobuf 编码。使用 tf.Example 编码的数据通常包含可变长度特征。例如,以下代码定义了一批四个 tf.Example 消息,它们具有不同的特征长度。

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

您可以使用 tf.io.parse_example 解析此编码数据,它接受一个序列化字符串张量和一个特征规范字典,并返回一个字典,该字典将特征名称映射到张量。要将可变长度特征读入不规则张量,您只需在特征规范字典中使用 tf.io.RaggedFeature

feature_specification = {
    'colors': tf.io.RaggedFeature(tf.string),
    'lengths': tf.io.RaggedFeature(tf.int64),
}
feature_tensors = tf.io.parse_example(example_batch, feature_specification)
for name, value in feature_tensors.items():
  print("{}={}".format(name, value))

tf.io.RaggedFeature 也可以用于读取具有多个不规则维度的特征。有关详细信息,请参阅 API 文档

数据集

tf.data 是一个 API,它使您能够从简单、可重用的部分构建复杂的输入管道。其核心数据结构是 tf.data.Dataset,它表示元素的序列,其中每个元素包含一个或多个组件。

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

使用不规则张量构建数据集

可以使用与用于从 tf.Tensor 或 NumPy array 构建数据集相同的方法从不规则张量构建数据集,例如 Dataset.from_tensor_slices

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

使用不规则张量对数据集进行批处理和取消批处理

可以使用 Dataset.batch 方法对具有不规则张量的数据集进行批处理(将 *n* 个连续元素组合成一个元素)。

batched_dataset = dataset.batch(2)
print_dictionary_dataset(batched_dataset)

相反,可以使用 Dataset.unbatch 将批处理数据集转换为扁平数据集。

unbatched_dataset = batched_dataset.unbatch()
print_dictionary_dataset(unbatched_dataset)

使用可变长度非不规则张量对数据集进行批处理

如果您有一个包含非不规则张量的数据集,并且张量长度在元素之间变化,那么您可以通过应用 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)

使用不规则张量转换数据集

您也可以使用 Dataset.map 在 Datasets 中创建或转换不规则张量。

def transform_lengths(features):
  return {
      'mean_length': tf.math.reduce_mean(features['lengths']),
      'length_ranges': tf.ragged.range(features['lengths'])}
transformed_dataset = dataset.map(transform_lengths)
print_dictionary_dataset(transformed_dataset)

tf.function

tf.function 是一个装饰器,它为 Python 函数预先计算 TensorFlow 图,这可以大幅提高 TensorFlow 代码的性能。不规则张量可以与 @tf.function 装饰的函数透明地使用。例如,以下函数适用于不规则张量和非不规则张量。

@tf.function
def make_palindrome(x, axis):
  return tf.concat([x, tf.reverse(x, [axis])], axis)
make_palindrome(tf.constant([[1, 2], [3, 4], [5, 6]]), axis=1)
make_palindrome(tf.ragged.constant([[1, 2], [3], [4, 5, 6]]), axis=1)

如果您希望为 tf.function 显式指定 input_signature,则可以使用 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.function 生成的单个跟踪图。不规则张量可以与具体函数透明地使用。

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

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

SavedModels

一个 SavedModel 是一个序列化 TensorFlow 程序,包括权重和计算。它可以从 Keras 模型或自定义模型构建。在这两种情况下,不规则张量都可以与 SavedModel 定义的函数和方法透明地使用。

示例:保存 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)

示例:保存自定义模型

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

重载运算符

RaggedTensor 类重载了标准 Python 算术和比较运算符,使执行基本的逐元素数学运算变得容易。

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

由于重载的运算符执行逐元素计算,因此所有二元运算的输入必须具有相同的形状或可广播到相同的形状。在最简单的广播情况下,单个标量与不规则张量中的每个值逐元素组合。

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

有关更高级案例的讨论,请查看“广播”部分。

不规则张量重载了与普通 Tensor 相同的运算符集:一元运算符 -~abs();以及二元运算符 +-*///%**&|^==<<=>>=

索引

不规则张量支持 Python 风格的索引,包括多维索引和切片。以下示例演示了使用 2D 和 3D 不规则张量进行不规则张量索引。

索引示例:2D 不规则张量

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

索引示例:3D 不规则张量

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

RaggedTensor 支持多维索引和切片,但有一个限制:不允许索引到不规则维度。这种情况存在问题,因为指示的值可能存在于某些行中,而不存在于其他行中。在这种情况下,不清楚您应该 (1) 抛出 IndexError;(2) 使用默认值;或者 (3) 跳过该值并返回一个行数少于您开始的行数的张量。遵循 Python 的指导原则(“在面对歧义时,拒绝猜测的诱惑”),此操作目前不允许。

张量类型转换

RaggedTensor 类定义了可用于在 RaggedTensortf.Tensortf.SparseTensors 之间转换的方法。

ragged_sentences = tf.ragged.constant([
    ['Hi'], ['Welcome', 'to', 'the', 'fair'], ['Have', 'fun']])
# RaggedTensor -> Tensor
print(ragged_sentences.to_tensor(default_value='', shape=[None, 10]))
# Tensor -> RaggedTensor
x = [[1, 3, -1, -1], [2, -1, -1, -1], [4, 5, 8, 9]]
print(tf.RaggedTensor.from_tensor(x, padding=-1))
#RaggedTensor -> SparseTensor
print(ragged_sentences.to_sparse())
# SparseTensor -> RaggedTensor
st = tf.SparseTensor(indices=[[0, 0], [2, 0], [2, 1]],
                     values=['a', 'b', 'c'],
                     dense_shape=[3, 3])
print(tf.RaggedTensor.from_sparse(st))

评估不规则张量

要访问不规则张量中的值,您可以

  1. 使用 tf.RaggedTensor.to_list 将不规则张量转换为嵌套的 Python 列表。
  2. 使用 tf.RaggedTensor.numpy 将不规则张量转换为 NumPy 数组,其值是嵌套的 NumPy 数组。
  3. 使用 tf.RaggedTensor.valuestf.RaggedTensor.row_splits 属性,或行分区方法(如 tf.RaggedTensor.row_lengthstf.RaggedTensor.value_rowids)将不规则张量分解为其组成部分。
  4. 使用 Python 索引从不规则张量中选择值。
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())

不规则形状

张量的形状指定每个轴的大小。例如,[[1, 2], [3, 4], [5, 6]] 的形状为 [3, 2],因为有 3 行和 2 列。TensorFlow 有两种独立但相关的描述形状的方式。

  • 静态形状:关于轴大小的信息,这些信息是在静态情况下已知的(例如,在跟踪 tf.function 时)。可以部分指定。

  • 动态形状:轴大小的运行时信息。

静态形状

张量的静态形状包含有关其轴大小的信息,这些信息在图构建时已知。对于 tf.Tensortf.RaggedTensor,它可以使用 .shape 属性获得,并使用 tf.TensorShape 编码。

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

不规则维度的静态形状始终为 None(即未指定)。但是,反之则不成立——如果 TensorShape 维度为 None,则可能表示该维度是不规则的,它可能表示该维度是统一的,但其大小在静态情况下未知。

动态形状

张量的动态形状包含有关其轴大小的信息,这些信息在运行图时已知。它使用 tf.shape 操作构建。对于 tf.Tensortf.shape 返回形状作为 1D 整数 Tensor,其中 tf.shape(x)[i] 是轴 i 的大小。

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

但是,1D Tensor 无法充分表达不规则张量的形状。相反,不规则张量的动态形状使用专用类型 tf.experimental.DynamicRaggedShape 编码。在以下示例中,由 tf.shape(rt) 返回的 DynamicRaggedShape 指示不规则张量有 4 行,长度分别为 1、3、0 和 2。

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

动态形状:操作

DynamicRaggedShape 可与大多数期望形状的 TensorFlow 操作一起使用,包括 tf.reshapetf.zerostf.onestf.filltf.broadcast_dynamic_shapetf.broadcast_to

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

动态形状:索引和切片

DynamicRaggedShape 也可以被索引以获取统一维度的尺寸。例如,我们可以使用 tf.shape(rt)[0] 找到不规则张量中的行数(就像我们对非不规则张量所做的那样)。

rt_shape[0]

但是,使用索引尝试检索不规则维度的尺寸会导致错误,因为它没有单个尺寸。(由于 RaggedTensor 会跟踪哪些轴是不规则的,因此此错误仅在急切执行或跟踪 tf.function 时抛出;它永远不会在执行具体函数时抛出。)

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

DynamicRaggedShape 也可以被切片,只要切片以轴 0 开始,或者仅包含密集维度。

rt_shape[:1]

动态形状:编码

DynamicRaggedShape 使用两个字段编码

  • inner_shape:一个整数向量,给出密集 tf.Tensor 的形状。
  • row_partitions:一个 tf.experimental.RowPartition 对象列表,描述了该内部形状的最外层维度应如何被分区以添加不规则轴。

有关行分区的更多信息,请参阅下面的“RaggedTensor 编码”部分,以及 tf.experimental.RowPartition 的 API 文档。

动态形状:构造

DynamicRaggedShape 通常是通过将 tf.shape 应用于 RaggedTensor 来构造的,但它也可以直接构造。

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

如果所有行的长度在静态情况下已知,则 DynamicRaggedShape.from_lengths 也可用于构造动态不规则形状。(这主要对测试和演示代码有用,因为不规则维度的长度在静态情况下很少已知)。

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

广播

广播是使具有不同形状的张量具有兼容形状以进行逐元素操作的过程。有关广播的更多背景信息,请参阅

将两个输入 xy 广播到具有兼容形状的基本步骤是

  1. 如果 xy 的维度数量不同,则添加外部维度(大小为 1)直到它们相同。

  2. 对于 xy 大小不同的每个维度

  • 如果 xy 在维度 d 中的大小为 1,则跨维度 d 重复其值以匹配另一个输入的大小。
  • 否则,引发异常(xy 不兼容广播)。

在张量在统一维度中的大小是单个数字(跨该维度的切片大小);而张量在不规则维度中的大小是切片长度列表(对于跨该维度的所有切片)。

广播示例

# x       (2D ragged):  2 x (num_rows)
# y       (scalar)
# result  (2D ragged):  2 x (num_rows)
x = tf.ragged.constant([[1, 2], [3]])
y = 3
print(x + y)
# x         (2d ragged):  3 x (num_rows)
# y         (2d tensor):  3 x          1
# Result    (2d ragged):  3 x (num_rows)
x = tf.ragged.constant(
   [[10, 87, 12],
    [19, 53],
    [12, 32]])
y = [[1000], [2000], [3000]]
print(x + y)
# x      (3d ragged):  2 x (r1) x 2
# y      (2d ragged):         1 x 1
# Result (3d ragged):  2 x (r1) x 2
x = tf.ragged.constant(
    [[[1, 2], [3, 4], [5, 6]],
     [[7, 8]]],
    ragged_rank=1)
y = tf.constant([[10]])
print(x + y)
# x      (3d ragged):  2 x (r1) x (r2) x 1
# y      (1d tensor):                    3
# Result (3d ragged):  2 x (r1) x (r2) x 3
x = tf.ragged.constant(
    [
        [
            [[1], [2]],
            [],
            [[3]],
            [[4]],
        ],
        [
            [[5], [6]],
            [[7]]
        ]
    ],
    ragged_rank=2)
y = tf.constant([10, 20, 30])
print(x + y)

以下是一些不广播的形状示例

# x      (2d ragged): 3 x (r1)
# y      (2d tensor): 3 x    4  # trailing dimensions do not match
x = tf.ragged.constant([[1, 2], [3, 4, 5, 6], [7]])
y = tf.constant([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
try:
  x + y
except tf.errors.InvalidArgumentError as exception:
  print(exception)
# x      (2d ragged): 3 x (r1)
# y      (2d ragged): 3 x (r2)  # ragged dimensions do not match.
x = tf.ragged.constant([[1, 2, 3], [4], [5, 6]])
y = tf.ragged.constant([[10, 20], [30, 40], [50]])
try:
  x + y
except tf.errors.InvalidArgumentError as exception:
  print(exception)
# x      (3d ragged): 3 x (r1) x 2
# y      (3d ragged): 3 x (r1) x 3  # trailing dimensions do not match
x = tf.ragged.constant([[[1, 2], [3, 4], [5, 6]],
                        [[7, 8], [9, 10]]])
y = tf.ragged.constant([[[1, 2, 0], [3, 4, 0], [5, 6, 0]],
                        [[7, 8, 0], [9, 10, 0]]])
try:
  x + y
except tf.errors.InvalidArgumentError as exception:
  print(exception)

RaggedTensor 编码

Ragged 张量使用 RaggedTensor 类进行编码。在内部,每个 RaggedTensor 包含

  • 一个 values 张量,它将可变长度的行连接成一个扁平列表。
  • 一个 row_partition,它指示这些扁平化的值如何划分为行。

RaggedTensor encoding

可以使用四种不同的编码来存储 row_partition

  • row_splits 是一个整数向量,指定行之间的分割点。
  • value_rowids 是一个整数向量,指定每个值的行索引。
  • row_lengths 是一个整数向量,指定每行的长度。
  • uniform_row_length 是一个整数标量,指定所有行的单个长度。

row_partition encodings

一个整数标量 nrows 也可以包含在 row_partition 编码中,以说明具有 value_rowids 的空尾部行或具有 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)

选择哪种编码用于行分区由不规则张量在内部管理,以提高某些环境中的效率。特别是,不同行分区方案的一些优缺点是

  • 高效索引row_splits 编码支持对不规则张量进行常数时间索引和切片。
  • 高效连接row_lengths 编码在连接不规则张量时效率更高,因为当两个张量连接在一起时,行长度不会改变。
  • 小编码尺寸value_rowids 编码在存储具有大量空行的稀疏张量时效率更高,因为张量的大小仅取决于值的总数。另一方面,row_splitsrow_lengths 编码在存储具有较长行的稀疏张量时效率更高,因为它们每个行只需要一个标量值。
  • 兼容性value_rowids 方案与操作使用的 分段 格式匹配,例如 tf.segment_sumrow_limits 方案与诸如 tf.sequence_mask 之类的操作使用的格式匹配。
  • 统一维度:如下所述,uniform_row_length 编码用于编码具有统一维度的稀疏张量。

多个不规则维度

具有多个不规则维度的稀疏张量通过使用嵌套的 RaggedTensor 来编码 values 张量。每个嵌套的 RaggedTensor 添加一个不规则维度。

Encoding of a ragged tensor with multiple ragged dimensions (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.from_nested_row_splits 可用于通过提供 row_splits 张量列表直接构造具有多个不规则维度的 RaggedTensor

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)

不规则秩和平坦值

稀疏张量的不规则秩 是底层 values 张量被分区的次数(即 RaggedTensor 对象的嵌套深度)。最里面的 values 张量被称为它的flat_values。在以下示例中,conversations 的 ragged_rank=3,其 flat_values 是一个包含 24 个字符串的 1D Tensor

# shape = [batch, (paragraph), (sentence), (word)]
conversations = tf.ragged.constant(
    [[[["I", "like", "ragged", "tensors."]],
      [["Oh", "yeah?"], ["What", "can", "you", "use", "them", "for?"]],
      [["Processing", "variable", "length", "data!"]]],
     [[["I", "like", "cheese."], ["Do", "you?"]],
      [["Yes."], ["I", "do."]]]])
conversations.shape
assert conversations.ragged_rank == len(conversations.nested_row_splits)
conversations.ragged_rank  # Number of partitioned dimensions.
conversations.flat_values.numpy()

统一内部维度

具有统一内部维度的稀疏张量通过使用多维 tf.Tensor 来编码 flat_values(即最里面的 values)。

Encoding of ragged tensors with uniform inner dimensions

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

统一非内部维度

具有统一非内部维度的稀疏张量通过使用 uniform_row_length 对行进行分区来编码。

Encoding of ragged tensors with uniform non-inner dimensions

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