Paoの技術力を磨くブログ

機械学習やブロックチェーン等の技術を身に付けていくブログです。

麻雀の危険牌を予測する part1 ~解釈性を添えて~

久々に麻雀AI関連のネタです。 続編を(脳内では)用意してるのでpart1としています。

ちょっとした背景

将棋や囲碁ほどではないですが、麻雀界で麻雀AIが最近少しずつ知名度が上がっています。 有名どころでは「爆打」や「NAGA」、「Suphx」あたりでしょうか。

特にMicrosoft製である「Suphx」はネット麻雀の天鳳で10段という、かなりの上位成績を残しており、最近注目されています。 Arxivに論文も投稿されており、深層強化学習ベースで、学習に対しての工夫も面白いものでした。

news.microsoft.com arxiv.org

私自身、過去に麻雀AIを作っていて作業量の多さから無期限休止してたのですが、仕事やKaggleで得た経験を元に、あらためて何かできないかなと思いやってみました。

「麻雀の危険牌予測」とは?

麻雀では、「手牌から何を切るか?」「立直するか?」「チー・ポンなどの鳴きをするか?」といった判断をプレイヤーが行います。

そしてその判断をするためには

  • どの牌が山にどれくらいありそうか?
  • 立直してない相手がテンパイしてそうか?
  • 立直した/テンパイしてそうな相手にとって何が危険な牌か?

等々の読みを行うことが重要です。 そしてその読みの精度が麻雀の実力における大事な要素とも言えるでしょう。

今回は、そんな読みの中でも、「立直した相手にとって何が危険な牌なのか?」について、機械学習で取り扱ってみます。

問題設定

危険牌の予測について、以下のようにシンプルな問題設定としました。

  • 立直者以外の1人のプレイヤー(以降ではプレイヤーAとします)の視点から、立直している人の当たり牌を予測する
  • 立直宣言牌が場に出た時点での予測とする
  • プレイヤーAから見える牌としては以下の通り。
    • 4人の捨て牌
    • 自身の手牌
    • ドラ表示牌
  • それぞれの牌がツモ切りかどうかは見ない

なお、他プレイヤーの副露情報および副露時に晒される手牌(鳴いた牌以外の2枚)については、今回は見えていないという前提にしています。 (ツモ切りかどうかと合わせて本当は大事なのですが、実装の都合上面倒だったので今回は削りました。ごめんなさい)

データの用意

今回は天鳳鳳凰卓(四麻)4年分(2016~2019)の牌譜データを学習・検証に用いました。

1つの局を1レコードとして441万個レコードあります。

改めてこうして過去分のデータを無料で公開していただいているのは、ありがたいですね。

牌譜データは、mjlogという独自形式であり、パースするのがちょっと面倒だったので下記リポジトリソースコードを参考にさせていただきました。

github.com

モデル設計

今回予測する麻雀の当たり牌は以下の特徴を持ちます。

  • 待ちは34種類の牌の中に存在
  • 待ちには種類がある(単騎待ち、両面待ち、カンチャン待ちなど)
  • 同時に複数枚の待ちがありえて、何種類あるかは分からない。
    • 例:単騎待ち、カンチャン/ペンチャン待ちでは1枚、両面待ちでは2枚、国士無双13面待ちでは13枚等
  • 特定の組み合わせの待ちのパターン(1-4待ちなど)が多いが、組み合わせは多数ある(シャンポン待ち、清一色多面待ちなど)

また今回、教師あり学習のモデルとしてはLightGBMを用います。 LightGBMを用いる理由としては、後半でも出てきますが、精度面と解釈性などを考慮した結果です。

LightGBMで今回の問題を解こうとした場合、いくつか方法が考えられますが、今回はそれぞれの牌34種類ごとにモデルを作ります。

1萬が当たり牌かどうかのモデル、2萬が当たり牌かどうかのモデル、、、、という形です。

特徴量エンジニアリング

さぁKagglerの腕の見せ所です...!!

と言いたいところではありますが、今回は34個のモデルを学習させないといけません。 ということで、学習時間的にもあまり作り過ぎず、自分のドメイン知識を活かしつつ以下のような特徴量合計144個を作りました。

  • 場況情報(各プレイヤーの点棒、場風、自風、本場、ドラ等)
  • 見えている牌の情報(それぞれの牌が場に何枚見えているか)
  • 立直プレイヤーの捨て牌情報
    • 各牌を何枚ずつ切っているか
    • 各色(萬子/筒子/索子)、字牌の割合(全体と6巡目まで)
    • 最初の6巡でヤオチュウ牌(字牌と1,9)以外の割合、4,5,6の割合
    • 各色で最初に切った牌、2番目に切った牌、およびその数字の差
    • 各色で最後に切った牌、最後から2番目に切った牌、およびその数字の差
    • 立直宣言牌、およびその色
    • 各筋を切っているかどうか(1-4萬, 2-5萬, ....., 6-9ソウ)
    • 各色で最初/最後に切った3つの牌を3桁の数字で表現(3ピン→9ピン→4ピンなら394)

学習結果

比較用のベースライン

今回1つのモデルしか作っていないため、超簡易なベースラインも作成しました。 ベースラインとしては、安牌と分かっている牌を除く牌が平均1.8枚均等に当たるとした場合のスコアを用いました。 1.8枚としたのは、今回のデータ全体で、待ちの枚数の平均が約1.8枚だったためです。

例えば、立直した人が萬子と字牌のみを全て切っている場合、残りの牌であるソウズとピンズ計18種類それぞれの牌の確率は、

P(1ピン) = P(2ピン) = ..... = P(9ソウ) = 1.0 * 1.8 / 18 = 0.1

P(1マン) = P(2マン) = .... = P(中) = 0.0

となります。

さすがにこれよりは良いスコアが出ないと、何も学習できていないことになります。

評価指標・バリデーション

全データのうち、ランダムに選択した20%の局を評価データ、残りを学習データとして学習を行いました。 麻雀の立直においては、局単位で互いに独立とほぼほぼ言えるはずなので、時間軸は無視してます。

評価指標としては、MultiLoglossを用いています。 MulitiLoglossは多クラス分類かつ、複数の正解があるケースでもよく使われる一般的な指標です。 値が小さいほど、正しく当てられていることになり、0が最も良い精度ということになります。

結果

条件 MultiLogloss
ベースライン 6.31628
LightGBM 5.665

さすがにベースラインよりは、良い精度が出ています。 とはいえ、あまり大きく変わらない気も。。。そもそもこの数字見ても、どれだけ当てられているかよく分からないですよね。

ということで他の視点でも見てみましょう。

Feature Importance

Feature Importanceとは、どの特徴量がモデルの予測に役だったかを示すものです。

2つの牌で見てみましょう。

1萬

feature gain
萬子のスジ14をきったか 428518
1萬をきったか 246415
最初に切られた萬子の牌 53528
立直宣言牌 42191.8
捨て牌に含まれる筋の数 40264.3
2萬を切ったか 35750
最初の6枚に萬子が含まれる割合 29590.7
4萬を切ったか 29198
捨て牌の萬子の割合 18617.2
3萬を切ったか 18063.7
1萬が何枚見えているか 12186.1
2萬が何枚見えているか 9478.43
2番目に切られた萬子の牌 9232.62
ヤオチュウ牌の割合 7794.8
最初の6枚に4,5,6が含まれる割合 6770.15
立直者の点数 6616.69
萬子で最後に切られた牌 6178.87
捨て牌の数 5963.82
立直宣言牌の数字 5644.4
萬子の最初の牌と次の牌の数字の差 5305.09

gainというのが重要度のスコアを表しており、大きいほど重要となります。

当然ながら1萬を切ったかどうか、ということを表現している特徴量は上位に来ています。 それ以外には、最初に切った萬子の牌、2萬を切ったかどうか等の特徴量が上位に来ているのは非常に納得感がありますね。 他にも萬子の1~4あたりに関する特徴量が多いのも納得感がありますね。

「立直者の点数」が上位に入っているのも興味深いですね。 自分の点棒が少ないときはタンヤオ狙いで1萬待ちが減るということでしょうか。

feature gain
南が場に何枚見えているか 359076
南を切ったか 84561
捨て牌に含まれる筋の数 19852.3
捨て牌の数 8081.86
場風 6031.25
最初の3枚でヤオチュウ牌の以外割合 4031.93
立直した人の点数 2902.47
字牌で最初に切った牌 2767.41
最初の6枚で字牌の割合 2365.44
ドラを切った数 2241.72
立直宣言牌 2138.46
立直した人の上家の点数 1929.55
立直した人の下家の点数 1928.33
宣言牌が何枚切られているか 1693.06
捨て牌のヤオチュウ牌の割合 1466.89
立直した人の対面の点数 1435.81
字牌の割合 1189.01
ソウズの割合 1181.95
1萬が見えている数 1167.43
マンズの割合 1109.77

次は字牌です。 字牌は数字に比べて予測がより難しいです。

こちらも南が切られたかどうかが上位に来ているのは当然ですね。

「場風」が上位に来ているのが、良いですね。 東場よりも南場のほうが南であがると得点があがるので、場風は非常に大事です。

