おもちゃラボ

Unityで遊びを作ってます

【Unity】Tensor Flowを使ってディープラーニングをする

Unityで簡単にTensorFlowを使ってディープラーニング(Deep Learning)したい場合は、ml-agents(Machine Learning Agents)というフレームワークを使うのが便利です。今回の記事では、ml-agentsの考え方と概要、ml-agentsを使った機械学習の方法を説明します。

記事の内容は次のようになります。

今回使用するml-agentsのサンプルは、次のようなパドルとボールを使ったものです。未学習の状態ではパドルは簡単にボールを落としてしまいます。ディープラーニングで学習後は、できるだけボールを落とさないように、自動的にパドルの傾きを調節できるようになります。

f:id:nn_hokuson:20171226205722j:plain:w600

Unityのml-agentsの概要

ml-agentsを使うとUnityでディープラーニングの学習が簡単にできます。ディープラーニングとはなにか?を手を動かしながら学習したい方は次の書籍が役に立ちました。

Unityのml-agentsは「Academy」「Brain」「Agents」の3つのコンポーネントで構成されています。それぞれのコンポーネントの役割は次のようになります。

コンポーネント名 役割
Academy TensorFlowとやり取りするためのコンポーネント
Brain Agentsを束ねるコンポーネント
Agents Reward(報酬)を計算するためのコンポーネント


色々なコンポーネントがあってややこしいですが、最初のうちはAcademyとBrainは殆ど書き換えることはないので、少しの間忘れてもOKです(たぶん・・・)

重要なのはAgentsスクリプトで、Agentsのスクリプトに「どのような場合にRewardを増やし、どのような場合に場合にRewardを減らすか」を定義します。例えば上の3DBallのデモの場合、ボールを落としたらReward=-1、ボールが保持されていればReward=0.1と定義しています。

f:id:nn_hokuson:20171226203851p:plain:w500

Agentsで報酬系の設定ができたらて、機械学習によってモデルに動きを学習させます。Unityでml-agentsを使って学習をするには次のような流れになります。

  1. アプリをExternalモードで書き出す
  2. そのバイナリを使ってディープラーニングで学習をさせる
  3. 学習結果をファイル出力
  4. アプリをInternalモードにして学習結果を読み込み実行

Unityでディープラーニングをする流れは図にすると次のような流れになります。

f:id:nn_hokuson:20171226205559j:plain

Anacondaで環境設定

TensorFlowを使うにはPython3の環境が必要になります。MacにはPythonがインストールされていますが、標準ではバージョンが2系のため、追加で3系のPythonをインストールする必要があります。

Homebrewなどを使ってインストールすることもできますが、何かとハマリポイントが多いため、ここではAnaconda(蛇つながり!?)と呼ばれるパッケージを使ってインストールすることにします。

以下のサイトからインストーラをダウンロードしてインストールを進めて下さい。

www.anaconda.com

インストール終了後、ターミナルを起動して次のコマンドを入力してみて下さい。

python --version

次のように、「Python 3.6.3 :: Anaconda, Inc.」など、3系の表示になっていればOKです。

f:id:nn_hokuson:20171223132846p:plain:w600

ml-agentsをインポートする

続けてUnityでディープラーニングをするための本体パッケージ「ml-agents」をダウンロードします。

github.com

上のサイトから「Clone or Download」→「ZIP Download」を選択して、ml-agentsパッケージをダウンロードしてください。

f:id:nn_hokuson:20171223133153j:plain:w570

ダウンロードできたら、ml-agents-masterフォルダの「unity-environment/Assets/ML-Agents/Examples/3DBall/Scene.unity 」をダブルクリックして起動して下さい。3DBallのプロジェクトが表示されます。

f:id:nn_hokuson:20171223133341j:plain:w570

TensorFlowが使えるようにセットアップする

UnityでTensorFlowが使えるようにするためのプラグインをプロジェクトにインポートします。次のサイトからプラグインをダウンロードしてインポートして下さい。

https://s3.amazonaws.com/unity-agents/TFSharpPlugin.unitypackage

