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

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

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でフルに活用させていただき、最新の強化学習のキャッチアップ含め、勉強になりました。ありがとうございました。

新しくKaggle用のマシンを調達しました(2020年ver)

皆さんこんにちは
tereka114です。本日はKaggle Advent Calendar24日目の記事です。
今回はKaggle用マシンで購入したものとパーツ選びの基準をご紹介します。

qiita.com

Kaggle用マシン

Kaggle用マシンがなぜ必要か

Kaggleをするためにはノートパソコン一台でも十分です。
しかし、勝つには、最近の巨大データ、画像、NLPコンペの増加に伴いハイスペックPCが必要になってきました。
特に画像やNLPでは、CNN、BERTと呼ばれるニューラルネットワークの手法がソリューションを締めており、
普通の特徴量抽出の手法では勝てなくなってきています。

そのため、それなりの人たちの多くはGPUがあるマシンを持っていることが多くなりました。

クラウドがあるのではないか?といった疑問を持たれた方もいると思いますが、
はっきりいってオンプレ運用より高くなるので緊急時以外おすすめしません。

どのような要件のマシンが必要か

高性能なCPU、メモリ、GPUです。といっても元も子もないので、
どのようなところに影響するのかはパーツの選定の箇所で説明します。

パーツの選定

ここでは実際に組み上げたPCのパーツの紹介をしつつ、選定基準を記載していきます。

基本方針

CPU、GPU、メモリは良いものを選定するのは基本です。
しかし、意外に見落としがちなのはマザボと電源です。
この2パーツはとにかく、故障したときに原因がわかりにくいもので相当曲者です。
ここで失敗すると精神衛生が悲惨です。

各々のパーツ

CPU

lightgbmとテーブル加工の処理、画像の前処理で利用します。
画像処理のあるあるですが、良いGPUだけを準備してもCPUがボトルネックになれば高速化されないので、GPUに合わせてCPUも良くする必要があります。
可能な限りスペックをよくしたいなーと思っていたので、次のCPUを選択しました。
10コア20スレッドです。

メモリ

Kaggleでメモリが必要になるケースは概ね2パタンです。

  • 巨大なテーブルデータの場合、ほとんどのデータをメモリに載せる必要があるので
  • 高速化のためのキャッシュ、画像コンペだとすれば画像を読む、加工、廃棄をすることになりますが、廃棄せずメモリに持てると性能が向上します。

GPU

RTX3090です。私が参加するコンペの多くは画像、もしくは、ニューラルネットワーク
勝つソリューションが多いので、ここは譲れないところでした。

MSI GeForce RTX 3090 GAMING X TRIO 24G グラフィックスボード VD7347

MSI GeForce RTX 3090 GAMING X TRIO 24G グラフィックスボード VD7347

  • 発売日: 2020/09/24
  • メディア: Personal Computers

GPUの選定ですが、基本は性能とメモリです。
Kaggle界ではメモリの方が重要視されている気がします。

巨大モデルである程度のBatchsizeを担保しながら動かさないといけないので、11GBでは足りなくなってきていると感じています(MixedPrecisionも使っていますが厳しい)
そのため、24GBある3090を首を長くしてお待ちしていました。

ちなみに、普通の機械学習の勉強とかであれば、ColaboratoryにもGPUあるので、そういったところにGPUはおまかせするのも一つの手だと思います。

SSD

あまり考えずに選定していますが、最近のデータセットの規模から2TBは最低あっても良いと思います。
少ないとストレスなので、可能であれば大きめのものを買うと良いでしょう。

電源

先程簡単に説明しましたが、電源とマザボは最重要パーツの一つです。
というのも、電源とマザボが故障すると原因の調査が難しいものの一つで、問題が起こると、
一度総解体する必要も出てくるぐらい厄介なパーツです。

電源において重要なポイントとしては消費電力のギリギリのW数の電源を購入するのはいけません。
電源自体が劣化すると出力も下がり、運用していて落ちるようになります。
後々の平穏のためにも決してケチってはいけません。

そのため、200-300Wほどは余裕を持っておくことをおすすめします。

マザーボード

電源の箇所で紹介しましたが、最重要パーツの一つです。
可能な限り高めのもの(1万超え-3万程度)のものを購入するようにしています。
私のマザボ購入時のチェックはCPUの型番に対応しているか(これ対応していないと購入し直しです)。
メモリのスロットが何枚あるか、PCI-Eのスロットはどの程度あるかは確認しています。

私はよくASUSのものを購入しています。電源がついているかどうかなどわかりやすいので、気に入っています。

ASUS INTEL Z490 搭載 LGA1200 対応 ROG STRIX Z490-E GAMING 【 ATX 】

ASUS INTEL Z490 搭載 LGA1200 対応 ROG STRIX Z490-E GAMING 【 ATX 】

  • 発売日: 2020/05/21
  • メディア: Personal Computers

PCケース

タワー、ミドルタワーそしてフルタワーの種類があります。
GPU指す場合はフルタワーのものをおすすめです。でかいので余裕があります。

ファン

Intelの標準装備はあまり性能が良くないので必ず外部で購入するようにしています。
巨大ですが、それなりにコスパが良いものを使っています。
私は虎徹を購入するのが標準的なので今回もそれを買いました。

