OpenAI Retro Contestの「Gym Retro Integration」でソニック・ザ・ヘッジホッグをプレイする

つい先日、OpenAIが主催するOpenAI Retro Contestが終了したようです。このコンテストでは"Sonic The Hedgehog"を題材に、ゲームをプレイするエージェントを作成しその性能を競うものでした。コンテストの結果は実際にプレイ動画とともにleaderboardから見ることができるのですが、上位陣のエージェントのプレイを眺めていると、深層強化学習の進歩をひしひしと感じます。中でも1位の"Dharmaraja"というアリババのチームは途中でオブジェクトの隙間に入り込むバグを発見して大きくスコアを伸ばしていたようです。この先機械がゲームをプレイする未来を考えると、実用的には機械がテストプレイをしたり、非実用的にはTASを自動で作成したりと、色々と夢が広がるものでした。

さて、私もちょっと強化学習を勉強してみようと思ってOpenAI Retro Contestのドキュメントを眺めていたのですが、その中で実際にゲーム画面を見ながら開発ができるよう、ゲームの状態を確認したり操作を行うGym Retro Integrationというユーザインターフェイスが開発されていたので、ちょっと試してみました。それほどハマるポイントがあるわけではないのですが、色々とやることがあって若干ややこしいので、利用するまでの手順を書いておきます。

環境

Gym Retro Integrationの準備

1. retoレポジトリをgit cloneする

2018/06/27現在はdevelopブランチからコンパイルする必要がありますので、ソースコードをcloneしてきてdevelopブランチをチェックアウトします。

$ git clone https://github.com/openai/retro/
$ git checkout develop

2. Gym Retro Integrationをコンパイルする

ここからはdevelopブランチのREADME.mdに記載されている通りにコマンドを実行します。

openai/retro at develop

$ brew install pkg-config capnp lua@5.1 qt5
$ cmake . -DCMAKE_PREFIX_PATH=/usr/local/opt/qt -DBUILD_UI=ON -UPYLIB_DIRECTORY
$ make -j$(sysctl hw.ncpu | cut -d: -f2)

なお、fish shellの場合は$()が利用できません。ここではマシンのCPUコア数を指定しているだけなので、make -j4といった形で実行します。

これが終われば、retroディレクトリにGym Retro Integration.appが作成されていますので、ダブルクリックまたはopen "Gym Retro Integration.app"で実行します。

プレイするROMの準備("Sonic The Hedgehog"の場合)

プレイする環境は準備ができたので、次にプレイするソフトウェアの準備をします。ここの部分はOpenAI Retro ContestのDetailにも記載されている内容と同様のものです。

OpenAI Retro Contest

1. Steamで購入する

Steamで今回のOpenAI Retro Contestで対象となっている下記のゲームを購入します。SteamではWindows用と表示されていますが、気にせず購入して問題ありません。

ちなみに、2018/06/27現在はサマーセール中ということで、通常は1本498円のところ、66%引きの168円で購入することができました。

2. gym-retroでsteamからROMをダウンロードする

Steamで購入したソフトをダウンロードします。ゲームを遊ぶときのようにSteam.appからダウンロードする方法もあるのですが、openai/retroでは直接ダウンロードするPythonスクリプトが用意されているので、そちらを利用します。

まず、openai/retroをインストールします。上記でソースコードをcloneしているので、そちらを利用しても問題ありません。

$ pip install gym-retro

次にretro.import.sega_classicsを実行します。ここではSteamにログインするときのUsernameとPasswordnほかに、Steam ガードコードと呼ばれるメールで届くワンタイムトークンの入力が求められます。ここでソニックのソフトを購入できていれば、下記のように3つのゲームが無事ダウンロードできます。

$ python -m retro.import.sega_classics
Steam Username: yag_ays
Steam Password (leave blank if cached):
Downloading games...
Installing games...
Importing SonicAndKnuckles3-Genesis
Importing SonicTheHedgehog2-Genesis
Importing SonicTheHedgehog-Genesis
Imported 3 games

