思考の本棚

機械学習のことや読んだ本の感想を整理するところ

Indoorコンペ振り返り

はじめに

2021/01/28~05/18の期間、kaggleで開催されていたIndoor Location & Navigation (通称: Indoorコンペ)に参加しました。twitterで知り合った4人の方とチームを組み、1170チーム中16位をとることができたので振り返り記事を書こうと思います。なおこの記事では自分たちのソリューションではなく、どのような趣旨のコンペであったか、どういう取り組みが重要であったかに重きを置いて書こうと思います。

f:id:kutohonn:20210519150222p:plain

コンペ概要

屋内の位置推定コンペ。もう少し具体的にいうとユーザが持っているスマホ内蔵のセンサデータや屋内施設に設置されているwifi端末, bluetooth端末から受信するデータを用いて、ある時間におけるユーザの位置(x,y)と建物の階数(floor)を推定するというもの。

評価指標

f:id:kutohonn:20210520203953p:plain 今回予測するのは x,y, floorでありx,yはtargetとの距離の平均誤差で与えられており非常にわかりやすいもの。floorの方はtargetとの絶対値誤差で重みがp=15というように設定されているため、floorを間違えてしまうとスコアに大きく影響するというものでした。

提供データ

Android APIを使ってスマホから取得したデータがtxt形式で与えられていた。与えられるデータは大きく3つ。

  • wifiデータ

    • スマホが受信した周囲のwifi端末の情報
    • wifiSSID(ネットワーク名)、BSSID(端末のID)、RSSI(電波強度)など。
  • センサデータ

    • 加速度、ジャイロ、回転、磁気などスマホで測定できるセンサ値。
    • 測定した時刻がそれぞれに紐づいている。
    • データ数が一番多い
  • beaconデータ

    • スマホが受信した周囲のbluetooth端末の情報
    • wifiと似たようなデータ
    • ただしwifiよりデータ数が少なく推定精度も低いため今回のコンペではあまり使われていなかった印象

上記に加えて建物の構造を示すpngファイルやjsonファイルなどのメタデータも提供されていた。建物は24siteが予測対象だった。 wifi,センサ,beaconデータはpathごとにtxtファイル化されていた。pathというのは以下の図のような連続的な移動のまとまりのこと。以下の図はpathごとに色分けされている。

f:id:kutohonn:20210520213236p:plain

txtファイルのままでは扱いにくいのでデータセットを作成するところからのスタートで、コンペ当初はさまざまなデータセットが公開されてた。 floorの予測は99%の精度のnotebookが公開されるなど位置推定と比べるとそこまで難しいタスクではなく、x,yの予測をいかに行うかが重要であった。(ただし公開notebookは精度100%ではない&floorのミスはスコアには大きく影響するためfloor予測モデルを自分たちで作成するのも重要ではあった) 公開notebook及びソリューションを見るとwifiデータを使ってNNやlightGBMに与えて位置推定をするというのが本コンペの基本的なアプローチであった。

公開されていた後処理

本コンペはおそらく参加者全員が使っていた公開notebookの後処理が2つある。この2つを後処理として加えるだけで大きくスコアを改善することができた。 また上位陣の多くはこの処理を複数回繰り返すことでさらにスコアを伸ばしていた。(弊チームでは6回繰り返していた)

(1) Cost minimaization.

  • 機械学習で予測した位置(絶対位置)とセンサデータから計算される相対位置を組み合わせ連続値の最適化問題を解くことによってより正確な予測にする手法。
  • ホストのGitHubでセンサデータから相対位置を算出するコードが公開されており(精度はそこまで高くない)、この手法を紹介してくれた公式notebookではその相対位置を用いて最適化を行なっていた。
  • 機械学習で予測される絶対位置もセンサデータから計算される相対位置も誤差を含んでいるがこの最適化問題を解くことで予測精度を高めることができ、コンペ終了後の解説ではカルマンフィルタ(スムーザ)と等価の処理を最適化によりおこなっていることが共有された。

(2) Snap to grid.

  • trainデータのtargetであるx,yの点をgridとみなし、そのgridの近くに予測値がある場合、gridの座標を予測値とする(gridへ予測値をsnapする)というヒューリスティックな手法。
  • train/testの多くが同じような位置に存在していたことから本コンペでは非常に強力な後処理手法だった。

重要であったポイント

終了後の解法を見て本コンペで特に重要であったポイントは3つあると感じた。

機械学習モデルによる位置推定

  • ここが一番取り組みやすい部分で多くの参加者がコンペ当初からここに力を入れていたと思う。
  • wifiデータセットの作成が重要で、予測対象の時刻とwifiの計測時刻の差分が大きいものはノイズとなるためそのデータを取り除くことが重要だった。(弊チームはこれだけで銀圏モデルができていた模様。)
  • モデルはMLP, lightGBM, RNN, kNNなどが使われていた。NNの方が精度が良く多くの人がそちらを使っていた印象。
  • 1位, 2位はweighted kNNを使っていたようで非常に強力なモデルであったみたい。ただ単純にモデルを適用というわけではなく色々工夫されていた。
  • 上記例は全て回帰問題としてx,yを学習していたが、2位のチームはfloorを細かいセグメントにわけ分類問題として解く手法を行なっておりとても新鮮だった。
  • 7位9位のチームはpseudo labelingを行うことでスコアを大きく向上させていた。後処理やアンサンブルをしたものを使って繰り返しpseudo labelingを行うことでpseudo labelの精度をあげた結果スコアの大幅な向上につながったみたい。弊チームや2位チームもpseudo labelingを試していたが効果は薄かった。

相対位置推定

  • 概要で述べたように本コンペではcost minimaizationの後処理が非常に強力であった。
  • しかしそこで使用している相対位置は精度が高くなかったため、相対位置を何らかのモデルで推定することが重要であった。
  • site, floorごとにパラメータを調整した歩行モデリング,RNNやMLPなどのNNモデルが利用されていた。
  • 弊チームではsite,floorごとに線形回帰モデルでホストの相対位置を補正するアプローチをとっていた。(ただし上位陣がやっていた手法と比べると強力でなかったと思う)
  • この相対位置の推定を行っていたのは金圏、銀圏の一部のチームだったと思う。

離散最適化

  • 完全には把握できていないが解法を読む限り、これを行っていたのは入賞圏や一部の金圏チームのみだったと思う。
  • そもそもの前提として本コンペはtrain,testの予測点がかぶっているケースが多くそれによりSnap to gridが大きな効果をもたらしていた。
  • そのため予測値がどのgridに紐づくかがスコアに影響する。
  • 例えば以下は後処理後の予測値を繋いだpathであるがオレンジ色のpathを右側をスタートとして見てみると、明らかにおかしなルートをとっていることがわかる。

f:id:kutohonn:20210520215207p:plain

  • 2点目3点目は下の歩行通路に本来は来るべきだとこの可視化を見ると気づくことができる。これらをより正しい位置に修正するために通路に存在するgrid(train dataの点)とそれらを結ぶ経路をグラフとみなし最適化を行うことで予測値を修正することが重要だった
  • この離散的な最適化問題を1位は複数の制約条件を加えたBeam Searchによる最適化、2位は複数の制約条件を加えたgreedy法による最適化,11 位は動的計画法による最適化を行なっていた。
  • 今聞くと非常に納得感が強いがコンペ中はこの発想に全く至らなかった。

