めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

DQN implementation for Open AI Gym CartPole-v0

References

github.com

Hands-On Machine Learning With Scikit-Learn and Tensorflow: Concepts, Tools, and Techniques to Build Intelligent Systems

Hands-On Machine Learning With Scikit-Learn and Tensorflow: Concepts, Tools, and Techniques to Build Intelligent Systems

Implementation

TensorFlowのSavedModelに関するメモ

何の話かというと

TF1.0から、新しいモデルの保存形式として、SavedModelが導入されました。

このフォーマットの特徴と使い方のTipsをまとめます。

SavedModel形式で保存する方法

典型的には、Experiment APIを利用します。Experiment APIは、tf.contrib.learnの中でも最もハイレベルなAPIで、

・モデル
・トレーニング用データ入力関数
・評価用データ入力関数

を与えて実行すると、トレーニング用データでトレーニングを行いつつ、適当なタイミングで評価用データによる評価結果も出力してくれるというものです。しかも、Experiment APIを利用したコードを Cloud MLE のジョブに投げると、何も考えなくても、自動的に分散学習処理が行われます。また、学習途中のチェックポイントを自動保存するので、たとえば、global step=1000 まで実行した後、再度、同じジョブを投げると、global step=1000 のところから学習を再開してくれます。

Experiment APIを用いたコードのサンプルは、この辺りを参考にしてください。

SavedModelに関連するポイントとしては、次のボイラープレートで、serving_input_fn() を定義して、

def serving_input_fn():
    feature_placeholders = {
      'is_male': tf.placeholder(tf.string, [None]),
      'mother_age': tf.placeholder(tf.float32, [None]),
      'mother_race': tf.placeholder(tf.string, [None]),
      'plurality': tf.placeholder(tf.float32, [None]),
      'gestation_weeks': tf.placeholder(tf.float32, [None]),
      'mother_married': tf.placeholder(tf.string, [None]),
      'cigarette_use': tf.placeholder(tf.string, [None]),
      'alcohol_use': tf.placeholder(tf.string, [None])
    }
    features = {
      key: tf.expand_dims(tensor, -1)
      for key, tensor in feature_placeholders.items()
    }
    return tflearn.utils.input_fn_utils.InputFnOps(
      features,
      None,
      feature_placeholders)

Experimentオブジェクトを生成する時に、下記の形で、export_strategiesオプションにこの関数を渡します。

tflearn.Experiment(
...
        export_strategies=[saved_model_export_utils.make_export_strategy(
            serving_input_fn,
            default_output_alternative_key=None,
            exports_to_keep=1
        )]
    )

これは、トレーニングが終わったモデルをつかってPredictionを行う際の入力変数を指定するものです。トレーニングが終了すると、これらをPlaceholderで受けて、Prediction結果を出力するためのグラフが自動的に構築されて、SavedModelの一部として保存されます。これにより、Cloud MLE上で、Prediction用のAPIを公開した際の入力フォーマットが決まります。上記の例であれば、Prediction処理は次のようなコードになります。

from googleapiclient import discovery
from oauth2client.client import GoogleCredentials
credentials = GoogleCredentials.get_application_default()
api = discovery.build('ml', 'v1', credentials=credentials)

request_data = {'instances':
  [
      {
        'is_male': 'True',
        'mother_age': 26.0,
        'mother_race': 'Asian Indian',
        'plurality': 1.0,
        'gestation_weeks': 39,
        'mother_married': 'True',
        'cigarette_use': 'False',
        'alcohol_use': 'False'
      },
      {
        'is_male': 'False',
        'mother_age': 29.0,
        'mother_race': 'Asian Indian',
        'plurality': 1.0,
        'gestation_weeks': 38,
        'mother_married': 'True',
        'cigarette_use': 'False',
        'alcohol_use': 'False'
      }
  ]
}

parent = 'projects/%s/models/%s/versions/%s' % (PROJECT, 'babyweight', 'v1')
response = api.projects().predict(body=request_data, name=parent).execute()
print "response={0}".format(response)

------
response={u'predictions': [{u'outputs': 7.265425682067871}, {u'outputs': 6.78857421875}]}

細かすぎて伝わらない注意点(その1)