インポートしたTensorFlowのプラグインが使えるようにするための設定を行います。メニューバーから「Edit→Project Settings→Player」を選択し、インスペクタの「Other Settings」→「Scripting Runtime Version」を「Experimental(.NET 4.6 Equivalent)」に設定します。

また、「Scripting Define Symbols」の欄に「ENABLE_TENSORFLOW」と入力して下さい。

f:id:nn_hokuson:20171223133855p:plain:w400

ここまでで、UnityでTensorFlowを使うための準備は完了です。次はTensorFlowを使ってディープラーニング学習をしていきます。

トレーニング用のバイナリを書き出す

続いて、機械学習によってパドルがボールを落とさないように学習させます。ml-agentsを使って学習をするには、一旦アプリをExternalモードで書き出し、そのバイナリを使って学習を進めます。

f:id:nn_hokuson:20171226210544j:plain

ヒエラルキービューからBall3DAcademy/Ball3DBrainを選択し、インスペクタでBrain Typeを「External」に変更します。これにより、TensorFlowを使った入力を受け付けるようになります。

f:id:nn_hokuson:20171223135110p:plain:w400

続いてメニューバーから「File」→「Build Settings」を選択し、Scenes in Buildに3DBallのシーンが選択されていることを確認してから、Buildボタンを押してアプリを書き出します。書き出すアプリ名は任意のもので構いませんが、ここでは「3dball」にしました。アプリを書き出す場所はダウンロードしたml-agents-masterの中のpythonフォルダを選択して下さい。

f:id:nn_hokuson:20171223135303j:plain:w450

機械学習でトレーニングする

アプリをExternalモードで書き出せたところで、いよいよメインのTensorFlowを使った学習の段階に進みます。

f:id:nn_hokuson:20171226210702j:plain

まずはml-agents-master内のpythonフォルダにターミナルで移動し、次のコマンドを入力して下さい。これにより必要なライブラリがインストールされます。

pip install .

次にpythonを使って学習を行うため、次のコマンドを入力して下さい。

jupyter notebook

次のような画面がブラウザに表示されるので、PPO.ipynbをクリックして開いて下さい。

f:id:nn_hokuson:20171223135837j:plain:w450

ここからは、実際にTensorFlowを使ってディープラーニングを実行しましょう。ブラウザでテキストボックスに表示されているPythonのプログラムを選択し、画面上部の「Run」ボタンを押していきます。

f:id:nn_hokuson:20171223141321p:plain:w400

Step1. ライブラリのインポート

In [1]:では次のようなwarningが出るかもしれませんが、無視しても問題ありません。

f:id:nn_hokuson:20171223142508j:plain:w620

RuntimeWarning: compiletime version 3.5 of module 'tensorflow.python.framework.fast_tensor_util' does not match runtime version 3.6

Step2. Hyparametersの設定

ディープラーニングを使って学習するためにはHyparametersと呼ばれるパラメータを設定する必要があります。必ず変更する必要があるのは「env_name」の項目で、ここにはさきほど書き出したアプリのファイル名を指定します。先ほどは「3dball」という名前で書き出したので、「env_name = "3dball"」と書き換えて下さい。

f:id:nn_hokuson:20171223140122j:plain:w620

このハイパラメータの設定がディープラーニングの学習方針を決めるのですが、最初のうちはどう設定したら良いのか戸惑うかもしれません。デフォルトのままでも問題ないので、一旦このまま進めてみましょう。

ハイパラメータの意味とおすすめ設定は次のようになります。慣れてきたら学習させたいモデルによって変更してみて下さい。

ハイパラメータ名 値の範囲 意味
Batch Size 32〜409600 勾配下降計算するときに使用するデータ量
Beta 1e^{-4}1e^{-2} 小さくすればエントロピーの減少が早くなる
Hidden Units 32〜512 ディープラーニングで使用する隠れ層の数
Learning Rate 1e^{-5}1e^{-3} 勾配下降する場合のステップ量

ハイパラメータの意味はこちらで詳しく解説されているので、参考にして見てください。

https://github.com/Unity-Technologies/ml-agents/blob/master/docs/best-practices-ppo.mdgithub.com