感想

このコンペはモデリングだけでなく後処理が非常に重要で、機械学習だけでなく数理最適化の技術やヒューリスティックな方法などさまざまな工夫が解法に見られたコンペで、解法を読んでいてとても面白かったです。 参加した感想としてこのコンペは考えないといけないことが非常に多く、一人では全然手が回らなかったなと思います。そういう意味で今回チームを組んだおかげでモチベーションも維持でき、やることを分担したおかげで銀圏に入ることができたと思っています。チームメイトにはとても感謝です。現在似たコンペとしてGoogle Smartphone Decimeter Challenge という屋外の位置推定を行うコンペ(通称:outdoorコンペ)も開催されているので今回学んだことを元に取り組んでみようかなとは思っています。 最後まで読んでいただきありがとうございました。

解法

参考までに僕たちのチームの解法をおいておきます。

f:id:kutohonn:20210519151329p:plain

16th place solution www.kaggle.com

鳥蛙コンペの振り返り

はじめに

2020年の12月から2月にかけてkaggleでRainforest Connection Species Audio Detectionというコンペが開催されており、最終的に5位を取ることができました。この記事ではコンペの概要や取り組みを記録しておこうと思います。

f:id:kutohonn:20210218113601p:plain

コンペとタスク

コンペ概要

今回のコンペは熱帯雨林で録音された音声から24種の匿名の鳴き声を検出・分類するというものでした。データはtrain/testともに音声ファイル(60s)1つにつき、複数種の鳴き声が存在するためmultilabelで学習・予測を行う必要がありました。

音声認識について

今回のタスクに似たコンペとしてDCASEという環境音認識のコンペがあり、こちらに音声認識の概要がわかりやすく書かれています。 engineering.linecorp.com

評価指標

今回の評価指標はLRAPという予測値のrankingに基づく指標にクラスごとのラベル数で重み付けされたLWLRAPが使用されていました。こちらは2019年に開催されたFreesound Audio Tagging 2019でも使用されており音声認識のタスクで使われる指標のようです。

評価指標の説明に関してはこちらのdiscussionがとても参考になりました。

鳥コンペとの違い

似たようなコンペとして2020年8~9月にkaggleでCornel Birdcall Identification(通称: 鳥コンペ)が行われていました。こちらも鳥の鳴き声を分類するタスクで今回紹介する鳥蛙コンペと非常に似た趣旨のコンペでした。 鳥コンペと異なる点としては以下のような項目が挙げられます。

鳥コンペ 今回
評価指標 micro averaged F1 score LWLRAP
ラベル付け weak label strong label
クラス数 264 24
クラス名 あり なし(匿名)
予測 5s単位で予測 60s単位で予測
その他 nocall(鳥が鳴いていないこと)の予測が必要 FPデータあり(後述)

補足するとweak labelとは音声ファイル単位でラベルが付けられているもので、strong labelとは音声内のどこで鳴いているかという時間情報も加わったより詳細なラベルとなります。

課題(本コンペの特徴)

今回のコンペで個人的に重要なポイントと考えたのは以下の3点です。

1. train/testでアノテーション方法が異なる

本コンペではtrainとtestでアノテーション方法が異なることが知らされていました。 アノテーション方法について整理したdiscussionでまとめられていたのが以下の図です。

f:id:kutohonn:20210218182213p:plain

trainデータはrudimentary detection algorithumによって検知された音声の箇所を専門家によって正例か負例かに分類することでtrainデータのアノテーションを行っている一方で、testデータはアルゴリズムを介さずに専門家が音声とスペクトログラム画像を確認することでアノテーションがされています。この違いによりtrain/testでラベルの分布が大きく異なる可能性が考えられました。

2. missing labelが多い

課題1に関連して、こちらのdiscussionで紹介されているように今回のtrainデータには鳴き声があるがラベルがついていない, missing labelが存在していました。 下の図で赤色で示しているのが与えられているラベルですが、モデルで予測すると、青色で示すようにラベルがついているところ以外でも実際は鳴き声があることがわかります。(図は上記discussionより引用) f:id:kutohonn:20210218180112p:plain

上記のように同じ種のラベルが欠損しているのであればそこまで問題ではありませんが、鳴いているのにラベル付けされていないクラスがある場合、学習に支障をきたすことが考えられます。 実際、trainデータのラベルを確認すると1132件の音声のうち複数の種のラベルが1つの音声についていたのはわずか27件のみでした。

f:id:kutohonn:20210218175431p:plain

コンペ終盤に出たdiscussionによるとtestデータは1つの音声に平均して4~5種の鳴き声が含まれているという示唆があったことから、trainデータはtestデータに比べて圧倒的にラベルの数が少ないことが課題であり、これに対処する必要がありました。

3. FPデータの扱い

今回は通常のラベルに加えてFalse Positive(FP)ラベルも与えられていました。これはどういうラベルなのかというと、課題1で説明したアルゴリズムによって鳴いている(Positive)と判定された種のうち、専門家によって「判定が正しい(Positive)」と判定されたものをTrue Positive(TP)データ、「判定が正しくない(False)」と判定されたものをFalse Positive(FP)データとして与えられていました。 TPデータはそのままラベルとして使うことができますが、FPデータは「種Aは鳴いていない」という情報しかないためそのまま使うのは難しいものでした。ただTPデータの音声は約1000件でFPデータの音声は約3000件あったのでFPデータをどのように使うかというのが一つのポイントであったと考えています。

解法

上記の課題を踏まえて私たちのアプローチを紹介しようと思います。 最も重要だと考えるポイントは以下の3つです。

  • 3stageによる学習
  • 訓練データに対するpseudo labelingによりmissing labelを補完
  • custom lossにより曖昧なラベルはlossを計算しないようにする

今回のコンペでは後述するpseudo labelingとcustom lossによるoverfitを防ぐために3 stageで学習を行いました。以下私のモデルを例に説明していきます。

stage1

CV:0.81 LB:0.84

  • EfficientNet-b2
  • SED model (clipwiseでloss計算と予測を行う)
  • 5fold StratifiedKFold
  • 30 epoch
  • LSEP Loss
  • TPデータのみ使用
  • 画像サイズ(height,width)=(244, 400)
  • batchsize 16
  • Adam
  • learning_rate=1e-3
  • CosineAnnealingLR(max_T=10)
  • augmentationなし
  • 10s単位で学習・予測

stage1では鳥コンペや本コンペで有効とされていたSound Event Detection(SED)モデルを使用しました。SEDについては鳥コンペのnotebook本コンペのdiscussionがとても参考になります。

