{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "Tce3stUlHN0L" }, "source": [ "##### Copyright 2021 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "cellView": "form", "execution": { "iopub.execute_input": "2024-01-11T17:45:44.555656Z", "iopub.status.busy": "2024-01-11T17:45:44.555082Z", "iopub.status.idle": "2024-01-11T17:45:44.558628Z", "shell.execute_reply": "2024-01-11T17:45:44.558025Z" }, "id": "tuOe1ymfHZPu" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "D70XgUYdLwI6" }, "source": [ "# TensorFlow 1.x と TensorFlow 2 の比較 - 動作と API" ] }, { "cell_type": "markdown", "metadata": { "id": "MfBg1C5NB3X0" }, "source": [ "\n", " \n", " \n", " \n", " \n", "
TensorFlow.org で表示 Google Colab で実行 GitHub でソースを表示ノートブックをダウンロード
" ] }, { "cell_type": "markdown", "metadata": { "id": "akxmN3SQsEcb" }, "source": [ "内部では、TensorFlow 2 は TF1.x とは根本的に異なるプログラミングパラダイムに従っています。\n", "\n", "このガイドでは、動作と API に関する TF1.x と TF2 の基本的な違いと、これらすべてが移行過程にどのように関連するかについて説明します。" ] }, { "cell_type": "markdown", "metadata": { "id": "Xzy2mT87mwth" }, "source": [ "## 主な変更点の全体的な概要\n", "\n", "基本的に、TF1.x と TF2 は、実行(TF2 では eager)、変数、制御フロー、テンソル形状、およびテンソル等価比較に関して、異なる一連のランタイム動作を使用します。TF2 と互換性を持たせるには、コードが TF2 動作の完全なセットと互換性がある必要があります。移行中に、これらの動作のほとんどを `tf.compat.v1.enable_*` または `tf.compat.v1.disable_*` API を介して個別に有効または無効にすることができます。1 つの例外は、コレクションの削除です。これは、Eager execution の有効化/無効化の副作用です。\n", "\n", "TensorFlow 2 の概要は次のとおりです。\n", "\n", "- [冗長な API](https://github.com/tensorflow/community/blob/master/rfcs/20180827-api-names.md) を削除します。\n", "- API の一貫性を高めます。例えば、[統合 RNN](https://github.com/tensorflow/community/blob/master/rfcs/20180920-unify-rnn-interface.md) や[統合オプティマイザ](https://github.com/tensorflow/community/blob/master/rfcs/20181016-optimizer-unification.md)などです。\n", "- [セッションよりも関数](https://github.com/tensorflow/community/blob/master/rfcs/20180918-functions-not-sessions-20.md)を優先し、グラフとコンパイルの自動制御依存関係を提供する `tf.function` ととも に Eager execution がデフォルトで有効になっている Python ランタイムとの統合を改善します。\n", "- グローバルグラフ[コレクション](https://github.com/tensorflow/community/blob/master/rfcs/20180905-deprecate-collections.md)を非推奨にします。\n", "- [`ReferenceVariables` よりも `ResourceVariables` ](https://github.com/tensorflow/community/blob/master/rfcs/20180817-variables-20.md)を使用して、変数の同時実行のセマンティクスを変更します。\n", "- [関数ベース](https://github.com/tensorflow/community/blob/master/rfcs/20180507-cond-v2.md)の微分可能な[制御フロー](https://github.com/tensorflow/community/blob/master/rfcs/20180821-differentiable-functional-while.md)(制御フロー v2)をサポートします。\n", "- `tf.compat.v1.Dimension` オブジェクトの代わりに `int` を保持するように TensorShape API を簡素化します。\n", "- テンソルの等価性の仕組みを更新します。TF1.x では、テンソルと変数の `==` 演算子がオブジェクト参照の等価性をチェックします。TF2 では、値の等価性をチェックします。さらに、テンソル/変数はハッシュ可能ではなくなりました。しかし、テンソル/変数をセットで、または `dict` キーとして使用する必要がある場合は、`var.ref()` 経由でそれらへのハッシュ可能なオブジェクト参照を取得できます。\n", "\n", "以下のセクションでは、TF1.x と TF2 の相違点についてさらに説明します。TF2 の設計プロセスの詳細については、[RFC](https://github.com/tensorflow/community/pulls?utf8=%E2%9C%93&q=is%3Apr) と[設計ドキュメント](https://github.com/tensorflow/community/tree/master/rfcs)をご覧ください。" ] }, { "cell_type": "markdown", "metadata": { "id": "dlCiIgEE2OhY" }, "source": [ "## API のクリーンアップ\n", "\n", "TF2 では、多数の API が取り除かれたか移行されています。主な変更点には、現在ではオープンソースとなった [absl-py](https://github.com/abseil/abseil-py) の導入による `tf.app`、`tf.flags`、および `tf.logging` の削除、`tf.contrib` にあったプロジェクトの移植、使用頻度の低い関数を `tf.math` などのサブパッケージに移動することによるメインの `tf.*` 名前空間のクリーンアップなどがあります。一部の API は TF2 バージョンの `tf.summary`、`tf.keras.metrics`、および `tf.keras.optimizers` に置き換えられました。\n", "\n", "### `tf.compat.v1`: レガシーおよび互換性 API エンドポイント\n", "\n", "`tf.compat` および `tf.compat.v1` 名前空間のシンボルは、TF2 API とは見なされません。これらの名前空間は、互換性シンボルの組み合わせと、TF 1.x のレガシー API エンドポイントを公開します。これらは、TF1.x から TF2 への移行を支援することを目的としています。ただし、これらの `compat.v1` API はいずれも慣用的な TF2 API ではないため、新しい TF2 コードを記述するために使用しないでください。\n", "\n", "個々の `tf.compat.v1` シンボルは、TF2 動作が有効になっていても動作し続けるため(`tf.compat.v1.losses.mean_squared_error` など)、TF2 と互換性がある場合がありますが、他のものは TF2 と互換性がありません(`tf.compat.v1.metrics.accuracy`)。多くの `compat.v1` シンボル(すべてではありません)のドキュメントには、TF2 動作との互換性の程度と、それらを TF2 API に移行する方法を説明する専用の移行情報が含まれています。\n", "\n", "[TF2 アップグレードスクリプト](https://www.tensorflow.org/guide/migrate/upgrade)は、多くの `compat.v1` API シンボルを、それらがエイリアスであるか、同じ引数を持っていても順序が異なる場合に同等の TF2 API にマップできます。アップグレードスクリプトを使用して、TF1.x API の名前を自動的に変更することもできます。\n", "\n", "### False friend(空似言葉)API\n", "\n", "TF2 `tf` 名前空間(`compat.v1` の下ではない)には、内部で TF2 の動作を実際に無視する、TF2 の動作の完全なセットと完全に互換性がない、またはその両方の一連の「false-friend」シンボルがあります。そのため、これらの API は、潜在的にサイレントな方法で、TF2 コードで誤動作する可能性があります。\n", "\n", "- `tf.estimator.*`: Estimator は内部でグラフとセッションを作成して使用します。そのため、これらは TF2 互換と見なされるべきではありません。コードが Estimator を実行している場合、TF2 の動作は使用されません。\n", "- `keras.Model.model_to_estimator(...)`: これは内部で Estimator を作成しますが、上記にあるように、TF2 互換ではありません。\n", "- `tf.Graph().as_default()`: これは TF1.x グラフの動作をし、標準の TF2 互換の `tf.function` の動作には従いません。このようなグラフに入るコードは、通常、セッションを介してグラフを実行し、TF2 互換と見なすべきではありません。\n", "- `tf.feature_column.*` 特徴量列 API は一般に TF1 スタイルの `tf.compat.v1.get_variable` 変数の作成に依存し、作成された変数はグローバルコレクション経由でアクセスされると想定します。TF2 はコレクションをサポートしていないため、TF2 の動作を有効にして API を実行すると、API が正しく機能しない場合があります。\n", "\n", "### その他の API の変更\n", "\n", "- TF2 は、`tf.colocate_with` の使用を不要にするデバイス配置アルゴリズムの大幅な改善を特徴としています。削除によりパフォーマンスが低下する場合は、[バグを報告してください](https://github.com/tensorflow/tensorflow/issues)。\n", "\n", "- `tf.v1.ConfigProto` のすべての使用を `tf.config` からの同等の関数に置換します。" ] }, { "cell_type": "markdown", "metadata": { "id": "RxEU79Rd83Yz" }, "source": [ "## Eager execution\n", "\n", "TF1.x では、tf.* API 呼び出しを作成して抽象構文ツリー(グラフ)を手動でつなぎ合わせ、出力テンソルと入力テンソルのセットを `session.run` 呼び出しに渡し、抽象構文ツリーを手動でコンパイルする必要がありました。TF2 はこれを(Python が通常行うように)eagerly に実行し 、グラフとセッションは実装の詳細のような感覚になっています。\n", "\n", "Eager execution の副産物として注目しておきたいのは、`tf.control_dependencies` が不要になったという点です。これは、コードのすべての行が順に実行されるようになったためです(`tf.function` 内では、副次的影響のあるコードは記述された順に実行されます)。" ] }, { "cell_type": "markdown", "metadata": { "id": "LH3YizX-9S7g" }, "source": [ "## global の排除\n", "\n", "TF1.X では、グローバル名前空間とコレクションに暗黙的に大きく依存していました。`tf.Variable` を呼び出すと、デフォルトのグラフにあるコレクションに配置され、それをポイントする Python 変数を追跡できなくなってもグラフに残っていました。その `tf.Variable` は復元できますが、その作成に使用された名前がわかっている場合のみでした。変数の作成を管理していないユーザーにとっては困難なことでした。その結果、変数をもう一度見つけ出すためのさまざまな仕組みが生まれただけでなく、変数スコープ、グローバルコレクション、`tf.get_global_step` のようなヘルパーメソッド、`tf.global_variables_initializer`、すべてのトレーニング可能な変数の勾配を暗黙的に計算するオプティマイザなど、ユーザー作成変数を検索するフレームワークが急増しました。TF2 は、これらすべての仕組み([Variables 2.0 RFC](https://github.com/tensorflow/community/pull/11))を排除し、自分の変数は自分で追跡するというデフォルトの仕組みを採択します。`tf.Variable` を追跡できなくなると、ガベージコレクションによって収集されます。\n", "\n", "変数を追跡する必要があるため、余分な作業が発生しますが、[モデリングシム](./model_mapping.ipynb)などのツールと、[`tf.Module` および `tf.keras.layers.Layer` の暗黙的なオブジェクト指向変数コレクション](https://www.tensorflow.org/guide/intro_to_modules)などの動作により、負担は最小限に抑えられます。" ] }, { "cell_type": "markdown", "metadata": { "id": "NXwBgAjJ98J2" }, "source": [ "## セッションではなく関数\n", "\n", "`session.run` 呼び出しは、ほぼ関数呼び出しと変わりません。入力と呼び出される関数を指定すると、一連の出力が返されます。TF2 では、`tf.function` を使って、TensorFlow が単一のグラフとして実行できるように Python 関数に JIT コンパイルのマークをつけます([Functions 2.0 RFC](https://github.com/tensorflow/community/pull/20))。この仕組みにより、TF2 は Graph モードのすべてのメリットを得ることができます。\n", "\n", "- パフォーマンス: 関数を最適化できます(ノード枝狩り、カーネル融合など)\n", "- 移植性: 関数をエクスポート/再インポート([SavedModel 2.0 RFC](https://github.com/tensorflow/community/pull/34))できるため、モジュール型 TensorFlow 関数を再利用し共有することができます。\n", "\n", "```python\n", "# TF1.x\n", "outputs = session.run(f(placeholder), feed_dict={placeholder: input})\n", "# TF2\n", "outputs = f(input)\n", "```\n", "\n", "Python と TensorFlow コードを自由に混在させられるため、Python の表現力を活用できます。ただし、移植可能な TensorFlow は、モバイル、C++、JavaScript など、Python インタープリターを使用しないコンテキストで実行されます。`tf.function`を追加する際にコードの書き直しを避けるには、[AutoGraph](https://tensorflow.org/guide/function) を使用して、Python コンストラクトのサブセットを TensorFlow の同等のものに変換します。\n", "\n", "- `for`/`while` -> `tf.while_loop` (`break` と `continue` はサポートされています)\n", "- `if `-> `tf.cond`\n", "- `for _ in dataset` -> `dataset.reduce`\n", "\n", "AutoGraph では制御フローを任意にネストできるため、シーケンスモデル、強化学習、カスタムトレーニングループなど、多くの複雑な ML プログラムを効率的かつ簡潔に実装することができます。" ] }, { "cell_type": "markdown", "metadata": { "id": "Mj3gaj4tpi7O" }, "source": [ "## TF 2.x の動作変更への適応\n", "\n", "TF2 への移行は、TF2 の動作の完全なセットに移行して初めて完了します。`tf.compat.v1.enable_v2_behaviors` および `tf.compat.v1.disable_v2_behaviors` を介して、動作の完全なセットを有効または無効にすることができます。以下のセクションでは、それぞれの主要な動作の変更について詳しく説明します。" ] }, { "cell_type": "markdown", "metadata": { "id": "_M0zEtR9p0XD" }, "source": [ "### `tf.function` の使用\n", "\n", "移行中のプログラムへの最大の変更は、基本的なプログラミング モデルのパラダイムグラフとセッションから Eager execution と `tf.function` へのシフトから生じる可能性があります。Eager execution および `tf.function` と互換性のない API からそれらと互換性のある API への移行の詳細については、[TF2 移行ガイド](https://tensorflow.org/guide/migrate)を参照してください。\n", "\n", "注意: 移行中に、`tf.compat.v1.enable_eager_execution` と `tf.compat.v1.disable_eager_execution` を使用して eager execusion を直接有効または無効にすることを選択できますが、これはプログラムの有効期間中に一度だけ行うことができます。\n", "\n", "以下は、`tf.Graph` および `tf.compat.v1.Session` から `tf.function` による Eager execution に切り替える場合に問題を引き起こす可能性がある、いずれかの API に関連付けられていない一般的なプログラムパターンの一部です。" ] }, { "cell_type": "markdown", "metadata": { "id": "UgwEtwwN2PWy" }, "source": [ "#### パターン 1: 1 回のみ実行することを目的とした Python オブジェクトの操作と変数の作成が、複数回実行される。\n", "\n", "\n", "\n", "グラフとセッションに依存する TF1.x プログラムでは、通常、プログラム内のすべての Python ロジックが 1 回だけ実行されることが期待されます。ただし、Eager execution および `tf.function` を使用すると、Python ロジックは少なくとも 1 回実行されますが、場合によってはそれ以上の回数(eagerly に複数回、または異なる `tf.function` トレース全体で複数回)が実行される可能性があります。時には、`tf.function` が同じ入力を 2 回トレースし、予期しない動作を引き起こすことさえあります(例 1 と 2 をご覧ください)。詳細については、`tf.function` [ガイド](https://www.tensorflow.org/guide/function)を参照してください。\n", "\n", "注意: 通常、このパターンにより、`tf.function` なしで eagerly に実行すると、コードが警告なしに誤動作しますが、問題のあるコードを `tf.function` 内にラップしようとすると、一般に `InaccessibleTensorError` または `ValueError` が発生します。この問題を発見してデバッグするには、早い段階でコードを `tf.function` でラップし、[pdb](https://docs.python.org/3/library/pdb.html) または対話型デバッグを使用して `InaccessibleTensorError` のソースを特定することをお勧めします。\n", "\n", "**例 1: 変数の作成**\n", "\n", "関数が呼び出されたときに変数を作成する以下の例を考えてみましょう。\n", "\n", "```python\n", "def f():\n", " v = tf.Variable(1.0)\n", " return v\n", "\n", "with tf.Graph().as_default():\n", " with tf.compat.v1.Session() as sess:\n", " res = f()\n", " sess.run(tf.compat.v1.global_variables_initializer())\n", " sess.run(res)\n", "```\n", "\n", "ただし、変数の作成を含む上記の関数を単純に `tf.function` でラップすることは許可されていません。`tf.function`は、[最初の呼び出しでのシングルトン変数の作成](https://www.tensorflow.org/guide/function#creating_tfvariables)のみをサポートします。これを強制するには、tf.function が最初の呼び出しで変数の作成を検出すると、再度トレースを試行し、2 番目のトレースで変数の作成がある場合はエラーを発生させます。\n", "\n", "```python\n", "@tf.function\n", "def f():\n", " print(\"trace\") # This will print twice because the python body is run twice\n", " v = tf.Variable(1.0)\n", " return v\n", "\n", "try:\n", " f()\n", "except ValueError as e:\n", " print(e)\n", "```\n", "\n", "回避策として、変数を最初の呼び出しで作成した後にキャッシュして再利用します。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.v = None\n", "\n", " @tf.function\n", " def __call__(self):\n", " print(\"trace\") # This will print twice because the python body is run twice\n", " if self.v is None:\n", " self.v = tf.Variable(0)\n", " return self.v\n", "\n", "m = Model()\n", "m()\n", "```\n", "\n", "**例 2: `tf.function` の再トレースによる範囲外のテンソル**\n", "\n", "例 1 で示したように、最初の呼び出しで変数の作成を検出すると、`tf.function` は再トレースします。2 つのトレースで 2 つのグラフが作成されるため、これはさらに混乱を招く可能性があります。再トレースからの 2 番目のグラフが最初のトレース中に生成されたグラフからテンソルにアクセスしようとすると、Tensorflow ではテンソルが範囲外であるというエラーを発生します。シナリオを示すために、以下のコードは最初の `tf.function` 呼び出しでデータセットを作成します。これは期待どおりに実行されます。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.dataset = None\n", "\n", " @tf.function\n", " def __call__(self):\n", " print(\"trace\") # This will print once: only traced once\n", " if self.dataset is None:\n", " self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])\n", " it = iter(self.dataset)\n", " return next(it)\n", "\n", "m = Model()\n", "m()\n", "```\n", "\n", "ただし、最初の `tf.function` 呼び出しで変数を作成しようとすると、コードはデータセットが範囲外であることを通知するエラーを発生させます。これは、データセットが最初のグラフにあり、2 番目のグラフもそれにアクセスしようとしているためです。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.v = None\n", " self.dataset = None\n", "\n", " @tf.function\n", " def __call__(self):\n", " print(\"trace\") # This will print twice because the python body is run twice\n", " if self.v is None:\n", " self.v = tf.Variable(0)\n", " if self.dataset is None:\n", " self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])\n", " it = iter(self.dataset)\n", " return [self.v, next(it)]\n", "\n", "m = Model()\n", "try:\n", " m()\n", "except TypeError as e:\n", " print(e) # is out of scope and cannot be used here.\n", "```\n", "\n", "最も簡単な解決策は、変数の作成とデータセットの作成が両方とも `tf.function` 呼び出しの外にあることを確認することです。例えば、次のとおりです。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.v = None\n", " self.dataset = None\n", "\n", " def initialize(self):\n", " if self.dataset is None:\n", " self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])\n", " if self.v is None:\n", " self.v = tf.Variable(0)\n", "\n", " @tf.function\n", " def __call__(self):\n", " it = iter(self.dataset)\n", " return [self.v, next(it)]\n", "\n", "m = Model()\n", "m.initialize()\n", "m()\n", "```\n", "\n", "ただし、`tf.function` で変数を作成することが避けられない場合があります(一部の [TF keras オプティマイザ](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Optimizer#slots)のスロット変数など)。それでも、データセットの作成を `tf.function` 呼び出しの外に移動するだけです。これに依存できる理由は、`tf.function` が暗黙的な入力としてデータセットを受け取り、両方のグラフが適切にアクセスできるためです。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.v = None\n", " self.dataset = None\n", "\n", " def initialize(self):\n", " if self.dataset is None:\n", " self.dataset = tf.data.Dataset.from_tensors([1, 2, 3])\n", "\n", " @tf.function\n", " def __call__(self):\n", " if self.v is None:\n", " self.v = tf.Variable(0)\n", " it = iter(self.dataset)\n", " return [self.v, next(it)]\n", "\n", "m = Model()\n", "m.initialize()\n", "m()\n", "```\n", "\n", "**例 3: dict の使用による予期しない Tensorflow オブジェクトの再作成**\n", "\n", "`tf.function` は、リストへの追加、ディクショナリへのチェック/追加など、Python の副作用に対するサポートが非常に貧弱です。詳細は[「tf.function によるパフォーマンスの向上」](https://www.tensorflow.org/guide/function#executing_python_side_effects)にあります。以下の例では、コードはディクショナリを使用してデータセットとイテレータをキャッシュしています。同じキーの場合、モデルへの各呼び出しは、データセットの同じイテレータを返します。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.datasets = {}\n", " self.iterators = {}\n", "\n", " def __call__(self, key):\n", " if key not in self.datasets:\n", " self.datasets[key] = tf.compat.v1.data.Dataset.from_tensor_slices([1, 2, 3])\n", " self.iterators[key] = self.datasets[key].make_initializable_iterator()\n", " return self.iterators[key]\n", "\n", "with tf.Graph().as_default():\n", " with tf.compat.v1.Session() as sess:\n", " m = Model()\n", " it = m('a')\n", " sess.run(it.initializer)\n", " for _ in range(3):\n", " print(sess.run(it.get_next())) # prints 1, 2, 3\n", "```\n", "\n", "ただし、上記のパターンは `tf.function` では期待どおりに機能しません。トレース中、`tf.function` はディクショナリへの追加による Python の副作用を無視します。代わりに、新しいデータセットとイテレータの作成のみを記憶します。その結果、モデルへの各呼び出しは常に新しいイテレータを返します。この問題は、数値結果またはパフォーマンスが十分に重要でない限り、気づきにくいものです。したがって、`tf.function` を単純に Python コードにラップする前に、コードについて慎重に検討することをお勧めします。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.datasets = {}\n", " self.iterators = {}\n", "\n", " @tf.function\n", " def __call__(self, key):\n", " if key not in self.datasets:\n", " self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])\n", " self.iterators[key] = iter(self.datasets[key])\n", " return self.iterators[key]\n", "\n", "m = Model()\n", "for _ in range(3):\n", " print(next(m('a'))) # prints 1, 1, 1\n", "```\n", "\n", "[`tf.init_scope`](https://www.tensorflow.org/api_docs/python/tf/init_scope) を使用して、期待される動作を実現するために、データセットとイテレータの作成をグラフの外に持ち上げることができます。\n", "\n", "```python\n", "class Model(tf.Module):\n", " def __init__(self):\n", " self.datasets = {}\n", " self.iterators = {}\n", "\n", " @tf.function\n", " def __call__(self, key):\n", " if key not in self.datasets:\n", " # Lifts ops out of function-building graphs\n", " with tf.init_scope():\n", " self.datasets[key] = tf.data.Dataset.from_tensor_slices([1, 2, 3])\n", " self.iterators[key] = iter(self.datasets[key])\n", " return self.iterators[key]\n", "\n", "m = Model()\n", "for _ in range(3):\n", " print(next(m('a'))) # prints 1, 2, 3\n", "```\n", "\n", "一般的な経験則は、ロジックで Python の副作用に依存することを避け、それらをトレースのデバッグにのみ使用することです。\n", "\n", "**例 4: グローバル Python リストの操作**\n", "\n", "次の TF1.x コードは、現在のトレーニングステップによって生成された損失のリストのみを維持するために使用する損失のグローバルリストを使用します。リストに損失を追加する Python ロジックは、セッションが実行されるトレーニングステップの数に関係なく、1 回だけ呼び出されることに注意してください。\n", "\n", "```python\n", "all_losses = []\n", "\n", "class Model():\n", " def __call__(...):\n", " ...\n", " all_losses.append(regularization_loss)\n", " all_losses.append(label_loss_a)\n", " all_losses.append(label_loss_b)\n", " ...\n", "\n", "g = tf.Graph()\n", "with g.as_default():\n", " ...\n", " # initialize all objects\n", " model = Model()\n", " optimizer = ...\n", " ...\n", " # train step\n", " model(...)\n", " total_loss = tf.reduce_sum(all_losses)\n", " optimizer.minimize(total_loss)\n", " ...\n", "...\n", "sess = tf.compat.v1.Session(graph=g)\n", "sess.run(...)\n", "```\n", "\n", "ただし、この Python ロジックが Eager execution で単純に TF2 にマッピングされている場合、損失のグローバルリストには各トレーニングステップで新しい値が追加されます。これは、以前はリストに現在のトレーニングステップからの損失のみが含まれると想定していたトレーニングステップコードが、これまでに実行されたすべてのトレーニングステップからの損失のリストを実際に確認するようになったことを意味します。これは意図しない動作の変更であり、各ステップの開始時にリストをクリアするか、トレーニングステップに対してローカルにする必要があります。\n", "\n", "```python\n", "all_losses = []\n", "\n", "class Model():\n", " def __call__(...):\n", " ...\n", " all_losses.append(regularization_loss)\n", " all_losses.append(label_loss_a)\n", " all_losses.append(label_loss_b)\n", " ...\n", "\n", "# initialize all objects\n", "model = Model()\n", "optimizer = ...\n", "\n", "def train_step(...)\n", " ...\n", " model(...)\n", " total_loss = tf.reduce_sum(all_losses) # global list is never cleared,\n", " # Accidentally accumulates sum loss across all training steps\n", " optimizer.minimize(total_loss)\n", " ...\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "qaYnjPo-tmTI" }, "source": [ "#### パターン 2: TF1.x のすべてのステップで再計算されることを意図したシンボリックテンソルが、eager に切り替わる際に誤って初期値でキャッシュされる。\n", "\n", "\n", "\n", "このパターンは通常、コードを tf.functions の外部で eagerly に実行すると、警告なしに誤動作を引き起こしますが、初期値のキャッシュが `tf.function` の内部で発生した場合は `InaccessibleTensorError` を発生させます。ただし、上記の[パターン 1](#pattern-1) を回避するために、エラーを発生させる可能性のある tf.function外部でこの初期値のキャッシュが発生するように、コードを誤って構造化することがよくあることに注意してください。そのため、プログラムがこのパターンの影響を受けやすいことがわかっている場合は、特に注意してください。\n", "\n", "このパターンの一般的な解決策は、コードを再構築するか、必要に応じて Python callable を使用して、値が誤ってキャッシュされるのではなく、毎回再計算されるようにすることです。\n", "\n", "**例 1: グローバルステップに依存する、学習率やハイパーパラメータなどのスケジュール**\n", "\n", "次のコードスニペットでは、セッションが実行されるたびに最新の `global_step` 値が読み取られ、新しい学習率が計算されることが期待されます。\n", "\n", "```python\n", "g = tf.Graph()\n", "with g.as_default():\n", " ...\n", " global_step = tf.Variable(0)\n", " learning_rate = 1.0 / global_step\n", " opt = tf.compat.v1.train.GradientDescentOptimizer(learning_rate)\n", " ...\n", " global_step.assign_add(1)\n", "...\n", "sess = tf.compat.v1.Session(graph=g)\n", "sess.run(...)\n", "```\n", "\n", "ただし、eager に切り替えようとする場合は、意図したスケジュールに従うのではなく、学習率が 1 回だけ計算されてから再利用されることに注意してください。\n", "\n", "```python\n", "global_step = tf.Variable(0)\n", "learning_rate = 1.0 / global_step # Wrong! Only computed once!\n", "opt = tf.keras.optimizers.SGD(learning_rate)\n", "\n", "def train_step(...):\n", " ...\n", " opt.apply_gradients(...)\n", " global_step.assign_add(1)\n", " ...\n", "```\n", "\n", "この特定の例は一般的なパターンであり、オプティマイザは各トレーニングステップではなく 1 回だけ初期化する必要があるため、TF2 オプティマイザは学習率やその他のハイパーパラメータの引数として `tf.keras.optimizers.schedules.LearningRateSchedule` スケジュールまたは Python callable をサポートします。\n", "\n", "**例 2: オブジェクト属性として割り当てられ、ポインターを介して再利用されるシンボリック乱数の初期化が、eager への切り替え時に誤ってキャッシュされる**\n", "\n", "次の `NoiseAdder` モジュールを考えてみましょう。\n", "\n", "```python\n", "class NoiseAdder(tf.Module):\n", " def __init__(shape, mean):\n", " self.noise_distribution = tf.random.normal(shape=shape, mean=mean)\n", " self.trainable_scale = tf.Variable(1.0, trainable=True)\n", " \n", " def add_noise(input):\n", " return (self.noise_distribution + input) * self.trainable_scale\n", "```\n", "\n", "TF1.x で次のように使用すると、セッションが実行されるたびに新しいランダムノイズテンソルが計算されます。\n", "\n", "```python\n", "g = tf.Graph()\n", "with g.as_default():\n", " ...\n", " # initialize all variable-containing objects\n", " noise_adder = NoiseAdder(shape, mean)\n", " ...\n", " # computation pass\n", " x_with_noise = noise_adder.add_noise(x)\n", " ...\n", "...\n", "sess = tf.compat.v1.Session(graph=g)\n", "sess.run(...)\n", "```\n", "\n", "ただし、TF2 では、最初に `noise_adder` を初期化すると、`noise_distribution` が 1 回だけ計算され、すべてのトレーニングステップで凍結されます。\n", "\n", "```python\n", "...\n", "# initialize all variable-containing objects\n", "noise_adder = NoiseAdder(shape, mean) # Freezes `self.noise_distribution`!\n", "...\n", "# computation pass\n", "x_with_noise = noise_adder.add_noise(x)\n", "...\n", "```\n", "\n", "これを修正するには、毎回同じテンソルオブジェクトを参照する代わりに、新しいランダムテンソルが必要になるたびに `NoiseAdder` を呼び出すように `tf.random.normal` をリファクタリングします。\n", "\n", "```python\n", "class NoiseAdder(tf.Module):\n", " def __init__(shape, mean):\n", " self.noise_distribution = lambda: tf.random.normal(shape=shape, mean=mean)\n", " self.trainable_scale = tf.Variable(1.0, trainable=True)\n", " \n", " def add_noise(input):\n", " return (self.noise_distribution() + input) * self.trainable_scale\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "j2PXkSflCaCl" }, "source": [ "#### パターン 3: TF1.x コードはテンソルに直接依存し、名前で検索する\n", "\n", "\n", "\n", "TF1.x コードテストでは、グラフに存在するテンソルまたは演算のチェックに依存するのが一般的です。まれに、モデリングコードもこれらの名前による検索に依存することがあります。\n", "\n", "`tf.function` の外で eagerly に実行する場合、テンソル名はまったく生成されないため、`tf.Tensor.name` のすべての使用は `tf.function` 内で発生する必要があります。実際に生成された名前は、同じ `tf.function` 内であっても TF1.x と TF2 の間で異なる可能性が非常に高く、API 保証は TF バージョン間で生成された名前の安定性を保証しないことに注意してください。\n", "\n", "注意: 変数名は `tf.function` の外部でも生成されますが、[モデルマッピングガイド](./model_mapping.ipynb)の関連セクションに従う場合を除き、その名前が TF1.x と TF2 の間で一致することは保証されません。\n" ] }, { "cell_type": "markdown", "metadata": { "id": "5NB3bycl5Lde" }, "source": [ "#### パターン 4: TF1.x セッションが、生成されたグラフの一部のみを選択的に実行する\n", "\n", "\n", "\n", "TF1.x では、グラフを構築し、グラフ内のすべての演算を実行する必要のない入力と出力のセットを選択することにより、セッションでそのサブセットのみを選択的に実行することを選択できます。\n", "\n", "例えば、1 つのグラフ内にジェネレータとディスクリミネータの両方を持ち、個別の `tf.compat.v1.Session.run` 呼び出しを使用して、ディスクリミネータのみのトレーニングとジェネレータのみのトレーニングを交互に行うことができます。\n", "\n", "TF2 では、`tf.function` の自動制御の依存関係と Eager execution により、`tf.function` トレースの選択的な枝刈りはありません。例えば、ディスクリミネータまたはジェネレータの出力のみが `tf.function` から出力される場合でも、すべての変数の更新を含む完全なグラフが実行されます。\n", "\n", "したがって、プログラムのさまざまな部分を含む複数の `tf.function` を使用するか、実際に実行したいものだけを実行するために分岐する `tf.function` への条件付き引数を使用する必要があります。" ] }, { "cell_type": "markdown", "metadata": { "id": "CnNaUmROp5fV" }, "source": [ "### コレクションの削除\n", "\n", "Eager execution が有効になっている場合、グラフコレクション関連の `compat.v1` API(`tf.compat.v1.trainable_variables` などの内部でコレクションを読み書きする API を含む)は使用できなくなります。`ValueError` を発生させるものもあれば、警告なしに空のリストを返すものもあります。\n", "\n", "TF1.x でのコレクションの最も標準的な使用法は、初期化子、グローバルステップ、重み、正則化損失、モデル出力損失、および `BatchNormalization` レイヤーなどから実行する必要がある変数の更新を維持することです。\n", "\n", "これらの標準的な使用法をそれぞれ処理するには、次のようにします。\n", "\n", "1. 初期化子 - 無視します。Eager execution が有効になっている場合、変数の手動初期化は必要ありません。\n", "2. グローバルステップ - 移行手順については、`tf.compat.v1.train.get_or_create_global_step` のドキュメントをご覧ください。\n", "3. 重み - モデルマッピングガイドのガイダンスに従って、モデルを `tf.Module`/`tf.keras.layers.Layer`/ tf.keras.Model にマップし、`tf.module.trainable_variables` などのそれぞれの重み追跡の仕組みを使用します。\n", "4. 正則化損失 - モデルマッピングガイドのガイダンスに従ってモデルを `tf.Module`/ `tf.keras.layers.Layer`/ tf.keras.Model にマッピングし、 `tf.keras.losses` を使用します。または、正則化損失を手動で追跡することもできます。\n", "5. モデル出力損失 - `tf.keras.Model` 損失管理の仕組みを使用するか、コレクションを使用せずに損失を個別に追跡します。\n", "6. 重みの更新 - このコレクションを無視します。Eager execution と `tf.function`(autograph と auto-control-dependencies を使用)は、すべての変数の更新が自動的に実行されることを意味します。そのため、最後にすべての重みの更新を明示的に実行する必要はありませんが、コントロールの依存関係の使用方法によっては、TF1.x コードとは異なるタイミングで重みの更新が行われる可能性があることに注意してください。\n", "7. 概要 - [移行概要 API ガイド](https://www.tensorflow.org/tensorboard/migrate)を参照してください。\n", "\n", "より複雑なコレクションの使用(カスタムコレクションの使用など)では、コードをリファクタリングして、独自のグローバルストアを維持するか、グローバルストアにまったく依存しないようにする必要がある場合があります。" ] }, { "cell_type": "markdown", "metadata": { "id": "8J_ckZstp8y1" }, "source": [ "### `ReferenceVariables` の代わりに `ResourceVariables`\n", "\n", "`ResourceVariables` には、`ReferenceVariables` よりも強力な読み書き一貫性保証があります。これにより、変数を使用するときに以前の書き込みの結果を観察するかどうかについて、より予測可能で推論しやすいセマンティクスが得られます。この変更により、既存のコードでエラーが発生したり、 警告なしに中断したりする可能性はほとんどありません。\n", "\n", "ただし、これらの強力な一貫性の保証により、特定のプログラムのメモリ使用量が増加する***可能性は低いですが、あります***。これが当てはまる場合は、[問題](https://github.com/tensorflow/tensorflow/issues)を提出してください。さらに、変数の読み取りに対応するグラフ内の演算子名に対する正確な文字列比較に依存する単体テストがある場合は、リソース変数を有効にすると、これらの演算子名がわずかに変更される可能性があることに注意してください。\n", "\n", "コードに対するこの動作変更の影響を分離するために、Eager execution が無効になっている場合、`tf.compat.v1.disable_resource_variables()` および `tf.compat.v1.enable_resource_variables()` を使用して、この動作変更をグローバルに無効または有効にすることができます。Eager execution が有効になっている場合、`ResourceVariables` は常に使用されます。\n" ] }, { "cell_type": "markdown", "metadata": { "id": "FTU-4P1vux0e" }, "source": [ "### 制御フロー v2\n", "\n", "TF1.x では、`tf.cond` や `tf.while_loop` などの制御フロー演算は、`Switch`、`Merge` などのインラインの低レベル演算です。TF2 は、すべてのブランチに対して個別の `tf.function` トレースで実装され、高次微分をサポートする、改善された関数制御フロー演算を提供します。\n", "\n", "コードに対するこの動作変更の影響を分離するために、eager execution が無効になっている場合、`tf.compat.v1.disable_control_flow_v2()` および `tf.compat.v1.enable_control_flow_v2()` を使用して、この動作変更をグローバルに無効または有効にすることができます。ただし、eager execution も無効になっている場合にのみ、制御フロー v2 を無効にできます。Eager execution を有効にすると、制御フロー v2 が常に使用されます。\n", "\n", "この動作の変更により、制御フローを使用する生成された TF プログラムの構造が劇的に変化する可能性があります。これは、1 つのフラットグラフではなく、複数のネストされた関数トレースが含まれるためです。そのため、生成されたトレースの正確なセマンティクスに大きく依存するコードは、何らかの変更が必要になる場合があります。これには次が含まれます。\n", "\n", "- 演算子名とテンソル名に依存するコード\n", "- そのブランチの外側から TensorFlow 制御フローのブランチ内で作成されたテンソルを参照するコード。これは `InaccessibleTensorError` を生成する可能性があります\n", "\n", "この動作の変更は、パフォーマンスをニュートラルから肯定的にすることを目的としていますが、制御フロー v2 のパフォーマンスが TF1.x 制御フローよりも悪いという問題が発生した場合は、再現手順で[問題](https://github.com/tensorflow/tensorflow/issues)を報告してください。 " ] }, { "cell_type": "markdown", "metadata": { "id": "W7VwgVCGqE9S" }, "source": [ "## TensorShape API の動作の変更\n", "\n", "`TensorShape` クラスは、`tf.compat.v1.Dimension` オブジェクトの代わりに `int` を保持するように単純化されました。したがって、`int` を取得するために `.value` を呼び出す必要はありません。\n", "\n", "個々の tf.compat.v1.Dimension オブジェクトは依然として tf.TensorShape.dims からアクセス可能です。\n", "\n", "コードに対するこの動作変更の影響を分離するには、`tf.compat.v1.disable_v2_tensorshape()` および `tf.compat.v1.enable_v2_tensorshape()` を使用して、この動作変更をグローバルに無効または有効にすることができます。" ] }, { "cell_type": "markdown", "metadata": { "id": "x36cWcmM8Eu1" }, "source": [ "以下は、TF1.x と TF2 の違いを示しています。" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:44.563968Z", "iopub.status.busy": "2024-01-11T17:45:44.563404Z", "iopub.status.idle": "2024-01-11T17:45:46.878221Z", "shell.execute_reply": "2024-01-11T17:45:46.877530Z" }, "id": "QF4un9UpVTRA" }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "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\n", "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\n", "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\n" ] } ], "source": [ "import tensorflow as tf" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.882491Z", "iopub.status.busy": "2024-01-11T17:45:46.881792Z", "iopub.status.idle": "2024-01-11T17:45:46.888974Z", "shell.execute_reply": "2024-01-11T17:45:46.888355Z" }, "id": "PbpD-kHOZR4A" }, "outputs": [ { "data": { "text/plain": [ "TensorShape([16, None, 256])" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Create a shape and choose an index\n", "i = 0\n", "shape = tf.TensorShape([16, None, 256])\n", "shape" ] }, { "cell_type": "markdown", "metadata": { "id": "kDFck03neNy0" }, "source": [ "TF1.x に以下があるとします。\n", "\n", "```python\n", "value = shape[i].value\n", "```\n", "\n", "TF2 ではこのようになります。\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.892055Z", "iopub.status.busy": "2024-01-11T17:45:46.891820Z", "iopub.status.idle": "2024-01-11T17:45:46.895817Z", "shell.execute_reply": "2024-01-11T17:45:46.895193Z" }, "id": "KuR73QGEeNdH" }, "outputs": [ { "data": { "text/plain": [ "16" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "value = shape[i]\n", "value" ] }, { "cell_type": "markdown", "metadata": { "id": "bPWPNKRiZmkd" }, "source": [ "TF1.x に以下があるとします。\n", "\n", "```python\n", "for dim in shape:\n", " value = dim.value\n", " print(value)\n", "```\n", "\n", "TF2 ではこのようになります。" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.899049Z", "iopub.status.busy": "2024-01-11T17:45:46.898601Z", "iopub.status.idle": "2024-01-11T17:45:46.901979Z", "shell.execute_reply": "2024-01-11T17:45:46.901355Z" }, "id": "y6s0vuuprJfc" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "16\n", "None\n", "256\n" ] } ], "source": [ "for value in shape:\n", " print(value)" ] }, { "cell_type": "markdown", "metadata": { "id": "YpRgngu3Zw-A" }, "source": [ "TF1.x に以下があるとします(またはその他の次元メソッドを使用していたとします)。\n", "\n", "```python\n", "dim = shape[i]\n", "dim.assert_is_compatible_with(other_dim)\n", "```\n", "\n", "TF2 ではこのようになります。" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.905355Z", "iopub.status.busy": "2024-01-11T17:45:46.904796Z", "iopub.status.idle": "2024-01-11T17:45:46.909655Z", "shell.execute_reply": "2024-01-11T17:45:46.909075Z" }, "id": "LpViGEcUZDGX" }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "other_dim = 16\n", "Dimension = tf.compat.v1.Dimension\n", "\n", "if shape.rank is None:\n", " dim = Dimension(None)\n", "else:\n", " dim = shape.dims[i]\n", "dim.is_compatible_with(other_dim) # or any other dimension method" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.912597Z", "iopub.status.busy": "2024-01-11T17:45:46.912365Z", "iopub.status.idle": "2024-01-11T17:45:46.915622Z", "shell.execute_reply": "2024-01-11T17:45:46.914957Z" }, "id": "GaiGe36dOdZ_" }, "outputs": [], "source": [ "shape = tf.TensorShape(None)\n", "\n", "if shape:\n", " dim = shape.dims[i]\n", " dim.is_compatible_with(other_dim) # or any other dimension method" ] }, { "cell_type": "markdown", "metadata": { "id": "3kLLY0I3PI-l" }, "source": [ "tf.TensorShape のブール型の値は、階数がわかっている場合は Trueで、そうでない場合は False です。" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.918891Z", "iopub.status.busy": "2024-01-11T17:45:46.918356Z", "iopub.status.idle": "2024-01-11T17:45:46.923237Z", "shell.execute_reply": "2024-01-11T17:45:46.922646Z" }, "id": "-Ow1ndKpOnJd" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "True\n", "True\n", "True\n", "True\n", "True\n", "\n", "False\n" ] } ], "source": [ "print(bool(tf.TensorShape([]))) # Scalar\n", "print(bool(tf.TensorShape([0]))) # 0-length vector\n", "print(bool(tf.TensorShape([1]))) # 1-length vector\n", "print(bool(tf.TensorShape([None]))) # Unknown-length vector\n", "print(bool(tf.TensorShape([1, 10, 100]))) # 3D tensor\n", "print(bool(tf.TensorShape([None, None, None]))) # 3D tensor with no known dimensions\n", "print()\n", "print(bool(tf.TensorShape(None))) # A tensor with unknown rank." ] }, { "cell_type": "markdown", "metadata": { "id": "KvfEd-uSsWqN" }, "source": [ "### TensorShape の変更による潜在的なエラー\n", "\n", "TensorShape の動作の変更によって、コードが警告なしに壊れることはほとんどありません。ただし、形状関連のコードが `AttributeError` を発生させ始めるかもしれません。`int` および `None` は `tf.compat.v1.Dimension` と同じ属性を持っていません。以下に、これらの `AttributeError` の例をいくつか示します。" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.926579Z", "iopub.status.busy": "2024-01-11T17:45:46.926131Z", "iopub.status.idle": "2024-01-11T17:45:46.930076Z", "shell.execute_reply": "2024-01-11T17:45:46.929389Z" }, "id": "r18f8JAGsQi6" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'int' object has no attribute 'value'\n" ] } ], "source": [ "try:\n", " # Create a shape and choose an index\n", " shape = tf.TensorShape([16, None, 256])\n", " value = shape[0].value\n", "except AttributeError as e:\n", " # 'int' object has no attribute 'value'\n", " print(e)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.933305Z", "iopub.status.busy": "2024-01-11T17:45:46.932790Z", "iopub.status.idle": "2024-01-11T17:45:46.936749Z", "shell.execute_reply": "2024-01-11T17:45:46.936139Z" }, "id": "t9flHru1uIdT" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'NoneType' object has no attribute 'assert_is_compatible_with'\n" ] } ], "source": [ "try:\n", " # Create a shape and choose an index\n", " shape = tf.TensorShape([16, None, 256])\n", " dim = shape[1]\n", " other_dim = shape[2]\n", " dim.assert_is_compatible_with(other_dim)\n", "except AttributeError as e:\n", " # 'NoneType' object has no attribute 'assert_is_compatible_with'\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": { "id": "Og7H_TwJqIOF" }, "source": [ "## 値によるテンソルの等価性\n", "\n", "変数とテンソルのバイナリ `==` および `!=` 演算子は、TF1.x でのようにオブジェクト参照で比較するのではなく、TF2 では値で比較するように変更されました。さらに、テンソルと変数は、値でハッシュすることができない可能性があるため、セットまたは dict キーで直接ハッシュ可能または使用可能ではなくなりました。代わりに、テンソルまたは変数へのハッシュ可能な参照を取得するために使用できる `.ref()` メソッドを公開します。\n", "\n", "この動作変更の影響を分離するために、`tf.compat.v1.disable_tensor_equality()` および `tf.compat.v1.enable_tensor_equality()` を使用して、この動作変更をグローバルに無効または有効にすることができます。" ] }, { "cell_type": "markdown", "metadata": { "id": "NGN4oL3lz0ki" }, "source": [ "例えば、TF1.x では、`==` 演算子を使用すると、同じ値を持つ 2 つの変数は false を返します。" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:46.939841Z", "iopub.status.busy": "2024-01-11T17:45:46.939591Z", "iopub.status.idle": "2024-01-11T17:45:49.170965Z", "shell.execute_reply": "2024-01-11T17:45:49.170340Z" }, "id": "dkGPGpEZ5DI-" }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tf.compat.v1.disable_tensor_equality()\n", "x = tf.Variable(0.0)\n", "y = tf.Variable(0.0)\n", "\n", "x == y" ] }, { "cell_type": "markdown", "metadata": { "id": "RqbewjIFz_oz" }, "source": [ "テンソル等価性チェックが有効になっている TF2 では、`x == y` は `True` を返します。" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:49.174714Z", "iopub.status.busy": "2024-01-11T17:45:49.174088Z", "iopub.status.idle": "2024-01-11T17:45:49.184039Z", "shell.execute_reply": "2024-01-11T17:45:49.183480Z" }, "id": "V5P_Rwy-zxVE" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tf.compat.v1.enable_tensor_equality()\n", "x = tf.Variable(0.0)\n", "y = tf.Variable(0.0)\n", "\n", "x == y" ] }, { "cell_type": "markdown", "metadata": { "id": "BqdUPLhHypfs" }, "source": [ "したがって、TF2 では、オブジェクト参照で比較する必要がある場合は、必ず `is` と `is not` を使用してください。" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:49.187361Z", "iopub.status.busy": "2024-01-11T17:45:49.187130Z", "iopub.status.idle": "2024-01-11T17:45:49.192494Z", "shell.execute_reply": "2024-01-11T17:45:49.191952Z" }, "id": "iEjXVxlu4uxo" }, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tf.compat.v1.enable_tensor_equality()\n", "x = tf.Variable(0.0)\n", "y = tf.Variable(0.0)\n", "\n", "x is y" ] }, { "cell_type": "markdown", "metadata": { "id": "r2ai1BGN01VI" }, "source": [ "### テンソルと変数のハッシュ\n", "\n", "TF1.x の動作により、`set` キーや `dict` キーなど、ハッシュを必要とするデータ構造に変数とテンソルを直接追加できました。\n", "\n", "```python\n", "x = tf.Variable(0.0)\n", "set([x, tf.constant(2.0)])\n", "```\n", "\n", "ただし、テンソルの等価性が有効になっている TF2 では、`==` および `!=` 演算子のセマンティクスが値の等価性チェックに変更されるため、テンソルと変数はハッシュ不可になります。" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:49.195438Z", "iopub.status.busy": "2024-01-11T17:45:49.195185Z", "iopub.status.idle": "2024-01-11T17:45:49.201806Z", "shell.execute_reply": "2024-01-11T17:45:49.201140Z" }, "id": "-TR1KfJu462w" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Variable is unhashable. Instead, use variable.ref() as the key. (Variable: )\n" ] } ], "source": [ "tf.compat.v1.enable_tensor_equality()\n", "x = tf.Variable(0.0)\n", "\n", "try:\n", " set([x, tf.constant(2.0)])\n", "except TypeError as e:\n", " # TypeError: Variable is unhashable. Instead, use tensor.ref() as the key.\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": { "id": "CQY7NvNAa7be" }, "source": [ "したがって、TF2 では、テンソルまたは変数オブジェクトをキーまたは `set` のコンテキストとして使用する必要がある場合、`tensor.ref()` を使用して、キーとして使用できるハッシュ可能な参照を取得できます。" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:49.204936Z", "iopub.status.busy": "2024-01-11T17:45:49.204677Z", "iopub.status.idle": "2024-01-11T17:45:49.210890Z", "shell.execute_reply": "2024-01-11T17:45:49.210316Z" }, "id": "p-1kVPs01ZuU" }, "outputs": [ { "data": { "text/plain": [ "{>,\n", " >}" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tf.compat.v1.enable_tensor_equality()\n", "x = tf.Variable(0.0)\n", "\n", "tensor_set = set([x.ref(), tf.constant(2.0).ref()])\n", "assert x.ref() in tensor_set\n", "\n", "tensor_set" ] }, { "cell_type": "markdown", "metadata": { "id": "PqqRqfOYbaOX" }, "source": [ "必要に応じて、`reference.deref()` を使用して参照からテンソルまたは変数を取得することもできます。" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2024-01-11T17:45:49.214151Z", "iopub.status.busy": "2024-01-11T17:45:49.213604Z", "iopub.status.idle": "2024-01-11T17:45:49.218247Z", "shell.execute_reply": "2024-01-11T17:45:49.217679Z" }, "id": "DwRZMYV06M7q" }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "referenced_var = x.ref().deref()\n", "assert referenced_var is x\n", "referenced_var" ] }, { "cell_type": "markdown", "metadata": { "id": "5XSFQbJaReVC" }, "source": [ "## リソースとその他の文献\n", "\n", "- TF1.x から TF2 への移行の詳細については、[TF2 への移行](https://tensorflow.org/guide/migrate)セクションをご覧ください。\n", "- [モデルマッピングガイド](./model_mapping.ipynb)を読んで、TF1.x モデルをマッピングして TF2 で直接動作させる方法を学習してください。 " ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "tf1_vs_tf2.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.18" } }, "nbformat": 4, "nbformat_minor": 0 }