サイズ オリジナルCPUクーラー 虎徹 Mark II

サイズ オリジナルCPUクーラー 虎徹 Mark II

  • 発売日: 2017/06/02
  • メディア: 付属品

最後に

Kaggleを真面目にやりたいと思っている人は購入されることをおすすめします。
多分完成品を購入するよりも自作の方が安いです。

Kaggle Notebookで使えるトリックを紹介します

皆さんこんにちは。
お元気ですか。私はもう小麦そのものは食べる専門です。
本日はKaggle Notebookに関するトリックを紹介しようと思います。

Kaggle Notebook

Kaggle NotebookはKaggle上で動作する実行環境みたいなものです。
Kaggle上で動いているJupyter Notebookと解釈しても差し支えないでしょう。

昨年からこのKaggle Notebookで推論のみ提出コンペが増加しています。
推論のみ提出の嬉しいこととして、実行環境による差分がでないことがあります。
画像や自然言語処理は無限のモデルを生成するためのマシンリソースで殴ると勝ちやすいことがありますが、この場合、アンサンブルのみではなくある程度は工夫が必要になります。
反対に提出物作るのが格段に面倒になります。(ファイルのアップロード、推論コードの修正、コミット、サブミットを手動で行う)

黒魔術紹介

ここからは私が使っているトリックを紹介していきます。

  • コードを呼び出すほうがリソース管理が楽
  • 外部パッケージインストール
  • 例外
  • 小数点以下の結果に注意
  • コミットまでの速度を早くする

コードを呼び出すほうがリソース管理が楽

Kaggle KernelからはJupyter Notebookと同様の方式で実装を呼び出せます。
実装上はただの「!python predict.py」です。
通常のJupyter Notebookだと、コードを書いていくのが一番オーソドックスであり、本来の使い方だと思います。

近年のコンペだとチームマージした場合に問題が起こりやすいと思います。
よくあるのは、Keras/PyTorchの実装が混合するケースです。
その場合、PyTorchやTensorFlowのGPUメモリ開放の方法を実装せねばなりません。
また、GPUの稀に開放されないこともあるので面倒になるため、プロセスの終了をトリガーとすると開放もれがなくなるので便利です。
デバッグや更新が面倒なため、GPUメモリがシビアなコンペで利用します。

※ただし、実装の更新は面倒なので、コマンドライン引数の設定の工夫が必要です。

外部パッケージインストール

コンペによっては外部パッケージをインストールしたほうが良いものもあります。
私がKernelコンペで利用したもののひとつとしてapexがあります。
例えばapexを利用する場合のインストールコマンドは次のとおりです。
その後再起動もいらないので、長時間実行するコンペだと利用可能でしょう。

!cd ../input/apexpytorch && pip install --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" . && cd -

例外

Kaggle Kernelの提出時に例外が出るケースがあります。
しかし、例外の内容がさっぱりわからないので、解析が難しいです。
例外が見えすぎてもコンペ参加者がTestを例外を使ってハックするという事が起こるため、どこまで参加者に見せるかは悩ましい問題です。
私が見つけたものだと、このあたりです。

Exception 説明
Notebook Exceeded Allowed Compute メモリのリークなどが原因でプロセスが落ちた場合
Submission Scoring Error 提出形式に不備があった場合、スコアが計算できなくて例外が発生
Notebook Timeout 計算時間オーバー
Notebook Threw Exception 例外発生時の問題(よくわからない)

小数点以下の結果に注意

Kaggle Kernelを利用して提出する場合、基本例外が発生してもよくわかりません。
Google QAのコンペ(Spearman's rank correlation coefficient)では、出力結果によりかなりの例外が発生することを確認していました。
数式的には均等な値(1しかない)でなければ例外が発生しうる要因はなかったので、疑問に思っていました。

調べたら小数点の桁をうまく調整すると回避できたので、それが原因でないかと推測し、対処できました。
殆ど使う機会はないのかなとは思っていますが、頭の片隅に残しておくと役に立つ日が来るでしょう。

www.kaggle.com

コミットまでの速度を早くする

週利用の制限時間が42hしかないため、実行時間の無駄使いはコンペとしては致命傷になりうります。
そのため、一部の処理をスキップしたり早めることで、コミット完了までの時間を早められます。

提出前後(submit)でそれはデータセットの長さに変化があります。
つまり、データセットの長さで変化するような実装を入れておけば短くできるということです。
次の例では、sample_submission.csvを使っていますが、この長さが変わらないこともあるようです。
その場合は別の方法でデータ数を取得するのが望ましいでしょう。

例えば、こんなコードを書けば良いです。

import pandas as pd
submission_df = pd.read_csv("sample_submission.csv")
if len(submission_df) < 11: # データ数で判断する。他の方式でも可能
     submission_df.to_csv("submission.csv", index=False)
     exit()
# 以降後続の処理を記載する。

最後に

Kaggle Notebookは不便なところも多いため、工夫して使わないと厳しいです。
ただ、工夫しがいはあるので、個人的には好きなコンペの開催形式の一つです。
懲りずに参加しようかと思います。