Aiuto proteggere la Grande Barriera Corallina con tensorflow sul Kaggle Join Sfida

Tipi di estensione

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

Impostare

!pip install -q tf_nightly
import tensorflow as tf
import numpy as np
from typing import Tuple, List, Mapping, Union, Optional
import tempfile

Tipi di estensione

I tipi definiti dall'utente possono rendere i progetti più leggibili, modulari e manutenibili. Tuttavia, la maggior parte delle API TensorFlow ha un supporto molto limitato per i tipi Python definiti dall'utente. Questo include sia API di alto livello (come Keras , tf.function , tf.SavedModel ) e di livello inferiore API (come tf.while_loop e tf.concat ). Tipi di estensione tensorflow possono essere utilizzati per creare tipi orientati agli oggetti definiti dall'utente che funzionano perfettamente con le API di tensorflow. Per creare un tipo di estensione, semplicemente definire una classe Python con tf.experimental.ExtensionType come base, e usare le annotazioni di tipo per specificare il tipo di ciascun campo.

class TensorGraph(tf.experimental.ExtensionType):
  """A collection of labeled nodes connected by weighted edges."""
  edge_weights: tf.Tensor               # shape=[num_nodes, num_nodes]
  node_labels: Mapping[str, tf.Tensor]  # shape=[num_nodes]; dtype=any

class MaskedTensor(tf.experimental.ExtensionType):
  """A tensor paired with a boolean mask, indicating which values are valid."""
  values: tf.Tensor
  mask: tf.Tensor       # shape=values.shape; false for missing/invalid values.

class CSRSparseMatrix(tf.experimental.ExtensionType):
  """Compressed sparse row matrix (https://en.wikipedia.org/wiki/Sparse_matrix)."""
  values: tf.Tensor     # shape=[num_nonzero]; dtype=any
  col_index: tf.Tensor  # shape=[num_nonzero]; dtype=int64
  row_index: tf.Tensor  # shape=[num_rows+1]; dtype=int64

Il tf.experimental.ExtensionType classe di base funziona in modo simile a typing.NamedTuple e @dataclasses.dataclass dalla libreria standard di Python. In particolare, si aggiunge automaticamente un costruttore e metodi speciali (come __repr__ e __eq__ ) sulla base delle annotazioni di tipo campo.

In genere, i tipi di estensione tendono a rientrare in una delle due categorie seguenti:

  • Strutture di dati, che raggruppano un insieme di valori correlati, e in grado di fornire utili operazioni sulla base di tali valori. Strutture di dati possono essere abbastanza generale (come il TensorGraph esempio precedente); oppure possono essere altamente personalizzati per un modello specifico.

  • Tipi, che si specializzano o estendere il concetto di Tensor-like "Tensor". Tipi di questa categoria hanno un rank , un shape , e di solito un dtype ; e ha senso usarli con operazioni tensore (quali tf.stack , tf.add o tf.matmul ). MaskedTensor e CSRSparseMatrix sono esempi di tipi tensore-like.

API supportate

I tipi di estensione sono supportati dalle seguenti API TensorFlow:

  • Keras: tipi interno possono essere utilizzati come ingressi e uscite per KERAS Models e Layers .
  • tf.data.Dataset: tipi di estensione possono essere inclusi nel Datasets , e restituiti dal set di dati Iterators .
  • Mozzo tensorflow: tipi interno possono essere utilizzati come ingressi e uscite per tf.hub moduli.
  • SavedModel: tipi interno possono essere utilizzati come ingressi e uscite per SavedModel funzioni.
  • tf.function: tipi interno possono essere utilizzati come parametri e valori di ritorno per le funzioni avvolti con il @tf.function decoratore.
  • while: tipi interno possono essere utilizzati come variabili di loop in tf.while_loop , e possono essere usati come argomenti e valori restituiti per il corpo del ciclo while.
  • condizionali: tipi di estensione possono essere selezionati utilizzando condizionale tf.cond e tf.case .
  • py_function: tipi interno possono essere utilizzati come parametri e valori di ritorno per il func argomento tf.py_function .
  • Tensor ops: tipi di estensione possono essere estese per supportare la maggior parte ops tensorflow che accettano ingressi tensore (ad esempio, tf.matmul , tf.gather e tf.reduce_sum ). Vedere la sezione "spedizione" di seguito per ulteriori informazioni.
  • strategia di distribuzione: tipi di estensione possono essere usati come valori per-replica.

Per maggiori dettagli, vedere la sezione "API TensorFlow che supportano ExtensionTypes" di seguito.

Requisiti

Tipi di campo

Tutti i campi (ovvero le variabili di istanza) devono essere dichiarati e deve essere fornita un'annotazione di tipo per ogni campo. Sono supportate le seguenti annotazioni di tipo:

Tipo Esempio
interi Python i: int
Python galleggia f: float
Stringhe in pitone s: str
booleani in pitone b: bool
Python Nessuno n: None
Forme tensoriali shape: tf.TensorShape
Tipi di tensore dtype: tf.DType
tensori t: tf.Tensor
Tipi di estensione mt: MyMaskedTensor
Tensori sfilacciati rt: tf.RaggedTensor
Tensori sparsi st: tf.SparseTensor
Fette indicizzate s: tf.IndexedSlices
Tensori opzionali o: tf.experimental.Optional
Tipo unioni int_or_float: typing.Union[int, float]
tuple params: typing.Tuple[int, float, tf.Tensor, int]
Tuple di lunghezza variabile lengths: typing.Tuple[int, ...]
mappature tags: typing.Mapping[str, tf.Tensor]
Valori opzionali weight: typing.Optional[tf.Tensor]

Mutabilità

I tipi di estensione devono essere immutabili. Ciò garantisce che possano essere correttamente tracciati dai meccanismi di tracciamento dei grafici di TensorFlow. Se ti ritrovi a voler mutare un valore del tipo di estensione, considera invece la definizione di metodi che trasformano i valori. Ad esempio, invece di definire un set_mask metodo per mutare un MaskedTensor , si potrebbe definire un replace_mask metodo che restituisce una nuova MaskedTensor :

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

  def replace_mask(self, new_mask):
      self.values.shape.assert_is_compatible_with(new_mask.shape)
      return MaskedTensor(self.values, new_mask)

Funzionalità aggiuntive per ExtensionType

ExtensionType classe di base fornisce le seguenti funzionalità:

  • Un costruttore ( __init__ ).
  • Procedimento rappresentazione stampabile ( __repr__ ).
  • Uguaglianza e gli operatori di disuguaglianza ( __eq__ ).
  • Un metodo di validazione ( __validate__ ).
  • Immutabilità forzata.
  • Un annidato TypeSpec .
  • Supporto per la spedizione dell'API Tensor.

Vedere la sezione "Personalizzazione dei tipi di estensione" di seguito per ulteriori informazioni sulla personalizzazione di questa funzionalità.

Costruttore

