עזרה להגן על שונית המחסום הגדולה עם TensorFlow על Kaggle הצטרפו אתגר

סוגי הרחבה

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-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 המוגדרים על ידי המשתמש. זה כולל הן APIs ברמה גבוהה (כגון Keras , tf.function , tf.SavedModel ) ו- APIs ברמה נמוכה יותר (כגון tf.while_loop ו tf.concat ). סוגי תוספות TensorFlow יכול לשמש כדי ליצור עבודה המוגדרים על ידי המשתמש סוגים מונחה עצמים כי חלקה עם APIs של TensorFlow. כדי ליצור סוג הסיומת, פשוט להגדיר מחלקה Python עם 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 מספריית Python הסטנדרטית. בפרט, זה אוטומטית מוסיף בנאי ושיטות מיוחדות (כגון __repr__ ו __eq__ ) המבוסס על הסברי סוג השדה.

בדרך כלל, סוגי הרחבות נוטים להתחלק לאחת משתי קטגוריות:

  • מבני נתונים, אשר הקבוצה יחד אוסף של ערכים קשורים, והוא יכול לספק פעולות שימושיות המבוססות על ערכים אלה. מבני נתונים עשויים להיות כלליים למדי (כגון TensorGraph בדוגמא לעיל); או שהם עשויים להיות מותאמים מאוד לדגם ספציפי.

  • מותח כמו סוגים, אשר מתמחים או להאריך את המושג "מותח". סוגים בקטגוריה זו יש rank , A shape , ובדרך כלל dtype ; וזה הגיוני להשתמש בהם עם פעולות מותח (כגון tf.stack , tf.add , או tf.matmul ). MaskedTensor ו CSRSparseMatrix הן דוגמא לסוגים דמויים מותחים.

ממשקי API נתמכים

סוגי הרחבות נתמכים על ידי ממשקי API של TensorFlow הבאים:

  • Keras: סוגי ההרחבה יכולים לשמש תשומות ותפוקות עבור Keras Models ו Layers .
  • tf.data.Dataset: סוגי ההרחבה יכולים להיכלל Datasets , וחזרו על ידי במערך Iterators .
  • רכזת Tensorflow: סוגי ההרחבה יכולים לשמש תשומות ותפוקות עבור tf.hub מודולים.
  • SavedModel: סוגי ההרחבה יכולים לשמש תשומות ותפוקות עבור SavedModel פונקציות.
  • ניתן להשתמש בסוגים רחבים כטיעונים וערכים בתמורת הפונקציות עטוף עם: tf.function @tf.function המעצב.
  • בעוד לולאות: סוגי הרחבה יכול לשמש כמשתנים הלולאה tf.while_loop , והוא יכול לשמש טיעונים וערכים בתמורה הגוף של בעוד לולאה.
  • תניות: סוגי ההרחבה ניתן לבחור תנאי שימוש tf.cond ו tf.case .
  • py_function: ניתן להשתמש בסוגים רחבים כטיעונים וערכים תמורת func הטיעון כדי tf.py_function .
  • ניתן להאריך סוגי ההרחבה לתמוך ביותר ops TensorFlow המקבלות תשומות מותחות (למשל,: טנסור ops 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]
Tuples 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 ים; ו Mapping שדות מומרים dicts משתנה.

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 ים של שדות שלה:

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 יש Class A המקביל TypeSpec בכיתה, אשר נוצר באופן אוטומטי ומאוחסן כמו <extension_type_name>.Spec .

מחלקה זו לוכדת את כל המידע מתוך ערך מלבד הערכים של כל tensors מקוננות. בפרט, TypeSpec עבור ערך נוצר על ידי החלפת כל מקוננות מותח, ExtensionType, או CompositeTensor עם שלה TypeSpec .

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

anne = Player("Anne", {"height": 8.3, "speed": 28.1})
anne_spec = tf.type_spec_from_value(anne)
print(anne_spec.name)  # Records dtype and shape, but not the string value.
print(anne_spec.attributes)  # Records keys and TensorSpecs for values.
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 ים.

