かえるのプログラミングブログ

プログラミングでつまずいたところとその解決策などを書いていきます。

単語 ID 列を長さでソートしてミニバッチ内で padding する。

こんばんは、kaerururu (@kaeru_nantoka) です。

今回は、kaggle meetup #6 での tks さんの発表にもありました、「 (batch 内で) batch 毎に padding する」の実装にプラスして ID列を長さでソートしたものを batch 内で padding できるようにした実装を公開しようと思います。

誤り、もっと良い実装等ございましたらご指摘いただけると幸いです。m( )m

何が嬉しいの?

ID 列の長さでソートしたものを dataloader に渡し、batch 内で padding すると 初めの方は、系列長 0 や 1, 2 などが渡ってくるので padding した後の長さが 1 や 2 になり無駄がなくなるので処理速度が爆速になります。

昨今の kaggle の NLP コンペ (NLPに限った話ではないですが..) では、学習は local でもやっても良いが推論は kaggle kernel 内で済ませる必要があるタイプが多く、いかに個別の model にかかる時間を節約し、規定の推論時間内に model を詰め込みアンサンブルするかが勝負の分かれ目になります。

デメリット

model の training 時には学習にかかる時間を大きく減らす効果が期待できます。一方で、以下の論文  

https://www.anlp.jp/proceedings/annual_meeting/2017/pdf_dir/A7-1.pdf

でも指摘されているように、長さの似た文章は似た情報を持っていることも考えられるため同じミニバッチに詰め込んで学習させることで model が偏った知識を学習してしまうことで精度が少し落ちてしまう恐れもあります。

実装

「 batch 毎に 0 padding して系列長を揃える 」までは、以下の kernel の実装を参考にしました。

https://www.kaggle.com/kunwar31/pytorch-pad-sequences-per-batch/notebook

あとは系列の長さで sort した x_train (sorted_x_train) とそれに対応する順番に並べ替えた y_train (sorted_y_train) を dataset に渡すだけで OK です。

sort した x_train と y_train を入手する関数を以下のように用意しました。

[[長さ], [x_train], [y_train]] のように配列を作り、1番目の配列(長さ)でソートしたのち、単体の配列に詰め直して返してます。

from operator import itemgetter

def get_sorted_list(lengths, x_train, y_train=None):
    tmp_list = []
    if y_train is not None:
        for l, m, n in zip(lengths, x_train, y_train):
            tmp_list.append([l, m, n])  
        tmp_list.sort(key=itemgetter(0))
        sorted_x_train = []
        sorted_y_train = []
        for i in tmp_list:
            sorted_x_train.append(i[1]) 
            sorted_y_train.append(i[2]) 
        return sorted_x_train, sorted_y_train
    else:
        for l, m in zip(lengths, x_train):
            tmp_list.append([l, m])  
        tmp_list.sort(key=itemgetter(0))
        sorted_x_train = []
        for i in tmp_list:
            sorted_x_train.append(i[1]) 
        return sorted_x_train

class TextDataset(data.Dataset):
    '''
    Simple Dataset
    '''
    def __init__(self,X,y=None):
        self.X = X
        self.y = y

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):
        if self.y is not None:
            return [self.X[idx],self.y[idx]]
        return self.X[idx]


class MyCollator(object):
    '''
    Yields a batch from a list of Items
    Args:
    test : Set True when using with test data loader. Defaults to False
    percentile : Trim sequences by this percentile
    '''
    def __init__(self,test=False,percentile=100):
        self.test = test
        self.percentile = percentile
    def __call__(self, batch):
        if not self.test:
            data = [item[0] for item in batch]
            target = [item[1] for item in batch]
        else:
            data = batch
        lens = [len(x) for x in data]
        max_len = np.percentile(lens,self.percentile)
        data = sequence.pad_sequences(data,maxlen=int(max_len))
        data = torch.tensor(data,dtype=torch.long).cuda()
        if not self.test:
            target = torch.tensor(target,dtype=torch.float32).cuda()
            return [data,target]
        return [data]
collate = MyCollator(percentile=100)

lengths = torch.from_numpy(np.array([len(x) for x in x_train]))

sorted_x_train, sorted_y_train = get_sorted_list(lengths, x_train, y_train_final)

train_dataset = TextDataset(sorted_x_train, sorted_y_train)

train_loader  = torch.utils.data.DataLoader(train_dataset, batch_size=512, shuffle=False, collate_fn=collate)

まとめ

Quora や jigsaw の solution を見ていて実装したいと思っていたのですが、日本語での解説記事など探しても見当たらなかったので公開しようと思った次第です。

(batch padding sort kaggle でググって公開カーネルにたどり着いた)

余力があれば、

1 ) 精度面でどれだけ悪化するのか

2 ) どれだけ早くなるのか

を比較実験した記事も書きたいなと思います。

ありがとうございました。

Kaggle Master になりました!

こんばんは、kaerururu (@kaeru_nantoka) です。

今回は、3人チームで参加した先日 Freesound Audio Tagging 2019 にて2枚目の金メダルを獲得し、Kaggle Master になることができたので私が kaggle (というか機械学習) を始めてから今までの 10ヵ月を振り返ってみようと思います。

最近 Kaggle 始めたけど titanic の次何すればええの? っていう質問を受けることも多くなったので、私の取り組みが少しでも参考になれば嬉しいです。


目次

・ Titanic, HomeCredit

・ 塩, Two Sigma

・ Quora

・ Petfinder

・ Freesound

・これから


2018年 8~9月 -- Kaggle に出会う, Titanic, HomeCredit --

  • できるようになったこと :
    1. kernel を folk して submission.csv を出力する,
    2. カラム同士をコネコネして新しい特徴を生み出して score の変化を楽しむ
    3. RandomForest
  • できなかったこと : テーブルの結合、Groupby