stage1では特別なことは行っていませんが1点だけ。多くの参加者がBCELossとFocalLossを用いていましたがここではLSEPLossを使用しました。 LSEP LossはFreesoundコンペの3rd solutionでも使用されていた損失関数で、今回のコンペのようなrankingに基づく評価指標の場合、BCELoss のような分類用の損失関数よりもより良い精度が出ると上記discussionで説明されていました。実際に使用したところBCELossと比べてLB scoreが0.01とかなりアップしました。

stage1の目的は後のstage2,3で使うpretrained modelを作ることです。コンペ前半はstage1のモデルを強化することに取り組んでいましたがなかなかスコアが伸びませんでした。

stage2

CV: 0.734 LB:0.896

  • stage1とほとんど同じ構成
  • stage1のモデルをpretrained modelとして使用
  • 5 epoch
  • FPデータをTPデータと同じ数だけsamplingして学習に使用
  • FocalLossベースのカスタム損失関数

stage2の目的は2つあります。1つはstage1のmodelに追加学習する形で精度を向上させること、もう1つは精度が向上したstage2のモデルを利用してTP・FPデータに対して予測を行い、pseudo label(擬似ラベル)を作成することです。精度を向上させるためにstage2ではFPデータの追加とcustom lossを導入を行いました。この追加学習によりLB scoreが+0.05とかなり向上しました。

custom lossについて

本コンペの課題としてmissing labelがありました。missing labelがあるということはTPデータで0(負例)としてラベル付けされているクラスの中にも実際は1(正例)のクラスが含まれているということです。そこでラベルを以下の3つに分けることにしました。

1: TPラベル(鳴いている)
0: 曖昧なラベル(正例が混じっているかもしれない)
-1: FPラベル(鳴いていない)

例)
label = [0, 0, 1, 0,...., -1, -1]

この3つのラベルのうち0ラベルは曖昧なラベルとして扱い、loss計算時に省くようにlossを設計しました。 FPデータは間違われやすいけど専門家によって鳴いていないと判定された非常に有益な情報なのでそれを0ラベルと違うことがわかるよう-1としてラベル付けし、loss計算の時には0(鳴いていないもの)として計算しています。このようにFPデータを扱うことで本コンペの課題の1つであるFPデータの利用に対処しました。

これにより明確にラベル付けされているところだけ学習され、曖昧な箇所は学習されないようになりスコアの向上に寄与しました。ちなみにこの損失関数でstage1のように1から学習することも試しましたが学習効率が悪く、スコアも低下したことからstageを分けて5 epochだけ追加学習するアプローチを取りました。

実装は以下です。stage1で使ったLSEPLossは以下のような実装が困難だったのでFocalLossをベースにカスタムしました。BCELossよりFocalLossの方が効いたのでFocalLossを採用しました。

class FocalLoss(nn.Module):
    def __init__(self,  gamma=2.0, alpha=1.0):
        super().__init__()
        self.posi_loss = nn.BCEWithLogitsLoss(reduction='none')
        self.nega_loss = nn.BCEWithLogitsLoss(reduction='none')
        self.zero_loss = nn.BCEWithLogitsLoss(reduction='none')
        self.gamma = gamma
        self.alpha = alpha
        # self.zero_smoothing = 0.45

    def forward(self, input, target):
        # mask
        posi_mask = (target == 1).float()
        nega_mask = (target == -1).float()  # (n_batch, n_class)
        zero_mask = (target == 0).float()  # ambiguous label
      
        posi_y = torch.ones(input.shape).to('cuda')
        nega_y = torch.zeros(input.shape).to('cuda')
        zero_y = torch.full(input.shape, self.zero_smoothing_label).to('cuda')   # use smoothing label

        posi_loss = self.posi_loss(input, posi_y)
        nega_loss = self.nega_loss(input, nega_y)
        zero_loss = self.zero_loss(input, zero_y)
        
        probas = input.sigmoid()
        focal_pw = (1. - probas)**self.gamma
        focal_nw = probas**self.gamma
        posi_loss = (posi_loss * posi_mask * focal_pw).sum()
        nega_loss = (nega_loss * nega_mask).sum()
        zero_loss = (zero_loss * zero_mask).sum()  # stage2ではこれをlossに加えない
        
        return posi_loss, nega_loss, zero_loss

訓練データに対するpseudo labelingについて

上記の方法で学習したstage2モデルを使ってtrainデータ(TP/FP全て)に対して予測を行いpseudo labelを作成しました。目的はtrainデータに欠けているラベル(missing label)を補填するためです。 ラベル付けは以下のように行いました。

threshold = 0.5として
1: 0.5 <= 予測値
0: 予測値 < 0.5

これを新たなラベルとして追加しstage3で使用します。これによりmissing labelに対処しました。 0ラベルには正例のラベルが含まれている可能性もあることからあくまで曖昧なラベルとして扱います。

stage3

CV:0.954 / LB:0.950

  • stage2とほとんど同じ構成
  • original label+pseudo labelで学習
  • stage1のモデルをpretrained modelとして使用
  • 5epoch
  • FPデータをTPデータと同じ数だけsamplingして学習
  • Focallossベースのカスタム損失関数
  • last layer mixup (+0.007)
  • 曖昧なラベルに対してlabel smoothingをかける(0 -> 0.45としてlossを計算) (+0.009)

stage3が最終的なモデルになります。pseudo labelを新たなラベルとして加えることによって課題の1つであったmissing labelに対処しました。これによりLB scoreが0.04向上しました。またニューラルネットワークの最終層でmixupも有効でした。注意点としてstage2で使っているcustom lossは過学習に陥りやすかったのでstage3ではstage2のモデルではなく、stage1のモデルをpretrained modelとして学習しています。以上がモデルの全体像になります。

CV

今回は課題1で述べたようにtrainとtestでラベルの分布が大きく異なっておりvalidationが難しいコンペだったように思います。 私たちのチームでは以下の5指標を確認しながらサブミットを行っていました。

  • pseudo labelありのLWLRAP
  • pseudo labelなしのLWLRAP
  • Recall
  • Precision
  • AUC

ただどの指標もLBと相関が十分にとれているとは言えませんでした。なのでPublic/Private LBに大きな差はないという仮定のもと、基本的にはtrust LBでモデルを改善していきました。 shake対策としては多様性の多いモデル(ViT, WaveNet, ResNet18)でpseudo labelと予測のアンサンブルを行いlabel及び予測がロバストになるようにしました。 結果としてはPublic/Privateで大きな差異はなく8th -> 5thにshakeupしてコンペ終了を迎えることができました。CVの良い方法についてはこれからdiscussionを読んで勉強したいと思います。

その他うまくいかなかったこと

  • mean-teacher
  • Conformer
  • testデータに対するpseudo labeling
  • 通常のmixup
  • noise追加/除去
  • TTA
  • SAM optimizer

おわりに

今回最終的に初の金メダルを取ることできました。自分より格上の人とチームを組めたことで上位陣の戦い方や考え方を学ぶとてもいい機会になったと思います。一緒にチームを組んでくれた2人にはとても感謝しています。またコンペ序盤から有益な情報を共有してくださったaraiさんやshinmuraさんからも勉強させていただきました。これからはコンペの順位はもちろんのこと、コミニュティへの貢献も意識して取り組んでいきたいと感じたコンペでした。