こちらのサイトでは日本語で紹介されています。

qiita.com

Step3. アプリケーションのロード

ln [3]:ではアプリを起動して通信を開始するまで少し時間がかかります。次のように表示されたら正しくアプリがロードされています。もし、表示されない場合はBrain Typeが「External」になっているかを再度確認して下さい。

f:id:nn_hokuson:20171223141745p:plain:w630

Step4. TensorFlowを使ったディープラーニング

ln [4]:がディープラーニングの実体です。PCの性能によりますが、30秒〜1分くらいで次のように学習結果が表示されます。Mean Rewardが70〜80程度になれば順調に学習が進んでいます。ある程度のところで「Run」ボタンの横にある停止ボタンを押して、ディープラーニングによる学習を停止しましょう。

f:id:nn_hokuson:20171223143051p:plain:w630

Step5. 学習結果の書き出し

最後のln [5]では学習結果をファイルに書き出します。学習ファイル名は、Hyparametersの項目で設定したenv_name + ".bytes"です。今回はenv_nameを3dballにしたので、学習結果のファイル名は3dball.bytesになります。

学習結果を反映する

学習が終わるとpython/models/ppoフォルダの中にball.bytesファイルが生成されます。これがTensorFlowを使ったディープラーニングの学習結果になります。この学習結果をアプリに読み込ませて実行してみましょう。

f:id:nn_hokuson:20171226210848j:plain

ball.bytesファイルをUnityのAssets/ML-Agents/Examples/3DBall/TFModelsフォルダに配置します。続いてヒエラルキービューからBall3DAcademy/Ball3DBrainを選択し、インスペクタでBrain TypeをInternalに変更します。これにより、TensorFlowを使った入力を受け付けるようになります

f:id:nn_hokuson:20171226212711j:plain:w300

Graph PlaceholdersのSizeを「1」にして設定項目を次のように指定指定下さい。この値はTensorFlowがノイズパラメータとして使う値です。Action Space TypeがDiscrete だった場合はepsilonの設定は不要です。

パラメータ
Name epsilon
Value Type Floating Point
Min Value 0
Max Value 0


さて・・・・いよいよです。
長かったですね!

Unity画面上部の再生ボタンを押してゲームを実行してみて下さい。ちゃんと学習できていれば、ボールを落とさないようにパドルの傾きが自動的に調節されるはずです。

f:id:nn_hokuson:20171226212138g:plain:w600

ちゃんと学習できていますね〜(^^)/ 上の結果で大体Mean Rewardが80程度のものです。もう少し値が低くても正しく動くはずです。

まとめ

今回は、UnityでTensorFlowを使ってディープラーニングをしてみました。ml-agentsというフレームワークを使うことで簡単に機械学習ができました。次回はAgentsのスクリプトを書き換えて独自のモデルを学習させてみようと思います!


2匹目の猫を飼うときの注意点と仲良くなるまでの60日間

珍しくコンピュータ系以外のお話です。我が家には2歳になる猫がいるのですが、縁あって2ヶ月の子猫をお迎えすることになりました。

2匹目の猫を迎えるにあたって注意したこと、大変だったこと、そして二匹が仲良くなるまでの60日間をシーケンシャルに書いていきたいと思います。

f:id:nn_hokuson:20171223104503j:plain:w500
↑いまはすっかり仲良しです!!(2匹目の猫が来てから6ヶ月後)

猫さん紹介

コジロー

先住猫は2歳のオスで去勢済みです。名前はコジローです(笑)

f:id:nn_hokuson:20170816193451j:plain

猫の種類はシルバータビーのアメリカンショートヘアー。好奇心はあるけど超ビビりな性格で、気になるものがあって遠巻きに様子を見続けるタイプ。

動物病院以外で他の猫と接触した経験はありません。

お千代

2匹目の猫は2ヶ月のメスです。名前はお千代です。なるべく昭和臭い名前をと、3日間くらいは厳選しました(笑)

f:id:nn_hokuson:20170816194041j:plain

