TensorFlow 1.x so với TensorFlow 2 - Hành vi và API

Xem trên TensorFlow.org Chạy trong Google Colab Xem trên GitHub Tải xuống sổ ghi chép

Về cơ bản, TensorFlow 2 tuân theo một mô hình lập trình về cơ bản khác với TF1.x.

Hướng dẫn này mô tả sự khác biệt cơ bản giữa TF1.x và TF2 về hành vi và API cũng như cách tất cả những điều này liên quan đến hành trình di chuyển của bạn.

Tóm tắt cấp cao về những thay đổi lớn

Về cơ bản, TF1.x và TF2 sử dụng một tập hợp các hành vi thời gian chạy khác nhau xung quanh việc thực thi (háo hức trong TF2), các biến, luồng điều khiển, hình dạng tensor và so sánh bình đẳng tensor. Để tương thích với TF2, mã của bạn phải tương thích với tập hợp đầy đủ các hành vi TF2. Trong quá trình di chuyển, bạn có thể bật hoặc tắt hầu hết các hành vi này riêng lẻ thông qua tf.compat.v1.enable_* hoặc tf.compat.v1.disable_* . Một ngoại lệ là việc loại bỏ các bộ sưu tập, đây là một tác dụng phụ của việc kích hoạt / vô hiệu hóa việc thực thi háo hức.

Ở cấp độ cao, TensorFlow 2:

  • Loại bỏ các API thừa .
  • Làm cho các API nhất quán hơn - ví dụ: RNN hợp nhất và Trình tối ưu hóa hợp nhất.
  • Thích các chức năng hơn các phiên và tích hợp tốt hơn với thời gian chạy Python với việc thực thi Eager được bật theo mặc định cùng với tf.function . cung cấp các phụ thuộc điều khiển tự động cho đồ thị và biên dịch.
  • Không dùng các bộ sưu tập đồ thị toàn cầu.
  • Thay đổi ngữ nghĩa đồng thời của biến bằng cách sử dụng ResourceVariables trên ReferenceVariables .
  • Hỗ trợ luồng điều khiển dựa trên chức năng và có thể phân biệt (Control Flow v2).
  • Đơn giản hóa API TensorShape để giữ int s thay vì các đối tượng tf.compat.v1.Dimension .
  • Cập nhật cơ học bình đẳng tensor. Trong TF1.x, toán tử == trên tensors và biến kiểm tra sự bình đẳng tham chiếu đối tượng. Trong TF2, nó kiểm tra sự bình đẳng giá trị. Ngoài ra, tensors / biến không còn có thể băm được nữa, nhưng bạn có thể nhận được các tham chiếu đối tượng có thể băm tới chúng thông qua var.ref() nếu bạn cần sử dụng chúng theo bộ hoặc dưới dạng khóa dict .

Các phần bên dưới cung cấp thêm một số ngữ cảnh về sự khác biệt giữa TF1.x và TF2. Để tìm hiểu thêm về quy trình thiết kế đằng sau TF2, hãy đọc RFCtài liệu thiết kế .

Dọn dẹp API

Nhiều API đã biến mất hoặc được di chuyển trong TF2. Một số thay đổi lớn bao gồm xóa tf.app , tf.flagstf.logging để hỗ trợ absl-py mã nguồn mở hiện nay, rehoming các dự án tồn tại trong tf.contrib và dọn dẹp không gian tên tf.* Chính bằng cách chuyển các hàm ít được sử dụng hơn vào các gói con như tf.math . Một số API đã được thay thế bằng các API tương đương - tf.summary , tf.keras.metricstf.keras.optimizers .

tf.compat.v1 : Điểm cuối API kế thừa và tương thích

Các ký hiệu trong không gian tên tf.compattf.compat.v1 không được coi là API TF2. Các không gian tên này cho thấy sự kết hợp của các ký hiệu tương thích, cũng như các điểm cuối API kế thừa từ TF 1.x. Chúng nhằm hỗ trợ chuyển đổi từ TF1.x sang TF2. Tuy nhiên, vì không có API compat.v1 nào là API TF2 tự nhiên, không sử dụng chúng để viết mã TF2 hoàn toàn mới.

Các ký hiệu tf.compat.v1 riêng lẻ có thể tương thích với TF2 vì chúng tiếp tục hoạt động ngay cả khi các hành vi TF2 được kích hoạt (chẳng hạn như tf.compat.v1.losses.mean_squared_error ), trong khi các ký hiệu khác không tương thích với TF2 (chẳng hạn như tf.compat.v1.metrics.accuracy ). Nhiều ký hiệu compat.v1 (mặc dù không phải tất cả) chứa thông tin di chuyển dành riêng trong tài liệu của chúng để giải thích mức độ tương thích của chúng với các hành vi TF2, cũng như cách di chuyển chúng sang các API TF2.

Tập lệnh nâng cấp TF2 có thể ánh xạ nhiều ký hiệu API compat.v1 các API TF2 tương đương trong trường hợp chúng là bí danh hoặc có các đối số giống nhau nhưng có thứ tự khác nhau. Bạn cũng có thể sử dụng tập lệnh nâng cấp để tự động đổi tên các API TF1.x.

API kết bạn sai

