simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

勾配降下法とユーティリティーベースAIのパラメータ決定

ユーティリティーベースAIの作り方について、とても分かりやすい記事を見つけました。
moon-bear.com

筆者のくろくまさんは、ユーティリティベースAIの評価関数の作り方について、明確に次の問題点を指摘しています。

しかし困ったことに「こうすればいい」というような決まりはない

ひいてはゲームAI全般に言えることですが、パラメータの決定を AI ディレクターなる立場の方が試行錯誤や勘に頼って行っているのが現在のゲーム業界です。
大手メーカー各社、ほか医療関係の業界では人の命にかかわる製品が多いため、必ず事故が起こらないようにパラメータを解析的に決定するようにしています。(経験的に決めた値が使われることもあるかも、必ずは言い過ぎ)

解析的に決めるという手法ですが
具体的には、ゲーム中にキャラクターを人間が動かして行動ログを取り、その人間の行動ログと適当なパラメータで決定したAIの行動ログの差を誤差として
誤差最小化の枠組みでパラメータを変動させながら決めるというのが、一般的なアプローチです。

5年前にこのような発言がゲーム業界に投げかけられたことがあったのですが
だれもが「そんな難しいことできません。開発工数にそういった高度な試みをする余裕がない」という回答を持って、聞き入れませんでした。
そして、現在も行われていません。

しかし、近年 GDC などのゲーム国際会議の議題レベルでは、Deep Learning をはじめとする機械学習のツールを活用したゲーム開発効率や作品の質向上について語られるようになってきました。
経営権を持つ上層の人たちも、こうした情報元を通じて、大量の行動ログとAIの行動ログの差を最小化する手法(一般的な機械学習のスタイル)について、見識を持ち始めています。

5年前と違って、今こそAI ディレクターが試行錯誤や勘に頼って頑張っている作業を機械化し、より高品質なユーティリティーベース、またはゲームAIが作れるようになる時代だと考えています。

すべては飽きがなかなか来ない愛せるほどの面白いゲームを作るため
ゲーム業界で大量のログを持て余している方の目にとまったならば、その人間のログとAIのログの差を最小化する手法を試そうと調べてみるのはどうでしょうか?

関連する、約3年前の私の記事です。

誤差は順調に減少していくと思いますから、そうなったとき好ましい結果を返すAIになってくれると良いですね。

simplestar-tech.hatenablog.com

ユーティリティベースの8つの欲求

The SiMs のキャラクターはまるで欲求を持って行動しているように見えるそうですが
実際、8つの欲求が設計されていて、時間と状況によって一番高い欲求に従うように行動しているそうな

その8つとはどんな欲求だったのか見てみます。

身体的欲求
1.Hunger :空腹
2.Comfort :快適さ
3.Hygiene :衛生
4.Bladder:膀胱(尿意、便意)
精神的欲求
5.Energy : 気力
6.Fun : 楽しさ、面白さ
7.Social :社会所属
8.Room : 一人になりたい

基本的にどれも減少していく値と結び付けられていて
例えば選択肢にトイレと風呂があったとき

トイレに行くと
Bladder が大回復、 Room が回復

風呂に行くと
Hygiene が大回復、Comfort が回復、Room が回復

する。

キャラクターの現在最も値の低い欲求値について、それを最も回復させてくれるものをプランニングして行動する
たとえば、「かなり尿意がある、から、トイレに行きたい」とキャラが発言できるようになるし
その発言の後に実際にトイレに行くことになる

これまでのゲームAI:7つの分類

ゲームの国際会議GDC2018のAIの集会で、日本人の三宅陽一郎(43)さんが発表してますが、ゲームAIについて良く調べている方と言えばこの人になります。
そんな三宅さんがゲームに用いられるAIを7つに分類してくれました。

前回の私の記事で、遺伝、習性、意思決定などのレイヤー構造のAIにすると良さそうだ、と酔った頭で考えていましたが
今回のこの 7つの分類は、そのレイヤーの一つとしてとらえていけそうな気がします。

解説いただいたその 7つの分類は次の通り