当時プログラミングをはじめて半年やそこらで周りにプログラミング友達が2, 3人しかいなかった私は Twitter をはじめました。 #プログラミング初心者さんと繋がりたい, や #100DaysOfCode といったハッシュタグもあり所謂プログラミングアカウントみたいなものがどっと増え始めた頃でした。

当時プログラミングアカウント界隈では、バズらせるのを目的に rubyonrails や Django のようなフレームワークで Webアプリを作り、Heroku に公開するのが流行っていました。私も例に漏れず、バズるアプリを作って一発当てようとネタを探している中で、Qiita で見つけた「AI(keras) で画像認識をする Webアプリ」(アイドルの画像分類機の作成) 記事を読み、これ面白いんじゃね?と見よう見まねで実装し、web にあげるのではオリジナリティがないので LINE BOT に仕立てて公開したところそこそこ反響がありました。

4値分類問題を設定し、Google の画像収集API でそれぞれのラベル 100ずつくらい集めました。この時、ディープラーニングで云々することの楽しさとデータを手動で集め、整形することの大変さの両方を知ることができました。 ディープラーニングにせっかく興味が湧いたものの、素人が予測モデルを作るのに必要なデータを自力で集めるのも面倒大変なところがあったので無料で大量のデータを扱えるサイトを探していたところ kaggle に出会いました。

そこでは 公開Kernal や Discussion というやその道の先人たちが知見を惜しまず公開しており、近くにディープラーニングを教えてくれる人などいない私が、独学するのに適した環境でした。

Titanic 問題を使って Kaggle の取り組み方を説明したサイト(日本語)などを参考に見よう見まねで取り組んでいきました。 また、当時アクティブだった HomeCredit コンペにも参戦してみました。 これは、金融機関の顧客データ(複数テーブル)を使い、借り入れ顧客がきちんと借金返してくれるか予測する問題でした。SQL なども使ったことがなかったので 複数テーブルにまたがった情報の取り扱いや、同じID の人が複数の情報を持っていたりと何が何だかわけがわからないまま終わりました。

Kaggle はわけがわかりませんでしたが #100DaysOfCode のタグをつけてその日の取り組みを呟くと優しい人がいいねくれたのでちょっとしたモチベーションになり続けることができました。


2018年 ~10月 -- 塩, Two Sigma --

  • できるようになったこと :

    1. ハイパーパラメータをいじって score の変化を楽しむ
    2. Keras の層を (わけもわからないまま) 追加する
    3. GCP をはじめて使う
    4. LightGBM を使う
    5. 複数テーブルの groupby --> concat
    6. 都度都度更新されるハイスコア Kernel と手元の folk してきた kernel との差分について考える
  • できなかったこと :

    1. Pretrained とか Finetune とか
    2. Predefined モデルの利用

取っ掛かりが Karas を用いた画像判別アプリの作成だったこともあり、 画像コンペに参加しようと引き続き塩コンペに参加しました。

適当な Kernel を folk (U-Net + Residual module)してきて最初は seed とか epoch数, train_test_split の比率などをいじったり最新のハイスコアカーネルとの差分についたり考えてはスコアの変化を楽しんでいました。 この頃には discussion も読むようになり、みんながやっている計算重くね? Kernal でできないぞ・・と思い立ち GCPで初のGPUインスタンスを立ててみたりしました。結局どんなインスタンス立てればいいかわからず、CPU 2core + NVIDIA TESLA P100 * 2 とかいうアホなインスタンスを立て、計算を回すも一向に計算が終わらず、無料$300クーポンを溶かすなどしてました。 discussion に出てくる ResNet34 がどうとか理解できないまま、これ以上は計算機が足らん・・となり撤退。

次は、元証券勤務ということで同じ期間に始まっていた Two Sigma コンペ(株価予測コンペ)に参加しました。 新聞記事のテキスト情報と直近10年分の株価情報という2つのテーブルが与えられていました。この時の公開カーネルをみて groupby や concat を扱えるようになりました。金融のドメインが多少あったので移動平均線ゴールデンラインといったドメイン由来の特徴量を作成し実装して LGBM に投げるなどして初のPublic銀圏に到達するなど(*1month to go) Twitter に呟いてはいいねがもらえることが楽しくてやって行きました。 リークやげきつよカーネルの登場で 2度LBが崩壊し、モチベーションが保てなくなり撤退。ただ基本的なことは結構身についた気がします。(気がする)


2018年 ~12月 -- PLAsTiCC コンペ --

  • できるようになったこと :

    1. ソロ銅メダルを入手!
    2. 巨大データを扱う (20GB)
    3. wikipedia で得た知識を特徴量にする
    4. ライブラリの実装を見に行って手元でそれっぽく再現実装する
    5. discussion の「情報」をコードにする
  • できなかったこと : GCP で適切なインスタンスを立てる

Twitter で色々な kaggler をフォローするようになった頃、TL の強強な方々が参加されるという PLAsTicc コンペに私も参加してみることにしました。この頃の私は 「LightGBM に何かしらの特徴量を渡して出てきた予測値を to_csv して submit する」ことには慣れていたのでひたすら特徴量のアイデアをひねり出すことに執心しました。

そのため discussion で皆が使っているらしいライブラリ (tsfresh) を Kernel で利用しようとして落ちて使えなかった時はドキュメントを見に行って使えそうな関数を手元で再現してみたり、wikipedia やよくわかる天体観測みたいな web ページみては特徴を作っていました。

