Riiid コンペ復習 - maskingなど

これは Riiid! Answer Correctness Prediction | Kaggle において自分が理解できていなかった部分をまとめるもの。間違いを含む可能性がある。間違いを見つけたら @higeponja にお知らせください。

Riiid はいわゆる time series コンペ。きちんと masking をしないとleakage が発生する。このあたりの自分の理解をまとめる。

未来の情報を使ってはいけない

train/infer 時のマスク

未来の情報を train/infer 時に使ってはいけない。これを厳密にやらないと leakage になる

  1. Transformer の attention で未来を見てはいけない。例)問題番号 102 の正解・不正解が分かれば、以前の問題101の予測は簡単になるかもしれない。いわゆる triu マスク。
  2. task_container_id を共有する問題は、すべての問題を解き終わらないと答えを知ることができない。これは Riiid 固有の話。そのため未来を参照しないマスクとあわせてもう1つマスクが必要。下記 task mask 参照。src_mask, tgt_mask, memory_mask すべてに同じマスクが必要。

Riiid-2

train & validation split

厳密には train/valid を split するときに task_container_id を分断してしまうと leakage が発生する。これの影響は軽微であるというコメントもあった。なぜならそれは分断面のみで起こるからデータが大きければ影響が少ないからだと思われる。

pad を参照しない

loss 計算時に pad 部分を mask する。

auc 計算

今回の Transformer 系の解法ではデータを右側に寄せる解法が多かった。これは長さの異なる input でも必ず seq[-1] が求める prediction になるから。このことから auc 計算時にはseq[-1] のみを計算対象とするべきだったようだ。

間違い、新発見があれば追記するかも。

Google Football コンペ

銅メダルでした。

  • 以前のRLコンペから学んでやらなかったこと
    • DQN などの自前実装:自分の学習のためなら良いが、自前実装はバグりやすくたくさんの罠がある。すでに動いているベースラインが運営から提供されているのでそれを使う。そうすれば reward / observation の調整などの本質に集中できる。本コンペの場合は seed_rl ベースの notebook が提供されていた。
  • 初挑戦したこと
    • seed_rl を利用することで GCP の AI Platform に詳しくなった。間違った configuration で高額課金になってしまうことを恐れていたが、きちんと document を読んでいけば怖くないよ。
  • やってよかったこと
    • 論文と関連研究の読み物。これにより成功しそうな施策のリストができた(例:difficulty を徐々に上げる adaptive learning)
    • GCP フリークレジットへの申込み。コンペの途中でわかったことだが football は GPU ではなく CPU heavy な training プロセスだった。GPU P100 1つにつき n1-standard_96 * 4 の構成で学習させる。これはクラウド以外ではほぼ不可能だった。
  • 失敗したこと
    • 失敗ではないが GCP クレジット(or お金)がもっとあれば training で試行錯誤できた。
    • seed_rl を TPU で動かすことが競争優位性となると思いがんばったが、実質不可能だった。早めに運営に質問すれば良かった。
  • 大まかな試行錯誤の流れ
    • 初期段階では自分のベースラインを作ろうと思い pfrl で簡単にデモを書いた。discussion で質問するにつれて seed_rl に勝てる部分がないことに気づき捨てた。
    • observation については以前の RL コンペから直接画像を入力するのは問題外だとわかっていたので SMM を time series で 4 つ stack したものをそのまま利用。結局 observation を変更することはなく最終日までこのままだった。ちなみに stacking することを submission コードで考慮していなかったので序盤に score が伸びず悩んだ。
    • reward はこれも score,checkpoint の両方がすでに用意されていてそれらがベースになった。kyohei さんが中盤から checkpoint reward を減衰させるコードを書いてくれた。agent が点数を入れられるようなってからは checkpoint は逆に足かせになるから。
    • 最初から hard 相手に training するよりも徐々に対戦相手を強くしていくほうが学習効率と最終的にもっと強くなるらしいので試してみた。アイデアは簡単だが実装は gym env の理解が必須だったので真面目に取り組んだ。がんばれば difficulty を動的に変更できたので良かった。
  • GCP + seed_rl でのトレーニン
    • seed_rl は強化学習フレームワークで learner (GPU) と複数の actor からなる。actor は episode を play するわけだが自分の action を決める場合の inference を自分自身では行わず GRPC で learnerで行うことで学習を scalable にするところが肝であった。
    • learner の GPU を使い切るには actors の数が物を言った。vcpu を 96 個つんだ instance を 4つかうという富豪構成。フリークレジット $1000 をもらえたとはいえかなりの制約だった。
  • team のメリット
    • kyohei さんに声をかけていただいてチームを組ませていただいた。ほぼ毎日 slack で議論して試行錯誤した。進捗の共有には dynalist を使った。チームを組むと自分に足りないものがよく見えてとても勉強になった。自分はもっと深くものを考えないといけない。つい浅はかな考えを元に手を動かしてます。
    • もう一つのメリットはやはり心が折れないこと。仲間がいるとがんばれる。

