簡易2d grid環境(minigrid)

だいぶ前だが強化学習の勉強を始めた頃、簡易環境としてよく迷路を使っていた
方策勾配法とニューラルネットワークで迷路を学習 - MEMOcho-

当時私は気づいてなかったが、そのくらいの時期から同じ用途で使える便利なminigridという環境があったようなのでメモ

自前で定義したlayout(単純な迷路のみ)やpredefinedな環境上で手動で動かしてみるための簡単なスクリプト例
https://github.com/y-kamiya/machine-learning-samples/blob/2f2ae8eb7b864cea38d3d2acb2aaf5c52752c32b/python3/reinforcement/minigrid-samples/run_manual.py

できること

  • 2d grid上をplayerが動き回る強化学習用の研究環境
    • gymnasiumの形で使える
  • 壁、ドア、鍵、ボールなどを自由に配置して環境のlayoutが可能
  • predefinedの環境も多く用意されている
  • 簡易なguiが存在して可視化が簡単
  • 前方の範囲のみ観測として取得する形も可能

action space

とても単純でこれだけ
https://github.com/Farama-Foundation/Minigrid/blob/fc41b5e/minigrid/core/actions.py

  • 移動系
    • turn left/right, forward
    • 実際の移動はforwardのみで後は向きを変えるのみ
  • object interaction系
    • pickup, drop, toggle
    • toggleの典型例はドアの開閉、dropはobjectを置く
  • 特殊系
    • done
    • agent側で完了を宣言する場合に利用(特定の環境でのみ)

reward

得られる条件としては

  • goalに到達すると報酬を得られるというのが基本的な設定
  • 環境によっては特定の状態に到達すると報酬が得られるものもある
    • ex. 黄色のボールの横に黄色の鍵を置く

rewardの大きさは得られるまでにかけたstep数によって線形に減少していく
https://github.com/Farama-Foundation/Minigrid/blob/5165f40/minigrid/minigrid_env.py#L245

 return 1 - 0.9 * (self.step_count / self.max_steps)

実装としては、こちらにある環境毎の定義に必要なら成功条件を入れている形
https://github.com/Farama-Foundation/Minigrid/tree/5165f40122c8ffc24cfd04e1333995c37a56a97e/minigrid/envs

例えば、能動的にdoneをactionとして使い成功条件を判定している環境例
https://github.com/Farama-Foundation/Minigrid/blob/5165f40122c8ffc24cfd04e1333995c37a56a97e/minigrid/envs/gotoobject.py#L155-L158

if action == self.actions.done:
    if (ax == tx and abs(ay - ty) == 1) or (ay == ty and abs(ax - tx) == 1):
        reward = self._reward()

mission

自然言語で記述されたタスク目的を表す文字列としてmissionというものが存在している https://github.com/Farama-Foundation/Minigrid/blob/5165f40122c8ffc24cfd04e1333995c37a56a97e/minigrid/envs/gotoobject.py#L20-L26

  • 環境上で動作するagent向けの情報としてobservationの一部として含まれている
    • 純粋にRLを行う場合はobsからimageのみを使ったりするためmissionは無視されることも多い
  • 人間向けの情報としてGUI上で表示される

特にbabyaiだと、言語情報を正しく読み取る部分も主要な要素として研究されているため重要な要素だが、単純にminigrid環境を利用するという意味では必要ない。

新しく自前の環境を作りたい場合、missionの定義と具体的な成功条件判定をあわせるのは環境実装側の責任となる。

predefinedの環境

defaultで用意されているenvironments
https://minigrid.farama.org/environments/minigrid/

何もないempty環境から、鍵を拾ってドアを開ける、複数の部屋に分かれた広めのマップなど様々。それぞれの環境毎に成功条件が定義されている。

babyaiで使われた環境も現在はこの一部となっていて、minigridより複雑な環境も同様の形式で使える
https://github.com/Farama-Foundation/Minigrid?tab=readme-ov-file#babyai

自前での環境layoutの具体的な方法
https://minigrid.farama.org/content/create_env_tutorial/

vim-lspでruffとpyrightを併用

今までは設定が楽なのでvim-lsp-settings用いてdefaultの形(pylsp-all)を使っていたが、ruffを使うようになってしばらく経つのでちゃんと設定変更することにしたのでメモ

ruffはlint/formatのみに特化する方針と書いてある

The server supports surfacing Ruff diagnostics, providing Code Actions to fix them, and formatting the code using Ruff's built-in formatter. Currently, the server is intended to be used alongside another Python Language Server in order to support features like navigation and autocompletion.

https://docs.astral.sh/ruff/editors/#language-server-protocol

というわけでcompletionには別のものを入れる必要があり、以下の理由によりpyrightにした

  • 最近はpyrightがよく使われている模様(特にvscodeで)
  • ちょっと動かしてみたら定義へのジャンプ動作が体感としてpylspより速い
  • type checkに対応している(というかそれがメイン)なのでmypyも置き換えられる

最近はneovimを使うのがほとんどなのでvimへの対応はなくしてしまってもよいものの、vim-lsp-settingsが設定要らずで便利なのでそこは変えない。

