TensorFlow 1.x と TensorFlow 2 の比較 - 動作と API

TensorFlow.org で表示 Google Colab で実行 GitHub でソースを表示 ノートブックをダウンロード

内部では、TensorFlow 2 は TF1.x とは根本的に異なるプログラミングパラダイムに従っています。

このガイドでは、動作と API に関する TF1.x と TF2 の基本的な違いと、これらすべてが移行過程にどのように関連するかについて説明します。

主な変更点の全体的な概要

基本的に、TF1.x と TF2 は、実行(TF2 では eager)、変数、制御フロー、テンソル形状、およびテンソル等価比較に関して、異なる一連のランタイム動作を使用します。TF2 と互換性を持たせるには、コードが TF2 動作の完全なセットと互換性がある必要があります。移行中に、これらの動作のほとんどを tf.compat.v1.enable_* または tf.compat.v1.disable_* API を介して個別に有効または無効にすることができます。1 つの例外は、コレクションの削除です。これは、Eager execution の有効化/無効化の副作用です。

TensorFlow 2 の概要は次のとおりです。

  • 冗長な API を削除します。
  • API の一貫性を高めます。例えば、統合 RNN統合オプティマイザなどです。
  • セッションよりも関数を優先し、グラフとコンパイルの自動制御依存関係を提供する tf.function ととも に Eager execution がデフォルトで有効になっている Python ランタイムとの統合を改善します。
  • グローバルグラフコレクションを非推奨にします。
  • ReferenceVariables よりも ResourceVariables を使用して、変数の同時実行のセマンティクスを変更します。
  • 関数ベースの微分可能な制御フロー(制御フロー v2)をサポートします。
  • tf.compat.v1.Dimension オブジェクトの代わりに int を保持するように TensorShape API を簡素化します。
  • テンソルの等価性の仕組みを更新します。TF1.x では、テンソルと変数の == 演算子がオブジェクト参照の等価性をチェックします。TF2 では、値の等価性をチェックします。さらに、テンソル/変数はハッシュ可能ではなくなりました。しかし、テンソル/変数をセットで、または dict キーとして使用する必要がある場合は、var.ref() 経由でそれらへのハッシュ可能なオブジェクト参照を取得できます。

以下のセクションでは、TF1.x と TF2 の相違点についてさらに説明します。TF2 の設計プロセスの詳細については、RFC設計ドキュメントをご覧ください。

API のクリーンアップ

TF2 では、多数の API が取り除かれたか移行されています。主な変更点には、現在ではオープンソースとなった absl-py の導入による tf.apptf.flags、および tf.logging の削除、tf.contrib にあったプロジェクトの移植、使用頻度の低い関数を tf.math などのサブパッケージに移動することによるメインの tf.* 名前空間のクリーンアップなどがあります。一部の API は TF2 バージョンの tf.summarytf.keras.metrics、および tf.keras.optimizers に置き換えられました。

tf.compat.v1: レガシーおよび互換性 API エンドポイント

tf.compat および tf.compat.v1 名前空間のシンボルは、TF2 API とは見なされません。これらの名前空間は、互換性シンボルの組み合わせと、TF 1.x のレガシー API エンドポイントを公開します。これらは、TF1.x から TF2 への移行を支援することを目的としています。ただし、これらの compat.v1 API はいずれも慣用的な TF2 API ではないため、新しい TF2 コードを記述するために使用しないでください。

個々の tf.compat.v1 シンボルは、TF2 動作が有効になっていても動作し続けるため(tf.compat.v1.losses.mean_squared_error など)、TF2 と互換性がある場合がありますが、他のものは TF2 と互換性がありません(tf.compat.v1.metrics.accuracy)。多くの compat.v1 シンボル(すべてではありません)のドキュメントには、TF2 動作との互換性の程度と、それらを TF2 API に移行する方法を説明する専用の移行情報が含まれています。