さて、肝心のゲームのROMはというと、gym-retroのパッケージのdata/ディレクトリ以下に配置されます。"Sonic The Hedgehog"の場合はSonicTheHedgehog-Genesis/rom.mdがROMに当たります。これをGym Retro Integration.appで開くことになるので、参照しやすいディレクトリにコピーしておくと良いと思います。

なお、これは利用している環境によって場所が異なりますので注意してください。私の場合はpyenvを利用しているので$HOME/.pyenv/versions/3.6.3/envs/main/lib/python3.6/site-packages/retro/dataにありました。この場所を探すときはipythonでgym-retroをインポートしてどこから読み込んでるのかを確認するのが手っ取り早いと思います。

In [1]: import retro

In [2]: retro?
Type:        module
String form: <module 'retro' from '/Users/yag_ays/.pyenv/versions/3.6.3/envs/main/lib/python3.6/site-packages/retro/__init__.py'>
File:        ~/.pyenv/versions/3.6.3/envs/main/lib/python3.6/site-packages/retro/__init__.py
Docstring:   <no docstring>

Gym Retro Integrationを使ってみる

ようやく準備が終わりました。それではGym Retro Integrationを使ってゲームを実行してみます。

まずGym Retro Integration.appを開くと、以下のような画面が表示されます。まだ何も表示されていないサラの状態です。

f:id:yag_ays:20180627224620p:plain

次にゲームのROMを読み込む必要があります。メニューバーの"Game"→"Open"(または"Load Game...")からROMを選択します。ここではSonicTheHedgehog-Genesisrom.mdを指定しました。

f:id:yag_ays:20180627224712p:plain

ようやくゲームが実行できました!!!ゲームが進むにつれて画面右の"Senario Information"ではFrameやTimestepの値がインクリメントされており、状態がきちんと取得できているようでした。実際には各ステージのシナリオ等を読み込む必要があると思いますが、ここでは割愛します。

そしてゲームのプレイですが、矢印キーで操作したりzキーでジャンプできます。操作方法は、メニューバーの"Window"→"Controls.."で確認したり変更することができます。

f:id:yag_ays:20180627225205p:plain

さて、ここまで色々準備してきましたが、まだエージェントを作成する準備が整っただけです。ここからopenai gymを使って様々な強化学習などの手法を用いてエージェントを学習させていく必要があります。そちらはまだ勉強途中ということもありますので、別の機会にアウトプットできればと思います。

参考

XGBoostのScikit-Learn APIでearly stoppingを利用する

この記事では、XGBoostのScikit-Learn APIを使いながらもearly stoppingを利用する方法を紹介します。

一般的な方法

XGBoostのLearning APIとは違って、Scikit-Learn APIXGBClassifierクラス自体にはearly stoppingのパラメータがありません。その代わりにXGBClassifier.fit()の引数にearly_stopping_roundsがありますので、こちらを利用します。その際にはeval_setを同時に指定する必要があります。

xgb_model = XGBClassifier()
xgb_model.fit(X_train,
              y_train,
              early_stopping_rounds=100,
              eval_set=[[X_test, y_test]])

Python API Reference — xgboost 0.6 documentation

GridSearchCV/RandomizedSearchCVを併用する方法

実際にscikit-learnと組み合わせている場合には単体でのfitよりも、GridSearchCVやRandomizedSearchCVといったグリッドサーチと併用することが多いです。その際には、以下のようにfit_paramsを指定することによって、グリッドサーチ内でのearly stoppingが可能になります。

fit_params = {"early_stopping_rounds": 100,
              "eval_set": [[X_test, y_test]]}

xgb_model = xgb.XGBClassifier()
gs = GridSearchCV(xgb_model,
                  params,
                  fit_params=fit_params,
                  cv=10,
                  n_jobs=-1,
                  verbose=2)
gs.fit(X_train, y_train)

参考

転職しました

前職では様々な方に大変お世話になりました。ありがとうございました。

年明けから新たな職場でのスタートになります。引き続きどうぞよろしくお願いいたします。