あとは、字牌をたくさん切っていると、数字の待ちの可能性が高くなるし、字牌があまり切られていないと字牌を含んだ変則待ちの可能性が高いはずなので、 ヤオチュウ牌の割合を使った特徴も上位に入っているのも納得です。

個別での分析

やはり、実際の捨て牌でどう予測したかを見ないと、何とも実感ないですよね。 ということでいくつかの場面での予測結果を見てみます。

ここではヒントが多そうな場面にしぼり、 シンプルに立直した人の捨て牌と、それに対する予測確率だけを表示します。

(他の人の捨て牌と合わせて、ノーチャンスの牌などが分からなくなってしまいますが、ご了承ください。)

せっかくなので、(元天鳳9段の)私自身の捨て牌を見た上での見解を述べて、予測結果と比較したいと思います。

場面1

f:id:go5paopao:20200809171108p:plain

私の見解

  • マンズは全体的に安全そう。とはいえ、5-8マンは全然ありうる。
  • ピンズは最初に5ピンが切られて、かつ上の方の牌だけ切られていることから、1-4ピンだけ危なく、6ピン, 8ピンは割と大丈夫そう
  • ソウズは最初に2ソウ, 3ソウと切られているので1-4ソウはマシそう。4-7ソウ、5-8ソウはかなり危険。
  • 特に宣言牌が9ソウなことから、「779」「889」「799」「899」からの9切りによる、7ソウ、8ソウの待ちもありかなり危険

予測結果

  • 既に切っている牌はほぼ0になっている
  • 7ソウが最も危険と予測しており、私の見解とも合っている
  • 危険度合いが4-7ソウ、5-8ソウ > 1-4ピン > 5-8マンというのも感覚と合う

正解

4-7ソウでした。 ちゃんと4-7ソウの危険度が高く、合っていますね。

場面2

f:id:go5paopao:20200809173318p:plain

私の見解

  • マンズは1-4マン、2-5マン、5-8マンどれも危ない。宣言牌周りの1-4, 2-5のほうが危険そう
  • ピンズは序盤の2, 9, 7ピン切りから、3-6ピンだけ危険そう。
  • ソウズは最後の5ソウ周りの3-6ソウ危険、1-4ソウも危なそう

予測結果

  • 5マンの危険度が最も高い。2-5マンと5-8マンの筋どちらもありうるため納得
  • 次に3-6ピンが危険。これも納得。
  • 1-4マンの危険度が想像より低い。
  • ソウズが見えていない筋も少し低め。

→他の人の捨て牌を見ると、 - 2マンが4枚見えているため、1-4マンがノーチャンスであり、低いことがわかりました。(それにしては高い気もしますが) - 1ソウ4枚、4ソウ2枚見えており、1-4ソウは薄い待ち(上がりにくいので立直しにくい)であることがわかりました。

これらを考慮すると納得いく予測だと言えそうです。

正解

3-6ピンでした。 これも危険度を高めに予測できていますね。

場面3

f:id:go5paopao:20200809175502p:plain

私の見解

いかにも怪しい捨て牌ですね。 序盤から真ん中の牌がどんどん切られています。

ピンフ系には見えません。

また1-9が序盤から切られていること、どの色も切られていることから、染め手やチャンタの線も薄そうです。

チートイツでしょうか。 無筋の牌を警戒しつつも、ヤオチュウ牌も危険という感じでしょうか。

予測結果

  • 危険度が高いものTop5としては、「1マン」「4マン」「5ソウ」「1ソウ」「4ソウ」
  • たしかにTop5は危なそうなので納得
  • 7マンが通っているわけでもないのに、4マンより1マンのほうが危ないと出ているのは、変則待ちの可能性があると判断している?

正解

正解は5ピンの単騎待ちでした。 これはなかなかの難題でした。

モデルの予測としては、大きく外れても当たってもいないという感じですね。

解釈性

モデルの予測結果を見ることで、そこそこの精度では予測できてそうなことが分かりました。

ここで少し話は変わりますが、仮に今回作ったモデルが精度が良かった場合のこのモデルの使い道についても少し考えてみたいと思います。

Suphxのような強いAIを作る上でのパーツとして利用し最強のAIを目指す道も考えられますが、個人的にはもう一つ有力な使い道として、人間への指導ツールとしての価値もあると思っています。

自分が麻雀を打っているときに「この場面では(自分より強い)AIはこういった予測・選択をしている」というのが分かれば、自分が考えていなかったことや考え方の偏りに気付けるかもしれません。

今回はそういった指導ツールとしての使い道も考える上で重要な「解釈性」についても考慮してみます。

解釈性とは簡単に言うと、「なぜAIがその選択をしたかがどれだけ分かるか?」というものです。

今回の危険牌予測で解釈性があるということは、なぜこの牌が危険だと思ったのか?AIの考えが分かるということであり、それがあるほうが指導ツールとしても使えるかもしれません。

解釈性は、しばしばAIをビジネスで活用する上でも重要であり、「xAI (ExplainableAI: 説明可能な AI)」と呼ばれる分野として研究も盛んです。

参考:ドワンゴ製NAGAの牌譜解析レポート

ドワンゴ製のNAGAでこの点を最も考慮して作られているように感じます。 ちょうど先日まで行われていたNAGAが参加する大会では参加者視点でNAGAがどう考えているかのレポートを公開しています。

各局面での期待順位や手牌それぞれの危険度、切ると期待値が高い牌を教えてくれます。

何より、誰でも使えるようなUIになっているのが良いですね。

f:id:go5paopao:20200816171541p:plain

tenhou.net

今回試してみること

今回用いているLightGBMは、深層学習と比べると比較的解釈性は高いと言われています。 実際、特徴量の重要度の算出も簡単に出来ます。

しかし、それはあくまで全体の傾向であり、一つ一つ予測する局面毎での解釈ではありません。

今回は一つ一つの予測で解釈性を持たせる方法として「SHAP」を用いました。

SHAPとは

SHAPとは、協力ゲーム理論のShaply Valueを元にしたアルゴリズムで、各説明変数が目的変数の予測にどれだけ寄与したのかを算出します。

特徴としては、モデル自体がブラックボックスでも取り扱えることです。

アルゴリズムの詳細は割愛しますが、下記の記事が非常に分かり易いです。

dropout009.hatenablog.com

SHAPによる実例

上とはまた別の1つの例でみてみます。

f:id:go5paopao:20200816223342p:plain

SHAP

SHAP Valueはモデル毎(牌の種類毎)に出せます。

今回は捨て牌的に気になる2マンについてみていきたいと思います。

f:id:go5paopao:20200816224410p:plain

簡単に説明すると、

  • 下に書かれた文字が特徴量(都合により英数字での表記です)
  • 赤色のものがスコアを上げている特徴量
  • 青色のものがスコアを下げている特徴量
  • 幅が広いものほど、より寄与している

です。 ここでは特に重要と思われる特徴量だけが表示されています。

この例では「discord_2」が最も幅が大きく寄与しています。discord_2は「2マンが捨て牌に含まれているかどうか」という意味の特徴量です。

これが最も大きくなるのは当然ですよね。

他の特徴量については下記の通りです。

feature 説明
manzu_suji14 1-4マンのどちらかを切ったか
manzu_first_hai マンズで最初にどの牌を切ったのか
can_see_count_2 2マンが場に何枚見えているか
sengen_hai 立直宣言牌が何か
manzu_suji25 マンズの筋25を切っているかどうか
discord_5 5マンを切っているかどうか

これらをまとめて簡単に要約すると、 「2マンを切られていなくて、宣言牌や2マンの見えている枚数、萬子の最初に切られた牌からして危険度が上がっている。ただし5マンが切られていることで危険度が少し下がっている」

といった感じでしょうか。

納得感ありますね。

面白いのが、「can_see_count_2」や「sengen_hai」はFeatureImportanceではそこまで上位ではないですが、今回の例では寄与度が高くなっていると言うことです。 すなわち、今回の場面だからこそ重要な特徴量とも捉えられます。

can_see_count_2について

特に納得できるのが「can_see_count_2(2マンが場に何枚見えているか)」です。

5マンが切られていることから、2マンで当たる形としては「カンチャン待ち」か「シャンポン待ち」、「単騎待ち」となります。 この中で特に「シャンポン待ち」は場に2マンが3枚以上見えていたらありえないですし、2枚でも山に0枚の待ちになるので、その選択をする可能性は低いです。 さらに単騎やカンチャンも同じですが、場に見えている枚数が少ない方が一般的には上がりやすいので、待ちになりやすいです。

このことから「can_see_count_2」が1以下かどうかが大事ということになります。

また、5マンが切られていない場合は、2が場にたくさん見えていても5が場に見えていなければ2-5待ちでの立直の可能性は全然あるので「can_see_count_2」はそこまで重要ではないでしょう。

ちなみに今回のcan_see_count_2は1でした。

解釈性についての考察

SHAPを使うことで、FeatureImportanceだけでは分からない各場面での特徴量の寄与度が分かりました。 これがあるだけで、「なぜその予測をしたのか?」が格段に分かりやすくなり、指導ツールとしての使える可能性も大きく広がったと思います。

とはいえ、実際に一通りやってみた上での課題や考慮すべきことも見つかりました。

特徴量の作り方次第で解釈性は大きく変わる