参考

コンペのGitHubレポジトリはこちらです。 (branchごとに分けてやっていたのでごちゃごちゃしてしまった。) github.com

PyTorch-Lightningでの再現性

はじめに

PyTorchで再現性を持たせたい場合、以下のようなコードを使うケースが多いと思います。

def set_seed(seed: int = 42):
    random.seed(seed)
    np.random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

以前まではこれでseedを固定できていたのですが、PyTorch-Lightningに切り替えた場合、同じコードを実行してもlossや予測値がブレる現象が起きたのでその原因と対策を記録しておきます。

結論

pytorch_lightning.Trainerクラスの引数でdeterministic=Trueとする。

pytorch_lightning.Trainerはデフォルトではdeterministic=Falseとなっています。 これにより上記関数でtorch.backends.cudnn.deterministic = Trueと設定していてもTrainerによりFalseに上書きされていました。 厳密な再現性を望まない場合は不要な設定かもしれませんが、比較実験を行う場合はこの設定をしておく必要があるかと思います。 pytorch-lightning.readthedocs.io

その他

今回はlightning周りで再現性がとれない現象が起きていましたがdataloader周りでも問題が起こることもあるようなので念の為共有しておきます。 qiita.com

Google Research Football環境を利用した選手の行動分析

はじめに

この記事スポーツアナリティクス Advent Calendar 202016日目の記事になります。

この記事ではGoogle Researchが提供しているサッカーゲーム環境を使って選手の行動や試合の分析を行ってみたいと思います。これをやろうと思ったきっかけとしては、先月までkaggleで開催されていたサッカーゲームのコンペに参加しており、自由にデータを取得できるゲーム環境で分析をしてみたら面白いのでは?と思ったからです。コンペの内容については以下の記事で書いています。

kutohonn.hatenablog.com

それでは早速進めていきます。

 

 

概要

ゲーム環境の説明

今回はGoogle Research Footballというサッカーゲーム環境を利用しています。試合の様子はウイイレFIFAとほとんど同じと考えてもらって大丈夫です。本ゲーム環境はAPIが用意されておりpipでinstall可能となっています。

f:id:kutohonn:20201201214927p:plain

詳細はGitHubをご覧ください。

github.com

試合の設定

試合設定についてご説明します。

1試合は前半1500steps、後半1500stepsの合計3000stepsから構成されています。アディショナルタイムはありません。

操作可能な選手はアクティブプレイヤーと呼ばれるボールに一番近い選手のみとなっています。これも通常のサッカーゲームと同じ仕様ですね。今回はこのアクティブプレイヤーをコンペで作成された2つのエージェントを持ってきて対戦させることで分析を行いたいと思います。なおこれは本ゲーム環境の特殊な点なのですが、前半後半でコートチェンジがありません。なので今後は両チームをそれぞれleftチーム, rightチームと呼ぶこととします。

 

選択可能な行動一覧

方向キー8個に加えてパス、シュートなど合計18種の行動が用意されています。詳細は以下。

github.com

 


試合の様子

leftチームとrightチームで10試合対戦してみた結果がこちらになります。

- left team right team 勝ちチーム
1試合目 1 6 right
2試合目 3 3 -
3試合目 4 3 left
4試合目 1 3 right
5試合目 1 7 right
6試合目 1 7 right
7試合目 0 3 right
8試合目 2 7 right
9試合目 5 4 left
10試合目 1 6 right
総得点 19 49 left:1 right:7

 

どうやらrightチームの方が強そうです。今回はこの試合の1試合目を対象に分析を行いたいと思います。今回の分析対象の試合の様子は以下のリンクから見れます。 

drive.google.com

 

分析

ここから本題の分析に移っていきたいと思います。

今回は以下の分析を行いたいと思います。

  • 試合内容の分析
  • ボールのトラッキング
  • アクティブプレイヤーの行動分析
  • 任意の選手の行動分析

 

試合内容の分析

まずは基本的な試合の内容に関して両チームで比較してみます。

f:id:kutohonn:20201214230159p:plain

rightチームの方がボール支配率やコーナーキックの回数が多いです。これらの結果が得点ともリンクしてそうです。


ボールトラッキング

次に試合中のボールの位置を可視化してみたいと思います。

ボールのトラッキング

f:id:kutohonn:20201213204441p:plain

ボール位置のヒートマップ

f:id:kutohonn:20201213205826p:plain

中央の動きが多くてサイドをめいいっぱい使った動きは少ないようですね。またボールは右コート側に多く存在していることが分かります。leftチームが攻めている、あるいはrightチームが自陣でボールを回していることが考えられます。また左コートでのみコーナーキックが起きていることも分かります。これは試合内容の分析とも一致しています。

 

アクティブプレイヤーの行動分析

アクティブプレイヤーがどのような行動をとっているかをみていきたいと思います。

アクティブプレイヤーの全行動の数を集計

(注意点:rightは両チーム攻め方向を表す)

          左チーム                 右チーム

f:id:kutohonn:20201213213217p:plain
f:id:kutohonn:20201213213230p:plain

 

どちらもsprint(ダッシュ)や移動行動(攻め方向であるright)が上位にあります。一見左チームの行動がバランスよく行動を選択しているように見えますが、3000回の内、shotを100回以上選択しているように、通常では考えられない数のシュート行動をとっていることから不要な箇所でシュート行動を選択してしまっていることも考えられます。

 

ただこの図は全18種類の行動で可視化していますが、idleやrelease_directionなどゲーム特有の行動がありノイズが多いです。また両チームdribbleが異常に小さいことが分かります。どうやらdribbleはここではうまくカウントされていないようです。

 

そこでここでは簡易的に攻めの方向に移動している行動(right, top_right, bottom_right)をdribbleとします。こうして作成したdribble特徴量と主要な行動であるsprint, sliding, shot, high pass, short pass, long passの7つに絞って以下のように可視化してみました。

f:id:kutohonn:20201213221354p:plain

どちらのチームもドリブルやスプリントが全体的に多めです。leftチーム(赤)はパス、シュート、スライディングがrightチームと比べると多いようです。一方rightチーム(青)はスプリントとドリブルがより多めのようです。このことからざっくりですがleftチームはパスで攻めるチーム、rightチームはドリブルで攻めるチームであることが予想されます。

任意の選手の行動

ここでは両チームのプレイヤーに関する動きを見ていきます。分析対象は両チームの10番の選手です。10番は本設定ではレフトミッドフォルダーとして定義されています。

 

10番トラッキング

f:id:kutohonn:20201213231040p:plain

10番の動きのヒートマップ

f:id:kutohonn:20201213231113p:plain

当たり前ですが、ポジションがレフトミッドフォルダーなのでどちらの選手も左側にいることが多いです。上では試合中の全ての動きをトラッキングしていますが、今度はボールを保有しているときの様子を見てみることにします。


 ボール保有状態でのトラッキングとヒートマップ

f:id:kutohonn:20201213231324p:plain

