مقدمه ای بر نمودارها و عملکرد tf

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

بررسی اجمالی

این راهنما به زیر سطح TensorFlow و Keras می رود تا نحوه عملکرد TensorFlow را نشان دهد. اگر می‌خواهید فوراً با Keras شروع کنید، مجموعه راهنمای Keras را بررسی کنید.

در این راهنما، یاد خواهید گرفت که چگونه TensorFlow به شما اجازه می دهد تا تغییرات ساده ای در کد خود ایجاد کنید تا نمودارها را دریافت کنید، نمودارها چگونه ذخیره و نمایش داده می شوند و چگونه می توانید از آنها برای سرعت بخشیدن به مدل های خود استفاده کنید.

این یک نمای کلی با تصویر بزرگ است که نحوه تغییر عملکرد tf.function را به شما امکان می دهد از اجرای مشتاقانه به اجرای گراف تغییر دهید. برای مشخصات tf.function به راهنمای tf.function .

نمودارها چیست؟

در سه راهنما قبلی، TensorFlow را مشتاقانه اجرا کردید. این بدان معناست که عملیات TensorFlow توسط پایتون اجرا می شود، عملیات به عملیات، و نتایج را به پایتون برمی گرداند.

در حالی که اجرای مشتاق چندین مزیت منحصر به فرد دارد، اجرای گراف قابلیت حمل خارج از پایتون را ممکن می کند و تمایل به ارائه عملکرد بهتر دارد. اجرای نمودار به این معنی است که محاسبات تانسور به عنوان یک گراف TensorFlow اجرا می شود که گاهی اوقات به عنوان tf.Graph یا به سادگی یک "گراف" نامیده می شود.

نمودارها ساختارهای داده ای هستند که شامل مجموعه ای از اشیاء tf.Operation هستند که واحدهای محاسباتی را نشان می دهند. و اشیاء tf.Tensor که واحدهای داده ای را نشان می دهند که بین عملیات جریان دارند. آنها در یک زمینه tf.Graph تعریف شده اند. از آنجایی که این نمودارها ساختارهای داده ای هستند، می توان آنها را بدون کد اصلی پایتون ذخیره، اجرا و بازیابی کرد.

این همان چیزی است که یک نمودار TensorFlow که یک شبکه عصبی دو لایه را نشان می دهد، هنگامی که در TensorBoard تجسم می شود، به نظر می رسد.

یک نمودار TensorFlow ساده

مزایای نمودارها

با یک نمودار، انعطاف پذیری زیادی دارید. می‌توانید از نمودار TensorFlow خود در محیط‌هایی که مفسر پایتون ندارند، مانند برنامه‌های کاربردی تلفن همراه، دستگاه‌های جاسازی شده و سرورهای پشتیبان استفاده کنید. TensorFlow از نمودارها به عنوان قالبی برای مدل های ذخیره شده استفاده می کند که آنها را از پایتون صادر می کند.

نمودارها نیز به راحتی بهینه می شوند و به کامپایلر اجازه می دهد تا تبدیل هایی مانند:

  • مقدار تانسورها را با تا کردن گره های ثابت در محاسبات خود به صورت ایستا استنتاج کنید ("تاشوی ثابت") .
  • بخش های فرعی یک محاسبات را که مستقل هستند از هم جدا کنید و آنها را بین رشته ها یا دستگاه ها تقسیم کنید.
  • با حذف عبارات فرعی رایج، عملیات حسابی را ساده کنید.

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

به طور خلاصه، نمودارها بسیار مفید هستند و به TensorFlow شما اجازه می دهند سریع اجرا شود، به صورت موازی اجرا شود و به طور موثر در چندین دستگاه اجرا شود.

با این حال، هنوز هم می‌خواهید مدل‌های یادگیری ماشین (یا سایر محاسبات) خود را برای راحتی در پایتون تعریف کنید و سپس به‌طور خودکار در صورت نیاز نمودارها را بسازید.

برپایی

import tensorflow as tf
import timeit
from datetime import datetime

استفاده از نمودارها