Il costruttore aggiunto da ExtensionType prende ogni campo come un argomento di nome (nell'ordine in cui sono stati elencati nella definizione della classe). Questo costruttore controllerà ogni parametro e li convertirà dove necessario. In particolare, Tensor campi vengono convertiti usando tf.convert_to_tensor ; Tuple campi vengono convertiti in tuple s; e Mapping campi vengono convertiti in dicts immutabili.

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

# Constructor takes one parameter for each field.
mt = MaskedTensor(values=[[1, 2, 3], [4, 5, 6]],
                  mask=[[True, True, False], [True, False, True]])

# Fields are type-checked and converted to the declared types.
# E.g., mt.values is converted to a Tensor.
print(mt.values)
tf.Tensor(
[[1 2 3]
 [4 5 6]], shape=(2, 3), dtype=int32)

Il costruttore solleva un TypeError se un valore di campo non può essere convertito al suo tipo dichiarato:

try:
  MaskedTensor([1, 2, 3], None)
except TypeError as e:
  print(f"Got expected TypeError: {e}")
Got expected TypeError: mask: expected a Tensor, got None

Il valore predefinito per un campo può essere specificato impostando il suo valore a livello di classe:

class Pencil(tf.experimental.ExtensionType):
  color: str = "black"
  has_erasor: bool = True
  length: tf.Tensor = 1.0

Pencil()
Pencil(color='black', has_erasor=True, length=<tf.Tensor: shape=(), dtype=float32, numpy=1.0>)
Pencil(length=0.5, color="blue")
Pencil(color='blue', has_erasor=True, length=<tf.Tensor: shape=(), dtype=float32, numpy=0.5>)

Rappresentazione stampabile

ExtensionType aggiunge un metodo predefinito stampabile rappresentazione ( __repr__ ) che include il nome della classe e il valore di ogni campo:

print(MaskedTensor(values=[1, 2, 3], mask=[True, True, False]))
MaskedTensor(values=<tf.Tensor: shape=(3,), dtype=int32, numpy=array([1, 2, 3], dtype=int32)>, mask=<tf.Tensor: shape=(3,), dtype=bool, numpy=array([ True,  True, False])>)

Operatori di uguaglianza

ExtensionType aggiunge operatori di uguaglianza di default ( __eq__ e __ne__ ) che considerano due valori uguali se hanno lo stesso tipo e tutti i loro campi sono uguali. I campi tensoriali sono considerati uguali se hanno la stessa forma e sono uguali per elemento per tutti gli elementi.

a = MaskedTensor([1, 2], [True, False])
b = MaskedTensor([[3, 4], [5, 6]], [[False, True], [True, True]])
print(f"a == a: {a==a}")
print(f"a == b: {a==b}")
print(f"a == a.values: {a==a.values}")
a == a: True
a == b: False
a == a.values: False

Metodo di convalida

ExtensionType aggiunge un __validate__ metodo, che può essere ignorato per eseguire i controlli di validazione sui campi. Viene eseguito dopo che è stato chiamato il costruttore e dopo che i campi sono stati controllati dal tipo e convertiti nei loro tipi dichiarati, quindi si può presumere che tutti i campi abbiano i loro tipi dichiarati.

egli seguente viene aggiornata MaskedTensor per convalidare la shape s e dtype s dei suoi campi:

class MaskedTensor(tf.experimental.ExtensionType):
  """A tensor paired with a boolean mask, indicating which values are valid."""
  values: tf.Tensor
  mask: tf.Tensor
  def __validate__(self):
    self.values.shape.assert_is_compatible_with(self.mask.shape)
    assert self.mask.dtype.is_bool, 'mask.dtype must be bool'
try:
  MaskedTensor([1, 2, 3], [0, 1, 0])  # wrong dtype for mask.
except AssertionError as e:
  print(f"Got expected AssertionError: {e}")
Got expected AssertionError: mask.dtype must be bool
try:
  MaskedTensor([1, 2, 3], [True, False])  # shapes don't match.
except ValueError as e:
  print(f"Got expected ValueError: {e}")
Got expected ValueError: Shapes (3,) and (2,) are incompatible

Immutabilità forzata

ExtensionType sostituisce il __setattr__ e __delattr__ metodi per prevenire mutazione, assicurando che i valori tipo di estensione sono immutabili.

mt = MaskedTensor([1, 2, 3], [True, False, True])
try:
  mt.mask = [True, True, True]
except AttributeError as e:
  print(f"Got expected AttributeError: {e}")
Got expected AttributeError: Cannot mutate attribute `mask` outside the custom constructor of ExtensionType.
try:
  mt.mask[0] = False
except TypeError as e:
  print(f"Got expected TypeError: {e}")
Got expected TypeError: 'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment
try:
  del mt.mask
except AttributeError as e:
  print(f"Got expected AttributeError: {e}")
Got expected AttributeError: Cannot mutate attribute `mask` outside the custom constructor of ExtensionType.

Specifica tipo annidata

Ogni ExtensionType classe ha un corrispondente TypeSpec classe, che viene creata automaticamente e memorizzato come <extension_type_name>.Spec .

Questa classe cattura tutte le informazioni da un valore ad eccezione dei valori di qualsiasi tensori nidificate. In particolare, il TypeSpec per un valore viene creato sostituendo qualsiasi nested Tensor, ExtensionType, o con il suo CompositeTensor TypeSpec .

class Player(tf.experimental.ExtensionType):
  name: tf.Tensor
  attributes: Mapping[str, tf.Tensor]

anne = Player("Anne", {"height": 8.3, "speed": 28.1})
anne_spec = tf.type_spec_from_value(anne)
print(anne_spec.name)  # Records dtype and shape, but not the string value.
print(anne_spec.attributes)  # Records keys and TensorSpecs for values.
WARNING:tensorflow:Mapping types may not work well with tf.nest. Prefer using MutableMapping for <class 'tensorflow.python.framework.immutable_dict.ImmutableDict'>
TensorSpec(shape=(), dtype=tf.string, name=None)
ImmutableDict({'height': TensorSpec(shape=(), dtype=tf.float32, name=None), 'speed': TensorSpec(shape=(), dtype=tf.float32, name=None)})

TypeSpec valori possono essere costruiti in modo esplicito, oppure possono essere costruiti da un ExtensionType valore utilizzando tf.type_spec_from_value :

spec1 = Player.Spec(name=tf.TensorSpec([], tf.float32), attributes={})
spec2 = tf.type_spec_from_value(anne)

TypeSpec s sono utilizzati da tensorflow valori divide in un componente statica e una componente dinamica:

  • Il componente statica (che è fissata al momento grafico-costruzione) è codificato con un tf.TypeSpec .
  • La componente dinamica (che può variare ogni volta che il grafico si esegue) è codificato come una lista di tf.Tensor s.

Ad esempio, tf.function ripercorre la sua funzione avvolto ogni volta che un argomento ha un inedito TypeSpec :

@tf.function
def anonymize_player(player):
  print("<<TRACING>>")
  return Player("<anonymous>", player.attributes)
# Function gets traced (first time the function has been called):
anonymize_player(Player("Anne", {"height": 8.3, "speed": 28.1}))
WARNING:tensorflow:Mapping types may not work well with tf.nest. Prefer using MutableMapping for <class 'tensorflow.python.framework.immutable_dict.ImmutableDict'>
WARNING:tensorflow:Mapping types may not work well with tf.nest. Prefer using MutableMapping for <class 'tensorflow.python.framework.immutable_dict.ImmutableDict'>
<<TRACING>>
Player(name=<tf.Tensor: shape=(), dtype=string, numpy=b'<anonymous>'>, attributes=ImmutableDict({'height': <tf.Tensor: shape=(), dtype=float32, numpy=8.3>, 'speed': <tf.Tensor: shape=(), dtype=float32, numpy=28.1>}))
# Function does NOT get traced (same TypeSpec: just tensor values changed)
anonymize_player(Player("Bart", {"height": 8.1, "speed": 25.3}))
Player(name=<tf.Tensor: shape=(), dtype=string, numpy=b'<anonymous>'>, attributes=ImmutableDict({'height': <tf.Tensor: shape=(), dtype=float32, numpy=8.1>, 'speed': <tf.Tensor: shape=(), dtype=float32, numpy=25.3>}))
# Function gets traced (new TypeSpec: keys for attributes changed):
anonymize_player(Player("Chuck", {"height": 11.0, "jump": 5.3}))
<<TRACING>>
Player(name=<tf.Tensor: shape=(), dtype=string, numpy=b'<anonymous>'>, attributes=ImmutableDict({'height': <tf.Tensor: shape=(), dtype=float32, numpy=11.0>, 'jump': <tf.Tensor: shape=(), dtype=float32, numpy=5.3>}))

Per ulteriori informazioni, vedere la tf.function Guida .

Personalizzazione dei tipi di estensione

Oltre a dichiarare semplicemente i campi e i loro tipi, i tipi di estensione possono:

  • Eseguire l'override del rappresentazione stampabile di default ( __repr__ ).
  • Definire i metodi.
  • Definire metodi di classe e metodi statici.
  • Definire le proprietà.
  • Eseguire l'override del costruttore di default ( __init__ ).
  • Override l'operatore di uguaglianza di default ( __eq__ ).
  • Definire gli operatori (come __add__ e __lt__ ).
  • Dichiarare i valori predefiniti per i campi.
  • Definire le sottoclassi.

Sovrascrivere la rappresentazione stampabile predefinita

È possibile sovrascrivere questo operatore di conversione stringa predefinito per i tipi di estensione. Il seguente esempio aggiorna la MaskedTensor classe per generare una rappresentazione di stringa più leggibile quando i valori sono stampati in modalità Eager.

class MaskedTensor(tf.experimental.ExtensionType):
  """A tensor paired with a boolean mask, indicating which values are valid."""
  values: tf.Tensor
  mask: tf.Tensor       # shape=values.shape; false for invalid values.

  def __repr__(self):
    return masked_tensor_str(self.values, self.mask)

def masked_tensor_str(values, mask):
  if isinstance(values, tf.Tensor):
    if hasattr(values, 'numpy') and hasattr(mask, 'numpy'):
      return f'<MaskedTensor {masked_tensor_str(values.numpy(), mask.numpy())}>'
    else:
      return f'MaskedTensor(values={values}, mask={mask})'
  if len(values.shape) == 1:
    items = [repr(v) if m else '_' for (v, m) in zip(values, mask)]
  else:
    items = [masked_tensor_str(v, m) for (v, m) in zip(values, mask)]
  return '[%s]' % ', '.join(items)

mt = MaskedTensor(values=[[1, 2, 3], [4, 5, 6]],
                  mask=[[True, True, False], [True, False, True]])
print(mt)
<MaskedTensor [[1, 2, _], [4, _, 6]]>

Definizione dei metodi

I tipi di estensione possono definire metodi, proprio come qualsiasi normale classe Python. Ad esempio, il MaskedTensor tipo potrebbe definire un with_default metodo che restituisce una copia di self con valori mascherati sostituito da un dato default valore. I metodi possono opzionalmente essere annotati con la @tf.function decoratore.

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

  def with_default(self, default):
    return tf.where(self.mask, self.values, default)

MaskedTensor([1, 2, 3], [True, False, True]).with_default(0)
<tf.Tensor: shape=(3,), dtype=int32, numpy=array([1, 0, 3], dtype=int32)>

Definizione di metodi di classe e metodi statici

Tipi di estensione possono definire metodi utilizzando le @classmethod e @staticmethod decoratori. Ad esempio, il MaskedTensor tipo potrebbe definire un metodo factory che maschera qualsiasi elemento con un dato valore:

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

  def __repr__(self):
    return masked_tensor_str(self.values, self.mask)

  @staticmethod
  def from_tensor_and_value_to_mask(values, value_to_mask):
    return MaskedTensor(values, values == value_to_mask)

x = tf.constant([[1, 0, 2], [3, 0, 0]])
MaskedTensor.from_tensor_and_value_to_mask(x, 0)
<MaskedTensor [[_, 0, _], [_, 0, 0]]>

Definizione delle proprietà

Tipi di estensione possono definire proprietà utilizzando la @property decoratore, proprio come qualsiasi normale classe Python. Ad esempio, il MaskedTensor tipo potrebbe definire una dtype proprietà che è un'abbreviazione per la dtype dei valori:

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

  @property
  def dtype(self):
    return self.values.dtype

MaskedTensor([1, 2, 3], [True, False, True]).dtype
tf.int32

Sovrascrivere il costruttore predefinito

È possibile sovrascrivere il costruttore predefinito per i tipi di estensione. I costruttori personalizzati devono impostare un valore per ogni campo dichiarato; e dopo il ritorno del costruttore personalizzato, tutti i campi verranno controllati dal tipo e i valori verranno convertiti come descritto sopra.

class Toy(tf.experimental.ExtensionType):
  name: str
  price: tf.Tensor
  def __init__(self, name, price, discount=0):
    self.name = name
    self.price = price * (1 - discount)

print(Toy("ball", 5.0, discount=0.2))  # On sale -- 20% off!
Toy(name='ball', price=<tf.Tensor: shape=(), dtype=float32, numpy=4.0>)

In alternativa, potresti considerare di lasciare il costruttore predefinito così com'è, ma aggiungendo uno o più metodi di fabbrica. Per esempio:

class Toy(tf.experimental.ExtensionType):
  name: str
  price: tf.Tensor

  @staticmethod
  def new_toy_with_discount(name, price, discount):
    return Toy(name, price * (1 - discount))

print(Toy.new_toy_with_discount("ball", 5.0, discount=0.2))
Toy(name='ball', price=<tf.Tensor: shape=(), dtype=float32, numpy=4.0>)

Override l'operatore di uguaglianza di default ( __eq__ )

È possibile ignorare il valore predefinito __eq__ dell'operatore per tipi di estensione. L'esempio seguire gli aggiornamenti MaskedTensor ignorare elementi mascherati quando si confrontano per la parità.

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

  def __repr__(self):
    return masked_tensor_str(self.values, self.mask)

  def __eq__(self, other):
    result = tf.math.equal(self.values, other.values)
    result = result | ~(self.mask & other.mask)
    return tf.reduce_all(result)

x = MaskedTensor([1, 2, 3, 4], [True, True, False, True])
y = MaskedTensor([5, 2, 0, 4], [False, True, False, True])
print(x == y)
tf.Tensor(True, shape=(), dtype=bool)

Utilizzo di riferimenti in avanti

Se il tipo per un campo non è stato ancora definito, puoi invece utilizzare una stringa contenente il nome del tipo. Nel seguente esempio, la stringa "Node" viene utilizzato per annotare il children campo perché il Node di tipo non è stato (completamente) ancora definita.

class Node(tf.experimental.ExtensionType):
  value: tf.Tensor
  children: Tuple["Node", ...] = ()

Node(3, [Node(5), Node(2)])
Node(value=<tf.Tensor: shape=(), dtype=int32, numpy=3>, children=(Node(value=<tf.Tensor: shape=(), dtype=int32, numpy=5>, children=()), Node(value=<tf.Tensor: shape=(), dtype=int32, numpy=2>, children=())))

Definizione di sottoclassi

I tipi di estensione possono essere sottoclassi utilizzando la sintassi standard di Python. Le sottoclassi del tipo di estensione possono aggiungere nuovi campi, metodi e proprietà; e può sovrascrivere il costruttore, la rappresentazione stampabile e l'operatore di uguaglianza. Il seguente esempio definisce una base TensorGraph classe che utilizza tre Tensor campi per codificare un insieme di archi tra i nodi. Si definisce quindi una sottoclasse che aggiunge un Tensor campo per registrare un "feature value" per ogni nodo. La sottoclasse definisce anche un metodo per propagare i valori delle caratteristiche lungo i bordi.

class TensorGraph(tf.experimental.ExtensionType):
  num_nodes: tf.Tensor
  edge_src: tf.Tensor   # edge_src[e] = index of src node for edge e.
  edge_dst: tf.Tensor   # edge_dst[e] = index of dst node for edge e.

class TensorGraphWithNodeFeature(TensorGraph):
  node_features: tf.Tensor  # node_features[n] = feature value for node n.

  def propagate_features(self, weight=1.0) -> 'TensorGraphWithNodeFeature':
    updates = tf.gather(self.node_features, self.edge_src) * weight
    new_node_features = tf.tensor_scatter_nd_add(
        self.node_features, tf.expand_dims(self.edge_dst, 1), updates)
    return TensorGraphWithNodeFeature(
        self.num_nodes, self.edge_src, self.edge_dst, new_node_features)

g = TensorGraphWithNodeFeature(  # Edges: 0->1, 4->3, 2->2, 2->1
    num_nodes=5, edge_src=[0, 4, 2, 2], edge_dst=[1, 3, 2, 1],
    node_features=[10.0, 0.0, 2.0, 5.0, -1.0, 0.0])

print("Original features:", g.node_features)
print("After propagating:", g.propagate_features().node_features)
Original features: tf.Tensor([10.  0.  2.  5. -1.  0.], shape=(6,), dtype=float32)
After propagating: tf.Tensor([10. 12.  4.  4. -1.  0.], shape=(6,), dtype=float32)

Definizione di campi privati

I campi di un tipo di estensione possono essere contrassegnati come privati ​​anteponendoli a un carattere di sottolineatura (seguendo le convenzioni standard di Python). Ciò non influisce in alcun modo sul modo in cui TensorFlow tratta i campi; ma serve semplicemente come segnale a tutti gli utenti del tipo di estensione che quei campi sono privati.

Personalizzazione del ExtensionType TypeSpec

Ogni ExtensionType classe ha un corrispondente TypeSpec classe, che viene creata automaticamente e memorizzato come <extension_type_name>.Spec . Per ulteriori informazioni, vedere la sezione "Nested TypeSpec" sopra.

Per personalizzare la TypeSpec , è sufficiente definire la propria classe annidata nome Spec , e ExtensionType userà che come base per la costruzione automaticamente TypeSpec . È possibile personalizzare la Spec classe:

  • Sovrascrivere la rappresentazione stampabile predefinita.
  • Sovrascrivere il costruttore predefinito.
  • Definizione di metodi, metodi di classe, metodi statici e proprietà.

L'esempio seguente personalizza il MaskedTensor.Spec classe per rendere più facile da usare:

class MaskedTensor(tf.experimental.ExtensionType):
  values: tf.Tensor
  mask: tf.Tensor

  shape = property(lambda self: self.values.shape)
  dtype = property(lambda self: self.values.dtype)

  def __repr__(self):
    return masked_tensor_str(self.values, self.mask)

  def with_values(self, new_values):
    return MaskedTensor(new_values, self.mask)

  class Spec:
    def __init__(self, shape, dtype=tf.float32):
      self.values = tf.TensorSpec(shape, dtype)
      self.mask = tf.TensorSpec(shape, tf.bool)

    def __repr__(self):
      return f"MaskedTensor.Spec(shape={self.shape}, dtype={self.dtype})"

    shape = property(lambda self: self.values.shape)
    dtype = property(lambda self: self.values.dtype)

Invio dell'API del tensore

Tipi di estensione possono essere "tensore-like", nel senso che sono specializzati o estendono l'interfaccia definita dalla tf.Tensor tipo. Esempi di tipi di estensione tensore simile includono RaggedTensor , SparseTensor e MaskedTensor . Decoratori spedizione possono essere utilizzati per ignorare il comportamento predefinito delle operazioni tensorflow quando applicata a tensore simili tipi di estensione. TensorFlow attualmente definisce tre decoratori di spedizione:

Spedizione per una singola API

Il tf.experimental.dispatch_for_api decoratore sovrascrive il comportamento predefinito di un'operazione tensorflow specificato quando viene chiamato con la firma specificata. Ad esempio, è possibile utilizzare questo decoratore di per specificare come tf.stack deve elaborare MaskedTensor valori:

@tf.experimental.dispatch_for_api(tf.stack)
def masked_stack(values: List[MaskedTensor], axis = 0):
  return MaskedTensor(tf.stack([v.values for v in values], axis),
                      tf.stack([v.mask for v in values], axis))

Questo sovrascrive l'implementazione predefinita per tf.stack ogniqualvolta è chiamata con una lista di MaskedTensor valori (in quanto il values argomento viene annotato con typing.List[MaskedTensor] ):

x = MaskedTensor([1, 2, 3], [True, True, False])
y = MaskedTensor([4, 5, 6], [False, True, True])
tf.stack([x, y])
<MaskedTensor [[1, 2, _], [_, 5, 6]]>

Per consentire tf.stack alle liste manico di misti MaskedTensor e Tensor valori, è possibile raffinare il tipo di annotazione per il values dei parametri e aggiornare il corpo della funzione in modo appropriato:

tf.experimental.unregister_dispatch_for(masked_stack)

def convert_to_masked_tensor(x):
  if isinstance(x, MaskedTensor):
    return x
  else:
    return MaskedTensor(x, tf.ones_like(x, tf.bool))

@tf.experimental.dispatch_for_api(tf.stack)
def masked_stack_v2(values: List[Union[MaskedTensor, tf.Tensor]], axis = 0):
  values = [convert_to_masked_tensor(v) for v in values]
  return MaskedTensor(tf.stack([v.values for v in values], axis),
                      tf.stack([v.mask for v in values], axis))
x = MaskedTensor([1, 2, 3], [True, True, False])
y = tf.constant([4, 5, 6])
tf.stack([x, y, x])
<MaskedTensor [[1, 2, _], [4, 5, 6], [1, 2, _]]>

Per un elenco di API che può essere ignorato, consultare la documentazione API per tf.experimental.dispatch_for_api .

Spedizione per tutte le API elementwise unari

Il tf.experimental.dispatch_for_unary_elementwise_apis decoratore sovrascrive il comportamento di tutti gli op elementwise unari (come tf.math.cos ) ogni volta che il valore per il primo argomento (generalmente denominato x ) corrisponde al tipo di annotazione x_type . La funzione decorata dovrebbe prendere due argomenti:

  • api_func : Una funzione che assume un singolo parametro ed esegue l'operazione elementwise (ad esempio, tf.abs ).
  • x : Il primo argomento all'operazione elementwise.

Il seguente esempio aggiorna tutte le operazioni unarie elementwise per gestire il MaskedTensor tipo:

@tf.experimental.dispatch_for_unary_elementwise_apis(MaskedTensor)
 def masked_tensor_unary_elementwise_api_handler(api_func, x):
   return MaskedTensor(api_func(x.values), x.mask)

Questa funzione sarà ora utilizzato ogniqualvolta un'operazione elementwise unaria viene chiamato un MaskedTensor .

x = MaskedTensor([1, -2, -3], [True, False, True])
 print(tf.abs(x))
<MaskedTensor [1, _, 3]>
print(tf.ones_like(x, dtype=tf.float32))
<MaskedTensor [1.0, _, 1.0]>

Invio per binario tutte le API elementwise

Analogamente, tf.experimental.dispatch_for_binary_elementwise_apis possono essere usate per modificare tutte le operazioni binarie elementwise per gestire il MaskedTensor tipo:

@tf.experimental.dispatch_for_binary_elementwise_apis(MaskedTensor, MaskedTensor)
def masked_tensor_binary_elementwise_api_handler(api_func, x, y):
  return MaskedTensor(api_func(x.values, y.values), x.mask & y.mask)
x = MaskedTensor([1, -2, -3], [True, False, True])
y = MaskedTensor([[4], [5]], [[True], [False]])
tf.math.add(x, y)
<MaskedTensor [[5, _, 1], [_, _, _]]>

Per un elenco delle API elementwise che vengono sovrascritte, consultare la documentazione API per tf.experimental.dispatch_for_unary_elementwise_apis e tf.experimental.dispatch_for_binary_elementwise_apis .

Tipi di estensione batch

Un ExtensionType è batchable se una singola istanza può essere utilizzato per rappresentare un gruppo di valori. Tipicamente, ciò viene realizzato aggiungendo dimensioni batch per tutti nidificata Tensor s. Le seguenti API TensorFlow richiedono che qualsiasi input di tipo di estensione sia batchable:

Per impostazione predefinita, BatchableExtensionType crea valori in batch per il dosaggio ogni nidificato Tensor s, CompositeTensor s, e ExtensionType s. Se questo non è appropriato per la classe, allora si avrà bisogno di utilizzare tf.experimental.ExtensionTypeBatchEncoder per ignorare questo comportamento predefinito. Ad esempio, non sarebbe opportuno creare un lotto di tf.SparseTensor valori semplicemente accatastamento singoli tensori sparse values , indices , e dense_shape campi - nella maggior parte dei casi, non è possibile impilare questi tensori, poiché hanno forme incompatibili ; e anche se si potesse, il risultato non sarebbe una valida SparseTensor .

Esempio BatchableExtensionType: Rete

A titolo di esempio, si consideri una semplice Network di classe utilizzato per il bilanciamento del carico, che le tracce di quanto lavoro resta da fare in ogni nodo, e la quantità di larghezza di banda è disponibile per spostare il lavoro tra i nodi:

class Network(tf.experimental.ExtensionType):  # This version is not batchable.
  work: tf.Tensor       # work[n] = work left to do at node n
  bandwidth: tf.Tensor  # bandwidth[n1, n2] = bandwidth from n1->n2

net1 = Network([5., 3, 8], [[0., 2, 0], [2, 0, 3], [0, 3, 0]])
net2 = Network([3., 4, 2], [[0., 2, 2], [2, 0, 2], [2, 2, 0]])

Per effettuare questo tipo batchable, modificare il tipo di base di BatchableExtensionType , e regolare la forma di ciascun campo per includere dimensioni lotti opzionali. Il seguente esempio aggiunge anche una shape campo per traccia keept della forma batch. Questa shape campo non è richiesto dalla tf.data.Dataset o tf.map_fn , ma è richiesto dalla tf.Keras .

class Network(tf.experimental.BatchableExtensionType):
  shape: tf.TensorShape  # batch shape.  A single network has shape=[].
  work: tf.Tensor        # work[*shape, n] = work left to do at node n
  bandwidth: tf.Tensor   # bandwidth[*shape, n1, n2] = bandwidth from n1->n2

  def __init__(self, work, bandwidth):
    self.work = tf.convert_to_tensor(work)
    self.bandwidth = tf.convert_to_tensor(bandwidth)
    work_batch_shape = self.work.shape[:-1]
    bandwidth_batch_shape = self.bandwidth.shape[:-2]
    self.shape = work_batch_shape.merge_with(bandwidth_batch_shape)

  def __repr__(self):
    return network_repr(self)

def network_repr(network):
  work = network.work
  bandwidth = network.bandwidth
  if hasattr(work, 'numpy'):
    work = ' '.join(str(work.numpy()).split())
  if hasattr(bandwidth, 'numpy'):
    bandwidth = ' '.join(str(bandwidth.numpy()).split())
  return (f"<Network shape={network.shape} work={work} bandwidth={bandwidth}>")
net1 = Network([5., 3, 8], [[0., 2, 0], [2, 0, 3], [0, 3, 0]])
net2 = Network([3., 4, 2], [[0., 2, 2], [2, 0, 2], [2, 2, 0]])
batch_of_networks = Network(
    work=tf.stack([net1.work, net2.work]),
    bandwidth=tf.stack([net1.bandwidth, net2.bandwidth]))
print(f"net1={net1}")
print(f"net2={net2}")
print(f"batch={batch_of_networks}")
net1=<Network shape=() work=[5. 3. 8.] bandwidth=[[0. 2. 0.] [2. 0. 3.] [0. 3. 0.]]>
net2=<Network shape=() work=[3. 4. 2.] bandwidth=[[0. 2. 2.] [2. 0. 2.] [2. 2. 0.]]>
batch=<Network shape=(2,) work=[[5. 3. 8.] [3. 4. 2.]] bandwidth=[[[0. 2. 0.] [2. 0. 3.] [0. 3. 0.]] [[0. 2. 2.] [2. 0. 2.] [2. 2. 0.]]]>

È quindi possibile utilizzare tf.data.Dataset per scorrere una serie di reti:

dataset = tf.data.Dataset.from_tensor_slices(batch_of_networks)
for i, network in enumerate(dataset):
  print(f"Batch element {i}: {network}")
Batch element 0: <Network shape=() work=[5. 3. 8.] bandwidth=[[0. 2. 0.] [2. 0. 3.] [0. 3. 0.]]>
Batch element 1: <Network shape=() work=[3. 4. 2.] bandwidth=[[0. 2. 2.] [2. 0. 2.] [2. 2. 0.]]>

E si può anche utilizzare map_fn per applicare una funzione per ogni elemento lotto:

def balance_work_greedy(network):
  delta = (tf.expand_dims(network.work, -1) - tf.expand_dims(network.work, -2))
  delta /= 4
  delta = tf.maximum(tf.minimum(delta, network.bandwidth), -network.bandwidth)
  new_work = network.work + tf.reduce_sum(delta, -1)
  return Network(new_work, network.bandwidth)

tf.map_fn(balance_work_greedy, batch_of_networks)
<Network shape=(2,) work=[[5.5 1.25 9.25] [3. 4.75 1.25]] bandwidth=[[[0. 2. 0.] [2. 0. 3.] [0. 3. 0.]] [[0. 2. 2.] [2. 0. 2.] [2. 2. 0.]]]>

API TensorFlow che supportano ExtensionTypes

@tf.funzione

tf.function è un decoratore che precomputes tensorflow grafici per funzioni Python, che può migliorare notevolmente il rendimento del codice tensorflow. Valori di tipo interno possono essere utilizzati in modo trasparente con @tf.function funzioni -decorated.

class Pastry(tf.experimental.ExtensionType):
  sweetness: tf.Tensor  # 2d embedding that encodes sweetness
  chewiness: tf.Tensor  # 2d embedding that encodes chewiness

@tf.function
def combine_pastry_features(x: Pastry):
  return (x.sweetness + x.chewiness) / 2

cookie = Pastry(sweetness=[1.2, 0.4], chewiness=[0.8, 0.2])
combine_pastry_features(cookie)
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1. , 0.3], dtype=float32)>