これがなかなか面白く、特徴量を思いつくたびに LB を更新し、一時は銀圏まで行きました。 結局は public で80位くらいで Finish し、shake down の洗礼を受け Private 103位のなんとか銅メダル獲得で終えました。


2019年 ~2月 -- Quora コンペ --

  • できるようになったこと :

    1. ソロ銀メダルを入手!
    2. 初の NLP
    3. 初の PyTorch
    4. Kaggle Expert になった
    5. 過去類似コンペを参考に特徴量を生成
  • できなかったこと :

    1. 高速化等の solution にある工夫
    2. NN レベルでの工夫

やったことのない領域にチャレンジしてみようということで自然言語処理に挑戦する目的で Quora コンペに参加しました。

最終的にはソロ銀メダルを獲得することができましたが、今思うとげきつよカーネルに過去類似コンペでやっていた text特徴の追加と NG辞書の拡充といったくらいの変更しかできなかったこと、評価指標が f1 score というちょっとした閾値の違いで差が出るものであったことこともあり運の要素が強かったなという所感ですが、何より自力で Expert にまで慣れたのは嬉しかったです。


2019年 ~3月 -- Pet コンペ --

  • できるようになったこと :

    1. 初チーム参加、初入賞
    2. 初の Multi-modal コンペ
    3. PyTorch に慣れた
    4. Predefined model の利用

またまたやったことのないことにチャレンジしてみようということで、複数データソース(teble, text, image) かつ外部データありの Pet コンペに当初はソロで参加しました。

このことについては別のエントリでも書きましたが、ひとりで銅メダルけん上位くらいまで行ったタイミングでつよつよな kaggler のみなさまとチームを組ませていただくことになりチームとしてガンガンスコアを伸ばして行くことができました。

一方で個人の取り組みでいうと、自分が最初に伸ばしていた model を強化することと、チームメイトの一人が discussion でこんな情報出たと共有してくれたもののうち、優先順位の低いものを1つ1つ潰していくことの両輪でした。最終的な weight は 0ではないくらい (シングルスコアはかなり悪かったが、抜いたら抜いたでスコアが落ちる)ということでチーム準優勝という輝かしい実績に反して、個人の力の及ばなさを強く実感する出来事でした。

とはいうもののこの pet コンペを通して、つよつよ kaggler の取り組みや思考、コンペを進める上でのスケジュール感、そして何より、自分の取り組みに対してフィードバックがもらえたことが自分の成長を加速させました。Wodori (その時のチーム名)のみなさまには感謝しかないです。

この Pet コンペでの取り組みを通して PyTorch でいい感じにできるようになったことが今の仕事や、後述の音声コンペにとても活きました。


2019年 4~6月 -- 音声コンペ --

  • できるようになったこと :

    1. 金メダルを入手!
    2. Kaggle Master になった
    3. PyTorch かなり慣れた (個人比)
    4. 論文を元に augmentation を実装
    5. Predefined の model のソースをいじって実装

理論を強化しようと参加したとある勉強会でツイッターのアカウント名と顔が一致した某氏と意気投合し、一緒にコンペに参加することになりました。.wav のデータを扱うの初めてだし面白そうという理由で音声コンペを選びました。その実態は画像コンペで個人的には塩コンペのリベンジかつPyTorch で色々やれてよかったです。Shirogane (当時のチーム名) のみなさまありがとうございました。

画像コンペということもあり、よく耳にする 〇〇 Net を片っ端から実装してみたり、同時期にやっていて先に終わった壺コンペの solution をみながら試していない Network や augmentation, Layer を実装してみたりするなど自分の中にかなりの知見が貯まりました。一方でチームでの weight はというとパッとせず、総合 7位金メダルという素晴らしい結果とは裏腹に、正直なところ自分の中で少なからず悔しい想いも残りました。この気持ちとチームで得られた知見を糧に精進していくつもりです。


これから何するの

tier 的には Kaggle Master になることができました。しかし、その瞬間瞬間で Kaggle Master たる働き・貢献ができていたのか? と自問自答するとうーんというのが正直なところです。(Weight は少なかったかもですが、投入時間は負けず劣らずだったはずです・・) 従って、まずは真の Kaggle Master を目指して以下の内容に取り組んでいこうかなーと思っています。

  • コード資産の拡充、画像、NLP コンペで使うベースライン notebook の作成

  • NLP コンペの solution であった network を再現実装して GitHub に草を生やす

  • NLP コンペの solution の手法リストを作成する

  • NLP コンペは(できるだけ)ソロ参加する

  • discussion 投稿、BaseLine Kernel 投稿

今までかなり場当たり的にやっていたのですが、そろそろ自分のやり方のようなものを確立したいなと思っています。特に NLP のコンペではどのコンペでもやっているような共通の処理があったり, 画像コンペでは predefined model の使用して色々な augmentation を試して~ などやることの大筋が変わらないなど一度きれいにまとめておくと、あのコンペの何位の手法と試すぞ!と行った実験を高速に回せるようになったり、どのコンペに出ようかなと思った時のとりあえずの 1st sub がすぐに実装できるのは判断の上で重要だと思います。

またせっかく業務で自然言語処理に取り組んでいるので、NLP系のコンペはしばらくソロで参加して力をつけたいと思っています。

最後に tier 的には Master になったのでできる範囲でコミュニティへの貢献できるように努めていこうと思っています。kaggle コミュニティだけでなく kaggler-ja での発言も増やしていきたいなあ。


以上です、ありがとうございました。

Xonsh はじめました。

こんばんは、かえるるる(@kaeru_nantoka)です。今回は、興が乗ったのでみんな大好き某くし氏激推しの shell 'xonsh' のセットアップを行いました。控えめにいってサイコーです。

