Типы расширения TensorFlow, объясненные примерами

Типы расширения TensorFlow, объясненные примерами

28 июля 2025 г.

Обзор контента

  • Настраивать
  • Типы расширения
  • Поддерживается API
  • Требования
  • Типы поля
  • Изменчивость
  • Функциональность добавлена по расширению
  • Конструктор
  • Печатное представление
  • Операторы равенства
  • Метод проверки
  • Принудительная неизменность
  • Вложенные типы
  • Настройка extensionTypes
  • Переопределение печатного представления по умолчанию
  • Определение методов
  • Определение класса -матодов и статичныхметодов
  • Определение свойств
  • Переоценивать конструктор по умолчанию
  • Переопределение оператора равенства по умолчанию (уравнение)
  • Используя прямые ссылки
  • Определение подклассов
  • Определение частных полей
  • Настройка TypeSpec от extensionType

Настраивать

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

Типы расширения

Пользовательские типы могут сделать проекты более читаемыми, модульными, обслуживаемыми. Тем не менее, большинство API-интерфейсов TensorFlow имеют очень ограниченную поддержку для пользовательских типов Python. Это включает в себя оба API высокого уровня (напримерКерасВtf.functionВtf.SavedModel) и API нижнего уровня (напримерtf.while_loopиtf.concat) TensorflowТипы расширенияМожет использоваться для создания пользовательских объектно-ориентированных типов, которые плавно работают с API-интерфейсом Tensorflow. To create an extension type, simply define a Python class withtf.experimental.ExtensionTypeв качестве основания и используйтеТип аннотацииЧтобы указать тип для каждого поля.

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

Аtf.experimental.ExtensionTypeбазовый класс работает аналогичноtyping.NamedTupleи@dataclasses.dataclassИз стандартной библиотеки Python. В частности, он автоматически добавляет конструктор и специальные методы (например, как__repr__и__eq__) на основе аннотаций типа поля.

Как правило, типы расширения, как правило, попадают в одну из двух категорий:

  • Структуры данных, который объединяет набор связанных значений и может обеспечить полезные операции на основе этих значений. Структуры данных могут быть довольно общими (напримерTensorGraphпример выше); Или они могут быть высоко настроены на конкретную модель.
  • Тенсорные типы, которые специализируют или расширяют концепцию «тензора». Типы в этой категории имеютrank, аshapeи обычноdtype; и имеет смысл использовать их с тензорными операциями (например, какtf.stackВtf.add, илиtf.matmul)MaskedTensorиCSRSparseMatrixпримеры тензоров, подобных типам.

Поддерживается API

Типы расширения подтверждаются следующими API -интерфейсом tensorflow:

  • Керас: Типы расширения могут использоваться в качестве входов и выходов для керовModelsиLayersПолем
  • tf.data.Dataset: Типы расширения могут быть включены вDatasetsи возвращается набором данныхIteratorsПолем
  • Tensorflow Hub: Типы расширения могут использоваться в качестве входов и выходов дляtf.hubмодули.
  • Сохраняйте модель: Типы расширения могут использоваться в качестве входов и выходов дляSavedModelфункции.
  • tf.function: Типы расширения могут использоваться в качестве аргументов и возвратных значений для функций, завернутых@tf.functionдекоратор.
  • Пока петли: Типы расширения могут использоваться в качестве переменных цикла вtf.while_loop, и может использоваться в качестве аргументов и возвращающих значений для тела That-Ploop.
  • Условия: Типы расширения могут быть условно выбраны с помощьюtf.condиtf.caseПолем
  • tf.py_function: Типы расширения могут использоваться в качестве аргументов и возвратных значений дляfuncаргументtf.py_functionПолем
  • Tensor Ops: Типы расширения могут быть расширены для поддержки большинства Tensorflow Ops, которые принимают тензорные входы (например, какtf.matmulВtf.gather, иtf.reduce_sum) Пойти в "Отправлять"Раздел ниже для получения дополнительной информации.
  • Стратегия распределения: Типы удлинения могут использоваться в качестве значений для за повторения.

Более подробную информацию см. В разделе «API -интерфейсы Tensorflow, которые поддерживают extensionTypes» ниже.

Требования

Типы поля

Все поля - переменные, которые должны быть объявлены, и аннотация типа должна быть предусмотрена для каждого поля. Поддерживаются следующие аннотации типа:

Тип

Пример

Python Itgers

i: int

Питон плавает

f: float

Струны Python

s: str

Python Booleans

b: bool

ПитонNone

n: None

Тенсорные формы

shape: tf.TensorShape

Тензорdtypeс

dtype: tf.DType

Тензоры

t: tf.Tensor

Типы расширения

mt: MyMaskedTensor

Рваные тензоры

rt: tf.RaggedTensor

Разреженные тензоры

st: tf.SparseTensor

Индексированные ломтики

s: tf.IndexedSlices

Дополнительные тензоры

