atmaCup#10で2位でした
2021/3/5〜3/13の期間で行われたatmaCup#10*1に参加しました!!
この記事は以下の要素で構成されています.
atmaCup#10結果
タイトルの通りでpublic 2nd / private 2nd という結果でした.
優勝を目指していたので勝てなくて悔しい思いもありますが,物理メダルをもらえる順位を取れたのは嬉しかったです.
出来るだけ優勝争いに絡めるように,LB上で1ページ目に滞在するということを目指していました.この点は概ねできたとは思いますが卒業確定後の院生が引越し等々の準備を捨てて時間を作った結果なので,今後いざ働きはじめたときに最終的な順位も含めてこういった結果が再現できるかというとかなり怪しいです.頑張っていきたいです...
Solutionに関するOutputリンク集
私のsolutionの概要はguruguruのdiscussion上に書いていたり
[Public 2nd/Private 2nd] LGBM single model solution
スライドを作って振り返り会で発表したりしました.このとき着ていた服はgotoさんともう1人atma社員のizumiさんに卒業お祝いで買ってもらった服です...この服を着て優勝して解法話すのが個人的目標でした...
www.youtube.com*2
あとcodeは遅くなりましたがGitHubにupしました.見ていただけると嬉しいです.
https://github.com/TeruakiUeda/atmaCup10
atmaCupへのお気持ち
自分は今までatmaCupに関わってきた回数は多い方で,開催期間が1dayだった初回の #0から参加してました.#0参加時はKaggle参加歴やプログラミング歴が2ヶ月程でした.なので#10という切れ目のタイミングで入賞出来たのは個人的にはエモでした.個人的にはatmaCupと育った気持ちを勝手に抱いており,ガッツリ取り組んだコンペがatmaCupで良かったと思います.
- 開催期間1dayの超短期のコンペだったり,1~2週間だったりで参加から終了までの期間が短いので,どのタイミングで参加してもまだ解法共有の時期に課題のことを覚えている
- データが面白い,設定が色々
- コンペ期間中のDiscussionや,コンペ終了後の解法共有を日本語で見ることが出来る
このあたりがほぼ初心者だった自分が楽しく参加出来て,成長できた理由な気だったと思います.
今後もatmaCup参加します!🍺
*1:#10 [初心者歓迎!] atmaCup - connpass
*2:ぼくの発表は24:15~あたりからです.緊張でガチガチです
reduce_mem_usageの高速化
Kaggleのテーブルコンペでreduce_mem_usageという関数を見かけます.
これはpandasのDataFrameのメモリ削減用の関数で,columnに含まれる数値の大きさに応じてpandasのデータ型(dtype)を変化させ,メモリ削減を行う関数です.特徴量生成後,学習を行う直前なんかに使われます.
簡単な具体的を挙げるとonehot特徴量 (0 or 1) のcolumnにint64は過剰でint8で十分だし,データ型を変換したらメモリが8分の1くらいに抑えられて嬉しいねみたいな話です.
ただ対象のDataFrameが大きい場合は,メモリ削減自体にかなり時間がかかってしまうので,今回はその関数の高速化に取り組みました.
コード
以下提案する関数,変更箇所は後述
実装の中身は,for文を回してcolumnごとの最大・最小値と,各dtypeで表せる最大・最小値を比較して,値が変わらない範囲で出来るだけ使用メモリの小さいdtypeに変えています.
def reduce_mem_usage(df, verbose=True): numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64'] start_mem = df.memory_usage().sum() / 1024**2 dfs = [] for col in df.columns: col_type = df[col].dtypes if col_type in numerics: c_min = df[col].min() c_max = df[col].max() if str(col_type)[:3] == 'int': if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: dfs.append(df[col].astype(np.int8)) elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: dfs.append(df[col].astype(np.int16)) elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max: dfs.append(df[col].astype(np.int32)) elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max: dfs.append(df[col].astype(np.int64) ) else: if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: dfs.append(df[col].astype(np.float16)) elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max: dfs.append(df[col].astype(np.float32)) else: dfs.append(df[col].astype(np.float64)) else: dfs.append(df[col]) df_out = pd.concat(dfs, axis=1) if verbose: end_mem = df_out.memory_usage().sum() / 1024**2 num_reduction = str(100 * (start_mem - end_mem) / start_mem) print(f'Mem. usage decreased to {str(end_mem)[:3]}Mb: {num_reduction[:2]}% reduction') return df_out
拾ったコードとの変更点
変えたのはデータ型を変換する部分で,デカいDataFrameを出来るだけ触らないように,データ型変換後のSeriesを都度listに入れて後からまとめてconcatするようにしました.
- [やり方1]見かけたコード*1
dtype変換して元のDataFrameに上書き
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: df[col] = df[col].astype(np.int8)
- [やり方2]提案するコード
listにdtype変換後のSeriesを突っ込み,後でまとめてconcat
dfs = [] ... if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: dfs.append(df[col].astype(np.int8)) ... df_out = pd.concat(dfs, axis=1)
実験結果
行数:10000で固定し,列数を10, 100, 200で変化させてかかった時間を計測しました.
上書きするやり方は元のDataFrameが大きくなるほど実行時間が大きくなる一方で*2,concatするやり方は列数がこのぐらいの範囲だとあまり増加せずに済んでいます.
(実行時間は1回ずつしか回してないので多少の変動はあると思いますし,列数固定で行数を変更させたり,条件次第な部分はあると思います.)
特徴量生成前にメモリ削減する際の注意点(余談)
特徴量生成前にメモリ削減をすると望まないオーバーフローが起こる場合があるので注意が必要です.
簡単な実験とし, int8のDataFrameを作成し,2つのcolumnの和を取って8+120の計算をします.
int8のデータ型では-128~127までの値しか表現できないためやり方次第ではオーバーを起こします.
- DataFrameの作成
# DataFrameの作成, dtypeはint8 df = pd.DataFrame(data=[[8, 120]], dtype=np.int8) print(df) # 0 1 # 0 8 120
- [パターン1] sumで和をとる
print(df.sum(axis=1)) # 0 128 # dtype: int64
- [パターン2] df[col1] + df[col2]
print(df[0] + df[1]) # 0 -128 # dtype: int8
パターン2ではオーバーフローが起こり大小関係がおかしくなっているのでこのまま特徴量に使ったら精度の悪化に繋がりそうです.
特徴量生成前にメモリ削減関数のreduce_mem_usageを使うと特徴量生成の速度は上がりますが,オーバーフローが起こりうるので気にした方が良いかもしれません.
今回のコードはGithubに載せています.
実験コードも同じレポジトリ内に置いてます.
github.com
ベイズ最適化で最高のコークハイを作る
はじめに
コークハイとか酎ハイをお店で飲むと、割り方とかレモンが効いていたりとかでお店によって結構違いが出ますよね
自分好みの最高のコークハイの作り方を知ることは全人類の夢だと思います。
本記事は一足先にそんな夢に挑戦したという記事です。
手法としてはベイズ最適化を使用します。
実データで実験計画と絡めながらベイズ最適化を実際に行う記事はあまり見かけなかったので今回は、
最適化パラメータ
1. コーラとウイスキーの比
2. レモン汁の量
目的変数
コークハイの美味しさ
という2次元入力、1次元出力で実際に実験とチューニングを並行しながら行ってみたいと思います。
目次
ベイズ最適化とは
ブラックボックス最適化で使われる最適化手法のひとつです。
ハイパーパラメータチューニングに使われるOptunaの中身もその一種です。*1
入力xとそれに対応する出力yは分かっているけど、x, yの関係を説明する関数fが分からないときに
過去のデータからガウス過程回帰等*2でブラックボックス関数fの回帰モデルを作成し
その回帰モデルをもとに獲得関数の最大化を行うことで、最適化を効率的に行うというものです。
パラメータサーチで一番わかりやすい方法は
想定される全てのxについて細かくグリッドサーチ的に調べあげ、ノイズが多ければ反復的にデータを取得すること、ですが
時間や費用など多大なコストが必要な実験では現実的ではありません。
そういった実験系ではベイズ最適化による効率的探索が重要となり、応用ドメインはWEBでのABテストから地質調査、材料力学、生物学、クッキーのレシピ*3...などかなり広いです。
ガウス過程回帰(+ベイズ最適化)について数式込みで詳しく知りたい方は
www.kspub.co.jp
ベイズ最適化については
明治大学講演資料「機械学習と自動ハイパーパラメタ最適化」 佐野正太郎
ベイズ的最適化(Bayesian Optimization)の入門とその応用
シンプルなベイズ最適化について | AI tech studio
などありがたいことに資料が公開されています。
あとはみんなでPRMLを頑張りましょう。
実験系の説明
実験条件
実験で考慮しないこと(パラメータ)
コークハイの割り方以外にも味の評価に影響しそうなパラメータはいくつか思いつきますが、実験系の単純化のために今回は考慮しません
- 飲酒のシチュエーションに関するパラメータ
周囲の気温とか水温は味に影響しそうですが考慮しません
例:夏の暑い日に冷たいものを飲むと美味しく感じる
- 被験者の体調に関するパラメータ
疲労具合であったり、満腹度合いや、喉が乾いているかどうかだったりは影響しそうですが考慮しません
例:疲れているときにレモンをかじると甘く感じるとかの話
(酔っ払い具合も影響しそうですが、これについては実験にゆっくり時間をかけて、少量のコークハイを摂取するという形で対処を行っています。)
実験をやりました(本題)
実装にはGpyを使用しました。
ガウス過程回帰に使用したカーネルはシンプルなRBFカーネル
既知のデータ点からの距離をみて、入力変数の値が近いデータ同士は出力も近い値が出るよねという表現
最適化対象の獲得関数はUCB(Upper Confidence Bound)
この式は"活用"と"探索"を表現していて
活用:平均値が高いところをより調べたい
探索:データ点が少ないところを調べたい(今回はデータ数nが大きくなれば係数が小さくなって影響が小さくなる)
という感じです
入力変数についてはそれぞれ0~1の範囲にMinMax正規化を行いました。
コーラの量0~50 → コーラの割合0~1
レモン汁の量0~15 → レモン汁の量0~1
また計量カップの都合上
コーラの割合は0.1刻み(元の量で換算すると5ml刻み)
レモン汁の量は0.2刻み(元の量で換算すると3ml刻み)としました。
初期点として
(コーラ割合, レモン汁量) = (1, 1), (0, 1)
すなわち
コーラ50ml + レモン15ml
コーラ0ml (ウイスキー50ml) + レモン0ml
の2点を既知として実験を行いました。
実装コード
import GPy import matplotlib.colors as colors import matplotlib.pyplot as plt import numpy as np import pandas as pd from matplotlib.cm import ScalarMappable
""" 実験データの逐次記入 """ df.loc[0] = [50, 15, 68] #初期点 df.loc[1] = [0, 0, 44] #初期点 df.loc[2] = [25, 15, 58] #実験1回目のデータ df.loc[3] = [50, 0, 85] #2回目 df.loc[4] = [45, 0, 94] #3回目... df.loc[5] = [25, 6, 50] df.loc[6] = [50, 6, 90] df.loc[7] = [0, 15, 46] """データサイズn""" n = len(df)
from sklearn.preprocessing import MinMaxScaler """ 入力変数をMinMaxスケーリング """ scaler = MinMaxScaler() scaler.fit(np.array([0,50]).reshape(-1,1)) #0~50を0~1に df["coke"] = scaler.transform(df["coke"].values.reshape(-1,1)) scaler = MinMaxScaler() scaler.fit(np.array([0, 15]).reshape(-1,1)) #0~15を0~1に df["lemon"] = scaler.transform(df["lemon"].values.reshape(-1,1))
"""入力の整形""" x_train = np.stack([df["coke"], df["lemon"]], axis=1) y_train = df["score"].values """既知のデータをもとにガウス過程回帰""" kern = GPy.kern.RBF(2)#, ARD=True) gpy_model = GPy.models.GPRegression(X=x_train.reshape(-1, 2), Y=y_train.reshape(-1, 1), kernel=kern, normalizer=True) """モデルの可視化""" fig = plt.figure(figsize=(10,6)) ax = fig.add_subplot(111) gpy_model.optimize() # カーネル最適化 gpy_model.plot_mean(ax=ax, cmap="jet") # カーネル最適化後の予測 """データ点の描画(defaultでは見辛い)""" [ax.plot(xi, yi, marker=".", color="k", markersize=10) for xi, yi in zip(df["coke"].values, df["lemon"].values)] ax.set_ylabel("normalized_lemon", fontsize=18) ax.set_xlabel("normalized_coke", fontsize=20) ax.tick_params(labelsize=20) """color bar追加""" axpos = ax.get_position() cbar_ax = fig.add_axes([1, 0.15, 0.02, 0.8]) norm = colors.Normalize(vmin=y_train.min(), vmax=y_train.max()) mappable = ScalarMappable(cmap='jet',norm=norm) cbar_ax.tick_params(labelsize=10) fig.colorbar(mappable, cax=cbar_ax) fig.tight_layout() plt.savefig(f"2dim_gaussian_n={n}.png")
""" 作った回帰モデルをもとに新たな入力xに対する出力yをみて 獲得関数acqを最大化する点(=次に実験を行うべき条件)を探す。 """ x = np.linspace(0, 1.0, 11) acq_list = [] for i in range(6): x_pred = np.array([x, np.full(11, i*0.2)]).T y_mean, y_var = gpy_model.predict(x_pred) acq = (y_mean + ((np.log(n) / n) ** 0.5 * y_var)).max() acq_list.append(acq) next_lem = acq_list.index(max(acq_list)) * 0.2 #次のレモンの分量 print("max lemon:", next_lem) """ 入力を1次元固定して予測(レモン)""" x_pred = np.array([x, np.full(11, next_lem)]).T y_mean, y_var = gpy_model.predict(x_pred) gpy_model.plot(fixed_inputs=[(1, next_lem)], plot_data=False, plot_limits=[-.01, 1.01]) plt.xlabel("normalized coke", fontsize=14) plt.ylabel("taste score [ g ]", fontsize=14) """獲得関数acq""" acq = (y_mean + ((np.log(n) / n) ** 0.5 * y_var)) / 5 """獲得関数の可視化""" plt.plot(np.linspace(-.01, 1.01, 11), acq, color="g") plt.plot(acq.argmax() * 0.1, acq.max(), marker=".", color="r", markersize=14) plt.legend(["Mean", "Acquisition", "Acq Max", "Confidence"]) plt.savefig(f"1dim_gaussian_n={n}_lemon={next_lem}.png")
実験開始
やっとコークハイが飲めます...長い道のりでした。
初期点の条件で前述の通りコークハイを作ってスコアを付けます。飲みながら書いた当時のコメント付きです。
- コークハイ1
ウイスキー:50[ml]
コーラ:0[ml]
レモン:0[ml]
スコア:44
厳しい…普通にロックですらない、ありのままのウイスキー
香りがめっちゃ強い。飲めるけどきつい。本音を言うと5mlで良い。
この時点でコークハイというかコーラが好きなだけでウイスキーを好んで飲めるほど味覚が大人でなかったことを思い出しますが、ベイズに全て委ねて実験を続けます。
- コークハイ2
ウイスキー:0[ml]
コーラ:50[ml]
レモン:15[ml]
スコア:68
今度はレモンが強すぎる。レモン汁のコーラ風味
上記の2点の初期点をもとにガウス過程回帰を行うと次のようになりました。
見方としては上の図の等高線のようなものが平均値を表し、下図はそれを横からみたようなイメージです。
下の図を見るとまだデータが少なく、データ点同士の距離が大きいのでどの範囲でも分散が大きいですね
獲得関数が最大となる条件を見ると、次はlemon=1.0, coke=0.5の条件を調べろとのことです。
実験1回目
- コークハイ3
ウイスキー:25[ml]
コーラ:25[ml]
レモン:15[ml]
スコア:58
感覚としてはウイスキーのレモン汁わり
まだまだウイスキー成分が多い気がするけどだいぶ飲める。
この辺りで酔ってくる。
lemon=0.0の点は既知なのでその周辺は分散が小さくなっています。
上の図を見ると、右上にいくほどスコアが良いことを表しているので
現状ではコーラとレモンが多いほど美味いんでしょ???と言われている感じ
実験2回目
- コークハイ4
ウイスキー:0[ml]
コーラ:50[ml]
レモン:0[ml]
スコア:85
コーラは美味しい
点差がばらけすぎて描画がイマイチになってしまいました
実験3回目
- コークハイ5
ウイスキー:5[ml]
コーラ:45[ml]
レモン:0[ml]
スコア:94
とても飲みやすいけどアルコールも感じてちょうどいい.
コークハイとして美味しいので高得点。
右下辺りがスコア高いんじゃないかという示唆が出てきました。良い感じです。
実験4回目
- コークハイ6
ウイスキー:25[ml]
コーラ:25[ml]
レモン:6[ml]
スコア:50
コークハイ3に比べてレモン汁が減ってウイスキー感(というかアルコール感)が強い
もう少しコーラが欲しい
次の条件はコーラ=1.0, lemon=0.4となり、またコークハイ以外のものを飲まされるようです。そろそろ不安です。
実験5回目
- コークハイ7
ウイスキー:0[ml]
コーラ:50[ml]
レモン:6[ml]
スコア:90
コーラにレモンはとても合う
あくまでコークハイを作りたいので少しスコアは控えめに
コーラが多いほど飲みやすいという感覚に近い結果が現れてきました。
とはいえコークハイを飲まずにコーラばかり飲んだ弊害か、スコアの高い部分が右下に振り切れてしまいました。
実験6回目
- コークハイ8
ウイスキー:50[ml]
コーラ:0[ml]
レモン:15[ml]
スコア:46
助けてコーラ…コーラ無しウイスキーは厳しい...
次の実験条件に既に済んだ条件が提示されてしまいました。
これは雑に0.1刻みに丸め込んだことや、探索する範囲があまり多くないことが原因として考えられそうです。
上の図を見るとcoke < 0.5の範囲についてはスコアが低いので熱心に探さなくても良いことが感覚ではなく、モデリングの結果から判断出来るは嬉しいポイントです。
全部で50通り考えられる入力変数の組み合わせのうち、8回の実験でこの結果が得られるのは予備実験としては中々良いと思います。
とはいえ点数の付け方的にも右下が一番良いというのは望ましい結果でなく、coke=0.7辺りの直感的に美味しく作れそうな範囲をもう少し調べて欲しいという思いは残ります(この辺りは後述の初期点や、変数の範囲設定の問題)
ARDありver.
ここでガウス過程回帰のカーネルにARD(関連度自動決定カーネル)を使用してみます。
これまでは2つの変数コーラの割合、レモン汁の量を同列に扱っていましたが、ARDを使用するとそれぞれに重み付けをして最適化を行います。
先ほどまでのデータを使って、ARDを使用した結果の変化もみてみたいと思います。
(ドメイン知識(=僕自身のコークハイの好み)的にもレモン汁の量よりもウイスキーがどれくらい薄まるか( ≒ コーラの割合)の方が変数として重要なのでこの2つが同列であることは考えにくいです。)
一番下の図を見ると、ウイスキーの割合が多いうちは変数cokeが支配的でレモンの量に関わらずスコアが低い一方で、coke > 0.7辺りからレモン汁の量もスコアに影響を及ぼすという結果が得られました。こっちの方が納得感ありますね。
反省点
今回はコーラの割合とレモン汁の量を最適化し、ARDを使うことでそれっぽい回帰モデルを作りパラメータサーチをすることができました。ただしいくつかの改善点や反省点があり
・初期点の荒さ
今回は初期点にウイスキーonlyの点と、コーラ+レモン汁の点のみを使用しましたが、最初からウイスキーとコーラの比が1:3くらいのコークハイとして一般的な条件を初期点として設定しておくことでより効率的な探索が可能になったと思います。(結果的にコーラばっかり飲んでたのはあまりよろしくない)
・説明変数の設定
コーラの割合とレモン汁の量というシンプルな変数を使用しましたが、実際の実験を通してアルコール濃度( = ウイスキーの薄さ)がピンポイントで効くように感じたので変数を変えることで結果が変わるやもしれません。
また想定外の取りこぼしを防ぐために探索範囲を広めに取りましたが、今回の結果をもとに、今後は探索範囲を狭めても良さそうです。(これもデータを取得したから判断できることですが)
さいごに
今回の実験ではある程度雑な条件設定でも個人的に納得できる結果が得られ、実験計画と絡めたベイズ最適化の面白さを確認しました。
このまま続けていけば少ない試行回数で絞り込むことができそうです。
とはいえ使える分野、場面が多い分ドメイン知識を持った上で運用しないと最適化する必要のない変数をチューニングするような結果になるかもしれないので注意が必要であると思います。
次は体力があれば今回の結果をベースに角ハイを使ったマルチタスクベイズ最適化を行って更にコークハイを突き詰めたいと思います。
めちゃくちゃ長くなりましたが読んでくださりありがとうございました。
*1:ハイパーパラメータ自動最適化ツール「Optuna」公開 | Preferred Networks Research & Development
*2:最近はNeural Processesなんかもあるみたいです
*4:買ってから2年経っていたので14年モノかも
*5:全お菓子の中で2番目くらいに美味しい
Kaggle Days Tokyoに参加しました!!
こんにちは!! atmaインターンの植田(@tellmoogry)です
今回は12月11~12日に開催されたKaggle Days Tokyoに会社で行かせてもらえた*1のでその参加記を書きたいと思います!
基本的にはふわっと発表の概要+僕のお気持ちの記事です。
詳しい発表内容は各発表者様の公開されているスライドをご確認ください。
Kaggle Days Tokyoについて
Kaggle Daysは簡単に言うと年に数回(4~6回)世界各地で行われるKaggleの公式オフラインイベントです。前回はChinaで行われました。
Day1はGrandMasterやMasterの方々のプレゼンテーション, ワークショップがあり、Day2は日経電子版のデータを使用したオンサイトコンペが行われました。
Kaggle Days Tokyoの構成(https://kaggledays.com/tokyo/より転載)
Kaggle Days Tokyoについての説明はDay1のオープニングで説明があり、今回はGrandMaster8名, Master37名, Expert65名を含む240名が参加し、チケットはなんと2日で完売したそうです!!
以下ではDay1について自分が参加したプレゼン/ワークショップについて時系列順に簡単なまとめと自分の感じたことを書いていこうと思います。
Day1
Day1はプレゼンとワークショップで構成されています。
ワークショップは事前に参加希望を出した上で抽選がある*2んですが,私はワークショップ「Practical Tips for handling noisy data and annotation」に参加することができました!!
(プレゼンもワークショップも全部行きたかったのに身体が足りなくて辛かった)
また、発表は日本語と英語がありましたが日本語→英語、英語→日本語については同時通訳があり優しいイベント設計でした。
( とはいえ専門的な話については通訳の方を介するよりも素直に英語の方が理解しやすかったりしたので、英語力がないと辛い場面も多いと思います*3 )
Leveling-up Kaggle Competitions
【概要】
KaggleのCTOによる、Kaggle全体のコンペに関する発表でした
kaggleで使われてきた手法が年によって変わってきたと共に、コンペの形も変わりますよというような話があり、新たにsimulationコンペという名前が出てきました...!
Prediction Competition(csvを提出するもの)
→Code Competition(kernel, notebookでコードを提出するもの)
→Simulation Competition(??? 強化学習???)
【お気持ち】
コンペの種類と同様に開催数も増えるなら無問題ですが、そうでない場合
コンペ参加のジャンル(table, image, NLP, ...)を選んでいたら参加する機会自体が減ると思うので、今後kaggleで勝つために幅広いパワーが必要になる気がしますね
Essential techniques for table competition
【概要】
GMのonoderaさんによるテーブルコンぺのテクニックについての発表でした
はじめにテーブルコンペ全般でやるべきの初動についての話といくつかのテーブルコンペで効いた発想についての紹介があり、加えてテーブルコンペを今後始める人に勧めたいコンペを3つ紹介していました。
◯テーブル全般の話
- 統計量をみる
- 連続値のtop10の値をみる
- Feature vs Target
- Target meanの棒グラフ確認
- ベン図をみる
◯テーブルコンペsolution
- idでsubset (instacart , KDDCUP2015)
- sorting(Home credit)
◯おすすめテーブルコンペTOP3
- 1st : instacart
レコメンドタスク, 指標への理解が必要, 実務的にも役立つ - 2nd : plastic
データが綺麗, trainデータが少ない - 3rd : otto group
マルチクラス, アンサンブルの練習
【お気持ち】
手法の点ではidごとの傾向をみたり、隠れIDを特定したりなどデータを細部まで見ることを大事にしているのが印象的でした。
(自分のことを振り返ってみると、idごとのtarget平均を見て特徴的なidを見たりすることはありますが、サイズが大きくなると細かく見ることを疎かにしがちなので改めてやるべきことをやっていないですね...)
オススメのテーブルコンペも膨大な過去のコンペの中からどれが勉強になるのかは中々分かりにくいので参考になりました。レコメンドのコンペは珍しく、他のタスクも経験して引き出しとして持っておきたいので、上から順にlate subをやってみたいですね!!
Hosting Kuzushiji the Competition
【概要】
くずし字コンペ主催者によるコンペ開催に関する発表でした
【個人的要点】
- 日本には古文書が沢山あるけど読まれていないものが数多くあり、その課題解決をしたい
- コンペ開催にかかった準備期間は5ヶ月
- 誰でも参加できるコンペ設計にしたかった(→日本人は3位だったので設計はうまくいった)
- 目的である文字認識に集中してもらえるように評価指標の設計を行った
- 主催者側も予めモデルを用意しており、そのモデルは12位相当の精度だった
- kagglerは何でもやるからleakageは避けられないと判断しメダル付与のないplaygroundのコンペになった(ここ少し僕の理解が甘いんですけど、多分勝つためにtest dataにある古文書自体を探し始める人たちが出てきて本来の文字認識のタスクから逸れることを避けるためだと解釈しています)
【お気持ち】
くずし字コンペが開催された頃は日本のいわゆる古文書に書かれたくずし字を読み解くコンペということで、開催された当初は、ドメイン的な観点で日本が有利そうだし興味あるけどメダル付与の対象外か〜というような意見でTLが賑わっていていた印象があり参加はしていませんでした。
ただ今回の発表を聞くと主催者側の非常に熱い想いによって作られたコンペだったんだなという風に印象が変わりました。
実務ではお客さんの課題を解決できる喜びだったり、課題を解決した先にあるものを一緒に想像できるという点が1つのモチベになりうると思います(働きが浅いので妄想です)。
今回の発表はモロにそういう部分を刺激するもので、主催者の気持ちを聞いてくずし字コンペ出れば良かったなと思った人は少なくないと思います。
これ以外のコンペについても、コンペが終わってからではなく、始まる前に主催者側の熱を聞く機会があっても良いと思わせる内容でした。*4 *5
Practical Tips for handling noisy data and annotation
【概要】
RKさんによるラベルノイズの取り扱いに関するワークショップでした。
怪しいラベルがついている場合や、アノテーションの粒度の違いなどから起こりうるラベルノイズへのアプローチの有効性をQuick Drawコンペで実験するという内容でした。
【発表キーワード】
* Mixup
* batch sizeを大きく
* Distillation
【お気持ち】
まずアプローチのが効いたかどうかの判定するための実験系が丁寧に作られている点と参考文献等の資料が充実しているのが印象的でした。
またラベルノイズ自体は人間がデータのアノテーションをしているようなデータでは広く起こりえそうな話で、かなり汎用的な内容だったのではないかと思います(実際発表の中で画像に限らず、manifold mixup等テーブルでもというお話がありました)。
ラベルノイズについてちゃんと考えたことがありませんでしたが、Hardラベル(0 or 1みたいなやつ)でのラベルノイズはclassごとのデータ数が少ない場合は影響が大きそうで、それをsoftラベルに置き換えるという発想は広く使えそうです。(pseudo labelong が効く場合、こういったsoftラベル化も要因のひとつになっているのかもしれませんね。調べたい)
How to encode categorical features for GBDT
【概要】
JackさんによるGBDTでのcategorical featureの取り扱い方が精度に及ぼす影響に関する発表でした。
カテゴリの水準数とnum_leavesの関係について細かく実験をなさっていて、target encodingの力強さを感じさせるものでした。
【お気持ち】
特にuselessなカテゴリが混ざっていても実験の中では悪影響を及ぼさなかったという結果が興味深かったです。
(カテゴリごとのデータ数が足りてないものは雑にまとめてしまって、target encoding使ったら良さそうですね。)
普段僕がカテゴリ変数を扱うときは横着してLGBMのcategorical featureを使用していましたが、実際にkaggleで勝つためにはtarget encodingを積極的に試す必要がありそうです。
target encodingにはcatboost的なordered TSであったり、cross validationのfoldごとにやるか、cross validationのfoldに合わせるやり方など様々ありますが、target encodingのやり方が及ぼす影響についても気になるところです...!
専業Kagglerの一年半 & LANL Earthquake Prediction 3rd place solution
【概要】
カレーちゃんさんによる発表で、前半部分は専業kagglerという職業の生活について、後半部分は地震コンペに関する内容でした。
後半の地震コンペの3rd solution解説では、単にsolutionの説明をするだけでなく、そこに至るまでの失敗も含めスコアの推移と共に試行錯誤の説明なさっていていました。
【気になったトピック】
- 上位solutionを全て自分のものにすることが強くなるためには必要
Kaggleコミュニティ最高
LB proving
- Discussion見落とさないこと
- hostの論文をチェック
- チームマージは勉強になる
- leakがあってもそれ前提で戦うことは楽しい
【お気持ち】
前半後半どちらもkaggle愛に溢れたユニークで面白い発表でした!!
また、発表者のカレーちゃんさんはupuraさんと一緒にnovice向けのkaggle本を出版されるそうです👀
Feature Engineering Techniques & GBDT implementation
【概要】
Kaggle本著者の一人であるthreecourseさんによるGBDTに関する発表でした。Kaggle本の話、Kaggle本に書かれているような特徴量エンジニアリングの話に加えてGBDTの実装kernelの公開の話まで内容盛り沢山でした。
【お気持ち】
Kaggle本は今となってはテーブルデータを学ぶ上で読まない理由がないと思いますが、そんな本が日本語で出版されていることは学習コスト的に非常に恵まれていると改めて感じました。
売れた理由の考察が面白くて、Kaggleのキャッチーさに加えて、初級者向けの本に比べ中級者向けの本が珍しいのが要因になったというお話でした。*6
XGBoostの実装会を最近関西の人たちでやっていたので、GBDT実装の話は凄くタイムリーでしたが、クオリティにびっくりしました。
Pythonでの実装だったり論文の解説記事だったりが充実しているので、これはもう全人類GBDT実装するしかないですね...
ML modeling with AutoML Tables
【概要】
AutoMLに関する発表でした。
【お気持ち】
理解が浅いんですが、今後はAutoMLが使用したモデルやモデルのパラメータを見ることが出来るようになるようで、feature importanceと合わせて分析のアイデア抽出器として使えそうです。
データ分析者が手を動かす際の初動で用いて、AutoMLの結果から重要な変数やデータセットの特性を解釈するというような使い方でしょうか...
atmaCupにも毎回AutoMLが参戦しているので今後の激戦に期待ですね🏳
My Journey to Grandmaster Success & Failure
【概要】
senkinさんによるGMになるまでの1年半の振り返りに関する発表でした。
アプローチももちろん凄いんですが、そもそもテーブルとNLPを中心にコンスタントにコンペに出続けているところが強さの源に感じました。コンペに出る 大事
【気になったトピック】
- 外れ値予測モデルでモデル分け
- テキストのように取り扱う(Tf-idf, Word2vec)
- テーブルデータをグラフ的に取り扱う
- NUll importanceで特徴量選択 ...などなどEloコンペを中心に他にも数多くの手法の紹介がありました
【お気持ち】
この辺りのテキストやグラフの考えをテーブルに流用する発想は、知っているだけでは結局何もできないので、画像とかNLPとかドメインを選ばずにひたすらやらないと強くなれないなという気持ちになりました。コンペに!!出よう!!
How to succeed in code(kernel) competitions
【概要】
GMであるThe Zooのdottさんによるカーネルコンペについての発表でした。
カーネルコンペ自体はあまり馴染みがなかったんですが、単純にコンペ全般的に学びとなるトピックも数多くありました。
【気になった言葉】
- Try more, fail fast
- In CV we trust
- Teaming strategies
【お気持ち】
(チームマージの話は色んな議論があると思いますが、メダル取得目的の話と学び目的の議論は分けて考えるのが健康的で、学び目的ならチームマージは外せない選択肢だと思いました。(誰かマージしましょう))
コードの最適化あたりもtrain, testデータがあまりにも大きい時にも使えるので自分用に用意しておくべきですね(ASHRAE*7の話をしています)
正直僕はGMレベルになるとこれまでの経験でそこまで試行錯誤しないと思っていましたが、(もちろん試行錯誤のコスパの良さは違うと思いますが)GMが沢山試してやっているならnovice, contributerは1つのコンペに時間めっちゃ割いてやらないと一生差は埋まらんなと思いました。
さいごに
Kaggle Days Tokyo Day1の僕視点での振り返りは以上となります。
全体を振り返ると強い人がやることやっているんだから、やらなきゃダメだなという気持ちです。
イベントとしてはKaggle Daysが大事にしているデータサイエンティストの交流という面が随所に感じられ、その場にKaggle参加者しかいないという異様な空間が非常に刺激的で楽しかったです。
個人的には今後、当面は1つのコンペにしっかりフル参加しながら、今までやって来なかった画像, NLPをチームで参加しながらkaggleを続けたいと思います。
それぞれの発表資料は公開されているものも多いので、振り返って自分のものにしたいと思います!
次のKaggle Days は2020年3月にドバイ開催らしいのでそれの参加記も書けると嬉しいですね!!!!!!*8
以上です。
*1:参加費、移動、宿泊、諸々出してもらいました…!
*2:弊社のnyker510さんは申し込み忘れてました
*3:辛かった
*4:もちろん基本はメダルや賞金目的なのでleakageがあれば使わないという選択肢はありませんが...
*5:https://www.kaggle.com/c/santander-customer-transaction-prediction/overview
*7:https://www.kaggle.com/c/ashrae-energy-prediction/overview
*8:会社のお金??nyk510さんのお金??で行ける説が出ている
atmaCup#2に参加しました&コンペ振り返り会でLTしました
コンペが終わってからしばらく経ちましたが、11/23に行われたatmaCup#2に参加しました!
インターンの身でありながら純粋な参加者として参加させていただきました🙏
結果は参加者39人中 Pubric 1位 / Private 4位 でした!
強い方々が参加する中でいい結果を出せて嬉しいです!
会社ブログに投稿したコンペ概要と参加記↓↓
atma.hatenablog.com
そして12/6に行われたamtaCup恒例の振り返り会では「atmaCup#2 上位解法を
GBDTの気持ちになって考えた」というタイトルでLT発表しました
発表スライドです↓↓
speakerdeck.com
コンペ当日の雰囲気や参加者の構成等は下記会社ブログをご覧ください
https://atma.hatenablog.com/atma.hatenablog.com
ここからは(も?)個人的な話ですが
コンペの中でatmaCup#0で優勝した際の物理金メダルの授与がありました。そしてコンペにはkaggle本著者のJACKさんとthreecourseさんがいらっしゃっておりkaggle本にサインをして頂きました!どちらも大事にします
次はデータにちゃんと向き合ってprivate入賞を目指したいと思います!
AtCoder ABC146のC問題を二分探索を使わずに解いてみる
AtCoder ABC 146のC問題を二分探索を使わずに解いてみました
言語はPythonです
atcoder.jp
整数Nを買うために必要なお金はA×N+B×d(N) [円]ですが
予め桁数を求めることで
A, B, d(N), xはそれぞれ定数となるので
以下実装
"""標準入力""" a, b, x = map(int, input().split()) """Nが取りうる桁数digitsを求める""" for n in range(1,10): digits = 10**n if (a*digits + b*len(str(digits))) > x: digits = 10**(n-1) break """ if文の中身 整数Nが上限(10^9)を超える場合:10^9を出力 購入できる整数Nがない場合:0を出力 digitsを超える桁数をとる場合:ans-1を出力(想定桁数に戻すため) """ const = x - len(str(digits)) * b ans = const // a if len(str(digits)) >= 10: print(10**9) elif ans <= 0: print(0) elif len(str(ans)) > len(str(digits)): print(ans-1) else: print(ans)
最後の出力部分ではコーナーケースごとにif文で分けています
elif len(str(ans)) > len(str(digits)): print(ans-1)
特に最後のif文部分についてもう少し詳細に書くと
例えば入力がa = 100, b = 1, x = 1001の場合
最初に桁数を求めた時点ではdigits = 1(0~9の値しか取らない意)になりますが
const = 1001 - 1 * 1 = 1000
ans = 10
となり矛盾が起ります(n敗)
素直に二分探索でやった方がいいですね
おまけの二分探索
a, b, x = map(int, input().split()) """にぶたん""" minim = 0 maxim = 10**9 + 1 while maxim - minim > 1: int_ave = (minim + maxim)//2 if (a*int_ave + b*len(str(int_ave))) > x: maxim = int_ave else: minim = int_ave print(minim)
【SIGNATE】マイナビコンペLoser's Solution
今回参加したSIGNATEの学生限定マイナビコンペについて振り返りを書きます
302チーム中最終34位と情けない結果に終わったものの、一応頑張ったんだぞって形跡を残したかったので、戒めとして記録を残したいと思います。
記事の構成
- コンペ概要
- 試したこと
- 反省
コンペ概要
コンペのタスクは外部データ使用アリの東京23区賃貸物件の賃料予測でした
おおよその賃料は10~20万円前後ですが、一部含まれている高級物件では100万円弱のものもあり、一番高いもので250万円の物件がtrainデータに含まれていました。
またtrain, test内には同一建物の物件情報が含まれており、同一建物であることを上手く特徴量にすることが必要でした
・評価指標
本コンペでの評価指標はRMSE(Root Mean Squared Error)
RMSEは外れ値の影響を受けやすい指標です。
誤差の二乗(の平均のルート)をスコアにするので、大きく予測を外しうる超高額物件を全力で当てにいく必要がありました
例:予測誤差が与えるスコアへの影響
100個の物件で2万の誤差 = 1つの物件で20万の誤差
・Kaggle本の存在
これはコンペとは直接関係ないですが、コンペ期間中になんでも書いてある本こと「Kaggle で勝つデータ分析の技術」が発売されました。
僕も買って読みましたが、TLを見た様子だと全人類買っていたので、定石は全て試し、他人がやっていないアイデア特徴量や、ドメインに特化した特徴量、モデルの組み合わせが必要だと感じました
以上をまとめて本コンペでの争点は主に次の4点だったと思います
- 同一建物の有効活用
- 外部データの使用
- 外れ値の取り扱い
- アイデア特徴量
この4点を踏まえながら以降は自分がコンペ期間中に試したことを書いていきます
以下Loser's Solutionです
試したこと
前処理
・単語の分割
3LDK → [3, LDK]
2階/12階建 → [2, 12]
東京都港区赤坂a-b-c → [港区, 港区赤坂, 港区赤坂a]
間取りは順序変数に(R < K < DK < LK , LDK)
所在階のカラムは書き方として以下の3パターンありました
- ◯階/◯階建
- /◯階建
- ◯階建
◯階建としか書かれていないものは一戸建てらしきものが多かったので一戸建てフラグを作りました。これはgroupbyで効きました
・記入ミスの訂正
同一建物の中で明らかに値段がおかしいものや、賃料/面積の値が高すぎる、低すぎるものについては修正することで手元のCVがちゃんとしたものになりました。
特徴量作成
基本的な特徴量
・単語ごとにOne-Hot
・Tfidf
・count encoding
・数値データをビニングしてカテゴリ化
・groupby
・四則演算(数字はとりあえず割る、引く。統計量取ってからも割る引く)
・その他色々
家賃ドメイン
特徴量生成にあたり家賃に関するドメイン知識を調査しました。
家賃情報について書かれたサイトはいくつかありますが、以下の考え方を参考にし、直接賃料を出す特徴量作りを目指しました。
賃料 = 単位面積の賃料 × 面積 × 方角倍率 × 階数倍率
方角:南向きが高くて北向きが安い
階数:上に行くほど高い
これらに加えて建物構造等の特徴量はカテゴリとしてではなく順序変数として扱いました。
またこのコンペの前に行われていたSIGNATEの土地コンペの上位解法を参考にバスと徒歩の距離スケールを合わせました。
(この辺りの事前知識と前処理と簡単な特徴量のみ作った際のfeature importanceから面積、位置情報が重要な特徴量だと感じたので、新たな特徴量を作ったらとりあえず面積と混ぜ、位置情報は建物から区まで様々な粒度で作るよう意識しました。)
同一建物の特定
同一建物の中でも表記揺れが多かったため、NULLが少ないカラムの表記揺れしにくい部分をIDに使いました。
建物ID = 所在地の◯丁目部分まで + 建物階数 + 築年数の年数部分
建物IDは同一建物内での回帰、target encoding、fold作成に使用しました。
回帰については、同一建物なら単位面積の賃料は同じだろうという仮定のもと建物ごとに、面積と階数情報だけを使ってモデルを作成しました。
ただ意外と本筋のLGBMでそこそこ予測ができていて、線形回帰は全体として精度が中途半端だったので使い所を持て余していました。
(ここでの回帰はtrainのRMSEをみて精度が悪いやつは使わない、外したら困る超高額物件だけ結果を用いる等でも良かったのかなと終わってみて思います。)
外部データの使用
外部データは緯度経度に加え、下記の2つのサイトから取得しました。
・地価データ
・駅の利用者数
・区ごとの一畳辺りの家賃【円】
・区ごとの昼夜人口比【%】
作った特徴量
・地価データ×面積
・家賃×面積, 方角, 階数 (駅の利用者数で重み付け)
・駅利用者数/駅からの距離
・住所、区、駅の緯度経度をGMM
・etc...
外れ値の扱い
今回のコンペと同様に評価指標がRMSEで外れ値の扱いが問題となっていたKaggleのEloコンペでの解法、特にえじさんのblogを参考にしました。
Eloの上位解法の中に、事前に外れ値予測の2値分類モデルを作成してから、予測結果を特徴量に加えるものがあり、そのアイデアを参考にしました。
マイナビコンペでは外れ値(ここでは賃料上位90%以上くらいのもの)の中でも取りうる値の幅が広かったので、一旦回帰モデルで予測して、予測結果を閾値で分けて3つに分けました。
0:上位85%まで
1:上位15%(賃料17万円以上)
2:上位0.2%(65万以上)
ただ、3値分類は最終日に取り組んだため良い閾値に調整できずに手元の結果と、LBとの結果に大きな乖離が生まれる結果となりました。
この辺りLBとの相関を見ながら調整する必要のあるものは序盤から試すべきでした*2
その他特徴量
・地価が高い地域からの距離
( http://tokyokante.b8.coreserver.jp/ 公益社団法人東京都不動産鑑定士協会HPより)
東京都の地価分布は中央区, 千代田区, 港区あたりを中心としてそこから離れていくにつれて下がっているようだったので、
「東京駅、池袋駅、渋谷駅、新宿駅、銀座、白金台」からの距離・角度の特徴量を作成しました。
これらはそこそこ効いて、一番効いたのは池袋駅からの距離でした。
・target encoding
賃料のtarget encodingはleakすらせず全然ダメで、単位面積の賃料でのtarget encodingは効きました。
・ モデル
使用したのはLGBM
foldの切り方は所在地でStratified-Kfold
objective:poisson
試したモデルは下記の通りです。
・外れ値ありで学習(手元cv:12000~14000)
・外れ値なしで学習、外れ値部分は外れ値モデルで補完
・target encodingアリのモデル(手元cv:15000前後)
・外れ値ありで学習、建物IDが同じものは建物毎の線形回帰の結果を補完
パラメータの調整はnykergotoさんの記事を参考に各モデルごとに調整しました*3
最終submitはそれぞれの結果をstackingしてtrain, testで建物の被りがあるものは線形回帰の結果で補完したものを選択しました。
・結果
結果として選んだsubはPublic:50~60位、Plivate:34位でした
最終日に作った特徴量(外部データ周り、外れ値) を入れることで手元のスコアは大きく上がりましたが、LBとの相関が取れておらず最後までスコアは伸び悩みました。
原因は外れ値予測特徴量の分布じゃないかと思うんですが、出してみないと分からないのでlate submissionさせて欲しい...
やり残したこと、試したかったこと
・目的変数を[0, 1]にしてxentoropy
・pseudo-labeling
・T-GAN
・LGBM以外のモデル
反省点
・特徴量でもモデルの面でも優先度の高いものから試して、毎日上限まで意味のあるサブミットすべき
・先人が効いたと書いてるものは全部やる
・(特徴量のindexずれ、効いていた特徴量の入れ忘れなどの)ミスに対応できるように実験結果を綺麗に残す*4
以上です。次はkaggle頑張ります。
書いてたやつをそのまま載せただけですがコードはgithubにあります
*1:過去に参加した、Sport Analyst Meetup(https://spoana.connpass.com/)の概要欄「スポーツデータを分析したいけどデータがない人向けのデータ取得方法」の項目にデータセットの主な収集先がまとめられており、sport関係なく参考になると思います政府統計もここで見つけました
*2:train, testでの3値の分布の違いに気づいたのがコンペ終了直前でどうにもならず辛い思いをしまいた。
*3:困ったことがあればとりあえずnykergoto’s blogを見れば半分は解決する印象があります。お世話になっています
*4:日付、使用した特徴量、パラメータ、各foldでの予測値あたりは一つのファイルにまとめたい気持ちが募りました