SHAPで分かるのは特徴量の寄与度のみであり、その特徴量自体が解釈できないものであれば解釈性があるとは言えません。

例えば今回はやっていませんが、もっと精度をあげようと思ったら以下のような特徴量エンジニアリングも考えられます

  • 34種類の牌が切られた枚数からPCAやTruncatedSVDなどで特徴圧縮
  • 各牌を一つの単語とみなし、Word2Vecで学習。Word2Vecでえられた各牌のベクトルの統計量を用いる

仮にこういった特徴量を入れていて、それが上位に来ても何のことかさっぱり分かりません。

また、ここまで複雑でなくても、他の人がパッと理解できるような特徴量にしないと使いづらくなってしまいます。

寄与度から、いかに人間が解釈できてかつ精度も出る特徴量エンジニアリングが出来るかが大事だと改めて感じました。

安牌のデータは学習から外すべきだった

今回、それぞれの牌について使えるデータは全て使いましたが、安牌のケースは学習から外した方が良いと感じました。

例えば、1マンの危険度を学習するときは、1マンが捨て牌にあるケースを学習データから外すといった形です。 そして、そういったケースはルールベースで危険度を0に変更するといった形です。

精度的にもその方がいいのかもしれませんが、解釈性をあげるためにもそうすべきだったと思いました。

今回のやり方では上の例でもそうですが、その牌を切ったかどうかが一番寄与するという結果にどの例でもなります。 これは当たり前のことであり、モデルに予測してもらわなくてもいいですし、その分他に寄与した特徴量の情報が分かりづらくなってしまいます。

麻雀AI以外のビジネスで活用する場合でも、解釈性としてSHAPのようなツールを使う場合、明らかにルールベースで何とかなるものは除いた上でモデリングをしたほうが良いかもしれません。

待ちの種類や手牌についても予測した方が解釈性は上がる

今回の例では危険度をそのまま予測しましたが、解釈性や指導ツールとしての使い道を考えると、「両面待ちか?」「染めていそうか?」といったことも予測し、それに対しての寄与度があった方が価値がありそうです。

麻雀の待ちを予測すると言うのは人間の思考でいくと、「相手の捨て牌から相手が何を手牌に持っていそうか?」、「どこがまだ完成していなさそうか?」、「どういう待ちを選択しそうか?」といった要素的な予測があった上で成り立っています。

強さを求めるだけの麻雀AIでは精度が全てですが、解釈性をあげるとなると出来るだけ人間の思考に近づけたモデルを用意してあげた方が良さそうです。

例えば上の2マンの例では、「立直宣言牌が何か?」が大きく寄与しているという結果でしたが、それだけではなく例えば「2マンが手牌に含まれているかどうかのモデル」があったとして、 「5マンを切った上で立直宣言牌が1マンだから2マンを持っている可能性が高い」という解釈がSHAPで出来ると、とても麻雀の勉強になりそうです。

もちろん無限にモデルを作るわけにはいかないですが、最終的に予測したいものでなくても人間の思考に合わせた目的変数の設計をすることが非常に大事だと分かりました。

その他改善すべきポイント

解釈性としては上に挙げたとおりですが、精度面においても、特徴量の工夫はもちろん下記のようなことをすればもっと良くなると思います(時間の都合上断念しました)。

  • フリテンのデータを削除(これが入っていた影響で切っている牌も確率が0にならない予測があったと考えられます)
  • 牌の色を入れ替えたDataAugmentation
  • プレイヤーの視点(上家、対面、下家)それぞれの視点でのデータを含める
  • 待ちの種類や役を予測するモデルとスタッキング

また、実際の麻雀では振り込む確率だけでなく、振り込んだときの得点も込みした期待値が大事なので、打点の予測などもした方が良さそうです。

まとめ

今回はLightGBMとSHAPを使った危険牌予測を行いました。

精度的にはそこそこ出ていそうです。 あと、実際に使ってみることで解釈性としての使い道も少し見えました。

次回は、危険牌予測version2としてTransformerを用いた予測モデルの構築をしてみる予定です!

長文にお付き合いいただきありがとうございました。

ソースコード

散らかっていますが、下記に学習や可視化に使ったソースコードを置いています。

github.com

転職エントリ ~大企業と育児と年齢とKaggleとキャリア~

初めての転職をしました。

2020年4月末を持って新卒から7年間勤めた会社を退職し、今年の5月から新しい会社で働いています。

自分に境遇が近い人などの参考になればと思い、書きました。

長くなると思うので、お題毎に気になるところだけでも読んでもらえると嬉しいです。

※前職及び転職先の会社名は書いていませんのでご了承ください。

簡単な自己紹介

  • 31歳
  • 情報系の修士卒で新卒から8年目
  • 一児(2歳)のパパ
  • 機械学習を学び始めたのは4年前くらい
  • Kaggle好き(Kaggle Master)

前職について

会社はいわゆるJTC(Japanese Traditional Company)と呼ばれるようなIT系の大企業でした。

最初の3年はWeb系のSIerみたいな仕事で、後半の4年は研究開発の部署で、機械学習を使ったユーザデータの解析やサービス検証などをやっていました。 入社3年目あたりで、趣味である麻雀のAIを作ってみようと思ったことがきっかけで、機械学習の面白さにハマり、仕事もそっち寄りのことができる部署に異動しました。

どんな会社か

働き方としてはかなりホワイトで、給料もそれなりにいいので、向いてる人には良い会社と思います。 またR&D系の部署では、良くも悪くも直接ビジネスをするわけではないため、業務の自由度は高かったです。

良かったところ

休みの数がかなり多い

年休20日+夏季5日+年末年始で、ほぼ強制的に消化しないといけません。 旅行が好きな人やプライベート充実させたい人には向いてると思います。 コンペ休暇とりたい人も向いていると思います

扱えるデータの種類・量が多い

これはデータサイエンティスト、機械学習エンジニアであればこそですが、嬉しいポイントです。 国内でも有数レベルのユーザ数とデータの種類があり、やろうと思えばほんとに色んなことが出来たと思います。 また、先人たちのおかげでそれらが一元的に管理されてるのもありがたいです。

ただしデータをうまく活用してサービスに応用するためには、それなりの社内調整力が求められます。

その他

これは個人的な部分になるのですが、「Kaggle Days Tokyo」や「海外の国際会議」に業務として聴講で行かせてもらえたことは非常に満足で前職に感謝しています。

自分には合わなかったところ

煩雑な事務処理系のタスクが多い

転職の理由の一つです。人にも寄りますが、かなりの時間を取られます。 契約関係の処理や固定資産管理、セルフアセスメント、現物調査、セキュリティチェック、社内e-learningなどなど。。しかもそれらが使い勝手の悪い社内システムと合体することで、より凶悪になります。 特に辛かったのが、数カ月ごとに他の人の出張申請などをランダムにN件以上確認して、添付資料や出張の食事代などを目視でチェックしていく仕事でした。もう二度としたくないですね。。

全国転勤/ジョブローテーション

大企業あるあるですが、基本全国転勤があります。 円も縁もない九州に飛ばされたり、家を買った直後に転勤したり、色んな悲劇を見てきました。 また転勤はなくても、定期的な異動はあり希望していない部署への異動はよくあります。 それを込みでの給料だったりするので、仕方ないことなのかもしれません。 幸い業務内容的に全国転勤の可能性は低めではありましたが、小さな子供と離れて単身赴任するのも嫌だったので、変なところに飛ばされる前には辞めたいと思っていました。

中間管理職に向けたキャリア

これも普通の大企業どこでも似てると思いますが、総合職のため昇進すると、一般社員→主査→担当課長→・・・(無限と続くピラミッド)と続き、担当課長あたりから完全にマネジメントで、コードを自分で書くことはほぼ無くなります。 またマネジメント職になってもまたまだ上にたくさんの人がおり、板挟み状態が続きます。社内政治がうまい出来る人は担当課長くらいでも実質的な裁量もありますが、基本はいわゆる中間管理職です。

私自身は、現時点ではまだプレイヤーとして磨きたいと思っていて、マネジメントするにしてもプレイングマネージャーみたいなポジションがいいなと思っています。そんな私にとっては前職の会社のマネジメント職での仕事が楽しそうとは思えませんでした。 また、データサイエンス分野ですごく優秀な先輩がいたのですが、課長に昇進したら、「コード書いてる場合じゃないよ」と上司に言われ、その後も会議に追われてる姿を見てしまったのも追い討ちになりました。

その他

他にも前職について思うことはありますが、直接転職とは関係ない部分なので省略します。

転職の理由

仕事の時間を出来る限り楽しい&スキルアップできる時間にしたい

プライベートな時間は取れる会社だったので、2年前くらいまでは仕事以外の時間で自分の好きなこと(機械学習やプログラミング)をのんびりとやってました。 しかし、子供が生まれてからは家族の時間を優先しており、仕事以外で使える時間がかなり限られるようになりました。 そうなったとき、仕事の時間というのが自分の中でとても貴重な時間であると気付き、出来る限り自分のやりたいことに充てたいという思いがかなり強くなりました。 そして、上に書いたような事務処理は当然やりたいことではなく、本当に勿体ない時間に感じ、やりたいことに注力できる会社で働きたくなりました。

