Tf.function সঙ্গে আরও ভাল পারফরম্যান্স

TensorFlow.org এ দেখুন Google Colab-এ চালান GitHub-এ উৎস দেখুননোটবুক ডাউনলোড করুন

TensorFlow 2-এ, ডিফল্টরূপে উদগ্রীব সম্পাদন চালু করা হয়। ইউজার ইন্টারফেসটি স্বজ্ঞাত এবং নমনীয় (একবার অপারেশন চালানো অনেক সহজ এবং দ্রুত), তবে এটি কর্মক্ষমতা এবং স্থাপনযোগ্যতার খরচে আসতে পারে।

আপনি আপনার প্রোগ্রাম থেকে গ্রাফ তৈরি করতে tf.function ব্যবহার করতে পারেন। এটি একটি রূপান্তর সরঞ্জাম যা আপনার পাইথন কোড থেকে পাইথন-স্বাধীন ডেটাফ্লো গ্রাফ তৈরি করে। এটি আপনাকে পারফরম্যান্ট এবং পোর্টেবল মডেল তৈরি করতে সাহায্য করবে এবং এটি SavedModel ব্যবহার করতে হবে।

এই নির্দেশিকা আপনাকে ধারণা করতে সাহায্য করবে কিভাবে tf.function হুডের নিচে কাজ করে, যাতে আপনি এটি কার্যকরভাবে ব্যবহার করতে পারেন।

প্রধান টেকওয়ে এবং সুপারিশ হল:

  • আগ্রহী মোডে ডিবাগ করুন, তারপর @tf.function দিয়ে সাজান।
  • বস্তুর মিউটেশন বা তালিকা সংযোজনের মতো পাইথনের পার্শ্বপ্রতিক্রিয়াগুলির উপর নির্ভর করবেন না।
  • tf.function ops এর সাথে সবচেয়ে ভালো কাজ করে; NumPy এবং Python কলগুলি ধ্রুবকগুলিতে রূপান্তরিত হয়।

সেটআপ

import tensorflow as tf

আপনি যে ধরণের ত্রুটির সম্মুখীন হতে পারেন তা প্রদর্শন করতে একটি সহায়ক ফাংশন সংজ্ঞায়িত করুন:

import traceback
import contextlib

# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
  try:
    yield
  except error_class as e:
    print('Caught expected exception \n  {}:'.format(error_class))
    traceback.print_exc(limit=2)
  except Exception as e:
    raise e
  else:
    raise Exception('Expected {} to be raised but no error was raised!'.format(
        error_class))

বেসিক

ব্যবহার

আপনার দ্বারা সংজ্ঞায়িত একটি Function (উদাহরণস্বরূপ @tf.function ডেকোরেটর প্রয়োগ করে) ঠিক একটি মূল TensorFlow অপারেশনের মতো: আপনি এটিকে সাগ্রহে সম্পাদন করতে পারেন; আপনি গ্রেডিয়েন্ট গণনা করতে পারেন; এবং তাই

@tf.function  # The decorator converts `add` into a `Function`.
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)>
v = tf.Variable(1.0)
with tf.GradientTape() as tape:
  result = add(v, 1.0)
tape.gradient(result, v)
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>

আপনি অন্যান্য Function ভিতরে Function ব্যবহার করতে পারেন।

@tf.function
def dense_layer(x, w, b):
  return add(tf.matmul(x, w), b)

dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
       [3., 3.],
       [3., 3.]], dtype=float32)>

Function s আগ্রহী কোডের চেয়ে দ্রুত হতে পারে, বিশেষ করে অনেক ছোট অপ্স সহ গ্রাফের জন্য। কিন্তু কিছু ব্যয়বহুল অপ্স সহ গ্রাফের জন্য (যেমন কনভোলিউশন), আপনি হয়তো খুব বেশি গতি দেখতে পাবেন না।

import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)

@tf.function
def conv_fn(image):
  return conv_layer(image)

image = tf.zeros([1, 200, 200, 100])
# Warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")
Eager conv: 0.006058974999177735
Function conv: 0.005791576000774512
Note how there's not much difference in performance for convolutions

ট্রেসিং

এই বিভাগটি প্রকাশ করে যে কীভাবে Function হুডের অধীনে কাজ করে, বাস্তবায়নের বিবরণ সহ যা ভবিষ্যতে পরিবর্তিত হতে পারে । যাইহোক, একবার আপনি বুঝতে পারলে কেন এবং কখন ট্রেসিং হয়, tf.function কার্যকরভাবে ব্যবহার করা অনেক সহজ!

"ট্রেসিং" কি?

একটি Function একটি টেনসরফ্লো গ্রাফে আপনার প্রোগ্রাম চালায়। যাইহোক, একটি tf.Graph সমস্ত জিনিস উপস্থাপন করতে পারে না যা আপনি একটি আগ্রহী TensorFlow প্রোগ্রামে লিখবেন। উদাহরণ স্বরূপ, পাইথন পলিমরফিজম সমর্থন করে, কিন্তু tf.Graph এর ইনপুটগুলির জন্য একটি নির্দিষ্ট ডেটা টাইপ এবং মাত্রা থাকতে হবে। অথবা আপনি কমান্ড-লাইন আর্গুমেন্ট পড়া, একটি ত্রুটি উত্থাপন, বা একটি আরো জটিল পাইথন বস্তুর সাথে কাজ করার মত পার্শ্ব কাজগুলি সম্পাদন করতে পারেন; এই জিনিসগুলির কোনটিই tf.Graph চলতে পারে না।

Function দুটি ধাপে আপনার কোড আলাদা করে এই ফাঁকটি পূরণ করে:

1) প্রথম পর্যায়ে, " ট্রেসিং " হিসাবে উল্লেখ করা হয়, Function একটি নতুন tf.Graph . গ্রাফ তৈরি করে। পাইথন কোড সাধারণত চলে, কিন্তু সমস্ত টেনসরফ্লো অপারেশন (যেমন দুটি টেনসর যোগ করা) স্থগিত করা হয় : সেগুলি tf.Graph . গ্রাফ দ্বারা ক্যাপচার করা হয় এবং চালানো হয় না।

2) দ্বিতীয় পর্যায়ে, একটি tf.Graph যা প্রথম ধাপে পিছিয়ে যাওয়া সমস্ত কিছু ধারণ করে চালানো হয়। এই পর্যায়টি ট্রেসিং পর্যায়ের চেয়ে অনেক দ্রুত।

এটির ইনপুটগুলির উপর নির্ভর করে, যখন এটি কল করা হয় তখন Function সর্বদা প্রথম পর্যায়ে চলবে না। এটি কীভাবে সেই সংকল্প করে তার আরও ভাল ধারণা পেতে নীচে "ট্রেসিংয়ের নিয়ম" দেখুন। প্রথম পর্যায়টি এড়িয়ে যাওয়া এবং শুধুমাত্র দ্বিতীয় পর্যায়টি সম্পাদন করাই আপনাকে টেনসরফ্লো-এর উচ্চ কার্যক্ষমতা প্রদান করে।

যখন Function ট্রেস করার সিদ্ধান্ত নেয়, তখন ট্রেসিং পর্যায়টি অবিলম্বে দ্বিতীয় পর্যায় দ্বারা অনুসরণ করা হয়, তাই tf.Graph Function এবং চালানো হয়। পরে আপনি দেখতে পাবেন কিভাবে আপনি get_concrete_function দিয়ে শুধুমাত্র ট্রেসিং স্টেজ চালাতে পারেন।

যখন আপনি একটি Function বিভিন্ন ধরনের আর্গুমেন্ট পাস করেন, তখন উভয় ধাপই চালানো হয়:

@tf.function
def double(a):
  print("Tracing with", a)
  return a + a

print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()
Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)

Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)

মনে রাখবেন যে আপনি যদি একই আর্গুমেন্ট টাইপের একটি Function বারবার কল করেন, TensorFlow ট্রেসিং স্টেজ এড়িয়ে যাবে এবং পূর্বে ট্রেস করা গ্রাফটি পুনরায় ব্যবহার করবে, কারণ জেনারেট করা গ্রাফটি একই রকম হবে।

# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
tf.Tensor(b'bb', shape=(), dtype=string)

আপনি উপলব্ধ সমস্ত ট্রেস দেখতে pretty_printed_concrete_signatures() ব্যবহার করতে পারেন:

print(double.pretty_printed_concrete_signatures())
double(a)
  Args:
    a: int32 Tensor, shape=()
  Returns:
    int32 Tensor, shape=()

double(a)
  Args:
    a: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

এখন পর্যন্ত, আপনি দেখেছেন যে tf.function এর গ্রাফ ট্রেসিং লজিকের উপরে একটি ক্যাশেড, ডাইনামিক ডিসপ্যাচ লেয়ার তৈরি করে। পরিভাষা সম্পর্কে আরো নির্দিষ্ট হতে:

  • একটি tf. Graph হল একটি tf.Graph কাঁচা, ভাষা-অজ্ঞেয়বাদী, বহনযোগ্য উপস্থাপনা।
  • একটি ConcreteFunction ফাংশন একটি tf.Graph
  • একটি Function ConcreteFunction s-এর ক্যাশে পরিচালনা করে এবং আপনার ইনপুটগুলির জন্য সঠিকটি বেছে নেয়।
  • tf.function একটি Python ফাংশন মোড়ানো, একটি Function অবজেক্ট ফিরিয়ে দেয়।
  • ট্রেসিং একটি tf.Graph তৈরি করে এবং এটি একটি ConcreteFunction ফাংশনে মোড়ানো হয়, এটি একটি ট্রেস নামেও পরিচিত।

ট্রেসিং এর নিয়ম

একটি Function একটি ইনপুট এর args এবং kwargs থেকে একটি ক্যাশে কী কম্পিউট করে একটি ট্রেস করা ConcreteFunction পুনরায় ব্যবহার করতে হবে কিনা তা নির্ধারণ করে। একটি ক্যাশে কী হল একটি কী যা Function কলের ইনপুট আর্গ এবং কোয়ার্গের উপর ভিত্তি করে একটি ConcreteFunction ফাংশন সনাক্ত করে, নিম্নলিখিত নিয়ম অনুসারে (যা পরিবর্তন হতে পারে):

  • একটি tf.Tensor এর জন্য উৎপন্ন কী হল এর আকৃতি এবং dtype।
  • একটি tf.Variable এর জন্য উৎপন্ন কী একটি অনন্য পরিবর্তনশীল আইডি।
  • পাইথন আদিম (যেমন int , float , str ) এর জন্য উৎপন্ন কী হল এর মান।
  • নেস্টেড dict এস, list এস, tuple এস, namedtuple এস এবং attr এর জন্য তৈরি করা কী হল পাতা-কীগুলির চ্যাপ্টা টিপল (দেখুন nest.flatten )। (এই সমতলকরণের ফলে, ট্রেসিংয়ের সময় ব্যবহৃত একটির থেকে একটি ভিন্ন নেস্টিং কাঠামোর সাথে একটি কংক্রিট ফাংশনকে কল করার ফলে একটি TypeError হবে)।
  • অন্যান্য সমস্ত পাইথন প্রকারের জন্য কীটি বস্তুর জন্য অনন্য। এইভাবে একটি ফাংশন বা পদ্ধতি স্বাধীনভাবে চিহ্নিত করা হয় প্রতিটি দৃষ্টান্তের জন্য যার সাথে এটি বলা হয়।

রিট্রেসিং নিয়ন্ত্রণ করা

রিট্রেসিং, যখন আপনার Function একাধিক ট্রেস তৈরি করে, তা নিশ্চিত করতে সাহায্য করে যে TensorFlow প্রতিটি সেট ইনপুটের জন্য সঠিক গ্রাফ তৈরি করে। তবে ট্রেসিং একটি ব্যয়বহুল অপারেশন! যদি আপনার Function প্রতিটি কলের জন্য একটি নতুন গ্রাফ রিট্রেস করে, আপনি দেখতে পাবেন যে আপনি tf.function ব্যবহার না করলে আপনার কোডটি আরও ধীরে ধীরে কার্যকর হয়।

ট্রেসিং আচরণ নিয়ন্ত্রণ করতে, আপনি নিম্নলিখিত কৌশলগুলি ব্যবহার করতে পারেন:

  • ট্রেসিং সীমিত করতে input_signaturetf.function উল্লেখ করুন।
@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
  print("Tracing with", x)
  return tf.where(x % 2 == 0, x // 2, 3 * x + 1)

print(next_collatz(tf.constant([1, 2])))
# You specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))

# You specified an int32 dtype in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([1.0, 2.0]))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception 
  <class 'ValueError'>:
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/1851403433.py", line 9, in <module>
    next_collatz(tf.constant([[1, 2], [3, 4]]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor(
[[1 2]
 [3 4]], shape=(2, 2), dtype=int32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None)).
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/1851403433.py", line 13, in <module>
    next_collatz(tf.constant([1.0, 2.0]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor([1. 2.], shape=(2,), dtype=float32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None)).
  • ট্রেস পুনঃব্যবহারে নমনীয়তার জন্য tf.TensorSpec এ একটি [None] মাত্রা নির্দিষ্ট করুন।

    যেহেতু TensorFlow তাদের আকৃতির উপর ভিত্তি করে টেনসরের সাথে মেলে, তাই ওয়াইল্ডকার্ড হিসেবে None ডাইমেনশন ব্যবহার করলে Function -কে পরিবর্তনশীল আকারের ইনপুটের জন্য ট্রেস পুনরায় ব্যবহার করার অনুমতি দেবে। পরিবর্তনশীল আকারের ইনপুট ঘটতে পারে যদি আপনার কাছে বিভিন্ন দৈর্ঘ্যের ক্রম বা প্রতিটি ব্যাচের জন্য বিভিন্ন আকারের ছবি থাকে (উদাহরণস্বরূপ ট্রান্সফরমার এবং ডিপ ড্রিম টিউটোরিয়াল দেখুন)।

@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def g(x):
  print('Tracing with', x)
  return x

# No retrace!
print(g(tf.constant([1, 2, 3])))
print(g(tf.constant([1, 2, 3, 4, 5])))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)
  • রিট্রেসিং কমাতে টেনসরে পাইথন আর্গুমেন্ট কাস্ট করুন।

    প্রায়শই, হাইপারপ্যারামিটার এবং গ্রাফ নির্মাণ নিয়ন্ত্রণ করতে পাইথন আর্গুমেন্ট ব্যবহার করা হয় - উদাহরণস্বরূপ, num_layers=10 বা training=True বা nonlinearity='relu' । সুতরাং, যদি পাইথন আর্গুমেন্ট পরিবর্তন হয়, তাহলে এটা বোঝায় যে আপনাকে গ্রাফটি রিট্রেস করতে হবে।

    যাইহোক, এটা সম্ভব যে একটি পাইথন যুক্তি গ্রাফ নির্মাণ নিয়ন্ত্রণ করতে ব্যবহার করা হচ্ছে না। এই ক্ষেত্রে, পাইথন মানের পরিবর্তন অপ্রয়োজনীয় রিট্রেসিং ট্রিগার করতে পারে। উদাহরণস্বরূপ, এই প্রশিক্ষণ লুপটি নিন, যা অটোগ্রাফ গতিশীলভাবে আনরোল করবে। একাধিক ট্রেস থাকা সত্ত্বেও, জেনারেট করা গ্রাফটি আসলে অভিন্ন, তাই রিট্রেসিং অপ্রয়োজনীয়।

def train_one_step():
  pass

@tf.function
def train(num_steps):
  print("Tracing with num_steps = ", num_steps)
  tf.print("Executing with num_steps = ", num_steps)
  for _ in tf.range(num_steps):
    train_one_step()

print("Retracing occurs for different Python arguments.")
train(num_steps=10)
train(num_steps=20)

print()
print("Traces are reused for Tensor arguments.")
train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))
Retracing occurs for different Python arguments.
Tracing with num_steps =  10
Executing with num_steps =  10
Tracing with num_steps =  20
Executing with num_steps =  20

