tkinterでOpenAIのgym描画を実装

OpenAI gymを自前で作成する場合、環境の状態を描画するviewerをどうするかかなり迷います。PFRLstable-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