のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

ChatGPTはKaggleでどう使えるか?

皆さんこんにちは
お元気でしょうか?忘年会シーズンで疲れ果てていたら、1日遅刻しました。

qiita.com

この1年、生成AIのブームが来ており、Kagglerの皆様もChatGPTを使っている方が多いのではないかと推測しています。
ということで、本日は私の使い方を簡単に紹介しようと思います。

コンペの使い方

ChatGPTをうまく利用すると生産性の向上が見込めます。
反面、ハルシネーションも生み出すので、使い方を誤ると致命的です。
そのため、出力を信じず、基本はチェックができるものにしておくのが良いでしょう。

具体的な利用内容

コンペ理解

説明文の理解

専門や馴染みのないものに関しては、正直、説明文を理解するのも大変です。
その時にはChatGPTに説明してもらう事が可能です。

www.kaggle.com

データの理解

データの内容も専門分野であれば正直わからないことが多いです。
そのため、データの内容も説明してもらいましょう。わからないことも多いのでその時は返信で更に質問して理解を深めましょう。


実装

実装してもらう

自分で実装と慣れておらず、難しいものがあります。
その場合、ChatGPTに入力して、実装してもらうのが望ましいでしょう。
テストさえできれば、入出力によって正しいか確認できるので、そのようなテストを書きつつ、作ることでバグも防げます。


実装の変換

実装に慣れていないコードを書くのはつらいです。
例えば、普段PyTorchの実装を書いている人にKerasの実装を書くのはとても辛いです。
その時にはChatGPTに変換させましょう。
Kaggleでは、この稀有なユースケースはtensorflow liteに変換するときに使えます。

また、Cythonによる実装も可能なので、Pythonでパフォーマンスを出しきれないときには非常に便利です。

デバッグ

時々、バグの要因がよくわからないことがあるので、
関連していると思われる実装と例外のメッセージを入力すると時々正しい回答が返って来ます。

その他

翻訳

ソリューション書くときは英語にする必要がありますが、
残念ながら、英語ネイティブではないので、英語で書くのが非常に難しく、伝わる英語になっているか怪しいです。
そのため、ChatGPTに翻訳と修正をしてもらいます。

最後に

ChatGPTが増えることによって難しい実装をChatGPTに任せるといったことができるようになりました。
来年以降はこの流れが加速するので、更に色んなことができるかもしれませんね。

AIで作った画像をAIは見分けられるのか!?

皆さんこんにちは
tereka114です。最近はNLP関係の技術調査も兼ねて、FB3をやってます。
最近の社会の流れを見ていて、少し時間の片隅でやってみたい事ができ、やったので、実験とその結果を書いておきます。

導入

今年はStable Diffusionが発表され、また、mimic、NovelAIなど画像の生成に関して
社会に劇的なインパクトを与えるモデル/サービスが発表されてきました。
今までの生成モデルもそれなりにおおっと言われてましたが、ここまでインパクトが世間的に出てくることはほとんどなかったのではないかと思っています。
私もお仕事であまり関係をしない都合上、GAN以降のモデルはノーマークだったので、非常に驚きました。(記憶だとBigGANが最後・・・)

私がGANを使っていた頃は32px, 64pxを生成するのが限度で、かつ、きれいではありませんでした。
それが、わずか数年(5年ほどだと思いますが・・・)こんなに綺麗な画像が生成できるとは思っていませんでした。
もちろん、機械学習エンジニアとして、AIすげぇ、大歓迎といった本人主観の一面だけで見た至極単純な感想もありますが、
デザイナー/イラストレーターさんからすると、やはり驚異の一つであり、既存の絵と近しいものが出てきた といったことになれば、
AIによる絵のパクリなど、新しい問題が生まれるのは当然のことです。

そのためなのか、サービスの規約でAIの画像をアップロードを阻止しているといった情報もちらほらTwitterで見られています。

それらのAIが生成した画像の権利・倫理関係の良し悪しや実態の正誤はともかくとして、
人類が作った画像AIが作ったデータを見分けるのは将来的に価値があるものだと思っています。
段々と難しくなっていくと思いますが、今の段階だとどうなのかを見てみることは意味がありそうです。

準備

画像側のデータセットはほぼダウンロードするだけです。

Stable Diffusionのセットアップ

