एक्सटेंशन प्रकार

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

एक्सटेंशन प्रकार

उपयोगकर्ता-परिभाषित प्रकार परियोजनाओं को अधिक पठनीय, मॉड्यूलर, रखरखाव योग्य बना सकते हैं। हालाँकि, अधिकांश TensorFlow API के पास उपयोगकर्ता-परिभाषित पायथन प्रकारों के लिए बहुत सीमित समर्थन है। इसमें उच्च-स्तरीय API (जैसे Keras , tf.function , tf.SavedModel ) और निम्न-स्तरीय API (जैसे tf. tf.while_loop और tf.concat ) दोनों शामिल हैं। TensorFlow एक्सटेंशन प्रकारों का उपयोग उपयोगकर्ता-परिभाषित ऑब्जेक्ट-ओरिएंटेड प्रकार बनाने के लिए किया जा सकता है जो TensorFlow के API के साथ मूल रूप से काम करते हैं। एक एक्सटेंशन प्रकार बनाने के लिए, बस 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 टेंसर जैसे प्रकारों के उदाहरण हैं।

समर्थित एपीआई

एक्सटेंशन प्रकार निम्नलिखित TensorFlow API द्वारा समर्थित हैं:

  • केरस : केरस Models और Layers के लिए एक्सटेंशन प्रकारों का उपयोग इनपुट और आउटपुट के रूप में किया जा सकता है।
  • tf.data.Dataset : एक्सटेंशन प्रकारों को Datasets में शामिल किया जा सकता है, और डेटासेट Iterators द्वारा लौटाया जा सकता है।
  • Tensorflow हब : एक्सटेंशन प्रकारों का उपयोग tf.hub मॉड्यूल के लिए इनपुट और आउटपुट के रूप में किया जा सकता है।
  • सेव्डमॉडल : SavedModel फंक्शन्स के लिए एक्सटेंशन टाइप्स को इनपुट और आउटपुट के रूप में इस्तेमाल किया जा सकता है।
  • tf.function : एक्सटेंशन प्रकारों का उपयोग तर्कों के रूप में किया जा सकता है और @tf.function डेकोरेटर के साथ लिपटे कार्यों के लिए मान वापस कर सकते हैं।
  • जबकि लूप : एक्सटेंशन प्रकारों का उपयोग tf. tf.while_loop में लूप वैरिएबल के रूप में किया जा सकता है, और इसका उपयोग तर्क के रूप में किया जा सकता है और जबकि-लूप के शरीर के लिए मान लौटाया जा सकता है।
  • सशर्त : एक्सटेंशन प्रकारों को tf.cond और tf.case का उपयोग करके सशर्त रूप से चुना जा सकता है।
  • py_function : एक्सटेंशन प्रकारों का उपयोग तर्क के रूप में किया जा सकता है और func तर्क के लिए tf.py_function पर मान वापस कर सकते हैं।
  • Tensor ops : अधिकांश TensorFlow ops का समर्थन करने के लिए एक्सटेंशन प्रकारों को बढ़ाया जा सकता है जो Tensor इनपुट स्वीकार करते हैं (जैसे, tf.matmul , tf.gather , और tf.reduce_sum )। अधिक जानकारी के लिए नीचे " प्रेषण " अनुभाग देखें।
  • वितरण रणनीति : विस्तार प्रकारों का उपयोग प्रति-प्रतिकृति मानों के रूप में किया जा सकता है।

अधिक विवरण के लिए, नीचे "ExtensionTypes का समर्थन करने वाले TensorFlow API" अनुभाग देखें।

आवश्यकताएं

फ़ील्ड प्रकार

सभी फ़ील्ड (उर्फ इंस्टेंस वैरिएबल) घोषित किए जाने चाहिए, और प्रत्येक फ़ील्ड के लिए एक प्रकार का एनोटेशन प्रदान किया जाना चाहिए। निम्नलिखित प्रकार के एनोटेशन समर्थित हैं:

प्रकार उदाहरण
पायथन पूर्णांक i: int
पायथन तैरता है f: float
पायथन स्ट्रिंग्स s: str
पायथन बूलियन b: bool
अजगर कोई नहीं n: None
टेंसर आकार shape: tf.TensorShape
टेंसर डीटाइप्स 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
  • टेंसर एपीआई प्रेषण समर्थन।

इस कार्यक्षमता को अनुकूलित करने के बारे में अधिक जानकारी के लिए नीचे "कस्टमाइज़िंग एक्सटेंशन टाइप" अनुभाग देखें।

निर्माता

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__ विधि जोड़ता है, जिसे फ़ील्ड पर सत्यापन जाँच करने के लिए ओवरराइड किया जा सकता है। इसे कंस्ट्रक्टर को बुलाए जाने के बाद चलाया जाता है, और फ़ील्ड्स को टाइप-चेक करने और उनके घोषित प्रकारों में बदलने के बाद, इसलिए यह मान सकता है कि सभी फ़ील्ड्स के घोषित प्रकार हैं।

वह निम्नलिखित उदाहरण अपने क्षेत्रों के shape s और dtype s को मान्य करने के लिए MaskedTensor को अपडेट करता है:

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.

नेस्टेड प्रकारSpec

प्रत्येक ExtensionType टाइप वर्ग में एक संबंधित TypeSpec वर्ग होता है, जो स्वचालित रूप से बनाया जाता है और <extension_type_name>.Spec के रूप में संग्रहीत किया जाता है।

यह वर्ग किसी भी नेस्टेड टेंसर के मूल्यों को छोड़कर सभी सूचनाओं को एक मूल्य से कैप्चर करता है। विशेष रूप से, किसी भी नेस्टेड टेंसर, एक्सटेंशन टाइप, या कंपोजिट टेंसर को TypeSpec के साथ बदलकर किसी मान के लिए 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 मानों को स्पष्ट रूप से बनाया जा सकता है, या उन्हें tf.type_spec_from_value का उपयोग करके ExtensionType टाइप मान से बनाया जा सकता है:

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

TypeSpec द्वारा TypeSpec s का उपयोग एक स्थिर घटक और एक गतिशील घटक में मानों को विभाजित करने के लिए किया जाता है:

  • स्थिर घटक (जो ग्राफ़-निर्माण समय पर तय होता है) एक tf.TypeSpec के साथ एन्कोड किया गया है।
  • गतिशील घटक (जो हर बार ग्राफ़ चलाने पर भिन्न हो सकता है) को tf.Tensor s की सूची के रूप में एन्कोड किया गया है।

उदाहरण के लिए, जब भी किसी तर्क में पहले से अनदेखी TypeSpec होता है, tf.function अपने लिपटे फ़ंक्शन को वापस लेता है:

@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>}))
प्लेसहोल्डर33
# Function does NOT get traced (same TypeSpec: just tensor values changed)
anonymize_player(Player("Bart", {"height": 8.1, "speed": 25.3}))
प्लेसहोल्डर34
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 गाइड देखें।

एक्सटेंशन प्रकार अनुकूलित करना

केवल फ़ील्ड और उनके प्रकार घोषित करने के अलावा, एक्सटेंशन प्रकार निम्न हो सकते हैं:

  • डिफ़ॉल्ट प्रिंट करने योग्य प्रतिनिधित्व ( __repr__ ) को ओवरराइड करें।
  • विधियों को परिभाषित कीजिए।
  • क्लासमेथड्स और स्टेटिकमेथड्स को परिभाषित करें।
  • गुणों को परिभाषित करें।
  • डिफ़ॉल्ट कंस्ट्रक्टर ( __init__ ) को ओवरराइड करें।
  • डिफ़ॉल्ट समानता ऑपरेटर ( __eq__ ) को ओवरराइड करें।
  • ऑपरेटरों को परिभाषित करें (जैसे __add__ और __lt__ )।
  • फ़ील्ड के लिए डिफ़ॉल्ट मान घोषित करें।
  • उपवर्गों को परिभाषित करें।