1.ルールベース(すべてを平たんに if ~ else ~ で記述する反射行動)
2.ステートベース(状態ごとに反射行動を記述、状態遷移によって処理を分岐)
3.ビヘイビアベース(開発者にやさしい設計で描く反射行動)
4.ゴール思考(手段目的構造でプランニング)
5.タスクベース(目的をタスクに分解してプランニング)
6.ユーティリティベース(欲求の効用曲線、動物的な習性)
7.シミュレーションベース(短期的な未来を計算して、その場における最善手を選ぶ)

また補足説明として 1.2.3.は反射的な行動、ゆえにあまり賢いAIに見えない
4.5.は有名FPSとかに導入されているけど、作品数は多くない
6.は日常生活系のシミュレータで大活躍
7.こそ研究段階、囲碁や将棋AIはこれ

ということでした。

かれこれ 2016年の9月ころから私が考えてきたAIは、7.シミュレーションベースのその先の研究領域だったのかもしれません。
周囲の環境をシミュレーション可能な枠組みで作るという逆転の発想を用いて、その結果から行動を決めるAI
また、何度も同じような行動が繰り返されたときに、Deep Learning 技術で特徴学習を行って、短時間で過去の経験から賢い選択をできるようになるAI
そんなAIを目指しています。

すべては飽きがなかなか来ない愛せるほどの面白いゲームを作るためですが、少しずつ世界がその方向に向かって動き出していることを感じられてうれしく思いました。

レイヤー構造AIデザイン(古いAI技術を新しいものに置き換えるのではなく、積み重ねて利用する考え方)

ふと、別部署の方とAI談義をする機会があったのですが、興味深い気づきを与えてもらいました。
まとめます。

ゲームAI技術のうち

GA と呼ばれる遺伝的アルゴリズムによる進化
ユーティリティーベースの習性による行動優先度決め
未来予測と行動選択をするモンテカルロツリーサーチ
DNNベースの特徴学習と特徴抽出結果の利用による判別や行動決定

などがありますが、これらを別々のものととらえるのではなく、ハードウェア寄り、ソフトウェア寄りにレイヤーを組んで、一式すべてを利用するエージェントを作ると、期待するAIに近づくだろうという話に発展しました。

陸上で速く走るために、意思の力で馬は指の爪を大きくして、つま先だけで走れるようになったわけではなく
海の中を効率的に速く泳ぐために、意思の力で鼻の穴を頭の上に移動させて、手をひれのようにし、毛をうろこ状に変化させたわけではなく
世代を超えて遺伝しつつも突然変異として多様性を持たせ、より優れた適応性を持つものを多く生き残らせることで、環境に適したフォルムを手に入れる
これが GA であり、GA は世代間の種の保存を目的としたハードウェアの構成要素としての AI と考えることができます。

例えば髪の毛を意識の力で伸ばしたり太くしたりはできません。
しかし、かみそりで剃るなどの刺激を与えると早く成長したり、太く強くなったりします。
これはハードウェア寄りのユーティリティーベースの習性によるもので
より健康で長生きできたために、そのようなハードウェア寄りのベースロジックが備わっていると考えます。

息苦しくなったら窓を開け、疲れたら眠り、腹がすいたら食事をし、種を残す行為をしたがる性欲がわくといったことになります。
これはソフトウェア寄りのユーティリティーベースの習性によるもので、ここで初めて意思の力でこうした行動を抑制できるようになりますが、それは置いておいて
ひとまずソフトウェア寄りの習性を持たせることで、こうした行動を行う動物的なAIを表現できます。

未来予測と行動選択により、死に関わるような一瞬の判断など、生き残るために重要な思考を行うことができます。
これはソフトウェアの機能としてのAIで、より正確に未来を予見できるものは頭が良いとされます。
ソフトウェア寄りの習性も、この未来予測と行動選択によって抑制することができ、特に動物の中でも、人はこの力を持つことによって、我慢と協調という社会性を獲得し
時に個としての種の保存よりも、種全体の保存にとって重要な行動を未来予測によって決定して死を迎える人が存在します。
習性としてこれを持つ蜂などの昆虫もいます。レイヤー間では、高いレイヤーから低いレイヤーへ習性として、なじむように作られていると考えられます。