Có một tập hợp các biểu tượng "bạn giả" được tìm thấy trong không gian tên TF2 tf (không phải trong compat.v1 ) thực sự bỏ qua các hành vi TF2 ẩn và / hoặc không hoàn toàn tương thích với tập hợp đầy đủ các hành vi TF2. Do đó, các API này có khả năng hoạt động sai với mã TF2, có thể theo những cách im lặng.

  • tf.estimator.* : Công cụ ước tính tạo và sử dụng biểu đồ và phiên hoạt động. Do đó, chúng không nên được coi là tương thích với TF2. Nếu mã của bạn đang chạy công cụ ước tính, nó không sử dụng các hành vi TF2.
  • keras.Model.model_to_estimator(...) : Điều này tạo ra một Công cụ ước tính ẩn, như đã đề cập ở trên không tương thích với TF2.
  • tf.Graph().as_default() : Điều này nhập các hành vi đồ thị TF1.x và không tuân theo các hành vi chức năng tf.function . tương thích với TF2 tiêu chuẩn. Mã nhập vào các biểu đồ như thế này thường sẽ chạy chúng qua Phiên và không được coi là tương thích với TF2.
  • tf.feature_column.* Các API của cột tính năng thường dựa vào việc tạo biến tf.compat.v1.get_variable kiểu TF1 và giả định rằng các biến đã tạo sẽ được truy cập thông qua các bộ sưu tập chung. Vì TF2 không hỗ trợ các bộ sưu tập, các API có thể không hoạt động chính xác khi chạy chúng với các hành vi TF2 được kích hoạt.

Các thay đổi API khác

  • TF2 có những cải tiến đáng kể đối với thuật toán vị trí thiết bị khiến việc sử dụng tf.colocate_with không cần thiết. Nếu việc xóa nó làm giảm hiệu suất, vui lòng gửi lỗi .

  • Thay thế tất cả cách sử dụng tf.v1.ConfigProto bằng các chức năng tương đương từ tf.config .

Háo hức thực hiện

TF1.x yêu cầu bạn ghép thủ công một cây cú pháp trừu tượng (biểu đồ) bằng cách thực hiện lệnh gọi tf.* API và sau đó biên dịch cây cú pháp trừu tượng theo cách thủ công bằng cách chuyển một tập hợp các tensor đầu ra và tensors đầu vào cho một lệnh gọi session.run . TF2 thực thi một cách háo hức (giống như Python thường làm) và làm cho đồ thị và phiên giống như chi tiết triển khai.

Một sản phẩm phụ đáng chú ý của việc thực thi háo hức là tf.control_dependencies không còn được yêu cầu nữa, vì tất cả các dòng mã đều thực thi theo thứ tự (trong một tf.function ., mã có tác dụng phụ sẽ thực thi theo thứ tự đã viết).

Không còn hình cầu

TF1.x chủ yếu dựa vào các tập hợp và không gian tên toàn cầu ngầm định. Khi bạn gọi tf.Variable , nó sẽ được đưa vào một bộ sưu tập trong đồ thị mặc định và nó sẽ vẫn ở đó, ngay cả khi bạn mất dấu biến Python trỏ đến nó. Sau đó, bạn có thể khôi phục tf.Variable đó, nhưng chỉ khi bạn biết tên mà nó đã được tạo. Điều này khó thực hiện nếu bạn không kiểm soát được việc tạo biến. Kết quả là, tất cả các loại cơ chế đã phát triển để cố gắng giúp bạn tìm lại các biến của mình và cho các khuôn khổ để tìm các biến do người dùng tạo. Một số trong số này bao gồm: phạm vi biến, tập hợp toàn cục, các phương thức trợ giúp như tf.get_global_steptf.global_variables_initializer , trình tối ưu hóa tính toán ngầm định độ dốc trên tất cả các biến có thể đào tạo, v.v. TF2 loại bỏ tất cả các cơ chế này ( Biến 2.0 RFC ) có lợi cho cơ chế mặc định - bạn theo dõi các biến của mình. Nếu bạn mất dấu tf.Variable , nó sẽ được thu gom.

Yêu cầu theo dõi các biến tạo ra một số công việc bổ sung, nhưng với các công cụ như miếng chêm mô hình hóa và các hành vi như bộ sưu tập biến hướng đối tượng ngầm định trong tf.Module s và tf.keras.layers.Layer s , gánh nặng được giảm thiểu.

Chức năng, không phải phiên

Một cuộc gọi session.run gần giống như một lệnh gọi hàm: bạn chỉ định các đầu vào và hàm sẽ được gọi, và bạn nhận lại một tập hợp các đầu ra. Trong TF2, bạn có thể trang trí một hàm Python bằng cách sử dụng tf.function để đánh dấu nó cho quá trình biên dịch JIT để TensorFlow chạy nó dưới dạng một biểu đồ duy nhất ( Functions 2.0 RFC ). Cơ chế này cho phép TF2 đạt được tất cả các lợi ích của chế độ đồ thị:

  • Hiệu suất: Chức năng có thể được tối ưu hóa (cắt bớt nút, kết hợp hạt nhân, v.v.)
  • Tính di động: Chức năng có thể được xuất / nhập lại ( SavedModel 2.0 RFC ), cho phép bạn sử dụng lại và chia sẻ các chức năng TensorFlow mô-đun.
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)