Recognizing Textual EntailmentをLSTMで解く

NLPディープラーニング周りの可視化手法が知りたくて色々読んでいる中でReasoning about Entailment with Neural AttentionでRecognizing Textual Entailment(以下RTE)という問題を解いていたので、データセットも公開されていることもあり試しにチャレンジしてみることにしました。

タスク

RTEはテキスト間の含意を推論するタスクです。与えられた2つの文章間の関係性を、幾つかの含意を表すラベルで表現します。データセットによってラベルの表現方法は異なりますが、今回はThe Stanford NLP Groupが公開しているSNLIのデータセットを考えます。

The Stanford Natural Language Inference (SNLI) Corpus

SNLIでは、2つのテキストのペアに対してentailment, contradiction, neutralという3つのラベルが付けられています。

例えば、

Text: A soccer game with multiple males playing.

Hpyothesis: Some men are playing a sport.

という2つの文章があった場合に、どちらも複数の男性がサッカーをしているという意味なので、これはentailmentです。

一方で、

Text: A black race car starts up in front of a crowd of people.

Hypothesis: A man is driving down a lonely road.

の場合、車を運転している状況は同じかもしれませんがcrowdとlonelyが矛盾しているのでcontradictionです。

他にどちらとも言えないというneutralというラベルの合計3つのラベルがあります。

Text:A smiling costumed woman is holding an umbrella.

Hypothesis:A happy woman in a fairy costume holds an umbrella.

いわばKaggleで開催されたQuoraコンペティションの多値バージョンという感じですね。Quoraでは質問文が意味的に同じかどうかを二値分類するタスクでしたが、RTEはその意味的な種類を判定する少し難しい問題になっています。

ただし、RTEも場合によってはcontradictionとneutralをまとめてしまい、entrailemnt/Nonentrailmentの二値分類として解く場合もあるようです。また、京大黒橋研が公開しているTextual Entailment 評価データでは◎、〇、△、×の4クラスに分類しており、こちらも二値分類に変換したデータセットが同様に公開されています。

データセット

データセットはSNLIのページからダウンロードすることができます。

The Stanford Natural Language Processing Group

LSTM構築

今回はQuoraコンペティションabhishekが公開しているDNNの構造を参考にします。下記のコードのように、それぞれの文章の入力をEmbedding layerとLSTMに入れて、それを結合したのちに幾つか層を重ねる形式です。

model1 = Sequential()
model1.add(Embedding(num_words + 1,
                     embedding_dim,
                     weights=[embedding_matrix],
                     trainable=False))
model1.add(LSTM(embedding_dim, recurrent_dropout=0.5, dropout=0.5))

model2 = Sequential()
model2.add(Embedding(num_words + 1,
                     embedding_dim,
                     weights=[embedding_matrix],
                     trainable=False))
model2.add(LSTM(embedding_dim, recurrent_dropout=0.5, dropout=0.5))

model = Sequential()
model.add(Merge([model1, model2], mode="concat"))
model.add(BatchNormalization())

model.add(Dense(300))
model.add(PReLU())
model.add(Dropout(0.5))
model.add(BatchNormalization())

model.add(Dense(300))
model.add(PReLU())
model.add(Dropout(0.5))
model.add(BatchNormalization())

model.add(Dense(300))
model.add(PReLU())
model.add(Dropout(0.5))
model.add(BatchNormalization())

model.add(Dense(3, activation="sigmoid"))
model.compile(loss="categorical_crossentropy",
              optimizer="adam",
              metrics=["accuracy"]
              )

今回使用したソースコード全体はGithubにて公開しています。

https://github.com/yagays/rte_snli

結果

今回train約50万文を学習に使用し、test約1万文に対して評価した結果、Test accuracyが80.5%となりました。また、混合行列は下記のようになりました。

f:id:yag_ays:20170812232350p:plain

Classification Reportはこんな感じ。特段苦手なラベルがあるわけではないものの、neutralはちょっと低いですね。contradictionやentailmentと比較して、neutralはラベル付けの基準が人によって異なっていたり、判断が難しいことが影響しているのかもしれません。