先ほどの served_fn() の定義内で、Placeholder を [None] のサイズで定義した後に、下記の tf.expand_dims() で [None,1] のサイズに拡張しています。謎ですね。

    features = {
      key: tf.expand_dims(tensor, -1)
      for key, tensor in feature_placeholders.items()
    }

一般的には、Placeholder のサイズは [None, x] で、この場合、この Feature は x 要素のベクトルとして与える必要があります。ただし、このサンプルでは、各 Feature はスカラーなので、仮に、Placeholder 自体を [None, 1] で定義すると、スカラー値を ['hoge'] のように1要素のベクトルとして渡す必要があって面倒です。そこで、Placeholder 自体は [None] にして、スカラー値をそのまま受け取れるようにしておき、その後で [None, 1] に拡張してから後段のグラフに渡しています。(後段のグラフは、Placeholderからの入力は、[None, 1] を期待しているので。)ああわかりにくい。

ちなみに、この次に出てくる例(MNIST)では、Feature は、784個の要素のベクトルなので、普通に [None, 784] で定義して、そのまま使っています。

細かすぎて伝わらない注意点(その2)

serving_input_fn() を定義する際に、feature_placeholdersに指定したキーがPredictionのAPIを呼ぶ際の入力項目のキーになることが分かります。ところが、むっちゃ細かい話なんですが、次のように入力項目が1つだけの場合、キーの名前が強制的に 'inputs' に変換されます。

def serving_input_fn():
  feature_placeholders = {'image': tf.placeholder(tf.float32, [None, 784]}
  features = {
    key: tensor
    for key, tensor in feature_placeholders.items()
  }
  return learn.utils.input_fn_utils.InputFnOps(
    features,
    None,
    feature_placeholders
  )

犯人はこのあたりなんですが、まったくもって余計なお世話ですね・・・。(このせいで3時間ぐらい悩みました。)

SavedModel形式の中身

SavedModel形式で保存すると、出力ディレクトリ直下に、モデルのメタ情報をprotobufで記載したファイルsaved_model.pbが保存されます。Variablesの値は、これとは別に、variablesディレクトリの下に独自のバイナリ形式で保存されます。Cloud MLE上でPrediction用APIを立ち上げる際は、出力ディレクトリを指定すると、その中のファイルを適当にあさって、必要な情報を吸い上げてくれます。

ただし、自分のコードからモデルをリストアする際は、メタ情報の内容を理解しておく必要があります。export_strategiesを指定する際、次のように「as_text=True」を指定すると、テキスト形式のsaved_model.pbtxtが出力されるので、このファイルを開くと、メタ情報の構造が確認できます。

tflearn.Experiment(
...
        export_strategies=[saved_model_export_utils.make_export_strategy(
            serving_input_fn,
            default_output_alternative_key=None,
            exports_to_keep=1,
            as_text=True
        )]
    )

また、自前のPythonコードからSavedModel形式のモデルをロードする関数が、tf.saved_model.loader.load() として用意されています。これを利用して自前のコード上でPredictionを行う例がこちらになります。メタ情報から、入出力項目のキーに対応するTensorオブジェクトの名前を取得するという一手間を加えてあります。

import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

export_dir = 'モデルを保存したディレクトリ'

def run_inference_on_image():
    mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
    
    with tf.Session(graph=tf.Graph()) as sess:
        meta_graph = tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], export_dir)
        
        model_signature = meta_graph.signature_def['serving_default']
        input_signature = model_signature.inputs
        output_signature = model_signature.outputs
        
        input_tensor_name = input_signature['inputs'].name
        prob_tensor_name = output_signature['probabilities'].name
        label_tensor_name = output_signature['classes'].name

        images = sess.graph.get_tensor_by_name(input_tensor_name)
        prob = sess.graph.get_tensor_by_name(prob_tensor_name)
        label = sess.graph.get_tensor_by_name(label_tensor_name)
        label_pred, prob_pred = sess.run([label, prob], feed_dict={images: mnist.test.images[0:10]})
        return label_pred, prob_pred

ちなみに、これは、下記のサンプルコードで学習したモデルをリストアして利用する例です。

Cloud MLE and GCE compatible TensorFlow distributed training example

このモデルでは、serving_input_fn()で定義した入力項目が1つなので、入力項目のキーが強制的に 'inputs' になっています。また、このモデルは、tf.contrib.learnのカスタムEstimatorを使用しており、出力項目のキーについては、下記のようにカスタムEstimator作成のお作法に従って指定が行われています。

