Tensori irregolari

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza la fonte su GitHubScarica taccuino

Documentazione API: tf.RaggedTensor tf.ragged

Impostare

import math
import tensorflow as tf

Panoramica

I tuoi dati hanno molte forme; anche i tuoi tensori dovrebbero. Tensori Ragged sono il tensorflow equivalente di liste di lunghezza variabile nidificati. Semplificano l'archiviazione e l'elaborazione dei dati con forme non uniformi, tra cui:

  • Lungometraggi a lunghezza variabile, come il set di attori in un film.
  • Lotti di input sequenziali di lunghezza variabile, come frasi o clip video.
  • Input gerarchici, come documenti di testo suddivisi in sezioni, paragrafi, frasi e parole.
  • Campi individuali in input strutturati, come i buffer di protocollo.

Cosa puoi fare con un tensore irregolare

Tensori frastagliati sono supportati da più di cento operazioni tensorflow, comprese le operazioni matematiche (come tf.add e tf.reduce_mean ), le operazioni di matrice (come tf.concat e tf.tile ), op gestione delle stringhe (come tf.substr ), controllo operazioni (come flusso tf.while_loop e tf.map_fn ), e molti altri:

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

Esistono anche numerosi metodi e operazioni specifici per i tensori irregolari, inclusi metodi di fabbrica, metodi di conversione e operazioni di mappatura del valore. Per un elenco dei ops supportati, vedere il tf.ragged documentazione del pacchetto.

Tensori Ragged sono supportati da molte API tensorflow, tra Keras , Dataset , tf.function , SavedModels e tf.Example . Per ulteriori informazioni, consultare la sezione sulla tensorflow API di seguito.

Come con i normali tensori, puoi utilizzare l'indicizzazione in stile Python per accedere a sezioni specifiche di un tensore irregolare. Per ulteriori informazioni, consultare la sezione relativa indicizzazione di seguito.

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

E proprio come i normali tensori, puoi usare l'aritmetica Python e gli operatori di confronto per eseguire operazioni elementwise. Per ulteriori informazioni, consultare la sezione sugli operatori di overload di seguito.

print(digits + 3)
<tf.RaggedTensor [[6, 4, 7, 4], [], [8, 12, 5], [9], []]>
print(digits + tf.ragged.constant([[1, 2, 3, 4], [], [5, 6, 7], [8], []]))
<tf.RaggedTensor [[4, 3, 7, 5], [], [10, 15, 9], [14], []]>

Se è necessario eseguire una trasformazione elementwise ai valori di un RaggedTensor , è possibile utilizzare tf.ragged.map_flat_values , che prende una funzione più uno o più argomenti, e si applica la funzione di trasformare la RaggedTensor valori s'.

times_two_plus_one = lambda x: x * 2 + 1
print(tf.ragged.map_flat_values(times_two_plus_one, digits))
<tf.RaggedTensor [[7, 3, 9, 3], [], [11, 19, 5], [13], []]>

Tensori Ragged possono essere convertiti in annidata Python list s e NumPy array s:

digits.to_list()
[[3, 1, 4, 1], [], [5, 9, 2], [6], []]
digits.numpy()
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py:2063: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  return np.array(rows)
array([array([3, 1, 4, 1], dtype=int32), array([], dtype=int32),
       array([5, 9, 2], dtype=int32), array([6], dtype=int32),
       array([], dtype=int32)], dtype=object)

Costruire un tensore irregolare

Il modo più semplice per costruire un tensore frastagliato utilizza tf.ragged.constant , che costruisce la RaggedTensor corrispondente ad un dato Python annidato list o NumPy array :

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

Tensori laceri possono anche essere costruiti accoppiando valori piane tensori con tensori fila partizionamento indica come questi valori dovrebbero essere divisi in righe, utilizzando classmethods fabbrica come tf.RaggedTensor.from_value_rowids , tf.RaggedTensor.from_row_lengths e tf.RaggedTensor.from_row_splits .

tf.RaggedTensor.from_value_rowids

Se si conosce quale riga ogni valore appartiene, allora si può costruire un RaggedTensor utilizzando un value_rowids tensore row-partizionamento:

value_rowids tensore di partizionamento delle righe

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

tf.RaggedTensor.from_row_lengths

Se si sa per quanto tempo ogni riga è, quindi è possibile utilizzare un row_lengths tensore fila-partizionamento:

row_lengths tensore di partizionamento delle righe

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

tf.RaggedTensor.from_row_splits

Se si conosce l'indice in cui ogni riga inizia e finisce, quindi è possibile utilizzare un row_splits tensore fila-partizionamento:

row_splits tensore di partizionamento delle righe

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

Vedere la tf.RaggedTensor documentazione di classe per una lista completa dei metodi di fabbrica.

Cosa puoi immagazzinare in un tensore cencioso

Come con normale Tensor s, i valori in una RaggedTensor devono avere lo stesso tipo; ei valori devono essere tutti alla stessa profondità di annidamento (il rango del tensore):

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

Esempio di caso d'uso

L'esempio seguente dimostra come RaggedTensor s può essere utilizzato per costruire e combinare unigram e sui bigrammi incastri per una serie di query di lunghezza variabile, utilizzando i marcatori speciali per l'inizio e la fine di ogni frase. Per maggiori dettagli sui ops utilizzati in questo esempio, controllare la tf.ragged documentazione del pacchetto.

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

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

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

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

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

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

# Find the average embedding for each sentence
all_embeddings = tf.concat([word_embeddings, bigram_embeddings], axis=1)    # ⑤
avg_embedding = tf.reduce_mean(all_embeddings, axis=1)                      # ⑥
print(avg_embedding)
tf.Tensor(
[[-0.14285272  0.02908629 -0.16327512 -0.14529026]
 [-0.4479212  -0.35615516  0.17110227  0.2522229 ]
 [-0.1987868  -0.13152348 -0.0325102   0.02125177]], shape=(3, 4), dtype=float32)

Esempio di tensore irregolare

Dimensioni irregolari e uniformi

Una dimensione incompleta è una dimensione cui fette possono avere lunghezze differenti. Ad esempio, il (colonna) dimensione interna di rt=[[3, 1, 4, 1], [], [5, 9, 2], [6], []] è RAGGED, poiché le fette di colonna ( rt[0, :] , ..., rt[4, :] ) hanno lunghezze diverse. Dimensioni cui fette hanno tutti la stessa lunghezza sono chiamati dimensioni uniformi.

La dimensione più esterna di un tensore frastagliato è sempre uniforme, poiché consiste in una singola fetta (e, quindi, non c'è possibilità di lunghezze di fetta differenti). Le restanti dimensioni possono essere irregolari o uniformi. Ad esempio, è possibile memorizzare la parola incastri per ogni parola in un lotto di frasi utilizzando un tensore frastagliato con figura [num_sentences, (num_words), embedding_size] , dove le parentesi intorno (num_words) indicano che la dimensione sia irregolare.

Incorporamenti di parole usando un tensore irregolare

I tensori irregolari possono avere più dimensioni irregolari. Ad esempio, è possibile memorizzare una serie di documenti di testo strutturato utilizzando un tensore con figura [num_documents, (num_paragraphs), (num_sentences), (num_words)] (dove ancora una volta parentesi sono usati per indicare le dimensioni cenciosi).

Come con tf.Tensor , la posizione di un tensore frastagliato è il suo numero totale delle dimensioni (comprendenti sia le dimensioni laceri ed uniformi). Un tensore potenzialmente ragged è un valore che può essere sia un tf.Tensor o un tf.RaggedTensor .

Quando si descrive la forma di un RaggedTensor, le dimensioni irregolari sono convenzionalmente indicate racchiudendole tra parentesi. Per esempio, come si è visto sopra, la forma di un RaggedTensor 3D che incastri i negozi di parole per ogni parola in un lotto di frasi possono essere scritte come [num_sentences, (num_words), embedding_size] .

Il RaggedTensor.shape attributo restituisce un tf.TensorShape per un tensore lacero cui dimensioni laceri hanno dimensioni None :

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

Il metodo tf.RaggedTensor.bounding_shape può essere utilizzato per trovare una forma di delimitazione stretto per un dato RaggedTensor :

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

Ragged vs sparse

Un tensore lacero non deve essere pensato come un tipo di tensore sparse. In particolare, tensori sparse sono codifiche efficienti per tf.Tensor quel modello gli stessi dati in un formato compatto; ma tensore lacero è un'estensione di tf.Tensor che i modelli di una classe estesa di dati. Questa differenza è cruciale quando si definiscono le operazioni:

  • L'applicazione di un op a un tensore sparso o denso dovrebbe sempre dare lo stesso risultato.
  • L'applicazione di un op a un tensore irregolare o sparso può dare risultati diversi.

Come esempio illustrativo, considerare come operazioni di matrice come concat , stack , e tile sono definiti per laceri vs sparse tensori. La concatenazione di tensori irregolari unisce ogni riga per formare una singola riga con la lunghezza combinata:

Concatenazione di tensori irregolari

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

Tuttavia, concatenare tensori sparsi equivale a concatenare i corrispondenti tensori densi, come illustrato dal seguente esempio (dove Ø indica valori mancanti):

Concatenazione di tensori sparsi

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)