डिफ़ॉल्ट प्रिंट करने योग्य प्रतिनिधित्व को ओवरराइड करना

आप एक्सटेंशन प्रकारों के लिए इस डिफ़ॉल्ट स्ट्रिंग रूपांतरण ऑपरेटर को ओवरराइड कर सकते हैं। जब ईगर मोड में मान मुद्रित होते हैं, तो निम्न उदाहरण अधिक पठनीय स्ट्रिंग प्रतिनिधित्व उत्पन्न करने के लिए MaskedTensor वर्ग को अद्यतन करता है।

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

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

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

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

परिभाषित करने के तरीके

किसी भी सामान्य पायथन वर्ग की तरह, एक्सटेंशन प्रकार विधियों को परिभाषित कर सकते हैं। उदाहरण के लिए, MaskedTensor प्रकार एक with_default विधि को परिभाषित कर सकता है जो किसी दिए गए default मान द्वारा प्रतिस्थापित किए गए नकाबपोश मानों के साथ self की एक प्रति देता है। विधियों को वैकल्पिक रूप से @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]]>

गुणों को परिभाषित करना

एक्सटेंशन प्रकार किसी भी सामान्य पायथन वर्ग की तरह, @property डेकोरेटर का उपयोग करके गुणों को परिभाषित कर सकते हैं। उदाहरण के लिए, MaskedTensor प्रकार एक 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 को समानता के लिए तुलना करते समय नकाबपोश तत्वों को अनदेखा करने के लिए अद्यतन करता है।

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 टाइप वर्ग में एक संबंधित TypeSpec वर्ग होता है, जो स्वचालित रूप से बनाया जाता है और <extension_type_name>.Spec के रूप में संग्रहीत किया जाता है। अधिक जानकारी के लिए, ऊपर "नेस्टेड टाइपस्पेक" अनुभाग देखें।

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)

टेंसर एपीआई प्रेषण

एक्सटेंशन प्रकार "टेंसर-जैसे" हो सकते हैं, इस अर्थ में कि वे tf.Tensor प्रकार द्वारा परिभाषित इंटरफ़ेस को विशेषज्ञ या विस्तारित करते हैं। टेंसर-जैसे एक्सटेंशन प्रकारों के उदाहरणों में RaggedTensor , SparseTensor और MaskedTensor शामिल हैं। डिस्पैच डेकोरेटर्स का उपयोग टेंसर-जैसे एक्सटेंशन प्रकारों पर लागू होने पर TensorFlow संचालन के डिफ़ॉल्ट व्यवहार को ओवरराइड करने के लिए किया जा सकता है। TensorFlow वर्तमान में तीन प्रेषण डेकोरेटर को परिभाषित करता है:

एकल एपीआई के लिए प्रेषण

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

यह tf.stack के लिए डिफ़ॉल्ट कार्यान्वयन को ओवरराइड करता है जब भी इसे MaskedTensor मानों की सूची के साथ बुलाया जाता है (चूंकि 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 की सूची के लिए जिन्हें ओवरराइड किया जा सकता है, tf.experimental.dispatch_for_api के लिए API दस्तावेज़ देखें।

सभी यूनरी एलिमेंटवाइज एपीआई के लिए डिस्पैच

tf.experimental.dispatch_for_unary_elementwise_apis डेकोरेटर सभी यूनरी एलिमेंटवाइज ऑप्स (जैसे tf.math.cos ) के डिफ़ॉल्ट व्यवहार को ओवरराइड करता है जब भी पहले तर्क के लिए मान (आमतौर पर नामित x ) टाइप एनोटेशन x_type से मेल खाता है। सजाए गए फ़ंक्शन को दो तर्क लेने चाहिए:

  • api_func : एक फ़ंक्शन जो एक पैरामीटर लेता है और तत्व के अनुसार ऑपरेशन करता है (उदाहरण के लिए, tf.abs )।
  • x : एलीमेंटवाइज ऑपरेशन का पहला तर्क।

निम्न उदाहरण 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)