Visual Studio Code + Docker for Kaggle

Kaggle 用の docker image が公開されているのでそれを Mac 上の Visual Studio Code 使う。

  • やったこと
    • Remote-Containers という拡張を入れて拡張経由で docker を使う。これは host と container 内の両方で Visual Studio Code を起動するので開発環境=実行環境になりうれしい。
    • devcontainer.json に Container で起動する Visual Studio Code にインストールしたい拡張の記述
  • 諦めたこと
    • Container 内で GitHub に push などをやろうとした。.git_config の共有、credential を環境変数で渡すなど。どれもうまく行かなかったのでやめた。よく考えれば host 側のファイルシステムを mount させてhost 側で commit すれば良い
    • 非 root ユーザーでの実行。tutorial に従ったが home directory が permission error で作れなかった。深堀りはしなかった。
  • 想定する開発フロー

Karabiner-Elements

Karabiner-Elements を使っていたのだが Visual Studio Code で有効になり困っていたので入れ直す。

で教えていただいたことを元にきれいにインストールした時のメモ。

現状の問題点

macOS 上で Emacs キーバインドが使えるのは良いが、Visual Studio Code で有効になると困る。なぜならば VS Code では別途 Emacs キーバインドが用意されておりそちらを優先したい。

クリーンインストール

  • ~/.config/karabiner を別の場所に退避
  • Karabiner-Elements 設定 - Misc - Uninstall Karabiner-Eelements
  • mac 再起動
  • macOS の素の状態を確認
    • Ctrl-W は効かない。
    • Ctrl-F や Ctrl-B などは効く。これは macOS の機能?
  • Visual Studio Code で Ctrl-X Ctrl-F や Ctrl-X B などが効くことを確認。
  • Karabiner-Elements を公式サイトからインストール

設定

  • 起動
  • Version 13.1.0
  • Complex Modifications - Add Rule - Import more rules from the Internet
  • Emacs key bindings (rev 12) の上4つを有効にする。

f:id:higepon:20201108155433p:plain

追記

number1cruncher.com にある mark region とか ctrl-y を取り込む。 https://github.com/higepon/dotfiles/commit/c912fdd6624354d373426c8dc3d3920658fc4f2e#diff-0dc7a7cc6b2e4051d7e9ecd928e8af90f31115dc774b182451abf6b496ba3762

Kaggle Halite コンペ + 強化学習

Halite by Two Sigma | Kaggle に参加していた。今回はお誘いいただいて @threecourse さん と @Seed57_cash さんとチームで挑んた。

コンペの目的

  • 4人プレイの岩塩(halite)集めゲーム
  • 1プレイヤーが複数の shipyards and ships を操作することが可能
  • 盤面サイズ 21 x 21 のボードゲーム
  • 詳細はここ

前半: チーム結成前

強化学習で解くことを決意。以前学んだ強化学習は記憶から飛んでいるので1から勉強し直すことに。決めた方針は

  • 強化学習は難しいので、とにかくシンプルスタート
  • 時間がたっぷりあるので DQN を自前で実装して理解を深める
  • halite コンペではなく、もっとシンプルな connectx コンペで練習する
  • Colab Pro

結果としてこの方針は良い面も悪い面もあった