私はこのリンクのdocker/docker-composeを利用して、環境を構築しました。
注意しなければならないのはgit-lfsを入れておかないと、hugging faceからのモデルダウンロード時に実体が落ちてこないため、ハマります。
zenn.dev

次のコマンドを実行すれば、git-lfsをダウンロードできます。

sudo apt install git-lfs

実験

実験目的

本記事ではAIが作ったのか否かを見分けることを目的です。
利用用途を鑑みて次の2つを調査する必要があります。

  • 同じドメインで見分けられるのか。例えば、ランドマークを学習させて、新しいランドマークが見つかったときに、そのランドマークを正しく判定できるのか。
  • 違うドメインでも見分けられるのか。例えば、ランドマークを学習させて、車を判定する場合、その車は正しく判定できるのか。

データ作成

Stable Diffusionと人工のデータを準備し、単純に人類/AIで2値分類します。
一つのカテゴリだと難しいものもあるかもしれないので、いくつかのプロンプト(呪文)で試しました。

Category Real Dataset SD Prompt
Landmark GLDv1 landmark
Car myautage car outside

リアル画像と生成された画像です。どうでしょう?わかりますでしょうか?

Landmark Original

Stable Diffusion Landmark

Car Original

Stable Diffusion Car

モデル作成

モデルはEfficientNetV2m(Pretrained ImageNet)で前処理を次の2つのパタンを試しました。
Resize/RandomCrop(学習)+Resize/CenterCrop(推論)を用いました。Resizeだと画像に加工が入るので、特徴的なノイズが含まれる場合、そのノイズが変な方向に修正、もしくは、消える可能性があります。
そのため、Crop系のであれば、そのノイズを加工せず取り込めることを期待します。

評価方法

AUC、F1、Recall、Precisionを計算します。

実験結果

条件 前処理 AUC F1 Recall Precision
Landmark Resize 0.998 0.982 0.996 0.969
Car Resize 0.975 0.582 0.412 0.994
Landmark Crop 0.999 0.999 1.0 0.999
Car Crop 0.999 0.998 0.999 0.997

実装の詳細は次のKaggle Notebookを見てください。
Crop
https://www.kaggle.com/code/tereka/detectstablediffusionimages?scriptVersionId=109399786

Resize
https://www.kaggle.com/code/tereka/detectstablediffusionimages?scriptVersionId=109300787

Cropのほうが精度が高かったです。
リサイズは元画像の解像度によっても縮小のされ方が異なるので、判定が難しかったのではないかと推測できます。

Future Work

やり残したこととして、次があります。

  • 様々なモデルを使っての検出、Stable Diffusionのみならず、他のモデルも使って試みてどのモデルかを当てる。
  • より異なるドメインの違いによる差分はあるが、例えば、アニメ画像(Waifu diffusionなど)であれば、どのようになるのか。
  • 解像度の比較、Stable Diffusionの解像度が固定だったが、様々な解像度の画像が含まれる場合にどうなるのか。

最後に

今回のデータセットではAIを見分けることができたので、生成された画像に何らかの特徴があったのではと思っています。
AIでAIで作った画像がわかるか?といったことだけでAIによる諸問題が全て解決するとはもちろん言えませんが、問題の解決に一役買ってくれるとありがたいと考えています。

Kaggle Grandmasterになるまでの7年間の軌跡

皆さんこんにちは
お元気でしょうか。冬だというのに、GPUと暖房で半袖装備でも過ごせています。
今年、長きにわたるMaster生活の終演を迎え、ようやくGrandmasterになることができました。
そこで、Grandmasterになるまでの経験をこちらに書き記しておこうと思います。

この記事はKaggle AdventCalendar2021カレンダー2、25日目になります。

qiita.com

著者の背景

M2(情報系)のときにKaggleをはじめました。(学部は文系です)

先輩が研究の一つの検証(過程)でKaggleにサブミットしているのを見て知りました。
そうしたふとある日、現実逃避でもはじめたのか、Kaggleで競争してみるのが面白そうと思って参加を試みました。
AutoEncoderなど流行っていた頃ではあったので、研究でC++やtheanoを書いていました。研究分野としては画像です。