Với khả năng tự do xen kẽ mã Python và TensorFlow, bạn có thể tận dụng khả năng diễn đạt của Python. Tuy nhiên, TensorFlow di động thực thi trong các ngữ cảnh không có trình thông dịch Python, chẳng hạn như thiết bị di động, C ++ và JavaScript. Để tránh viết lại mã của bạn khi thêm tf.function . function, hãy sử dụng AutoGraph để chuyển đổi một tập hợp con các cấu trúc Python thành các cấu trúc tương đương TensorFlow của chúng:

  • for / while -> tf.while_loop ( breakcontinue được hỗ trợ)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph hỗ trợ lồng các luồng điều khiển tùy ý, giúp bạn có thể triển khai một cách chính xác và ngắn gọn nhiều chương trình ML phức tạp như mô hình tuần tự, học củng cố, vòng huấn luyện tùy chỉnh, v.v.

Thích ứng với Thay đổi Hành vi TF 2.x

Quá trình di chuyển của bạn sang TF2 chỉ hoàn tất khi bạn đã chuyển sang tập hợp đầy đủ các hành vi TF2. Tập hợp đầy đủ các hành vi có thể được bật hoặc tắt thông qua tf.compat.v1.enable_v2_behaviorstf.compat.v1.disable_v2_behaviors . Các phần bên dưới thảo luận chi tiết về từng thay đổi hành vi chính.

Sử dụng tf.function . functions s

Những thay đổi lớn nhất đối với các chương trình của bạn trong quá trình di chuyển có thể đến từ việc chuyển mô hình lập trình cơ bản từ đồ thị và phiên sang thực thi háo hức và tf.function . Tham khảo hướng dẫn di chuyển TF2 để tìm hiểu thêm về cách chuyển từ các API không tương thích với chức năng thực thi và tf.function háo hức sang các API tương thích với chúng.

Dưới đây là một số mẫu chương trình phổ biến không gắn với bất kỳ API nào có thể gây ra sự cố khi chuyển từ tf.Graph s và tf.compat.v1.Session sang thực thi mong muốn với tf.function . functions s.

Mẫu 1: Thao tác đối tượng Python và tạo biến chỉ được thực hiện một lần được chạy nhiều lần

Trong các chương trình TF1.x dựa trên đồ thị và phiên, kỳ vọng thường là tất cả logic Python trong chương trình của bạn sẽ chỉ chạy một lần. Tuy nhiên, với việc thực thi háo hức và tf.function , thật công bằng khi mong đợi rằng logic Python của bạn sẽ được chạy ít nhất một lần, nhưng có thể nhiều lần hơn (nhiều lần một cách háo hức hoặc nhiều lần trên các dấu vết tf.function . function khác nhau). Đôi khi, tf.function thậm chí sẽ theo dõi hai lần trên cùng một đầu vào, gây ra các hành vi không mong muốn (xem Ví dụ 1 và 2). Tham khảo hướng dẫn tf.function . để biết thêm chi tiết.

Ví dụ 1: Tạo biến

Hãy xem xét ví dụ bên dưới, trong đó hàm tạo một biến khi được gọi:

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

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    res = f()
    sess.run(tf.compat.v1.global_variables_initializer())
    sess.run(res)

Tuy nhiên, không cho phép gói hàm trên có chứa tạo biến với tf.function . function một cách ngây thơ. tf.function chỉ hỗ trợ các sáng tạo biến singleton trong lần gọi đầu tiên . Để thực thi điều này, khi tf. function phát hiện việc tạo biến trong lần gọi đầu tiên, nó sẽ cố gắng theo dõi lại và đưa ra lỗi nếu có sự tạo biến trong lần theo dõi thứ hai.

@tf.function
def f():
  print("trace") # This will print twice because the python body is run twice
  v = tf.Variable(1.0)
  return v

try:
  f()
except ValueError as e:
  print(e)

Một cách giải quyết là lưu vào bộ nhớ đệm và sử dụng lại biến sau khi nó được tạo trong lần gọi đầu tiên.

class Model(tf.Module):
  def __init__(self):
    self.v = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    return self.v

m = Model()
m()

Ví dụ 2: Độ căng ngoài phạm vi do tf.function lại chức năng

Như đã trình bày trong Ví dụ 1, tf.function sẽ truy xuất lại khi nó phát hiện ra việc tạo Biến trong lần gọi đầu tiên. Điều này có thể gây thêm nhầm lẫn, bởi vì hai đặc điểm sẽ tạo ra hai biểu đồ. Khi biểu đồ thứ hai từ việc kiểm tra lại cố gắng truy cập một Tensor từ biểu đồ được tạo trong lần truy tìm đầu tiên, Tensorflow sẽ phát sinh lỗi phàn nàn rằng Tensor nằm ngoài phạm vi. Để giải thích tình huống, đoạn mã dưới đây tạo một tập dữ liệu về lần gọi tf.function . function đầu tiên. Điều này sẽ chạy như mong đợi.

class Model(tf.Module):
  def __init__(self):
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print once: only traced once
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return next(it)

m = Model()
m()