TF2 アップグレードスクリプトは、多くの compat.v1 API シンボルを、それらがエイリアスであるか、同じ引数を持っていても順序が異なる場合に同等の TF2 API にマップできます。アップグレードスクリプトを使用して、TF1.x API の名前を自動的に変更することもできます。

False friend(空似言葉)API

TF2 tf 名前空間(compat.v1 の下ではない)には、内部で TF2 の動作を実際に無視する、TF2 の動作の完全なセットと完全に互換性がない、またはその両方の一連の「false-friend」シンボルがあります。そのため、これらの API は、潜在的にサイレントな方法で、TF2 コードで誤動作する可能性があります。

  • tf.estimator.*: Estimator は内部でグラフとセッションを作成して使用します。そのため、これらは TF2 互換と見なされるべきではありません。コードが Estimator を実行している場合、TF2 の動作は使用されません。
  • keras.Model.model_to_estimator(...): これは内部で Estimator を作成しますが、上記にあるように、TF2 互換ではありません。
  • tf.Graph().as_default(): これは TF1.x グラフの動作をし、標準の TF2 互換の tf.function の動作には従いません。このようなグラフに入るコードは、通常、セッションを介してグラフを実行し、TF2 互換と見なすべきではありません。
  • tf.feature_column.* 特徴量列 API は一般に TF1 スタイルの tf.compat.v1.get_variable 変数の作成に依存し、作成された変数はグローバルコレクション経由でアクセスされると想定します。TF2 はコレクションをサポートしていないため、TF2 の動作を有効にして API を実行すると、API が正しく機能しない場合があります。

その他の API の変更

  • TF2 は、tf.colocate_with の使用を不要にするデバイス配置アルゴリズムの大幅な改善を特徴としています。削除によりパフォーマンスが低下する場合は、バグを報告してください

  • tf.v1.ConfigProto のすべての使用を tf.config からの同等の関数に置換します。

Eager execution

TF1.x では、tf.* API 呼び出しを作成して抽象構文ツリー(グラフ)を手動でつなぎ合わせ、出力テンソルと入力テンソルのセットを session.run 呼び出しに渡し、抽象構文ツリーを手動でコンパイルする必要がありました。TF2 はこれを(Python が通常行うように)eagerly に実行し 、グラフとセッションは実装の詳細のような感覚になっています。

Eager execution の副産物として注目しておきたいのは、tf.control_dependencies が不要になったという点です。これは、コードのすべての行が順に実行されるようになったためです(tf.function 内では、副次的影響のあるコードは記述された順に実行されます)。

global の排除

TF1.X では、グローバル名前空間とコレクションに暗黙的に大きく依存していました。tf.Variable を呼び出すと、デフォルトのグラフにあるコレクションに配置され、それをポイントする Python 変数を追跡できなくなってもグラフに残っていました。その tf.Variable は復元できますが、その作成に使用された名前がわかっている場合のみでした。変数の作成を管理していないユーザーにとっては困難なことでした。その結果、変数をもう一度見つけ出すためのさまざまな仕組みが生まれただけでなく、変数スコープ、グローバルコレクション、tf.get_global_step のようなヘルパーメソッド、tf.global_variables_initializer、すべてのトレーニング可能な変数の勾配を暗黙的に計算するオプティマイザなど、ユーザー作成変数を検索するフレームワークが急増しました。TF2 は、これらすべての仕組み(Variables 2.0 RFC)を排除し、自分の変数は自分で追跡するというデフォルトの仕組みを採択します。tf.Variable を追跡できなくなると、ガベージコレクションによって収集されます。

変数を追跡する必要があるため、余分な作業が発生しますが、モデリングシムなどのツールと、tf.Module および tf.keras.layers.Layer の暗黙的なオブジェクト指向変数コレクションなどの動作により、負担は最小限に抑えられます。

セッションではなく関数