Se si desidera specificare esplicitamente input_signature per tf.function , allora si può farlo utilizzando il tipo di estensione TypeSpec .

pastry_spec = Pastry.Spec(tf.TensorSpec([2]), tf.TensorSpec(2))

@tf.function(input_signature=[pastry_spec])
def increase_sweetness(x: Pastry, delta=1.0):
  return Pastry(x.sweetness + delta, x.chewiness)

increase_sweetness(cookie)
Pastry(sweetness=<tf.Tensor: shape=(2,), dtype=float32, numpy=array([2.2, 1.4], dtype=float32)>, chewiness=<tf.Tensor: shape=(2,), dtype=float32, numpy=array([0.8, 0.2], dtype=float32)>)

Funzioni concrete

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

cf = combine_pastry_features.get_concrete_function(pastry_spec)
cf(cookie)
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1. , 0.3], dtype=float32)>

Controllare le operazioni di flusso

I tipi di estensione sono supportati dalle operazioni del flusso di controllo di TensorFlow:

# Example: using tf.cond to select between two MaskedTensors.  Note that the
# two MaskedTensors don't need to have the same shape.
a = MaskedTensor([1., 2, 3], [True, False, True])
b = MaskedTensor([22., 33, 108, 55], [True, True, True, False])
condition = tf.constant(True)
print(tf.cond(condition, lambda: a, lambda: b))
<MaskedTensor [1.0, _, 3.0]>
# Example: using tf.while_loop with MaskedTensor.
cond = lambda i, _: i < 10
def body(i, mt):
  return i + 1, mt.with_values(mt.values + 3 / 7)