Tuy nhiên, nếu chúng tôi cũng cố gắng tạo một biến trong lần gọi tf.function đầu tiên, mã sẽ phát sinh lỗi khiếu nại rằng tập dữ liệu nằm ngoài phạm vi. Điều này là do tập dữ liệu nằm trong biểu đồ đầu tiên, trong khi biểu đồ thứ hai cũng đang cố gắng truy cập nó.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  @tf.function
  def __call__(self):
    print("trace") # This will print twice because the python body is run twice
    if self.v is None:
      self.v = tf.Variable(0)
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
try:
  m()
except TypeError as e:
  print(e) # <tf.Tensor ...> is out of scope and cannot be used here.

Giải pháp dễ hiểu nhất là đảm bảo rằng việc tạo biến và tạo tập dữ liệu đều nằm ngoài lệnh gọi tf.funciton . Ví dụ:

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])
    if self.v is None:
      self.v = tf.Variable(0)

  @tf.function
  def __call__(self):
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Tuy nhiên, đôi khi không thể tránh khỏi việc tạo các biến trong tf.function (chẳng hạn như các biến vị trí trong một số trình tối ưu hóa TF keras ). Tuy nhiên, chúng ta có thể chỉ cần di chuyển việc tạo tập dữ liệu ra bên ngoài lệnh gọi tf.function . function. Lý do mà chúng ta có thể dựa vào điều này là vì tf.function sẽ nhận tập dữ liệu như một đầu vào ngầm định và cả hai đồ thị đều có thể truy cập nó đúng cách.

class Model(tf.Module):
  def __init__(self):
    self.v = None
    self.dataset = None

  def initialize(self):
    if self.dataset is None:
      self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])

  @tf.function
  def __call__(self):
    if self.v is None:
      self.v = tf.Variable(0)
    it = iter(self.dataset)
    return [self.v, next(it)]

m = Model()
m.initialize()
m()

Ví dụ 3: Tái tạo đối tượng Tensorflow không mong muốn do sử dụng chính tả

tf.function hỗ trợ rất kém cho các tác dụng phụ của python như thêm vào danh sách hoặc kiểm tra / thêm vào từ điển. Thông tin chi tiết có trong "Hiệu suất tốt hơn với tf. Chức năng" . Trong ví dụ dưới đây, mã sử dụng từ điển để lưu bộ dữ liệu và trình vòng lặp vào bộ nhớ cache. Đối với cùng một khóa, mỗi lệnh gọi đến mô hình sẽ trả về cùng một trình lặp của tập dữ liệu.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.compat.v1.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = self.datasets[key].make_initializable_iterator()
    return self.iterators[key]

with tf.Graph().as_default():
  with tf.compat.v1.Session() as sess:
    m = Model()
    it = m('a')
    sess.run(it.initializer)
    for _ in range(3):
      print(sess.run(it.get_next())) # prints 1, 2, 3

Tuy nhiên, mô hình trên sẽ không hoạt động như mong đợi trong tf.function . Trong quá trình truy tìm, tf.function . function sẽ bỏ qua tác dụng phụ của python khi thêm vào từ điển. Thay vào đó, nó chỉ ghi nhớ việc tạo tập dữ liệu và trình lặp mới. Kết quả là, mỗi lệnh gọi đến mô hình sẽ luôn trả về một trình lặp mới. Khó nhận thấy vấn đề này trừ khi kết quả số hoặc hiệu suất đủ quan trọng. Do đó, chúng tôi khuyên người dùng nên suy nghĩ kỹ về mã trước khi gói tf.function một cách ngây thơ vào mã python.

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
      self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 1, 1

Chúng ta có thể sử dụng tf.init_scope để nâng tập dữ liệu và tạo trình lặp bên ngoài biểu đồ, nhằm đạt được hành vi mong đợi:

class Model(tf.Module):
  def __init__(self):
    self.datasets = {}
    self.iterators = {}

  @tf.function
  def __call__(self, key):
    if key not in self.datasets:
      # Lifts ops out of function-building graphs
      with tf.init_scope():
        self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])
        self.iterators[key] = iter(self.datasets[key])
    return self.iterators[key]

m = Model()
for _ in range(3):
  print(next(m('a'))) # prints 1, 2, 3

Nguyên tắc chung là tránh dựa vào các tác dụng phụ của Python trong logic của bạn và chỉ sử dụng chúng để gỡ lỗi các dấu vết của bạn.

Ví dụ 4: Thao tác một danh sách Python toàn cục

Mã TF1.x sau đây sử dụng danh sách tổn thất toàn cầu mà nó sử dụng để chỉ duy trì danh sách tổn thất được tạo ra bởi bước đào tạo hiện tại. Lưu ý rằng logic Python thêm lỗ vào danh sách sẽ chỉ được gọi một lần bất kể phiên được chạy bao nhiêu bước huấn luyện.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

g = tf.Graph()
with g.as_default():
  ...
  # initialize all objects
  model = Model()
  optimizer = ...
  ...
  # train step
  model(...)
  total_loss = tf.reduce_sum(all_losses)
  optimizer.minimize(total_loss)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)  

Tuy nhiên, nếu logic Python này được ánh xạ một cách ngây thơ đến TF2 với khả năng thực thi nhanh chóng, danh sách tổn thất toàn cầu sẽ có các giá trị mới được thêm vào nó trong mỗi bước huấn luyện. Điều này có nghĩa là mã bước đào tạo trước đây dự kiến ​​danh sách chỉ chứa các tổn thất từ ​​bước đào tạo hiện tại, giờ đây thực sự thấy danh sách tổn thất từ ​​tất cả các bước đào tạo đã chạy cho đến nay. Đây là một thay đổi hành vi ngoài ý muốn và danh sách sẽ cần được xóa khi bắt đầu mỗi bước hoặc được chuyển thành cục bộ cho bước đào tạo.