Traces are reused for Tensor arguments.
Tracing with num_steps =  Tensor("num_steps:0", shape=(), dtype=int32)
Executing with num_steps =  10
Executing with num_steps =  20

যদি আপনাকে জোরপূর্বক রিট্রেসিং করতে হয়, একটি নতুন Function তৈরি করুন। পৃথক Function বস্তুর ট্রেস ভাগ না নিশ্চিত করা হয়.

def f():
  print('Tracing!')
  tf.print('Executing')

tf.function(f)()
tf.function(f)()
Tracing!
Executing
Tracing!
Executing

কংক্রিট ফাংশন প্রাপ্তি

প্রতিবার একটি ফাংশন ট্রেস করা হয়, একটি নতুন কংক্রিট ফাংশন তৈরি করা হয়। get_concrete_function ব্যবহার করে আপনি সরাসরি একটি কংক্রিট ফাংশন পেতে পারেন।

print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.constant("a"))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))
Obtaining concrete trace
Executing traced function
tf.Tensor(b'aa', shape=(), dtype=string)
tf.Tensor(b'bb', shape=(), dtype=string)
# You can also call get_concrete_function on an InputSpec
double_strings_from_inputspec = double.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.string))
print(double_strings_from_inputspec(tf.constant("c")))
tf.Tensor(b'cc', shape=(), dtype=string)

একটি ConcreteFunction মুদ্রণ এর ইনপুট আর্গুমেন্ট (প্রকার সহ) এবং এর আউটপুট প্রকারের একটি সারাংশ প্রদর্শন করে।

print(double_strings)
ConcreteFunction double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

আপনি সরাসরি একটি কংক্রিট ফাংশনের স্বাক্ষর পুনরুদ্ধার করতে পারেন।

print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {})
Tensor("Identity:0", shape=(), dtype=string)

বেমানান ধরনের সঙ্গে একটি কংক্রিট ট্রেস ব্যবহার করে একটি ত্রুটি নিক্ষেপ করা হবে

with assert_raises(tf.errors.InvalidArgumentError):
  double_strings(tf.constant(1))
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/3196284684.py", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_162 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_162]

আপনি লক্ষ্য করতে পারেন যে পাইথন আর্গুমেন্টগুলি একটি কংক্রিট ফাংশনের ইনপুট স্বাক্ষরে বিশেষ চিকিত্সা দেওয়া হয়। TensorFlow 2.3-এর আগে, Python আর্গুমেন্টগুলি কংক্রিট ফাংশনের স্বাক্ষর থেকে সরানো হয়েছিল। টেনসরফ্লো 2.3 দিয়ে শুরু করে, পাইথন আর্গুমেন্টগুলি স্বাক্ষরে থাকে, কিন্তু ট্রেসিংয়ের সময় সেট করা মান নিতে বাধ্য হয়।

@tf.function
def pow(a, b):
  return a ** b

square = pow.get_concrete_function(a=tf.TensorSpec(None, tf.float32), b=2)
print(square)
ConcreteFunction pow(a, b=2)
  Args:
    a: float32 Tensor, shape=<unknown>
  Returns:
    float32 Tensor, shape=<unknown>
assert square(tf.constant(10.0)) == 100

with assert_raises(TypeError):
  square(tf.constant(10.0), b=3)
Caught expected exception 
  <class 'TypeError'>:
Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py", line 1721, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py", line 1765, in _call_with_flat_signature
    raise TypeError(f"{self._flat_signature_summary()} got unexpected "
TypeError: pow(a) got unexpected keyword arguments: b.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/2310937119.py", line 4, in <module>
    square(tf.constant(10.0), b=3)
TypeError: ConcreteFunction pow(a, b) was constructed with int value 2 in b, but was called with int value 3.

গ্রাফ প্রাপ্ত

প্রতিটি কংক্রিট ফাংশন একটি tf.Graph চারপাশে একটি কলযোগ্য মোড়ক। যদিও প্রকৃত tf.Graph অবজেক্টটি পুনরুদ্ধার করা এমন কিছু নয় যা আপনাকে সাধারণত করতে হবে, আপনি যেকোনো কংক্রিট ফাংশন থেকে এটি সহজেই পেতে পারেন।

graph = double_strings.graph
for node in graph.as_graph_def().node:
  print(f'{node.input} -> {node.name}')
[] -> a
['a', 'a'] -> add
['add'] -> Identity

ডিবাগিং

সাধারণভাবে, tf.function এর ভিতরের তুলনায় ইজির মোডে ডিবাগিং কোড সহজ। tf.function দিয়ে সাজানোর আগে আপনাকে নিশ্চিত করতে হবে যে আপনার কোডটি এগার মোডে ত্রুটি-মুক্তভাবে কার্যকর করে। ডিবাগিং প্রক্রিয়ায় সহায়তা করার জন্য, আপনি tf.config.run_functions_eagerly(True) কল করতে পারেন tf.function কে বিশ্বব্যাপী নিষ্ক্রিয় এবং পুনরায় সক্রিয় করতে।

শুধুমাত্র tf.function এর মধ্যে প্রদর্শিত সমস্যাগুলি ট্র্যাক করার সময়, এখানে কিছু টিপস দেওয়া হল:

  • সাধারণ পুরানো পাইথন print কলগুলি শুধুমাত্র ট্রেসিংয়ের সময়ই কার্যকর হয়, যখন আপনার ফাংশন (পুনরায়) ট্রেস করা হয় তখন আপনাকে ট্র্যাক করতে সাহায্য করে।
  • tf.print কলগুলি প্রতিবার কার্যকর হবে এবং কার্যকর করার সময় মধ্যবর্তী মানগুলি ট্র্যাক করতে আপনাকে সাহায্য করতে পারে।
  • tf.debugging.enable_check_numerics যেখানে NaN এবং Inf তৈরি করা হয়েছে তা ট্র্যাক করার একটি সহজ উপায়।
  • pdb ( পাইথন ডিবাগার ) আপনাকে ট্রেসিংয়ের সময় কী ঘটছে তা বুঝতে সাহায্য করতে পারে। (সতর্কতা: pdb আপনাকে অটোগ্রাফ-রূপান্তরিত সোর্স কোডে ফেলে দেবে।)

অটোগ্রাফ রূপান্তর

AutoGraph হল একটি লাইব্রেরি যা tf.function-এ ডিফল্টরূপে চালু থাকে এবং Python eager কোডের একটি উপসেটকে গ্রাফ-সামঞ্জস্যপূর্ণ tf.function এ রূপান্তরিত করে। এর মধ্যে if , for , while এর মতো নিয়ন্ত্রণ প্রবাহ অন্তর্ভুক্ত।

tf.cond এবং tf.while_loop এর মত tf.cond অপ্সগুলি কাজ করতে থাকে, কিন্তু Python এ লেখার সময় নিয়ন্ত্রণ প্রবাহ প্রায়ই লেখা এবং বোঝা সহজ হয়।

# A simple loop

@tf.function
def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

f(tf.random.uniform([5]))
[0.666458249 0.713946581 0.723879576 0.330758929 0.184087753]
[0.582645297 0.613145649 0.619306684 0.319202513 0.182036072]
[0.524585426 0.546337605 0.550645113 0.308785647 0.18005164]
[0.481231302 0.497770309 0.501003504 0.299331933 0.178130865]
[0.447229207 0.460361809 0.462906033 0.290701121 0.176270396]
[0.419618756 0.430379033 0.432449728 0.282779962 0.174467146]
[0.396609187 0.405638 0.407366514 0.275476 0.172718227]
[0.377043903 0.384762734 0.386234313 0.268712848 0.17102097]
[0.360137492 0.366836458 0.368109286 0.262426734 0.169372901]
[0.345335096 0.351221472 0.352336824 0.256563932 0.167771652]
[0.332231969 0.337458342 0.338446289 0.251078814 0.166215062]
[0.320524871 0.325206399 0.326089561 0.24593246 0.164701089]
[0.309981436 0.314206958 0.31500268 0.241091311 0.163227797]
[0.300420195 0.304259449 0.304981351 0.236526251 0.161793426]
[0.291697085 0.295205742 0.295864582 0.232211992 0.160396278]
[0.283696055 0.286919087 0.287523568 0.228126258 0.159034774]
[0.276322395 0.279296666 0.27985391 0.224249557 0.157707423]
[0.269497961 0.272254 0.272769839 0.220564634 0.15641281]
[0.263157606 0.265720904 0.266200244 0.21705614 0.155149609]
[0.257246554 0.259638608 0.260085613 0.213710397 0.153916568]
[0.251718313 0.25395745 0.254375577 0.210515186 0.152712509]
[0.246533215 0.248635098 0.249027327 0.207459539 0.151536316]
[0.241657034 0.243635193 0.244004101 0.204533577 0.15038693]
[0.237060249 0.238926381 0.239274174 0.201728329 0.149263337]
[0.232717097 0.234481394 0.234810054 0.199035719 0.148164615]
[0.228605017 0.230276451 0.230587661 0.196448416 0.147089839]
[0.224704206 0.226290658 0.22658591 0.193959698 0.14603813]
[0.220997125 0.222505584 0.222786173 0.191563457 0.145008713]
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.21746822, 0.21890487, 0.21917202, 0.18925412, 0.14400077],
      dtype=float32)>