DNNベースの特徴学習は近年注目されているAI技術ですが、これは未来予測を有事の際に高速に正確に行えるために、事前に最適化したモデルを獲得する技術ととらえることができます。
未来予測と実際の行動結果を近づけるために限られたリソース内で簡略化したモデルを構築し、これをソフトウェア機能としてのAIで利用します。
達人と呼ばれる領域になれば、レントゲン写真から腫瘍を発見したり、刀身の動きから相手の次の太刀筋を見極めることができるようになります。

私たち生命の動きを決めているAI要素はレイヤー構造となっていて

DNN(ディープラーニング:現在2018年に人類が挑んでいる最先端技術)
MCTS(モンテカルロツリーサーチ:AlphaGoなどの人類を超越した思考AI)
BT(ビヘイビアツリー:モダンゲームAI、ユーティリティベースのAIデザイン)
GA(遺伝的アルゴリズム:基本AI、ランダムに変化させて、良かったものを残す単純ロジック)

という順番でレイヤーを構成して、それぞれ下からハードウェア寄り、上に行くにつれてソフトウェア寄りにアルゴリズムやロジックを配置して走らせることで、生命として、意識を持つAIとして機能し始めることが初めてできるのではないか

なーんて、飲み会の後の酔った頭でバスに揺られながら1時間ほど話をしてました。
最近はAIを作るには未来予測が重要だと示していましたが、もちろんそれは変わりませんが、既存のGAや最新のDNNなども、未来予測を挟み込むレイヤー技術と考えることができるようになれたのが大きいです。
このレイヤー型技術を真剣に取り入れていけば、環境に適用するように世代ごとにだんだん進化していき、環境に適した習性をもつようになり、そのうえで習性を抑制するように直近の望む未来への行動を決定する AI が作れるようになると思うのです。

Distributed TensorFlow with Cloud ML Engine (Implementation Details)

■前置き
今現在、世界中の天才と呼ばれる人類層が、必死になって神秘のヴェールをはがしている領域で
ごく最近使われるようになってきた単語を使って説明するので、かなり知的負荷が高いこと書きます。
筆者もいっぱいいっぱいで、わからなくても気にせず、読み流してください。

■本記事で伝えたいこと
Deep Learning という言葉が流行り始めたのが 2012 年の ImageNet(別名Large Scale Visual Recognition Challenge)にてトロント大学チームがこれまでの画像認識率に大差をつけて勝利したころから。
世界中で人工知能 (AI) ブームが巻き起こり、Deep Learning ライブラリの群雄割拠時代が始まり、ようやく研究者の間で一つのライブラリに収束してきたこの頃です。
GoogleDeep Learning 用のライブラリ Tensor Flow と、膨大な計算量を簡易にスケールアウトできる Cloud ML サービスを提供しています。
Cloud ML は複数の計算マシンを活用して、本来自宅PCだと数週間かかるようなAIの計算を、その数百分の1の数時間で完了させる道具と認識できます。
世界で誰も見たことが無いような予測結果を描く Deep Learning を中心としたAI技術に携わる方は、Google の Cloud ML を最大限に活用しなければならないと言っても過言ではありません。(むしろここがスタートラインです)

Cloud ML と Tensor Flow の使い方ドキュメント
Introduction  |  TensorFlow
Distributed TensorFlow  |  TensorFlow
を順番に読んでいけば、Tensor Flow の Core API を利用した場合は、 あなたの実装コードに cluster (複数マシンの役割設定)定義を書く必要があることに気づくでしょう。
その具体的な記述方法と、なぜこんな書き方になるのか理解しなければならない概念イメージの中で特に集中して見えていなければならない点を強調します。
先ほどのリンク先を読み進めて、疑問点が晴れるのが一番良いので、深く理解できた方はこの先を読む必要はありません。
ここから先は、実装を試しながら深く読み進めた人間が、結局、少数の何を覚えておけばよいかという点を示していきます。

■具体的な記述方法

前提知識、技術水準はかなり高いかもしれません。
以下の項目ができるようになってから続きを読んでください。(ちょっと偉そうですね、すみません)

  1. Tensor Flow の Core API を使ってデータフローグラフのメンタルモデルを構築できる(要はコード見ただけで計算内容がイメージでき、図で他人に説明できる、別に他人は理解できなくて良い)
  2. 登場する Python 書式で不明点がない (または、不明書式が出てきてもすぐに調べて解決できるくらいのコンピュータ知識とプログラミング経験)