all_losses = []

class Model():
  def __call__(...):
    ...
    all_losses.append(regularization_loss)
    all_losses.append(label_loss_a)
    all_losses.append(label_loss_b)
    ...

# initialize all objects
model = Model()
optimizer = ...

def train_step(...)
  ...
  model(...)
  total_loss = tf.reduce_sum(all_losses) # global list is never cleared,
  # Accidentally accumulates sum loss across all training steps
  optimizer.minimize(total_loss)
  ...

Mẫu 2: Một tensor tượng trưng được tính toán lại mỗi bước trong TF1.x vô tình được lưu vào bộ nhớ đệm với giá trị ban đầu khi chuyển sang háo hức.

Mẫu này thường khiến mã của bạn hoạt động sai một cách âm thầm khi thực thi háo hức bên ngoài tf.functions, nhưng lại gây ra lỗi InaccessibleTensorError nếu bộ nhớ đệm giá trị ban đầu xảy ra bên trong tf.function . Tuy nhiên, hãy lưu ý rằng để tránh Mẫu 1 ở trên, bạn thường sẽ vô tình cấu trúc mã của mình theo cách mà bộ nhớ đệm giá trị ban đầu này sẽ xảy ra bên ngoài bất kỳ tf.function . nào có thể gây ra lỗi. Vì vậy, hãy cẩn thận hơn nếu bạn biết chương trình của mình có thể dễ bị ảnh hưởng bởi mẫu này.

Giải pháp chung cho mẫu này là cấu trúc lại mã hoặc sử dụng các lệnh gọi Python nếu cần thiết để đảm bảo giá trị được tính lại mỗi lần thay vì được lưu vào bộ nhớ cache một cách tình cờ.

Ví dụ 1: Tốc độ học / siêu tham số / v.v. lịch trình phụ thuộc vào bước toàn cầu

Trong đoạn mã sau, kỳ vọng là mỗi khi phiên chạy, giá trị global_step gần đây nhất sẽ được đọc và tỷ lệ học tập mới sẽ được tính toán.

g = tf.Graph()
with g.as_default():
  ...
  global_step = tf.Variable(0)
  learning_rate = 1.0 / global_step
  opt = tf.compat.v1.train.GradientDescentOptimizer(learning_rate)
  ...
  global_step.assign_add(1)
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Tuy nhiên, khi cố gắng chuyển sang háo hức, hãy cảnh giác với việc tốc độ học tập chỉ được tính toán một lần sau đó được sử dụng lại, thay vì tuân theo lịch trình đã định:

global_step = tf.Variable(0)
learning_rate = 1.0 / global_step # Wrong! Only computed once!
opt = tf.keras.optimizers.SGD(learning_rate)

def train_step(...):
  ...
  opt.apply_gradients(...)
  global_step.assign_add(1)
  ...

Vì ví dụ cụ thể này là một mẫu phổ biến và các trình tối ưu hóa chỉ nên được khởi tạo một lần thay vì ở mỗi bước đào tạo, các trình tối ưu hóa TF2 hỗ trợ tf.keras.optimizers.schedules.LearningRateSchedule lịch biểu hoặc các bảng gọi Python làm đối số cho tốc độ học và các siêu tham số khác.

Ví dụ 2: Khởi tạo số ngẫu nhiên tượng trưng được chỉ định làm thuộc tính đối tượng sau đó được sử dụng lại thông qua con trỏ vô tình được lưu vào bộ nhớ đệm khi chuyển sang háo hức

Hãy xem xét mô-đun NoiseAdder sau:

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution + input) * self.trainable_scale

Sử dụng nó như sau trong TF1.x sẽ tính toán bộ căng nhiễu ngẫu nhiên mới mỗi khi phiên được chạy:

g = tf.Graph()
with g.as_default():
  ...
  # initialize all variable-containing objects
  noise_adder = NoiseAdder(shape, mean)
  ...
  # computation pass
  x_with_noise = noise_adder.add_noise(x)
  ...
...
sess = tf.compat.v1.Session(graph=g)
sess.run(...)

Tuy nhiên, trong TF2, việc khởi tạo noise_adder ngay từ đầu sẽ khiến noise_distribution chỉ được tính một lần và bị đóng băng cho tất cả các bước huấn luyện:

...
# initialize all variable-containing objects
noise_adder = NoiseAdder(shape, mean) # Freezes `self.noise_distribution`!
...
# computation pass
x_with_noise = noise_adder.add_noise(x)
...

Để khắc phục điều này, hãy cấu trúc lại NoiseAdder để gọi tf.random.normal mỗi khi cần một tensor ngẫu nhiên mới, thay vì tham chiếu đến cùng một đối tượng tensor mỗi lần.

class NoiseAdder(tf.Module):
  def __init__(shape, mean):
    self.noise_distribution = lambda: tf.random.normal(shape=shape, mean=mean)
    self.trainable_scale = tf.Variable(1.0, trainable=True)

  def add_noise(input):
    return (self.noise_distribution() + input) * self.trainable_scale