जब भी मास्कडटेन्सर पर एक 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]>

बाइनरी सभी एलिमेंटवाइज एपीआई के लिए डिस्पैच

इसी तरह, 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], [_, _, _]]>

ओवरराइड किए गए एलिमेंटवाइज एपीआई की सूची के लिए, tf.experimental.dispatch_for_unary_elementwise_apis और tf.experimental.dispatch_for_binary_elementwise_apis के लिए API दस्तावेज़ देखें।

बैचेबल एक्सटेंशन प्रकार

एक ExtensionType टाइप बैच योग्य है यदि मूल्यों के बैच का प्रतिनिधित्व करने के लिए एक एकल उदाहरण का उपयोग किया जा सकता है। आमतौर पर, यह सभी नेस्टेड Tensor s में बैच आयाम जोड़कर पूरा किया जाता है। निम्नलिखित TensorFlow API के लिए आवश्यक है कि कोई भी एक्सटेंशन प्रकार के इनपुट बैचेबल हों:

डिफ़ॉल्ट रूप से, BatchableExtensionType किसी नेस्टेड Tensor s, CompositeTensor s, और ExtensionType s को बैच करके बैच किए गए मान बनाता है। यदि यह आपकी कक्षा के लिए उपयुक्त नहीं है, तो आपको इस डिफ़ॉल्ट व्यवहार को ओवरराइड करने के लिए tf.experimental.ExtensionTypeBatchEncoder का उपयोग करना होगा। उदाहरण के लिए, tf.SparseTensor मानों का एक बैच बनाना उचित नहीं होगा, बस अलग-अलग विरल टेन्सर्स के values , indices , और dense_shape फ़ील्ड्स को स्टैक करके - ज्यादातर मामलों में, आप इन टेंसरों को स्टैक नहीं कर सकते, क्योंकि उनके पास असंगत आकार हैं ; और यदि आप कर सकते हैं, तो भी परिणाम मान्य SparseTensor नहीं होगा।

बैचेबल एक्सटेंशन टाइप उदाहरण: नेटवर्क

एक उदाहरण के रूप में, लोड संतुलन के लिए उपयोग किए जाने वाले एक साधारण 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.]]]>

TensorFlow API जो एक्सटेंशन टाइप का समर्थन करते हैं

@tf.function

tf.function एक डेकोरेटर है जो Python फ़ंक्शंस के लिए TensorFlow ग्राफ़ को प्रीकंप्यूट करता है, जो आपके TensorFlow कोड के प्रदर्शन में काफी सुधार कर सकता है। एक्सटेंशन प्रकार के मानों को @tf.function -decorated functions के साथ पारदर्शी रूप से उपयोग किया जा सकता है।

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. 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 गहन शिक्षण मॉडल के निर्माण और प्रशिक्षण के लिए TensorFlow का उच्च-स्तरीय API है। एक्सटेंशन प्रकार को केरस मॉडल के इनपुट के रूप में पारित किया जा सकता है, केरस परतों के बीच पारित किया जा सकता है, और केरस मॉडल द्वारा लौटाया जा सकता है। केरस वर्तमान में विस्तार प्रकारों पर दो आवश्यकताएं रखता है:

  • उन्हें बैच करने योग्य होना चाहिए (ऊपर "बैचेबल एक्सटेंशन टाइप" देखें)।
  • एक फ़ील्ड या प्रॉपर्टी का shape होना चाहिए. shape[0] को बैच आयाम माना जाता है।

निम्नलिखित दो उपखंड उदाहरण देते हैं कि कैसे केरस के साथ विस्तार प्रकारों का उपयोग किया जा सकता है।

केरस उदाहरण: Network

पहले उदाहरण के लिए, ऊपर "बैचेबल एक्सटेंशन टाइप" खंड में परिभाषित Network वर्ग पर विचार करें, जिसका उपयोग नोड्स के बीच लोड संतुलन कार्य के लिए किया जा सकता है। इसकी परिभाषा यहाँ दोहराई गई है:

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