将来的なキャリア

これも上に書いたとおり、中間管理職に向けたキャリアは自分の描くキャリアとは方向性が違ったので、それが実現できる道に進むことにしました。

転職における軸・条件

カジュアル面談などをしながら、少しずつ軸を固めていき、最終的には以下の軸・条件で決めました。

  • エンジニアリング&データサイエンスができる(時間的にもそこに注力できる)
  • 自分が伸ばしたい分野で優秀な人がいる
  • 多くのデータに触れて、データサイエンスの技術の幅を広げられる
  • 小さな子持ちでも安心して働ける
  • 給与:現状以上

スキルアップ面を重視しての転職のため、給与よりも他の条件を重視しました。

転職先について

最終的には、2社選考に進むことにして、ありがたいことに2社ともに内定をいただくことができました。

その2社でかなり悩んだのですが、現時点でやりたいことに近いと感じた機械学習をメインとした某ベンチャーに行きました。 同分野の中では比較的歴史のある会社だと思います。

私のポジションとしては、受託に近いデータサイエンティストといった感じです。

話を聞いた中で一番、軸・条件がマッチしていると感じたのでそこに決めました。

2ヶ月くらい働いた現在、とてもマッチしてると感じてますし、転職してよかったと思ってます。

30歳過ぎ・子持ちでの大企業からの転職

ここについては今回非常に考えさせられました。

自分がやっていきたいデータサイエンス・機械学習の領域はレッドオーシャンですし、何より学生でもバリバリ実力をもっている人がたくさんいる世界です。

そんな若者を自分と比べると、現時点のスキル以外にも特に

  • 若者が勉強に使える時間に比べて、子持ちの自分が使える時間が圧倒的に少ない
  • 求める給料条件が自分の方が(現状を維持しようとすると)高く、それに見合う能力があるか怪しい

というところは悩まされました。

特に2つ目は大きかったです。それなりに労働条件の良い大企業で30歳まで働いていると、そこそこ良い給料が普通に働いていればもらえます。 スキルがあるかどうかに関わらずです。 そして転職しようと思ったときにそれなりの給料である現状を維持しようとすると、ベンチャーだとかなり高い方になりそれに見合うだけの能力が要求されます。

しかし、何も考えず大企業で働いていると、自分が今もらっている給料が世間的に実力に見合うものかと言われると、そこに差があることに気付きます。 もちろん大企業でも実力をつけることはできますが、意識的にしないと社内調整力とかで成果をあげることに慣れ、社外で客観的に分かるスキルセットがない。。。みたいなことになりがちです。

そして自分のやりたいことを求めて転職をしようと思ったときには、現状の能力的に給料を下げざるを得なくなる。しかし、子供もいる身なので給料を下げることはなかなか厳しい。。となったりします。(自分もなりかかっていました)

私の場合幸いにも仕事でも機械学習の経験を積んでいて、趣味でもプログラミングしたりKaggleをしたりしててそれが功を奏して何とか転職できましたが、 それでも世間の相場にあっているかと言われると自信はないですし、給与条件が合わず選考に進むことを断念したケースもありました。

Twitterによる転職

転職活動としてはtwitterをメインに活用しました。 (他にも一部Wantedlyやイベント経由での転職活動も少しありましたが)

下記の通り、拡散は希望せずに主にフォローいただいてる方に対して発信しました。

元々知っている方を中心に、ありがたいことに10~20件のDMをいただきました。 特にこのツイートをした時点ではatmaCupでの優勝もKaggleMasterにもまだの時だったので、それでもこれだけ声をかけていただけるのは驚きでした。

業界について知らないことも多かったので、10件近くのカジュアル面談を実際にしました。

個人的にこの転職の方法はかなり良かったです。

良かった点としては

  • 日頃のTLである程度自分のことを知った上での声がけなので、期待値齟齬が少ない&履歴書/職務経歴書を頑張らなくてもよい
  • 元々知っている人からの紹介のため、良い会社であることが多い(自分の会社が微妙だと思っている場合は紹介しないはず)
  • 業界的に無限に会社があり、自分で探しても良く分からない

知っている人からDMいただけるのは嬉しかったです。 特にkaggleでチームを組んだことある方から声がけいただいたのはとても嬉しかったです。

また、拡散希望しなかった理由としては、 仮にたくさんDMが来たときに対応しきれないのと、拡散すると実質転職サイトのスカウトとあまり変わらなくなるためです。 私の場合は、拡散しなくて良かったと思ってます。

10年前くらいに始めたTwitterで当時は麻雀とテニスのツイートしかしていなかったアカウントがまさか転職に役に立つとは思いもしませんでした。 Twitter続けてるといいこともあるものですね(笑)

※求める条件に合う会社が少なかったり、元々その業界のフォロワーがあまりいない場合は微妙な方法かもしれません。

JTCも悪くない

割と世間では、JTC(Japanese Traditional Company)/大企業に対してのネガティブなイメージが増えている印象があります。 (スペックの低いPCや、昭和時代と思われる新入社員研修、ハンコリレー文化など)

しかし、向き不向きなのかなと最近感じます。 「前職について」の部分で書いたところとも被りますが

  • 利益が安定しているため、研究開発にも十分に投資している(結果的に社会人PhDをとれたり、やりたいことに予算を取れたり)
  • 休みが多く、プライベートな時間を確保しやすい
  • うまく立ち回れば、テコの原理的にインパクトの大きい仕事もできる

といったところはかなり良いのかなと個人的には思います(会社にもよりますが)。

研修とかへの寛容さと自分のキャリア・能力について意識さえしてれば、選択肢として十分だと今でも思ってます。

今回の転職活動を通じて改めて感じた良い部分だったので、前職のフォローではないのですが書いてみました。

転職と育児

育児についても転職する上でかなり考慮しました。 特に私の転職タイミングと妻の復職・保育園入園のタイミングが重なっていることもあり、条件的に合わずに選考に進まなかった会社もたくさんありました。

どこの会社も会話すると「考慮するよー」と言ってくれるのですが、実際入ったらうまくいかないことも多々ある気がしています。 特に元々の会社が育児面への配慮はかなりあったため、転職した結果、家庭に大きく迷惑をかけるといったことは極力避けたくてかなり慎重でした。 私の場合以下の2つをみて判断しました。

  • 実際に子持ちパパかつ育児に積極参加している人が働いている会社なのか
  • 労働時間・働き方的に融通がきくのか

1つ目は、ベンチャーの場合まだ若い人が多く家庭持ちがあまりいない人会社もあり、そう言った場合面談した人は問題なくても、 実際に一緒に働く人たちが家庭に理解あるかと言われると別問題だと感じました。 特に育児については、仕方がないのですが、実際に理解しているつもりであっても育児を経験していないと分からないこともたくさんあると思っています。 そういった意味でも実際に似た境遇の人が既に働いているのかは大事でした。

2つ目は、保育園に通わせて共働きな以上、柔軟な働き方は大事でした。 具体的には「急な熱で保育園に迎えに行っても労働条件的に大丈夫か?」「在宅勤務が可能か?」という部分は必須条件でした。

実際今これらを十分に満たしている会社で働いているのですが、withコロナの状況下ではより助かっていて、しっかり選んで良かったと本当に思ってます。

転職とkaggle

「Kaggleが転職に役に立つのか?」みたいな話が時々Twitterでも話題になりますが、私の場合はかなり役に立ちました。 そもそも上で書いたTwitter転職ができたのもKaggleのおかげだし、Kaggleしてなかったら転職する気になっていなかった可能性すらあるのですが。

ただ「Kaggleやってれば転職できる」とか単純なことではなくて、分解していくと以下のようなメリットだったのかなと思ってます。

  • 客観的なコンペの結果があり、書類選考で落ちにくい
  • Kaggleをやる中でコミュニティに参加していけば人脈が作れる
  • プライベートな時間も積極的に学ぶ姿勢が見える

なのでKaggleやってれば転職できるかと言われるとそうは言えないし、転職のためにKaggleやるべきかと言われたら他にやったほうがいいことがもっとあると思います。 私の場合は転職にも役立つかもという思いは少しはありましたが、Kaggleは好きでやっているだけであり、それが結果的につながったというだけです。 Kaggleが役に立つというより、(ある程度仕事に関係しそうな範囲で)好きなことを極めていくということ自体は役に立つのかなとは思います。

またKaggle Expertの時に転職活動を始めましたが、仮にKaggleMasterになってたとしても結果的には何も変わらなかったと思います。(GrandMasterくらいなれば別かもですが)

私の場合は、Kaggleでの成績だけでなく、機械学習周りでの業務経験があること、面談の中での性格的なマッチ度合いが高かったといったことが重なって内定をいただけたのかなと思っています。 (選考途中でKaggleでソロ金とatmaCup優勝できたのも役に立ったのかもしれませんが、それはよくわかりません。)

今後のキャリアについて

ここについて語ると長くなりすぎるので、ほぼ書きませんがざっくりとは以下のようなイメージで考えています。

  • データサイエンスでの技術的な幅を広げる
  • エンジニアリングもしっかりできるようになる

