メロスは激怒した
「Google Colaboratory」で OpenAI Gym のゲーム環境の一つである「CarRacing-v0」をやろうとした人の10人に11人は挫折したことと思います。なにせ「Colab CarRacing-v0」で検索しても情報がほとんど出てきません。
なぜこんなにも情報がないのか。よく検証してみると3段階のハードルがありました。
問題1.ColabのプリインストールのBox2Dがぶっ壊れている(壁度★★☆☆☆)
Colab には Gym も box2d も入っているのですが、壊れています。
import gym
env = gym.make('CarRacing-v0')
AttributeError: module 'gym.envs.box2d' has no attribute 'CarRacing'
というエラーが出ます。ググるといろんな解決策が出ますが、眉唾ものもありどれが良いのかはっきりしません。
問題2.env.render()で画面が立ち上がれないのでエラーが出る(壁度★★★☆☆)
Colab では新規に画面がポップアップするような表示はできず、HTML経由でインライン表示(?)するしかありません。CarRacing-v0 はこれに対応していませんので、env.render() でエラーが出ます。
env.render()
NameError: name 'base' is not defined
環境によっては次の場合もあるでしょう
NotImplementedError: abstract
gym 関係ではよくこの base と abstract に阻まれます。しかし、通常はこのエラーにたどり着くことはありません。その前に3のエラーが出るからです。
問題3.env.reset()でエラーが出る(壁度★★★★★)
これが致命的です。最初の一歩である環境のリセットでエラーが出ます。
state = env.reset()
NameError: name 'base' is not defined
エラーをよく見ると、env.reset() 内の env.step() 内の env.render() でエラーが出ているので、問題2が原因であることが分かります。env.step() がダメということは何もできないということです。
さて、表示系だけがダメなのであれば「Colabで学習だけして、表示はローカルで行う」という方針で良いのですが、問題3のエラーがあるためにColabでは何一つ動作ができません。
ということで、今回は「car_racing.py」を書き換えて Colab で動作するように改造しました。また、おまけとして遺伝的アルゴリズム(GA)でプレイを最適化した結果もお見せします。
書き換えた後のライセンス関係がよく分からないのですが、権利は元の作者に帰属し、ここの筆者はなんら権利を主張しません。また、改造によって生じたいかなる損害も補償しません。大人の方、よろしくお願いします。
実行環境
WinPython3.6 のようなローカル環境では、適切に pip install したあと、~/gym/envs/box2d/car_racing.py を開いて「実行>ファイルごとの設定>外部システムターミナルで実行」するとゲームのプレイができます。また、本記事の改造によって内部コンソールでの実行もできるようになります。
Google Colaboratory ではゲームのプレイはできませんが、本記事の改造によって実行ができるようになります。
Colab での実行方法
まず、プリインストールの gym を葬ります。
#========================================================
# プリインストールのgymを葬る
#========================================================
!pip uninstall gym
Proceed (y/n)?
と聞かれるので「y」で進みます。
新たに gym と box2d をインストールします。
#========================================================
# 新たにgymとbox2dをインストール
#========================================================
!pip install gym
!pip install box2d-py
これで問題1は解決しました。
次に、こちらの改造済みファイル「car_racing.py」を Colab 画面左のカレントディレクトリにアップロードします。
アップしたファイルで、インストールしたBox2Dフォルダ内の同ファイルを上書きします。
#========================================================
# 改造済みのcar_racing.pyを./にアップして、gym内の該当ファイルにコピー(=上書き)
#========================================================
!cp ./car_racing.py ../usr/local/lib/python3.6/dist-packages/gym/envs/box2d/car_racing.py
動作確認コードです。問題1のエラーは出ないと思います。(Warningが出ても気にしないでください)
#========================================================
# importとCarRacingの選択
#========================================================
#以上の手順の後に初めてgymをimportすること!
import gym
import numpy as np
import numpy.random as nr
import matplotlib.pyplot as plt
#ゲームの選択
env = gym.make('CarRacing-v0')
#デフォルトのactionsの確認(steer, gas, brakeの3次元連続値)
print('env.action_space: {}'.format(env.action_space))
#アクションを離散値で用意しておく
actions = np.array([[ 0, 0, 0], #actions[0]:何もしない(=等速直線運動)
[ 0, 1, 0], #actions[1]:加速
[ 0, 0, 1], #actions[2]:減速
[ 1, 0, 0], #actions[3]:右旋回
[-1, 0, 0]]) #actions[4]:左旋回
print('actions:\n{}'.format(actions))
動作確認コードです。問題2と3のエラーは出ないと思います。
#========================================================
# 動作テスト
#========================================================
#ステージを固定して初期化
env.seed(1)
state = env.reset()
#初期画像を表示
plt.figure()
plt.imshow(env.render(mode='get_img'))
plt.show()
#ランダムにアクション(1アクションは4フレーム継続)
for i in range(10):
for _ in range(4):
#アクションの選択
rand = nr.randint(5)
#状態の更新
state, reward, done, goal = env.step(actions[rand])
print('reward:{}'.format(reward))
#1アクション(=4フレーム)毎に画像を表示
plt.figure()
plt.imshow(env.render(mode='get_img'))
plt.show()
改造で追加された env.render(mode=’get_img’) によってこんな画像が表示されるはずです。
改造したところ
表示まわり
車体の主観視点(車体が常に上を向くようにコースが回転する)をやめ、コースが回らないように変更
→目に優しいうえ、コースを把握しやすい
→人間がプレイするのは難しくなった
道路(踏むと報酬がもらえるパネル?)の色を濃くした
→すでに踏んだかどうかが分かりやすくなった
赤白の縁石をなくした
→表示速度を少し向上
デフォルトを verbose=0 に変更
→学習時に「Track generation…」がたくさん表示されて邪魔なので、print類はすべてOFFに
env.step() 内の env.render() を廃止(問題2・問題3)
→普通にいらない。表示したければ都度 env.render() すればいい。(これにより env.step() を繰り返すような学習の速度が50倍速になった)
env.render(mode=’get_img’) を追加
→コース全体の画像情報がndarray形式で返ってくるので、matplotlib等でColab上に表示できる。学習中の確認に必要
報酬まわり
1フレーム毎に-0.1点されるのを廃止
→強化学習向けの設定と思うが、進行度が分かりづらくなるのでいらない。必要ならば手動で-0.1点/フレームすればよい。
画面外にアウトで-1000点に変更
→元は-100点だったが、累計報酬が負になったほうが処理上助かるので
env.step() の4つ目の返り値を「ゴールしたかどうか」に変更
→元は空だったが、とりあえずこうした。本当はもっといろいろな情報を辞書形式で返すべきだが、それは今後のやる気次第で・・・
外部システムターミナルで実行した場合
開始時1秒間のズーム演出をなくした
→いらない
ズーム率を下げ、より広くコースが見渡せるようにした
→コースを把握しやすい
CarRacing-v0 の仕様を確認
赤字は改造による仕様です。
self.action_space = spaces.Box( np.array([-1,0,0]), np.array([+1,+1,+1]), dtype=np.float32) # steer, gas, brake
から読み取れるように、env.step() に入れる入力値は3次元の連続値で、ステアリングが-1~1、アクセルが0~1、ブレーキが0~1の範囲です。明記されていませんが、[0, 0, 0] など意思のない入力をしたときはほぼ等速直線運動をします。
道路は3色のしましまになっており、1周に約250~300個のしましまパネルがあります。パネルを一つ踏むたびに約3~4点の報酬が返ってきて、全部でちょうど1000点になります。したがって累計報酬が1000点になったらクリアと判断できます(実際には丸め誤差で999.999くらいですが)。画面外に到達すると-1000点が返ってきます(オリジナルは-100点)。
状態の更新は次のようにします。
#状態の更新
state, reward, done, finish = env.step(3次元の連続値)
state:None
reward:このステップでの報酬。通常は0、道路を新規に踏むと3~4点、コースアウトすると-1000点
done:ゲームが終了したかどうか。True であれば「コースアウト」か「クリア」のどちらか
goal:クリアしたかどうか。True であれば「クリア」
ですから、3番目と4番目の返り値で「コースアウト」か「クリア」を判断できます。
画像のインライン表示は次のようにします。
#初期画像を表示
plt.figure()
plt.imshow(env.render(mode='get_img'))
plt.show()
ステージを毎回固定したい場合は以下のように乱数シードを固定します。
#ステージを固定して初期化
env.seed(1)
state = env.reset()
やりのこし
env.step() の4つ目の返り値には、
- 車の位置、速度、加速度
- コースの情報
- まだ踏んでいない道路の情報
- 前方の景色
など、機械学習に有益な情報を辞書形式で詰め込むべきなのですが、とりあえず保留とします。
おまけ
おまけと言いつつこれが目的だったのですが、特定のステージのプレイを遺伝的アルゴリズム(GA)で最適化してみました。
Colab での出力はこんな感じです。
ローカル環境で外部システムコンソールから実行した場合です。
最適化アルゴリズムはこちらの記事のものを踏襲しています。