そこから今の会社に就職しても、続けて、早7年です。
昔から画像系のコンペを中心に参加していましたが、テーブルやNLPも面白そうであれば参加しています。
戦績は金6枚、銀21枚、銅4枚といったほとんど銀で埋め尽くされているため、ほぼシルバーコレクター化しております。金も多く獲得したいです。

Masterになるまで4年、Grandmasterになるまで7年もかかっており、他の現Grandmasterの方と比べても非常に長期間かかったように思えます。

f:id:tereka:20211216001735p:plain
Kaggle Profile

Kaggleへの取り組み

1-3年目

このあたりは正直コンペ、よくわからないと思いながら参加していました。

当時は、所謂コンペティション、データ分析の知識の高速道路が少なかったです。
そのため、ソリューションを読んでもどうしてこれをやったのか、お決まりのパターンはよくわからないものも多く、色々と手探りで進めていた覚えがあります。

今の画像やNLPなどの非構造データよりも圧倒的に構造化されているテーブルデータが多かったです。
毎回EDAをしたりしますが、今のように体系だった、GroupbyのAggregationやTargetEncoderを使うような特徴量の作り方がよくわからず、頭を抱えながらトライしていました。

DLのFWも当時、今ほどPyTorchかKerasぐらいに絞れているわけではなく、theano, lasagne, torch(lua)を中心として、色々なライブラリに触れてました。
なんとなくですが、Kerasは人気ありました。

この時代は画像のモデルはPretrainedが禁止されている傾向にありましたので、ランダム初期値から学習させてました。
しばらくして、このコンペははじめて、Pretrainedの利用がOKになったように記憶しています。(ResNet出た頃ですね)

State Farm Distracted Driver Detection | Kaggle

そのため、今のようにPretrainedを意識することなく、コンペの公開Notebookなどに従って、各自使いやすいライブラリを使っていたのではと思っています。(私はそうでした)

※今はKaggle本をはじめとして、初心者からでも学べるものが増えてきているのは良いところですね。

4年目

IEEE's Signal Processing Society
f:id:tereka:20211218102833p:plain
IEEE's Signal Processing Society

写真を撮影したカメラを推定する問題です。
問題設定としては面白く、私の目で確認しても何もわかりませんでしたが、機械的には9割を超える精度が獲得できます。
ただし、このコンペ、ルール側に穴があり、最後、阿鼻叫喚で大変だった覚えがあります。

ロシアチームの多くが上位にいたのですが、External Dataをダウンロードして利用するといった条件を運営がルールに書き忘れていました。

その結果、コンペの特性上、YandexやFlickrなどのデータからダウンロードしていました。
EXIFをチェックすれば答えがわかるので、画像をダウンロードするといったことで精度向上が可能といった状態でした。
これに気づいていなかったチームは苦戦を強いられました。
(ロシアのチーム、ods.ai以外気づいていなかったのでは‥)

このコンペで最終的にはギリギリで金に収まり、晴れてKaggle Masterになりました。

www.slideshare.net