o: tf.experimental.Optional

Типа профсоюзов

int_or_float: typing.Union[int, float]

Кортежи

params: typing.Tuple[int, float, tf.Tensor, int]

Клетки длиной длины

lengths: typing.Tuple[int, ...]

Отображения

tags: typing.Mapping[str, tf.Tensor]

Необязательные значения

weight: typing.Optional[tf.Tensor]

Изменчивость

Типы расширения должны быть неизменными. Это гарантирует, что они могут быть должным образом отслеживаны механизмами графика Tensorflow. Если вы обнаружите, что хотите мутировать значение типа расширения, вместо этого рассмотрите возможность определения методов, которые преобразуют значения. Например, вместо определенияset_maskметод для мутированияMaskedTensor, вы можете определитьreplace_maskметод, который возвращает новый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)

Функциональность добавленаExtensionType

АExtensionTypeБазовый класс предоставляет следующую функциональность:

  • Конструктор (__init__)
  • Метод печати представления (__repr__)
  • Операторы равенства и неравенства (__eq__)
  • Метод проверки (__validate__)
  • Принудительная неизменность.
  • ВложенныйTypeSpecПолем
  • Tensor API -поддержка диспетчерской.

Перейти к «настройкеExtensionTypeS "Раздел ниже для получения дополнительной информации о настройке этой функции.

Конструктор

Конструктор, добавленныйExtensionTypeпринимает каждое поле в качестве названного аргумента (в порядке они были перечислены в определении класса). Этот конструктор будет проверять каждый параметр и при необходимости конвертирует их. В частности,TensorПоля преобразуются с использованиемtf.convert_to_tensorTupleполя преобразуются вtupleS; иMappingПоля преобразуются в неизменные дайки.

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.
# For example, `mt.values` is converted to a Tensor.
print(mt.values)

Конструктор поднимаетTypeErrorЕсли значение поля не может быть преобразовано в его объявленный тип:

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

Значение по умолчанию для поля может быть указано, установив его значение на уровне класса:

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

Pencil()

Pencil(length=0.5, color="blue")

Печатное представление

ExtensionTypeДобавляет метод представления по умолчанию по умолчанию (__repr__) Это включает имя класса и значение для каждого поля:

print(MaskedTensor(values=[1, 2, 3], mask=[True, True, False]))

Операторы равенства

ExtensionTypeдобавляет операторов равенства по умолчанию (__eq__и__ne__), которые рассматривают два значения равны, если они имеют одинаковый тип, и все их поля равны. Тенсорные поля считаются равными, если они имеют одинаковую форму и являются элементами равны для всех элементов.

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

Примечание:Если какое -либо поле содержитTensor, затем__eq__может вернуть скалярное логическоеTensor(а не логическое значение Python).

Метод проверки

ExtensionTypeдобавляет__validate__Метод, который может быть переопределен для выполнения проверки проверки на полях. Он запускается после того, как конструктор называется, и после того, как поля были проверены типом и преобразованы в их объявленные типы, поэтому он может предположить, что все поля имеют свои объявленные типы.

Следующий пример обновленийMaskedTensorЧтобы подтвердитьshapeпесокdtypeс его полей:

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

try:
  MaskedTensor([1, 2, 3], [True, False])  # shapes don't match.
except ValueError as e:
  print(f"Got expected ValueError: {e}")

Принудительная неизменность

ExtensionTypeпереопределяет__setattr__и__delattr__Методы предотвращения мутации, гарантируя, что значения типа удлинения неизменными.

mt = MaskedTensor([1, 2, 3], [True, False, True])

try:
  mt.mask = [True, True, True]
except AttributeError as e:
  print(f"Got expected AttributeError: {e}")

try:
  mt.mask[0] = False
except TypeError as e:
  print(f"Got expected TypeError: {e}")

try:
  del mt.mask
except AttributeError as e:
  print(f"Got expected AttributeError: {e}")

Вложенные типы

КаждыйExtensionTypeкласс имеет соответствующийTypeSpecкласс, который создается автоматически и хранится как<extension_type_name>.SpecПолем

Этот класс фиксирует всю информацию из значениякромеДля ценностей любых вложенных тензоров. В частности,TypeSpecдля значения создается путем замены любого вложенного тензора, extensiontype или 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.

TypeSpecзначения могут быть построены явно, или они могут быть построены изExtensionTypeзначение с использованиемtf.type_spec_from_value:

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

TypeSpecs используются TensorFlow для разделения значений настатический компонентидинамический компонент:

  • Астатический компонент(который фиксируется во время строительства графика) кодируется сtf.TypeSpecПолем
  • Адинамический компонент(который может варьироваться каждый раз, когда запускается график) кодируется как списокtf.Tensorс

Например,tf.functionПоискает свою обернутую функцию всякий раз, когда у аргумента ранее невидимый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}))