print(tf.while_loop(cond, body, [0, b])[1])
<MaskedTensor [26.285717, 37.285698, 112.285736, _]>

Flusso di controllo dell'autografo

I tipi di estensione sono supportati anche dalle istruzioni del flusso di controllo in tf.function (usando l'autografo). Nel seguente esempio, il if economico e for istruzioni vengono convertiti automaticamente tf.cond e tf.while_loop operazioni, quali tipi di estensione supporto.

@tf.function
def fn(x, b):
  if b:
    x = MaskedTensor(x, tf.less(x, 0))
  else:
    x = MaskedTensor(x, tf.greater(x, 0))
  for i in tf.range(5 if b else 7):
    x = x.with_values(x.values + 1 / 2)
  return x

print(fn(tf.constant([1., -2, 3]), tf.constant(True)))
print(fn(tf.constant([1., -2, 3]), tf.constant(False)))
<MaskedTensor [_, 0.5, _]>
<MaskedTensor [4.5, _, 6.5]>

Keras

tf.keras è API di alto livello di tensorflow per la costruzione e la formazione di modelli di apprendimento profondo. I tipi di estensione possono essere passati come input a un modello Keras, passati tra i livelli Keras e restituiti dai modelli Keras. Keras attualmente pone due requisiti sui tipi di estensione:

  • Devono essere batchable (vedere "Tipi di estensione batchable" sopra).
  • La deve avere un campo o proprietà denominata shape . shape[0] viene considerata la dimensione batch.

Le seguenti due sottosezioni forniscono esempi che mostrano come i tipi di estensione possono essere usati con Keras.

Keras esempio: Network

Per il primo esempio, si consideri la Network classe definita nella sezione "Batchable ExtensionTypes" di cui sopra, che può essere utilizzato per il bilanciamento del carico di lavoro tra i nodi. La sua definizione è ripetuta qui:

class Network(tf.experimental.BatchableExtensionType):
  shape: tf.TensorShape  # batch shape.  A single network has shape=[].
  work: tf.Tensor        # work[*shape, n] = work left to do at node n
  bandwidth: tf.Tensor   # bandwidth[*shape, n1, n2] = bandwidth from n1->n2

  def __init__(self, work, bandwidth):
    self.work = tf.convert_to_tensor(work)
    self.bandwidth = tf.convert_to_tensor(bandwidth)
    work_batch_shape = self.work.shape[:-1]
    bandwidth_batch_shape = self.bandwidth.shape[:-2]
    self.shape = work_batch_shape.merge_with(bandwidth_batch_shape)

  def __repr__(self):
    return network_repr(self)
single_network = Network(  # A single network w/ 4 nodes.
    work=[8.0, 5, 12, 2],
    bandwidth=[[0.0, 1, 2, 2], [1, 0, 0, 2], [2, 0, 0, 1], [2, 2, 1, 0]])

batch_of_networks = Network(  # Batch of 2 networks, each w/ 2 nodes.
    work=[[8.0, 5], [3, 2]],
    bandwidth=[[[0.0, 1], [1, 0]], [[0, 2], [2, 0]]])

È possibile definire un nuovo livello Keras che elabora Network s.

class BalanceNetworkLayer(tf.keras.layers.Layer):
  """Layer that balances work between nodes in a network.

  Shifts work from more busy nodes to less busy nodes, constrained by bandwidth.
  """
  def call(self, inputs):
    # This function is defined above, in "Batchable ExtensionTypes" section.
    return balance_work_greedy(inputs)

È quindi possibile utilizzare questi livelli per creare un modello semplice. Per alimentare un ExtensionType in un modello, è possibile utilizzare un tf.keras.layer.Input livello con type_spec insieme a quello del tipo di estensione TypeSpec . Se il modello Keras sarà utilizzato per lotti di processo, allora il type_spec deve includere la dimensione dei lotti.

input_spec = Network.Spec(shape=None,
                          work=tf.TensorSpec(None, tf.float32),
                          bandwidth=tf.TensorSpec(None, tf.float32))
model = tf.keras.Sequential([
    tf.keras.layers.Input(type_spec=input_spec),
    BalanceNetworkLayer(),
    ])

Infine, puoi applicare il modello a una singola rete ea un batch di reti.

model(single_network)
<Network shape=() work=[ 9.25 5. 14. -1.25] bandwidth=[[0. 1. 2. 2.] [1. 0. 0. 2.] [2. 0. 0. 1.] [2. 2. 1. 0.]]>
model(batch_of_networks)
<Network shape=(2,) work=[[8.75 4.25] [3.25 1.75]] bandwidth=[[[0. 1.] [1. 0.]] [[0. 2.] [2. 0.]]]>

Esempio di Keras: MaskedTensor

In questo esempio, MaskedTensor viene esteso per supportare Keras . shape è definito come una proprietà che viene calcolato dal values campo. Keras richiede thatyou Aggiungi questa proprietà sia per il tipo di estensione e la sua TypeSpec . MaskedTensor definisce anche un __name__ variabile, che sarà richiesta per SavedModel serializzazione (sotto).

class MaskedTensor(tf.experimental.BatchableExtensionType):
  # __name__ is required for serialization in SavedModel; see below for details.
  __name__ = 'extension_type_colab.MaskedTensor'

  values: tf.Tensor
  mask: tf.Tensor

  shape = property(lambda self: self.values.shape)
  dtype = property(lambda self: self.values.dtype)

  def with_default(self, default):
    return tf.where(self.mask, self.values, default)

  def __repr__(self):
    return masked_tensor_str(self.values, self.mask)

  class Spec:
    def __init__(self, shape, dtype=tf.float32):
      self.values = tf.TensorSpec(shape, dtype)
      self.mask = tf.TensorSpec(shape, tf.bool)

    shape = property(lambda self: self.values.shape)
    dtype = property(lambda self: self.values.dtype)

    def with_shape(self):
      return MaskedTensor.Spec(tf.TensorSpec(shape, self.values.dtype),
                               tf.TensorSpec(shape, self.mask.dtype))

Successivamente, i decoratori di invio vengono utilizzati per sovrascrivere il comportamento predefinito di diverse API TensorFlow. Poiché queste API vengono utilizzati da livelli standard KERAS (come il Dense strato), ignorando questi permetterà di usare questi strati con MaskedTensor . Ai fini di questo esempio, matmul per tensori mascherati è definito per trattare i valori mascherati da zero (cioè, di non includerli nel prodotto).

@tf.experimental.dispatch_for_unary_elementwise_apis(MaskedTensor)
def unary_elementwise_op_handler(op, x):
 return MaskedTensor(op(x.values), x.mask)

@tf.experimental.dispatch_for_binary_elementwise_apis(
    Union[MaskedTensor, tf.Tensor],
    Union[MaskedTensor, tf.Tensor])
def binary_elementwise_op_handler(op, x, y):
  x = convert_to_masked_tensor(x)
  y = convert_to_masked_tensor(y)
  return MaskedTensor(op(x.values, y.values), x.mask & y.mask)

@tf.experimental.dispatch_for_api(tf.matmul)
def masked_matmul(a: MaskedTensor, b,
                  transpose_a=False, transpose_b=False,
                  adjoint_a=False, adjoint_b=False,
                  a_is_sparse=False, b_is_sparse=False,
                  output_type=None):
  if isinstance(a, MaskedTensor):
    a = a.with_default(0)
  if isinstance(b, MaskedTensor):
    b = b.with_default(0)
  return tf.matmul(a, b, transpose_a, transpose_b, adjoint_a,
                   adjoint_b, a_is_sparse, b_is_sparse, output_type)

È quindi possibile costruire un modello Keras che accetta MaskedTensor ingressi, utilizzando livelli standard Keras:

input_spec = MaskedTensor.Spec([None, 2], tf.float32)

masked_tensor_model = tf.keras.Sequential([
    tf.keras.layers.Input(type_spec=input_spec),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(1)])
masked_tensor_model.compile(loss='binary_crossentropy', optimizer='rmsprop')
a = MaskedTensor([[1., 2], [3, 4], [5, 6]],
                  [[True, False], [False, True], [True, True]])
masked_tensor_model.fit(a, tf.constant([[1], [0], [1]]), epochs=3)
print(masked_tensor_model(a))
Epoch 1/3
1/1 [==============================] - 1s 955ms/step - loss: 10.2833
Epoch 2/3
1/1 [==============================] - 0s 5ms/step - loss: 10.2833
Epoch 3/3
1/1 [==============================] - 0s 5ms/step - loss: 10.2833
tf.Tensor(
[[-0.09944128]
 [-0.7225147 ]
 [-1.3020657 ]], shape=(3, 1), dtype=float32)

Modello salvato

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 tipi di estensione possono essere usati in modo trasparente con le funzioni e i metodi definiti da un SavedModel.

SavedModel può salvare modelli, livelli e le funzioni che tipi di estensione processo, a patto che i tipi di estensione hanno un __name__ campo. Questo nome viene utilizzato per registrare il tipo di estensione, in modo che possa essere individuato quando il modello viene caricato.

Esempio: salvataggio di un modello Keras

Modelli Keras che utilizzano tipi di estensione possono essere salvate utilizzando SavedModel .

masked_tensor_model_path = tempfile.mkdtemp()
tf.saved_model.save(masked_tensor_model, masked_tensor_model_path)
imported_model = tf.saved_model.load(masked_tensor_model_path)
imported_model(a)
2021-11-06 01:25:14.285250: W tensorflow/python/util/util.cc:368] 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/tmp3ceuupv9/assets
INFO:tensorflow:Assets written to: /tmp/tmp3ceuupv9/assets
<tf.Tensor: shape=(3, 1), dtype=float32, numpy=
array([[-0.09944128],
       [-0.7225147 ],
       [-1.3020657 ]], dtype=float32)>