# {"contradiction":0, "entailment":1, "neutral":2} 
              precision    recall  f1-score   support

          0       0.86      0.78      0.82      3237
          1       0.78      0.86      0.82      3368
          2       0.77      0.77      0.77      3219

avg / total       0.80      0.80      0.80      9824

現在SNLIプロジェクトページにて公開されているSOTAが88.8%なので、まだまだですね。まあSNLI論文のベンチマークの77.6%よりは上回ったので、今回は良しとしましょう。

参考

コマンドラインからEC2のインスタンスの状態を確認できるec2instを作りました

概要

最近は機械学習の計算やJupyter Notebookなどを、ローカルやVPSではなくAWSのEC2で動かしたりしているのですが、そうなると気になるのがインスタンスの状態だったりパブリックIPアドレスだったりするわけです。普通ならブラウザからAWSのコンソールを開けば万事解決なのですが、自分の場合はブラウザのタブを開きすぎててどこに行ったか分からなかったり、あとはAWSコンソールのトップページからEC2のインスタンス一覧を見るには2回ほど画面遷移が必要だったりと、何かと不便に感じることが多くなってきました。

そこで、ターミナルのコマンドラインからEC2のインスタンス一覧を取得して、いい感じにリストアップしてくれるスクリプトを作りました。といっても、そもそもAWSaws-cliというコマンドラインインターフェイスを作成していたり、boto3というPythonSDKを作っていたりするので、今回作成したのはいわばその機能の一部を使って結果を見やすくするという、ラッパー程度のものです。n番煎じ、もしくは車輪の再発明になってしまったとは思うのですが、まあ勉強になったので良いでしょう……。

インストール

PyPIから取得します。

$ pip install ec2inst

基本的に上記の方法でインストールして問題ありませんが、以下のようにgithubから直接pipでインストールすることもできます。

$ pip install git+https://github.com/yagays/ec2inst.git  

準備

ec2instはaws-cliもしくはboto3の設定ファイルを読み込んで、その中に登録されているaws_access_key_idaws_secret_access_keyを利用します。そのため、まず始めにAWSのウェブコンソールからアクセスキーIDと秘密アクセスキーを取得し、aws-cliで登録しておきましょう。ここでの設定は~/.aws/configおよび~/.aws/credentialsに登録されます。なお、もし複数のプロファイルを使い分ける場合にはaws configure --profile yag_aysのようにプロファイルに名前を付けておくと後々切り分けが楽になります。

$ aws configure
AWS Access Key ID [None]: XXX...
AWS Secret Access Key [None]: YYY...
Default region name [None]: ap-northeast-1
Default output format [None]: json

使い方

ec2instコマンドで以下のような結果が返ってきます。

$ ec2inst
instance_id  instance_type  image_id      instance_name    public_ip_address  private_ip_address  instance_state  
-----------  -------------  ------------  ---------------  -----------------  ------------------  --------------  
i-00000000   t2.medium      ami-00000000  instance_name_1  xxx.xxx.xxx.xxx    172.20.xxx.xxx      running         
i-11111111   g2.2xlarge     ami-11111111  gpu_instance     -                  172.20.yyy.yyy      stopped         
i-22222222   c3.xlarge      ami-22222222  -                zzz.zzz.zzz.zzz    172.20.zzz.zzz      running         

ここでは色が付いていないですが、実際にはインスタンスの状態に色が付きます。

f:id:yag_ays:20161010005309p:plain

あとは-p/--profileコマンドでaws-cli/boto3のプロファイルを指定したり、-c/--columnsで表示させるカラムを設定することができます。詳しくはec2inst -hを参考下さい。

$ ec2inst -p yag_ays -c instance_type,instance_name,instance_state 
instance_type  instance_name    instance_state  
-------------  ---------------  --------------  
t2.medium      instance_name_1  running         
g2.2xlarge     gpu_instance     stopped         
c3.xlarge      -                running         

まとめ

EC2の利用は計画的に😇

参考