আপনি যদি কৌতূহলী হন তবে আপনি অটোগ্রাফ তৈরি করা কোডটি পরীক্ষা করতে পারেন।

print(tf.autograph.to_code(f.python_function))
def tf__f(x):
    with ag__.FunctionScope('f', '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 (x,)

        def set_state(vars_):
            nonlocal x
            (x,) = vars_

        def loop_body():
            nonlocal x
            ag__.converted_call(ag__.ld(tf).print, (ag__.ld(x),), None, fscope)
            x = ag__.converted_call(ag__.ld(tf).tanh, (ag__.ld(x),), None, fscope)

        def loop_test():
            return (ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) > 1)
        ag__.while_stmt(loop_test, loop_body, get_state, set_state, ('x',), {})
        try:
            do_return = True
            retval_ = ag__.ld(x)
        except:
            do_return = False
            raise
        return fscope.ret(retval_, do_return)

শর্তাবলী

অটোগ্রাফ কিছু if <condition> স্টেটমেন্টকে সমতুল্য tf.cond কলে রূপান্তর করবে। এই প্রতিস্থাপন করা হয় যদি <condition> একটি Tensor হয়। অন্যথায়, if স্টেটমেন্টটি পাইথন শর্তসাপেক্ষ হিসাবে কার্যকর করা হয়।

একটি পাইথন কন্ডিশনাল ট্রেসিংয়ের সময় কার্যকর করে, তাই শর্তসাপেক্ষের ঠিক একটি শাখা গ্রাফে যোগ করা হবে। অটোগ্রাফ ছাড়া, ডেটা-নির্ভর নিয়ন্ত্রণ প্রবাহ থাকলে এই ট্রেস করা গ্রাফটি বিকল্প শাখা নিতে অক্ষম হবে।

tf.cond ট্রেস করে এবং গ্রাফে শর্তসাপেক্ষের উভয় শাখা যোগ করে, কার্যকর করার সময় গতিশীলভাবে একটি শাখা নির্বাচন করে। ট্রেসিং এর অনিচ্ছাকৃত পার্শ্বপ্রতিক্রিয়া হতে পারে; আরো তথ্যের জন্য অটোগ্রাফ ট্রেসিং প্রভাব পরীক্ষা করুন.

@tf.function
def fizzbuzz(n):
  for i in tf.range(1, n + 1):
    print('Tracing for loop')
    if i % 15 == 0:
      print('Tracing fizzbuzz branch')
      tf.print('fizzbuzz')
    elif i % 3 == 0:
      print('Tracing fizz branch')
      tf.print('fizz')
    elif i % 5 == 0:
      print('Tracing buzz branch')
      tf.print('buzz')
    else:
      print('Tracing default branch')
      tf.print(i)

fizzbuzz(tf.constant(5))
fizzbuzz(tf.constant(20))
Tracing for loop
Tracing fizzbuzz branch
Tracing fizz branch
Tracing buzz branch
Tracing default branch
1
2
fizz
4
buzz
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

অটোগ্রাফ-রূপান্তরিত হলে বিবৃতিতে অতিরিক্ত সীমাবদ্ধতার জন্য রেফারেন্স ডকুমেন্টেশন দেখুন।

লুপস

অটোগ্রাফ কিছু for এবং while বিবৃতিকে tf.while_loop এর মতো সমতুল্য tf.while_loop লুপিং অপ্স-এ রূপান্তর করবে। রূপান্তরিত না হলে, for or while লুপটি পাইথন লুপ হিসাবে কার্যকর করা হয়।

এই প্রতিস্থাপন নিম্নলিখিত পরিস্থিতিতে তৈরি করা হয়:

  • for x in y : y যদি টেনসর হয়, tf.while_loop রূপান্তর করুন। বিশেষ ক্ষেত্রে যেখানে y একটি tf.data.Dataset , tf.data.Dataset ops এর সমন্বয় তৈরি হয়।
  • while <condition> : যদি <condition> একটি টেনসর হয়, tf.while_loop রূপান্তর করুন।

একটি পাইথন লুপ ট্রেসিংয়ের সময় কার্যকর করে, লুপের প্রতিটি পুনরাবৃত্তির জন্য tf.Graph . গ্রাফে অতিরিক্ত অপ্স যোগ করে।

একটি টেনসরফ্লো লুপ লুপের বডি ট্রেস করে এবং এক্সিকিউশনের সময় কতগুলি পুনরাবৃত্তি চালাতে হবে তা গতিশীলভাবে নির্বাচন করে। লুপ বডি শুধুমাত্র একবার জেনারেট করা tf.Graph . গ্রাফে উপস্থিত হয়।

বিবৃতি এবং while অটোগ্রাফ-রূপান্তরিত অতিরিক্ত সীমাবদ্ধতার for রেফারেন্স ডকুমেন্টেশন দেখুন।

পাইথন ডেটা লুপ করা হচ্ছে

একটি সাধারণ সমস্যা হল একটি tf.function মধ্যে Python/NumPy ডেটা লুপ করা। এই লুপটি ট্রেসিং প্রক্রিয়া চলাকালীন কার্যকর করবে, লুপের প্রতিটি পুনরাবৃত্তির জন্য tf.Graph . গ্রাফে আপনার মডেলের একটি অনুলিপি যোগ করবে।

আপনি যদি tf.function এ সম্পূর্ণ ট্রেনিং লুপ র‍্যাপ করতে চান, তাহলে এটি করার সবচেয়ে নিরাপদ উপায় হল আপনার ডেটাকে tf.data.Dataset হিসেবে মোড়ানো যাতে AutoGraph ট্রেনিং লুপটিকে গতিশীলভাবে আনরোল করে।

def measure_graph_size(f, *args):
  g = f.get_concrete_function(*args).graph
  print("{}({}) contains {} nodes in its graph".format(
      f.__name__, ', '.join(map(str, args)), len(g.as_graph_def().node)))

@tf.function
def train(dataset):
  loss = tf.constant(0)
  for x, y in dataset:
    loss += tf.abs(y - x) # Some dummy computation.
  return loss

small_data = [(1, 1)] * 3
big_data = [(1, 1)] * 10
measure_graph_size(train, small_data)
measure_graph_size(train, big_data)

measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: small_data, (tf.int32, tf.int32)))
measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: big_data, (tf.int32, tf.int32)))
train([(1, 1), (1, 1), (1, 1)]) contains 11 nodes in its graph
train([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]) contains 32 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 6 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 6 nodes in its graph