session.run 呼び出しは、ほぼ関数呼び出しと変わりません。入力と呼び出される関数を指定すると、一連の出力が返されます。TF2 では、tf.function を使って、TensorFlow が単一のグラフとして実行できるように Python 関数に JIT コンパイルのマークをつけます(Functions 2.0 RFC)。この仕組みにより、TF2 は Graph モードのすべてのメリットを得ることができます。

  • パフォーマンス: 関数を最適化できます(ノード枝狩り、カーネル融合など)
  • 移植性: 関数をエクスポート/再インポート(SavedModel 2.0 RFC)できるため、モジュール型 TensorFlow 関数を再利用し共有することができます。
# TF1.x
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TF2
outputs = f(input)

Python と TensorFlow コードを自由に混在させられるため、Python の表現力を活用できます。ただし、移植可能な TensorFlow は、モバイル、C++、JavaScript など、Python インタープリターを使用しないコンテキストで実行されます。tf.functionを追加する際にコードの書き直しを避けるには、AutoGraph を使用して、Python コンストラクトのサブセットを TensorFlow の同等のものに変換します。

  • for/while -> tf.while_loop (breakcontinue はサポートされています)
  • if-> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph では制御フローを任意にネストできるため、シーケンスモデル、強化学習、カスタムトレーニングループなど、多くの複雑な ML プログラムを効率的かつ簡潔に実装することができます。

TF 2.x の動作変更への適応

TF2 への移行は、TF2 の動作の完全なセットに移行して初めて完了します。tf.compat.v1.enable_v2_behaviors および tf.compat.v1.disable_v2_behaviors を介して、動作の完全なセットを有効または無効にすることができます。以下のセクションでは、それぞれの主要な動作の変更について詳しく説明します。

tf.function の使用

移行中のプログラムへの最大の変更は、基本的なプログラミング モデルのパラダイムグラフとセッションから Eager execution と tf.function へのシフトから生じる可能性があります。Eager execution および tf.function と互換性のない API からそれらと互換性のある API への移行の詳細については、TF2 移行ガイドを参照してください。

注意: 移行中に、tf.compat.v1.enable_eager_executiontf.compat.v1.disable_eager_execution を使用して eager execusion を直接有効または無効にすることを選択できますが、これはプログラムの有効期間中に一度だけ行うことができます。

以下は、tf.Graph および tf.compat.v1.Session から tf.function による Eager execution に切り替える場合に問題を引き起こす可能性がある、いずれかの API に関連付けられていない一般的なプログラムパターンの一部です。

パターン 1: 1 回のみ実行することを目的とした Python オブジェクトの操作と変数の作成が、複数回実行される。

グラフとセッションに依存する TF1.x プログラムでは、通常、プログラム内のすべての Python ロジックが 1 回だけ実行されることが期待されます。ただし、Eager execution および tf.function を使用すると、Python ロジックは少なくとも 1 回実行されますが、場合によってはそれ以上の回数(eagerly に複数回、または異なる tf.function トレース全体で複数回)が実行される可能性があります。時には、tf.function が同じ入力を 2 回トレースし、予期しない動作を引き起こすことさえあります(例 1 と 2 をご覧ください)。詳細については、tf.function ガイドを参照してください。

注意: 通常、このパターンにより、tf.function なしで eagerly に実行すると、コードが警告なしに誤動作しますが、問題のあるコードを tf.function 内にラップしようとすると、一般に InaccessibleTensorError または ValueError が発生します。この問題を発見してデバッグするには、早い段階でコードを tf.function でラップし、pdb または対話型デバッグを使用して InaccessibleTensorError のソースを特定することをお勧めします。

例 1: 変数の作成

関数が呼び出されたときに変数を作成する以下の例を考えてみましょう。

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)

ただし、変数の作成を含む上記の関数を単純に tf.function でラップすることは許可されていません。tf.functionは、最初の呼び出しでのシングルトン変数の作成のみをサポートします。これを強制するには、tf.function が最初の呼び出しで変数の作成を検出すると、再度トレースを試行し、2 番目のトレースで変数の作成がある場合はエラーを発生させます。

@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)

回避策として、変数を最初の呼び出しで作成した後にキャッシュして再利用します。

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()

例 2: tf.function の再トレースによる範囲外のテンソル