レッドタビーの、こちらもアメショ。お千代は好奇心が強く物怖じしない性格です。気になったらとりあえず突っ込んでいくタイプ。この子は我が家に迎える前、ペットショップで他の猫ちゃんと過ごしていました。

1日目

お昼頃にペットショップに受け取りに行って、タクシーで帰宅。まずは部屋を完全隔離しました。コジローはリビング、お千代は寝室に。うちの間取りを描くとこんな感じ。

f:id:nn_hokuson:20170816195726p:plain:w400

部屋を隔離した上で、お千代にはゲージに入ってもらいます(まだ2ヶ月なので安全のためにも!)お互いの姿も見えないよう扉は締め切りました。下の写真では開いていますが・・・どうしてでしょう(笑)

f:id:nn_hokuson:20170816195944j:plain:w400

ちなみに使ったゲージはコチラの2階建てのものです。コジローをお迎えするときに買ったものを流用しています。

コジローは今まで入れていた部屋に急に入れなくなったのが嫌なのか、部屋の中にいる何かが気になるのか、お千代のいる部屋の前に座り込んで「にゃーにゃー」鳴いていました。動物の勘かな!?

2日目

お千代はゲージに入れたままで、遊ぶときだけ外に出しています。めちゃくちゃスバシッコイから写真を取るのにも一苦労・・・

f:id:nn_hokuson:20170816202630j:plain:w500

猫じゃらしは↓を使っています。お千代に1度目のワクチンを打つまでは、できるだけコジローと物理的な接触は避けるために猫じゃらしは新しく買いました

[asin:B002WSHMI6:detail]

コジローが好きな時に覗きに行けるように、夕方頃からお千代のいる部屋の扉を開けっぱなしにしました。コジローがそれを見つけて「あの部屋開いてる!」と部屋に入っていこうとしましたが、初めてお千代の姿を目にした瞬間、部屋を飛び出して全速力で逃げていきました。

足の上をドリフトしながら逃げていったので、3本の流血マークが・・・!!

その後コジローは、一度だけ扉から首だけ伸ばしてお千代の部屋を覗き込んでいたけれど、めっちゃ腰は引けてました(笑)そしてまたもや猛ダッシュで逃げる・・・

3日目

コジローは、お千代のいる部屋の扉が全開だと、怖くて部屋にすら近づけないようでした。
お千代の部屋の扉を半開きにしてみたところ、部屋の手前くらいのところまではおそるおそる近づくようになりました。

f:id:nn_hokuson:20171223102909j:plain:w550

4日目

進展なしです。お千代のいる部屋の扉をちょい開けしておいたら、一度だけ覗きに行っていました。それ以外は普段と変わらずで、人間がヤキモキするターン。

5日目

これまでビビってばかりのコジローでしたが、このあたりからお千代の部屋を覗きに行く回数が増えました。あいかわらず部屋の手前で引き返してはいましたけど(笑)

あと、コジローはお千代のを目撃してから鳴かなくなっていたのですが、この日からまた「にゃーにゃー」鳴くようになりました。

一度だけ、お千代のいる部屋の中まで行きました。おちよのいる部屋を少し見てから、離れていきました。ここで気づいたんですが、どうやらトイレ前に、安全確認のためにお千代のの部屋を観察しているようでした。「すごく近づいてる!」と感じたときは、だいたいその後にトイレをしていたので・・・

f:id:nn_hokuson:20171223104828j:plain:w550

