ML Community Day คือวันที่ 9 พฤศจิกายน! ร่วมกับเราสำหรับการปรับปรุงจาก TensorFlow, JAX และอื่น ๆ เรียนรู้เพิ่มเติม

ข้อมูลเบื้องต้นเกี่ยวกับกราฟและ 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 ใช้กราฟเป็นรูปแบบสำหรับ รุ่นที่บันทึกไว้ เมื่อมันส่งออกพวกเขาจากงูใหญ่

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

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

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

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

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

ติดตั้ง

import tensorflow as tf
import timeit
from datetime import datetime

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

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

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 และตรรกะหลามเช่น if-then เบ็ดเตล็ดลูป, break , return , continue , และอื่น ๆ ในขณะที่การดำเนินงาน TensorFlow ถูกจับได้อย่างง่ายดายโดย tf.Graph ต้องการตรรกะหลามเฉพาะจะได้รับการขั้นตอนพิเศษเพื่อที่จะกลายเป็นส่วนหนึ่งของกราฟ tf.function ใช้ห้องสมุดที่เรียกว่าลายเซ็น ( 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
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    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
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_1"
      }
    }
    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
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_2"
      }
    }
    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
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_3"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const_3:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    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
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_4"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond/Const_4:output:0"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    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
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond_identity_1_x"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    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: 808
  min_consumer: 12
}

ส่วนใหญ่เวลาที่ tf.function จะทำงานโดยไม่มีการพิจารณาเป็นพิเศษ แต่มีบางประการและ คู่มือ tf.function สามารถช่วยที่นี่เช่นเดียวกับ การอ้างอิง Autograph สมบูรณ์

Polymorphism: หนึ่ง Function กราฟหลาย

tf.Graph มีความเชี่ยวชาญในชนิดที่เฉพาะเจาะจงของปัจจัยการผลิต (ตัวอย่างเช่นเทนเซอร์ที่มีเฉพาะ dtype หรือวัตถุที่มีเดียวกัน id() )

ทุกครั้งที่คุณเรียกใช้ Function ใหม่ dtypes และรูปทรงในการขัดแย้งของ Function สร้างใหม่ tf.Graph สำหรับข้อโต้แย้งใหม่ dtypes และรูปร่างของ tf.Graph ปัจจัยการผลิต 's เป็นที่รู้จักกันในฐานะที่เป็นลายเซ็นของการป้อนข้อมูลหรือเพียงแค่ลายเซ็น

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

เพื่อให้ห่างไกลคุณได้เรียนรู้วิธีการแปลงฟังก์ชั่นหลามเข้าไปในกราฟง่ายๆโดยการใช้ 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([6 1 7 8 0], shape=(5,), dtype=int32)
tf.Tensor([6 0 1 8 6], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=14>

เพื่อตรวจสอบว่าคุณ Function 's กราฟจะทำการคำนวณเช่นเดียวกับฟังก์ชั่นหลามเทียบเท่า, คุณสามารถทำให้มันรันกระหายกับ 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=14>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)

แต่ Function สามารถทำงานแตกต่างกันภายใต้กราฟและการดำเนินการกระตือรือร้น งูหลาม print ฟังก์ชั่นเป็นตัวอย่างหนึ่งของวิธีการเหล่านี้สองโหมดที่แตกต่างกัน ตรวจสอบ Let 's จากสิ่งที่เกิดขึ้นเมื่อคุณใส่ 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 ทำงานรหัสเดิมเพื่อสร้างกราฟในกระบวนการที่เรียกว่า "การติดตาม" ติดตามการดำเนินงานจับ 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.print
    • การดำเนินงานการแก้จุดบกพร่องเช่นฟังก์ชั่นยืนยันใน tf.debugging
    • การกลายพันธุ์ของ tf.Variable

ลักษณะการทำงานนี้มักเรียกว่า "การดำเนินการที่ไม่เข้มงวด" และแตกต่างจากการดำเนินการที่กระตือรือร้น ซึ่งดำเนินการตามขั้นตอนของการดำเนินการของโปรแกรมทั้งหมด จำเป็นหรือไม่

โดยเฉพาะอย่างยิ่ง การตรวจสอบข้อผิดพลาดรันไทม์ไม่นับเป็นผลที่สังเกตได้ หากการดำเนินการถูกข้ามเนื่องจากไม่จำเป็น จะไม่สามารถทำให้เกิดข้อผิดพลาดรันไทม์ได้

ในตัวอย่างต่อไปที่ "ไม่จำเป็น" การดำเนินการ tf.gather คือข้ามในระหว่างการดำเนินกราฟดังนั้นข้อผิดพลาด runtime 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 และวัตถุ 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.0122516460000384
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.6084441319999883

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 คู่มือ