একটি ডেটাসেটে Python/NumPy ডেটা মোড়ানোর সময়, tf.data.Dataset.from_generator বনাম tf.data.Dataset.from_tensors সম্পর্কে মনে রাখবেন। পূর্ববর্তীটি পাইথনে ডেটা রাখবে এবং tf.py_function এর মাধ্যমে এটি আনবে যার কার্যকারিতা প্রভাব থাকতে পারে, যেখানে পরবর্তীটি গ্রাফে একটি বড় tf.constant() নোড হিসাবে ডেটার একটি অনুলিপি বান্ডেল করবে, যার মেমরির প্রভাব থাকতে পারে।

TFRecordDataset , CsvDataset , ইত্যাদির মাধ্যমে ফাইলগুলি থেকে ডেটা পড়া হল ডেটা ব্যবহার করার সবচেয়ে কার্যকর উপায়, কারণ তখন TensorFlow নিজেই পাইথনকে জড়িত না করেই ডেটার অ্যাসিঙ্ক্রোনাস লোডিং এবং প্রিফেচিং পরিচালনা করতে পারে। আরও জানতে, tf.data দেখুন : বিল্ড টেনসরফ্লো ইনপুট পাইপলাইন গাইড।

একটি লুপে মান জমা করা

একটি সাধারণ প্যাটার্ন হল লুপ থেকে মধ্যবর্তী মান সংগ্রহ করা। সাধারণত, এটি একটি Python তালিকায় যোগ করার মাধ্যমে বা একটি Python অভিধানে এন্ট্রি যোগ করার মাধ্যমে সম্পন্ন করা হয়। যাইহোক, যেহেতু এগুলি পাইথনের পার্শ্বপ্রতিক্রিয়া, তাই এগুলি গতিশীলভাবে আনরোলড লুপে প্রত্যাশিতভাবে কাজ করবে না। গতিশীলভাবে tf.TensorArray করা লুপ থেকে ফলাফল সংগ্রহ করতে tf.TensorArray ব্যবহার করুন।

batch_size = 2
seq_len = 3
feature_size = 4

def rnn_step(inp, state):
  return inp + state

@tf.function
def dynamic_rnn(rnn_step, input_data, initial_state):
  # [batch, time, features] -> [time, batch, features]
  input_data = tf.transpose(input_data, [1, 0, 2])
  max_seq_len = input_data.shape[0]

  states = tf.TensorArray(tf.float32, size=max_seq_len)
  state = initial_state
  for i in tf.range(max_seq_len):
    state = rnn_step(input_data[i], state)
    states = states.write(i, state)
  return tf.transpose(states.stack(), [1, 0, 2])

dynamic_rnn(rnn_step,
            tf.random.uniform([batch_size, seq_len, feature_size]),
            tf.zeros([batch_size, feature_size]))
<tf.Tensor: shape=(2, 3, 4), dtype=float32, numpy=
array([[[0.06309307, 0.9938811 , 0.90789986, 0.42136216],
        [0.44997275, 1.9107027 , 1.0716251 , 0.717237  ],
        [0.6026064 , 2.1622117 , 1.4164022 , 1.4153863 ]],

       [[0.04946005, 0.69127274, 0.56848884, 0.22406638],
        [0.8148316 , 1.0278493 , 0.6207781 , 1.1935129 ],
        [0.9178308 , 1.320889  , 0.989761  , 2.0120025 ]]], dtype=float32)>

সীমাবদ্ধতা

TensorFlow Function ডিজাইনের কিছু সীমাবদ্ধতা রয়েছে যেগুলি একটি Python ফাংশনকে একটি Function রূপান্তর করার সময় আপনার সচেতন হওয়া উচিত।

পাইথন পার্শ্ব প্রতিক্রিয়া নির্বাহ

পার্শ্ব প্রতিক্রিয়া, যেমন মুদ্রণ, তালিকায় যুক্ত করা এবং গ্লোবাল মিউটেটিং, একটি Function ভিতরে অপ্রত্যাশিতভাবে আচরণ করতে পারে, কখনও কখনও দুবার বা সবগুলোই সম্পাদন করে। আপনি ইনপুটগুলির একটি সেট সহ একটি Function কল করার সময় প্রথমবার এটি ঘটে। তারপরে, পাইথন কোডটি নির্বাহ না করেই ট্রেস করা tf.Graph . গ্রাফ পুনরায় কার্যকর করা হয়।

থাম্বের সাধারণ নিয়ম হল আপনার যুক্তিতে পাইথনের পার্শ্বপ্রতিক্রিয়াগুলির উপর নির্ভর করা এড়াতে এবং শুধুমাত্র আপনার চিহ্নগুলি ডিবাগ করতে সেগুলি ব্যবহার করুন৷ অন্যথায়, TensorFlow APIs যেমন tf.data , tf.print , tf.summary , tf.Variable.assign , এবং tf.TensorArray হল আপনার কোড প্রতিটি কলের সাথে TensorFlow রানটাইম দ্বারা কার্যকর হবে তা নিশ্চিত করার সর্বোত্তম উপায়।

@tf.function
def f(x):
  print("Traced with", x)
  tf.print("Executed with", x)

f(1)
f(1)
f(2)
Traced with 1
Executed with 1
Executed with 1
Traced with 2
Executed with 2

আপনি যদি একটি Function প্রতিটি আহ্বানের সময় পাইথন কোড চালাতে চান, tf.py_function হল একটি প্রস্থান হ্যাচ। tf.py_function এর অসুবিধা হল এটি বহনযোগ্য বা বিশেষভাবে পারফরম্যান্ট নয়, SavedModel দিয়ে সংরক্ষণ করা যায় না এবং বিতরণকৃত (মাল্টি-GPU, TPU) সেটআপে ভাল কাজ করে না। এছাড়াও, যেহেতু tf.py_function কে গ্রাফে তার যুক্ত করতে হয়, তাই এটি সমস্ত ইনপুট/আউটপুটকে টেনসরে কাস্ট করে।

পাইথন গ্লোবাল এবং ফ্রি ভেরিয়েবল পরিবর্তন করা

পাইথন গ্লোবাল এবং ফ্রি ভেরিয়েবল পরিবর্তন করা একটি পাইথন পার্শ্ব প্রতিক্রিয়া হিসাবে গণ্য হয়, তাই এটি শুধুমাত্র ট্রেসিংয়ের সময় ঘটে।

external_list = []

@tf.function
def side_effect(x):
  print('Python side effect')
  external_list.append(x)

side_effect(1)
side_effect(1)
side_effect(1)
# The list append only happened once!
assert len(external_list) == 1
Python side effect

কখনও কখনও অপ্রত্যাশিত আচরণ লক্ষ্য করা খুব কঠিন। নীচের উদাহরণে, counter একটি ভেরিয়েবলের বৃদ্ধিকে সুরক্ষিত করার উদ্দেশ্যে। যদিও এটি একটি পাইথন পূর্ণসংখ্যা এবং টেনসরফ্লো অবজেক্ট নয়, এটির মান প্রথম ট্রেসের সময় ধরা হয়। যখন tf.function ব্যবহার করা হয়, assign_add অন্তর্নিহিত গ্রাফে নিঃশর্তভাবে রেকর্ড করা হবে। তাই v 1 দ্বারা বৃদ্ধি পাবে, প্রতিবার tf.function করা হবে। এই সমস্যাটি এমন ব্যবহারকারীদের মধ্যে সাধারণ যারা tf.function ডেকোরেটর ব্যবহার করে তাদের Grpah-মোড টেনসরফ্লো কোড টেনসরফ্লো 2 এ স্থানান্তর করার চেষ্টা করে, যখন পাইথন পার্শ্ব-প্রতিক্রিয়া (উদাহরণে counter ) ব্যবহার করা হয় তখন কোন অপ্স চালানো হবে তা নির্ধারণ করতে ( assign_add করুন) ) সাধারণত, ব্যবহারকারীরা সন্দেহজনক সংখ্যাসূচক ফলাফল দেখে বা প্রত্যাশিতভাবে উল্লেখযোগ্যভাবে কম কর্মক্ষমতা দেখার পরেই এটি উপলব্ধি করে (যেমন যদি রক্ষিত অপারেশনটি খুব ব্যয়বহুল হয়)।