この2匹の猫の硬直状態に人間のほうがしびれを切らし・・・(笑)お千代のをキャリーケースに入れた状態で、コジローを抱っこして近づいてみました。1.5メートルほど近づいたらコジローがビビりだしたので、すぐに放しました(なんせ、流血事件があったばかりなもので(笑)お千代はその間、ずっと「にゃーにゃー」言ってキャリーから出してほしそうでした。


【Unity】マリオっぽいゲームを作るのに必要な5つのこと

ファミコンの横スクロールマリオの挙動をUnityで作ってみました。Physicsに全ておまかせ・・・というわけにはいかず、思っていたよりも大変です(笑)ということで、今回はそのレポートを書いてみます!

f:id:nn_hokuson:20171130210038p:plain:w400

今回の記事では、Unityでマリオの挙動を作るのに必要な項目を「ジャンプ編」「衝突判定編」「アニメーション編」「横スクロール編」「入力デバイス編」の5つに分けて紹介していきます。

ジャンプの挙動編

マリオのジャンプは普通のジャンプとは異なる点が3つあります。

  1. ジャンプボタンを押し続けると、ジャンプの高さが変わる
  2. ジャンプの軌跡は放物線ではない
  3. 空中で左右キーを押すと移動できる

なんか、これだけでもう大変そう・・・(笑)1つずつ、見ていくことにしましょう。

ジャンプボタンを押しっぱなしにしたときの挙動

2Dマリオでは、ジャンプボタンを押しっぱなしにしたときジャンプの高さが変化します。といっても、ボタンを押しているあいだ、常に上方向に加速度がかかり続けるわけではなく、放物線の頂点が変わるイメージです。

f:id:nn_hokuson:20171130192605p:plain:w270

ジャンプに使う放物線の方程式は次のものを使いました。横軸(x軸)はフレーム、縦軸(y軸)はピクセルです。

y = -0.045*(x-37)^2+62

図にするとこんな感じ。要するに最大37フレームで62ピクセル上昇するということです。

f:id:nn_hokuson:20171127205916p:plain:w300

ジャンプボタンが押されているあいだは、上の方程式に従ってマリオを移動します。

ジャンプ後、落下の軌跡

マリオのジャンプは物理法則に従った挙動にはなっていません(ボタンを押している間、ジャンプし続ける時点で超人ですが。笑)特にジャンプしたときの軌跡が放物線にならないのが特徴的です。

次の図のように上昇速度と下降速度が異なります。ジャンプするときの重力と落下するときの重力が変化する、といったほうが分かりやすいかもしれません。

f:id:nn_hokuson:20171130192624p:plain:w400

そこで、ジャンプボタンが離されたら、通常の重力計算に切り替えます。落下速度の計算は次の式を使います。

落下速度 += 重力加速度
マリオのy座標 += 落下速度

重力加速度は-0.02ピクセル/フレーム^2で、速度は-0.3ピクセル/フレーム以下はクリップします。この辺の値は、見た目で微調整しています。

空中で移動できる

マリオは空中で十字キーを押すと、矢印方向に移動できます。かなり意味不明ですね(笑)ただ空中移動の実装は簡単で、マリオの速度ベクトルに左右方向の加速度を加えるだけです。

当たり判定編

ジャンプにPhysicsを使っていないので、当たり判定も全てPhysicsにおまかせ!という訳にはいきません。といっても矩形同士の当たり判定を全て自前でやるのは結構大変です。

qiita.com

ということで、ここではフレーム毎に上下左右の4方向にRayを飛ばして当たり判定をすることにします。

f:id:nn_hokuson:20171130192642p:plain:w180

UnityでRayを飛ばすにはRaycastメソッドを使います。使い方は次のような感じになります。

RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.up, 0.5f);
if (hit.collider != null)
{
     //上方向に障害物があった場合
}	

上方向の衝突判定

上方向に当たった場合、ブロックに当たったので、ジャンプをやめて落下に切り替えます。落下に切り替える際にはy方向の速度は0にします。

f:id:nn_hokuson:20171130192655p:plain:w500

横方向の衝突判定

左右方向に当たった場合は、プレイヤのX方向の速度ベクトルを0にしています。これでジャンプ中に横からブロックに当たった場合は、壁に沿って滑りながら進むことになります。

f:id:nn_hokuson:20171130192713p:plain:w600

めり込み対策

また、ブロックに当たった場合はめり込みを押し戻しています。めり込みを押し戻す方法は、全てのブロックが1m単位のグリッド上に乗っていることを利用して、次の式で計算しています(かなり手抜きです。笑)

プレイヤの位置=ブロックの位置±1

f:id:nn_hokuson:20171130192730p:plain:w350

この方法で大体うまくいくのですが、土管だけは2m x 2mの大きさなので、この大きさのBox Colliderを使うと押し戻しの計算が正しくできません。