例 1 で示したように、最初の呼び出しで変数の作成を検出すると、tf.function は再トレースします。2 つのトレースで 2 つのグラフが作成されるため、これはさらに混乱を招く可能性があります。再トレースからの 2 番目のグラフが最初のトレース中に生成されたグラフからテンソルにアクセスしようとすると、Tensorflow ではテンソルが範囲外であるというエラーを発生します。シナリオを示すために、以下のコードは最初の tf.function 呼び出しでデータセットを作成します。これは期待どおりに実行されます。

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()

ただし、最初の tf.function 呼び出しで変数を作成しようとすると、コードはデータセットが範囲外であることを通知するエラーを発生させます。これは、データセットが最初のグラフにあり、2 番目のグラフもそれにアクセスしようとしているためです。

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.

最も簡単な解決策は、変数の作成とデータセットの作成が両方とも tf.function 呼び出しの外にあることを確認することです。例えば、次のとおりです。

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()

ただし、tf.function で変数を作成することが避けられない場合があります(一部の TF keras オプティマイザのスロット変数など)。それでも、データセットの作成を tf.function 呼び出しの外に移動するだけです。これに依存できる理由は、tf.function が暗黙的な入力としてデータセットを受け取り、両方のグラフが適切にアクセスできるためです。

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()

例 3: dict の使用による予期しない Tensorflow オブジェクトの再作成

tf.function は、リストへの追加、ディクショナリへのチェック/追加など、Python の副作用に対するサポートが非常に貧弱です。詳細は「tf.function によるパフォーマンスの向上」にあります。以下の例では、コードはディクショナリを使用してデータセットとイテレータをキャッシュしています。同じキーの場合、モデルへの各呼び出しは、データセットの同じイテレータを返します。

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

ただし、上記のパターンは tf.function では期待どおりに機能しません。トレース中、tf.function はディクショナリへの追加による Python の副作用を無視します。代わりに、新しいデータセットとイテレータの作成のみを記憶します。その結果、モデルへの各呼び出しは常に新しいイテレータを返します。この問題は、数値結果またはパフォーマンスが十分に重要でない限り、気づきにくいものです。したがって、tf.function を単純に 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

tf.init_scope を使用して、期待される動作を実現するために、データセットとイテレータの作成をグラフの外に持ち上げることができます。

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

一般的な経験則は、ロジックで Python の副作用に依存することを避け、それらをトレースのデバッグにのみ使用することです。

例 4: グローバル Python リストの操作

次の TF1.x コードは、現在のトレーニングステップによって生成された損失のリストのみを維持するために使用する損失のグローバルリストを使用します。リストに損失を追加する Python ロジックは、セッションが実行されるトレーニングステップの数に関係なく、1 回だけ呼び出されることに注意してください。

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(...)

ただし、この Python ロジックが Eager execution で単純に TF2 にマッピングされている場合、損失のグローバルリストには各トレーニングステップで新しい値が追加されます。これは、以前はリストに現在のトレーニングステップからの損失のみが含まれると想定していたトレーニングステップコードが、これまでに実行されたすべてのトレーニングステップからの損失のリストを実際に確認するようになったことを意味します。これは意図しない動作の変更であり、各ステップの開始時にリストをクリアするか、トレーニングステップに対してローカルにする必要があります。

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)
  ...

パターン 2: TF1.x のすべてのステップで再計算されることを意図したシンボリックテンソルが、eager に切り替わる際に誤って初期値でキャッシュされる。

このパターンは通常、コードを tf.functions の外部で eagerly に実行すると、警告なしに誤動作を引き起こしますが、初期値のキャッシュが tf.function の内部で発生した場合は InaccessibleTensorError を発生させます。ただし、上記のパターン 1 を回避するために、エラーを発生させる可能性のある tf.function外部でこの初期値のキャッシュが発生するように、コードを誤って構造化することがよくあることに注意してください。そのため、プログラムがこのパターンの影響を受けやすいことがわかっている場合は、特に注意してください。

