OpenAI gymを自前で作成する場合、環境の状態を描画するviewerをどうするかかなり迷います。PFRLやstable-baselines3で強化学習を進めるならば
env = gym.make('環境名') # または class my_env(gym.Env) のインスタンス作成
for _ in range(100):
env.step(action)
env.render()
というコードでtrainer側がループのペースをコントロールする必要があります。こういったフレームワークが整備されていなかった6年前のChainerでやってみるDeep Q Learning - 立ち上げ編 - Qiitaでは、環境の方にtrainerを組み込み、GUIアプリの処理の合間に強化学習をしていました。 現状のプラクティスとしては下記を見かけます。
- サーバー/クライアント方式
- ROSを介する環境などで良く見かける。Viewerをサーバにして、クライアントであるgym環境から描画に必要な情報を書き込む。Unityの環境もこの形式なのかな?未チェックだけど。
- matplotlib方式
- matplotlibのアニメーション プロットを使う。renderからmode='rgb_array'で実装されるような内容を受け取るか、envの内部情報で、しこしこお絵描きする。
- マルチプロセス方式
- GUI側のクライアントを別プロセスで立ち上げ、env側にハンドルをもたせ描画のタイミングを操作する。envを終了するときにGUIのプロセスをきっちり終了させる必要がある。
- pygame方式
- 詳細はよく知らない。OpenAIのbox2d環境で使われているっぽい。
いずれも一長一短なのですがpygame方式よりも低レベルなモジュールで実装したいと思ったので、tkinterで実装する場合を開拓しました。デメリットはstep()
の処理が重いと色々おしゃかになること。
class RenderWindow(tk.Tk):
def __init__(self, env) -> None:
super().__init__()
self.render_offset = 5
self.geometry("%dx%d"%(
env.width + self.render_offset * 2,
env.height + self.render_offset * 2
)
)
self.canvas = tk.Canvas(self,
width = env.width + self.render_offset * 2,
height = env.height + self.render_offset * 2,
bg="#fff"
)
self.canvas.place(x = 1, y = 1)
def render(self, objects):
h = self.winfo_height()
w = self.winfo_width()
self.canvas.delete('all')
self.canvas.create_rectangle(0, 0, w, h, fill = 'white')
# オブジェクト郡の描画処理
for obj in objects:
if obj.property == RECT:
# 矩形の描画
elif ...
class FlatWorld(gym.Env):
def __init__(self) -> None:
super().__init__()
self.width = 640
self.height = 480
# 中略
self.window = None
self.reset()
def render(self, mode = 'human', close = False):
if self.window is None:
self.window = RenderWindow(self)
self.window.render( self.objects )
# self.window.mainloop()と違って実行をブロックしない
self.window.update_idletasks()
self.window.update()
おまけ
wxPythonでも行けそうだぜ!pyQtはダメ。おとなしくマルチプロセス/マルチスレッドにするのが良い。
No comments:
Post a Comment