![]() |
![]() |
![]() |
![]() |
當您將 TensorFlow 程式碼從 TF1.x 遷移至 TF2 時,最好確保遷移後的程式碼在 TF2 中的行為與在 TF1.x 中的行為相同。
本指南涵蓋遷移程式碼範例,其中 tf.compat.v1.keras.utils.track_tf1_style_variables
模型填充碼已套用至 tf.keras.layers.Layer
方法。請閱讀模型對應指南,以進一步瞭解 TF2 模型填充碼。
本指南詳細說明您可使用的方法來
- 驗證使用遷移程式碼訓練模型所得結果的正確性
- 驗證您的程式碼在不同 TensorFlow 版本之間的數值等價性
設定
pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is available only in
# Tensorflow 2.8
pip install -q tf-nightly
pip install -q tf_slim
import tensorflow as tf
import tensorflow.compat.v1 as v1
import numpy as np
import tf_slim as slim
import sys
from contextlib import contextmanager
!git clone --depth=1 https://github.com/tensorflow/models.git
import models.research.slim.nets.inception_resnet_v2 as inception
如果您將大量前向傳遞程式碼放入填充碼中,您會想知道其行為是否與在 TF1.x 中的行為相同。例如,考慮嘗試將整個 TF-Slim Inception-Resnet-v2 模型放入填充碼中,如下所示
# TF1 Inception resnet v2 forward pass based on slim layers
def inception_resnet_v2(inputs, num_classes, is_training):
with slim.arg_scope(
inception.inception_resnet_v2_arg_scope(batch_norm_scale=True)):
return inception.inception_resnet_v2(inputs, num_classes, is_training=is_training)
class InceptionResnetV2(tf.keras.layers.Layer):
"""Slim InceptionResnetV2 forward pass as a Keras layer"""
def __init__(self, num_classes, **kwargs):
super().__init__(**kwargs)
self.num_classes = num_classes
@tf.compat.v1.keras.utils.track_tf1_style_variables
def call(self, inputs, training=None):
is_training = training or False
# Slim does not accept `None` as a value for is_training,
# Keras will still pass `None` to layers to construct functional models
# without forcing the layer to always be in training or in inference.
# However, `None` is generally considered to run layers in inference.
with slim.arg_scope(
inception.inception_resnet_v2_arg_scope(batch_norm_scale=True)):
return inception.inception_resnet_v2(
inputs, self.num_classes, is_training=is_training)
碰巧的是,此層實際上可以完美運作 (包含精確的正規化損失追蹤)。
但是,這不是您可以理所當然的事情。請按照以下步驟驗證其行為是否真的與在 TF1.x 中的行為相同,甚至觀察到完美的數值等價性。這些步驟也有助於您找出前向傳遞的哪個部分導致與 TF1.x 的差異 (找出差異是否發生在模型前向傳遞中,而不是模型的不同部分)。
步驟 1:驗證變數是否僅建立一次
您應該驗證的第一件事是,您是否已正確建構模型,使其在每次呼叫中重複使用變數,而不是意外地在每次呼叫時建立和使用新變數。例如,如果您的模型在每次前向傳遞呼叫中建立新的 Keras 層或呼叫 tf.Variable
,則很可能無法擷取變數,並且每次都建立新的變數。
以下是兩個情境管理員範圍,您可以使用它們來偵測模型何時建立新變數,並偵錯模型的哪個部分正在執行此操作。
@contextmanager
def assert_no_variable_creations():
"""Assert no variables are created in this context manager scope."""
def invalid_variable_creator(next_creator, **kwargs):
raise ValueError("Attempted to create a new variable instead of reusing an existing one. Args: {}".format(kwargs))
with tf.variable_creator_scope(invalid_variable_creator):
yield
@contextmanager
def catch_and_raise_created_variables():
"""Raise all variables created within this context manager scope (if any)."""
created_vars = []
def variable_catcher(next_creator, **kwargs):
var = next_creator(**kwargs)
created_vars.append(var)
return var
with tf.variable_creator_scope(variable_catcher):
yield
if created_vars:
raise ValueError("Created vars:", created_vars)
第一個範圍 (assert_no_variable_creations()
) 會在您嘗試在範圍內建立變數時立即引發錯誤。這可讓您檢查堆疊追蹤 (並使用互動式偵錯) 以準確找出哪些程式碼行建立變數而不是重複使用現有的變數。
如果任何變數最終被建立,第二個範圍 (catch_and_raise_created_variables()
) 將在範圍結束時引發例外狀況。此例外狀況將包含在範圍中建立的所有變數的清單。如果您可以發現一般模式,這對於找出模型正在建立的所有權重的集合非常有用。但是,這對於找出這些變數建立的確切程式碼行不太有用。
使用以下兩個範圍來驗證,基於填充碼的 InceptionResnetV2 層在第一次呼叫後不會建立任何新變數 (推測會重複使用它們)。
model = InceptionResnetV2(1000)
height, width = 299, 299
num_classes = 1000
inputs = tf.ones( (1, height, width, 3))
# Create all weights on the first call
model(inputs)
# Verify that no new weights are created in followup calls
with assert_no_variable_creations():
model(inputs)
with catch_and_raise_created_variables():
model(inputs)
在以下範例中,觀察這些裝飾器如何在每次錯誤地建立新權重而不是重複使用現有權重的層上運作。
class BrokenScalingLayer(tf.keras.layers.Layer):
"""Scaling layer that incorrectly creates new weights each time:"""
@tf.compat.v1.keras.utils.track_tf1_style_variables
def call(self, inputs):
var = tf.Variable(initial_value=2.0)
bias = tf.Variable(initial_value=2.0, name='bias')
return inputs * var + bias
model = BrokenScalingLayer()
inputs = tf.ones( (1, height, width, 3))
model(inputs)
try:
with assert_no_variable_creations():
model(inputs)
except ValueError as err:
import traceback
traceback.print_exc()
model = BrokenScalingLayer()
inputs = tf.ones( (1, height, width, 3))
model(inputs)
try:
with catch_and_raise_created_variables():
model(inputs)
except ValueError as err:
print(err)
您可以修正此層,方法是確保它只建立權重一次,然後每次重複使用它們。
class FixedScalingLayer(tf.keras.layers.Layer):
"""Scaling layer that incorrectly creates new weights each time:"""
def __init__(self):
super().__init__()
self.var = None
self.bias = None
@tf.compat.v1.keras.utils.track_tf1_style_variables
def call(self, inputs):
if self.var is None:
self.var = tf.Variable(initial_value=2.0)
self.bias = tf.Variable(initial_value=2.0, name='bias')
return inputs * self.var + self.bias
model = FixedScalingLayer()
inputs = tf.ones( (1, height, width, 3))
model(inputs)
with assert_no_variable_creations():
model(inputs)
with catch_and_raise_created_variables():
model(inputs)
疑難排解
以下是一些常見原因,說明您的模型為何可能會意外地建立新權重而不是重複使用現有權重
- 它使用明確的
tf.Variable
呼叫,而未重複使用已建立的tf.Variables
。修正此問題的方法是先檢查是否尚未建立,然後重複使用現有的變數。 - 它在每次前向傳遞中直接建立 Keras 層或模型 (而不是
tf.compat.v1.layers
)。修正此問題的方法是先檢查是否尚未建立,然後重複使用現有的變數。 - 它建立在
tf.compat.v1.layers
之上,但未能為所有compat.v1.layers
指派明確的名稱,或將您的compat.v1.layer
用法包裝在具名的variable_scope
中,導致自動產生的層名稱在每次模型呼叫中遞增。修正此問題的方法是在您的填充碼裝飾方法內放入具名的tf.compat.v1.variable_scope
,以包裝您的所有tf.compat.v1.layers
用法。
步驟 2:檢查變數計數、名稱和形狀是否相符
第二個步驟是確保您的層在 TF2 中執行時,建立的權重數量與對應程式碼在 TF1.x 中執行時建立的權重數量相同,且形狀也相同。
您可以混合使用手動檢查它們以查看是否相符,以及以程式設計方式在單元測試中執行檢查,如下所示。
# Build the forward pass inside a TF1.x graph, and
# get the counts, shapes, and names of the variables
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
height, width = 299, 299
num_classes = 1000
inputs = tf.ones( (1, height, width, 3))
out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=False)
tf1_variable_names_and_shapes = {
var.name: (var.trainable, var.shape) for var in tf.compat.v1.global_variables()}
num_tf1_variables = len(tf.compat.v1.global_variables())
接下來,對 TF2 中的填充碼封裝層執行相同的操作。請注意,模型在擷取權重之前也會被多次呼叫。這樣做是為了有效地測試變數重複使用。
height, width = 299, 299
num_classes = 1000
model = InceptionResnetV2(num_classes)
# The weights will not be created until you call the model
inputs = tf.ones( (1, height, width, 3))
# Call the model multiple times before checking the weights, to verify variables
# get reused rather than accidentally creating additional variables
out, endpoints = model(inputs, training=False)
out, endpoints = model(inputs, training=False)
# Grab the name: shape mapping and the total number of variables separately,
# because in TF2 variables can be created with the same name
num_tf2_variables = len(model.variables)
tf2_variable_names_and_shapes = {
var.name: (var.trainable, var.shape) for var in model.variables}
# Verify that the variable counts, names, and shapes all match:
assert num_tf1_variables == num_tf2_variables
assert tf1_variable_names_and_shapes == tf2_variable_names_and_shapes
基於填充碼的 InceptionResnetV2 層通過了此測試。但是,如果它們不相符,您可以透過差異 (文字或其他) 執行它,以查看差異在哪裡。
這可以提供線索,說明模型的哪個部分未如預期般運作。透過即時執行,您可以使用 pdb、互動式偵錯和中斷點來深入研究模型中看起來可疑的部分,並更深入地偵錯哪裡出錯。
疑難排解
請密切注意任何直接透過明確的
tf.Variable
呼叫和 Keras 層/模型建立的變數的名稱,因為即使其他一切都運作正常,它們的變數名稱產生語意在 TF1.x 圖表和 TF2 功能 (例如即時執行和tf.function
) 之間也可能略有不同。如果您的情況是這樣,請調整您的測試以考量任何略有不同的命名語意。您有時可能會發現,即使在 TF1.x 中它們被變數集合擷取,在訓練迴圈的前向傳遞中建立的
tf.Variable
、tf.keras.layers.Layer
或tf.keras.Model
也會從您的 TF2 變數清單中遺失。修正此問題的方法是將您的前向傳遞建立的變數/層/模型指派給模型中的執行個體屬性。請參閱此處以取得更多資訊。
步驟 3:重設所有變數,檢查在停用所有隨機性的情況下的數值等價性
下一步是驗證實際輸出和正規化損失追蹤的數值等價性,當您修正模型以使其不涉及隨機數字產生時 (例如在推論期間)。
執行此操作的確切方式可能取決於您的特定模型,但在大多數模型 (例如此模型) 中,您可以透過以下方式執行此操作
- 將權重初始化為相同的值,且不帶隨機性。這可以透過在建立權重後將其重設為固定值來完成。
- 在推論模式下執行模型,以避免觸發任何可能成為隨機性來源的 dropout 層。
以下程式碼示範如何以這種方式比較 TF1.x 和 TF2 結果。
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
height, width = 299, 299
num_classes = 1000
inputs = tf.ones( (1, height, width, 3))
out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=False)
# Rather than running the global variable initializers,
# reset all variables to a constant value
var_reset = tf.group([var.assign(tf.ones_like(var) * 0.001) for var in tf.compat.v1.global_variables()])
sess.run(var_reset)
# Grab the outputs & regularization loss
reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
tf1_output = sess.run(out)
print("Regularization loss:", tf1_regularization_loss)
tf1_output[0][:5]
取得 TF2 結果。
height, width = 299, 299
num_classes = 1000
model = InceptionResnetV2(num_classes)
inputs = tf.ones((1, height, width, 3))
# Call the model once to create the weights
out, endpoints = model(inputs, training=False)
# Reset all variables to the same fixed value as above, with no randomness
for var in model.variables:
var.assign(tf.ones_like(var) * 0.001)
tf2_output, endpoints = model(inputs, training=False)
# Get the regularization loss
tf2_regularization_loss = tf.math.add_n(model.losses)
print("Regularization loss:", tf2_regularization_loss)
tf2_output[0][:5]
# Create a dict of tolerance values
tol_dict={'rtol':1e-06, 'atol':1e-05}
# Verify that the regularization loss and output both match
# when we fix the weights and avoid randomness by running inference:
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)
當您移除隨機性來源時,TF1.x 和 TF2 之間的數字會相符,且 TF2 相容的 InceptionResnetV2
層通過了測試。
如果您觀察到您自己的模型的結果出現差異,您可以使用列印或 pdb 和互動式偵錯來找出結果從何處以及為何開始產生差異。即時執行可以使此過程顯著簡化。您也可以使用消融方法僅在固定的中繼輸入上執行模型的小部分,並隔離差異發生的位置。
方便的是,許多 slim 網路 (和其他模型) 也公開您可以探查的中繼端點。
步驟 4:對齊隨機數字產生,檢查訓練和推論中的數值等價性
最後一個步驟是驗證 TF2 模型在數值上與 TF1.x 模型相符,即使在考量到變數初始化和前向傳遞本身 (例如前向傳遞期間的 dropout 層) 中的隨機數字產生時也是如此。
您可以透過使用以下測試工具,使隨機數字產生語意在 TF1.x 圖表/工作階段和即時執行之間相符。
TF1 舊版圖表/工作階段和 TF2 即時執行使用不同的有狀態隨機數字產生語意。
在 tf.compat.v1.Session
中,如果未指定種子,則隨機數字產生取決於在新增隨機運算時圖表中有多少運算,以及圖表執行多少次。在即時執行中,有狀態隨機數字產生取決於全域種子、運算隨機種子,以及具有指定隨機種子的運算執行多少次。請參閱 tf.random.set_seed
以取得更多資訊。
以下 v1.keras.utils.DeterministicRandomTestTool
類別提供情境管理員 scope()
,可使有狀態隨機運算在 TF1 圖表/工作階段和即時執行之間使用相同的種子。
此工具提供兩種測試模式
constant
,它為每個單一運算使用相同的種子,無論它被呼叫了多少次,以及num_random_ops
,它使用先前觀察到的有狀態隨機運算的數量作為運算種子。
這同時適用於用於建立和初始化變數的有狀態隨機運算,以及用於計算的有狀態隨機運算 (例如 dropout 層)。
產生三個隨機張量,以示範如何使用此工具使有狀態隨機數字產生在工作階段和即時執行之間相符。
random_tool = v1.keras.utils.DeterministicRandomTestTool()
with random_tool.scope():
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
a = tf.random.uniform(shape=(3,1))
a = a * 3
b = tf.random.uniform(shape=(3,3))
b = b * 3
c = tf.random.uniform(shape=(3,3))
c = c * 3
graph_a, graph_b, graph_c = sess.run([a, b, c])
graph_a, graph_b, graph_c
random_tool = v1.keras.utils.DeterministicRandomTestTool()
with random_tool.scope():
a = tf.random.uniform(shape=(3,1))
a = a * 3
b = tf.random.uniform(shape=(3,3))
b = b * 3
c = tf.random.uniform(shape=(3,3))
c = c * 3
a, b, c
# Demonstrate that the generated random numbers match
np.testing.assert_allclose(graph_a, a.numpy(), **tol_dict)
np.testing.assert_allclose(graph_b, b.numpy(), **tol_dict)
np.testing.assert_allclose(graph_c, c.numpy(), **tol_dict)
但是,請注意在 constant
模式下,由於 b
和 c
是使用相同的種子產生且具有相同的形狀,因此它們將具有完全相同的值。
np.testing.assert_allclose(b.numpy(), c.numpy(), **tol_dict)
追蹤順序
如果您擔心 constant
模式下某些隨機數字相符會降低您對數值等價性測試的信心 (例如,如果多個權重採用相同的初始化),則可以使用 num_random_ops
模式來避免這種情況。在 num_random_ops
模式下,產生的隨機數字將取決於程式中隨機運算的順序。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
a = tf.random.uniform(shape=(3,1))
a = a * 3
b = tf.random.uniform(shape=(3,3))
b = b * 3
c = tf.random.uniform(shape=(3,3))
c = c * 3
graph_a, graph_b, graph_c = sess.run([a, b, c])
graph_a, graph_b, graph_c
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
a = tf.random.uniform(shape=(3,1))
a = a * 3
b = tf.random.uniform(shape=(3,3))
b = b * 3
c = tf.random.uniform(shape=(3,3))
c = c * 3
a, b, c
# Demonstrate that the generated random numbers match
np.testing.assert_allclose(graph_a, a.numpy(), **tol_dict)
np.testing.assert_allclose(graph_b, b.numpy(), **tol_dict )
np.testing.assert_allclose(graph_c, c.numpy(), **tol_dict)
# Demonstrate that with the 'num_random_ops' mode,
# b & c took on different values even though
# their generated shape was the same
assert not np.allclose(b.numpy(), c.numpy(), **tol_dict)
但是,請注意,在此模式下,隨機產生對程式順序敏感,因此以下產生的隨機數字不相符。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
a = tf.random.uniform(shape=(3,1))
a = a * 3
b = tf.random.uniform(shape=(3,3))
b = b * 3
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
b_prime = tf.random.uniform(shape=(3,3))
b_prime = b_prime * 3
a_prime = tf.random.uniform(shape=(3,1))
a_prime = a_prime * 3
assert not np.allclose(a.numpy(), a_prime.numpy())
assert not np.allclose(b.numpy(), b_prime.numpy())
為了允許偵錯因追蹤順序而產生的變化,num_random_ops
模式下的 DeterministicRandomTestTool
可讓您使用 operation_seed
屬性查看已追蹤多少隨機運算。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
print(random_tool.operation_seed)
a = tf.random.uniform(shape=(3,1))
a = a * 3
print(random_tool.operation_seed)
b = tf.random.uniform(shape=(3,3))
b = b * 3
print(random_tool.operation_seed)
如果您需要在測試中考量不同的追蹤順序,您甚至可以明確設定自動遞增的 operation_seed
。例如,您可以使用它來使隨機數字產生在兩個不同的程式順序之間相符。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
print(random_tool.operation_seed)
a = tf.random.uniform(shape=(3,1))
a = a * 3
print(random_tool.operation_seed)
b = tf.random.uniform(shape=(3,3))
b = b * 3
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
random_tool.operation_seed = 1
b_prime = tf.random.uniform(shape=(3,3))
b_prime = b_prime * 3
random_tool.operation_seed = 0
a_prime = tf.random.uniform(shape=(3,1))
a_prime = a_prime * 3
np.testing.assert_allclose(a.numpy(), a_prime.numpy(), **tol_dict)
np.testing.assert_allclose(b.numpy(), b_prime.numpy(), **tol_dict)
但是,DeterministicRandomTestTool
不允許重複使用已使用的運算種子,因此請確保自動遞增的序列不會重疊。這是因為即時執行會為相同運算種子的後續用法產生不同的數字,而 TF1 圖表和工作階段則不會,因此引發錯誤有助於使工作階段和即時有狀態隨機數字產生保持一致。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
random_tool.operation_seed = 1
b_prime = tf.random.uniform(shape=(3,3))
b_prime = b_prime * 3
random_tool.operation_seed = 0
a_prime = tf.random.uniform(shape=(3,1))
a_prime = a_prime * 3
try:
c = tf.random.uniform(shape=(3,1))
raise RuntimeError("An exception should have been raised before this, " +
"because the auto-incremented operation seed will " +
"overlap an already-used value")
except ValueError as err:
print(err)
驗證推論
您現在可以使用 DeterministicRandomTestTool
來確保 InceptionResnetV2
模型在推論中相符,即使在使用隨機權重初始化時也是如此。為了獲得更強大的測試條件 (由於程式順序相符),請使用 num_random_ops
模式。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
height, width = 299, 299
num_classes = 1000
inputs = tf.ones( (1, height, width, 3))
out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=False)
# Initialize the variables
sess.run(tf.compat.v1.global_variables_initializer())
# Grab the outputs & regularization loss
reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
tf1_output = sess.run(out)
print("Regularization loss:", tf1_regularization_loss)
height, width = 299, 299
num_classes = 1000
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
model = InceptionResnetV2(num_classes)
inputs = tf.ones((1, height, width, 3))
tf2_output, endpoints = model(inputs, training=False)
# Grab the regularization loss as well
tf2_regularization_loss = tf.math.add_n(model.losses)
print("Regularization loss:", tf2_regularization_loss)
# Verify that the regularization loss and output both match
# when using the DeterministicRandomTestTool:
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)
驗證訓練
由於 DeterministicRandomTestTool
適用於所有有狀態隨機運算 (包括權重初始化和計算,例如 dropout 層),因此您可以使用它來驗證模型在訓練模式下是否也相符。您可以再次使用 num_random_ops
模式,因為有狀態隨機運算的程式順序相符。
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
height, width = 299, 299
num_classes = 1000
inputs = tf.ones( (1, height, width, 3))
out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=True)
# Initialize the variables
sess.run(tf.compat.v1.global_variables_initializer())
# Grab the outputs & regularization loss
reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
tf1_output = sess.run(out)
print("Regularization loss:", tf1_regularization_loss)
height, width = 299, 299
num_classes = 1000
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
model = InceptionResnetV2(num_classes)
inputs = tf.ones((1, height, width, 3))
tf2_output, endpoints = model(inputs, training=True)
# Grab the regularization loss as well
tf2_regularization_loss = tf.math.add_n(model.losses)
print("Regularization loss:", tf2_regularization_loss)
# Verify that the regularization loss and output both match
# when using the DeterministicRandomTestTool
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)
您現在已驗證,使用裝飾器圍繞 tf.keras.layers.Layer
即時執行的 InceptionResnetV2
模型在數值上與在 TF1 圖表和工作階段中執行的 slim 網路相符。
例如,直接使用 training=True
呼叫 InceptionResnetV2
層會根據網路建立順序將變數初始化與 dropout 順序交錯。
另一方面,首先將 tf.keras.layers.Layer
裝飾器放入 Keras 函數式模型中,然後僅使用 training=True
呼叫模型,這相當於先初始化所有變數,然後使用 dropout 層。這會產生不同的追蹤順序和一組不同的隨機數字。
但是,預設的 mode='constant'
對於追蹤順序中的這些差異不敏感,即使在將層嵌入在 Keras 函數式模型中時,也會在沒有額外工作的情況下通過。
random_tool = v1.keras.utils.DeterministicRandomTestTool()
with random_tool.scope():
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
height, width = 299, 299
num_classes = 1000
inputs = tf.ones( (1, height, width, 3))
out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=True)
# Initialize the variables
sess.run(tf.compat.v1.global_variables_initializer())
# Get the outputs & regularization losses
reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
tf1_output = sess.run(out)
print("Regularization loss:", tf1_regularization_loss)
height, width = 299, 299
num_classes = 1000
random_tool = v1.keras.utils.DeterministicRandomTestTool()
with random_tool.scope():
keras_input = tf.keras.Input(shape=(height, width, 3))
layer = InceptionResnetV2(num_classes)
model = tf.keras.Model(inputs=keras_input, outputs=layer(keras_input))
inputs = tf.ones((1, height, width, 3))
tf2_output, endpoints = model(inputs, training=True)
# Get the regularization loss
tf2_regularization_loss = tf.math.add_n(model.losses)
print("Regularization loss:", tf2_regularization_loss)
# Verify that the regularization loss and output both match
# when using the DeterministicRandomTestTool
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)
步驟 3b 或 4b (選用):使用預先存在的檢查點進行測試
在上述步驟 3 或步驟 4 之後,如果您有一些預先存在的基於名稱的檢查點,則從這些檢查點開始執行數值等價性測試可能會很有用。這可以測試您的舊版檢查點載入是否正常運作,以及模型本身是否正常運作。重複使用 TF1.x 檢查點指南涵蓋如何重複使用您預先存在的 TF1.x 檢查點並將其轉移到 TF2 檢查點。
其他測試與疑難排解
當您新增更多數值等價性測試時,您也可以選擇新增一項測試,以驗證您的梯度計算 (甚至您的最佳化工具更新) 是否相符。
反向傳播和梯度計算比模型前向傳遞更容易出現浮點數值不穩定性。這表示,隨著您的等價性測試涵蓋訓練中更多非隔離的部分,您可能會開始看到完全即時執行與您的 TF1 圖表之間存在非微不足道的數值差異。這可能是由 TensorFlow 的圖表最佳化引起的,這些最佳化會執行諸如以較少的數學運算取代圖表中的子運算式等操作。
為了隔離這是否可能是這種情況,您可以將您的 TF1 程式碼與在 tf.function
(它會套用圖表最佳化傳遞,例如您的 TF1 圖表) 內發生的 TF2 計算進行比較,而不是與純粹的即時計算進行比較。或者,您可以嘗試使用 tf.config.optimizer.set_experimental_options
來停用最佳化傳遞,例如 "arithmetic_optimization"
,然後再進行 TF1 計算,以查看結果是否在數值上更接近您的 TF2 計算結果。在您的實際訓練執行中,建議您使用啟用最佳化傳遞的 tf.function
以提高效能,但您可能會發現停用它們在您的數值等價性單元測試中很有用。
同樣地,您也可能會發現,即使它們代表的數學公式相同,tf.compat.v1.train
最佳化工具和 TF2 最佳化工具也具有略微不同的浮點數值屬性。這不太可能成為您的訓練執行中的問題,但可能需要在等價性單元測試中使用更高的數值容差。