このパターンの一般的な解決策は、コードを再構築するか、必要に応じて Python callable を使用して、値が誤ってキャッシュされるのではなく、毎回再計算されるようにすることです。

例 1: グローバルステップに依存する、学習率やハイパーパラメータなどのスケジュール

次のコードスニペットでは、セッションが実行されるたびに最新の global_step 値が読み取られ、新しい学習率が計算されることが期待されます。

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(...)

ただし、eager に切り替えようとする場合は、意図したスケジュールに従うのではなく、学習率が 1 回だけ計算されてから再利用されることに注意してください。

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)
  ...

この特定の例は一般的なパターンであり、オプティマイザは各トレーニングステップではなく 1 回だけ初期化する必要があるため、TF2 オプティマイザは学習率やその他のハイパーパラメータの引数として tf.keras.optimizers.schedules.LearningRateSchedule スケジュールまたは Python callable をサポートします。

例 2: オブジェクト属性として割り当てられ、ポインターを介して再利用されるシンボリック乱数の初期化が、eager への切り替え時に誤ってキャッシュされる

次の NoiseAdder モジュールを考えてみましょう。

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

TF1.x で次のように使用すると、セッションが実行されるたびに新しいランダムノイズテンソルが計算されます。

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(...)

ただし、TF2 では、最初に noise_adder を初期化すると、noise_distribution が 1 回だけ計算され、すべてのトレーニングステップで凍結されます。

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

これを修正するには、毎回同じテンソルオブジェクトを参照する代わりに、新しいランダムテンソルが必要になるたびに NoiseAdder を呼び出すように tf.random.normal をリファクタリングします。

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

パターン 3: TF1.x コードはテンソルに直接依存し、名前で検索する

TF1.x コードテストでは、グラフに存在するテンソルまたは演算のチェックに依存するのが一般的です。まれに、モデリングコードもこれらの名前による検索に依存することがあります。

tf.function の外で eagerly に実行する場合、テンソル名はまったく生成されないため、tf.Tensor.name のすべての使用は tf.function 内で発生する必要があります。実際に生成された名前は、同じ tf.function 内であっても TF1.x と TF2 の間で異なる可能性が非常に高く、API 保証は TF バージョン間で生成された名前の安定性を保証しないことに注意してください。

注意: 変数名は tf.function の外部でも生成されますが、モデルマッピングガイドの関連セクションに従う場合を除き、その名前が TF1.x と TF2 の間で一致することは保証されません。

パターン 4: TF1.x セッションが、生成されたグラフの一部のみを選択的に実行する

TF1.x では、グラフを構築し、グラフ内のすべての演算を実行する必要のない入力と出力のセットを選択することにより、セッションでそのサブセットのみを選択的に実行することを選択できます。

例えば、1 つのグラフ内にジェネレータとディスクリミネータの両方を持ち、個別の tf.compat.v1.Session.run 呼び出しを使用して、ディスクリミネータのみのトレーニングとジェネレータのみのトレーニングを交互に行うことができます。

TF2 では、tf.function の自動制御の依存関係と Eager execution により、tf.function トレースの選択的な枝刈りはありません。例えば、ディスクリミネータまたはジェネレータの出力のみが tf.function から出力される場合でも、すべての変数の更新を含む完全なグラフが実行されます。

したがって、プログラムのさまざまな部分を含む複数の tf.function を使用するか、実際に実行したいものだけを実行するために分岐する tf.function への条件付き引数を使用する必要があります。

コレクションの削除

Eager execution が有効になっている場合、グラフコレクション関連の compat.v1 API(tf.compat.v1.trainable_variables などの内部でコレクションを読み書きする API を含む)は使用できなくなります。ValueError を発生させるものもあれば、警告なしに空のリストを返すものもあります。

TF1.x でのコレクションの最も標準的な使用法は、初期化子、グローバルステップ、重み、正則化損失、モデル出力損失、および BatchNormalization レイヤーなどから実行する必要がある変数の更新を維持することです。

