مشاهده در 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
گنجاند و توسط DatasetIterators
برگردانده شد. - هاب 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 در حال حاضر سه دکوراتور اعزامی را تعریف می کند:
-
@tf.experimental.dispatch_for_api(tf_api)
-
@tf.experimental.dispatch_for_unary_elementwise_api(x_type)
-
@tf.experimental.dispatch_for_binary_elementwise_apis(x_type, y_type)
ارسال برای یک 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 زیر مستلزم این هستند که هر ورودی نوع افزونه قابل دسته بندی باشد:
-
tf.data.Dataset
(batch
،from_tensor_slices
unbatch
-
tf.Keras
(fit
،evaluate
،predict
) -
tf.map_fn
به طور پیشفرض، 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]>