ここではトラッキングデータとヒートマップを重ねて表示しています。ボール保有状態にするとデータ数と対象範囲がかなり小さくなったのが分かります。全ての動きを可視化したものと比べると、より真ん中のエリアでボールに触れていることが分かります。このことからボールはやはり中央部分に集まっており、ライン側を使った攻めは少ないことが考えられます。

 

その他行ったこと

  • 走行距離

選手の座標記録から『走行距離』を算出しようとしたのですがおそらく計算ミスでうまく出せず諦めました。

本ゲームにはプレイヤーごとに疲労度パラメータが割り振られているのでそれを可視化すると面白いと思ったのですが、どうやら試合が止まると疲労度が回復する仕様?になっていたので今回はぼつになりました。

  • 特定のシナリオでの分析

本ゲーム環境には特定のシナリオからプレイを始める機能が備わっています。例えばコーナーキックやゴール前での3 vs 1などです。シナリオの種類は以下参照。

https://github.com/google-research/football/blob/master/gfootball/doc/scenarios.md

しかし人数が変わると今回使用しているagentの挙動がおかしくなってしまったので今回は見送りました。

 

おわりに

今回はサッカーゲームを用いて試合や選手の分析をしてみました。実際のサッカーの試合とはもちろん異なるところは多々あると思いますが、データが入手しやすい点からスポーツ分析の練習として良いのではないかと思いました。また今後このようなシミュレーション環境の性能が上がっていくと、ゲームを通して実際の試合の戦略を立てるといったこともあるのかなと思いました。

 

分析に用いたレポジトリ

github.com

サッカーを強化学習する

f:id:kutohonn:20201207171940p:plain
 

はじめに

 

この記事は強化学習苦手の会Advent Calenderの12日目の記事です。 私は11月末までKaggle上で開催されていたGoogle Research Football with Manchester City F.C.に参加していました。このコンペはGoogle Researchが用意したサッカーゲーム上でサッカーエージェント(プレイヤー)を作成し、その強さを競うというものです。

私はhigeponさんとチームを組ませていただき、強化学習アプローチでコンペ開催から終了まで取り組みました。そこでサッカーエージェントを強化学習で育成する際に工夫した点や苦労した点を共有できればと思います。

 

 kaggle: Google Research Football competition

www.kaggle.com

GitHub: Google Research Football

github.com

 



コンペの概要


ゲームのルールは通常のサッカーとほとんど同じです。オフサイドやレッドカード、ファールもありです。また今回のコンペではアクティブユーザー(ボールに一番近い)1人のみが操作可能という仕様でした。FIFAウイイレとほぼ同じと考えてもらって大丈夫です。エージェントをkaggle上に提出すると毎日複数の敵と複数回対戦しその勝敗によって順位が変動する仕組みです。

ではゲーム上のエージェントをどのように学習するかというところですが、以下の3つで定義された環境を用いてエージェントを学習させていきます。

①試合の観測状態

②エージェントの行動

③エージェントの行動による報酬(見返り)

この3項目について以下で説明します。


①試合の観測状態(State)

Stateとしては生データとそこからの派生として3種類の表現方法が提供されています。

生データ

試合に関する全ての状態を含む生データ。情報量は一番多い。

(1)Floats

選手やボールの(x,y)座標、アクティブプレイヤーの番号など、ゲーム情報を115次元のベクトル表現として扱う表現方法。

(2)SMM(Simplified spacial minimap)

 72 * 96のミニマップ画像(4ch)を用いた表現方法。シンプルな画像として扱える。

  • 1ch目が自分のチームの選手の位置
  • 2ch目が敵チームの選手の位置
  • 3ch目がボールの位置
  • 4ch目が操作可能なアクティブプレイヤーの位置 

以下SMMの画像例

f:id:kutohonn:20201201215927p:plain

 

(3)Pixels

下記のような実際のゲーム画像を 72 * 96にダウンスケールした表現方法。グレースケールバージョンも用意されている。情報としてはリッチだがノイズも多め。

f:id:kutohonn:20201201214927p:plain


②エージェントの行動(Actions)

方向キーやパス、シュートなど全部で19の行動が設定されている。詳細は以下。

github.com


③エージェントの行動による報酬(Reward)

Google Research Football環境には予め2つの報酬関数が定義されている。

(1)SCORING報酬:

得失点に対応する報酬

得点したら+1で失点したら-1 というシンプルなもの。試合のスコアに対応する。

(2)CHECKPOINTS報酬:

相手フィールド半分を10個のエリアに分割しゴールに近づくにつれて+0.1ずつ与えられる即時報酬のようなもの。 なお敵が自分のフィールドに攻めてきてもCHECKPOINTによるマイナス報酬は発生しない。 上限が設定されており1試合で最大+1.0まで。

またgymのwrapperを使ってカスタム報酬を作成することも可能。

 

 

 学習のアプローチ

本コンペではエージェントの学習方法として以下の3つのアプローチが取られていました。この記事で主に扱うのは強化学習ですがその他の2つの手法もここで簡単に触れておきます。

ルールベースアプローチ

コーディングによってエージェントの行動を決める方法です。人の手でルールを決めてagentの実装を行うため、エージェントの学習コストがなく、またドメイン知識を最も生かすことのできる方法であるため、コンペ開催直後から中盤にかけて多くの方が取り組んでいたアプローチだったのではないかと思います。

www.kaggle.com

機械学習アプローチ

こちらはコンペ中盤から目立ち始めたアプローチです。どういうものかというと過去の対戦データを使ってエージェントを学習させるというものです。本コンペは作成したエージェントをkaggle上にアップロードして複数の相手と毎日対戦させることで暫定順位を出す設計になっており、各エージェントの対戦結果はデータとして取得できるものになっていました。

そこで機械学習モデルで過去のエージェントの行動を教師ラベルとして学習(模倣)するアプローチが中盤から終盤にかけて取り組まれていたと思います。学習器としてはRandom ForestやGBDTなどの決定木ベースのアルゴリズムが私が観測した範囲では使用されていました。コンペ上位の方も使用されている非常に有効なアプローチであったようです。

 

s_toppo's solution(6th)

www.kaggle.com

強化学習アプローチ

こちらが今回の本題である強化学習アプローチです。機械学習アプローチと異なる点としては教師ラベルは利用せず、試合の結果(報酬)をもとにして各状態でのとるべき行動をエージェント自身が学習していく方法になります。コンペ上位の方で強化学習アプローチを取られている方が複数いたことからも本コンペでは強化学習アプローチは有効な手法であったと思われます。

 


取り組みの概要

以下、行った取組みの概要を示します。

  • 強化学習アルゴリズムとしてSEED RL(V-trace)による分散型強化学習を採用
  • 観測状態としてSMM(ミニマップ)を使用
  • 学習序盤で対戦botの難易度を段階的に増加
  • 学習序盤でCHECKPOINT報酬の減衰
  • 複数の対戦相手による過学習の抑制
  • カスタム報酬はノイズになりうるのであえて加えない
  • 1620M steps(541,666試合)の学習

いくつかのポイントについて説明します。

SEED RL