...
  predictions = {
      "classes": tf.argmax(input=logits, axis=1),
      "probabilities": tf.nn.softmax(logits)
  }

  return model_fn_lib.ModelFnOps(mode=mode, loss=loss, train_op=train_op,
                                 predictions=predictions)

「プログラマのためのGoogle Cloud Platform入門」が発売されます。

プログラマのためのGoogle Cloud Platfrom入門 サービスの全体像からクラウドネイティブアプリケーション構築まで

プログラマのためのGoogle Cloud Platfrom入門 サービスの全体像からクラウドネイティブアプリケーション構築まで

翔泳社より、表題の書籍が出版されることになりました。共著者の阿佐さんを始めとして、執筆にご協力いただいた方々に改めてお礼を申し上げます。

2016年の中頃、Google Cloud Platform(GCP)に関わる仕事をはじめた当初より感じていた、「とにかく、わかりやすくて実践的な入門書を提供したい!」という想いをようやく形にすることができました。本書では、Google Compute Engine、Google Container Engine、Google App Engineを中心としたGCPの主要サービスを用いて、最適なアプリケーションアーキテクチャーを実現する方法を具体的なサンプルアプリケーションを使って説明しています。特に、GCPを利用する上で理解しておきたいインフラの基礎技術についても解説が加えられていますので、GCPを通してインフラ技術の基礎を習得したいという方にも最適な入門書となっています。

ちなみに、GCPには、アプリケーションを効率的に開発・提供するためのアプリケーションプラットフォームという側面に加えて、大量データを効率的に処理するためのデータ処理プラットフォームという側面があります。本書は、まずは、アプリケーションプラットフォームとしての特徴を理解して、使いこなしていただくことが大きな目標となっています。データ処理機能について、あまり触れることができなかった点が心残りですが、またいつの日か、データ処理プラットフォームとしてのGCPを解説した書籍が提供できるよう、GCPユーザーの皆さんの声援がいただけると幸いです。

最後に、共著者の阿佐さんに執筆していただいた「はじめに」の一部を参考として掲載させていただきます。

はじめに

 「レゴブロックのようにパーツを組み合わせるだけで簡単にシステムが構築できる」――パブリッククラウドについて、そんな宣伝文句を耳にしたことはありませんか? しかしながら、現実のシステム開発はそれほど甘くはありません。クラウド上で提供されるそれぞれのサービスやコンポーネントの特性をよく理解して、ビジネス要件に応じた、最適なシステム設計を実現する必要があります。そのような観点で見た場合、Google Cloud Platformにはどのような特徴があるのでしょうか?

 現在、Google社は、サーチエンジンをはじめとする世界規模のWebサービスを広く展開しています。同社が提供するこれらのサービスは、世界最先端のハードウェア/ソフトウェア技術を用いて実現した社内インフラ、現在で言うところのプライベートクラウドの上に構築されており、そこには、プログラマの創造性と生産性を支えるためのさまざまな仕組みが用意されています。Google Cloud Platformは、このようなGoogle独自の社内インフラをパブリッククラウドという形で、一般のユーザーにも開放したサービスと言えます。そこでは、伝統的な仮想マシン環境はもちろんのこと、マイクロサービス型のアプリケーションに適したコンテナインフラ、あるいは、開発/運用の効率化を徹底的に追求した独自のPaaS環境などが、使いやすく整備された形で提供されています。機械学習を活用したアプリケーション開発という点でも大きな強みがあります。

 つまり、既存技術の枠組みを脱して、新たな技術や方法論を取り入れた次世代システムへの変革に挑戦したいと願うユーザーにとって、Google Cloud Platformは、最適なクラウドサービスと言えるでしょう。そこには、「最先端の技術を活用して、時代に即した変化に強いシステムを開発したい!」と願うプログラマにとって、大きなチャンスが広がっているのです。そのような思いをもったプログラマの方々が、はじめの一歩を踏み出す手助けとなることを目指して、本書の執筆に取り組みました。世界規模でのスケーラビリティーや開発生産性の向上、先進技術を活用した新しいビジネスへの取り組みなど、コスト削減にとどまらない、Google Cloud Platformのメリットを実感するきっかけが提供できたとすれば幸いです。

〜中略〜

執筆者を代表して
阿佐 志保