आप एक नई केरस परत को परिभाषित कर सकते हैं जो Network एस को संसाधित करती है।

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 पर सेट हो। यदि केरस मॉडल का उपयोग बैचों को संसाधित करने के लिए किया जाएगा, तो 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 का विस्तार किया गया है। shape को एक ऐसी संपत्ति के रूप में परिभाषित किया जाता है जिसकी गणना values फ़ील्ड से की जाती है। केरस के लिए आवश्यक है कि आप इस गुण को एक्सटेंशन प्रकार और इसके 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))

इसके बाद, प्रेषण डेकोरेटर का उपयोग कई TensorFlow API के डिफ़ॉल्ट व्यवहार को ओवरराइड करने के लिए किया जाता है। चूंकि इन एपीआई का उपयोग मानक केरस परतों (जैसे कि घनी परत) द्वारा किया जाता है, इन्हें ओवरराइड करने से हम उन परतों का उपयोग 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)

फिर आप एक केरस मॉडल का निर्माण कर सकते हैं जो मानक केरस परतों का उपयोग करके MaskedTensor इनपुट स्वीकार करता है:

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 एक क्रमबद्ध TensorFlow प्रोग्राम है, जिसमें वज़न और गणना दोनों शामिल हैं। इसे केरस मॉडल या कस्टम मॉडल से बनाया जा सकता है। किसी भी मामले में, एक्सटेंशन प्रकारों को एक सहेजे गए मॉडल द्वारा परिभाषित कार्यों और विधियों के साथ पारदर्शी रूप से उपयोग किया जा सकता है।

SavedModel एक्सटेंशन प्रकारों को संसाधित करने वाले मॉडल, परतों और फ़ंक्शंस को तब तक सहेज सकता है, जब तक कि एक्सटेंशन प्रकारों में __name__ फ़ील्ड हो। इस नाम का उपयोग एक्सटेंशन प्रकार को पंजीकृत करने के लिए किया जाता है, इसलिए यह मॉडल लोड होने पर स्थित हो सकता है।

उदाहरण: केरस मॉडल को सहेजना

केरस मॉडल जो एक्सटेंशन प्रकारों का उपयोग करते हैं 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, _]>

एक सहेजा गया मॉडल लोड हो रहा है जब एक्सटेंशन टाइप अनुपलब्ध है

यदि आप एक सहेजे गए मॉडल को लोड करते हैं जो एक ExtensionType SavedModel का उपयोग करता है, लेकिन वह ExtensionType टाइप उपलब्ध नहीं है (यानी, आयात नहीं किया गया है), तो आपको एक चेतावनी दिखाई देगी और TensorFlow एक "अनाम एक्सटेंशन प्रकार" ऑब्जेक्ट का उपयोग करने के लिए वापस आ जाएगा। इस ऑब्जेक्ट में मूल प्रकार के समान फ़ील्ड होंगे, लेकिन इस प्रकार के लिए आपके द्वारा जोड़े गए किसी और अनुकूलन की कमी होगी, जैसे कि कस्टम विधियाँ या गुण।

TensorFlow सर्विंग के साथ एक्सटेंशन टाइप का उपयोग करना

वर्तमान में, TensorFlow सेवा (और SavedModel "हस्ताक्षर" शब्दकोश के अन्य उपभोक्ताओं) के लिए आवश्यक है कि सभी इनपुट और आउटपुट कच्चे टेंसर हों। यदि आप एक्सटेंशन प्रकारों का उपयोग करने वाले मॉडल के साथ TensorFlow सर्विंग का उपयोग करना चाहते हैं, तो आप रैपर विधियों को जोड़ सकते हैं जो टेंसर से एक्सटेंशन प्रकार मान बनाते या विघटित करते हैं। जैसे:

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 एक एपीआई है जो आपको सरल, पुन: प्रयोज्य टुकड़ों से जटिल इनपुट पाइपलाइन बनाने में सक्षम बनाता है। इसकी मूल डेटा संरचना 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]>