לדוגמה, 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 בכיתה כדי ליצור ייצוג מחרוזת קריא יותר כאשר ערכי מודפסים במצב להוט.

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

הגדרת שיטות

סוגי הרחבות עשויים להגדיר שיטות, בדיוק כמו כל מחלקה רגילה של Python. לדוגמה, MaskedTensor סוג יכול להגדיר with_default שיטה שמחזירה עותק של self עם ערכים רעולי פנים הוחלף נתון default ערך. שיטות ניתן מבואר אופציונאלי עם @tf.function המעצב.

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

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

MaskedTensor([1, 2, 3], [True, False, True]).with_default(0)
<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 המעצב, בדיוק כמו כול כיתת Python נורמלית. לדוגמה, MaskedTensor סוג יכול להגדיר dtype נכס זה קיצור עבור dtype של ערכים:

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

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

MaskedTensor([1, 2, 3], [True, False, True]).dtype
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=())))

הגדרת תת מחלקות

סוגי הרחבות עשויים להיות מסווגים משנה באמצעות תחביר Python הסטנדרטי. תת-מחלקות מסוג הרחבה עשויות להוסיף שדות, שיטות ומאפיינים חדשים; והוא עשוי לעקוף את הבנאי, את הייצוג הניתן להדפסה ואת אופרטור השוויון. דוגמא הבאה מגדירה בסיסית TensorGraph בכיתה כי שימושים שלושה Tensor שדות לקודד קבוצה של קצוות בין צמתים. לאחר מכן הוא מגדיר תת המוסיפה Tensor שדה להקליט "ערך התכונה" עבור כל צומת. תת המחלקה גם מגדירה שיטה להפצת ערכי התכונה לאורך הקצוות.

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

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

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

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

print("Original features:", g.node_features)
print("After propagating:", g.propagate_features().node_features)
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)

הגדרת שדות פרטיים

שדות של סוג הרחבה עשויים להיות מסומנים כפרטיים על ידי הקדמתם עם קו תחתון (בהתאם למוסכמות הסטנדרטיות של Python). זה לא משפיע על האופן שבו TensorFlow מתייחס לשדות בשום צורה; אלא פשוט משמש כאות לכל משתמש מסוג ההרחבה שהשדות האלה הם פרטיים.

התאמה אישית של ExtensionType של TypeSpec

כל ExtensionType יש Class A המקביל 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)

שליחת Tensor API

סוגי תוספות יכולים להיות "מותח דמוי", במובן זה שהם מתמחים או להאריך את הממשק שהוגדר על ידי tf.Tensor הסוג. דוגמא לסוגי הרחבה דמוית מותחים כוללות RaggedTensor , SparseTensor , ו MaskedTensor . מעצבי Dispatch יכול לשמש כדי לעקוף את התנהגות ברירת המחדל של פעולות TensorFlow כאשר מוחל מותח דמוי סוגי תוספות. TensorFlow מגדיר כרגע שלושה מעצבי שיגור:

שיגור עבור API יחיד

tf.experimental.dispatch_for_api המעצב עוקף את התנהגות ברירת המחדל של פעילות מפורטת TensorFlow כשזה נקרא עם החתימה שצוינה. לדוגמה, אתה יכול להשתמש מעצב זה כדי לציין כיצד tf.stack צריך לעבד MaskedTensor ערכים:

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

זו עוקפת את יישום ברירת המחדל עבור 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 אשר ניתן לעקוף, עיין בתיעוד API עבור tf.experimental.dispatch_for_api .

שיגור עבור כל ממשקי ה-API האלמנטריים

tf.experimental.dispatch_for_unary_elementwise_apis מעצב עוקפת את התנהגות ברירת המחדל של כל ops elementwise אונרי (כגון tf.math.cos ) בכל פעם את הערך עבור הטיעון הראשון (ששמו בדרך כלל x ) תואם את ביאור סוג x_type . הפונקציה המעוטרת צריכה לקחת שני ארגומנטים:

  • api_func : פונקציה שלוקחת פרמטר יחיד מבצע את הפעולה elementwise (למשל, tf.abs ).
  • x : הטענה הראשונה למבצע elementwise.