xonshrc は基本的に某ブログのコピペで問題ないと思いますが、私の環境では丸々コピペで ls 系統のコマンドが全部利用できなくなるという事故を経験したので ls の alias 部分をこう書き換えたらうまくいったよということを残しておきます。

ほぼコピペですが、私の xonshrc と zshrc は以下のリポジトリにおいています。

https://github.com/osuossu8/dotfiles


参考

https://vaaaaaanquish.hatenablog.com/entry/2018/06/22/194227


つまづいたところ

ls 関連の alias 部分です。 以下の設定のまま source ~/.xonshrc すると・・・

aliases['ls'] = "ls --color=auto"
aliases["l"] = "ls -l"
aliases["lf"] = "ls -f"
aliases["ld"] = "ls -d"
aliases["la"] = "ls -la"
aliases["ll"] = "ls -l"

このようなエラーが出て、無事 ls が利用できなくなりました。

ls: illegal option -- -
usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]

上の部分を以下のように書き換えて source ~/.xonshrc したら復活しました。やったぜ。

aliases["lt"] = "ls -ltr -G"
aliases["l"] = "ls -ltr -G"
aliases["la"] = "ls -la -G"
aliases["ll"] = "ls -l -G"
aliases["ls"] = "ls -G"

まとめ

・ 某くしさんのチュートリアル記事で xonsh のセットアップができた。

・ 色とか補完がいい感じでサイコーだった。

・ xonshrc だと環境によっては ls が使えなくなるのでソースを少しいじった。

以上です。ありがとうございました。

「 Sports Analyst Meetup #2 (#spoana)」に参加してきました。~ 安西先生 LTがしたいです。 ~

こんばんは、かえるるる(@kaeru_nantoka)です。 今日は、「 Sports Analyst Meetup #2 (#spoana)」に参加してきました。 こちらのイベントに参加(聴講)するにあたって、目標を1つ自分に課しました。その観点に沿って聞いてメモしたものの中から抜粋して、そのまとめを綴っていこうと思います。


目次

・どんなイベントなの?

・どんな課題感で望んだの?

・各発表について

・まとめ


1 どんなイベント?

スポーツデータを題材に 実務 / 研究 / 趣味 で分析をしている方々による発表 ・ LT を聞ける会。 #2 では、実務(プロチームの戦略立案に役立てるための分析)でスポーツデータにを分析している方 2名のロングトークとLT 10本という大満足の内容だった。

https://spoana.connpass.com/event/126625/


2 どんな課題感で望んだの?

「自分が LT をすることを想定して、どのようにデータを用意し、どのような流れでまとめているかを生の LT を聞くことで知る」

ことを目標に、相棒の iPad Pro 9.7 インチにメモを書きなぐりました。

このような課題を自分に課した経緯としては次の通りです。

自分のアウトプットの機会の1つとして、せっかく(機械学習)エンジニアをやっているわけなので、LT をやってみたいという兼ねてからの願望があった。

-> やってみよう。・・・そもそもデータがない。

-> データを集めよう。・・・そもそもデータってどうやって(方法・観点・サンプル数)集めるんだっけ???

-> \(o^)/

ということで他の方の生の発表を聞いてみたいと思いました。

以上の目標を達成するために、観点を、

( i ) 概要・手法

( i i ) データ取得方法

( i i i ) データ数

( i v ) 期間

( v ) 感想

に分けて、メモしていきました。。メモが追いつかなかったものなどは - (ハイフン)にしています。

その中から一部抜粋して、ご紹介します。


3 各発表について

LT #1 戦略シミュレーション分析 (バドミントン)

( i ) ショットをハイリスク・ローリスクに分け、各ショットから得られる得点の期待値を数式として、またそのショットの組み合わせを戦略として定義し、その条件下でシミュレーション。その結果からどの戦略が優位であるかを示した。

( i i ) なし

( i i i ) なし

( i v ) なし

( v ) データがなくてもここまでの発表ができるのか。と自分の視野の狭さを痛感。 問題設定とアプローチは、学部自体にやったゲーム理論や効用最大化問題に近いと感じた。 競技の特性、プラスの効用とマイナスの効用をどう定義するかなど難しさはあるがぜひ挑戦してみたいと思った。


LT #2 大相撲優勝決定巴戦に見る不合理な分布 (相撲)

( i ) 相撲の優勝決定戦という、実力が拮抗 (両者の勝率が 0.5 と仮定)している時の理論値と標本値の比較、適合度の検定(カイ自乗検定)を行い、実力・・拮抗してるんじゃなかったっけ...? (=八○長の存在)を数的根拠を以って示唆した。

( i i ) -

( i i i ) 142

( i v ) 1958年 (今の巴戦のルールが確立した年) ~ 2014年

( v ) 理系の出身の方なら当たり前の手法なのかもしれないが、個人的に理論分布と標本分布の差異を数的に示す手法は私の目標の観点から、とても参考になった。サンプル数も142と自力でアノテーションできる数だなと思った。


LT #3 高校セーリング部のための「データ活用ツール」制作秘話 (セーリング)

( i ) 高校ヨット部をデータ分析の力で強くしよう。しかし、航路、船の傾きなど気象条件やルール上の制約からデータ集めるのが大変 -> どうするか という内容。

( i i ) スマホで集める。

競技の特性上、センサーやコンパス、防水性など多機能かつ高性能な分析デバイスが必要 -> スマホでよくね?

( i i i ) 大変

( i v ) 大変