これらの標準的な使用法をそれぞれ処理するには、次のようにします。

  1. 初期化子 - 無視します。Eager execution が有効になっている場合、変数の手動初期化は必要ありません。
  2. グローバルステップ - 移行手順については、tf.compat.v1.train.get_or_create_global_step のドキュメントをご覧ください。
  3. 重み - モデルマッピングガイドのガイダンスに従って、モデルを tf.Module/tf.keras.layers.Layer/ tf.keras.Model にマップし、tf.module.trainable_variables などのそれぞれの重み追跡の仕組みを使用します。
  4. 正則化損失 - モデルマッピングガイドのガイダンスに従ってモデルを tf.Module/ tf.keras.layers.Layer/ tf.keras.Model にマッピングし、 tf.keras.losses を使用します。または、正則化損失を手動で追跡することもできます。
  5. モデル出力損失 - tf.keras.Model 損失管理の仕組みを使用するか、コレクションを使用せずに損失を個別に追跡します。
  6. 重みの更新 - このコレクションを無視します。Eager execution と tf.function(autograph と auto-control-dependencies を使用)は、すべての変数の更新が自動的に実行されることを意味します。そのため、最後にすべての重みの更新を明示的に実行する必要はありませんが、コントロールの依存関係の使用方法によっては、TF1.x コードとは異なるタイミングで重みの更新が行われる可能性があることに注意してください。
  7. 概要 - 移行概要 API ガイドを参照してください。

より複雑なコレクションの使用(カスタムコレクションの使用など)では、コードをリファクタリングして、独自のグローバルストアを維持するか、グローバルストアにまったく依存しないようにする必要がある場合があります。

ReferenceVariables の代わりに ResourceVariables

ResourceVariables には、ReferenceVariables よりも強力な読み書き一貫性保証があります。これにより、変数を使用するときに以前の書き込みの結果を観察するかどうかについて、より予測可能で推論しやすいセマンティクスが得られます。この変更により、既存のコードでエラーが発生したり、 警告なしに中断したりする可能性はほとんどありません。

ただし、これらの強力な一貫性の保証により、特定のプログラムのメモリ使用量が増加する可能性は低いですが、あります。これが当てはまる場合は、問題を提出してください。さらに、変数の読み取りに対応するグラフ内の演算子名に対する正確な文字列比較に依存する単体テストがある場合は、リソース変数を有効にすると、これらの演算子名がわずかに変更される可能性があることに注意してください。

コードに対するこの動作変更の影響を分離するために、Eager execution が無効になっている場合、tf.compat.v1.disable_resource_variables() および tf.compat.v1.enable_resource_variables() を使用して、この動作変更をグローバルに無効または有効にすることができます。Eager execution が有効になっている場合、ResourceVariables は常に使用されます。

制御フロー v2

TF1.x では、tf.condtf.while_loop などの制御フロー演算は、SwitchMerge などのインラインの低レベル演算です。TF2 は、すべてのブランチに対して個別の tf.function トレースで実装され、高次微分をサポートする、改善された関数制御フロー演算を提供します。

コードに対するこの動作変更の影響を分離するために、eager execution が無効になっている場合、tf.compat.v1.disable_control_flow_v2() および tf.compat.v1.enable_control_flow_v2() を使用して、この動作変更をグローバルに無効または有効にすることができます。ただし、eager execution も無効になっている場合にのみ、制御フロー v2 を無効にできます。Eager execution を有効にすると、制御フロー v2 が常に使用されます。

この動作の変更により、制御フローを使用する生成された TF プログラムの構造が劇的に変化する可能性があります。これは、1 つのフラットグラフではなく、複数のネストされた関数トレースが含まれるためです。そのため、生成されたトレースの正確なセマンティクスに大きく依存するコードは、何らかの変更が必要になる場合があります。これには次が含まれます。

  • 演算子名とテンソル名に依存するコード
  • そのブランチの外側から TensorFlow 制御フローのブランチ内で作成されたテンソルを参照するコード。これは InaccessibleTensorError を生成する可能性があります