動画Ver(Kaggle Meetup #4)
www.youtube.com

Avito Demand Prediction Challenge
f:id:tereka:20211218102949p:plain
Avito Demand Prediction Challenge

個人的に面白いコンペでした。
最近ではあまりないのですが、テーブル+画像+NLPの三種類のデータを扱わなければならないもので、ニューラルネットワークの作成しがいがありました。
非構造単品だと普通なので、いかに多くのドメインを扱うかといったところはやりがいあると感じます。

私が途中CVPRに行きながら参加していたため、日本、アメリカ、ヨーロッパで24h体制でコンペしていたことを覚えています。
かなり頑張ったのですが、最後Shakedownして金から弾かれてしまったのはとても悔しかったです。力量不足でした。

www.kaggle.com

Home Credit Default Risk
f:id:tereka:20211218103116p:plain
Home Credit Default Risk Leaderboard

12人でチームを組んだ感慨深いコンペです。
少なくともKaggleでは、もうこの人数でチームを組むことはないでしょう。
(これがきっかけでチームマージがこのあと確か5人までになったような・・)

チームを組んだそれぞれのメンバが違うアプローチに取り組み、色々と議論ができたのは勉強になりました。
チームマージ当時、既にいろいろなものがやり尽くされているのでは‥とまで思っていたので、趣向を凝らして、様々なソリューションに取り組みました。

最後ギリギリで2位の初賞金になったのはなんともいえませんが貴重な経験でした。
ソリューションの図は今、振り返っても参加した中で、最も複雑な図面だと思います。

speakerdeck.com

5年目あたり

なかなか成果が出ない年で、また金に届かなかったと思うことが多くありました。
Google QA、Humpbackは金を狙っていましたが、最後に追い抜かれたり、シェイクダウンしたりで一歩届きませんでした。
ただ、負ける中でも各コンペで学べたことは多く、今振り返ると無駄なことはなかったんだなと思います。当時は頭抱えてました。

どのコンペもソリューションを読んでほんのわずかな差で足りなかったもので、あと一息だとも思いましたが、同時にそこで勝てないのが実力なのかなと思っていました。

6年目

Global Wheat Detection
f:id:tereka:20211218103229p:plain
Global Wheat Detection Leaderboard

コンペで初めて真面目に行う物体検出でした。
通称、小麦コンペの難しいところは、世界各国の小麦の検出が必要になり、地方によって小麦の穂が変わってきます。
今回、このドメインの差分をどのように埋めるかがコンペティションの中で難しかったポイントだと思います。

問題設定としては面白かったのですが、データやモデルなど色々問題があり、最後までヒヤヒヤさせられていました。
物体検出で今もなお利用されているYoloV5のモデルがありますが、このモデルがGPL-3であることにより使っていいのか悪いのか議論が多くありました。
DeepFakeコンペみたいに終わってからライセンス失格になるような状態にはなりたくないので、モデルやデータの利用は慎重にならざるお得ませんでした。

判断の賛否はあると思いますが、運営がはっきりとYes/No出してくれたのが良いポイントかなと思います。

このコンペは非常に精神ダメージが高く、Private公開されてから
データのアノテーション修正前と後間違ってたすまんやり直す、みたいなことが発生しており、大変心臓に悪かったことを覚えています。
ある日起きたら深夜に更新されており、気がついたら金にいました。(最初の公開時は吹っ飛んでました)

ここでソロ金を取れたのはこれから残りのメダルを集める上で、何も気にする必要がなくなった点で非常に楽でした。

www.kaggle.com

7年目

Shopee - Price Match Guarantee
f:id:tereka:20211218103500p:plain
Shopee - Price Match Guarantee

軽い気持ちでECサイトのデータに興味あり、解析を適当やっていたら上位に来てしまいました。
NLP+Imageの両方のデータを解析しないと上位に到達できないこともあり、非常に面白いコンペだったように記憶しています。

このあたりからTransformer関連(Swin Transformer)のモデルが猛威を奮っており、時代の変化を感じだした頃合いです。

www.kaggle.com

Hungry Geese
f:id:tereka:20211218103601p:plain
Hungry Geese Leaderboard

初めて挑戦した強化学習コンペティションです。
日本人が多かった&HandyRLベースの実装が公開されており、強化学習にきちんと挑戦するにはもってこいといった状況でした。
強化学習の勉強をしていくにつれ、モデルや探索系のアルゴリズムも必要になり厳しかったことを覚えています。
そのような中でチームを組ませていただき、議論や改善できたのは非常に貴重な体験でした。

ちなみに評価期間2Wの手が出せない期間はもどかしく、日夜カツ丼を食べる、前日は物理神社に通うなどしており、神頼みスタイルで乗り切りました。
普段は金から追放される私が11位ギリギリに残りKaggle Grandmasterになりました。

www.kaggle.com

Landmark Competitions
f:id:tereka:20211221224652p:plain
Landmark Retrieval2021 Leaderboard

ほぼ毎年参加しているLandmark Competitonです。(2019は計算リソースは必要すぎるので諦めた)
2021年はRecognition/RetrievalともにNotebookで、Retrievalは昨年の謎のTensorFlow縛りもなくなり、純粋に面白いコンペティションになりました。
過去のコンペだと2018年にGLDv1、2019年からより巨大なGLDv2、2020年からNotebookと進化してきました。

コンペ序盤にチームに誘われたのでマージしました。
少しやってみて想像通り、計算リソースが足りないことははじめからわかっていたので、マージして効率よくモデルを作ろうとしていました。
お互いがTPUを利用していてGCPで30万の請求されたのも苦しいですが、良い思い出でした。

※30万溶かした記事
nonbiri-tereka.hatenablog.com

終結果としては、Retrieval:5位(金)、Recognition:12位(銀)になり、どちらも金を狙っていた私達としては悔しい気持ちになりました。
しかし、RetrievalにてICCVに発表する機会をもらい、非常に嬉しかったです。
Kaggleなど成果を国際学会できちんと対外的に報告するのは一つの目標だったので、それが実現できたのは良かったです。

www.kaggle.com

動画はこちら
www.youtube.com

Kaggleに取り組んで思ったこと

この経験から、時々議論されるKaggleやっててどうだったのかといった話を書いていこうと思います。

良かったこと

データを見ればいろんな意味でやばさがわかる。

仕事でも当然ですが、データを見て、分析することが必要になってきます。
私ももちろんですが、大半の方は一度ぐらいは交差検証ミスをやらかしたことがあると思います。
実際の仕事のシステムを考慮すると検証時のリークは非常に危険なもので、PoC後に、システム化して、期待と違うと思われるのは問題です。

Kaggleはその感覚をノーリスクで体験できます。(リスクに後でショックを受けるとかは省いています)

手数の多さ

Kaggleでコンペをいくつか経験すると、成否問わず、自分で考え、挑戦してきた結果や知見があると思います。
これに基づき、実データに対してその経験が適用できるかできなさそうかの判断ができるようになる。
また、どうすれば解けそうかがなんとなく見えるようになってきます。
そして大体の場合、実装のストックを持っていることが多いので、その実装を利用すれば、すぐに試せます。

こういった手数の多さ(=アイデアの多さ)は非常に役立つので、Kaggleやっててよかったと思うことはあります。
(いざ直面して、実際今までやったことがあるものも多いです)

精度の見積もりと実現できる手法

コンペの経験が多くなってくると、そもそもこの検証、精度がすぐに頭打ちになりそうか、
直感的に難しそうかの区別がつくようになってくると思っています。
また、問題を見てすぐにこの手法だとほぼ成功しそうといったものが経験ベースでわかるのはかなり安心できます。

そういったことを経験ベースで得られるのは非常に貴重な場だと思います。

悪かったこと/身につかないこと

問題定義をする能力

Kaggleのコンペは開催地点で基本的に問題と評価が定義されています。
ただし、実案件は機械学習を使ってやりたいことを決めることが多いと思いますので、システム的にどのような物が良いのか、などそういったものの役には直接は立ちません。

しかし、その問題が解けそうかの判断はなんとなくわかるようになるので、その設定で問題ないかがわかります。

精神力の消耗

特に上位にいて追い抜かれるときのストレスは危険です。
計算中は気晴らししないと正常な精神を保てないことも多々ありました。

Kaggleで勝つ&コンペ続けるのには精神力が必要です。(他のことに手がつかない・・・

今後の挑戦

Kaggle Championship

今年の一番大きな目標の一つはこのコンペティションです。
一度、New Delhiで参加していますが、普段と違う能力が問われており、刺激的でした。
4時間という私が参加した中で史上最短のコンペでしたが、参加することでの学びも多くあり、本戦出場に向けて一つ一つ積み上げたいと思っています。

皆さんとスペインでお会いしたいので、がんばります。

学会系コンペへの参加

Kaggleに限らず、学会の中で主催されているコンペにも参加してきたいと思っています。
例えば、RecSysやKDDでコンペが開催されていますが、そういったところも挑戦したいと考えています。
Kaggleとは違った知見を獲得できそうなので、積極的に新しい知識を得ていきます。

情報発信

Kaggle Grandmasterになるまでその達成に完全にかかりきりになっており、あまり他のことに取り組む時間が取れておりませんでした。
コンペ参加して、Youtubeやブログなど何らかの形で書き残すことも大事だと思うので、来年は増やしていければと思っています。

最後に

Grandmasterになるまで、非常に長い道のりでした。
その長い期間で多くのコンペに参加してきましたが、学生から社会人と無駄なことはなかったなと振り返って思います。

コンペは引き続き参加していきますので皆さんよろしくお願いします。

Google Cloud Storage(GCS)でうっかり30万以上溶かした話

皆さんこんにちは。
コンペで頑張ったので疲れました。

さて、Google Landmark 2021が終了し、Retrieval5位(金)、Recognition12位(銀)となりました。
本日は自戒と反省により、クラウドで30万円消失した話を
記録として書こうと思います。皆さん私を見て反面教師にしてください。

事象

9月入ってからLandmark2021に参加し、Google Cloud Platform、通称GCPを利用していた。
主な利用はGoogle Cloud Storageのみで、ほぼ容量課金だろうと高をくくっており、課金請求の上限など入れ忘れてました。

すると9/18に久々に請求額を確認すると32万ほどの請求額がありました。
さすがに目玉が飛び出て、調査にあたったといったものになります。

課金内容を確認したら原因はすぐにわかり、チームで対策を打ちました。(私が慌てて学習にストップかけた)

f:id:tereka:20210919012424p:plain
請求額

理由

課金の表面的な事象はGCSの大陸間通信です。
GCSはどこから利用するかにより、通信量に応じて課金がされるといった体系になっており、
この部分を完全にみおとしました。

今回、2人でLandmarkを取り組んでおり、TPUを中心に実施していました。
TPUで行う必要があったのはLandmarkが巨大なデータであったため、そして、GCSを利用したのはTFRecordをローカルに落とす必要がなかったからです。
相方のGCPインスタンスがヨーロッパ、私が格納していたデータのGCSのリージョンが北米にあったことにより、大陸間通信が膨大となり、課金されてしまったということです。

正直、チームマージして取り組む場合に大陸間通信のコストなどが影響あることを完全に見落とし、
そのあたりの共通認識を取らなかったのは大きな反省点です。
逆に言えば、チーム組む皆さんはきちんと認識を取りましょう。AWSのS3でも似たような課金体系だったはずです。

対策

1. 課金額のアラートをしかける。
シンプルですが、どんなにしょぼいものでも必ず請求アラートをしかけましょう。
何が起こるかわからないので、仕掛けておいて損はありません。
僕は油断して目玉が飛び出ました。

2. チーム内のリージョンを確認する。
データセットの共有などはストレージを利用することは多いと思います。
特にTPU関連を利用する際、GCP/GCSを利用することになるので、通信コストがかからないよう、
チームメンバーでのリージョン統一などは事前に行っておきましょう。

最後に

そもそもTPUを真面目に運用しない限り起こり得ない事象ではありますが、
私を反面教師にして、うっかりを発生せず、正しいクラウドライフをお送りください。

分散深層強化学習ライブラリHandyRLをコンペで使ってみた。

皆さんこんにちは
お元気ですか。ブログ書きながら、当チームのガチョウを見守っています。

最近までHungryGeeseに参加しており、このコンペでHandyRLライブラリには大変お世話になりました。
このコンペでHandyRLを改造して使ったので、そのポイントを記録として残しておきます。

HandyRLとは

一言で言えば、PyTorchで利用できる軽量な深層分散強化学習用のフレームワークです。
実際に使ってみた感想としても、これまでの強化学習のFWより直感的に理解しやすいものでした。

github.com

分散深層強化学習ではこの2つの処理を同時に行っています。

1. エージェントを自己対戦させてエピソードを生成する処理
2. 生成したエピソードを使ってモデルを学習させる処理

これを自前で実装するのは非常に骨が折れるものですが、このあたりをきれいに実装されており、使いやすいものになってました。

HandyRLの使い方

基本編

基本はチュートリアルを読んだほうが早いです。
基本的な流れとしてこのあたりを抑えておきましょう。たった3Stepでできます。

  1. ゲームごとにEnviroment Classを作成する。(チュートリアルだと、TicTacToe)
  2. 設定(config.yaml)を変更する。
  3. 学習する。

github.com

※気が向いたら別途記事書くかも・・・

Tips

コンペで勝つには、上記の基本だけだと対応しづらい部分があります。
そのため、他コンペでも利用できる改造ポイントをTipsを書いておきます。

自己対戦以外でエージェント作成

HandyRLのデフォルトエージェントは、自己対戦になります。
しかし、多様性の観点からある程度、様々なエージェントと対戦させたほうが良いです。
自己対戦以外のエージェントを利用する場合、train.pyとworker.pyを変更する必要があります。
今回は変更の例とその解説を簡単に行います。

train.pyのL587付近

                            for p in self.env.players():
                                if random.random() > 0.5:
                                    args['model_id'][p] = -1
                                else:
                                    args['model_id'][p] = self.model_era
                                    is_self_model = True
                                    players.append(p) # 自己対戦リスト

                            if is_self_model is False:
                                p = random.choice(self.env.players())
                                args['model_id'][p] = self.model_era
                                players.append(p)

入力の値が-1であれば、事前に学習済のエージェントを使ってエピソードを生成する、
また、self.model_era(=学習回数,Epoch)であれば、自己対戦用エージェントを利用する。といった方式を想定して入力しています。
入力されたデータによってどのモデルかは、worker.pyで選択するようにします。

worker.py

class Worker:
    def __init__(self, args, conn, wid):
        # 省略、以下を追加、モデルをPoolしておく処理、get_modelsでエージェントとなるモデルの一覧を取得する。
        self.trained_models = [ModelWrapper(model, ts=1) for model in get_models()] + [ModelWrapper(model, ts=2) for
                                                                 model in get_2ts_models()] 

    def _gather_models(self, model_ids, role="g"):
        model_pool = {}
        for model_id in model_ids:
            if model_id not in model_pool:
                if model_id < 0:
                    if role == "g":
                        model_pool[model_id] = self.trained_models # Poolしたモデルを利用する。
                elif model_id == self.latest_model[0]:
                    model_pool[model_id] = self.latest_model[1]
                else:
                    model_pool[model_id] = ModelWrapper(pickle.loads(send_recv(self.conn, ('model', model_id))), ts=-1)
                    if model_id > self.latest_model[0]:
                        self.latest_model = model_id, model_pool[model_id]
        return model_pool

    def run(self):
        while True:
            args = send_recv(self.conn, ('args', None))
            role = args['role']

            models = {}
            if 'model_id' in args:
                model_ids = list(args['model_id'].values())
                model_pool = self._gather_models(model_ids, role=role)

                # make dict of models
                for p, model_id in args['model_id'].items():
                    if model_id < 0:
                        models[p] = deepcopy(random.choice(model_pool[model_id])) # Poolの中からランダムに選択する。
                    else:
                        models[p] = model_pool[model_id]

学習済モデルを利用する実装です。いわばPoolのようなもので、学習済モデルをこの中から選択する方式です。
上記コードでは次のことを行っています。

・学習済モデルをコンストラクタで初期化
・_gather_modelsで、エピソード生成時にmodel_idが-1の場合にモデルPoolを選択
・runでPoolの中から一つモデルを選択し、対戦させる。

様々なエージェントで評価

初期値だとランダム動作での評価になる(HungryGeeseだけ?)ので、正直ある程度学習させると弱すぎてほぼ勝利し意味のある評価ができなくなります。
そのため、多様性がありつつも、ある程度強いエージェントで評価が必要です。

こちらもtrain.py/worker.pyを修正する必要があります。

train.pyのL596付近

                        elif args['role'] == 'e':
                            # evaluation configuration
                            args['player'] = [self.env.players()[self.num_results % len(self.env.players())]]
                            for p in self.env.players():
                                if p in args['player']:
                                    args['model_id'][p] = self.model_era
                                else:
                                    args['model_id'][p] = -1
                            self.num_results += 1

train.pyでは、一つ必ず、学習しているモデルを選択し、残りを評価として選択するためのモデルとして設定します。

worker.py

class Worker:
    def __init__(self, args, conn, wid):
        self.evaluate_models = [ModelWrapper(model, ts=1) for model in get_models()] + [ModelWrapper(model, ts=2) for
                                                                                        model in get_2ts_models()]

    def _gather_models(self, model_ids, role="g"):
        model_pool = {}
        for model_id in model_ids:
            if model_id not in model_pool:
                if model_id < 0:
                    if role == "g":
                        # 略
                    else:
                        model_pool[model_id] = self.evaluate_models

roleが"g"であれば、エピソード生成、"e"であれば、評価になります。
そのため、評価をする場合は定義しておいたものからランダムに選択する実装にしています。(選択部は学習と同じ選択仕様)

自己対戦モデルを一定期間保存する

一定期間のEpisodeを保存しておき、そのエージェントと対戦させたいといったケースが考えられます。
これにより学習を進めた結果、今までのモデルに負けるといったことを防ぐ取り組みです。
例えば、AlphaStarでは過去学習させたエージェントも保存しておき、対戦するようなこともされました。

この方式もworker.pyを変更することで実現しました。

class Worker:
    def __init__(self, args, conn, wid):
        # 省略、以下を追加、自己対戦モデル用のPool
        self.self_fight_models = []

    def _gather_models(self, model_ids, role="g"):
        model_pool = {}
        for model_id in model_ids:
            if model_id not in model_pool:
                if model_id < 0:
                    if role == "g":
                        if len(self.self_fight_models) == 0:
                            model_pool[model_id] = self.trained_models
                        else:
                            model_pool[model_id] = \
                                random.choices([self.self_fight_models, self.trained_models], k=1, weights=[1, 1])[0] # 自己対戦モデル or 学習済エージェント
                    else:
                        model_pool[model_id] = self.evaluate_models
                elif model_id == self.latest_model[0]:
                    # 略
                else:
                    model_pool[model_id] = ModelWrapper(pickle.loads(send_recv(self.conn, ('model', model_id))), ts=-1)
                    if model_id > self.latest_model[0]:
                        self.latest_model = model_id, model_pool[model_id]
                        if model_id % 300 == 0: # 300学習につき一つ対戦用のPoolに追加する。
                            self.self_fight_models.append(model_pool[model_id])
                            if len(self.self_fight_models) > 100: # 100件以上作られれば最初のを捨てる。
                                self.self_fight_models = self.self_fight_models[1:]
学習済モデルの実行

事前に学習したモデルを使いたいといった場面があります。
例えば、Kaggleでは、上位LBなど他エージェントのエピソードを獲得できます。
それを用いて勝利モデルの行動を事前に模倣学習することで、ある一定の強さのエージェントから学習を開始でき、学習の高速化を見込めます。
ソースをほぼ変更しなくとも具体的には次の方式で実現可能です。

1. modelsディレクトリを作成し、モデルを「1.pth」して配置する。
2. 設定の「config.yaml」の「restart_epoch」を1にする。
3. 学習を実行する。

HandyRLはepisodeの設定を1以上でmodels配下のモデルを利用する方式を採用しています。
また、デフォルトの読み込み(pytorchのload_state_dict)のStrict=Falseとなっているため、若干の構造変化には対応できます。

決定的動作で動かす

HandyRLのエピソード生成でのエージェントのアクションはモデルの出力の分布からサンプリングになります。
しかし、一番確度が高そうな行動を決定的に動かしたほうがエージェントとしては(おそらく)強くなります。

train.pyのL596付近

                            for p in self.env.players():
                                if random.random() > 0.5:
                                    args['model_id'][p] = -1
                                else:
                                    args['model_id'][p] = self.model_era
                                    is_self_model = True
                                    players.append(p)

                                if random.random() < 0.05:
                                    determistics.append(True)
                                else:
                                    determistics.append(False)

                            args['determistics'] = determistics

これによりdetermisticsに各プレイヤーが決定的に動くかどうかを入力しています。
(実装例は5%の確率で決定的に動作するエージェントになる)

また、generation.pyのL53の次の箇所を修正します。

                        if args['determistics'][player]:
                            action = legal_actions[np.argmax(p[legal_actions])]
                        else:
                            action = random.choices(legal_actions, weights=softmax(p[legal_actions]))[0]

これにより、determisticsフラグがTrueであれば、決定的に動作するようにしています。

感想

今まで強化学習を行うとなれば、複雑なライブラリや実装を読み解く必要があり、少し改造するにも大変だと感じていました。
しかし、HandyRLは非常にシンプルな構成で実装も読みやすく、扱いやすかったです。
シュミレーションコンペなどで強化学習を行う場合はこのライブラリを積極的に使うのが今後良さそうに思えます。

最後にこれを利用したのはHandyRLチームが積極的にHungryGeeseで学習の条件やNotebookを公開いただき、シュミレーションコンペと強化学習への障壁を下げてもらったからでした。
今回、HungryGeeseでフルに活用させていただき、最新の強化学習のキャッチアップ含め、勉強になりました。ありがとうございました。