よくあるモデリング以外に「エンジニアリング」と「ビジネスサイド」どちらを強めていくか?みたいなことにはけっこう悩みました。 が、自分自身データサイエンス関係なく、コードを書いて何かを作るということが好きなのでその純粋な気持ちを大事にしてエンジニアリングを強化したいと思っています。

年齢と市場的な部分もあり他にも考えていることは多々ありますが、そこは今回は省きます。

最後に

今回の転職活動で、声をかけてくださったり気さくにカジュアル面談をしてくださった会社の方々、相談に乗ってもらった友人など多くの方に感謝しております。 今後の長いキャリアの中で一緒にお仕事をする機会があれば嬉しいです。

また、今回の転職エントリが自分と近い境遇の誰かに役立つと嬉しいです。

そして新しい会社に入ったところでまだまだこれからなので、しっかり活躍したいと思います!! 長文を読んでいただき、ありがとうございました!!

atmaCup#2(オフラインコンペ)で優勝しました

先週末11/23に大阪で行われたatmaCupで優勝できたので、簡単に振り返りたいと思います。

なお、データの内容はNGでカラムや問題設定までは公開OKであるため、公開可能な範囲でのポエム記事になります。

atmaCupとは

atma株式会社が運営をするデータ分析コンペティションです。 今回は#0、#1に続き第3回目のようです。

1日かけて会場に集まった参加者(30〜40人)がデータから目的の値を予測する精度を競い合います。

kaggleなどのコンペでは約1〜3カ月かけて解くのに対し、 atmaCupでは1日(8時間)という限られた時間で問題を解く部分が最も大きな違いだと思います。

www.atma.co.jp

atma.connpass.com

今回のタスクについて

ユーザの睡眠時間や起床時間、アプリが計測した睡眠の質、お酒を飲んだか、アンケート回答等の情報からユーザの日中のコンディションを予測するというものです。

ユーザの日中のコンディションは、ユーザ自身が入力した数値であり、評価指標はRMSE(Root Mean Square Error)でした。

kaggleと同様train/test(public/private)に分割されており、 testデータに存在するユーザidは、trainにも含まれるもので、時系列的にはtrain=>public=>privateの順になっています。

今回のコンペの詳細は下記の記事が詳細まで記載していたので 気になる方はこちらを見ることをおすすめします。

atma.hatenablog.com

またこちらのtakapyさんの記事も非常にわかりやすかったです。

www.takapy.work

コンペ中に取り組んだ流れ

基本的なフローですが、以下の流れで取り組みました。

  1. 簡単なEDAでデータ把握
  2. パイプライン上で動くようコーディングしてベースライン作成
  3. 簡単なパラメータチューニング

以下4〜6の繰り返し

  1. 過去のコンペの経験則、データをじっくり見る(EDA)、学習の実行結果(精度、Feature Importance等)の確認しながら、仮説立てる
  2. 特に効果的だと思われる仮説の検証(コーディング)
  3. 結果(CV, LB, FeatureImportance)の確認

  4. モデル同士のアンサンブル

仮説と書くと凄そうにきこえますが、「このあたりの特徴効くんじゃね?」みたいな軽いノリのものも含んでます。

時間配分としては、1, 2, 3がだいたい1時間くらいで、4〜6のループが殆どの時間、7が最後30分くらいでした。

リーダーボード

kaggleと同じようにPublicスコアの順位が表示されるので今の自分がどれくらいのスコアかが分かります。

前半はあまり順位が上がらず、5〜10位くらいをウロチョロしていました。

途中で何個か大事な発見をして、順位を4位まで上げます。

しかし、3位までとの差はかなり大きいまま。。 このあたりで頭も回らなくなって、部屋を出てクリスピードーナツを買いに行きました。

ドーナツ効果もあり、重要な特徴量を発見し、4位のままでしたが、上位との差はだいぶ詰めることができました。

このあとまだスコアを上げますが結局、Publicの順位は4位のままで終了。

ただ、大事なのはPrivateの順位です。 Privateスコアが全てです。

Publicの順位は当てにならない事例は下記にあるので暇な方がいたら読んでみてください。

※注意: 下記リンクのコンペと違い、今回のコンペはタスク設計もしっかりしており、Publicの順位はしっかり参考になるものです。

pao2.hatenablog.com

終結

f:id:go5paopao:20191126194335j:plain

まさかのShakeUp(Publicの順位よりPrivateの順位が上がる)で、1位!!!

戦略的にどうこうではなく、ShakeDown(Upの逆)に取り憑かれているので、ビックリしました。

テンション上がりすぎて日本語が変になってしまいました。

有効だった戦略・特徴量

ユーザ毎のtarget平均からの差分を予測する

今回のタスクのtargetがユーザ自身が入力するものであり、ユーザによって傾向が変わるものでした。 また、ユーザはtrainとtestで共通でした。

targetが同じ数値でもユーザによって本当のコンディションは異なります。 (今日1日の楽しさを100点満点で表してと言われたとき、自分に甘い私にとっての80点と、自分に厳しいAさんの80点は意味が異なります)

ユーザ毎のtargetの傾向を補正して「そのユーザにとって良いコンディションだったかどうか」を予測する問題に置き換えました。

重要な特徴同士の除算

これはkaggleでのテクニックとして、よく取り上げられるものですが、LightGBMなどのGBDT系モデルでは相互作用の考慮は出来るものの、直接特徴量として変数間の除算や乗算などをしたほうが効果的なことが多いです。

とはいえ、全パターンやるのも時間かかったり、組み合わせが無限になったりするので

  • FeatureImportanceで上位に来るもの
  • 一方の単位が時間など、何となくでも解釈できそうな特徴量になること

に絞ってやりました。 今回はその中の1つが大ヒットで大きく精度が上がりました。

過去の数値と比較

これも鉄板ですが、重要そうな特徴量について前日の値、前日との差を入れたら上がりました。 ちなみに、他の方のソリューションも参考に、コンペ終了後、2日前、3日前、7日前も入れるとさらに精度が上がりました。

Rollingさせて数日分の平均や分散を入れたりするのも定番ですが、日が連続でないケースもあって実装が簡易な前日の特徴量だけにしました。 事前に関数やスニペットを用意しておくべきだったなと思いました。

ユーザ毎の特徴量

1と似ています。 ユーザ毎の傾向、ユーザにとってその数値がどうなのかが重要でした。 睡眠時間6時間としたとき、十分かどうかと言われると人によるので、ユーザの平均と比較したりするイメージです。

また、ユーザのtargetの分散(回答にどれだけブレのあるユーザか)も有効でした。 (ただし、targetの情報を使っているのでリスキーなものです。)

振り返り(よかったこと)

効果が高そうなところの優先度を上げて取り組めた

kaggleでは思いついているもの、やれるだけやるという感じがありますが、 より時間の限られた今回のようなオフラインコンペでは 「限られた時間の中でいかに効果が高そうなところに取り組めるか」 が重要になります。

普段は子持ちkagglerのため、他の方と比べるとkaggle出来る時間は本当に限られています。 そのため、時間効率よく精度をあげていくというのは日頃から意識していて、それが活きたのかもしれません。 (結果論かもしれません。) 今回のようなオフラインコンペでは参加者皆、時間は平等に与えられるのでそういった意味でも子持ちkagglerにも優しかったです。

パイプラインがうまく働いた

パイプラインは、これまでのkaggle等のコンペで少しずつ積み重ねて作り上げた自分独自のものです。 今回のコンペでは実験を繰り返す上での高速化、過去の実験結果のログ確認などで役に立ちました。

振り返り(反省)

まだまだやれることはあった

今回1位にはなったものの他の方のソリューションを聞いていると、「それやればよかった」と思えるものがたくさんありました。

まだまだ改善の余地はたくさんあって、もう少し期間が伸びていたら抜かれていた可能性も高いと思います。

他の方のソリューションを参考にしてもっと強くなりたいと思いました。

ShakeUpしたものの

今回まさかのShakeUpしたものの、しっかりShake対策が出来ていたかというとそうではありません。

もちろんPublic/Privateの分割を気にして特徴作成したりはしていましたが、明確にこれのおかげでShakeUpしたとはいえないです。

今回はShakeの神様がたまたま振り向いてくれたにしても、次はまた落ちるかもしれないので、今後もShake対策はしっかり考えたいと思います。

感想

最近kaggleでいまいち結果が残せず、人類皆kaggle masterになっていく中、金メダルが取れない日々が続いていました。 努力しても結果が出ないというのは辛いものです。

ですが、今回強者がたくさんいる中で1位になれたというのは、運の良さがあったとはいえ、久々に努力が報われた感じがして本当に嬉しかったです。 いや今も嬉しいです。

転職活動中だったので、一つ履歴書に書けることが増えたのも嬉しいです。

参加者を見てみるとGrandMaster2人/Master8人くらいいたし、物理金メダルももらえたので、これで実質Masterですかね(笑)。

嘘です。kaggleはkaggleでしっかり結果残せるように頑張りたいと思います。

最後に

今回このイベントを開催いただいたatma株式会社の皆様、ランチスポンサーをしていただいた株式会社i-plugの皆様ありがとうございました。 とくにコンペの素晴らしいタスク設計からコンペサイトの作成までしていただいた ニューヨーカーGOTO (@nyker_goto) | Twitterさんには感謝と尊敬の念でいっぱいです。