# Function does NOT get traced (same TypeSpec: just tensor values changed)
anonymize_player(Player("Bart", {"height": 8.1, "speed": 25.3}))

# Function gets traced (new TypeSpec: keys for attributes changed):
anonymize_player(Player("Chuck", {"height": 11.0, "jump": 5.3}))

Для получения дополнительной информации см.TF.Function GuideПолем

НастройкаExtensionTypeс

В дополнение к простому объявлению полей и их типам, типы расширения могут:

  • Переопределить представление о печати по умолчанию (__repr__)
  • Определить методы.
  • Определятьclassmethodпесокstaticmethodс
  • Определить свойства.
  • Переопределить конструктор по умолчанию (__init__)
  • Переопределить оператора равенства по умолчанию (__eq__)
  • Определить операторов (например__add__и__lt__)
  • Объявить значения по умолчанию для полей.
  • Определите подклассы.

Переопределение печатного представления по умолчанию

Вы можете переопределить этот оператор преобразования строк по умолчанию для типов расширения. В следующем примере обновляетMaskedTensorКласс для генерации более читаемого представления строки, когда значения печатаются в нетерпеливом режиме.

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)

Определение методов

Типы расширения могут определять методы, как и любой обычный класс Python. Например,MaskedTensorТип может определитьwith_defaultметод, который возвращает копиюselfс заменой значений в маскахdefaultценить. Методы могут быть аннотированы с помощью@tf.functionдекоратор.

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)

Определениеclassmethodпесокstaticmethodс

Типы расширения могут определять методы, используя@classmethodи@staticmethodдекораторы. Например,MaskedTensorТип может определить фабричный метод, который маскирует любой элемент с заданным значением:

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)

Определение свойств

Типы расширения могут определять свойства, используя@propertyДекоратор, как и любой обычный класс Python. Например,MaskedTensorТип может определитьdtypeсобственность, которая является сокращением дляdtypeзначений:

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

Переоценивать конструктор по умолчанию

Вы можете переопределить конструктор по умолчанию для типов расширения. Пользовательские конструкторы должны установить значение для каждого объявленного поля; и после возврата пользовательского конструктора все поля будут проверены типом, а значения будут преобразованы, как описано выше.

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!

В качестве альтернативы вы можете подумать о том, чтобы оставить конструктор по умолчанию как есть, но добавление одного или нескольких заводских методов. Например:

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

Переопределение оператора равенства по умолчанию (__eq__)

Вы можете переопределить по умолчанию__eq__оператор для типов расширения. Следующий пример обновленийMaskedTensorигнорировать элементы маски при сравнении с равенством.

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)

Примечание:Как правило, вам не нужно переопределять__ne__, поскольку его реализация по умолчанию просто вызывает__eq__и отрицает результат.

Используя прямые ссылки

Если тип для поля еще не определен, вы можете использовать строку, содержащую имя типа. В следующем примере строка"Node"используется для аннотированияchildrenполе, потому чтоNodeТип еще не был определен (полностью).

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

Node(3, [Node(5), Node(2)])

Определение подклассов

Типы расширения могут быть подклассы с использованием стандартного синтаксиса Python. Подклассы типа расширения могут добавлять новые поля, методы и свойства; и может переопределить конструктор, печатное представление и оператор равенства. В следующем примере определяется базовыйTensorGraphкласс, который использует триTensorполя, чтобы кодировать набор краев между узлами. Затем он определяет подкласс, который добавляетTensorполе для записи «значения функции» для каждого узла. Подкласс также определяет метод распространения значений функций по краям.

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)

Определение частных полей

Поля типа расширения могут быть отмечены частными путем префикса их подчеркиванием (после стандартных конвенций на Python). Это не влияет на то, как Tensorflow каким -либо образом относится к полям; Но просто служит сигналом для любого пользователям расширения, что эти поля являются частными.

НастройкаExtensionTypeS.TypeSpec

КаждыйExtensionTypeкласс имеет соответствующийTypeSpecкласс, который создается автоматически и хранится как<extension_type_name>.SpecПолем Для получения дополнительной информации см. Раздел «Вложенные типы» выше.

Чтобы настроитьTypeSpec, просто определите свой собственный вложенный класс по имениSpec, иExtensionTypeбудет использовать это в качестве основы для автоматически построенногоTypeSpecПолем Вы можете настроитьSpecкласс по:

  • Переоценивая представление по умолчанию.
  • Переопределение конструктора по умолчанию.
  • Определение методов,classmethodс,staticmethodS и свойства.

В следующем примере настраиваетMaskedTensor.Specкласс, чтобы облегчить использование:

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)

Примечание:ОбычайSpecКласс может не использовать какие -либо переменные экземпляра, которые не были объявлены в оригиналеExtensionTypeПолем


Первоначально опубликовано наTensorflowВеб -сайт, эта статья появляется здесь под новым заголовком и имеет лицензию в CC на 4.0. Образцы кода, разделенные по лицензии Apache 2.0.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE