انواع افزونه

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در GitHubدانلود دفترچه یادداشت

برپایی

!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های سطح بالا (مانند Keras ، tf.function ، tf.SavedModel ) و APIهای سطح پایین (مانند tf.while_loop و tf.concat ) می شود. از انواع پسوند TensorFlow می توان برای ایجاد انواع شی گرا تعریف شده توسط کاربر استفاده کرد که به طور یکپارچه با API های TensorFlow کار می کنند. برای ایجاد یک نوع پسوند، به سادگی یک کلاس پایتون با tf.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 از کتابخانه استاندارد پایتون کار می کند. به ویژه، به طور خودکار یک سازنده و روش‌های خاص (مانند __repr__ و __eq__ ) را بر اساس حاشیه‌نویسی‌های نوع فیلد اضافه می‌کند.

به طور معمول، انواع پسوند به یکی از دو دسته تقسیم می شوند:

  • ساختارهای داده ، که مجموعه ای از مقادیر مرتبط را در کنار هم قرار می دهند و می توانند عملیات مفیدی را بر اساس آن مقادیر ارائه دهند. ساختارهای داده ممکن است نسبتاً کلی باشند (مانند مثال TensorGraph در بالا). یا ممکن است به شدت برای یک مدل خاص سفارشی شوند.

  • انواع تانسور مانند ، که مفهوم "تنسور" را تخصصی یا گسترش می دهند. انواع این دسته دارای rank ، shape و معمولاً dtype . و منطقی است که از آنها با عملیات Tensor استفاده کنید (مانند tf.stack ، tf.add ، یا tf.matmul ). MaskedTensor و CSRSparseMatrix نمونه هایی از انواع تانسور مانند هستند.

API های پشتیبانی شده

انواع برنامه های افزودنی توسط API های TensorFlow زیر پشتیبانی می شوند:

  • Keras : انواع برنامه های افزودنی را می توان به عنوان ورودی و خروجی برای Models ها و Layers های Keras استفاده کرد.
  • tf.data.Dataset : انواع پسوند را می توان در Datasets گنجاند و توسط Dataset Iterators برگردانده شد.
  • هاب Tensorflow : انواع برنامه های افزودنی را می توان به عنوان ورودی و خروجی برای ماژول های tf.hub استفاده کرد.
  • SavedModel : انواع برنامه های افزودنی را می توان به عنوان ورودی و خروجی برای توابع SavedModel استفاده کرد.
  • tf.function : انواع برنامه های افزودنی را می توان به عنوان آرگومان و مقادیر برگردانده برای توابع پیچیده شده با @tf.function decorator استفاده کرد.
  • حلقه‌های while : انواع برنامه‌های افزودنی را می‌توان به‌عنوان متغیرهای حلقه در tf.while_loop استفاده کرد و می‌تواند به‌عنوان آرگومان و مقادیر بازگشتی برای بدنه حلقه while استفاده شود.
  • شرطی : انواع پسوند را می توان به صورت مشروط با استفاده از tf.cond و tf.case کرد.
  • py_function : انواع پسوند را می توان به عنوان آرگومان استفاده کرد و مقادیر را برای آرگومان func به tf.py_function .
  • Tensor ops : انواع برنامه های افزودنی را می توان برای پشتیبانی از اکثر عملیات های TensorFlow که ورودی های Tensor را می پذیرند، گسترش داد (به عنوان مثال، tf.matmul ، tf.gather ، و tf.reduce_sum ). برای اطلاعات بیشتر به بخش " ارسال " در زیر مراجعه کنید.
  • استراتژی توزیع : انواع پسوند را می توان به عنوان مقادیر هر ماکت استفاده کرد.

برای جزئیات بیشتر، بخش "API های TensorFlow که از ExtensionTypes پشتیبانی می کنند" را در زیر ببینید.

الزامات

انواع میدان

همه فیلدها (متغیرهای نمونه با نام مستعار) باید اعلان شوند، و یک نوع حاشیه نویسی باید برای هر فیلد ارائه شود. حاشیه نویسی نوع زیر پشتیبانی می شود:

تایپ کنید مثال
اعداد صحیح پایتون i: int
پایتون شناور است f: float
رشته های پایتون s: str
بولین های پایتون b: bool
پایتون هیچکدام n: None
اشکال تانسور shape: tf.TensorShape
تانسور dtypes 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.

برای اطلاعات بیشتر در مورد سفارشی کردن این عملکرد، بخش "سفارشی کردن انواع برنامه های افزودنی" را در زیر ببینید.

سازنده

سازنده اضافه شده توسط ExtensionType هر فیلد را به عنوان آرگومان نامگذاری شده (به ترتیبی که در تعریف کلاس فهرست شده است) می گیرد. این سازنده هر پارامتر را تایپ می کند و در صورت لزوم آنها را تبدیل می کند. به طور خاص، فیلدهای Tensor با استفاده از tf.convert_to_tensor تبدیل می شوند. فیلدهای Tuple به tuple s تبدیل می شوند. و فیلدهای 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.
# 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)

اگر یک مقدار فیلد را نتوان به نوع اعلام شده خود تبدیل کرد، سازنده یک TypeError ایجاد می کند:

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

مقدار پیش فرض یک فیلد را می توان با تنظیم مقدار آن در سطح کلاس مشخص کرد:

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

نمایندگی قابل چاپ

ExtensionType یک روش نمایش قابل چاپ پیش فرض ( __repr__ ) اضافه می کند که شامل نام کلاس و مقدار هر فیلد است:

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

اپراتورهای برابری

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}")
a == a: True
a == b: False
a == a.values: False

روش اعتبارسنجی

ExtensionType یک متد __validate__ را اضافه می کند که می تواند برای انجام بررسی های اعتبار سنجی فیلدها لغو شود. پس از فراخوانی سازنده، و پس از بررسی تایپ فیلدها و تبدیل آنها به انواع اعلام شده اجرا می شود، بنابراین می توان فرض کرد که همه فیلدها دارای انواع اعلام شده خود هستند.

او مثال زیر MaskedTensor را به‌روزرسانی می‌کند تا shape s و dtype s فیلدهای آن را تأیید کند:

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

تغییرناپذیری اجباری

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

تو در تو TypeSpec

هر کلاس ExtensionType یک کلاس TypeSpec مربوطه دارد که به طور خودکار ایجاد می شود و به عنوان <extension_type_name>.Spec ذخیره می شود.

این کلاس تمام اطلاعات یک مقدار را به جز مقادیر تانسورهای تودرتو می گیرد. به طور خاص، TypeSpec برای یک مقدار با جایگزینی هر Tensor، 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.
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 را می توان به طور صریح ساخت، یا می توان آنها را از یک مقدار ExtensionType با استفاده از tf.type_spec_from_value :

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

TypeSpec توسط TensorFlow برای تقسیم مقادیر به یک مؤلفه استاتیک و یک مؤلفه دینامیک استفاده می شود :

  • جزء استاتیک (که در زمان ساخت نمودار ثابت می شود) با tf.TypeSpec کدگذاری می شود.
  • مؤلفه پویا (که می تواند هر بار که نمودار اجرا می شود تغییر کند) به عنوان لیستی از tf.Tensor s کدگذاری می شود.

برای مثال، 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}))
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>}))

برای اطلاعات بیشتر، به راهنمای tf.function مراجعه کنید.

سفارشی کردن ExtensionTypes

علاوه بر اعلام ساده فیلدها و انواع آنها، انواع پسوند ممکن است:

  • بازنمایی پیش‌فرض قابل چاپ ( __repr__ ) را لغو کنید.
  • روش ها را تعریف کنید.
  • متدهای کلاسی و استاتیکی را تعریف کنید.
  • خواص را تعریف کنید
  • نادیده گرفتن سازنده پیش فرض ( __init__ ).
  • عملگر برابری پیش‌فرض ( __eq__ ) را نادیده بگیرید.
  • عملگرها را تعریف کنید (مانند __add__ و __lt__ ).
  • مقادیر پیش فرض فیلدها را اعلام کنید.
  • زیر کلاس ها را تعریف کنید

لغو نمایش پیش‌فرض قابل چاپ

می‌توانید این عملگر تبدیل رشته پیش‌فرض را برای انواع پسوند لغو کنید. مثال زیر کلاس MaskedTensor را به‌روزرسانی می‌کند تا زمانی که مقادیر در حالت 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]]>

تعریف روش ها

انواع پسوند ممکن است متدها را تعریف کنند، درست مانند هر کلاس پایتون معمولی. به عنوان مثال، نوع 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)
<tf.Tensor: shape=(3,), dtype=int32, numpy=array([1, 0, 3], dtype=int32)>

تعریف روشهای کلاس و روشهای استاتیکی

انواع برنامه های افزودنی ممکن است روش هایی را با استفاده از دکوراتورهای @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)
<MaskedTensor [[_, 0, _], [_, 0, 0]]>

تعریف خواص

انواع برنامه‌های افزودنی ممکن است مانند هر کلاس پایتون معمولی، ویژگی‌ها را با استفاده از decorator @property تعریف کنند. به عنوان مثال، نوع 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
tf.int32

نادیده گرفتن سازنده پیش فرض

می‌توانید سازنده پیش‌فرض را برای انواع پسوند لغو کنید. سازنده های سفارشی باید برای هر فیلد اعلام شده یک مقدار تعیین کنند. و پس از بازگشت سازنده سفارشی، تمام فیلدها تایپ بررسی می شوند و مقادیر به شرح بالا تبدیل می شوند.

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

از طرف دیگر، می‌توانید سازنده پیش‌فرض را همانطور که هست رها کنید، اما یک یا چند روش کارخانه را اضافه کنید. به عنوان مثال:

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

نادیده گرفتن عملگر برابری پیش فرض ( __eq__ )

می‌توانید عملگر پیش‌فرض __eq__ را برای انواع برنامه‌های افزودنی لغو کنید. مثال زیر MaskedTensor را به‌روزرسانی می‌کند تا هنگام مقایسه برای برابری، عناصر 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)
tf.Tensor(True, shape=(), dtype=bool)

استفاده از مراجع رو به جلو

اگر نوع یک فیلد هنوز تعریف نشده است، می توانید به جای آن از یک رشته حاوی نام نوع استفاده کنید. در مثال زیر، رشته "Node" برای حاشیه نویسی فیلد children استفاده می شود زیرا نوع Node هنوز (به طور کامل) تعریف نشده است.

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

تعریف زیر کلاس ها

انواع برنامه های افزودنی ممکن است با استفاده از نحو استاندارد پایتون زیر کلاس بندی شوند. زیر کلاس‌های نوع پسوند ممکن است فیلدها، روش‌ها و ویژگی‌های جدیدی اضافه کنند. و ممکن است سازنده، نمایش قابل چاپ و عملگر برابری را لغو کند. مثال زیر یک کلاس اصلی 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)
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)

تعریف فیلدهای خصوصی

فیلدهای یک نوع پسوند ممکن است با پیشوند کردن آنها با یک زیرخط (طبق قراردادهای استاندارد پایتون) خصوصی مشخص شوند. این روشی که TensorFlow با فیلدها برخورد می کند به هیچ وجه تأثیری ندارد. اما به سادگی به عنوان یک سیگنال برای هر کاربر از نوع پسوند عمل می کند که آن فیلدها خصوصی هستند.

سفارشی کردن TypeSpec ExtensionType

هر کلاس ExtensionType یک کلاس TypeSpec مربوطه دارد که به طور خودکار ایجاد می شود و به عنوان <extension_type_name>.Spec ذخیره می شود. برای اطلاعات بیشتر، به بخش "Nested TypeSpec" در بالا مراجعه کنید.

برای سفارشی کردن TypeSpec ، به سادگی کلاس تودرتوی خود را با نام Spec تعریف کنید، و ExtensionType از آن به عنوان مبنایی برای TypeSpec ساخته شده به طور خودکار استفاده می کند. شما می توانید کلاس Spec را با موارد زیر سفارشی کنید:

  • لغو نمایش پیش‌فرض قابل چاپ.
  • نادیده گرفتن سازنده پیش فرض.
  • تعریف روش‌ها، روش‌های کلاس، روش‌های استاتیکی و خواص.

مثال زیر کلاس 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)

ارسال Tensor API

انواع برنامه های افزودنی می توانند "تانسورمانند" باشند، به این معنا که رابط تعریف شده توسط نوع tf.Tensor را تخصصی یا گسترش می دهند. نمونه‌هایی از انواع پسوند تانسور مانند عبارتند از RaggedTensor ، SparseTensor و MaskedTensor . دکوراتورهای Dispatch می‌توانند برای نادیده گرفتن رفتار پیش‌فرض عملیات TensorFlow هنگام اعمال بر روی انواع پسوند تانسور مانند استفاده شوند. TensorFlow در حال حاضر سه دکوراتور اعزامی را تعریف می کند:

ارسال برای یک API واحد

دکوراتور tf.experimental.dispatch_for_api رفتار پیش‌فرض یک عملیات TensorFlow مشخص شده را زمانی که با امضای مشخص فراخوانی می‌شود لغو می‌کند. برای مثال، می‌توانید از این دکوراتور برای تعیین نحوه پردازش مقادیر tf.stack MaskedTensor استفاده کنید:

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

هر زمان که با لیستی از مقادیر MaskedTensor فراخوانی شود، اجرای پیش‌فرض tf.stack لغو می‌شود (زیرا آرگومان values با تایپ کردن حاشیه‌نویسی 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]]>

برای اینکه به tf.stack اجازه دهید فهرستی از مقادیر ترکیبی MaskedTensor و Tensor را مدیریت کند، می توانید نوع حاشیه نویسی پارامتر values را اصلاح کنید و بدنه تابع را به طور مناسب به روز کنید:

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

برای فهرستی از APIهایی که می‌توانند لغو شوند، به مستندات API برای tf.experimental.dispatch_for_api کنید.

ارسال برای همه APIهای عنصری واحد

تزیین کننده tf.experimental.dispatch_for_unary_elementwise_apis رفتار پیش‌فرض همه عملیات‌های unary elementwise (مانند tf.math.cos ) را هر زمان که مقدار آرگومان اول (معمولاً با نام x ) مطابق با نوع حاشیه نویسی x_type باشد، لغو می‌کند. تابع تزئین شده باید دو آرگومان داشته باشد:

  • api_func : تابعی که یک پارامتر واحد را می گیرد و عملیات عنصر را انجام می دهد (مثلا tf.abs ).
  • x : اولین آرگومان برای عملیات elementwise.

مثال زیر تمام عملیات unary elementwise را برای مدیریت نوع MaskedTensor به روز می کند:

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

این تابع اکنون هر زمان که یک عملیات unary elementwise روی 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]>

ارسال برای همه APIهای باینری عنصر

به طور مشابه، tf.experimental.dispatch_for_binary_elementwise_apis را می توان برای به روز رسانی تمام عملیات عناصر باینری برای مدیریت نوع MaskedTensor استفاده کرد:

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

برای فهرستی از APIهای عنصری که لغو شده‌اند، به مستندات API برای tf.experimental.dispatch_for_unary_elementwise_apis و tf.experimental.dispatch_for_binary_elementwise_apis کنید.

انواع پسوند قابل دسته بندی

یک ExtensionType قابل دسته بندی است اگر بتوان از یک نمونه برای نمایش دسته ای از مقادیر استفاده کرد. به طور معمول، این کار با افزودن ابعاد دسته ای به تمام Tensor تودرتو انجام می شود. API های TensorFlow زیر مستلزم این هستند که هر ورودی نوع افزونه قابل دسته بندی باشد:

به طور پیش‌فرض، BatchableExtensionType مقادیر دسته‌ای را با دسته‌بندی هر Tensor ، CompositeTensor و ExtensionType ایجاد می‌کند. اگر این برای کلاس شما مناسب نیست، باید از tf.experimental.ExtensionTypeBatchEncoder برای لغو این رفتار پیش فرض استفاده کنید. برای مثال، ایجاد دسته‌ای از مقادیر tf.SparseTensor با کنار هم قرار دادن values تانسورهای پراکنده، indices و فیلدهای dense_shape - در بیشتر موارد، نمی‌توانید این تانسورها را روی هم قرار دهید، زیرا آنها اشکال ناسازگاری دارند. ; و حتی اگر بتوانید، نتیجه SparseTensor معتبر نخواهد بود.

نمونه BatchableExtensionType: شبکه

به عنوان مثال، یک کلاس Network ساده را در نظر بگیرید که برای متعادل کردن بار استفاده می شود، که میزان کار باقی مانده برای انجام هر گره و میزان پهنای باند موجود برای جابجایی کار بین گره ها را ردیابی می کند:

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

برای اینکه این نوع دسته‌بندی شود، نوع پایه را به BatchableExtensionType تغییر دهید و شکل هر فیلد را طوری تنظیم کنید که ابعاد دسته‌ای اختیاری داشته باشد. مثال زیر همچنین یک فیلد shape برای پیگیری شکل دسته اضافه می کند. این فیلد shape توسط tf.data.Dataset یا tf.map_fn مورد نیاز نیست، اما 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.]]]>

سپس می توانید از tf.data.Dataset برای تکرار از طریق دسته ای از شبکه ها استفاده کنید:

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

و همچنین می توانید از map_fn برای اعمال یک تابع به هر عنصر دسته ای استفاده کنید:

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 که از ExtensionTypes پشتیبانی می کنند

@tf.function

tf.function یک دکوراتور است که نمودارهای TensorFlow را برای توابع پایتون از قبل محاسبه می کند، که می تواند عملکرد کد TensorFlow شما را به طور قابل ملاحظه ای بهبود بخشد. مقادیر نوع برنامه افزودنی را می توان به صورت شفاف با توابع @tf.function -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)>

اگر می خواهید به صراحت input_signature را برای tf.function مشخص کنید، می توانید این کار را با استفاده از 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)>)

توابع بتنی

توابع بتن، نمودارهای ردیابی شده را که توسط tf.function ساخته شده اند، محصور می کنند. انواع پسوند را می توان به صورت شفاف با عملکردهای بتنی استفاده کرد.

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

کنترل عملیات جریان

انواع برنامه های افزودنی توسط عملیات کنترل جریان 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, _]>

جریان کنترل خودکار

انواع برنامه های افزودنی نیز توسط دستورات جریان کنترل در tf.function (با استفاده از خودکار) پشتیبانی می شوند. در مثال زیر دستور if و for به طور خودکار به عملیات tf.cond و tf.while_loop تبدیل می شوند که از انواع پسوند پشتیبانی می کنند.

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

کراس

tf.keras API سطح بالای TensorFlow برای ساخت و آموزش مدل های یادگیری عمیق است. انواع پسوند ممکن است به عنوان ورودی به یک مدل Keras ارسال شوند، بین لایه‌های Keras ارسال شوند و توسط مدل‌های Keras برگردانده شوند. Keras در حال حاضر دو الزام را در مورد انواع پسوند اعمال می کند:

  • آنها باید قابل دسته بندی باشند (به "انواع پسوند قابل دسته بندی" در بالا مراجعه کنید).
  • باید یک فیلد یا ویژگی به نام shape داشته باشد. shape[0] بعد دسته ای در نظر گرفته می شود.

دو بخش فرعی زیر مثال‌هایی ارائه می‌دهند که نشان می‌دهد چگونه می‌توان از انواع پسوند با Keras استفاده کرد.

مثال کراس: Network

برای مثال اول، کلاس Network تعریف شده در بخش "Batchable ExtensionTypes" در بالا را در نظر بگیرید، که می تواند برای کار متعادل سازی بار بین گره ها استفاده شود. تعریف آن در اینجا تکرار شده است:

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

می توانید یک لایه Keras جدید تعریف کنید که 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)

سپس می توانید از این لایه ها برای ایجاد یک مدل ساده استفاده کنید. برای تغذیه یک ExtensionType به یک مدل، می‌توانید از یک لایه tf.keras.layer.Input با type_spec روی TypeSpec نوع افزونه استفاده کنید. اگر از مدل Keras برای پردازش دسته ها استفاده می شود، در این type_spec باید شامل بعد دسته باشد.

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

در نهایت، می‌توانید مدل را برای یک شبکه و یک دسته از شبکه‌ها اعمال کنید.

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

مثال Keras: MaskedTensor

در این مثال، MaskedTensor برای پشتیبانی از Keras گسترش یافته است. shape به عنوان خاصیتی تعریف می شود که از فیلد values محاسبه می شود. Keras نیاز دارد که این ویژگی را هم به نوع پسوند و هم به TypeSpec آن اضافه کنید. MaskedTensor همچنین یک متغیر __name__ را تعریف می کند که برای سریال سازی SavedModel مورد نیاز است (در زیر).

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

سپس، دکوراتورهای اعزام برای نادیده گرفتن رفتار پیش‌فرض چندین API TensorFlow استفاده می‌شوند. از آنجایی که این APIها توسط لایه‌های استاندارد Keras (مانند لایه Dense ) استفاده می‌شوند، نادیده گرفتن آن‌ها به ما امکان می‌دهد از آن لایه‌ها با MaskedTensor استفاده کنیم. برای اهداف این مثال، matmul برای تانسورهای پوشانده تعریف شده است تا مقادیر پوشانده شده را به عنوان صفر در نظر بگیرند (یعنی برای درج نکردن آنها در محصول).

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

سپس می توانید یک مدل Keras بسازید که ورودی MaskedTensor را با استفاده از لایه های استاندارد 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)

SavedModel

SavedModel یک برنامه TensorFlow سریالی است که هم وزن و هم محاسبات را شامل می شود. می توان آن را از یک مدل Keras یا از یک مدل سفارشی ساخت. در هر صورت، انواع پسوند را می توان با توابع و روش های تعریف شده توسط SavedModel به صورت شفاف استفاده کرد.

SavedModel می‌تواند مدل‌ها، لایه‌ها و توابعی را که انواع پسوندها را پردازش می‌کنند، ذخیره کند، تا زمانی که انواع پسوند دارای فیلد __name__ باشند. این نام برای ثبت نوع پسوند استفاده می‌شود، بنابراین می‌توان آن را در هنگام بارگذاری مدل پیدا کرد.

مثال: ذخیره یک مدل Keras

مدل‌های Keras که از انواع پسوند استفاده می‌کنند ممکن است با استفاده از 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)>

مثال: ذخیره یک مدل سفارشی

SavedModel همچنین می تواند برای ذخیره زیر کلاس های سفارشی tf.Module با توابعی که انواع پسوند را پردازش می کند استفاده شود.

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

بارگیری SavedModel زمانی که ExtensionType در دسترس نیست

اگر SavedModel را بارگیری کنید که از یک ExtensionType استفاده می کند، اما ExtensionType در دسترس نیست (یعنی وارد نشده است)، یک اخطار می بینید و TensorFlow دوباره به استفاده از یک شی "نوع پسوند ناشناس" بازمی گردد. این شیء همان فیلدهای نوع اصلی را خواهد داشت، اما فاقد هرگونه سفارشی سازی دیگری است که برای نوع اضافه کرده اید، مانند روش ها یا ویژگی های سفارشی.

استفاده از ExtensionTypes با سرویس TensorFlow

در حال حاضر، سرویس دهی TensorFlow (و سایر مصرف کنندگان فرهنگ لغت "امضا" SavedModel) نیاز دارند که همه ورودی ها و خروجی ها تانسورهای خام باشند. اگر می‌خواهید از سرویس TensorFlow با مدلی استفاده کنید که از انواع پسوند استفاده می‌کند، می‌توانید روش‌های wrapper را اضافه کنید که مقادیر نوع پسوند را از تانسورها ترکیب یا تجزیه می‌کنند. به عنوان مثال:

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>

مجموعه داده ها

tf.data یک API است که شما را قادر می سازد خطوط لوله ورودی پیچیده را از قطعات ساده و قابل استفاده مجدد بسازید. ساختار داده اصلی آن tf.data.Dataset است که نشان دهنده دنباله ای از عناصر است که در آن هر عنصر از یک یا چند جزء تشکیل شده است.

ساخت مجموعه داده با انواع پسوند

مجموعه داده‌ها را می‌توان از مقادیر نوع پسوند با استفاده از Dataset.from_tensors ، Dataset.from_tensor_slices یا 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]>

دسته بندی و جداسازی مجموعه داده ها با انواع پسوند

مجموعه‌های داده با انواع پسوند را می‌توان با استفاده از 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]>