今回はSEED RLという計算資源の拡張性に優れた分散型強化学習フレームワークを使用しました。こちらはGoogle Researchから2020年に出されたものでSEED RL内でIMPALA(V-trace),R2D2,soft-Actor-Criticの3つの強化学習アルゴリズムが利用可能となっています。本論文内でGoogle Research Football環境での性能評価を行っており、実装及びGCPによる計算環境も整っていたことからSEED RL内で扱えるIMPALA(V-trace)を強化学習アルゴリズムとして採用しました。また本論文では3つの観測状態(floats, SMM, Pixels)の比較も行なっており、SMMが良い結果を示していたことからSMMを利用することにしました。

github.com

序盤での対戦相手の難易度(difficulty)増加

Google Research Football環境にはあらかじめBot(NPCのようなもの)が用意されており、difficultyを変更することが可能であったため、easyモードに該当するdifficuly=0.05から最難易度であるdifficuly=1.0まで徐々に上げていきました。difficultyは学習時の平均得失点差が1を越した(勝ち越した)時に0.05ずつ上げていく方式を取りました。

CHECKPOINT報酬の減衰

CHECKPOINT報酬はエージェントをうまくゴール近くに誘導するために良い効果を発揮します。これは学習序盤では効果的なのですが、学習が進行してくると得点することよりもゴールの方向へ進むことに意味を見出してしまい、学習のノイズになってしまうことが考えられました。そこでCHECKPOINTの割合を徐々に減らしていき、最終的には0にすることで、試合に勝つことのみに報酬(SCORING報酬のみ)を与えるようにしました。CHECKPOINTの減衰はdifficultyの増加に合わせて行い、difficultyが最高難易度に達した時にCHECKPOINTがゼロになるように設定しました。

 

計算資源

GCPのAI Platformを利用しました。

SEED RLはactor(試合をplayしてデータを取得する役割) とlearner(データから学習をする役割)に分かれており、GPUがlearner、CPUがactorに対応します。

  • GPU:  p100*1
  • CPU: n1-standard-4*96(合計384個のvCPU)

合計534体のactorを同時に並列で試合をさせて学習を行いました。

 

学習過程

今回作成したエージェントは最終的には1620Msteps(約54万試合分)の過程を経て学習されました。ここではその過程について説明していきます。

こちらは1620Mstepsまでの1試合ごとに返される報酬結果を示したものです。縦軸が対戦相手との得失点差を示していると考えてもらえればいいです。

f:id:kutohonn:20201207173618p:plain

0M ~ 160M steps

この期間は対戦botの難易度の増加とCHECKPOINT報酬の減衰を行なっていました。

LBスコア*: 700 

(*kaggle上で公開されているLeader Boardのスコアのこと600が平均値)

160M ~ 900M steps

160Mあたりから性能が急激に落ちてしまいました。難易度とCHECKPOINT減衰のスピードが早すぎたのかもしれません。しかし200Mあたりから右肩上がりで学習していき、bot相手には圧勝できるようになりました。試合の様子はこんな感じです。

drive.google.com

drive.google.com

ところがkaggle上でのスコアは思うように伸びませんでした。

LBスコア: 800~900

 

900M~1400M steps

対戦相手のBotには大差で勝ち越せるようになっているにも関わらず、LB Scoreが思うように上がらないことからBot過学習していると考え、対戦相手を追加することにしました。相手はkaggle上で公開されておりLBスコアが1100(当時上位5-10%の順位)とかなり高いルールベースのBotを利用しました。学習比率はBot : ルールベースBot=1 : 3の比で学習させました。

これにより停滞していたLBスコアの向上が見られました。

LB スコア:900-1100

1400M - 1620M steps

上記Botを追加し手元ではどちらもBotにも勝ち越せるようになりましたがLBスコアは期待以上には伸びませんでした。そこで新たに新たに機械学習ベースのBotを対戦相手として追加し学習させました。ここで時間(資金)切れとなり終了となりました。

LB Score 1100-1200

 

最終のエージェントの試合の様子

drive.google.com

 

終結

本日(12/12)コンペが終了し、最終成績は以下のようになりました。

最終LBスコア 1174 (58th /1141)

f:id:kutohonn:20201212091410j:plain

ギリギリの銅メダル。。金銀目指していたので悔しさはもちろんありますが上位の方の手法を見る限り力不足だったなと思いました。

 

私たちの解法をまとめたDiscussion

www.kaggle.com


難しかった点

① 学習の推移が予想できない

 特に学習序盤ですが、推移の変動が大きく、学習がどのように進むのか予想できませんでした。今回はひとまずそれなりの時間放置して経過を見守るというスタンスを取りましたが、③で書いているようにコストがかなりかかるのでどの時点で学習を止めるべきかの判断が難しいように感じました。

② 思っていた以上に過学習する

 最初はBot相手に強くなれば性能・スコアも右肩上がりで上がっていくと思っていたのですが今思うと過学習気味だったので、序盤から複数の相手と戦わせたり自己対戦をしていたらもう少しスコアが上がったのかなと思いました。

③ 学習コストが大きい

1620M stepsの学習を行うのにGCPのAI platformを利用しました。

この学習にざっくり20万円近くのコストがかかっています

私たちは本コンペ用に抽選で配布された1人$1000(約10万4千円分)のGCPクーポンを運よく2人ともゲットすることができたので計算資金はそれなりにあったと言えますが、それでも強化学習にはかなりの学習コストを要することがわかりました。

④試行錯誤の数が限られる

強化学習は答えを与えない分データ効率が悪いので学習するまでに多くの時間を要します。そのためいろいろ試して比較するということがなかなかできず、どのように学習を進めていくべきかの判断が難しい印象を受けました。行動の探索空間を限定したり補助的な報酬を与えるなどのデータ効率を上げる工夫、試行の良し悪しをどの時点のどの基準で判断するかの意思決定が重要のように感じました。

 

試したけどうまくいかなかったこと

  • アンサンブル学習

機械学習のように複数のRL エージェントを用意し選択行動を投票で決めるアンサンブルを行いましたがうまくいきませんでした。

  • カスタムシナリオによる部分学習

通常の試合だけではなくゴール前での得失点に関わる状況に限定したシナリオを作成し学習をすることに取り組みましたが、単一で行うとこれまでの戦略を忘却したかのように全く異なる行動をとるようになり、それを避けようと通常の試合と混ぜながら行なっても性能の向上が見込めなかったのでボツとなりました。

 

上位解法の良さそうな取り組み

  • 自己対戦学習

3位解法6位解法はどちらも自己対戦を取り入れた自己対戦学習を行なっていました。自己対戦の場合、データ効率が倍になりかつ過学習にもある程度対策ができていたようで一つの良いアプローチだったのかなと思いました。ちなみに上位の方は自己対戦とは別に複数の敵との対戦も同時に行なっており過学習対策をしっかりされている印象でした。

  • 階層的な行動空間

行動の選択肢はidle状態を除くと全部で17こありますが、全てが重要というわけではありません。6位解法では8つの方向移動行動を1つの移動行動としてまとめて、移動行動が選ばれた時にのみ方向を選択するという取り組みを行なっていました。これにより探索する行動空間を小さくすることができるのでデータ効率を上げる狙いがあったようです。非常に面白い取り組みだと感じました。

  • 模倣学習

