TensorFlow モデルを作る

はじめに

BigQuery ML は インポートした TensorFlow モデルでの予測 ができます。 BigQuery ML で使える TensorFlow モデルを作るために色々なドキュメントを往復したので、まとめておきます。 BigQuery ML を使って TensorFlow モデルを管理できれば、データソースとの転送を省略したり、 モデルや実行環境の管理を BigQuery と Cloud Storage に任せたりできます。

また SavedModel 形式は、予測に限らず数式を入れたりできるので、brainfuck が実装できるか遊んでみました(敗北)。

モデルの作り方

TensorFlow モデルをインポートする CREATE MODEL ステートメント にあるように、BigQuery ML で使える TensorFlow モデルは SavedModel として保存されている必要があります。 SavedModel を実際に作っていきましょう。

SavedModel を作る

シンプルな SavedModel を作る

tf.saved_model.save の例から始めましょう。 例では tf.TensorSpec は shape=None と定義されていますが、BigQuery ML から使う場合は必須のようですので、 shape=1 とします。

import tensorflow as tf
class Adder(tf.Module):
@tf.function(input_signature=[tf.TensorSpec(shape=1, dtype=tf.float32)])
def add(self, x):
return x + x + 1.
to_export = Adder()
tf.saved_model.save(to_export, '/tmp/adder')
## 認証しておけば Google Storage に直接転送できる
## tf.saved_model.save(to_export, 'gs://tmp/adder')

作った SavedModel を BigQuery にインポートする

TensorFlow モデルのインポート を参考にモデルをインポートします。 Cloud Storage にある SavedModel を参照できるので、予め転送しておきましょう。 クエリ 1 つでインポートできるのでとてもお手軽です。

CREATE OR REPLACE MODEL
example_dataset.imported_tf_model OPTIONS (MODEL_TYPE='TENSORFLOW',
MODEL_PATH='gs://tmp/adder/*')

インポートしたモデルを使う

インポートした TensorFlow モデルでの予測 を参考にモデルで予測します。