次のatmaCupも参加できそうであれば2連覇目指してがんばります!!

要注目?DeepGBM: ニューラルネット+GBDT(速報)

KDD2019のPaper一覧で気になるものがあったので紹介します。

※記載時点でまだ論文公開、発表されておらず、こちら鮮度重視の記事です。 内容に誤りがある可能性は十分あるのでご了承ください。

DeepGBMとは

データマイニングのトップカンファレンスKDD2019で発表される予定の手法です。

Guolin Ke, Zhenhui Xu, Jia Zhang, Jiang Bian, and Tie-yan Liu. "DeepGBM: A Deep Learning Framework Distilled by GBDT for Online Prediction Tasks." In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining, ACM, 2019.

発表概要

  • KDD2019のOralで発表予定
  • 1st AuthorはLightGBMのAuthor(Microsoft)

問題意識

テーブルデータをベースとしたオンライン予測タスクについて以下の課題がある。 - GBDT(勾配ブースティング)はスパースなカテゴリに対して、弱い - NN(ニューラルネットワーク)はNumericなデータに対して弱い

提案手法

  • Numericなデータに対して、GBDTでの学習をNNで蒸留する
  • Categoricalなデータは、カテゴリをembeddingして、かつ相互作用を考慮したNN
  • NumericとCategoricalのoutputをconcatしてfull connect layerにつなげる

f:id:go5paopao:20190703193250j:plain

実験結果

  • 多くのデータセットに対して、DeepFMやGBDTよりも良い結果
  • ちなみにGBDTはLightGBMを利用

※データセットMalwareというのがあったのが気になった。これまさか。。?

個人的感想

テーブルデータではGBDTのほうが良い精度が出ることが多いが、確かにスパースなカテゴリデータには強くないと思う。CountEncodingやTargetEncodingで何とかすることが多い気がする。
逆にNNはカテゴリにはembedding層でいい感じにできるイメージ。
そこでNNとGBDT混ぜてやろうってのは良さそうに聞こえる。実際kaggleの上位もほぼNNとGBDTのアンサンブルだし。

GBDTをNNで蒸留するところが、鍵だと思うけど、論文も出てないし、いまいち理解できてない。(理解するまえにブログ書いた。) Githubでソースは公開されてるので、時間ある方いたら読んで教えてください(もしくは論文待とう)

LigbtGBMの人だし、KDDで採択されてるし、信頼できそう? 要注目です。

リンク

論文

まだ公開されてなさそう。 公開されたら更新します。

github

https://github.com/motefly/DeepGBM/blob/master/README.md

概要スライド

https://youtu.be/UzXNzW2s8Pw

【kaggle奮闘記】malwareコンペ〜天国と地獄〜

kaggleばっかりやっていてブログを更新していませんでした。
kaggleやってる最中に内容ブログに書くとルール違反になっちゃうのでなかなか難しいですね。。 先日までkaggleのmalwareコンペに参加していて、かなり頑張ったコンペだったので、振り返りがてら記事にしました。

kaggleのこと知ってる前提なのと、あまり技術的な内容は含んでない日記です。

malwareコンペとは

PCの状態に関する情報から、そのPCがマルウェアに感染してたかを予測するコンペ。
コンペの特徴としては

  • trainとtestで感染したかどうかの判定日が時間的に異なる(testのほうが1,2ヶ月後)
  • さらにtestの中でもPublicとPrivateで時間が異なる
  • アンチウイルスソフトやOSのバージョンといった情報が感染率によく影響を与えるが、時間が異なる影響でtestにしかないバージョンが多数ある
  • データ量が多い(train、testともに800万レコードくらい)

といった感じ。特にバージョン特徴量の扱い、train/test(public)/test(private)で時間が異なる点が大きい。

最下位からのスタート

年末に出した最初のスコア。

AUCで0.3って... まぁ感染したPCではなく、感染してないものを予測した値を出してたので、当然の結果。

全員抜いてやる...!!
ここから全てが始まった。

育児の合間のkaggle

言い訳にしかならないのであまり書きたくはないが、10ヶ月の息子の育児もありそんなにkaggleの時間はとれない。
基本は通勤時間に情報収集やアイデア出しを行い、時間が取れたときに出来る範囲でコーティング。
kaggle自体よりkaggleの時間を作るほうが頑張ったかもしれない。いや、頑張ったのは妻である。

なんで子供が出来てからkaggle始めたんだろう?

LightGBMでの苦戦

これまでのコンペではほぼLightGBMしか使ってなかったので、今回もLightGBMで始めた。
だが、バージョン特徴の扱いに苦戦し、頑張ってる割にスコアが全然あがらない日々。
1月後半時点でLB0.691くらいだった(その時点で上位10〜20%くらいだったはず)

ニューラルネットワーク(NN)への挑戦

前々からNNでkaggleしたい気持ちがあり、今回のコンペはデータ量が多く相性がいいと判断し、NNでチャレンジ。
麻雀AIを作ってたときからNNで遊んでいたので、あまり時間をかけずモデルを作れた。

すると、LightGBMより調子がよくスコアがどんどん伸びていく。 基本的な前処理を丁寧に行い、アーキテクチャのチューニングをしたら、特に新しい特徴量をほぼ作らないまま、シングルモデルで0.695までいった。
しかもアイデアはまだまだある。これは来たと思った。

はじめてのチームマージ

シングルモデルが0.695までいった時点で、複数のNNのブレンドで0.697、LightGBMと合わせると0.698になった。(その時点で、20位台前半)

金メダルが見えてきた。

ここでチームマージを考えた。やはり何としても金メダルがほしい。

そして順位的にもマージしてもらいやすく、かつNNで精度が出ていることから、LightGBMだけでやってる人とチーム組んだら相乗効果が高いと判断した。

はじめてのチーム戦であり、近い順位の日本人であるyiemonさんに声をかけた。

今回のコンペで一番の功績はこのチームマージだったかもしれない。

yiemonさんはその時点で、LightGBMシングルで0.697を出していた。 チームマージ依頼に快諾していただき、初のチーム完成。
お互いのベストモデルのブレンドをサブミットしてみると、0.701!暫定7位まできた。

金メダルの可能性が一気に上がった。

スコア停滞期

チームマージ後、お互いの特徴量やアイデアの共有、ディスカッションを繰り返した。 それにより、少しずつスコアが伸びていくが、周りも伸びていく。
NNも論文読んで実装したり、色々やってみるがいまいち伸びない

また、この頃子供の夜泣きがピークで、2時間おきくらいに起きる。スコアが伸びてないからって泣かないでくれ。。

110位が130位になってもあまり気にしないが、7位が12位になるとすごく気になるんだね。。

kaggle masterへ王手

GoogleAnalyticsコンペ(通称Rコンペ)の結果が出る。
このコンペも語ると長くなるが、とりあえず色々省略し銀メダルだった。これで今回金メダルをとればkaggle master!!
Rコンペのメダル込みでMasterでいいの??っていう心の声は聞こえたが、いったん抑え込む。

一人合宿の始まり

3月の頭に嫁と子供が友人の結婚式のため実家に帰り一人暮らしが始まった。
またコンペ終盤だったので、そのまましばらく実家にいるという配慮もしてくれた。 嫁への感謝と寂しさと共に、生活をkaggle中心に切り替える。

さすがに取り組める時間が一気に増えた。 一人暮らしってこんなにkaggleできるのね。

特徴量探し

この頃、コンペ上位にonoderaさんやnyanpさんといった強い日本人kagglerたちが一気に上位にきていた。
何でそんな少ないサブ数でそこまでいけるんや。。

チームディスカッションの結果、これは重要な特徴量が何かあると推察し、EDA、特徴量探しにチームで努めた。

ついに神特徴量の発見

EDAのグラフをひたすらslackに貼って議論を繰り返したが なかなか見つからない。。

そしてコンペ終了10日前、 前日夜遅くまで直接ディスカッションをやったかいあって(多分)、yiemonさんがついに神特徴量を発見する。

特徴量としては、該当日のカテゴリのシェアやその変化率など。
私が見つけたわけではないので詳細は割愛。

そこから約1週間、その派生で特徴量を増やし、LGBMシングル0.707、NNシングル0.706を達成。

それぞれのシングルモデルどちらも金メダル圏内。

これは勝った。
金メダルというか賞金も現実的になった。

ちなみに残りサブ数は限られていたので、特徴量のパターン追加やFFM系のモデル作成などに出来るだけサブミットは使い、 ブレンドはぎりぎりまでしなかった。

ついにLB1位を達成

最終日前日、ここで神特徴量発見後、初のブレンドを行う。 LGBM同士ブレンドしたものとNN同士ブレンドしたもののRankAverageを利用。 (CVが信じられないコンペだったのでStackingはしていない)

きたあああああああああああああ
最下位から始まり、ついにここまできた。

ちなみに数時間後に、他の日本人チームに抜かれて最終的にはPublicLBは2位で終了。

ラストスパート

最後はやはり、ブレンド勝負。
混ぜれば混ぜるほどスコアが伸びる(迷信)。
銀〜銅メダルくらいであれば、サーバコストかかるしやらないが、 Prize圏内にいるとなれば話が違う。