タグを使って処理を分けてもよいのですが、今回は1m x 1mの大きさのColliderを4つ配置して対応しています。これだとスクリプトで処理を分けなくてもよくて、様々な形の障害物にも対応できるので便利です!

f:id:nn_hokuson:20171130194417p:plain:w450

真面目にPhysicsを使わずに当たり判定をしたい場合は「ゲームプログラミングのためのリアルタイム衝突判定」が役に立ちます。

アニメーション編

プレイヤのステートは次の3つを実装しています。本当は急に走る方向を変えた場合のスリップ状態もあるのですが・・・勝手に省略しました(笑)

  • Idle
  • Run
  • Jump

次のようにIdleとJumpは1枚絵、Runは3枚のパラパラ漫画で作ります。

www.youtube.com

アニメーションの切り替えはAnimatorを利用して制御しています。次のように「Idle」「Run」「Jump」のステートを作り、プレイヤの状態によってアニメーションを切り替えます。

f:id:nn_hokuson:20171127213341j:plain:w600

「Idle」と「Run」の状態はプレイヤの横方向のスピードによって切り替えます。スピードが0.005ピクセル/フレーム以上だったら「Run」に切り替え、0.01以下だったら「Idle」に切り替えています。

このように、2つの閾値を変えてヒステリシスを持たせることで、アニメーションがパタつくのを防ぐことができます。

f:id:nn_hokuson:20171128210755p:plain:w400

また、ジャンプ制御はbool値で行います。ジャンプボタンが押されたときにフラグを立てて、地面に着地したときにフラグを折ります。これで、ジャンプ中はジャンプ用のスプライトが表示されます。

f:id:nn_hokuson:20171130192828p:plain:w400

Animatorの考え方や詳しい使い方は「Unityの教科書」でも紹介しているので、ぜひ参考にしてみて下さい!

横スクロール編

プレイヤの移動にあわせてカメラを動かすとき、単純に「カメラのX座標=プレイヤのX座標+offset」のように実装するとカメラがスムーズにスクロールせず、ガタガタしてしまいます。

これはプレイヤを動かすスクリプトとカメラを動かすスクリプトの実行順序が保証されていないことが原因です。順番に呼ばれないと、プレイヤが移動する前にカメラを移動してしまい、カメラの移動速度が一定にならないため、ガタガタして見えてしまいます。

f:id:nn_hokuson:20171130194344p:plain
Unityではメニューバーから「Edit」→「Project Settings」→「Script Execution Order」という機能を使ってスクリプトを実行する順序を指定できます。

今回は、プレイヤの移動→カメラの移動という順番になるようにスクリプトの実行順序を指定しました。

f:id:nn_hokuson:20171128213836p:plain:w350

コントローラ入力編

最後に、作成したマリオをキーボードだけではなく、ゲームパッドでも操作できるようにします。ゲームパッドの入力を取得するにはInput Managerを使います。

Input Managerは簡単に言うと、キー入力やゲームパッドの入力、タッチパネルからの入力に名前をつける仕組みです。次の例では「Aキー」と「十字キーの左ボタン」のどちらにも「Left」という名前をつけています。

f:id:nn_hokuson:20171120224625j:plain:w450

Input Managerを使うことで入力デバイスを仮想化できるため、異なる入力デバイスでもスクリプトからは同じ名前で扱うことが出来るようになるのです。

今回のゲームでは次のようにキーを割り当てました。

  • 十字キー:左右の移動
  • Aボタン:ジャンプ
  • Bボタン:ダッシュ

Input Managerからの入力は、GetButtonDownメソッドやGetAxisメソッドなどを使って取得できます。

void Update () {
	if(Input.GetButtonDown("Left")){
		Debug.Log("Left Key Pressed");
	}
}

Input Mnagerに関しても、次の記事で使い方を紹介しているので、参考にしてみて下さい。

nn-hokuson.hatenablog.com

まとめ

Unityを使って2Dマリオの挙動を目コピしてみました。敵の挙動やキノコによるパワーアップなど、まだまだ未完成ではありますが、ジャンプの挙動などマリオをマリオたらしめている重要な要素は網羅できたかと思います。