TensorFlow でデータを扱う際、定数(Constant)、変数(Variable)、プレースホルダ(Placeholder)という3つの概念が登場します。TensorFlow 2.x では Eager モードがデフォルトになり、プレースホルダは非推奨となりましたが、レガシーコードを読む機会もあるため、それぞれの役割と違いを理解しておくことは大切です。
定数 - tf.constant
定数は一度作成すると値を変更できないテンソルです。学習中に変化しないデータ、たとえばハイパーパラメータや固定の入力データに使います。
import tensorflow as tf # 定数の作成 learning_rate = tf.constant(0.01) labels = tf.constant([0, 1, 1, 0, 1]) print(learning_rate) # tf.Tensor(0.01, shape=(), dtype=float32) print(labels) # tf.Tensor([0 1 1 0 1], shape=(5,), dtype=int32)
定数はグラフに値がそのまま埋め込まれるため、大きなデータを定数にするとメモリ効率が悪くなる場合があります。大量のデータを扱う場合は tf.data.Dataset などを検討してください。
変数 - tf.Variable
変数は値を更新できるテンソルです。ニューラルネットワークの重みやバイアスなど、訓練中に最適化されるパラメータに使います。
# 変数の作成 weights = tf.Variable(tf.random.normal([3, 2])) bias = tf.Variable(tf.zeros([2])) print(weights) print(bias)
変数の値を更新するには assign、assign_add、assign_sub メソッドを使います。直接代入はできない点に注意が必要です。
counter = tf.Variable(0) # 値の代入 counter.assign(10) print(counter) # <tf.Variable ... numpy=10> # 加算 counter.assign_add(5) print(counter) # <tf.Variable ... numpy=15> # 減算 counter.assign_sub(3) print(counter) # <tf.Variable ... numpy=12>
変数と定数の決定的な違いは「値を変更できるかどうか」です。訓練ループの中でオプティマイザが自動的に変数を更新してくれるため、モデルのパラメータには必ず tf.Variable を使います。
値は不変。ハイパーパラメータや固定データに使う。グラフに値が埋め込まれるため、大きなデータには不向き。
値を更新できる。モデルの重みやバイアスに使う。オプティマイザによる自動更新の対象になる。
変数の訓練対象フラグ
tf.Variable には trainable パラメータがあります。デフォルトは True で、オプティマイザの更新対象になります。転移学習などで特定の層を凍結したい場合は False に設定します。
# 訓練対象の変数 trainable_var = tf.Variable(1.0, trainable=True) # 凍結された変数(オプティマイザが更新しない) frozen_var = tf.Variable(1.0, trainable=False) # trainable な変数の一覧を取得 model_vars = [trainable_var, frozen_var] trainable_only = [v for v in model_vars if v.trainable] print(len(trainable_only)) # 1
この仕組みにより、モデルの一部だけを学習させるファインチューニングが簡単に実現できます。
プレースホルダ - tf.placeholder(TensorFlow 1.x)
プレースホルダは TensorFlow 1.x で使われていた仕組みで、計算グラフの中に「後からデータを流し込む穴」を定義するものでした。
# TensorFlow 1.x のコード(2.x では非推奨) import tensorflow.compat.v1 as tf1 tf1.disable_eager_execution() x = tf1.placeholder(tf1.float32, shape=[None, 3]) y = x * 2 with tf1.Session() as sess: result = sess.run(y, feed_dict={x: [[1, 2, 3]]}) print(result) # [[2. 4. 6.]]
Session と feed_dict を使ってデータを供給する必要があり、デバッグが困難でした。TensorFlow 2.x では Eager モードにより、Python の関数をそのまま呼び出す形で計算できるため、プレースホルダは不要になっています。
TensorFlow 2.x での代替パターン
TensorFlow 2.x ではプレースホルダの代わりに、Python の関数引数やKeras の tf.keras.Input を使います。
# 方法1: 通常の Python 関数 def compute(x): return x * 2 result = compute(tf.constant([[1, 2, 3]])) print(result) # [[2, 4, 6]]
# 方法2: Keras の Input レイヤー inputs = tf.keras.Input(shape=(3,)) outputs = tf.keras.layers.Dense(1)(inputs) model = tf.keras.Model(inputs=inputs, outputs=outputs) model.summary()
Keras の Input はモデルの入力形状を宣言する役割を持ちますが、プレースホルダとは異なり、Eager モードの恩恵を受けてデバッグしやすくなっています。
実践での使い分けまとめ
学習率、ラベルデータ、設定値など変化しないデータに使います。計算途中の中間結果も自動的に定数テンソルとして扱われます。
重み、バイアスなどオプティマイザが更新するパラメータに使います。Keras のレイヤーは内部で自動的に Variable を作成するため、手動で作る機会は限られます。
大量のデータは tf.data.Dataset パイプラインで供給し、小さなデータは関数の引数として渡すのが TensorFlow 2.x の標準的なパターンです。
TensorFlow 2.x を使っている限り、日常的に意識するのは tf.constant と tf.Variable の2つです。Keras を使えば変数の管理もフレームワークが自動で行ってくれるため、まずはこの2つの違いをしっかり理解しておきましょう。