Per un altro esempio del perché questa distinzione è importante, considerare la definizione di “valore medio di ogni riga” per un op come tf.reduce_mean . Per un tensore irregolare, il valore medio per una riga è la somma dei valori della riga divisa per la larghezza della riga. Ma per un tensore sparso, il valore medio per una riga è la somma dei valori della riga divisa per la larghezza complessiva del tensore sparso (che è maggiore o uguale alla larghezza della riga più lunga).

API TensorFlow

Keras

tf.keras è API di alto livello di tensorflow per la costruzione e la formazione di modelli di apprendimento profondo. Tensori laceri possono essere passati come fattori del modello Keras dalla regolazione ragged=True su tf.keras.Input o tf.keras.layers.InputLayer . I tensori irregolari possono anche essere passati tra i livelli Keras e restituiti dai modelli Keras. L'esempio seguente mostra un modello LSTM giocattolo che viene addestrato utilizzando tensori irregolari.

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

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

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

keras_model.compile(loss='binary_crossentropy', optimizer='rmsprop')
keras_model.fit(hashed_words, is_question, epochs=5)
print(keras_model.predict(hashed_words))
WARNING:tensorflow:Layer lstm will not use cuDNN kernels since it doesn't meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.
Epoch 1/5
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/indexed_slices.py:449: UserWarning: Converting sparse IndexedSlices(IndexedSlices(indices=Tensor("gradient_tape/sequential/lstm/RaggedToTensor/boolean_mask_1/GatherV2:0", shape=(None,), dtype=int32), values=Tensor("gradient_tape/sequential/lstm/RaggedToTensor/boolean_mask/GatherV2:0", shape=(None, 16), dtype=float32), dense_shape=Tensor("gradient_tape/sequential/lstm/RaggedToTensor/Shape:0", shape=(2,), dtype=int32))) to a dense Tensor of unknown shape. This may consume a large amount of memory.
  "shape. This may consume a large amount of memory." % value)
1/1 [==============================] - 2s 2s/step - loss: 3.1269
Epoch 2/5
1/1 [==============================] - 0s 18ms/step - loss: 2.1197
Epoch 3/5
1/1 [==============================] - 0s 19ms/step - loss: 2.0196
Epoch 4/5
1/1 [==============================] - 0s 20ms/step - loss: 1.9371
Epoch 5/5
1/1 [==============================] - 0s 18ms/step - loss: 1.8857
[[0.02800461]
 [0.00945962]
 [0.02283431]
 [0.00252927]]

tf.Esempio

tf.Example è uno standard protobuf codifica per i dati tensorflow. I dati codificati con tf.Example s spesso include caratteristiche di lunghezza variabile. Ad esempio, il codice seguente definisce una serie di quattro tf.Example messaggi con differenti lunghezze includono:

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

È possibile analizzare questi dati codificati utilizzando tf.io.parse_example , che prende un tensore di stringhe serializzato e un dizionario specifica funzione, e restituisce una funzione di mappatura del dizionario nomi da tensori. Per leggere le caratteristiche di lunghezza variabile in tensori laceri, è sufficiente utilizzare tf.io.RaggedFeature nel dizionario specifica funzione:

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 può anche essere usato per leggere caratteristiche con dimensioni multiple frastagliati. Per i dettagli, consultare la documentazione API .

Set di dati

tf.data è un'API che consente di costruire oleodotti ingresso complessi da semplici, pezzi riutilizzabili. La sua struttura dati nucleo è tf.data.Dataset , che rappresenta una sequenza di elementi, in cui ogni elemento è costituito da uno o più componenti.

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

Costruire set di dati con tensori irregolari