הדוגמה הבאה מעדכנת את כל פעולות 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)

פונקציה זו כעת ישמש בכל פעם מבצע 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 של elementwise

באופן דומה, tf.experimental.dispatch_for_binary_elementwise_apis יכול לשמש כדי לעדכן את כל פעולות elementwise בינארי לטפל 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 elementwise כי הם לדרוס, עיין בתיעוד API עבור tf.experimental.dispatch_for_unary_elementwise_apis ו tf.experimental.dispatch_for_binary_elementwise_apis .

סוגי הרחבות הניתנים לאצווה

ExtensionType הוא batchable אם מופע יחיד יכול לשמש כדי לייצג קבוצה של ערכים. בדרך כלל, זה מושג על ידי הוספת מאפיינים יצוו לכול מקוננות Tensor ים. ממשקי ה-API של TensorFlow הבאים דורשים שכל קלט מסוג הרחבה יהיה ניתן לאצווה:

כברירת מחדל, BatchableExtensionType יוצר ערכים באצוות ידי batching כל מקוננות Tensor ים, CompositeTensor ים, ו ExtensionType ים. אם זה לא מתאים בכיתה שלך, אז אתה תצטרך להשתמש tf.experimental.ExtensionTypeBatchEncoder כדי לעקוף את ההתנהגות הזו. לדוגמה, זה לא יהיה מתאים כדי ליצור קבוצה של tf.SparseTensor ערכים פשוט על ידי הערמה tensors דלילה הפרט values , indices , ואת dense_shape שדות - ברוב המקרים, אתה לא יכול מחסנית tensors אלה, שכן יש להם צורות בקנה ; וגם אם אתה יכול, התוצאה לא תהיה תקפה 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]])

כדי להפוך סוג batchable זה, לשנות את סוג בסיס ל BatchableExtensionType , ולהתאים את הצורה של כל שדה לכלול מימדים אצווה אופציונלי. הדוגמה הבאה גם מוסיפה shape שדה למסלול keept של הצורה תצווה. זו 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 precomputes עבור פונקציות Python, אשר יכול לשפר את הביצועים באופן משמעותי של קוד 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. קרס מציב כרגע שתי דרישות לגבי סוגי הרחבות:

  • הם חייבים להיות ניתנים לאצווה (ראה "סוגי הרחבות הניתנים לאצווה" למעלה).
  • תירוש יש בשם שדה או רכוש shape . shape[0] ההנחה היא שזהו הממד אצווה.

שני הסעיפים הבאים מספקים דוגמאות המראות כיצד ניתן להשתמש בסוגי הרחבות עם Keras.

לדוגמה 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 ים.

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

דוגמה של קרס: MaskedTensor

בדוגמה זו, MaskedTensor הוארך לתמוך Keras . shape מוגדרת נכס מחושב מן values בשדה. Keras דורש thatyou להוסיף נכס זה הוא סוג ההרחבה שלו 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. מאז APIs אלה נמצאים בשימוש על ידי שכבות תקן 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 בהמשכים, כולל שתי משקולות החישוב. ניתן לבנות אותו מדגם קרס או מדגם מותאם אישית. בכל מקרה, ניתן להשתמש בסוגי הרחבות באופן שקוף עם הפונקציות והשיטות המוגדרות על ידי SavedModel.

SavedModel יכול לחסוך דגמים, שכבות, ופונקציות כי סוגים רחבים תהליך, כל עוד סוגי התוספות יש __name__ שדה. שם זה משמש לרישום סוג ההרחבה, כך שניתן לאתר אותו כאשר הדגם נטען.

דוגמה: שמירת דגם של קרס

מודלי 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 subclasses עם פונקציות סוגי תוספות תהליך.

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 "חתימות") דורשים כי כל התשומות והתפוקות להיות tensors גלם. אם ברצונך להשתמש בשרת 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 הוא ה- 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]>

אצווה וביטול ערכות נתונים עם סוגי הרחבות

מערכי נתונים עם סוגי תוספות יכול להיות batchand ו unbatched באמצעות Dataset.batch ומפלגתו 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]>