Esempio: salvataggio di un modello personalizzato

SavedModel può anche essere utilizzato per salvare personalizzato tf.Module sottoclassi con funzioni che tipi di estensione processo.

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

  @tf.function
  def grow(self, x: MaskedTensor):
    """Increase values in `x` by multiplying them by `self.v`."""
    return MaskedTensor(x.values * self.v, x.mask)

module = CustomModule(100.0)

module.grow.get_concrete_function(MaskedTensor.Spec(shape=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(MaskedTensor([1., 2, 3], [False, True, False]))
INFO:tensorflow:Assets written to: /tmp/tmp2x8zq5kb/assets
INFO:tensorflow:Assets written to: /tmp/tmp2x8zq5kb/assets
<MaskedTensor [_, 200.0, _]>

Caricamento di un modello salvato quando ExtensionType non è disponibile

Se si carica un SavedModel che utilizza un ExtensionType , ma che ExtensionType non è disponibile (ad esempio, non è stato importato), poi si vedrà un avvertimento e tensorflow cadrà di nuovo a usando un oggetto "anonimo tipo di estensione". Questo oggetto avrà gli stessi campi del tipo originale, ma mancherà di qualsiasi ulteriore personalizzazione aggiunta per il tipo, come metodi o proprietà personalizzati.

Utilizzo di ExtensionTypes con il servizio TensorFlow

Attualmente, tensorflow servire (e gli altri consumatori del dizionario "firme" SavedModel) richiedono che tutti gli ingressi e le uscite siano tensori prime. Se desideri utilizzare la pubblicazione di TensorFlow con un modello che utilizza i tipi di estensione, puoi aggiungere metodi wrapper che compongono o scompongono i valori dei tipi di estensione dai tensori. Per esempio:

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

  @tf.function
  def var_weighted_mean(self, x: MaskedTensor):
    """Mean value of unmasked values in x, weighted by self.v."""
    x = MaskedTensor(x.values * self.v, x.mask)
    return (tf.reduce_sum(x.with_default(0)) /
            tf.reduce_sum(tf.cast(x.mask, x.dtype)))

  @tf.function()
  def var_weighted_mean_wrapper(self, x_values, x_mask):
    """Raw tensor wrapper for var_weighted_mean."""
    return self.var_weighted_mean(MaskedTensor(x_values, x_mask))

module = CustomModuleWrapper([3., 2., 8., 5.])

module.var_weighted_mean_wrapper.get_concrete_function(
    tf.TensorSpec(None, tf.float32), tf.TensorSpec(None, tf.bool))
custom_module_path = tempfile.mkdtemp()
tf.saved_model.save(module, custom_module_path)
imported_model = tf.saved_model.load(custom_module_path)
x = MaskedTensor([1., 2., 3., 4.], [False, True, False, True])
imported_model.var_weighted_mean_wrapper(x.values, x.mask)
INFO:tensorflow:Assets written to: /tmp/tmpxhh4zh0i/assets
INFO:tensorflow:Assets written to: /tmp/tmpxhh4zh0i/assets
<tf.Tensor: shape=(), dtype=float32, numpy=12.0>

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.

Creazione di set di dati con tipi di estensione

Dati possono essere costruiti da valori di tipo estensione utilizzando Dataset.from_tensors , Dataset.from_tensor_slices o Dataset.from_generator :

ds = tf.data.Dataset.from_tensors(Pastry(5, 5))
iter(ds).next()
Pastry(sweetness=<tf.Tensor: shape=(), dtype=int32, numpy=5>, chewiness=<tf.Tensor: shape=(), dtype=int32, numpy=5>)
mt = MaskedTensor(tf.reshape(range(20), [5, 4]), tf.ones([5, 4]))
ds = tf.data.Dataset.from_tensor_slices(mt)
for value in ds:
  print(value)
<MaskedTensor [0, 1, 2, 3]>
<MaskedTensor [4, 5, 6, 7]>
<MaskedTensor [8, 9, 10, 11]>
<MaskedTensor [12, 13, 14, 15]>
<MaskedTensor [16, 17, 18, 19]>
def value_gen():
  for i in range(2, 7):
    yield MaskedTensor(range(10), [j%i != 0 for j in range(10)])

ds = tf.data.Dataset.from_generator(
    value_gen, output_signature=MaskedTensor.Spec(shape=[10], dtype=tf.int32))
for value in ds:
  print(value)
<MaskedTensor [_, 1, _, 3, _, 5, _, 7, _, 9]>
<MaskedTensor [_, 1, 2, _, 4, 5, _, 7, 8, _]>
<MaskedTensor [_, 1, 2, 3, _, 5, 6, 7, _, 9]>
<MaskedTensor [_, 1, 2, 3, 4, _, 6, 7, 8, 9]>
<MaskedTensor [_, 1, 2, 3, 4, 5, _, 7, 8, 9]>

Batch e unbatching di set di dati con tipi di estensione

Serie di dati con tipi di estensione possono essere batchand e unbatched utilizzando Dataset.batch adn Dataset.unbatch .

batched_ds = ds.batch(2)
for value in batched_ds:
  print(value)
<MaskedTensor [[_, 1, _, 3, _, 5, _, 7, _, 9], [_, 1, 2, _, 4, 5, _, 7, 8, _]]>
<MaskedTensor [[_, 1, 2, 3, _, 5, 6, 7, _, 9], [_, 1, 2, 3, 4, _, 6, 7, 8, 9]]>
<MaskedTensor [[_, 1, 2, 3, 4, 5, _, 7, 8, 9]]>
unbatched_ds = batched_ds.unbatch()
for value in unbatched_ds:
  print(value)
<MaskedTensor [_, 1, _, 3, _, 5, _, 7, _, 9]>
<MaskedTensor [_, 1, 2, _, 4, 5, _, 7, 8, _]>
<MaskedTensor [_, 1, 2, 3, _, 5, 6, 7, _, 9]>
<MaskedTensor [_, 1, 2, 3, 4, _, 6, 7, 8, 9]>
<MaskedTensor [_, 1, 2, 3, 4, 5, _, 7, 8, 9]>