後悔はしたくなかったので、 これまでやってきて精度が悪くなかったいろんなアーキテクチャのNNのモデルを作る

f:id:go5paopao:20190326123450p:plain

AWS様への廃課金。 仕事でもこんなGPUサーバ立てたことない。 まぁ賞金もらえれば、これくらいすぐに清算できるしね。

そして最終順位発表

ついにメダル、Prizeが発表される14日の9時前。
最後まで議論して悩み抜いて決めた2つのサブミッション。
Prize逃しても金はとれるように選んだ2つ。

そして9時になり、ブラウザを更新する。

。。。

 

。。。

 

。。。

 

。。。

あれ、まだスコア計算中かな?
順位がバグってるぞ?

もう1回更新してみる。

F5..

 

F5..

 

F5..

 

変わらない。

順位を確認してみる。

f:id:go5paopao:20190325210659p:plain

f:id:go5paopao:20190325210854j:plain

いや、もっと呆然としてた気がする。

2位から1500位?うそやろ?全然理解できない。

しかしtwitterを見てると皆呆然としており夢ではないことに気づく。

どうやら盛大なshake downを食らったらしい。

振り返る

masterになってるはずだったのにおかしい。 。

いまだに消化できない。。。

とまぁこんな感じで絶望しながらも、少しでも消化するためにこんなブログ書いちゃいました。

メダルすら取れてないけど、最終日にPublicLBで1位、2位までいけたことは自信につながりました。

ほんとはチーム戦の楽しさや真面目な技術的な内容も書きたかったけど、そんな雰囲気じゃなくなったのでやめます。

このままでは終われないのでどこかのコンペでリベンジしてやります。

みとけよおおおおおおおおおおおおおおお

【Kaggle】初コンペで上位1.5%に入るまでの道のりと振り返り【HomeCredit】

8月に、初めてKaggleのコンペに参加したので、その内容を振り返る。(もう1ヶ月たってしまった。。)

取り組んだ期間は8月上旬からの約3週間で参加した。

結果は7189チーム中97位。 目標が上位5%(銀メダル獲得)なのに対し、結果が上位1.5%以内だったので、初めて参加した結果としては十分だったと思う。

そもそもKaggleとは・・・

世界中の機械学習やデータサイエンスに携わっている人が、機械学習によるデータの予測・分析力を競い合うコンペとそのプラットフォームのこと。
上位入賞者は賞金がもらえたり、kaggleでの実績や称号が企業から評価され採用に有利になったりすることもあるらしい。

参加した背景

今回、Kaggleのコンペに参加しようと思ったのは、基本的には「勉強のため」。 前から機械学習とかやってはいたけど、自分の武器というには弱いところだったので、もっと極めたいと思った。

また、客観的に自分の実力測定ができるのも理由としてある。

もともと機械学習の勉強をしたり、自分でモデルを作ったときに、 「良いモデルを作れているのか、きちんと予測できていたのかわからない」という思いがあった。
例えば、麻雀のAIを作っているといっても、それが限界に近い精度なのか、実力不足なのかよく分からない。

Kaggleを通じて機械学習の勉強をしながら、実力をつけていき、そのうち自分の実力を証明できればと思った。

参加したコンペ

参加したコンペは「HomeCreditDefaultRisk」。

Home Credit Default Risk | Kaggle

ローンを申し込んだ人が返済が可能かどうかを推測するコンペであり、Kaggle史上、参加者が過去最大のコンペだった。

ちなみに成績上位者の手法については、下記にまとめている。

pao2.hatenablog.com

コンペの特徴

関連するテーブルが多数存在し、とにかく情報量が多い。
何を意味しているのか分かりにくいデータ列もかなりあり、さらには欠損データも多い。
そのためどう特徴量を作って、選んでいくかがカギとなる。

コンペの詳細については下記の記事が分かりやすかった。

kurupical.hatenablog.com

取り組んだ流れ

データ内容の理解

とにかく謎の変数が多かったので調べまくった。 変数の意味が分かっても、数字や文字列の解釈が難しいものもけっこうあった。
ローン関連の知識があると理解しやすいのだと思う。 最後まで理解できなかった変数もあった。

Kaggle内のdiscussionに「この変数何?」みたいなトピックがけっこうあったのが助かった。

特徴量の作成、選択

ここにほとんどの時間を使った。
公開されていたカーネルにあった基本的な特徴量以外としては、

  • カテゴリ変数のtarget encoding(kagglerの中では普通かも)
  • 該当する職業や年齢別の平均収入・平均借入金、それらのその人の収入の比率
  • 過去のローンでまだ支払が終わってないものがどれくらいあるか
  • 支払い終わってないローンと今回申し込んだローンの額の比率、それらの合計と収入との比率や差分

などなど。。(書ききれない)

特にこの特徴量が大きく効いた!みたいなのは少なく、地道な作業の積み重ねで少しずつ精度があがった。

上位の人は同じような特徴量を少なくとも作っていたと思う。

モデルの選択・チューニング

LightGBMをメインで使った。
今までXGBoostとかを使ってたけど、LightGBMのほうが学習時間が明らかに速かった。

ちなみに、XGBoostも今回のコンペで試したが、LightGBMの方が少し精度がよかった。
(基本的には似たような勾配ブースティングのアルゴリズムのため、精度は似たようなものになる)

Kaggleでは何度も試行錯誤で学習を繰り返すので、この学習時間が短いことは非常に大事であり、Kaggle内で使われまくっているだけある。

ハイパーパラメータの調整はBayesianOptimizationという手法を使った。 こちらも初めて使ったが、GridSearchやRandomSearchより時間効率もよく、精度もけっこうあがった。
今後も使っていきたい。

モデル同士のアンサンブル

アンサンブル周りに関しては大したことが出来なかった。
やったのは、複数の乱数シードでの学習の平均処理(seed averaging)と、 いくつかのモデルの重み付け平均くらい。

重み付け平均については、LightGBMのboosting_typeの「goss」と「dart」、XGBoostの3つで行ったが、 最終はLightGBMの「goss」単体が一番精度が良い結果となった。

詳しくは後述するが、そもそも利用する特徴量とかを工夫しないと、モデル同士の多様性が生まれず、アンサンブルしてもあまり効果がない。
特にLightGBMもXGBoostもBoosting木の基本は同じなので、似たような結果になりやすい。

うまくいったこと

特徴量の作成、選択に力を入れられたこと

データの種類が多く複雑なため、人によって特徴量の作成に差が出ると予想し、ここにかなりの時間を要した。 ここまでFeatureEngineeringを細かくやったことなかったので時間はかかったが、結果が良かったのはここがほぼ全てだと思う。

最終提出の選択が良かった

kaggleでは、今まで自分が提出した予測結果の中から、最終提出として2つ選ぶ。
仮に1位になれる予測結果を持っていたとしても、それを選ばなければ1位にはなれない。

ここの選択を正しく行えたことも良かった。
(実際あとで確認すると、一番精度のよいものを最終提出として選んでいた。)

このコンペは全体的にCV(1)よりもPublicLB(2)が高くなる傾向にあり、 最後提出をCVが高いものを選ぶべきかPublicLBが高いものを選ぶべきかというところも話題になっていた。

自分の場合、CVを信じたものを選び、最終順位がPubilicLBの700位から97位へと大幅に上昇した。

おそらくOpenSolution(Kagglerの誰かがソースコードごと公開している手法)としてPublicLBがかなり良く、CVがその割には低いものが公開されており、 それを利用した人たちが一気に順位が落ちたのだと思われる。

1:CrossValidationの略。学習用データの一部を順番にテストデータとして使って精度を確認する。
2:評価用データの予測結果をKaggle内にアップロードすることで出てくるスコア。  評価用データの結果はPublicLBとPrivateLBに分かれており、PublicLBはコンペ中にスコアが公開される。  最終順位はPrivateLBで決まるが、こちらはコンペ終了まで結果がわからない。

気づき

上位に入るには相関の低いモデルをいかにたくさん作るかが重要

相関が低くて精度がよいモデルがたくさんあると最後のアンサンブルでスコアが上昇しやすい。
感覚的には、一人の予想よりも同レベルの複数人で予想した結果を集めたほうが精度が良くなるといった感じ。

相関性の低いモデルを複数用意するためには

といったことが重要になる。

チームを組み、それぞれのメンバーが作ったモデルを集めるのも有効な手段。

そして、実際上位の手法の多くは、チームを組みながら、さらに学習アルゴリズムや特徴量セットをたくさん用意することを行っていた。

私の場合は、一つの特徴量セット・LightGBMでの学習で精一杯でそれらが出来なかった。 (ソロ参加だったというのもある)

自分の狙う順位によって取り組む部分の優先度を変えたほうがいい

時間が無限にあれば出来ること全てやったらいいが、そうもいかないので、目標すべきラインによって、どこに注力すべきかを考えたほうがいいと思った。

銅メダル・銀メダル圏内くらいであれば、アンサンブルをすることより、基本は一つのモデルの精度を高めていくことをまず考えたほうがいいと思う。
実際、上位のチームでも、アンサンブル前の一つ一つのモデルが金メダル圏内に入れるくらいの精度を持っている。