Dati possono essere costruiti da tensori laceri usando gli stessi metodi che vengono utilizzati per costruirli da tf.Tensor s o NumPy array s, come 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]

Batching e unbatch Dataset con tensori irregolari

Serie di dati con tensori laceri possono essere dosate (che combina n elementi consecutivi in un unico elementi) utilizzando il Dataset.batch metodo.

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

Al contrario, un set di dati dosata può essere trasformato in un gruppo di dati piano usando 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]

Raccolta di set di dati con tensori non irregolari di lunghezza variabile

Se si dispone di un set di dati che contiene tensori non laceri e tensore lunghezze variano tra elementi, allora si può ammucchiare tali tensori non frastagliati in tensori laceri applicando il dense_to_ragged_batch trasformazione:

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

Trasformare set di dati con tensori irregolari

È inoltre possibile creare o trasformare tensori laceri a set di dati utilizzando 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 è un decoratore che precomputes tensorflow grafici per funzioni Python, che può migliorare notevolmente il rendimento del codice tensorflow. Tensori Ragged possono essere utilizzati in modo trasparente con @tf.function funzioni -decorated. Ad esempio, la seguente funzione funziona con tensori irregolari e non irregolari:

@tf.function
def make_palindrome(x, axis):
  return tf.concat([x, tf.reverse(x, [axis])], axis)
make_palindrome(tf.constant([[1, 2], [3, 4], [5, 6]]), axis=1)
<tf.Tensor: shape=(3, 4), dtype=int32, numpy=
array([[1, 2, 2, 1],
       [3, 4, 4, 3],
       [5, 6, 6, 5]], dtype=int32)>
make_palindrome(tf.ragged.constant([[1, 2], [3], [4, 5, 6]]), axis=1)
2021-09-22 20:36:51.018367: W tensorflow/core/grappler/optimizers/loop_optimizer.cc:907] Skipping loop optimization for Merge node with control input: RaggedConcat/assert_equal_1/Assert/AssertGuard/branch_executed/_9
<tf.RaggedTensor [[1, 2, 2, 1], [3, 3], [4, 5, 6, 6, 5, 4]]>

Se si desidera specificare esplicitamente input_signature per il tf.function , allora si può farlo utilizzando 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)>)

Funzioni concrete

Funzioni calcestruzzo incapsulano singoli diagrammi tracciati che si creano tf.function . I tensori irregolari possono essere utilizzati in modo trasparente con funzioni concrete.

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

Modelli salvati

Un SavedModel è un programma tensorflow serializzato, inclusi sia pesi e calcolo. Può essere costruito da un modello Keras o da un modello personalizzato. In entrambi i casi, i tensori irregolari possono essere utilizzati in modo trasparente con le funzioni e i metodi definiti da un SavedModel.

Esempio: salvataggio di un modello Keras

import tempfile

keras_module_path = tempfile.mkdtemp()
tf.saved_model.save(keras_model, keras_module_path)
imported_model = tf.saved_model.load(keras_module_path)
imported_model(hashed_words)
2021-09-22 20:36:52.069689: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
WARNING:absl:Function `_wrapped_model` contains input name(s) args_0 with unsupported characters which will be renamed to args_0_1 in the SavedModel.
INFO:tensorflow:Assets written to: /tmp/tmp114axtt7/assets
INFO:tensorflow:Assets written to: /tmp/tmp114axtt7/assets
<tf.Tensor: shape=(4, 1), dtype=float32, numpy=
array([[0.02800461],
       [0.00945962],
       [0.02283431],
       [0.00252927]], dtype=float32)>

Esempio: salvataggio di un modello personalizzato

class CustomModule(tf.Module):
  def __init__(self, variable_value):
    super(CustomModule, self).__init__()
    self.v = tf.Variable(variable_value)

  @tf.function
  def grow(self, x):
    return x * self.v

module = CustomModule(100.0)

# Before saving a custom model, you must ensure that concrete functions are
# built for each input signature that you will need.
module.grow.get_concrete_function(tf.RaggedTensorSpec(shape=[None, None],
                                                      dtype=tf.float32))