この動作の変更は、パフォーマンスをニュートラルから肯定的にすることを目的としていますが、制御フロー v2 のパフォーマンスが TF1.x 制御フローよりも悪いという問題が発生した場合は、再現手順で問題を報告してください。

TensorShape API の動作の変更

TensorShape クラスは、tf.compat.v1.Dimension オブジェクトの代わりに int を保持するように単純化されました。したがって、int を取得するために .value を呼び出す必要はありません。

個々の tf.compat.v1.Dimension オブジェクトは依然として tf.TensorShape.dims からアクセス可能です。

コードに対するこの動作変更の影響を分離するには、tf.compat.v1.disable_v2_tensorshape() および tf.compat.v1.enable_v2_tensorshape() を使用して、この動作変更をグローバルに無効または有効にすることができます。

以下は、TF1.x と TF2 の違いを示しています。

import tensorflow as tf
2024-01-11 17:45:44.989156: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
2024-01-11 17:45:44.989204: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
2024-01-11 17:45:44.990677: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
# Create a shape and choose an index
i = 0
shape = tf.TensorShape([16, None, 256])
shape
TensorShape([16, None, 256])

TF1.x に以下があるとします。

value = shape[i].value

TF2 ではこのようになります。

value = shape[i]
value
16

TF1.x に以下があるとします。

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

TF2 ではこのようになります。

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

TF1.x に以下があるとします(またはその他の次元メソッドを使用していたとします)。

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

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

tf.TensorShape のブール型の値は、階数がわかっている場合は Trueで、そうでない場合は 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

TensorShape の変更による潜在的なエラー

TensorShape の動作の変更によって、コードが警告なしに壊れることはほとんどありません。ただし、形状関連のコードが AttributeError を発生させ始めるかもしれません。int および Nonetf.compat.v1.Dimension と同じ属性を持っていません。以下に、これらの AttributeError の例をいくつか示します。

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'

値によるテンソルの等価性

変数とテンソルのバイナリ == および != 演算子は、TF1.x でのようにオブジェクト参照で比較するのではなく、TF2 では値で比較するように変更されました。さらに、テンソルと変数は、値でハッシュすることができない可能性があるため、セットまたは dict キーで直接ハッシュ可能または使用可能ではなくなりました。代わりに、テンソルまたは変数へのハッシュ可能な参照を取得するために使用できる .ref() メソッドを公開します。

この動作変更の影響を分離するために、tf.compat.v1.disable_tensor_equality() および tf.compat.v1.enable_tensor_equality() を使用して、この動作変更をグローバルに無効または有効にすることができます。

例えば、TF1.x では、== 演算子を使用すると、同じ値を持つ 2 つの変数は false を返します。

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

x == y
False

テンソル等価性チェックが有効になっている TF2 では、x == yTrue を返します。

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>

したがって、TF2 では、オブジェクト参照で比較する必要がある場合は、必ず isis not を使用してください。

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

x is y
False

テンソルと変数のハッシュ

TF1.x の動作により、set キーや dict キーなど、ハッシュを必要とするデータ構造に変数とテンソルを直接追加できました。

x = tf.Variable(0.0)
set([x, tf.constant(2.0)])

ただし、テンソルの等価性が有効になっている TF2 では、== および != 演算子のセマンティクスが値の等価性チェックに変更されるため、テンソルと変数はハッシュ不可になります。

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 variable.ref() as the key. (Variable: <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>)

したがって、TF2 では、テンソルまたは変数オブジェクトをキーまたは set のコンテキストとして使用する必要がある場合、tensor.ref() を使用して、キーとして使用できるハッシュ可能な参照を取得できます。

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>>}

必要に応じて、reference.deref() を使用して参照からテンソルまたは変数を取得することもできます。

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

リソースとその他の文献

  • TF1.x から TF2 への移行の詳細については、TF2 への移行セクションをご覧ください。
  • モデルマッピングガイドを読んで、TF1.x モデルをマッピングして TF2 で直接動作させる方法を学習してください。