( v ) やりたい分析対象はあるのに、変動パラメータが多すぎてデータを集めるのに苦戦していた発表者の方。自分の欲しいデータを効率的に集めるためのスマホアプリの紹介もされててすごかったです。一方スマホをデータ収集ツールとして使えばいいじゃないという観点にはハッとさせられた。以前 u++ さんが、iPhone のヘルスケアツールから歩行データなどを xml で取り出せるみたいな記事を書かれていたが、気づいていないだけでそういった魅力的な生データはその辺りに落ちているのかもしれない。


LT #5 富士山登山競争を定量的に評価する (登山)

( i ) 富士山登山競争という競技のつらさを構成する要素を数式で定義。そして、安静時やフルマラソン完走時、高尾山登山時のつらさも同じ式で表して定量的に評価した。

( i i ) 富士山登山競争の過去競争タイムデータから、区間ごとの距離・高低差・平均タイム を運動の消費カロリー(METS)に換算して使用

( i i i ) 1レース分?

( i v ) 1レース分?

( v ) フェルミ推定を彷彿とさせる手法だった。オチも用意されておりプレゼンって感じだった。


LT #7 ボール保持力・奪取力マップから見るロシアW杯2018 (サッカー)

( i ) ボール保持力・奪取力を可視化、国や選手の特性をうまくプロットしていた。

( i i ) Statsbomb 社がオープン化しているデータセット (.json)

Competition : FIFA World Cup 2018

Event : "Pass", "Dribble", "Miscontroll", "Duel"

Location : PosX, PosY

Output : Success, Failure

( i i i ) -

( i v ) FIFA World Cup 2018

( v ) ゲームの視聴者として経験的に把握していた、国や選手の特性を実際の試合データをプロットすることで可視化できており凄かった。(語彙)


4 まとめ

本日 spoana に参加してきた。

「LTをやってみたい。だが何をどんな観点で発表すればいいのか、そもそもデータってどうやって収集するのかわからない」という自分の中にある課題に対し、「その手がかりになる情報を生の発表の中から持ち帰る」という目標を課した。

結果、ぼんやりと聞くより多くの情報をインプットできた(気がする。)し、 自分が LT をするまでに何をすべきか道すじがクリアになった(気がする。)

ぜひ、自分も LT をして良質なアウトプットをする習慣をつけたい。

以上です。ありがとうございました。

PetFinder.my Adoption Prediction で準優勝しました!

こんばんは、kaerururu (@kaeru_nantoka) です。

今回は、5人チームで参加した Kaggle の通称ペットコンペで準優勝し、金メダルを無事獲得できたのでそのポエム簡単な経緯と役割分担のような話を綴っていきます:)

技術的な話(解法など)は、kaggle の discussion に

Summary by upura

kaeru part description

などなど追記されていくと思いますので、そちらをご参照ください。


経緯

きっかけは「かえるさんの転職活動成功のお祝いをしよう」と u++ さんがお声かけしてくださったことでした。

他に誰か誘おうとなったときに kaggle の meet up で ynktk さんと3人でお話ししたことを思い出し、お声かけしたところ OK. すぐに日程調整し、u++ さんが予約してくださった焼き鳥居酒屋「をどり」で会食。

Kaggle のイベントでお会いしたのがきっかけなのでもちろん話題は kaggle .

その時私はソロで参加しており、銅メダル上位くらいの順位でした。

「かえるさんは今何のコンペに参加しているのですか?」

この質問に対し、ペットコンペの簡単な概要(table, image, text を用いたマルチモーダルなコンペであること)、チームで参加してみたいのでぜひやりましょう!という旨をお伝えしました。

「え〜 0 sub だけどいいんですか?」と言っていたが、彼らとチームを組んだら絶対に楽しいと思ったのでそのまま鋭意勧誘、その場でスマホから invite してそのまま accept してもらいました笑

チーム名は、飲み会の会場の名前からとって「Wodori」。簡単にはわからないだろうし、なんか響きと綴りが気に入りました。のちに綴りが Wodori ではなく Odori であることに気づく・・笑

二人の加入で、銀圏まで上昇。当時はお二方とも何やかんや忙しい&キャッチアップの時間が欲しいということで、パッと思いつくアドバイスをいただきながら kaeru が実装するというようなスタイルだった。これで銀と銅を行ったり来たりするくらいで落ち着いた。ちょうど銀圏にいる時、東京大学の入学式に関するツイートを発見し、入学式...新歓!! ということでツイッターでふざけて Wodori チームメンバー募集の旨を呟いた。

何と反応があった。初期に参加していたが 1ヶ月ほどペットコンペの戦線から離れていた takuoko さんだった。

「takuoko さん・・・?!」顔パスだった。 4名になった Wodori は takuoko さんにチーム名の由来を伝えることなく邁進。忙しかったお二方も徐々に(というか急激に)コミット量が増えた。

結果4人でアンサンブル ( kaeru + ynktk takuoko u++) を駆使し金圏手前までいった。

もうひと伸び・・!と思っていたところに開始1週間ほどで金圏手前まで駆け上ってきたペンギンアカウントの gege さんが現れた。u++ さんにより、gege さんのチームマージに成功。

何と彼はシングルで金圏手前まで来ていた。

gege さんのソースをマージし、試行錯誤する中で金圏に到達。

そのまま、金圏で finish.

コンペ期間中は、夜中まで slack が動いているなどそれはそれは楽しい時間だった。

わかったこと

  • 当初 0 sub であってもチームマージしてデータを見るとがっつりやっちゃうのが kaggler だった。

  • チーム戦はめちゃくちゃ勉強になるし、楽しい。

  • チームメイトに迷惑をかけられないといういい緊張感がある。