前提を揃えたら、Tensor Flow の Cloud ML 実行のチュートリアルが以下のリンクに示されているので、なぞります。
Using Distributed TensorFlow with Cloud ML Engine and Cloud Datalab  |  Cloud ML Engine for TensorFlow  |  Google Cloud
2018年7月に私が試した限りでは、問題なくすべてのチュートリアルが完了することを確認できました。

ここまでやった人なら気づきますが、コードレベルの詳細説明は載っていないので、このサンプルをベースに活用するには、利用した Python コードを読んで理解する必要があります。
コードをローカルに引きます。まずは次の git clone コマンドを成功させてください。

git clone https://github.com/GoogleCloudPlatform/cloudml-dist-mnist-example

PythonIDE として今おすすめなのは VSCode でしょうか、プラグインを入れて Python 実装を見やすくしておいてください。
キーワードとして cluster を検索します。
一つも引っかかりません。

ということで、これ Custom Estimator という Core API 使わない Tensor Flow のライトユーザー向けサンプルですね。

Tensor Flow 利用者は、新しく理論を作り出すような玄人向けの Core API を活用する人と、出来合いの Estimator をいじるライトユーザーの二つのユーザー層に分かれています。
今回のサンプルはライトユーザー層向けのものでした。

cluster の書き方は、結局
こちらDistributed TensorFlow  |  TensorFlow
を参照して、頑張って API ドキュメントの知識を繋ぎながら確信あるサンプルコードを自力で作る必要があります。

ということで、私が自力で作ってみるので、少々お待ちください。

追記:
とても参考になる実装を見つけました。
2017年10月と、半年以上古いですが Google の中の人のサンプルです。
github.com

こちらがグラフ作成時に気を付けることです。
あらかじめ cluster を作っておいたら、その情報を使って現在のマシンに関する文字列を作り、device_fn を作ります。
それを tf.device に渡して、その後のスコープでグラフを作成します。
これによって、グラフは指定されたマシンでのみ実行されることになります。

    if self.cluster:
      logging.info('Starting %s/%d', self.task.type, self.task.index)
      server = start_server(self.cluster, self.task)
      target = server.target
      device_fn = tf.train.replica_device_setter(
          ps_device='/job:ps',
          worker_device='/job:%s/task:%d' % (self.task.type, self.task.index),
          cluster=self.cluster)
      # We use a device_filter to limit the communication between this job
      # and the parameter servers, i.e., there is no need to directly
      # communicate with the other workers; attempting to do so can result
      # in reliability problems.
      device_filters = [
          '/job:ps',
          '/job:%s/task:%d' % (self.task.type, self.task.index)
      ]
      config = tf.ConfigProto(device_filters=device_filters)
    else:
      target = ''
      device_fn = ''
      config = None

    with tf.Graph().as_default() as graph:
      with tf.device(device_fn):
        # Build the training graph.
        self.tensors = self.model.build_train_graph(self.args.data_dir, self.args.batch_size)

気になる cluster の作成方法ですが、以下のように環境変数 TF_CONFIG から情報を取得して、マシン情報を取得し、処理を分岐します。

  env = json.loads(os.environ.get('TF_CONFIG', '{}'))

  # Print the job data as provided by the service.
  logging.info('Original job data: %s', env.get('job', {}))

  # First find out if there's a task value on the environment variable.
  # If there is none or it is empty define a default one.
  task_data = env.get('task', None) or {'type': 'master', 'index': 0}
  task = type('TaskSpec', (object,), task_data)

  cluster_data = env.get('cluster', None)
  cluster = tf.train.ClusterSpec(cluster_data) if cluster_data else None

クラウドで準備したマシンの環境変数 TF_CONFIG から、このように情報を取得しなさいというドキュメントは、こちらに書かれています。
Using TF_CONFIG for Distributed Training Details  |  Cloud ML Engine for TensorFlow  |  Google Cloud

最後に、ps ことパラメータサーバーだった場合は、グラフ構築を行う前に server.join を行って、他の master や worker からのリクエストを待つために join します。