class Model(tf.Module):
  def __init__(self):
    self.v = tf.Variable(0)
    self.counter = 0

  @tf.function
  def __call__(self):
    if self.counter == 0:
      # A python side-effect
      self.counter += 1
      self.v.assign_add(1)

    return self.v

m = Model()
for n in range(3):
  print(m().numpy()) # prints 1, 2, 3
1
2
3

প্রত্যাশিত আচরণ অর্জনের জন্য একটি সমাধান হল tf.init_scope ব্যবহার করে ফাংশন গ্রাফের বাইরের ক্রিয়াকলাপগুলিকে উত্তোলন করা। এটি নিশ্চিত করে যে পরিবর্তনশীল বৃদ্ধি শুধুমাত্র ট্রেসিং সময়কালে একবার করা হয়। এটা লক্ষ করা উচিত init_scope এর অন্যান্য পার্শ্বপ্রতিক্রিয়া রয়েছে যার মধ্যে রয়েছে ক্লিয়ার কন্ট্রোল ফ্লো এবং গ্রেডিয়েন্ট টেপ। কখনও কখনও init_scope ব্যবহার বাস্তবসম্মতভাবে পরিচালনা করার জন্য খুব জটিল হয়ে উঠতে পারে।

class Model(tf.Module):
  def __init__(self):
    self.v = tf.Variable(0)
    self.counter = 0

  @tf.function
  def __call__(self):
    if self.counter == 0:
      # Lifts ops out of function-building graphs
      with tf.init_scope():
        self.counter += 1
        self.v.assign_add(1)

    return self.v

m = Model()
for n in range(3):
  print(m().numpy()) # prints 1, 1, 1
1
1
1

সংক্ষেপে, একটি নিয়ম হিসাবে, আপনার অজগর বস্তুর পরিবর্তন করা এড়ানো উচিত যেমন পূর্ণসংখ্যা বা ধারকগুলি যেমন Function বাইরে থাকে। পরিবর্তে, আর্গুমেন্ট এবং TF অবজেক্ট ব্যবহার করুন। উদাহরণস্বরূপ, "লুপে মান সংগ্রহ করা" বিভাগে একটি উদাহরণ রয়েছে যে কীভাবে তালিকার মতো ক্রিয়াকলাপগুলি প্রয়োগ করা যেতে পারে।

আপনি, কিছু ক্ষেত্রে, অবস্থা ক্যাপচার এবং ম্যানিপুলেট করতে পারেন যদি এটি একটি tf.Variable হয়। একই ConcreteFunction ফাংশনে বারবার কল করার মাধ্যমে কেরাস মডেলের ওজন এইভাবে আপডেট করা হয়।

পাইথন পুনরাবৃত্তিকারী এবং জেনারেটর ব্যবহার করে

অনেক পাইথন বৈশিষ্ট্য, যেমন জেনারেটর এবং পুনরাবৃত্তিকারী, স্টেট ট্র্যাক রাখতে পাইথন রানটাইমের উপর নির্ভর করে। সাধারণভাবে, এই কন্সট্রাকশনগুলি যেমন উদগ্রীব মোডে প্রত্যাশিতভাবে কাজ করে, সেগুলি পাইথনের পার্শ্বপ্রতিক্রিয়াগুলির উদাহরণ এবং তাই শুধুমাত্র ট্রেসিংয়ের সময় ঘটে।

@tf.function
def buggy_consume_next(iterator):
  tf.print("Value:", next(iterator))

iterator = iter([1, 2, 3])
buggy_consume_next(iterator)
# This reuses the first value from the iterator, rather than consuming the next value.
buggy_consume_next(iterator)
buggy_consume_next(iterator)
Value: 1
Value: 1
Value: 1

ঠিক যেমন TensorFlow-এর তালিকা তৈরির জন্য একটি বিশেষ tf.TensorArray আছে, এটিতে পুনরাবৃত্তি নির্মাণের জন্য একটি বিশেষ tf.data.Iterator আছে। একটি ওভারভিউ জন্য অটোগ্রাফ রূপান্তর বিভাগ দেখুন. এছাড়াও, tf.data API জেনারেটর প্যাটার্ন বাস্তবায়নে সাহায্য করতে পারে:

@tf.function
def good_consume_next(iterator):
  # This is ok, iterator is a tf.data.Iterator
  tf.print("Value:", next(iterator))

ds = tf.data.Dataset.from_tensor_slices([1, 2, 3])
iterator = iter(ds)
good_consume_next(iterator)
good_consume_next(iterator)
good_consume_next(iterator)
Value: 1
Value: 2
Value: 3

একটি tf.function এর সমস্ত আউটপুট অবশ্যই রিটার্ন মান হতে হবে

tf.Variable s বাদ দিয়ে, একটি tf.function এর সমস্ত আউটপুট ফেরত দিতে হবে। রিটার্ন মানের মধ্যে না গিয়ে একটি ফাংশন থেকে যেকোনো টেনসর সরাসরি অ্যাক্সেস করার চেষ্টা করা "লিক" এর কারণ।

উদাহরণস্বরূপ, নীচের ফাংশনটি পাইথন গ্লোবাল x এর মাধ্যমে টেনসর a কে "লিক" করে:

x = None

@tf.function
def leaky_function(a):
  global x
  x = a + 1  # Bad - leaks local tensor
  return a + 2

correct_a = leaky_function(tf.constant(1))

print(correct_a.numpy())  # Good - value obtained from function's returns
try:
  x.numpy()  # Bad - tensor leaked from inside the function, cannot be used here
except AttributeError as expected:
  print(expected)
3
'Tensor' object has no attribute 'numpy'

এটি সত্য এমনকি যদি ফাঁস হওয়া মানটিও ফেরত দেওয়া হয়:

@tf.function
def leaky_function(a):
  global x
  x = a + 1  # Bad - leaks local tensor
  return x  # Good - uses local tensor

correct_a = leaky_function(tf.constant(1))

print(correct_a.numpy())  # Good - value obtained from function's returns
try:
  x.numpy()  # Bad - tensor leaked from inside the function, cannot be used here
except AttributeError as expected:
  print(expected)

@tf.function
def captures_leaked_tensor(b):
  b += x  # Bad - `x` is leaked from `leaky_function`
  return b

with assert_raises(TypeError):
  captures_leaked_tensor(tf.constant(2))
2
'Tensor' object has no attribute 'numpy'
Caught expected exception 
  <class 'TypeError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/566849597.py", line 21, in <module>
    captures_leaked_tensor(tf.constant(2))
TypeError: Originated from a graph execution error.

The graph execution error is detected at a node built at (most recent call last):
>>>  File /usr/lib/python3.7/runpy.py, line 193, in _run_module_as_main
>>>  File /usr/lib/python3.7/runpy.py, line 85, in _run_code
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py, line 16, in <module>
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/traitlets/config/application.py, line 846, in launch_instance
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelapp.py, line 677, in start
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tornado/platform/asyncio.py, line 199, in start
>>>  File /usr/lib/python3.7/asyncio/base_events.py, line 534, in run_forever
>>>  File /usr/lib/python3.7/asyncio/base_events.py, line 1771, in _run_once
>>>  File /usr/lib/python3.7/asyncio/events.py, line 88, in _run
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 457, in dispatch_queue
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 446, in process_one
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 353, in dispatch_shell
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/kernelbase.py, line 648, in execute_request
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/ipkernel.py, line 353, in do_execute
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel/zmqshell.py, line 533, in run_cell
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 2902, in run_cell
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 2947, in _run_cell
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/async_helpers.py, line 68, in _pseudo_sync_runner
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 3173, in run_cell_async
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 3364, in run_ast_nodes
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/IPython/core/interactiveshell.py, line 3444, in run_code
>>>  File /tmp/ipykernel_26244/566849597.py, line 7, in <module>
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/traceback_utils.py, line 150, in error_handler
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 910, in __call__
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 958, in _call
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 781, in _initialize
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py, line 3157, in _get_concrete_function_internal_garbage_collected
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py, line 3557, in _maybe_define_function
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py, line 3402, in _create_graph_function
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py, line 1143, in func_graph_from_py_func
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py, line 672, in wrapped_fn
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py, line 1125, in autograph_handler
>>>  File /tmp/ipykernel_26244/566849597.py, line 4, in leaky_function
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/traceback_utils.py, line 150, in error_handler
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/math_ops.py, line 1383, in binary_op_wrapper
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/traceback_utils.py, line 150, in error_handler
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/util/dispatch.py, line 1096, in op_dispatch_handler
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/math_ops.py, line 1737, in _add_dispatch
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/gen_math_ops.py, line 476, in add_v2
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/op_def_library.py, line 746, in _apply_op_helper
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/func_graph.py, line 691, in _create_op_internal
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/ops.py, line 3705, in _create_op_internal
>>>  File /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/framework/ops.py, line 2101, in __init__