SELECT
*
FROM
ML.PREDICT(MODEL example_dataset.imported_tf_model,
(
SELECT
*
FROM
UNNEST(GENERATE_ARRAY(1,10))x

実行結果。

output_0x
03.01
15.02
27.03
39.04
411.05
513.06
615.07
717.08
819.09
921.010

無事に実行できました。

サポートされている型

サポートされている入力 にありますが、再掲します。

TensorFlow 型BigQuery ML type
tf.int8, tf.int16, tf.int32, tf.int64, tf.uint8, tf.uint16, tf.uint32, tf.uint64INT64
tf.float16, tf.float32, tf.float64, tf.bfloat16FLOAT64
tf.boolBOOL
tf.stringSTRING

2020 年 2 月 12 日現在、対応している入出力型は限定的なため、BigQuery のデータ型とモデル作成時の型の自由度の差異に注意しましょう。

tf.estimator を使った SavedModel の作り方

予測に使用する SavedModel のエクスポート を参考に、情報を補足します。

tf.estimator.BoostedTreesClassifier の SavedModel を作る

Boosted trees using Estimators を参考に作ります。

データのロード
import numpy as np
import pandas as pd
import tensorflow as tf
dftrain = pd.read_csv('https://storage.googleapis.com/tf-datasets/titanic/train.csv')
dfeval = pd.read_csv('https://storage.googleapis.com/tf-datasets/titanic/eval.csv')
y_train = dftrain.pop('survived')
y_eval = dfeval.pop('survived')
入力値の作成
NUMERIC_COLUMNS = ['age', 'fare']
feature_columns = []
for feature_name in NUMERIC_COLUMNS:
feature_columns.append(tf.feature_column.numeric_column(feature_name,
dtype=tf.float32))
NUM_EXAMPLES = len(y_train)
def make_input_fn(X, y, n_epochs=None, shuffle=True):
def input_fn():
dataset = tf.data.Dataset.from_tensor_slices((dict(X), y))
if shuffle:
dataset = dataset.shuffle(NUM_EXAMPLES)
## For training, cycle thru dataset as many times as need (n_epochs=None).
dataset = dataset.repeat(n_epochs)
## In memory training doesn't use batching.
dataset = dataset.batch(NUM_EXAMPLES)
return dataset
return input_fn
## Training and evaluation input functions.
train_input_fn = make_input_fn(dftrain, y_train)
eval_input_fn = make_input_fn(dfeval, y_eval, shuffle=False, n_epochs=1)

tf.estimator の作成

est = tf.estimator.BoostedTreesClassifier(feature_columns,
n_batches_per_layer=1)
est.train(train_input_fn)
## Eval.
## result = est.evaluate(eval_input_fn)
## print(pd.Series(result))

SavedModel の作成

トレーニング中にサービスグラフを作成する にある json_serving_input_fn を使って、export すると BigQuery ML から理想的な形で呼び出すことができます。

def json_serving_input_fn():
"""Build the serving inputs."""
inputs = {}
for feat in feature_columns:
inputs[feat.name] = tf.placeholder(shape=[None], dtype=feat.dtype)
return tf.estimator.export.ServingInputReceiver(inputs, inputs)
path = est.export_saved_model('gs://tmp/btc',
json_serving_input_fn)
ここが厄介

TensorFlow 1.x は tf.placeholder が使えるので上のコードが動作します。 TensorFlow 2.x は tf.placeholder が使えないため、以下の serving_input_fn で Proto Buffers を経由して頑張る必要がありそうです。予め Proto Buffers に変換したテーブルを用意するのが解になってしまいます。他には tensorflow/tensorflow/core/example の .proto を JavaScript にコンパイルして UDF を作成すると回避できる可能性があります。

serving_input_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(
tf.feature_column.make_parse_example_spec(feature_columns)
)

作った SavedModel を BigQuery にインポートする

CREATE OR REPLACE MODEL
example_dataset.imported_tf_model OPTIONS (MODEL_TYPE='TENSORFLOW',
MODEL_PATH='gs://tmp/btc/1581656284/*')

インポートしたモデルを使う

SELECT
*
FROM
ML.PREDICT(MODEL bigqueryml.gbdt,
(
SELECT
*
FROM
UNNEST([STRUCT(10 AS age,
0 AS fare), (80,
30)])))

実行結果。

all_class_idsall_classesclass_idsclasseslogisticlogitsprobabilitiesagefare
100000.001847356558-6.2921504970.9981526732100
110.001847356558
200110.99757450826.0192823410.002425516498030
110.9975745082

動いてそうです。

Brainfuck を実装する

UnliftableError に敗北

実装してみたのですが、循環参照が計算グラフに変換できなさそうなエラーと遭遇して断念しました。

---------------------------------------------------------------------------
UnliftableError Traceback (most recent call last)
<ipython-input-14-2aa1513a2c78> in <module>()
1 to_export = Brainfuck()
----> 2 op = to_export.brainfuck(tf.constant('++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.', shape=(1,)))
3
4 with tf.Session() as sess:
5 print(sess.run(op))
52 frames
/usr/local/lib/python3.6/dist-packages/tensorflow_core/python/ops/op_selector.py in map_subgraph(init_tensor, sources, disallowed_placeholders, visited_ops, op_outputs, add_sources)
411 "Unable to lift tensor %s because it depends transitively on "
412 "placeholder %s via at least one path, e.g.: %s"
--> 413 % (repr(init_tensor), repr(op), _path_from(op, init_tensor, sources)))
414 for inp in graph_inputs(op):
415 op_outputs[inp].add(op)
UnliftableError: Unable to lift tensor <tf.Tensor 'Variable/Initializer/ReadVariableOp:0' shape=(64,) dtype=int64> because it depends transitively on placeholder <tf.Operation 'add/cond/Identity_1' type=Placeholder> via at least one path, e.g.: Variable/Initializer/ReadVariableOp (ReadVariableOp) <- strided_slice/_assign (ResourceStridedSliceAssign) <- add_2 (AddV2) <- strided_slice_1 (StridedSlice) <- strided_slice_1/stack_1 (Pack) <- add_1 (AddV2) <- add/cond/Identity_1 (Placeholder)

先行研究

EsotericTensorFlow で、Brainfuck の実装が行われています。 しかし、Brainfuck のソースコードを渡して計算モデルを作成するもので、計算モデルにソースコードを渡して実行するインタプリタではなさそうです。 そのため、私は TensorFlow の計算モデルのチューリング完全性を証明することはできませんでした。

おわりに

BigQuery ML で使える TensorFlow の SavedModel を作って動作確認しました。 チューリング完全性の証明には至りませんでしたが、BigQuery ML でテンソルグラフ計算や、 BigQuery ML 未リリースの BoostedTreesClassifier を実現できました。 BigQuery ML を使えれば、データとモデルが近い位置におけるメリットと、クエリ拡張性が出るため活用できると良いですね。

Last updated on