役割分担

  • kaerururu リーダー、自分のパートをひたすら改善, 手が回らないところを拾う
  • u++ 司令塔, discussion追う, 類似過去コンペの検証、external特徴作成, データクレンジング, u++特徴量作成, catboost検証, 猫カフェ
  • ynktk 提出コードの大枠作成, NN 検証 postprocess、高速化、Stacking
  • takuoko ソースコードマージ、NN実装、LGBM
  • gege gege part の改善, 実務でのデータ分析経験を活かしたinsight で作成した特徴作成

上で書いたのはほんの一例ですが、いい具合に得意分野や使える時間のバランスが取れていたと思います。


ソースコードの管理など

ペットコンペは、kernel 限定コンペという kaggle 上で提供されている実行環境 ( = Kernel ) で書かれたコードにより出力された提出ファイルしか提出できないコンペでした。

従来のコンペでは、 各々がローカル環境やクラウド環境など各々環境を構築し、それぞれが計算し、 npy, feather... などの形式で保存した特徴量ファイルを融通し合い、誰か一人がそれらの特徴を load して model の計算を行うなどしてチーム運営がなされていたと聞きます。(私自身チーム戦が初めてだったので伝聞口調です。)

一方、カーネルコンペでは、特徴量の生成〜モデリング(アンサンブル)〜 postprocess までを一気通貫で行う(しかもGPUカーネルの実行時間制限の2h に収めないといけない)必要があり従来のチーム戦に慣れていたチームメイトは勝手が違うために戸惑っていたようです。

また notebook 形式で書かれた kernel は実行単位でセルが分かれていることもあり、まとまりがない書き方をしても気にならないためソースコードマージが大変だったとチームメイトの takuoko さんは言っていました。

結果的に、kaeru (notebook), gegeさん(noteboook), ynktk & takuoko & u++ 諸氏 (script) で途中まで各々進め、マージするタイミングでとてもとても見やすい ynktk さんの script ( with 文で実行単位ごとに区切るような形で記述 )をベースに takuoko さんが鋭意マージしてくださり、3000~4000行の大作になりました。

それ以降は、with 文で区切られた自分のパートの中を書き換え --> 自分のモデルのスコアを確認 --> 良さげだったら、全体で回してサブミット する流れでやっていきました。

今回のコンペを通じて、script 記法という慣れない記法に慣れることができ(むしろ終盤は script 万歳状態だった)こう言った形での成長もあるんだなーといった感じです。


感想

私は初めてのチーム戦だったのですが、最高の仲間と競技で世界2位になるという最高の結果を得られたのでとても最高な2ヶ月ほどでした。

もっともっと勉強して、また Wodori でコンペに出よう!となったらいいなあと思う深夜3時でした。

以上ポエムでした。

Google Colaboratory で fastText の pretrained model のSetup をする。

こんばんは、かえるるる(@kaeru_nantoka)です。今回は、fastText の公式ページだと説明が不親切だなーと感じたので 「pretrained の fasttext を GoogleColaboratory で利用する」というタイトルで手順を備忘録として残しておきます。