ちなみにruff-lspの機能はruff 0.5.3以降はruff自体に組み込まれたとのことなのでruff-lspは使わない
https://github.com/astral-sh/ruff-lsp

2つのlspを同時に起動する

ruffとpyright-language-serverをそれぞれLspInstallServerで入れてnvimを起動し直しても両方は起動してくれないようだったため、filetypeがpythonの際に使うlspを設定で明示したところ問題なく起動した

let g:lsp_settings_filetype_python = ['ruff', 'pyright-langserver']

lspの設定

pyrightではlint/format以外のことをやってほしいので重複する機能はoffにしておきたい

その話をしてるissue
https://github.com/astral-sh/ruff-lsp/issues/384#issuecomment-1941556771
type checkはoffになってほしくないのでignoreは入れない

pyrightのdiagnosticsのデフォルト設定はこちらにある通り
https://microsoft.github.io/pyright/#/configuration?id=diagnostic-settings-defaults
今のところ厳密にruffのものだけ出てほしいわけではないため、今後邪魔なものが見つかったら個別に設定を入れて対応

その他の設定はここにあるのでdefaultが明示されてなくて必要そうなものだけ入れておく
https://microsoft.github.io/pyright/#/configuration?id=type-check-diagnostics-settings

let g:lsp_settings = {
\  'pyright-langserver': {
\    'workspace_config': {
\      'python': {
\        'disableOrganizeImports': v:true,
\        'analysis': {
\          'autoSearchPaths': v:true,
\        }
\      }
\    }
\  },

ruffのhoverをoff

ruffはhoverの機能には対応しているものの、vim-lspで用いられる形とは異なるために邪魔になるとのことでdocsにある設定を入れておく
https://docs.astral.sh/ruff/editors/setup/#vim
https://github.com/astral-sh/ruff/issues/11719

capabilityをoffにする設定を入れておかないと、hoverさせようとした際にruffでは対応してないというエラーで動かない

function! s:on_lsp_buffer_enabled() abort
  let l:capabilities = lsp#get_server_capabilities('ruff')
  if !empty(l:capabilities)
    let l:capabilities.hoverProvider = v:false
  endif
endfunction
augroup lsp_install
     au!
     autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

後半のautocmd部分はvim-lspのhelpにあるものをそのまま持ってきただけ

まとめ

結果的に設定全体はこうなる

[[plugins]]
repo = 'mattn/vim-lsp-settings'
hook_add = '''
  let g:lsp_settings_filetype_python = ['ruff', 'pyright-langserver']
  let g:lsp_settings = {
  \  'pyright-langserver': {
  \    'workspace_config': {
  \      'python': {
  \        'disableOrganizeImports': v:true,
  \        'analysis': {
  \          'autoSearchPaths': v:true,
  \        }
  \      }
  \    }
  \  }
  \}
  function! s:on_lsp_buffer_enabled() abort
    let l:capabilities = lsp#get_server_capabilities('ruff')
    if !empty(l:capabilities)
      let l:capabilities.hoverProvider = v:false
    endif
  endfunction
   augroup lsp_install
        au!
        autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
   augroup END
'''

追記

python以外のファイルを開いた際にruffが存在しなくてエラーになる https://github.com/prabirshrestha/vim-lsp/blob/04428c920002ac7cfacbecacb070a8af57b455d0/autoload/lsp.vim#L111

このちょっと上に有効なkeyかを判定する関数があったのでこれを使っておく https://github.com/prabirshrestha/vim-lsp/blob/04428c920002ac7cfacbecacb070a8af57b455d0/autoload/lsp.vim#L98-L100

  function! s:on_lsp_buffer_enabled() abort
    if lsp#is_valid_server_name('ruff')
      let l:capabilities = lsp#get_server_capabilities('ruff')
      if !empty(l:capabilities)
        let l:capabilities.hoverProvider = v:false
      endif
    endif
  endfunction

Era3Dとepipolar lineの勉強

Era3D: High-Resolution Multiview Diffusion using Efficient Row-wise Attention

multiview diffusionは1 viewあたりの解像度が256x256のものが多く、それだと顔などの細かい部分が描けないという問題がある。era3dは1 viewあたり512で生成できるようで気になったので調べてみる。

era3dの目的

  • camera pose推定用moduleを含めたmultiview diffusion modelの作成
    • focalやelevの学習時と推論時の条件の食い違いによる歪みを回避する
  • elevation=0の生成に特化することで、epipolar attentionを工夫して計算量の低減

camera条件の推定と固定

問題意識としては、既存のmultiview diffusionでは学習時/推論時のref imageのcamera条件が一致していないと歪んだ画像が生成されること。

学習時に固定(や狭い範囲)のfocalやcamera/objectのdistance、elevationなどでrenderingした画像を使っているためで、当然その範囲を外れた画像をref imageに使う場合は推論がうまくいかなくなる。

より広い条件の学習データを使って学習すればOKという簡単な問題でもなく、範囲を広くすれば学習自体が難しくなってしまう。既存の方法では暗黙的な形で以下のような学習をmodelが行っていることになる

  • ref imageからそのcamera条件推定
  • 同じcamera条件を持つmultivew画像を生成

そもそも一枚の画像からそのcamera条件を推定するのは難しい問題であり、2つ目の角度が異なる画像を生成するだけでも難しいのに余計に負荷のかかる状況となっている。

なのでこれを解決するために、ref imageのelevationとfocalを予測するmoduleを追加して補助し、生成される画像のcamera条件は固定にすることで学習難易度を下げて生成自体に集中できるようにする。

常にelevation=0としてmultiviewを生成するよう学習する

the input images are allowed to have arbitrary focal lengths and elevations while generated images are with orthogonal cameras and fixed viewpoints of 0 elevations.

azimuth(水平方向のcamera角度)も任意の値で条件づけできるわけではなく、ref imageから[0, 45, 90, -45, -90, 180]分回転した角度の6 viewを生成

when processing an input image captured at elevation α and azimuth β, Era3D produces a set of multiview images at an azimuth of {β, β + 45, β + 90, β − 45, β − 90, β + 180} and an elevation of 0.

epipolar attentionの計算量低減

elevation=0の画像のみを生成する形に固定したことで、epipolar attentionの範囲を限定して計算量を下げる。

そもそもepipolar lineとは
エピポーラ幾何 - Wikipedia

wikipediaより引用

言葉で書くとわかりづらいが、3d空間上でカメラAとオブジェクトXを通る直接を、カメラBが見る視界(2d)上にレンダリングした際のAX、のこと。さらに詳しい解説はこちらのqiitaなど
【コンピュータビジョン】ネコと学ぶエピポーラ幾何 #Python - Qiita

ということで、2つの視点から同じ物体を撮った画像があった場合、片方の画像上のある点Xは他方の画像上のepipolar line上に存在する。

なぜ計算量が下がるのか

epipolar attentionはこちらで提案されたもののよう
[2303.17598] Consistent View Synthesis with Pose-Guided Diffusion Models

詳細は読んでないものの、attentionする範囲をepipolar lineに限定して計算効率や対応点の予測精度を上げてmultiviewの一貫性を向上させるものだと考えられる。

era3dでは以下の理由により2 view間のepipolar lineは同じrowにのみ存在するので、さらにattentionの計算量が減らせる

  • elevation=0でしか生成しない
  • orthogonal cameraでのrendering

perspectiveとorthogonal cameraについて 床井研究室 - 第5回 座標変換

正しく理解できたかはわからないが、orthogonalの場合以下のようになると考えられる

  • カメラがレンダリング画像の各pixel上においてあるような形になる
    • 視線が奥行き方向に向けてすべて軸と平行
  • なので、xy平面がスクリーンだとすれば、カメラからレンダリングされた点の3d空間上での位置Xまでのラインは常にxz平面に平行
  • つまり、他のviewから見た場合のそのepipolar lineはyの値が等しくなる

era3dの論文より引用

  • naiveなattentionの場合(一番左)
    • 片方のviewの各pixelが、他方のviewの全pixelとattention(=S4)
  • epipolar attention(真ん中)
    • 片方のviewの各pixelが、他方のviewのepipolar line(K点sample)とattention(=S2*K)
  • eraa3d(一番右)
    • epipolar atttentionと同様だが、epipolar lineが必ず同じrowになる(=S3)

Kは斜めになるので少なくともSより大きな値が入ることは確実だが、対角線となる場合が最大という認識でいいのだろうか(それだと1.4倍程度しか変わらないのでもっと別の意味の値かも?)

resource削減具合

xformerを使う場合の比較で解像度256 -> 512における違いは

  • 通常のattention
    • memory usage: 0.99 -> 1.42 (GB)
    • running time: 1.77 -> 22.96 (ms)
  • row-wise
    • memory usage: 0.99 -> 1.08 (GB)
    • running time: 0.28 -> 1.86 (ms)

と解像度が上がってもメモリ消費がほぼ変わらず、512における処理時間がかなり短縮されているのがわかりやすい。

気になる点としては、入力としてorthogonal cameraによるレンダリング画像を使う必要がある点。遠近感がそれほどない画像であれば通常のカメラで撮影した画像でもある程度問題ないのだろうか。

生成してみる

以前も使ったunitychanの正面画像を使って生成してみた斜め画像がこちら
TADAをimage-3dへ拡張 - MEMOcho-

違和感なく斜めの画像が生成できているように見える。normalも生成できるよう学習されているのは3d生成に使う上ではよい。

生成されるviewはinputとして与えた画像(から推定したcamera pose)を正面として前後左右4 view + 前からの斜め2 viewと固定なので、single imageを与える問題設定でreconstructすると複雑なobjectでは厳しいかもしれない。というのとrendering側もorthogonal cameraを使う必要があるのを忘れそう。

ただ、input imageにおけるcameraのelevationやdistanceの条件を合わせなくても、model側で予測して決まったviewを出してくれるというのは、様々な条件の入力に対してうまくreconstructするには重要だと思われる。