شما یک نمودار را در TensorFlow با استفاده از tf.function ایجاد و اجرا می کنید، چه به صورت تماس مستقیم و چه به عنوان دکوراتور. tf.function یک تابع منظم را به عنوان ورودی می گیرد و یک Function برمی گرداند. یک Function یک پایتون قابل فراخوانی است که نمودارهای TensorFlow را از تابع پایتون می‌سازد. شما از یک Function مانند معادل پایتون آن استفاده می کنید.

# Define a Python function.
def a_regular_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# `a_function_that_uses_a_graph` is a TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)

# Make some tensors.
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

orig_value = a_regular_function(x1, y1, b1).numpy()
# Call a `Function` like a Python function.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)

در خارج، یک Function شبیه یک تابع معمولی است که با استفاده از عملیات TensorFlow می نویسید. اما در زیر آن بسیار متفاوت است. یک Function چندین tf.Graph را در پشت یک API کپسوله می کند. اینگونه است که Function می تواند مزایای اجرای نمودار مانند سرعت و قابلیت استقرار را به شما بدهد.

tf.function برای یک تابع و سایر توابع فراخوانی اعمال می شود :

def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator to make `outer_function` a `Function`.
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes `inner_function` as well as `outer_function`.
outer_function(tf.constant([[1.0, 2.0]])).numpy()
array([[12.]], dtype=float32)

اگر از TensorFlow 1.x استفاده کرده باشید، متوجه خواهید شد که هیچ وقت نیازی به تعریف Placeholder یا tf.Session .

تبدیل توابع پایتون به نمودار

هر تابعی که با TensorFlow بنویسید حاوی ترکیبی از عملیات TF داخلی و منطق پایتون است، مانند عبارت‌های if-then ، حلقه‌ها، break ، return ، continue و غیره. در حالی که عملیات TensorFlow به راحتی توسط tf.Graph می شود، منطق اختصاصی پایتون برای تبدیل شدن به بخشی از نمودار باید یک مرحله اضافی را طی کند. tf.function از کتابخانه ای به نام AutoGraph ( tf.autograph ) برای تبدیل کد پایتون به کد تولید گراف استفاده می کند.

def simple_relu(x):
  if tf.greater(x, 0):
    return x
  else:
    return 0

# `tf_simple_relu` is a TensorFlow `Function` that wraps `simple_relu`.
tf_simple_relu = tf.function(simple_relu)

print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
First branch, with graph: 1
Second branch, with graph: 0

اگرچه بعید است که نیاز به مشاهده مستقیم نمودارها داشته باشید، می توانید خروجی ها را برای بررسی نتایج دقیق بررسی کنید. خواندن اینها آسان نیست، بنابراین نیازی به دقت زیاد نیست!

# This is the graph-generating output of AutoGraph.
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x):
    with ag__.FunctionScope('simple_relu', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (do_return, retval_)

        def set_state(vars_):
            nonlocal retval_, do_return
            (do_return, retval_) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = ag__.ld(x)
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = 0
            except:
                do_return = False
                raise
        ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)
