Kerasでサクッとモデルを組み上げ、独自のカスタム損失関数(Custom Loss Function)を定義しようとして、いつも使い慣れている numpy や scikit-learn の便利な関数を内部に仕込んで実行した瞬間。赤いエラー文字が画面を支配します。
NotImplementedError: Cannot convert a symbolic Tensor (model/dense/BiasAdd:0) to a numpy array. This error may indicate that you're trying to pass a Tensor to a NumPy call, which is not supported...
「えっ?モデルの計算グラフに流れてきている配列(Tensor)を、numpy() に変換して計算したいだけなんだけど、なんでダメなの?」
このエラーの中心には、TensorFlowという巨大帝国が長年抱え続けてきた「静的グラフ(Graph)」と「動的実行(Eager Execution)」という、2つの全く異なるプログラミングパラダイムの衝突があります。
本記事では、このTF界隈で最も初心者を悩ませるこのエラーを通じて、GPU上で超高速に動かすための「グラフコンパイルの真実」を解説し、テンソル操作の達人になる道を示します!
1. 象徴的(Symbolic)テンソルとは「ただの空箱」である
TensorFlowには、実は「モードが2つ」存在します。
普段Jupyter Notebook上で a = tf.constant([1, 2, 3]) と打ち込んで print(a) とすると、すぐに中身が見えますね。これはEager Execution(動的実行)モードと呼ばれ、Pythonと同じように1行ずつ計算を即座に行って結果を出してくれます。
しかし、model.fit()(学習ループ)の中や、@tf.function というデコレータが付けられた関数の内部に入った瞬間、世界は一変します。
TensorFlowは、超高速でGPUを回すために、ソースコードを先読みして「静的な計算設計図(グラフ)」を事前に作成する Graphモード に強制的に切り替わります。
このGraphモード中に流れてくるテンソル引数は、具体的な「3」や「[5.1, 2.3]」といった実際の数値ではなく、
「将来、バッチサイズ32のFloat32データがここに流れてきますよ」という単なる【空箱(予約済みの枠、シンボリック・テンソル)】になるのです。
NumPyの関数(np.sum() など)は、中身が実在する具体的な数字でないと計算できません。
そのため、空箱(Symbolic Tensor)をNumPyに渡そうとした瞬間、「私は空箱を数列に変換(convert)なんてできないよ!」と激怒し、先ほどのエラーを叩きつけるのです。
import tensorflow as tf
import numpy as np
# 【エラーが起きる最悪のカスタムLossの実装】
def bad_custom_loss(y_true, y_pred):
# ここに入ってくる y_pred は、中身のない単なる「空箱(シンボリック)」です!
# ここでNumPy関数を使おうとすると、「空箱をNumPy配列にできません!」と大爆発する
diff = np.abs(y_true - y_pred)
return np.mean(diff)
model.compile(loss=bad_custom_loss, optimizer='adam')2. 全てを「tf」の名前空間で閉じ込める(TFバックエンドの強制)
この問題を回避するための絶対的なルールはたった1つです。
「Kerasのモデル内、あるいは損失関数の計算内では、NumPy (np.) や Pythonの標準関数 を一切使わず、全てを TensorFlowのバックエンド関数 (tf.) で書き直すこと!」
TensorFlowには、NumPyの全ての数学関数に完全に対応した「空箱のまま設計図を組み立てられる専用の関数」が用意されています。これを「TF Ops(オペレーション)」と呼びます。
# ===============================================
# 【美しい防弾仕様のカスタムLoss実装】
# ===============================================
def good_custom_loss(y_true, y_pred):
# numpy() に変換する必要は一切ありません。
# すべてを tf.math や tf.keras.backend の関数で記述します。
# 違い(差分)を計算。演算子 '-' はTFが内部でtf.subtract()にオーバーライドしています。
diff = tf.math.abs(y_true - y_pred)
# 平均の計算も np.mean ではなく tf.reduce_mean を使用する!
return tf.reduce_mean(diff)
model.compile(loss=good_custom_loss, optimizer='adam')
# => これならGPU上で最高速度にコンパイルされ、爆速で学習が走ります!!まとめ:Pythonの外側の「コンパイラ」を意識する
TensorFlowがなぜNumPyよりも強烈に速いのか。
それは、書かれたプログラミングをそのまま実行しているのではなく、Python言語を足場にして「GPU上で動くためのC++/CUDAの命令書(C++ Graph)」を裏側で自動コンパイル(翻訳・最適化)して生み出しているからです。
この「翻訳作業の最中」に、外部の別ライブラリであるNumPyの処理を混ぜようとするのが、根本的な思想の間違いなのです。
あなたが「NumPyに変換して処理したいな」と思った時は、絶対に tf.xxxx という名前の同じ機能を持つTFネイティブの関数が存在します。
公式ドキュメントや専門書を通じて、Pythonの実行モデルとTensorFlow Graphモデルの違い(オートグラフの仕組み)を深く理解することが、実践的なAIエキスパートへの一番の近道となります。


コメント