金メダルや上位入賞を狙うのであれば、一つ一つのモデルを極めながらも、相関性の低いモデルを用意することが必須で、 さらに他の参加者と差別化出来るアイデアまであったほうがいい。

基本的なコーディング力の重要性

いいモデルさえ作れたら、ソースコードはいくら汚くても問題はないが、ソースコードの質はかなり大事だと感じた。

特に、何度も試行錯誤を繰り返す上での速度が変わるので、コーディング力により限られた時間で出来ることが大きく変わる。

チームを組むわけでないなら、可読性はそこまで重要ではないが、

  • 特徴量の追加や削除、交差検証の変更、学習方法の変更などへの拡張性の高さ
  • プログラム実行時間を短縮する工夫

などが大事だと思う。

例えば、学習のたびにcsvを読み込み、特徴量を作り直すと無駄に時間がかかるため、一度作ったものをPickleなどで保存しておいて再利用することは時間短縮になる。
自分もまだまだなのでもっと効率的な方法を追求していきたい。

discussionとkernelの重要性

Kaggle内にはKernelとDisucussionというコンテンツがあり、これが宝の宝庫である。

Kernel

kernelはjupyter notebookのようなもので、kagglerたちがソースコードを公開している。
kernelのいいとことしては、以下のようなものがある - それなりの精度(上位10~20%くらい)のモデルがどう作られているか理解できる - Kagglerたちの特徴量の作り方やコードの書き方が学べる - 学習以外の手法(パラメータチューニングや特徴量選択、データの前処理手法)も学べる

Discussion

Discussionは掲示板のようなもので、Kagglerたちがコンペの課題や手法について議論している。 Discussionを読むことで、そのコンペの難しいところや注意すべき点などが理解できる。

まとめ

今回は初めてのKaggle参加についてまとめた。
はじめての割には良い結果だったが、今後はもっと上位を目指していきたい。

今も新しいコンペに取り組んでいるので、いい結果報告できるようにします!

【Kaggle】HomeCreditコンペ上位入賞者の手法まとめ(メモ)

KaggleのHomeCreditコンペに参加しました。初めてのKaggleコンペ参加です。
HomeCreditコンペは、ローンの支払が出来たかどうかを予測するもので、Kaggleの中で過去最大の参加者数のコンペでした。

私は、7198チーム中97位(上位1.5%)でした!
目標を上位5%にしてたので結果には満足していますが、今後より上位を目指すためにも、 自分の復習・勉強用に上位入賞者の手法をまとめたので共有します。

自分のコンペ振り返りについては別記事でまとめます。

前提

  • あくまでメモなので、このコンペに参加もしくは、データの内容を理解していないと解読できないところ多いです
    • 特に特徴量とかは参加していないと意味わからないと思います
  • あくまで自分用メモなので出来ていなかったところや気になったところ中心に書いています
  • 流し読みした部分もあり、解法が間違っている可能性もあります。もし何かあればコメントください

詳しい内容を見たい人は下記に上位入賞者毎の解法へのリンクがまとまっているので、こちらを見てください

**COMPETITION WRITEUP INDEX** (UPDATING!!!) | Kaggle

6thを除くTop10の内容を書いてます

上位者の全体傾向

  • モデルはLightGBMが中心だが、NeuralNetwork(NN)も使っているケースが多い
  • 特徴量の作成(Feature Engineering)に力を入れている
  • 存在しないデータ(interest rate)、欠損値(EXT_SOUCE)の推測
  • 過去の支払いについて、時系列を考慮した特徴量つくり
  • Stackingで精度をあげるために、いかに精度が高いながらも多様性のある(相関の低い)モデルを作るかが重要
  • 特徴量選択で工夫、学習手法を増やす、チームメンバーを増やして多様性を増やすなど

1st

特徴量

  • interest rate の予測モデルを作り、特徴量に利用
  • 特徴量の近い500個のデータのTARGET平均値を特徴量にする。
  • 特徴量はEXT_SOURCE_Xとcredit/annuity
  • AMT_ANNUITYと過去最大の分割支払い額の比率
  • EXT_SOURCE_3で割った値が有効だった
  • カテゴリ変数をLabelEncoding
  • previousの中でも最新のpreviousのカテゴリ変数を利用
  • いくつかの観点でaggregate
    • previousの直近N回、最初のN回、直近N日など閾値を変えてaggregate
    • installmentの1~4回目の支払い(NUM_INSTALMENT_NUMBER)のみでaggregate
    • 支払いが遅延したものをaggregate
  • REGION_ID_POPULATIONをカテゴリ変数として扱う
  • 直近のDAYS CREDIT
  • 日齢を年齢に変換して利用 AGE_INT: int(DAYS_BIRTH / -365)
  • 直近1000日のAMT_PAYMENT - AMT_INSTALMENTの平均
  • リッジ回帰による特徴量選択

推定モデル

  • NN:DAEを利用
  • stacking3層
  • 一層目 NN,XGB,LightGBM,
  • 二層目 NN,ExtraTree,Hill Climber.
  • 三層目 average
  • ExtraTreeでは、AMT_INCOME_TOTALも特徴量に追加
  • EXT_xの推測、穴埋めは効果なかった

2nd

チーム構成

推定モデル

  • LGBM,NN,RNN,CNN,RGF,CatBoost
    • チームメンバーで作るモデルが分担

Stacking/Blending

  • Adversarial Stochastic Blending
    • testとtrainを区別するモデルを作成。各特徴量セットで、そのモデルの精度を元に重み付け。使うtrainはランダムで抽出

その他

  • 同じ人が複数のSK_ID_CURRを持ってることが判明。そして、同じ人だと同じ結果になりやすいため、推定結果にたいして重み付け実施

3th

特徴量

  • 特徴量はCVでしぼって250個くらい
    • 一度数を減らすと学習時間も短く試行錯誤もやりやすかった
  • SK_ID_PREV単位での予測を特徴量に利用
  • EXT_SOURCEの推測
    • 推測値と実際の値の差分はいい特徴量に

推定モデル

  • たくさん作ったモデルのなかから、相関の低いモデルを数個選定
  • モデルはLGBM中心で、NNとXGBも
  • Stackingは、LGBM,RandomForest,ExtraTree,LinearRegressionの4つを使いaverageをとる
  • stacking時にはいくつかの元の特徴量も利用
  • stackingで使った特徴量は1つずつ増やして試した。特徴量も多くないから早い
  • ExtraTree,LinearRegressionでは、欠損値は平均でうめた

4th

  • 過去N回のinstallments and pos,bureau featuresでaggregate
  • 大量の特徴量セットから、特徴量を選択して相関の低いモデルをたくさん作る。
    • oof毎にRFEを使って特徴量選択
  • BayesianOptimizationの時の試行毎の予測結果を保存して利用した
  • revolving loan の予測結果を修正した(0.4を越えたら0.8かける)

5th

特徴量

  • 行ごとの推定
    • installment,bureau,pos,creditそれぞれ、行毎にtargetの推定を実施
    • 推定結果をSK_ID_CURR毎にaggregateして、min,max,mean等を特徴量に
  • interest_rateの予測
    • 予測interest_rateは現実的な範囲の価にしぼって利用
  • 一般的な金融リスクスコアの計算方法を調べ、それを特徴量とした
  • 一部のカテゴリ変数を数値化。例えば、NAME_EDUCATION_TYPE
  • 色んな特徴量でaggregate
    • 例 CREDIT_TYPE+CREDIT_ACTIVE

推定モデル

  • ニューラルネット
    • ユーザの過去の支払い状況を2次元マトリクスにして、1dCNN+LSTMを使ったNNを作成
    • NNの予測結果をLGBMにいれる

7th

8th

9th

推定モデル

  • xgb, lgbm, catboost, logistic regression, random forest, extra trees
  • 結果、CVとLBの関係がかなり安定
  • LightGBMについて、gbdtよりdartのほうがよかった。恐らく特徴量が多いから
    • dartで特徴量が多いときはfeature_fractionを減らすと良い
  • NN
    • Entity Embedded Neural Networksがよかった。
      • 詳細はEddy's kernel from Porto Seguroを見るとよい
    • バッチサイズは小さいほうが精度よかった
    • RankGauss Normalizationには小さいepochが必要
    • MinMaxScalerは必要
    • 層の数を増やしたほうが精度がいい
  • stackingよりaverage belendingのほうがよかった

特徴量

  • 時系列の情報に対して、速度・加速度のような値を算出。速度は前月との差分、加速度は差分の差分
  • CNT_INSTALMENT_FUTUREの遷移の仕方(基本1つずつ減るが一気に減るとまとめて返したことになる)
  • posなどで、(1-(DAYS_VALUE)/(minimum_value))のような割合を各特徴量にかけた

10th

特徴量

  • instalmentの(DAYS_INSTALMENT-DAYS_ENTRY_PAYMENT)は有効な特徴量であり、さらに直近1年のものに絞ると有効だった
  • creditの上限に対して利用した割合を特徴量に
    • 特に直近のものは有効
  • interest rateの推測を特徴量に
  • NAME_CONTRACT_TYPE別にモデルを作成
  • previousの直近N回、最初のN回、直近N日など閾値を変えてaggregate
  • EXT_SOURCE_Xの欠損値を推測モデルで埋める