def dispatch(args, model, cluster, task):
  if not cluster or not task or task.type == 'master':
    # Run locally.
    Trainer(args, model, cluster, task).run_training()
  elif task.type == 'ps':
    run_parameter_server(cluster, task)
  elif task.type == 'worker':
    Trainer(args, model, cluster, task).run_training()
  else:
    raise ValueError('invalid task_type %s' % (task.type,))


def run_parameter_server(cluster, task):
  logging.info('Starting parameter server %d', task.index)
  server = start_server(cluster, task)
  server.join()

def start_server(cluster, task):
  if not task.type:
    raise ValueError('--task_type must be specified.')
  if task.index is None:
    raise ValueError('--task_index must be specified.')

  # Create and start a server.
  return tf.train.Server(
      tf.train.ClusterSpec(cluster),
      protocol='grpc',
      job_name=task.type,
      task_index=task.index)

パラメーターサーバーは join するとして、他の master と worker は?
と思いますが、以下のように session を作る際に target として server を指定します。

server = start_server(self.cluster, self.task)
target = server.target
with self.sv.managed_session(target, config=config) as session:

このセッションを run すると、それぞれのマシンで計算が行われた時に、join しているパラメータサーバーが必要な変数情報を渡してくれるので、worker は単に計算を頑張り、結果をパラメータサーバーに返します。
以下の説明によれば、バッチ処理など、入力情報が異なるものについて計算を行い、変数の変更内容を互いにマージするように取り入れることによって、並列化による計算速度の向上をする仕組みとなっています。
Using Distributed TensorFlow with Cloud ML Engine and Cloud Datalab  |  Cloud ML Engine for TensorFlow  |  Google Cloud

Tensor Flow API についての知識をあらかたそろえてから Cloud ML のドキュメントを読み進めると、こうした Tensor Flow の記述についてわかってくるということでした。
Cloud ML は日本語訳が充実しているので、英語が苦手でも素早く読めます。

Concepts  |  Cloud ML Engine for TensorFlow  |  Google Cloud

Unity:UNETの基礎と実装

最近素晴らしい UNET の記事を見つけました。
nn-hokuson.hatenablog.com

ネットワークゲームには二種
Peer-to-Peer 型と Client-Server 型があります。

数万人がアクセスするような商業成功タイプは Client-Server 型ですが、UNET は残念?ながら数人がリアルタイムに同じゲームを楽しむ Peer-to-Peer 型をサポートするシステムです。
UNET でオブジェクトの動きが同期するのはサーバー側の動きを複数のクライアントに共有するからですが、難しくなるのはクライアント側での動きを、他のクライアントに伝える仕組みです。

これを直感的に説明してくれたのが、冒頭で紹介したおもちゃラボさんの記事でした。
その記事で出てくる Local Player Authority というものをどのように実装するか示して、UNET の全体像をつかんでもらおうと思います。

Unity で UNET を利用したい場合に、絶対に必要になるコンポーネントは二つあります。
一つ目:NetworkManager コンポーネント
f:id:simplestar_tech:20180627080438j:plain
二つ目:NetworkIdentity コンポーネント
f:id:simplestar_tech:20180627080706j:plain

またプロジェクトの設定も一か所いじる必要があります。
最近だと設定項目なくてもいけるようですが、もしビルド対象プラットフォームの publishing settings を開いて Internet Client Server のチェックが外れていたら、チェックを入れてください。
f:id:simplestar_tech:20180627081524j:plain

後は追加したコンポーネントのインスペクタを編集する作業に集中します。

まず初めに作るのが PlayerPrefab です。
Network Identity コンポーネントを追加したゲームオブジェクトを Prefab 化します。(Prefab化っていうのは、シーンのヒエラルキーからプロジェクトビューにドラッグ&ドロップする操作です。)
こんな見た目のものがプロジェクトに作られれば OK
f:id:simplestar_tech:20180627082259j:plain

おもちゃラボさんの説明に出てくる Local Player Authority というのは、この Player オブジェクトのインスペクタを見たときに表示される Network Identity のパラメータのことを指します。
インスペクタでチェックを入れたら、次の見た目になります。
f:id:simplestar_tech:20180627082614j:plain