Mẫu 3: Mã TF1.x trực tiếp dựa vào và tra cứu các tenxơ theo tên

Thông thường các bài kiểm tra mã TF1.x dựa vào việc kiểm tra các hàm hoặc hoạt động nào có trong biểu đồ. Trong một số trường hợp hiếm hoi, mã mô hình hóa cũng sẽ dựa trên những tra cứu này theo tên.

Tên tensor hoàn toàn không được tạo ra khi thực thi bên ngoài chức năng tf.Tensor.name tf.function xảy ra bên trong tf.function . Hãy nhớ rằng các tên được tạo thực tế rất có thể khác nhau giữa TF1.x và TF2 ngay cả trong cùng một tf.function . và đảm bảo API không đảm bảo tính ổn định của các tên được tạo trên các phiên bản TF.

Dạng 4: Phiên TF1.x chỉ chạy một cách có chọn lọc một phần của biểu đồ đã tạo

Trong TF1.x, bạn có thể xây dựng một biểu đồ và sau đó chọn chỉ chạy một cách chọn lọc chỉ một tập hợp con của nó với một phiên bằng cách chọn một tập hợp các đầu vào và đầu ra không yêu cầu chạy mọi hoạt động trong biểu đồ.

Ví dụ: bạn có thể có cả bộ tạo và bộ phân biệt bên trong một đồ thị và sử dụng các lệnh gọi tf.compat.v1.Session.run riêng biệt để xen kẽ giữa việc chỉ đào tạo bộ phân biệt hoặc chỉ đào tạo bộ tạo.

Trong TF2, do sự phụ thuộc điều khiển tự động trong tf.function và thực thi háo hức, không có sự cắt tỉa chọn lọc các dấu vết tf.function . Một đồ thị đầy đủ chứa tất cả các cập nhật biến sẽ được chạy ngay cả khi, ví dụ, chỉ có đầu ra của bộ phân biệt hoặc trình tạo là đầu ra từ hàm tf.function .

Vì vậy, bạn sẽ cần phải sử dụng nhiều tf.function . functions chứa các phần khác nhau của chương trình hoặc một đối số có điều kiện cho tf.function mà bạn phân nhánh để chỉ thực thi những thứ bạn thực sự muốn chạy.

Xóa bộ sưu tập

Khi kích hoạt thực thi mong muốn, các API compat.v1 liên quan đến bộ sưu tập đồ thị (bao gồm các API đọc hoặc ghi vào các bộ sưu tập ẩn như tf.compat.v1.trainable_variables ) sẽ không còn khả dụng. Một số có thể tăng ValueError s, trong khi những người khác có thể âm thầm trả về danh sách trống.

Cách sử dụng tiêu chuẩn nhất của các bộ sưu tập trong TF1.x là duy trì các bộ khởi tạo, bước toàn cục, trọng số, tổn thất chính quy, tổn thất đầu ra mô hình và các bản cập nhật có thể thay đổi cần được chạy, chẳng hạn như từ các lớp BatchNormalization .

Để xử lý từng cách sử dụng tiêu chuẩn này:

  1. Khởi tạo - Bỏ qua. Khởi tạo biến thủ công là không cần thiết khi kích hoạt thực thi háo hức.
  2. Bước toàn cầu - Xem tài liệu của tf.compat.v1.train.get_or_create_global_step để biết hướng dẫn di chuyển.
  3. Trọng lượng - Ánh xạ mô hình của bạn với tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s bằng cách làm theo hướng dẫn trong hướng dẫn lập bản đồ mô hình và sau đó sử dụng cơ chế theo dõi trọng lượng tương ứng của chúng như tf.module.trainable_variables .
  4. Tổn thất quy định - Ánh xạ mô hình của bạn tới tf.Module s / tf.keras.layers.Layer s / tf.keras.Model s bằng cách làm theo hướng dẫn trong hướng dẫn lập bản đồ mô hình và sau đó sử dụng tf.keras.losses . Ngoài ra, bạn cũng có thể theo dõi các khoản lỗ theo quy định của mình theo cách thủ công.
  5. Tổn thất đầu ra của mô hình - Sử dụng cơ chế quản lý tổn thất tf.keras.Model hoặc theo dõi tổn thất của bạn một cách riêng biệt mà không cần sử dụng bộ sưu tập.
  6. Cập nhật cân nặng - Bỏ qua bộ sưu tập này. Thực thi háo hức và tf.function (có chữ ký và phụ thuộc tự động kiểm soát) có nghĩa là tất cả các bản cập nhật biến sẽ được chạy tự động. Vì vậy, bạn sẽ không phải chạy tất cả các cập nhật trọng lượng một cách rõ ràng khi kết thúc, nhưng lưu ý rằng điều đó có nghĩa là cập nhật trọng lượng có thể xảy ra vào thời điểm khác với thời điểm chúng đã xảy ra trong mã TF1.x của bạn, tùy thuộc vào cách bạn đang sử dụng các phụ thuộc kiểm soát.
  7. Tóm tắt - Tham khảo hướng dẫn di chuyển API tóm tắt .