custom_module_path = tempfile.mkdtemp()
tf.saved_model.save(module, custom_module_path)
imported_model = tf.saved_model.load(custom_module_path)
imported_model.grow(tf.ragged.constant([[1.0, 4.0, 3.0], [2.0]]))
INFO:tensorflow:Assets written to: /tmp/tmpnn4u8dy5/assets
INFO:tensorflow:Assets written to: /tmp/tmpnn4u8dy5/assets
<tf.RaggedTensor [[100.0, 400.0, 300.0], [200.0]]>

Operatori sovraccarichi

I RaggedTensor sovraccarichi di classe gli operatori aritmetici e confronto standard di Python, rendendo più semplice per eseguire la matematica di base elementwise:

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

Poiché gli operatori di overload eseguono calcoli elementwise, gli input a tutte le operazioni binarie devono avere la stessa forma o essere trasmessi alla stessa forma. Nel caso di trasmissione più semplice, un singolo scalare viene combinato elemento per elemento con ciascun valore in un tensore irregolare:

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

Per una discussione di casi più avanzati, controllare la sezione sulla radiodiffusione.

Tensori Ragged sovraccaricare lo stesso insieme di operatori come normale Tensor s: gli operatori unari - , ~ , e abs() ; e gli operatori binari + , - , * , / , // , % , ** , & , | , ^ , == , < , <= , > e >= .

Indicizzazione

I tensori irregolari supportano l'indicizzazione in stile Python, inclusi l'indicizzazione e lo slicing multidimensionali. Gli esempi seguenti mostrano l'indicizzazione del tensore irregolare con un tensore irregolare 2D e 3D.

Esempi di indicizzazione: tensore irregolare 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']]>

Esempi di indicizzazione: tensore irregolare 3D

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

RaggedTensor supporto s indicizzazione multidimensionale e affettare con restrizione uno: l'indicizzazione in una dimensione incompleta non è permesso. Questo caso è problematico perché il valore indicato può esistere in alcune righe ma non in altre. In questi casi, non è evidente se si deve (1) sollevare un IndexError ; (2) utilizzare un valore predefinito; o (3) salta quel valore e restituisce un tensore con meno righe di quelle con cui hai iniziato. Seguendo i principi guida di Python ( "A fronte di ambiguità, rifiutare la tentazione di indovinare"), questa operazione è attualmente consentito.

Conversione del tipo di tensore

I RaggedTensor definisce classe metodi che possono essere utilizzati per la conversione tra RaggedTensor s e tf.Tensor s o 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']]>

Valutazione dei tensori irregolari

Per accedere ai valori in un tensore irregolare, puoi:

  1. Utilizzare tf.RaggedTensor.to_list per convertire il tensore lacero a una lista Python nidificato.
  2. Uso tf.RaggedTensor.numpy per convertire il tensore lacera in una matrice NumPy cui valori sono annidati array numpy.
  3. Decomporre il tensore lacera nei suoi componenti, utilizzando i tf.RaggedTensor.values e tf.RaggedTensor.row_splits proprietà o metodi per righe paritioning come tf.RaggedTensor.row_lengths e tf.RaggedTensor.value_rowids .
  4. Usa l'indicizzazione Python per selezionare i valori dal tensore irregolare.
rt = tf.ragged.constant([[1, 2], [3, 4, 5], [6], [], [7]])
print("Python list:", rt.to_list())
print("NumPy array:", rt.numpy())
print("Values:", rt.values.numpy())
print("Splits:", rt.row_splits.numpy())
print("Indexed value:", rt[1].numpy())
Python list: [[1, 2], [3, 4, 5], [6], [], [7]]
NumPy array: [array([1, 2], dtype=int32) array([3, 4, 5], dtype=int32)
 array([6], dtype=int32) array([], dtype=int32) array([7], dtype=int32)]
Values: [1 2 3 4 5 6 7]
Splits: [0 2 5 6 6 7]
Indexed value: [3 4 5]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/ragged/ragged_tensor.py:2063: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray
  return np.array(rows)

Trasmissione

Il broadcasting è il processo per fare in modo che tensori con forme diverse abbiano forme compatibili per operazioni elementwise. Per ulteriori informazioni sulla trasmissione, fare riferimento a:

I passi fondamentali per trasmettere due ingressi x e y per avere forme compatibili sono:

  1. Se x ed y non hanno lo stesso numero di dimensioni, quindi aggiungere dimensioni esterne (di dimensione 1) fino a che lo fanno.

  2. Per ciascuna dimensione dove x ed y hanno diverse dimensioni:

  • Se x o y hanno dimensioni 1 in dimensione d , quindi ripetere i valori di tutti dimensione d per adattarli al formato del altro ingresso.
  • In caso contrario, sollevare un'eccezione ( x e y non vengono trasmesse compatibile).