最後の基本ステップは、この Player オブジェクトを Player Prefabとして登録する作業です。
Network Manager コンポーネントを追加したオブジェクトをシーンに作成し、これのインスペクタを確認します。
先ほどの Player オブジェクトを Player Prefab を設定すると、このようになります。
f:id:simplestar_tech:20180627083058j:plain

あとは続けてこの Network Manager にOnline Scene とサーバーのアドレスを書き込むだけで、ひとまずローカル環境でチェックできます。
シーンの登録はビルドに含まれるシーンでなければ選択できませんのでご注意ください。(ビルド登録→Network Manager に登録の順番を開発者が守る必要がある)
f:id:simplestar_tech:20180627084144j:plain

最後の最後に、サーバーとしての待ち受けとクライアントとしての接続という処理が必要でした…
適当なスクリプトを作成してシーンオブジェクトに登録します。

using UnityEngine;
using UnityEngine.Networking;

public class InternetClientServer : MonoBehaviour {

	void Start () {
#if UNITY_EDITOR
        NetworkManager.singleton.StartServer();
#else
        NetworkManager.singleton.StartClient();
#endif
    }
}

エディタ実行するとサーバーとして機能し、それ以外のビルド成果物はクライアントとして機能するというコードです。
エディタ実行後、ビルド成果物を実行すると Player オブジェクトがエディタ側のヒエラルキーに追加されます。(確認風景)
f:id:simplestar_tech:20180627085247j:plain

プレイヤー(クライアント)から、ネットワークオブジェクト(Network Identityが追加されたオブジェクト)の情報を更新したい時
UNET の構造上、必ずネットワークコマンドを実行します。
そのコマンドを実行するスクリプトは、必ずこの Player (Clone) オブジェクトに追加されたスクリプトコンポーネントでなければならないということに注意してください。

あとは、UNET のコマンドや Sync Var 属性、Network Transform コンポーネント、Network Animator コンポーネントの公式ドキュメントを読み進めて習熟度を上げていけば、UNET でやりたいことができるようになると思います。
以上、数年ぶりに UNET の基礎を確認する作業でした。

おもちゃラボさんのUNET記事に感謝です!

TensorFlow GAN メモ

■前置き
今から半年ほど前の Google AI Blog の記事

ai.googleblog.com
TFGAN: A Lightweight Library for Generative Adversarial Networks

要約すると

「GANってのは、コンテンツ生成という新しい研究領域を広げてます。
例を見てみ、すごいっしょ!
すぐに動かせる軽量のチュートリアル作ったから、見てみ
めっちゃテストしたから、数学や確率のミスは無いと思って信用してツールとして使ってほしい
俺たちと同じ環境使って一緒にがんばろ!」

なるほど、これは面白そう!
前回、前々回と TensorFlow がGoogle Cloud ML で動くようになったし
とりあえずサンプル動くところまでやってみたくなりました。

そこでだ、実装について最初に読んだ解説がこちら
github.com
TensorFlow-GAN (TFGAN)

チュートリアルへの案内あり、こちらを先に読むことにした。
github.com

■作業ログ

まずは TensorFlow で gan が使えるか確認します。
python -c "import tensorflow.contrib.gan as tfgan"
エラーが出るようなら、出ないように TensorFlow のチュートリアルをこなしましょう。
一応エラー出なかった、使える!

Python 勉強
コード単体ならどんどん読み進められますが、ファイルをまたいで、not found エラーとかランタイムで出されると辛いっすよね。
この辺は TensorFlow とは別で Python の基本から頭に入れておかなければならない。
kannokanno.hatenablog.com

一言じゃ語れない
Python コード書式で不明点をなくす + Tensor Flow のデータフローグラフのメンタルモデルを作れるようにする
この二つの合わせ技がないと TensorFlow-GAN のコード読みの入り口に立てません。
無知から始めると、マスターまで三日くらい要しますので、連休とかに集中して取り組む覚悟が必要だと思います。

GAN の論文とか解説記事も理解できるようになる必要もあるので、結構頭使います。

もう私もいつのまにかオッサンなので、ゼロから知識構築するの大変

これが生きるってことなのか(◞‸◟)