ソースは例のごとく GitHub (https://github.com/osuossu8/Utils/blob/master/fastText_pretrained_Setup.ipynb) においていますので必要に応じてご参照ください。


目次

1. 動機

2. ソースコードと説明

3. 感想


1 : 動機

公式ページ (https://fasttext.cc/docs/en/english-vectors.html)のわかりやすいところに連携されている pretrained model は .vec の形式で与えられていました。

gensim のラッパーとして提供されている FastText.load_fasttext_format や KeyedVectors.load_word2vec_format やら試したものの未知語に出会うとエラーに。。未知語でもよしなにしてくれるのが fasttextの利点じゃなかったっけ・・??

どうやら、 .bin 形式で与えられているものがいいらしいということで FastText.load_fasttext_format を試してみるもエラー。。

もう自力でビルドするしかないとソース引っ張ってきてやってみた次第です。


2 : ソースコードと説明

# Colab だと tmp ディレクトリに落としてきて、ドライブで解凍すれば容量食わないからいい感じです
!wget  https://dl.fbaipublicfiles.com/fasttext/vectors-english/wiki-news-300d-1M-subword.bin.zip

!unzip wiki-news-300d-1M-subword.bin.zip -d drive/My\ Drive

!git clone https://github.com/facebookresearch/fastText.git

!cd fastText;sudo pip install .

import fastText

# なかなか時間かかる
model = fastText.FastText.load_model('drive/My Drive/wiki-news-300d-1M-subword.bin')
print(model)

# インスタンスの説明(どんなメソッドがあるかなど)が見られます。
# とても便利
print(help(fastText.FastText._FastText))


import numpy as np
import pandas as pd

# class でラップして使っています
class fastTextVectorizer:
    def __init__(self):
        self.model = model
 
    def get(self,corpus):
        vec = []
        # 空のリストが渡ってきたときにゼロベクトルで埋めるようにしてます
        if len(corpus) != 0:
            for p in corpus:
                vec.append(model.get_word_vector(p))
        else:
            vec.append(np.zeros(300))

        return np.array(vec)

Vectorizer = fastTextVectorizer()

sentance = "Kaerururu likes kaggle very much"

corpus = sentance.split(' ')

print(corpus)
# [out]
# ['Kaerururu', 'likes', 'kaggle', 'very', 'much']

vec = Vectorizer.get(corpus)

print(vec)

"""
[out]
array([[ 0.00479189,  0.00076622, -0.00025739, ..., -0.00490327,
        -0.00920221, -0.014365  ],
       [ 0.01596881,  0.04688494,  0.00066695, ...,  0.01253003,
         0.0161329 , -0.00909959],
       [ 0.01079815,  0.0054005 , -0.01178535, ...,  0.00313774,
         0.00204078, -0.00597636],
       [ 0.00384124, -0.06426769,  0.02252812, ..., -0.00964052,
        -0.01634751, -0.00016563],
       [ 0.00118656, -0.03619536,  0.0089293 , ..., -0.01233855,
         0.02183427, -0.01065825]], dtype=float32)
"""

'kaerururu' といった単語もきちんとベクトル化してくれました。


3 : 感想

bin が 7GB くらいあるのでなかなか容量持っていかれますが、Colab だとデフォルトで 15GB 割り当てあるし、色んな単語を vector 化できて楽しいので割にはあるんじゃないかなーと思います。また BERT や ELMo が盛り上がってきて fastText 使いたいのに古い情報しかないぞ・・・!!!と困った私みたいな方に届いたら嬉しいなーと思います。

あと GitHub が充実していくのって楽しいです。

以上です、ありがとうございました。

PyTorch NN を用いて Titanic コンペに挑戦する。

こんばんは、かえるるる(@kaeru_nantoka)です。今回は、テーブルデータに Neural Network (以下 NN) を応用してみる Tutorial として PyTorch で Titanic 問題に取り組んでみたので、そのソースコードと説明をご紹介できればと思います。

今回のソースコードは、kaggle kernel に公開設定で置いております( https://www.kaggle.com/kaerunantoka/titanic-pytorch-nn-tutorial?scriptVersionId=12211278 ) ので、質問やアドバイスなど大歓迎です。


目次

1. 動機

2. 参考 kernel 集

3. ソースコードの説明

4. 感想など

5. Todo


1 : 動機

https://twitter.com/nyanp/status/1111242580765769728

こちらの Nomi さんのスライドにも言及されているように、昨今の kaggle で上位入賞を果たしている方(チーム)はほとんど木系のモデルのみではなく、NN でもモデリングをしてアンサンブルすることでスコアを伸ばしています。

そんな重要な知見であるテーブルデータにおける NN ですが、ネットで調べてみてもほとんど出てこず、自分で勉強していく中で何か参考になる簡単な問題に対して適用されたコードがあればいいなと思いましたので、復習としてまとめたものを output しようと思った次第です。


2: 参考kernel集

今回の私のコードを書くにあたって参考にしたカーネルです。ネットで調べてもほとんど出てこず〜と上では書きましたが、PyTorch の docs で勉強し、うまい書き方ないかなと、過去コンペのコードを漁っていた傍、参考になりそうなものはありました。こちらにまとめておきます。

i : Quora コンペより

( https://www.kaggle.com/hengzheng/pytorch-starter )

model.fit() の引数で ループ数を回す keras と異なり、PyTorch では、 dataloader というデータと正解ラベルを n個づつ返す iterater のループを、さらに epoch数のループで回すような見慣れない書き方をします。

それプラス Kfold のループを回すような書き方を探していた時に見つけたカーネルです。 Quora コンペも Titanic コンペと同じ 2値分類タスクなので、評価指標の f1-score の部分を acc に書き換える実装をして他の部分はほとんどそのまま使っています。

i i : TwoSigma コンペより

( https://www.kaggle.com/christofhenkel/market-data-nn-baseline )

NN 自体は keras での実装ですが、NN に特徴量を投入する前の前処理部分を参考にしました。カテゴリカルデータ(文字などの数値で与えられていない特徴量や一応数値では与えられているが数値としての意味を持たないもの(男性、女性を 0, 1 として表して与えられているもの))の encoding や numerical データ(数値がそのままの意味を持っているデータ。金額や人数など)を正則化するなど最低限の前処理がなされております。


3: ソースコードの説明

[2] PyTorch の利点である 「seed を簡単に固定できる」 を実現してくれる部分です。

    def seed_everything(seed=1234):
        random.seed(seed)
        os.environ['PYTHONHASHSEED'] = str(seed)
        tf.set_random_seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        torch.cuda.manual_seed(seed)
        torch.backends.cudnn.deterministic = True
    kaeru_seed = 1337
    seed_everything(seed=kaeru_seed)

[5] categorical columns と numerical columns を指定します。 cat_cols は今回は数値でないものを適当に指定しています。

num_cols はテーブルによっては非常に多くなることもあるので、list(set()) を使用すると簡単に取得できます。

cat_cols = ['Cabin','Embarked','Name','Sex','Ticket',]

num_cols = list(set(train.columns) - set(cat_cols) - set(["Survived"]))

[6] [7] categorical columns と numerical columns それぞれの前処理の部分です。

def encode(encoder, x):
    len_encoder = len(encoder)
    try:
        id = encoder[x]
    except KeyError:
        id = len_encoder
    return id

encoders = [{} for cat in cat_cols]


for i, cat in enumerate(cat_cols):
    print('encoding %s ...' % cat, end=' ')
    encoders[i] = {l: id for id, l in enumerate(train.loc[:, cat].astype(str).unique())}
    train[cat] = train[cat].astype(str).apply(lambda x: encode(encoders[i], x))
    print('Done')

embed_sizes = [len(encoder) for encoder in encoders]

# =====

from sklearn.preprocessing import StandardScaler
 
train[num_cols] = train[num_cols].fillna(0)
print('scaling numerical columns')

scaler = StandardScaler()
train[num_cols] = scaler.fit_transform(train[num_cols])

[8] [9] 今回は私の練習として、自作の層を定義してそれを利用するような NN にしてます。

nn.Sequential() は keras の sequential model と同じように使えます。

自作レイヤーを使わずに net を定義し直すと、

net = nn.Sequential( nn.Linear(12, 32) nn.ReLU(), nn.Dropout(0.5), nn.Linear(32, 1) )

というように書けます。

class CustomLinear(nn.Module):
    def __init__(self, in_features,
                 out_features,
                 bias=True, p=0.5):
        super().__init__()
        self.linear = nn.Linear(in_features,
                               out_features,
                               bias)
        self.relu = nn.ReLU()
        self.drop = nn.Dropout(p)
        
    def forward(self, x):
        x = self.linear(x)
        x = self.relu(x)
        x = self.drop(x)
        return x

net = nn.Sequential(CustomLinear(12, 32),
                    nn.Linear(32, 1))

入力は 12次元 (targetを除いたもの), 出力は 1次元です。 のちに、BCEWithLogitsLoss を最小化するように計算して sigmoid 関数で 0,1 の2値分類を解く形になっています。 (誤りがあったら教えてくださいm( )m)

# Kfold のループ部分
for i, (train_idx, valid_idx) in enumerate(splits):

    # X_train, y_train, X_val, y_val をテンソル化(PyTorch で扱える形に変換)し、 .cuda() (GPUで計算するために特徴量を GPU に渡す処理)をする。
    x_train_fold = torch.tensor(X_train[train_idx], dtype=torch.float32).cuda()
    y_train_fold = torch.tensor(y_train[train_idx, np.newaxis], dtype=torch.float32).cuda()
    x_val_fold = torch.tensor(X_train[valid_idx], dtype=torch.float32).cuda()
    y_val_fold = torch.tensor(y_train[valid_idx, np.newaxis], dtype=torch.float32).cuda()
    
    # model を呼び出して、
    model = net
    # model も GPU に渡す。
    model.cuda()
    
    # loss 関数を呼び出す。BCELoss() よりも好まれるらしい。。
    loss_fn = torch.nn.BCEWithLogitsLoss(reduction="sum")
    optimizer = torch.optim.Adam(model.parameters())
    
    # dataloader で扱える形( = Dataset )にする
    train = torch.utils.data.TensorDataset(x_train_fold, y_train_fold)
    valid = torch.utils.data.TensorDataset(x_val_fold, y_val_fold)
    
    # x_train_fold batch_size個, y_train_fold batch_size個ずつを各ループで返す iterater の定義
    train_loader = torch.utils.data.DataLoader(train, batch_size=batch_size, shuffle=True)
    # x_valid_fold batch_size個, y_valid_fold batch_size個ずつを各ループで返す iterater の定義
    valid_loader = torch.utils.data.DataLoader(valid, batch_size=batch_size, shuffle=False)
    
    print(f'Fold {i + 1}')
    
    # epoch 分のループを回す
    for epoch in range(train_epochs):
        start_time = time.time()
        
        # model を train mode にする
        model.train()
        avg_loss = 0.

        # x_train_fold と y_train_fold を batch_size 個ずつ渡すループ
        for x_batch, y_batch in tqdm(train_loader, disable=True):
            # predict
            y_pred = model(x_batch)
            # loss の計算
            loss = loss_fn(y_pred, y_batch)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            avg_loss += loss.item() / len(train_loader)
        
        model.eval()
        valid_preds_fold = np.zeros((x_val_fold.size(0)))
        test_preds_fold = np.zeros(len(X_test))
        avg_val_loss = 0.
        for i, (x_batch, y_batch) in enumerate(valid_loader):
            y_pred = model(x_batch).detach()
            avg_val_loss += loss_fn(y_pred, y_batch).item() / len(valid_loader)
            valid_preds_fold[i * batch_size:(i+1) * batch_size] = sigmoid(y_pred.cpu().numpy())[:, 0]
        
        elapsed_time = time.time() - start_time 
        print('Epoch {}/{} \t loss={:.4f} \t val_loss={:.4f} \t time={:.2f}s'.format(
            epoch + 1, train_epochs, avg_loss, avg_val_loss, elapsed_time))

    # X_test_fold を batch_size ずつ渡すループ    
    for i, (x_batch,) in enumerate(test_loader):
        y_pred = model(x_batch).detach()

        # batch_size のリストのリストになっているのを単一階層のリストに変換して、cpuに値を渡し、テンソルから numpy.array()に変換したものを sigmoid 関数に渡す
        test_preds_fold[i * batch_size:(i+1) * batch_size] = sigmoid(y_pred.cpu().numpy())[:, 0]

    train_preds[valid_idx] = valid_preds_fold

    # 予測値の kfold数で割った値を加える
    test_preds += test_preds_fold / len(splits)

[15] 最後に予測値に対して閾値を決めて 予測値 を 0, 1 の形に変換する。

from sklearn.metrics import accuracy_score

def threshold_search(y_true, y_proba):
    best_threshold = 0
    best_score = 0
    for threshold in tqdm([i * 0.01 for i in range(100)]):
        score = accuracy_score(y_true=y_true, y_pred=y_proba > threshold)
        if score > best_score:
            best_threshold = threshold
            best_score = score
    search_result = {'threshold': best_threshold, 'accuracy_score': best_score}
    return search_result

この submission は、LB 0.66985 でした。

遠い昔、簡単に前処理と特徴量エンジニアリングして決定木にかけた時の最高スコアが、0.71770 だったので初めてにしては悪すぎずといったところでしょうか :)


4 : 感想

今回の目標である、テーブルデータに対して PyTorch の NN を適用することはできました。今後はこのコードを使いまわして、過去コンペの NN 縛り LateSubmission チャレンジをやってみようかなと思ってます。


5 : Todo

・出力1, BCELogitsLoss --> 出力2, CrossEntropyLoss でやってみる

・LGBM とアンサンブルしてどれくらいのスコアの上がり幅になるか見る。


以上です。ありがとうございました