Việc sử dụng các bộ sưu tập phức tạp hơn (chẳng hạn như sử dụng các bộ sưu tập tùy chỉnh) có thể yêu cầu bạn cấu trúc lại mã của mình để duy trì các cửa hàng toàn cầu của riêng bạn hoặc để làm cho nó hoàn toàn không phụ thuộc vào các cửa hàng toàn cầu.

ResourceVariables thay vì ReferenceVariables

ResourceVariables có đảm bảo nhất quán đọc-ghi mạnh hơn ReferenceVariables . Điều này dẫn đến việc dễ đoán hơn, dễ lý giải hơn về ngữ nghĩa về việc bạn có quan sát kết quả của lần viết trước đó hay không khi sử dụng các biến của bạn. Thay đổi này cực kỳ ít có khả năng khiến mã hiện tại phát sinh lỗi hoặc bị hỏng một cách âm thầm.

Tuy nhiên, có thể mặc dù không chắc rằng những đảm bảo tính nhất quán mạnh hơn này có thể làm tăng mức sử dụng bộ nhớ của chương trình cụ thể của bạn. Vui lòng gửi vấn đề nếu bạn thấy trường hợp này xảy ra. Ngoài ra, nếu bạn có các bài kiểm tra đơn vị dựa trên so sánh chuỗi chính xác với tên toán tử trong biểu đồ tương ứng với các lần đọc biến, hãy lưu ý rằng việc bật các biến tài nguyên có thể thay đổi một chút tên của các toán tử này.

Để cô lập tác động của thay đổi hành vi này đối với mã của bạn, nếu quá trình thực thi háo hức bị tắt, bạn có thể sử dụng tf.compat.v1.disable_resource_variables()tf.compat.v1.enable_resource_variables() để tắt hoặc bật thay đổi hành vi này trên toàn cầu. ResourceVariables sẽ luôn được sử dụng nếu kích hoạt khả năng thực thi háo hức.

Kiểm soát luồng v2

Trong TF1.x, các hoạt động luồng điều khiển như tf.condtf.while_loop nội tuyến cấp thấp như Switch , Merge , v.v. TF2 cung cấp các hoạt động luồng điều khiển chức năng cải tiến được triển khai với các dấu vết chức năng tf.function riêng biệt cho mọi nhánh và hỗ trợ phân hóa bậc cao.

Để cô lập tác động của sự thay đổi hành vi này đối với mã của bạn, nếu việc thực thi háo hức bị vô hiệu hóa, bạn có thể sử dụng tf.compat.v1.disable_control_flow_v2()tf.compat.v1.enable_control_flow_v2() để tắt hoặc bật tính năng thay đổi hành vi này trên toàn cầu. Tuy nhiên, bạn chỉ có thể vô hiệu hóa luồng điều khiển v2 nếu quá trình thực thi háo hức cũng bị vô hiệu hóa. Nếu nó được bật, luồng điều khiển v2 sẽ luôn được sử dụng.

Sự thay đổi hành vi này có thể thay đổi đáng kể cấu trúc của các chương trình TF được tạo sử dụng luồng điều khiển, vì chúng sẽ chứa một số dấu vết chức năng lồng nhau thay vì một đồ thị phẳng. Vì vậy, bất kỳ mã nào phụ thuộc nhiều vào ngữ nghĩa chính xác của các dấu vết được tạo ra đều có thể yêu cầu một số sửa đổi. Điêu nay bao gôm:

  • Mã dựa trên tên toán tử và tenxơ
  • Mã tham chiếu đến các tensor được tạo bên trong một nhánh luồng điều khiển TensorFlow từ bên ngoài nhánh đó. Điều này có thể tạo ra lỗi InaccessibleTensorError

Thay đổi hành vi này nhằm mục đích làm cho hiệu suất trung tính thành tích cực, nhưng nếu bạn gặp sự cố trong đó luồng điều khiển v2 hoạt động kém hơn so với luồng điều khiển TF1.x thì vui lòng gửi sự cố với các bước tái tạo.

Thay đổi hành vi của TensorShape API

Lớp TensorShape được đơn giản hóa để chứa int s, thay vì các đối tượng tf.compat.v1.Dimension . Vì vậy, không cần gọi .value để lấy giá trị int .

Các đối tượng tf.compat.v1.Dimension riêng lẻ vẫn có thể truy cập được từ tf.TensorShape.dims .

Để cô lập tác động của thay đổi hành vi này đối với mã của bạn, bạn có thể sử dụng tf.compat.v1.disable_v2_tensorshape()tf.compat.v1.enable_v2_tensorshape() để tắt hoặc bật tính năng thay đổi hành vi này trên toàn cầu.

Sau đây chứng minh sự khác biệt giữa TF1.x và TF2.

import tensorflow as tf
# Create a shape and choose an index
i = 0
shape = tf.TensorShape([16, None, 256])
shape
TensorShape([16, None, 256])

Nếu bạn có cái này trong TF1.x:

value = shape[i].value

Sau đó, làm điều này trong TF2:

value = shape[i]
value
16

Nếu bạn có cái này trong TF1.x:

for dim in shape:
    value = dim.value
    print(value)

Sau đó, làm điều này trong TF2:

for value in shape:
  print(value)
16
None
256

Nếu bạn có điều này trong TF1.x (hoặc sử dụng bất kỳ phương pháp thứ nguyên nào khác):