# This is the graph itself.
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node {
  name: "x"
  op: "Placeholder"
  attr {
    key: "_user_specified_name"
    value {
      s: "x"
    }
  }
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "shape"
    value {
      shape {
      }
    }
  }
}
node {
  name: "Greater/y"
  op: "Const"
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_INT32
        tensor_shape {
        }
        int_val: 0
      }
    }
  }
}
node {
  name: "Greater"
  op: "Greater"
  input: "x"
  input: "Greater/y"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "cond"
  op: "StatelessIf"
  input: "Greater"
  input: "x"
  attr {
    key: "Tcond"
    value {
      type: DT_BOOL
    }
  }
  attr {
    key: "Tin"
    value {
      list {
        type: DT_INT32
      }
    }
  }
  attr {
    key: "Tout"
    value {
      list {
        type: DT_BOOL
        type: DT_INT32
      }
    }
  }
  attr {
    key: "_lower_using_switch_merge"
    value {
      b: true
    }
  }
  attr {
    key: "_read_only_resource_inputs"
    value {
      list {
      }
    }
  }
  attr {
    key: "else_branch"
    value {
      func {
        name: "cond_false_34"
      }
    }
  }
  attr {
    key: "output_shapes"
    value {
      list {
        shape {
        }
        shape {
        }
      }
    }
  }
  attr {
    key: "then_branch"
    value {
      func {
        name: "cond_true_33"
      }
    }
  }
}
node {
  name: "cond/Identity"
  op: "Identity"
  input: "cond"
  attr {
    key: "T"
    value {
      type: DT_BOOL
    }
  }
}
node {
  name: "cond/Identity_1"
  op: "Identity"
  input: "cond:1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "Identity"
  op: "Identity"
  input: "cond/Identity_1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
library {
  function {
    signature {
      name: "cond_false_34"
      input_arg {
        name: "cond_placeholder"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
    }
    node_def {
      name: "cond/Const_1"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
    }
    node_def {
      name: "cond/Const_2"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
    }
    node_def {
      name: "cond/Const_3"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const_3:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
    }
    node_def {
      name: "cond/Const_4"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond/Const_4:output:0"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
  function {
    signature {
      name: "cond_true_33"
      input_arg {
        name: "cond_identity_1_x"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond_identity_1_x"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    attr {
      key: "_construction_context"
      value {
        s: "kEagerRuntime"
      }
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
}
versions {
  producer: 898
  min_consumer: 12
}

در بیشتر مواقع، tf.function بدون ملاحظات خاصی کار می کند. با این حال، برخی از هشدارها وجود دارد، و راهنمای tf.function می تواند در اینجا کمک کند، و همچنین مرجع کامل AutoGraph

چند شکلی: یک Function ، نمودارهای متعدد

یک tf.Graph به نوع خاصی از ورودی ها اختصاص دارد (مثلاً تانسورها با نوع dtype خاص یا اشیاء با همان id() ).

هر بار که Function را با dtypes و اشکال جدید در آرگومان های آن فراخوانی می کنید، Function یک tf.Graph جدید برای آرگومان های جدید ایجاد می کند. dtypes و اشکال ورودی های tf.Graph به عنوان یک امضای ورودی یا فقط یک امضا شناخته می شود.

Function ، tf.Graph مربوط به آن امضا را در یک ConcreteFunction ذخیره می کند. یک ConcreteFunction یک پوشش در اطراف یک tf.Graph است.

@tf.function
def my_relu(x):
  return tf.maximum(0., x)

# `my_relu` creates new graphs as it observes more signatures.
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1]))
print(my_relu(tf.constant([3., -3.])))
tf.Tensor(5.5, shape=(), dtype=float32)
tf.Tensor([1. 0.], shape=(2,), dtype=float32)
tf.Tensor([3. 0.], shape=(2,), dtype=float32)

اگر Function قبلاً با آن امضا فراخوانی شده باشد، Function یک tf.Graph جدید ایجاد نمی کند.

# These two calls do *not* create new graphs.
print(my_relu(tf.constant(-2.5))) # Signature matches `tf.constant(5.5)`.
print(my_relu(tf.constant([-1., 1.]))) # Signature matches `tf.constant([3., -3.])`.
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor([0. 1.], shape=(2,), dtype=float32)

از آنجا که توسط چندین نمودار پشتیبانی می شود، یک Function چند شکلی است. این امکان را به آن می‌دهد تا از انواع ورودی‌های بیشتری نسبت به یک tf.Graph کند، و همچنین هر tf.Graph را برای عملکرد بهتر بهینه‌سازی کند.

# There are three `ConcreteFunction`s (one for each graph) in `my_relu`.
# The `ConcreteFunction` also knows the return type and shape!
print(my_relu.pretty_printed_concrete_signatures())
my_relu(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

my_relu(x=[1, -1])
  Returns:
    float32 Tensor, shape=(2,)

my_relu(x)
  Args:
    x: float32 Tensor, shape=(2,)
  Returns:
    float32 Tensor, shape=(2,)

با استفاده از tf.function

تا کنون، شما یاد گرفته اید که چگونه یک تابع پایتون را به سادگی با استفاده از tf.function به عنوان دکوراتور یا بسته بندی به نمودار تبدیل کنید. اما در عمل، کارکرد صحیح tf.function می تواند مشکل باشد! در بخش‌های بعدی، یاد می‌گیرید که چگونه می‌توانید کد خود را با tf.function مطابق انتظار عمل کنید.

اجرای گراف در مقابل اجرای مشتاق

کد موجود در یک Function می تواند هم مشتاقانه و هم به صورت نمودار اجرا شود. به طور پیش فرض، Function کد خود را به صورت نمودار اجرا می کند:

@tf.function
def get_MSE(y_true, y_pred):
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
print(y_true)
print(y_pred)
tf.Tensor([1 0 4 4 7], shape=(5,), dtype=int32)
tf.Tensor([3 6 3 0 6], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=11>

برای تأیید اینکه نمودار Function شما همان محاسبه تابع پایتون را انجام می دهد، می توانید آن را با tf.config.run_functions_eagerly(True) مشتاقانه اجرا کنید. این سوئیچ است که توانایی Function را برای ایجاد و اجرای نمودارها خاموش می کند ، در عوض کد را به طور معمول اجرا می کند.

tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=11>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)

با این حال، Function می تواند تحت گراف و اجرای مشتاق متفاوت رفتار کند. تابع print پایتون نمونه ای از تفاوت این دو حالت است. بیایید بررسی کنیم وقتی یک دستور print را در تابع خود وارد می کنید و آن را مکرراً فراخوانی می کنید چه اتفاقی می افتد.

@tf.function
def get_MSE(y_true, y_pred):
  print("Calculating MSE!")
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)

به آنچه چاپ شده توجه کنید:

error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!

آیا خروجی تعجب آور است؟ get_MSE فقط یک بار چاپ شد حتی اگر سه بار فراخوانی شد.

برای توضیح، دستور print زمانی اجرا می شود که Function کد اصلی را اجرا می کند تا نمودار را در فرآیندی به نام "ردیابی" ایجاد کند. Tracing عملیات TensorFlow را در یک نمودار ثبت می کند و print در نمودار ثبت نمی شود. سپس آن نمودار برای هر سه تماس بدون اجرای مجدد کد پایتون اجرا می شود .

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

# Now, globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
# Observe what is printed below.
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!
Calculating MSE!
Calculating MSE!
tf.config.run_functions_eagerly(False)

print یک عارضه جانبی پایتون است و تفاوت های دیگری نیز وجود دارد که هنگام تبدیل یک تابع به یک Function باید از آنها آگاه باشید. در بخش محدودیت‌ها در راهنمای عملکرد بهتر با tf.function بیشتر بیاموزید .

اجرای غیر دقیق

اجرای گراف فقط عملیات لازم برای ایجاد اثرات قابل مشاهده را اجرا می کند که شامل:

  • مقدار بازگشتی تابع
  • عوارض جانبی شناخته شده مستند مانند:
    • عملیات ورودی/خروجی، مانند tf.print
    • عملیات اشکال زدایی، مانند توابع assert در tf.debugging
    • جهش tf.Variable

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

به طور خاص، بررسی خطای زمان اجرا به عنوان یک اثر قابل مشاهده به حساب نمی آید. اگر عملیاتی به دلیل غیر ضروری بودن نادیده گرفته شود، نمی تواند خطای زمان اجرا ایجاد کند.

در مثال زیر، عملیات "غیرضروری" tf.gather در حین اجرای نمودار نادیده گرفته می شود، بنابراین خطای زمان اجرا InvalidArgumentError همانطور که در اجرای مشتاقانه مطرح می شود، مطرح نمی شود. در هنگام اجرای نمودار به خطای مطرح شده اعتماد نکنید.

def unused_return_eager(x):
  # Get index 1 will fail when `len(x) == 1`
  tf.gather(x, [1]) # unused 
  return x

try:
  print(unused_return_eager(tf.constant([0.0])))
except tf.errors.InvalidArgumentError as e:
  # All operations are run during eager execution so an error is raised.
  print(f'{type(e).__name__}: {e}')
tf.Tensor([0.], shape=(1,), dtype=float32)
@tf.function
def unused_return_graph(x):
  tf.gather(x, [1]) # unused
  return x

# Only needed operations are run during graph exection. The error is not raised.
print(unused_return_graph(tf.constant([0.0])))
tf.Tensor([0.], shape=(1,), dtype=float32)

بهترین شیوه های tf.function

ممکن است کمی طول بکشد تا به رفتار Function عادت کنید. برای شروع سریع، کاربرانی که برای اولین بار استفاده می‌کنند باید با توابع تزئین اسباب‌بازی با @tf.function بازی کنند تا از اجرای گراف مشتاق به اجرای گراف را تجربه کنند.

طراحی برای tf.function ممکن است بهترین گزینه برای نوشتن برنامه های TensorFlow سازگار با گراف باشد. در اینجا چند نکته وجود دارد:

  • بین اجرای مشتاق و گراف در اوایل و اغلب با tf.config.run_functions_eagerly تا زمانی که دو حالت از هم جدا می‌شوند، اگر/ را مشخص کنید.
  • tf.Variable s را خارج از تابع پایتون ایجاد کنید و آنها را در داخل تغییر دهید. همین امر در مورد اشیایی که از tf.Variable استفاده می کنند، مانند keras.layers ، keras.Model و tf.optimizers می کند.
  • از نوشتن توابعی که به متغیرهای بیرونی پایتون وابسته هستند ، به استثنای tf.Variable s و Keras خودداری کنید.
  • ترجیحاً توابعی بنویسید که تانسورها و سایر انواع TensorFlow را به عنوان ورودی می گیرند. شما می توانید در انواع دیگر آبجکت پاس دهید اما مراقب باشید !
  • تا جایی که ممکن است محاسبات را تحت یک tf.function . قرار دهید تا بهره عملکرد را به حداکثر برسانید. به عنوان مثال، یک مرحله آموزشی کامل یا کل حلقه آموزشی را تزئین کنید.

با دیدن افزایش سرعت

tf.function معمولاً عملکرد کد شما را بهبود می بخشد، اما میزان افزایش سرعت به نوع محاسباتی که اجرا می کنید بستگی دارد. محاسبات کوچک را می توان تحت تسلط سربار فراخوانی یک نمودار قرار داد. شما می توانید تفاوت عملکرد را مانند زیر اندازه گیری کنید:

x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)

def power(x, y):
  result = tf.eye(10, dtype=tf.dtypes.int32)
  for _ in range(y):
    result = tf.matmul(x, result)
  return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Eager execution: 2.5637862179974036
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.6832536700021592

tf.function معمولا برای سرعت بخشیدن به حلقه های آموزشی استفاده می شود و می توانید در نوشتن حلقه آموزشی از ابتدا با Keras در مورد آن اطلاعات بیشتری کسب کنید.

عملکرد و معاوضه

نمودارها می توانند کد شما را سرعت بخشند، اما روند ایجاد آنها مقداری سربار دارد. برای برخی از توابع، ایجاد نمودار نسبت به اجرای نمودار زمان بیشتری می برد. این سرمایه‌گذاری معمولاً با افزایش عملکرد اجراهای بعدی به سرعت بازپرداخت می‌شود، اما مهم است که توجه داشته باشید که چند مرحله اول هر آموزش مدل بزرگ به دلیل ردیابی می‌تواند کندتر باشد.

مهم نیست که مدل شما چقدر بزرگ است، باید از ردیابی مکرر خودداری کنید. راهنمای tf.function نحوه تنظیم مشخصات ورودی و استفاده از آرگومان های تانسور برای جلوگیری از ردیابی مجدد را مورد بحث قرار می دهد. اگر متوجه شدید که عملکرد غیرعادی ضعیفی دارید، بهتر است بررسی کنید که آیا به طور تصادفی در حال ردیابی مجدد هستید یا خیر.

ردیابی Function چه زمانی است؟

برای اینکه بفهمید Function شما در حال ردیابی است، یک عبارت print را به کد آن اضافه کنید. به عنوان یک قاعده کلی، Function دستور print را هر بار که ردیابی می کند اجرا می کند.

@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!") # An eager-only side effect.
  return x * x + tf.constant(2)

# This is traced the first time.
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect.
print(a_function_with_python_side_effect(tf.constant(3)))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
# This retraces each time the Python argument changes,
# as a Python argument could be an epoch count or other
# hyperparameter.
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

آرگومان‌های جدید پایتون همیشه باعث ایجاد یک گراف جدید می‌شوند، بنابراین ردیابی اضافی.

مراحل بعدی

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