本コンペでは機械学習アプローチ(教師あり)も有効であったことから模倣学習も有効だったのではないかと考えました。模倣学習によりエージェントをある程度高いレベルまで学習させた後、自己対戦による強化学習などで学習を継続すれば効率よく、比較的少ないコストで学習ができたのではないかと考えました。

 

感想

今年の8月くらいから、強化学習の勉強を本格的にしたいと思い論文や書籍などで勉強していたところに、このような面白いコンペが開催されたので私としては非常に幸運でした。書籍では知っていた強化学習を行う上での難しさ(報酬設計、データ効率、過学習)を身をもって学ぶことができました。難しさを感じるコンペであったものの、学習初期はボールを素通りしたりオウンゴールをしていたエージェントがちゃんとパスをして相手のゴールに向かっていく姿にはちょっぴり感動しました。

 

本コンペを通して改めて強化学習は非常に興味深い分野だなと思ったので今後も勉強していき、このような面白いコンペがあったら積極的に参加していきたいと思います。

ここまで読んでいただき、ありがとうございました。

  

その他参考文献

Google Research Footballの論文

arxiv.org

Google Research Football環境で強化学習に取り組んだかなり質の高いブログ

sites.google.com

 

効果検証入門が良かったのでまとめてみた③

はじめに

 効果検証入門のまとめの続きです。前回はセレクションバイアスを軽減する基本的な方法である回帰分析についてまとめました。本記事ではセレクションバイアスを軽減する手段の1つである傾向スコアを用いた分析について説明していきます。 kutohonn.hatenablog.com

参考文献

効果検証入門


傾向スコア分析の仕組み

 傾向スコアとは各サンプルにおいて介入が行われる確率のことです。傾向スコアを用いた分析は、介入が行われた仕組みに着目しています。介入グループと非介入グループのデータの性質を近くする操作を行うことで、セレクションバイアスを軽減します。この操作は以下の仮定に基づいて行われます。 以下は回帰分析の仮定(CIA)と傾向スコア分析の仮定の比較をしたものです。

f:id:kutohonn:20200424183848j:plain

 セレクションバイアスを取り除くためには、目的関数Yに対して介入Zが独立している必要があることを前回説明しました。この独立性を満たすために回帰分析で考えられている仮定はCIAというものでした。これは共変量の値が同一のユーザの中で、目的関数Yに対して介入変数Zが独立しているという仮定です。一方、傾向スコアでは、傾向スコアP(X_i)が同一となるサンプルの中で、目的関数Yに対して介入変数Zが独立しているという仮定です。つまり回帰分析との違いは、介入が目的関数に対して独立であることを、共変量で条件づけるのではなく、傾向スコアで条件付ける点にあります。そのためには各データの傾向スコアP(X)を求めてあげる必要があります。


傾向スコアの推定方法

 傾向スコアを用いた分析では、上記で説明したように傾向スコアP(X)を求める必要があります。しかしながら傾向スコアP(X)を直接観測できる状況はほとんどないため、何かしらのモデルを用いて手持ちのデータから傾向スコアP(X)を推定する必要があります。この時多くの場合でロジスティック回帰が用いられます。(共変量数が多い場合は、勾配ブースティングも有効のようです。)ロジスティック回帰は介入変数Zの値を目的変数とし、以下のような回帰式となっています。

Z_i =\sigma(\beta X_i + u_i)
\sigma(x) = 1 / (1 + e^{-x})
\hat{P}(X_i) = \hat{Z}_i = \sigma(\hat{\beta}\ X_i)
u_i:誤差項, \beta:推定されるパラメータ, \sigma(X):シグモイド関数

 ここでのロジスティック回帰は、仮に変数Xと、0か1の値をとるYを与えると、Y=1となる確率を推定するモデルを得る方法と言えます。 ロジスティック回帰によって得られたモデルを利用することで傾向スコアの推定値\hat{P}(X_i)を得ることができます。ここで重要なのは傾向スコアの推定値\hat{P}(X_i)のみでモデルのパラメータ自体は特に分析する必要はありません。機械学習をやっている方だとモデルの妥当性や精度を考慮する必要があるように感じるかもしれません。しかしながら効果の分析において、傾向スコアの推定に用いたモデルの解釈は質の保証にはなりえません。そのため傾向スコアの推定を行ったモデルやそのパラメータに関しては特に解釈を行う必要はありません。


傾向スコアを用いた分析方法

ここでは傾向スコアを用いた2つの分析手法について説明します。

傾向スコアマッチング

 こちらはとてもシンプルなアイデアです。傾向スコアマッチングでは、介入ありグループから取り出したサンプルと傾向スコアの値が近いものを介入なしグループから取り出してペアにします。そしてペアの中で目的変数の差を算出し、これを全てのデータで行いその平均を効果の推定値とします。先ほど説明したように傾向スコアが同じ値を持つサンプルの中では介入変数ZY^{(0)}と独立して決定されていると考えられます。そのためこの中でグループ間の比較を行ってもセレクションバイアスの影響を受けることなく効果を推定することができます。

f:id:kutohonn:20200424184312j:plain

  マッチングにおいては傾向スコアごとにグループ間を比較し、それらの結果をグループに属しているサンプルの数を重みとした重み付きの平均を算出することで、効果を推定しています。マッチングによって推定される効果は以下のようになります。

\hat{\tau}_{match} = E\{E[Y|P(X), Z=1] - E[Y|P(X),Z=0]|Z=1\}

これは介入を受けたサンプルにおける介入効果の期待値で、ATT(Averaged Treatment effect on Treated)と呼ばれます。ATTは介入を受けたサンプルにおける効果なので、ATTの推定を行った値は平均的な効果を推定した値ATE(Average Treatment Effect)と結果が異なる場合があります。  

マッチングは実際の計算時間が長くなってしまう傾向にあります。また傾向スコアのモデルに含まれる変数が多くなると、傾向スコアが完全に一致しないため、傾向スコアの値が似ているサンプルをペアにする必要があります。


逆確率重み付き推定(IPW)

 逆確率重み付き推定(Inverse Probability Weighting;IPW)では、傾向スコアをサンプルの重みとして利用し、データ全体の介入を受けた場合の結果の期待値E[Y^{(1)}] と、介入を受けなかった場合の結果の期待値(E[Y^{(0)}])を推定します。そしてその後にこれらの差分を取ることで効果を推定します。 式を用いて説明します。

\bar{Y}^{(1)} = \sum_{i=1}^{N}{Z_i Y_i} / \sum_{i=1}^{N}{Z_i}

\bar{Y}^{(0)} = \sum_{i=1}^{N}{(1-Z_i) Y_i} / \sum_{i=1}^{N}{(1-Z_i)}

上記は単純にZ=1の時のY(1)、Z=0の時のY(0)の平均の推定値を示しています。これらの差から効果の推定を行うとセレクションバイアスの影響を受けてしまいます。そこで傾向スコアの推定値\hat{P}(X_i)を用いて以下のように変形します。