Error detected in node 'add' defined at: File "/tmp/ipykernel_26244/566849597.py", line 4, in leaky_function

TypeError: tf.Graph captured an external symbolic tensor. The symbolic tensor 'add:0' created by node 'add' is captured by the tf.Graph being executed as an input. But a tf.Graph is not allowed to take symbolic tensors from another graph as its inputs. Make sure all captured inputs of the executing tf.Graph are not symbolic tensors. Use return values, explicit Python locals or TensorFlow collections to access it. Please see https://www.tensorflow.org/guide/function#all_outputs_of_a_tffunction_must_be_return_values for more information.

সাধারণত, যখন আপনি পাইথন স্টেটমেন্ট বা ডেটা স্ট্রাকচার ব্যবহার করেন তখন এই ধরনের ফাঁস হয়। দুর্গম টেনসর লিক করা ছাড়াও, এই ধরনের বিবৃতিগুলি সম্ভবত ভুল কারণ সেগুলি পাইথনের পার্শ্বপ্রতিক্রিয়া হিসাবে গণনা করা হয় এবং প্রতিটি ফাংশন কলে কার্যকর করার গ্যারান্টি দেওয়া হয় না।

স্থানীয় টেনসরগুলি ফাঁস করার সাধারণ উপায়গুলির মধ্যে একটি বহিরাগত পাইথন সংগ্রহ বা একটি বস্তুর পরিবর্তন করা অন্তর্ভুক্ত:

class MyClass:

  def __init__(self):
    self.field = None

external_list = []
external_object = MyClass()

def leaky_function():
  a = tf.constant(1)
  external_list.append(a)  # Bad - leaks tensor
  external_object.field = a  # Bad - leaks tensor

পুনরাবৃত্ত tf. ফাংশন সমর্থিত নয়

রিকার্সিভ Function সমর্থিত নয় এবং অসীম লুপ হতে পারে। উদাহরণ স্বরূপ,

@tf.function
def recursive_fn(n):
  if n > 0:
    return recursive_fn(n - 1)
  else:
    return 1

with assert_raises(Exception):
  recursive_fn(tf.constant(5))  # Bad - maximum recursion error.
Caught expected exception 
  <class 'Exception'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/2233998312.py", line 9, in <module>
    recursive_fn(tf.constant(5))  # Bad - maximum recursion error.
tensorflow.python.autograph.impl.api.StagingError: in user code:

    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 4, in recursive_fn  *
        return recursive_fn(n - 1)
    File "/tmp/ipykernel_26244/2233998312.py", line 3, in recursive_fn  *
        if n > 0:
    File "/usr/lib/python3.7/abc.py", line 139, in __instancecheck__
        return _abc_instancecheck(cls, instance)

    RecursionError: maximum recursion depth exceeded while calling a Python object

এমনকি যদি একটি পুনরাবৃত্ত Function কাজ করে বলে মনে হয়, পাইথন ফাংশনটি একাধিকবার ট্রেস করা হবে এবং কার্যক্ষমতার প্রভাব থাকতে পারে। উদাহরণ স্বরূপ,

@tf.function
def recursive_fn(n):
  if n > 0:
    print('tracing')
    return recursive_fn(n - 1)
  else:
    return 1

recursive_fn(5)  # Warning - multiple tracings
tracing
tracing
tracing
tracing
tracing
<tf.Tensor: shape=(), dtype=int32, numpy=1>

জ্ঞাত সমস্যা

যদি আপনার Function সঠিকভাবে মূল্যায়ন না করে, তাহলে ত্রুটিটি এই পরিচিত সমস্যাগুলির দ্বারা ব্যাখ্যা করা যেতে পারে যা ভবিষ্যতে ঠিক করার পরিকল্পনা করা হয়েছে৷

পাইথন গ্লোবাল এবং ফ্রি ভেরিয়েবলের উপর নির্ভর করে

পাইথন আর্গুমেন্টের একটি নতুন মান দিয়ে কল করার সময় ফাংশন একটি নতুন ConcreteFunction Function তৈরি করে। যাইহোক, এটি পাইথন ক্লোজার, গ্লোবাল বা সেই Function অ-লোকালের জন্য তা করে না। যদি Function কলগুলির মধ্যে তাদের মান পরিবর্তিত হয়, তবে Function ট্রেস করার সময় তাদের কাছে থাকা মানগুলি ব্যবহার করবে৷ এটি নিয়মিত পাইথন ফাংশন কিভাবে কাজ করে তার থেকে আলাদা।

সেই কারণে, আপনার একটি কার্যকরী প্রোগ্রামিং শৈলী অনুসরণ করা উচিত যা বাইরের নামগুলি বন্ধ করার পরিবর্তে আর্গুমেন্ট ব্যবহার করে।

@tf.function
def buggy_add():
  return 1 + foo

@tf.function
def recommended_add(foo):
  return 1 + foo

foo = 1
print("Buggy:", buggy_add())
print("Correct:", recommended_add(foo))
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(2, shape=(), dtype=int32)
print("Updating the value of `foo` to 100!")
foo = 100
print("Buggy:", buggy_add())  # Did not change!
print("Correct:", recommended_add(foo))
Updating the value of `foo` to 100!
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(101, shape=(), dtype=int32)

একটি বিশ্বব্যাপী মান আপডেট করার আরেকটি উপায় হল এটিকে একটি tf.Variable করা এবং পরিবর্তে Variable.assign পদ্ধতি ব্যবহার করা।

@tf.function
def variable_add():
  return 1 + foo

foo = tf.Variable(1)
print("Variable:", variable_add())
Variable: tf.Tensor(2, shape=(), dtype=int32)
print("Updating the value of `foo` to 100!")
foo.assign(100)
print("Variable:", variable_add())
Updating the value of `foo` to 100!
Variable: tf.Tensor(101, shape=(), dtype=int32)

পাইথন বস্তুর উপর নির্ভর করে

tf.function এ আর্গুমেন্ট হিসেবে পাইথন অবজেক্ট পাস করার সুপারিশে বেশ কিছু পরিচিত সমস্যা রয়েছে, যেগুলো ভবিষ্যতে ঠিক করা হবে বলে আশা করা হচ্ছে। সাধারণভাবে, আপনি যদি একটি Python আদিম বা tf.nest -সামঞ্জস্যপূর্ণ কাঠামো একটি যুক্তি হিসাবে ব্যবহার করেন বা একটি Function একটি বস্তুর একটি ভিন্ন উদাহরণে পাস করেন তবে আপনি ধারাবাহিক ট্রেসিংয়ের উপর নির্ভর করতে পারেন। যাইহোক, Function একটি নতুন ট্রেস তৈরি করবে না যখন আপনি একই বস্তুটি পাস করেন এবং শুধুমাত্র এর বৈশিষ্ট্যগুলি পরিবর্তন করেন

class SimpleModel(tf.Module):
  def __init__(self):
    # These values are *not* tf.Variables.
    self.bias = 0.
    self.weight = 2.

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

