ข้อมูลเบื้องต้นเกี่ยวกับกราฟและ tf.function

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

ภาพรวม

คู่มือนี้กล่าวถึง TensorFlow และ Keras เพื่อสาธิตวิธีการทำงานของ TensorFlow หากคุณต้องการเริ่มต้นใช้งาน Keras ทันที ให้ตรวจสอบ ชุดคู่มือ Keras

ในคู่มือนี้ คุณจะได้เรียนรู้วิธีที่ TensorFlow อนุญาตให้คุณทำการเปลี่ยนแปลงง่ายๆ ในโค้ดของคุณเพื่อรับกราฟ วิธีการจัดเก็บและแสดงกราฟ และวิธีที่คุณจะใช้เพื่อเร่งโมเดลของคุณ

นี่คือภาพรวมภาพรวมที่ครอบคลุมวิธีที่ tf.function ช่วยให้คุณเปลี่ยนจากการดำเนินการอย่างกระตือรือร้นเป็นการเรียกใช้กราฟได้ สำหรับข้อมูลจำเพาะที่สมบูรณ์ยิ่งขึ้นของ tf.function ให้ไปที่ คู่มือ tf.function

กราฟคืออะไร?

ในคู่มือสามข้อก่อนหน้านี้ คุณใช้งาน TensorFlow อย่างกระตือรือร้น ซึ่งหมายความว่าการดำเนินการของ TensorFlow ดำเนินการโดย Python การดำเนินการโดยการดำเนินการ และส่งคืนผลลัพธ์กลับไปยัง Python

ในขณะที่การดำเนินการอย่างกระตือรือร้นนั้นมีข้อดีที่เป็นเอกลักษณ์หลายประการ การดำเนินการด้วยกราฟช่วยให้พกพาออกนอก Python และมีแนวโน้มที่จะให้ประสิทธิภาพที่ดีกว่า การดำเนินการกราฟ หมายความว่าการคำนวณเทนเซอร์จะดำเนินการเป็น กราฟ TensorFlow ซึ่งบางครั้งเรียกว่า tf.Graph หรือเพียงแค่ "กราฟ"

กราฟคือโครงสร้างข้อมูลที่มีชุดของออบเจกต์ tf.Operation ซึ่งแสดงถึงหน่วยของการคำนวณ และวัตถุ tf.Tensor ซึ่งเป็นตัวแทนของหน่วยข้อมูลที่ไหลระหว่างการดำเนินการ มีการกำหนดไว้ในบริบท tf.Graph เนื่องจากกราฟเหล่านี้เป็นโครงสร้างข้อมูล จึงสามารถบันทึก เรียกใช้ และกู้คืนได้ทั้งหมดโดยไม่ต้องใช้โค้ด Python ดั้งเดิม

นี่คือสิ่งที่กราฟ TensorFlow แสดงถึงโครงข่ายประสาทเทียมสองชั้น เมื่อแสดงเป็นภาพใน TensorBoard

กราฟ TensorFlow อย่างง่าย

ประโยชน์ของกราฟ

ด้วยกราฟ คุณจะมีความยืดหยุ่นอย่างมาก คุณสามารถใช้กราฟ TensorFlow ในสภาพแวดล้อมที่ไม่มีล่าม Python เช่น แอปพลิเคชันมือถือ อุปกรณ์ฝังตัว และเซิร์ฟเวอร์แบ็กเอนด์ TensorFlow ใช้กราฟเป็นรูปแบบสำหรับ โมเดลที่บันทึกไว้ เมื่อส่งออกจาก Python

กราฟยังได้รับการปรับให้เหมาะสมอย่างง่ายดาย ทำให้คอมไพเลอร์ทำการแปลงเช่น:

  • อนุมานค่าเทนเซอร์แบบคงที่โดยการพับโหนดคงที่ในการคำนวณของคุณ ("การพับคงที่")
  • แยกส่วนย่อยของการคำนวณที่เป็นอิสระและแยกส่วนย่อยระหว่างเธรดหรืออุปกรณ์
  • ลดความซับซ้อนของการดำเนินการเลขคณิตโดยกำจัดนิพจน์ย่อยทั่วไป

มีระบบการเพิ่มประสิทธิภาพทั้งหมด Grappler เพื่อดำเนินการนี้และการเร่งความเร็วอื่นๆ

กล่าวโดยย่อ กราฟมีประโยชน์อย่างยิ่ง และให้ TensorFlow ของคุณทำงาน ได้อย่างรวดเร็ว ทำงาน พร้อมกัน และทำงานอย่างมีประสิทธิภาพ บนอุปกรณ์หลายเครื่อง