dim = shape[i]
dim.assert_is_compatible_with(other_dim)

Sau đó, làm điều này trong TF2:

other_dim = 16
Dimension = tf.compat.v1.Dimension

if shape.rank is None:
  dim = Dimension(None)
else:
  dim = shape.dims[i]
dim.is_compatible_with(other_dim) # or any other dimension method
True
shape = tf.TensorShape(None)

if shape:
  dim = shape.dims[i]
  dim.is_compatible_with(other_dim) # or any other dimension method

Giá trị boolean của tf.TensorShapeTrue nếu thứ hạng được biết, nếu không thì là False .

print(bool(tf.TensorShape([])))      # Scalar
print(bool(tf.TensorShape([0])))     # 0-length vector
print(bool(tf.TensorShape([1])))     # 1-length vector
print(bool(tf.TensorShape([None])))  # Unknown-length vector
print(bool(tf.TensorShape([1, 10, 100])))       # 3D tensor
print(bool(tf.TensorShape([None, None, None]))) # 3D tensor with no known dimensions
print()
print(bool(tf.TensorShape(None)))  # A tensor with unknown rank.
True
True
True
True
True
True

False

Các lỗi tiềm ẩn do thay đổi TensorShape

Các thay đổi hành vi TensorShape không có khả năng âm thầm phá mã của bạn. Tuy nhiên, bạn có thể thấy mã liên quan đến hình dạng bắt đầu tăng AttributeError s như int s và None s không có cùng thuộc tính như tf.compat.v1.Dimension s. Dưới đây là một số ví dụ về các AttributeError này:

try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  value = shape[0].value
except AttributeError as e:
  # 'int' object has no attribute 'value'
  print(e)
'int' object has no attribute 'value'
try:
  # Create a shape and choose an index
  shape = tf.TensorShape([16, None, 256])
  dim = shape[1]
  other_dim = shape[2]
  dim.assert_is_compatible_with(other_dim)
except AttributeError as e:
  # 'NoneType' object has no attribute 'assert_is_compatible_with'
  print(e)
'NoneType' object has no attribute 'assert_is_compatible_with'

Tensor Equality theo giá trị

Các toán tử nhị phân ==!= Trên các biến và tensor đã được thay đổi để so sánh theo giá trị trong TF2 thay vì so sánh bằng tham chiếu đối tượng như trong TF1.x. Ngoài ra, các tensor và biến không còn có thể băm hoặc sử dụng trực tiếp trong các tập hợp hoặc khóa chính, vì có thể không thể băm chúng theo giá trị. Thay vào đó, chúng hiển thị một phương thức .ref() mà bạn có thể sử dụng để nhận tham chiếu có thể băm đến tensor hoặc biến.

Để cô lập tác động của sự thay đổi hành vi này, bạn có thể sử dụng tf.compat.v1.disable_tensor_equality()tf.compat.v1.enable_tensor_equality() để tắt hoặc bật tính năng thay đổi hành vi này trên toàn cầu.

Ví dụ: trong TF1.x, hai biến có cùng giá trị sẽ trả về false khi bạn sử dụng toán tử == :

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
False

Trong khi ở TF2 có bật kiểm tra bình đẳng tensor, x == y sẽ trả về True .

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x == y
<tf.Tensor: shape=(), dtype=bool, numpy=True>

Vì vậy, trong TF2, nếu bạn cần so sánh bằng tham chiếu đối tượng, hãy đảm is not is dụng

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)
y = tf.Variable(0.0)

x is y
False

Băm các tenxơ và biến

Với các hành vi TF1.x, bạn đã từng có thể thêm trực tiếp các biến và tensor vào cấu trúc dữ liệu yêu cầu băm, chẳng hạn như các khóa setdict .

tf.compat.v1.disable_tensor_equality()
x = tf.Variable(0.0)
set([x, tf.constant(2.0)])
{<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>,
 <tf.Tensor: shape=(), dtype=float32, numpy=2.0>}

Tuy nhiên, trong TF2 với bình đẳng tensor được kích hoạt, tensor và biến không thể truy cập được do ngữ nghĩa của toán tử ==!= Thay đổi thành kiểm tra bình đẳng giá trị.

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

try:
  set([x, tf.constant(2.0)])
except TypeError as e:
  # TypeError: Variable is unhashable. Instead, use tensor.ref() as the key.
  print(e)
Variable is unhashable. Instead, use tensor.ref() as the key.

Vì vậy, trong TF2 nếu bạn cần sử dụng tensor hoặc đối tượng biến làm khóa hoặc set nội dung, bạn có thể sử dụng tensor.ref() để nhận tham chiếu băm có thể được sử dụng làm khóa:

tf.compat.v1.enable_tensor_equality()
x = tf.Variable(0.0)

tensor_set = set([x.ref(), tf.constant(2.0).ref()])
assert x.ref() in tensor_set

tensor_set
{<Reference wrapping <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>>,
 <Reference wrapping <tf.Tensor: shape=(), dtype=float32, numpy=2.0>>}

Nếu cần, bạn cũng có thể lấy tensor hoặc biến từ tham chiếu bằng cách sử dụng reference.deref() :

referenced_var = x.ref().deref()
assert referenced_var is x
referenced_var
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>

Tài nguyên và đọc thêm