\bar{Y}^{(1)} = \sum_{i=1}^{N}\frac{Z_i Y_i}{\hat{P}(X_i)}/\sum_{i=1}^{N}\frac{Z_i}{\hat{P}(X_i)}

\bar{Y}^{(0)} = \sum_{i=1}^{N}\frac{(1-Z_i) Y_i}{1-\hat{P}(X_i)}/\sum_{i=1}^{N}\frac{(1-Z_i)}{1-\hat{P}(X_i)}

こちらがIPWで用いる重み付きの平均です。傾向スコアの推定値\hat{P}(X_i)の逆数を重みとして平均を取っています。詳細は省きますがこうすることによりセレクションバイアスの影響を低減することができます。あとはこれらの差分を取ることで効果を推定することができます。

\hat{\tau}_{IPW} = \bar{Y}^{(1)} - \bar{Y}^{(0)}

IPWでは先にE[Y^{(1)}]とE[Y^{(0)}]を推定し,その推定結果の差分を取ることで効果を推定しました。これはつまりデータ全体での平均的な効果を推定したことになります。これはATEの推定を行っていることになり、介入の効果がサンプルによって異なるような場合でもそれを考慮した平均的な効果が推定されることになります。


より良い傾向スコアとは

傾向スコアは回帰分析と同様、どのように推定してもセレクションバイアスを消し去ってくれるような便利な道具ではありません。 ここではより良い傾向スコアを得るためには何をする必要があるかについて説明します。 傾向スコアを用いた分析では、マッチングもしくは重み付けを行ったあとのデータにおいて、共変量のバランスが取れているかが重要です。


傾向スコアと回帰分析の比較

介入の効果の分析において、傾向スコアは共変量の影響を取り除くという点で回帰分析と役割はほぼ同じです。ではどのように使い分ける必要があるのでしょうか。以下に回帰分析と傾向スコアのメリットデメリットを整理してみます。

回帰分析 傾向スコア
メリット 手軽で取り組みやすい
モデリングが正しくできる場合、効果の標準誤差が小さくなる(検証結果の質が上がる)
Yに対するモデリングを行わなくて良い
情報を入手しやすいZの決定方法に関する調査を行うだけで良い
デメリット 目的関数と共変量の関係について入念にモデリングを行わないといけない マッチングは計算に時間を要するため大量のデータには不向き

よってYの値がどのような仕組みで決まるかに関して豊富な情報を持つ場合には、回帰分析を行うメリットが大きく、Yに関する情報があまりない場合には傾向スコアを用いるほうが望ましいです。


おわりに

今回は傾向スコアについてまとめてみました。傾向スコアのアイデアはシンプルながら個人的に非常に面白いなと思いました。

【Kaggleで役立つ】計算過程およびエラーをslackに通知する

f:id:kutohonn:20200423223709j:plain


はじめに

KaggleのNotebookやGoogle Colab, GCPなどのクラウド環境で計算を回していると、途中でエラーが発生していて計算が止まってしまったり, 処理がどう進んでいるかが気になってしまうことが多々あったので, 欲しい情報をslackに通知するようにしてみました。

f:id:kutohonn:20200423214153p:plain

この記事ではプログラムの処理過程をslackに通知するための方法について説明していきます。


参考にしたサイト

tech.bita.jp amalog.hateblo.jp


slackの設定

まずはslackの設定を行なっていきます。slackに登録していない方はこちらからサインインしてください。 それでは初めにワークスペースを作成します。私は1人で使う個人開発用のワークスペースを作成しました。 その後作成したワークスペースに入り、以下の図に示すように左側のappというタグを選択し、その後中央の画面で『Incoming Webhook』というアプリを検索してslackに追加します。

f:id:kutohonn:20200423220229p:plain


追加したら『Incoming Webhook』をクリックするとweb上の設定画面にリンクします。 設定画面で大事なのは主にこちらの2項目です。

f:id:kutohonn:20200423221713p:plain

1つ目に通知を送りたいチャンネルを選択します。もともとあるgenralなどでもいいですし新しいチャンネルを作成しそちらに通知を送るようにしてもいいです。 2つ目にWebhook URLのコピーです。Webhook URLというものが割り当てられますのでそれをコピーしておいてください。後ほど使います。そのほかの設定はお好みで大丈夫です。 これでslackでの設定は完了です。


関数の定義

次にslackに通知を送るためのコードを書いていきます。 動かしたいプログラムの中に以下のコードを貼り付けます。 コード中の<自分のWEBHOOK_URL>の部分は先ほど取得したWebhook URLに書き換えてください。

import json
import requests

# 任意のメッセージを通知する関数
def send_slack_message_notification(message):
    webhook_url = '<自分のWEBHOOK_URL>'  
    data = json.dumps({'text': message})
    headers = {'content-type': 'application/json'}
    requests.post(webhook_url, data=data, headers=headers)

# errorを通知する関数
def send_slack_error_notification(message):
    webhook_url = '<自分のWEBHOOK_URL>' 
    # no_entry_signは行き止まりの絵文字を出力
    data = json.dumps({"text":":no_entry_sign:" + message})  
    headers = {'content-type': 'application/json'}
    requests.post(webhook_url, data=data, headers=headers)

これで準備は整いました。次に実際の使い方を説明していきます。

使い方

まず任意のメッセージを通知する方法ですが以下のように先ほど定義した関数の引数に通知したい文章を与えることで通知することが可能です。 str型であれば基本、なんでも大丈夫です。

message1 = "[START] training phase"

val_score = 0.89
message2 = "valid score:{}".format(val_score)

send_slack_message_notification(message1)
send_slack_message_notification(message2)

f:id:kutohonn:20200423213002p:plain

通知結果はこのようになりました。kaggleの場合だと現在の処理フェーズであったり、算出したスコアであったりを通知すると便利そうです。


次にerrorが起きたときに通知する方法です。
今回はmainという関数内でエラーがあった場合を想定して説明していきます。ここは別に関数でなくても大丈夫です。 try構文を用いて例外処理を記述し、例外(エラー)が発生した場合にexcept以下を実行します。 通知されるのはsend_slack_error_notificationの引数に与えられた文章です。今回はtraceback.format_exc()によってerror内容を通知しています。

import traceback
def main():
    prin("試しにわざとエラーを出してみます。")

try:
    main()
except:
    send_slack_error_notification("[ERROR]\n" + traceback.format_exc())  

f:id:kutohonn:20200423213854p:plain

このような形でエラー内容を通知することができました。「クラウド上でプログラムを回してたらいつの間にかerrorで止まっていた、」なんてことはよくあるので、そういったときに役立ちそうです。例外処理を部分部分でいちいち書くのは面倒なので、全ての処理をmainの関数に集約して、その関数に対して上記のように実行すると楽だと思います。


おわりに

kaggleなどの機械学習タスクでは、処理時間がかなり長いため処理がうまくいってるか気になってしまう人は多いと思います。なのでこのように通知を送ることによって、計算の過程やエラーを確認し効率よく計算を回していければと思います。