อย่างไรก็ตาม คุณยังต้องการกำหนดโมเดลการเรียนรู้ของเครื่อง (หรือการคำนวณอื่นๆ) ใน Python เพื่อความสะดวก จากนั้นจึงสร้างกราฟโดยอัตโนมัติเมื่อคุณต้องการ

ติดตั้ง

import tensorflow as tf
import timeit
from datetime import datetime

การใช้ประโยชน์จากกราฟ

คุณสร้างและเรียกใช้กราฟใน TensorFlow โดยใช้ tf.function ไม่ว่าจะเป็นการโทรโดยตรงหรือเป็นมัณฑนากร tf.function ใช้ฟังก์ชันปกติเป็นอินพุตและส่งกลับ Function Function คือ Python callable ที่สร้างกราฟ TensorFlow จากฟังก์ชัน Python คุณใช้ Function ในลักษณะเดียวกับที่เทียบเท่ากับ Python

# 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 ในเวลาไม่นาน

การแปลงฟังก์ชัน Python เป็นกราฟ

ฟังก์ชันใดๆ ที่คุณเขียนด้วย TensorFlow จะประกอบด้วยการดำเนินการ TF ในตัวและตรรกะ Python เช่น if-then clauses, loops, break , return , continue และอีกมากมาย แม้ว่าการทำงานของ TensorFlow จะถูกจับโดย tf.Graph อย่างง่ายดาย ตรรกะเฉพาะของ Python จำเป็นต้องผ่านขั้นตอนพิเศษเพื่อที่จะได้เป็นส่วนหนึ่งของกราฟ tf.function ใช้ไลบรารีชื่อ AutoGraph ( tf.autograph ) เพื่อแปลงโค้ด Python เป็นโค้ดที่สร้างกราฟ

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 ฉบับสมบูรณ์

Polymorphism: หนึ่ง 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 จึงเป็นแบบ polymorphic ซึ่งช่วยให้สามารถรองรับประเภทอินพุตได้มากกว่าที่ 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

จนถึงตอนนี้ คุณได้เรียนรู้วิธีแปลงฟังก์ชัน Python เป็นกราฟได้ง่ายๆ โดยใช้ tf.function เป็นมัณฑนากรหรือ wrapper แต่ในทางปฏิบัติ การให้ 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 ของคุณใช้การคำนวณแบบเดียวกับฟังก์ชัน Python ที่เทียบเท่ากัน คุณสามารถทำให้มันทำงานได้อย่างรวดเร็วด้วย 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 Python เป็นตัวอย่างหนึ่งของความแตกต่างระหว่างสองโหมดนี้ มาดูกันว่าจะเกิดอะไรขึ้นเมื่อคุณแทรกคำสั่ง 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 จะไม่ถูกบันทึกในกราฟ จากนั้น กราฟนั้นจะถูกดำเนินการสำหรับการโทรทั้งสามครั้ง โดยที่ไม่ต้องเรียกใช้โค้ด Python อีก เลย

ในการตรวจสอบสุขภาพจิต ให้ปิดการดำเนินการกราฟเพื่อเปรียบเทียบ:

# 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 เป็น ผลข้างเคียงของ Python และมีความแตกต่างอื่นๆ ที่คุณควรทราบเมื่อแปลงฟังก์ชันเป็น Function เรียนรู้เพิ่มเติมในส่วน ข้อจำกัด ของคู่มือประสิทธิภาพที่ ดีขึ้นด้วย tf.function

การดำเนินการไม่เข้มงวด

การดำเนินการกราฟจะดำเนินการเฉพาะการดำเนินการที่จำเป็นในการสร้างผลกระทบที่สังเกตได้ ซึ่งรวมถึง:

  • ค่าส่งคืนของฟังก์ชัน
  • เอกสารผลข้างเคียงที่รู้จักกันดีเช่น:
    • การดำเนินการอินพุต/เอาต์พุต เช่น tf.print
    • การดำเนินการดีบัก เช่น ฟังก์ชันยืนยันใน 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 นอกฟังก์ชัน Python และแก้ไขที่ด้านใน เช่นเดียวกับวัตถุที่ใช้ tf.Variable เช่น keras.layers , keras.Model s และ tf.optimizers
  • หลีกเลี่ยงการเขียนฟังก์ชันที่ ขึ้นอยู่กับตัวแปร Python ภายนอก ยกเว้น 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)

อาร์กิวเมนต์ Python ใหม่จะทริกเกอร์การสร้างกราฟใหม่เสมอ ดังนั้นจึงมีการติดตามเพิ่มเติม

ขั้นตอนถัดไป

คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับ tf.function ได้ในหน้าอ้างอิง API และโดยปฏิบัติตามคำแนะนำ ประสิทธิภาพที่ ดีขึ้นด้วย tf.function