Dove la dimensione di un tensore in una dimensione uniforme è un singolo numero (la dimensione delle fette attraverso quella dimensione); e la dimensione di un tensore in una dimensione irregolare è un elenco di lunghezze di sezione (per tutte le sezioni in quella dimensione).

Esempi di trasmissione

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

Ecco alcuni esempi di forme che non trasmettono:

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

Codifica RaggedTensor

Tensori Ragged sono codificati utilizzando la RaggedTensor di classe. Internamente, ciascun RaggedTensor si compone di:

  • A values tensore, che concatena le righe di lunghezza variabile in una lista appiattita.
  • Un row_partition , che indica come i valori appiattiti sono suddivisi in righe.

Codifica RaggedTensor

Il row_partition può essere memorizzato utilizzando quattro differenti codifiche:

  • row_splits è un vettore numero intero che specifica i punti di divisione tra le righe.
  • value_rowids è un vettore intero che specifica l'indice di riga per ogni valore.
  • row_lengths è un vettore intero che specifica la lunghezza di ogni riga.
  • uniform_row_length è uno scalare intero che indica una singola lunghezza per tutte le righe.

codifiche row_partition

Un numero intero scalari nrows possono anche essere inclusi nel row_partition codifica per conto di righe vuote con trascinamento value_rowids o righe vuote 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 scelta di quale codifica utilizzare per le partizioni di riga è gestita internamente da tensori irregolari per migliorare l'efficienza in alcuni contesti. In particolare, alcuni dei vantaggi e degli svantaggi dei diversi schemi di partizionamento delle righe sono:

  • Indicizzazione efficiente: Il row_splits codifica consente tempo costante indicizzazione e affettamento in tensori frastagliati.
  • Concatenazione efficiente: I row_lengths codifica è più efficiente quando concatenazione tensori laceri, poiché le lunghezze di riga non cambiano quando due tensori vengono concatenati insieme.
  • Piccola dimensione codifica: Le value_rowids codifica è più efficace quando si ripone tensori frastagliati che hanno un gran numero di righe vuote, poiché la dimensione del tensore dipende solo dal numero totale di valori. D'altra parte, i row_splits e row_lengths codifiche sono più efficienti quando si ripone tensori frastagliati con righe più lunghi, in quanto richiedono un solo valore scalare per ogni riga.
  • Compatibilità: Il value_rowids schema corrisponde al di segmentazione formato utilizzato da operazioni, ad esempio tf.segment_sum . Il row_limits schema corrisponde al formato utilizzato da ops quali tf.sequence_mask .
  • Dimensioni uniformi: Come discusso in seguito, il uniform_row_length codifica è utilizzato per codificare laceri tensori con dimensioni uniformi.

Molteplici dimensioni irregolari

Un tensore stracciabile molteplici dimensioni laceri è codificato utilizzando un nidificata RaggedTensor per values tensore. Ogni nidificato RaggedTensor aggiunge una sola dimensione irregolare.

Codifica di un tensore irregolare con più dimensioni irregolari (rango 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 funzione fabbrica tf.RaggedTensor.from_nested_row_splits possono essere usati per costruire un RaggedTensor con dimensioni multiple laceri direttamente fornendo un elenco di row_splits tensori:

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 irregolare e valori piatti

Rango frastagliato di un tensore frastagliato è il numero di volte che i sottostanti values tensore è stato partizionato (cioè la profondità di annidamento di RaggedTensor oggetti). I più interne values tensore è noto come suoi flat_values. Nel seguente esempio, conversations ha ragged_rank = 3, e le sue flat_values è una 1D Tensor con 24 stringhe:

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

Dimensioni interne uniformi

Tensori stracciato con dimensioni interne uniformi sono codificati utilizzando un multidimensionale tf.Tensor per le flat_values (cioè le più interne values ).

Codifica di tensori irregolari con dimensioni interne uniformi

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

Dimensioni non interne uniformi

Tensori stracciato con dimensioni non uniformi interne sono codificati suddividendo righe con uniform_row_length .

Codifica di tensori irregolari con dimensioni non interne uniformi

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