simple_model = SimpleModel()
x = tf.constant(10.)
print(evaluate(simple_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
simple_model.bias += 5.0
print(evaluate(simple_model, x))  # Didn't change :(
Adding bias!
tf.Tensor(20.0, shape=(), dtype=float32)

মডেলের আপডেট করা উদাহরণ মূল্যায়ন করতে একই Function ব্যবহার করা বগি হবে কারণ আপডেট হওয়া মডেলের মূল মডেলের মতো একই ক্যাশে কী রয়েছে।

সেই কারণে, পরিবর্তনযোগ্য অবজেক্ট অ্যাট্রিবিউটের উপর নির্ভর করে বা নতুন বস্তু তৈরি করা এড়াতে আপনাকে আপনার Function লিখতে সুপারিশ করা হচ্ছে।

যদি তা সম্ভব না হয়, একটি সমাধান হল নতুন Function তৈরি করা প্রতিবার যখন আপনি আপনার অবজেক্টকে ফোর্স রিট্রেসিং করার জন্য পরিবর্তন করবেন:

def evaluate(model, x):
  return model.weight * x + model.bias

new_model = SimpleModel()
evaluate_no_bias = tf.function(evaluate).get_concrete_function(new_model, x)
# Don't pass in `new_model`, `Function` already captured its state during tracing.
print(evaluate_no_bias(x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
new_model.bias += 5.0
# Create new Function and ConcreteFunction since you modified new_model.
evaluate_with_bias = tf.function(evaluate).get_concrete_function(new_model, x)
print(evaluate_with_bias(x)) # Don't pass in `new_model`.
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

যেহেতু রিট্রেসিং ব্যয়বহুল হতে পারে , আপনি অবজেক্ট অ্যাট্রিবিউট হিসাবে tf.Variable s ব্যবহার করতে পারেন, যা রিট্রেসের প্রয়োজন ছাড়াই অনুরূপ প্রভাবের জন্য মিউটেট করা যেতে পারে (কিন্তু পরিবর্তিত নয়, সাবধান!)।

class BetterModel:

  def __init__(self):
    self.bias = tf.Variable(0.)
    self.weight = tf.Variable(2.)

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

better_model = BetterModel()
print(evaluate(better_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
better_model.bias.assign_add(5.0)  # Note: instead of better_model.bias += 5
print(evaluate(better_model, x))  # This works!
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

tf. ভেরিয়েবল তৈরি করা

Function শুধুমাত্র tf.Variable . ভ্যারিয়েবল s সমর্থন করে যা প্রথম কলে একবার তৈরি করা হয় এবং পরবর্তী ফাংশন কলগুলিতে পুনরায় ব্যবহার করা হয়। নীচের কোড স্নিপেট প্রতিটি ফাংশন কলে একটি নতুন tf.Variable . ভেরিয়েবল তৈরি করবে, যার ফলে একটি ValueError ব্যতিক্রম হবে।

উদাহরণ:

@tf.function
def f(x):
  v = tf.Variable(1.0)
  return v

with assert_raises(ValueError):
  f(1.0)
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/3018268426.py", line 7, in <module>
    f(1.0)
ValueError: in user code:

    File "/tmp/ipykernel_26244/3018268426.py", line 3, in f  *
        v = tf.Variable(1.0)

    ValueError: tf.function only supports singleton tf.Variables created on the first call. Make sure the tf.Variable is only created once or created outside tf.function. See https://www.tensorflow.org/guide/function#creating_tfvariables for more information.

এই সীমাবদ্ধতাকে ঘিরে কাজ করার জন্য ব্যবহৃত একটি সাধারণ প্যাটার্ন হল Python None মান দিয়ে শুরু করা, তারপর শর্তসাপেক্ষে tf.Variable . ভ্যারিয়েবল তৈরি করুন যদি মানটি None না হয়:

class Count(tf.Module):
  def __init__(self):
    self.count = None

  @tf.function
  def __call__(self):
    if self.count is None:
      self.count = tf.Variable(0)
    return self.count.assign_add(1)

c = Count()
print(c())
print(c())
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

একাধিক কেরা অপ্টিমাইজারের সাথে ব্যবহার করা

আপনি ValueError এর সম্মুখীন হতে পারেন ValueError: tf.function only supports singleton tf.Variables created on the first call. একটি tf.function সহ একাধিক কেরাস অপ্টিমাইজার ব্যবহার করার সময়। এই ত্রুটিটি ঘটে কারণ অপ্টিমাইজাররা অভ্যন্তরীণভাবে tf.Variables . ভেরিয়েবল তৈরি করে যখন তারা প্রথমবার গ্রেডিয়েন্ট প্রয়োগ করে।

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

@tf.function
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

train_step(w, x, y, opt1)
print("Calling `train_step` with different optimizer...")
with assert_raises(ValueError):
  train_step(w, x, y, opt2)
Calling `train_step` with different optimizer...
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_26244/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_26244/3167358578.py", line 18, in <module>
    train_step(w, x, y, opt2)
ValueError: in user code:

    File "/tmp/ipykernel_26244/3167358578.py", line 9, in train_step  *
        optimizer.apply_gradients(zip(gradients, [w]))
    File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 639, in apply_gradients  **
        self._create_all_weights(var_list)
    File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 828, in _create_all_weights
        _ = self.iterations
    File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 835, in __getattribute__
        return super(OptimizerV2, self).__getattribute__(name)
    File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 995, in iterations
        aggregation=tf.VariableAggregation.ONLY_FIRST_REPLICA)
    File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py", line 1202, in add_weight
        aggregation=aggregation)
    File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/engine/base_layer_utils.py", line 129, in make_variable
        shape=variable_shape if variable_shape else None)

    ValueError: tf.function only supports singleton tf.Variables created on the first call. Make sure the tf.Variable is only created once or created outside tf.function. See https://www.tensorflow.org/guide/function#creating_tfvariables for more information.

প্রশিক্ষণের সময় আপনার যদি অপ্টিমাইজার পরিবর্তন করার প্রয়োজন হয়, একটি সমাধান হল প্রতিটি অপ্টিমাইজারের জন্য একটি নতুন Function তৈরি করা, সরাসরি ConcreteFunction ফাংশনকে কল করা।

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

# Not a tf.function.
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

# Make a new Function and ConcreteFunction for each optimizer.
train_step_1 = tf.function(train_step).get_concrete_function(w, x, y, opt1)
train_step_2 = tf.function(train_step).get_concrete_function(w, x, y, opt2)
for i in range(10):
  if i % 2 == 0:
    train_step_1(w, x, y) # `opt1` is not used as a parameter. 
  else:
    train_step_2(w, x, y) # `opt2` is not used as a parameter.

একাধিক Keras মডেল ব্যবহার করে

এছাড়াও আপনি ValueError এর সম্মুখীন হতে পারেন ValueError: tf.function only supports singleton tf.Variables created on the first call. একই Function বিভিন্ন মডেলের উদাহরণ পাস করার সময়।

এই ত্রুটিটি ঘটে কারণ কেরাস মডেল (যার ইনপুট আকৃতি সংজ্ঞায়িত করা হয় না ) এবং কেরাস স্তরগুলি যখন প্রথম কল করা হয় তখন তারা tf.Variables . ভেরিয়েবল তৈরি করে। আপনি একটি Function ভিতরে সেই ভেরিয়েবলগুলি শুরু করার চেষ্টা করতে পারেন, যা ইতিমধ্যেই বলা হয়েছে। এই ত্রুটি এড়াতে, মডেল প্রশিক্ষণের আগে সমস্ত ওজন শুরু করতে model.build(input_shape) কল করার চেষ্টা করুন।

আরও পড়া

কিভাবে একটি Function রপ্তানি এবং লোড করতে হয় সে সম্পর্কে জানতে, SavedModel গাইড দেখুন। ট্রেসিংয়ের পরে সঞ্চালিত গ্রাফ অপ্টিমাইজেশন সম্পর্কে আরও জানতে, গ্র্যাপলার গাইড দেখুন। কীভাবে আপনার ডেটা পাইপলাইন অপ্টিমাইজ করবেন এবং আপনার মডেল প্রোফাইল করবেন তা জানতে, প্রোফাইলার গাইড দেখুন।