良かった点

  • DQN の実装にとても苦労したので強化学習デバッグに強くなった。動いていないのはなぜか?に対して仮説が立てられるように
  • connectx は十分にシンプルな題材で、他にも強化学習で解いている人がいたので参考情報が多かった
  • Colab Pro はすばらしい :)

悪かった点

  • DQN は不安定だったのでかなりの時間を溶かしてしまった。理想的には理論を理解し、サンプルを実行しあとはライブラリを使うのが正解な気がする
  • 時間はたっぷりなかった。強化学習デバッグ・トレーニングに時間がかかる

後半: チーム結成後

@threecourse さんに pfrl を教えてもらい自分の実装を移植した。ライブラリを使うと責任の分担がはっきりするので observation, reward そして model に集中できる。さらに DQN が動くようになると数行の修正でより安定した PPO が動くようになりコストパフォマンスが大変良かった。チームメイトと slack で議論・情報共有しながらできたのはとても良かった。他にも @threecourse さん と @Seed57_cash さんがモデルについて話しているときに、「ああ。このくらいやらないとだめなんだ」というベースラインをしれたのは大変ありがたかった。

実装の詳細

  • ship の convert はハードコーディング
  • 1 ship が 1 agent
  • self play (自己強化学習
  • 上記2つから 4人対戦の場合 4 * ship の数の agent がいることになるので training 負荷が高かった
  • reward
    • 衝突回避が難しかった。結局大きな goal (= 最終的にゲームに勝つ)では衝突回避は学習できなかった。衝突を事後検知して失われたスコアに応じて負のrewardを与えた
    • ship は4方向の移動と動かないという選択肢があるのだが、compound 命令として deposit を定義した。これのおかげで deposit という行動を取ると reward を貰えることを学んでくれた
  • observation
    • 6チャンネルの 2D array (自分の位置、味方の船、shipyard の位置、時間経過、halite の位置、spawn予定地)

どこまで行けたか

  • 1人プレイ & 1 ship で halite をあつめて deposit => OK
  • 2人プレイ& 1 ship for each で haliteをあつめて deposit => OK
  • 2人プレイ & 2 ship for each => カオス

結局複数プレイヤー複数 ship は正しく動くところまで持っていけなかった。pfrl を正しくない方法で使っているのでそれが原因かもしれない。

所感

  • 強化学習は安定させるのが難しい。ただしデバッグ方法を理解すれば、かなりましになる。
  • チームでやるほうが学びがある。本当にありがたい。
  • 実世界で役に立つ強化学習もやってみたい。

DQN デバッグメモ

デバッグの過程をまとめる。ほぼ自分用。

自分用のまとめ

  • PyTorch でも tensoarboard を使ってたくさんログをとる。ログをとればおかしいところはみつかる。
  • ログすべきもの
    • observation を tensorboard で visualize する。
    • 次の最善手が分かっている observation を手動で作る。policy network が出力する action values を tensorboard に出力する。その action value が最善手に converge するか見守る
    • loss
    • benchmark agent との対戦結果。win rate ではなくて win/draw/lost 数をプロットする。
    • 1 episode の長さをプロットする。self play では徐々に長くなることが想定される
    • self play でなければ average reward。
    • eps decay curve 。これをプロットして学習が進む前に random move をやめてしまうのを防ぐ。
  • 特に注意すべき点
    • agent に渡されている observation を人の目で観察する。与えられた情報で自分が勝てるようになるか考える。
    • observation を neural network 用に加工するときにミスをしていないか確認する。
    • reward が clipping されているか。
  • Target network の更新頻度を調節せよ
  • policy network の更新式を理解してデバッグする
  • 同様の試みの learning rate / モデルサイズ / learning が始まるまでの steps などを参考にする

以下は自分がデバッグしていたときの生ログで未整理です。

可能な限りログを取る

reward, action, observation, average_reward などを logger でログを取る。

Visualize

Observation を人の目で見られるように visualize した。もともとの DQN 実装が current_state - prev_state = observation としていたが、これではどうやっても Connect X では勝てない。差分だと置かれた1つのコマとそのポジションしかわからない。

epsilon decay

DQN では agent に exploration させるために一定の確率で action を random sample する。この確率は episode が進むにつれて小さくなるように設計されている(=学習が後半で進むと仮定しているから)。この確率をログしてみると。どうも学習が全然進んでない段階で小さくなりすぎているように見えたので調整した。

loss

reward ばかりに気を取られて loss をグラフに描いてみたら様子がおかしい。初期にガクッと下がって、その後ぐんぐん上がっていく。これはおかしいのだろうか。まずは loss の定義から見直そう。 To minimise this error, we will use the Huber loss. The Huber loss acts like the mean squared error when the error is small, but like the mean absolute error when the error is large - this makes it more robust to outliers when the estimates of Q are very noisy. とある。そもそも error は以下ののものを比べている。

  • state=s, action=a に対する Q value. Q(s, a)。
    • memory から取り出した s と a に一致する Q value をNN から取り出したもの
  • 次の time step における最大の Q value * γ + r。
    • memory から取り出した next_state に対して最大のQ value とそれを与える action を特定

このエラーが徐々に大きくなるのはいくつかのシナリオが考えられるが、まずは詳細なログを取ってみよう。 f:id:higepon:20200720172017p:plain

100 episodes ごとに Q value の絶対値が大きくなっていることが観察された。それにともない loss が相対的に大きくなっているように見える。Q value が大きくなっているのはなぜか。ある state に対する報酬全体が上がるとは、初期状態に Q function が見積もっていたよりも実際の報酬が高いということ?reward をログしてみた。reward は 1, -1, 0 のどれかである。reward を non_final_mask で絞るとすべて 0 だ。あれこれでは学習進まない? と思ったけど正しそう。他のDQN実装とも見比べた。あと loss が increase するのはありっぽい。

python - Cartpole-v0 loss increasing using DQN - Stack Overflow

状況を整理

  • 観察されていること
    • loss が下がってそのうち上がる
    • 同時に random agent との対決での勝率が下がる
  • 仮説またはアイデア
    • そもそもどれくらいで Converge するのかを調べる
    • 他の実装を動かしてみて観察する
    • loss が下がりきったときの勝率を見てみる。Eearly stopping するの?
      • episode 200 あたりで下がりきって勝率もそこから下がる
        • 普通の NN なら下がり続けるので上がる理由が理解できない。
    • learning rate が大きすぎるのでは
      • learning rate 1e-3 にして実験中
        • qvalues が stable で値も爆発していない。良いかも!
        • 50000 episodes は少なかったかも。
        • Pytorch DQN - ConnectX | Kaggle だと 5e-4
        • 200000 episodes, average reward も print しよう eps が急激に小さくなっていた。

うまく動き出したような気がする

eps をグラフに描いたら明らかにおかしかった。学習の初期に一気に最小値まで下がってしまっていた。EPS_DECAY = 200000 ほどにするとゆっくり下がってよい。この変更により明らかに avg reward が変わった。 f:id:higepon:20200723082543p:plain

うまく行っていると思う理由 - avg reward が徐々に上がって正の数になっていること。つまり勝ちが多い。 - 同様の指標だが実際の戦闘で勝率 0.5 を超えていること - とある state に対する qvalue が converge している など。loss は必ずしも下がるのが正解とは言えないということらしい。 うまく動いたものを保存するために別ディレクトリにグラフと script を隔離。

問題点

  • reward stats の計算に間違いがある
  • 最大限 debug しているので遅い。1 episode の時間を計測しよう。
  • loss の振れ幅が大きいので learning rate を小さくしてみよう
  • Colab でうごかしてみよう

Colab で動かしてみた lr=1e-2

f:id:higepon:20200723152751p:plain

qvalues と loss は全然収束してないのに Avg reward と Win rate は上がっている。

Colab で動かしてみた lr=1e-3

f:id:higepon:20200723153203p:plain

qvalues と loss は収束しているが Avg reward は下がっている。

速度改善・クラッシュ修正

pyplot がメモリを使いすぎてクラッシュ。描画点が増えるにつれて pyplot が急激に遅くなり学習時間よりも長くかかってしまう。Tensorboard in Pytorch の乗り換えてすべて解決。ちなみにグラフだけではなく observation を画像として Tensorboard に表示することも可能。こうやって苦労すると Tensorboard のすばらしさが分かる。

lr=1e-2

f:id:higepon:20200725080505p:plain

  • 9万エピソード
  • loss, qvalue ともに収束してないように見える
  • 勝率は 0.5 をほぼつねに超えている
  • 勝率は高めだが分散が大きい

以上の観察から lr が大きすぎてうまい local optimal を見つけられないのではと推測。

lr=1e-3

f:id:higepon:20200725080952p:plain

  • 7万エピソード
  • loss, qvalue ともにどこかに収束しつつあるように見える。自信なし。
  • avg reward > 0 である

1e-3 よりはまともにみえる。しかしやはり他の記事を見ると loss の大きさが気になる。

DQN debug ブログを読み解く

Solving Open AI gym Cartpole using DDQN - The intersection of energy and machine learning

  • batch size
  • model parameter 可視化
  • target copy 頻度
    • 10 episode ごとではなくて 10000 steps ごとにした
  • target copy うごいてる???
  • next q も表示してみる

target net の update の更新頻度

上記ブログを読んでみると target net の更新頻度の目安が書いてあり 10K - 100K step ごとらしい。頻繁すぎたかも。

lr=1e-3 10000 steps で更新

loss の scale が自分が思っていた範囲に収まっている。qvalue もしかり。 f:id:higepon:20200725202037p:plain

ところで以下も visualize してみたが policy net と target net の weights がおかしくない?5000 step 離れるとこんなに変わるものか?いや変わるか。5000 steps ごとのはずなのに全然表示されない部分がある。値がゼロに集中するわけではないし。これはなんだろうか。その後 debug したが target update は正常に行われていることを確認した。全然表示されないのは頻度が少なすぎるのかもしれない。 f:id:higepon:20200725202222p:plain

lr=1e-2 10000 steps で更新

f:id:higepon:20200725203003p:plain f:id:higepon:20200725203028p:plain

submission

lr=1e-2 10000 steps で流して reward を観察する。まずうまく動いているかを確認する。 うまく動いているならば early stopping が stability を試みる。local で random agent に対して 0.81 ののものを submit した。Score は 580.7。弱いが。最初の1歩。

negamax との対戦

random 相手に学習しても強くならないのは明らかなので、もう少し強い negamax と戦わせてみる。途中で時間切れになったが少しずつ reward が上がっているのが見える。ただし negamax はアルゴリズムとして遅いのであまり学習に向かないのでそこまで。 f:id:higepon:20200728081012p:plain

self play

self play を試みているが、まだコードが正しいか自信が持てない。気づいた点を片っ端から直していく。

episode time が定期的におそくなっている 

f:id:higepon:20200810130557p:plain

これはかなり怪しい。学習速度の足を引っ張っている可能性も。play 自体にかかっている時間かどうかを切り分けよう。結論 negamax との対戦が遅い。これはどうしようか。とりあえず頻度を下げた。

かかる時間

10000 episodes にかかる時間は ~4000 secs。

観察1

  • negamax との win rate がとてもゆるやかに上がっている
  • qvalue が収束していない
  • eps decay が速く下がりすぎている

f:id:higepon:20200811083751p:plain

次のステップ

  • この学習をそのまま走らせておく
  • eps decay を緩やかにしたものも走らせる
    • 結果:同様に win rate が下降気味。ただし qvalue は収束しつつある。
  • eps decay を緩やかかつ lr=1e-3 にしてみる。
    • 結果:同様に win rate が下降気味。ただし qvalue は収束しつつある。
    • 仮説:常に自分自身と戦うと成長しないのではないか?過去の自分のスナップショットと戦わせよう。
  • 過去の自分と対決するコードを書き始める

過去の自分と対決

self play を実装した。 f:id:higepon:20200813091617p:plain

時間をかけても強くなっていない。一方で他のグラフは特に矛盾がない。このことから agent が本当に強くなる前に世代交代しているのではないかという仮説を立てた。そのため Alpha Zero の論文のようにパラメータを改める。 - 100 episode ごとに世代評価を 1000 に。 - 対戦数を 11 -> 100 に。11 だと偶然6勝してしまうことがありそう。

あと気になるのは avg_reward が 0 以下なのに世代交代が起きていること。これは episode の reward の平均をとっていたからだ。勝負がつかないのは -0.05 が積まれるから。avg_reward は勝負がついたときの reward の平均としよう。

self play negamax, random を evaluation として使う

f:id:higepon:20200814175109p:plain f:id:higepon:20200814175133p:plain

  • reward が上がっているのは良い。
  • level は右肩上がりで良い
  • random 相手でも勝率 0.5 はだめすぎる。
    • 仮説1: random 相手に勝てるようになるにも時間がかかる
      • 検証する方法。
    • ConnectX の DQN 実装他に探す。収束までの episodes 調べる。
  • 仮説2: 実装に間違いがある
  • 仮説3: 1000 steps あたりでは vs random で勝率 0.85 だった。そのあと忘れた?

試すこと

  • Observation mean 0 std 1 に scaling する
    • 20200815_experiment_standardize_pixels.ipynb で試している
      • 特に変化なし。
      • f:id:higepon:20200816084342p:plain
  • 大きい replay buffer
    • 100000
      • 20200815_exp_buffer_100000.ipynb
      • loss の order が小さいままなのはよいのか?右肩下がりに random との戦績が下がっている。
      • f:id:higepon:20200816084700p:plain
    • 1000000
      • 20200815_exp_buffer_1000000.ipynb
      • 上記とあまり変わらず
      • f:id:higepon:20200816084820p:plain
  • Look at sensitivity of EVERY hyper parameter
    • lr 1e-3 をためそう 100000 と比較すること
      • random 相手に 0.5に収束。
      • eps length 11 あたりに収束
      • f:id:higepon:20200817082855p:plain
      • f:id:higepon:20200817082913p:plain
    • 勝率0.55 ではなくて 0.60/0.70 でせだいこうたいはどうか?
      • f:id:higepon:20200817084640p:plain
      • f:id:higepon:20200817084701p:plain
  • Look at episode length (sometimes more informative than episode reward).
    • episode length がきれいに長くなっている。これは良い傾向か?勝負がながびく reward は -0.05 にしているけども。あとやっぱり長引くにつれて random に負けるのはなぜ。もっとよく考えろ。多分良い傾向。お互い強くなってミスをしなくなっている。でもなぜ random 相手に勝てないのか?
    • f:id:higepon:20200816084949p:plain
    • f:id:higepon:20200816085004p:plain
    • f:id:higepon:20200816085049p:plain
  • You might need a huge buffer, so adapt code accordingly
  • 仮説: 勝率が 0.5 に近づく = random に近づくということから model が学びをリセットし続けている。モデルサイズが小さいという仮説。

他の実装をみる - https://github.com/kirarpit/connect4 を見ると 6x7 の board size だと死ぬほど時間がかかると書いてある。4x5だとすぐに収束するようだ。 - 試してみたが全然だめ。 - f:id:higepon:20200819184431p:plain - board_size 変えられるかな? https://codebox.net/pages/connect4 ここはネットワークサイズが分かる。

debug

  • Q-Learning 復習。今回は完全に理解した!
  • だめな点
    • 先手後手どちらも学ばせないといけない。next_state があるから。
    • 先手・後手の色は変えてはいけない。=> これはまちがい。OKです。
    • 盤面にある数で自分の手番はわかる。
    • scaling で色は変えてはいけない。
  • loss が 1000 steps を超えたら急激に上がる
  • target update 頻度を上げたら loss のけたがあがりすぎた?
    • そう。target に近づいて loss 下げる前にまた自分で更新されたら辛い。
  • よくよくself play reward を log してみたら 0 と 1 しかなかった。self play だと自分の手番で負けることがないから!! reward を 0 -> -1 にしたら全て動くようになった。

Reinforcement Learning の self play についてのまとめ

強化学習の self play について知りたいことがあるので、ざっくりと有名な論文を読んでいく。熟読はしない。

知りたいこと

  • self play は同一インスタンス、別インスタンスどちらか?
  • 直感的にはどこかで stuck しそうな感じがするけど?
  • 学習が進んでいることをどのように評価するか?

論文