CADDi Tech Blog

モノづくり産業のポテンシャルを開放するCADDiのTech Blogです。

キャディチームが Sansan × atmaCup #12 で 9th になりました

はじめに

先日行われた atmaCup #12 にて、「CADDiチーム立ち上げ期MLE・DS積極採用中」チームが 245チーム中 9位 になりました。

惜しくも入賞は逃してしまいましたが、コンペティション内でチームとして参加していた中では最も良い成績を残す事ができました。

コンペティションの詳細な内容には触れる事はできないのですが、1週間という短期コンペにチームで参加した経験を振り返り、今後についての考察をまとめて何かの学びになればと思い、本記事を執筆しています。

atmaCup × Sansan戦略

今回は社内から猿田(@srt_taka)と竹原(@myaunraitau)、社外から河合(@vaaaaanquish)を加え、3人での参加しました。

前提として3者とも、機械学習エンジニアとしての業務経験、コンペティション参加経験を持っていたため、最初の数日はいくつかの特徴量 + LightGBM + 5fold CVで各々が進んでいました。 いくつか特徴量に関する知見を交換しながらも、各々submitする形でした。

中間日辺りから、LightGBMが伸び悩みはじめ、互いの特徴量作りも手数が減ってきたため、河合がtabtransfomer、テーブルデータを画像化した上でのCNN、竹原がtransformer等を試しはじめました。 最終日2日前にそれらをマージした上で最終スコアを出し、post processの処理を書き始め最適化を行う事で最終スコアとなりました。

良かったこと

今回短期のコンペティションにチームで参加するにあたって、手数が他チームより多かったと思います。 特に短期のチーム戦では、各々の知見を集約しながらも、「手数を増やす」ことが重要だったと感じます。

今回上位陣は、多くがDeep Neural Networkに関連した手法を用いており、私達もCNN、Transformerを試す事で大きくスコアを向上させる事ができました。 仕事や家庭もある中、短期間のコンペティションでは、多くの論文をサーベイしたり、新しいライブラリをガツガツ導入するといった事は単独では難しい部分も多くあります。

その点において、今回チームメンバーの知見を集約し、様々なツール、手法に取り組めた事が、互いの知見向上の意味でも非常に良い体験でした。

利用したツール

具体的な手製の特徴量については触れられませんが、特徴量生成、特徴量選択、モデリングにおいては、LightGBMやsklearn、PyTorchに加えて以下のツールを利用しました。

KaggleMasterであるYuki Nakatsuka(@ynktk1)さんが公開しているkaggle_utilsです。 前処理で有用な2次特徴量変換が多く実装されており、過去のatmaCupでもKaggle Grand Masterである@takuokoさんがソリューションで利用していました。

以下のように、pandas.DataFrameを入力とし、簡易にCategoryEmbeddingやTargetEncoding、Aggregationを実装できます。

# pip install https://github.com/Ynakatsuka/kaggle_utils
from kaggle_utils.kaggle_utils.features.groupby import GroupbyTransformer
from kaggle_utils.kaggle_utils.features.groupby import DiffGroupbyTransformer

param_dict = [
    {'key': ['id'], 'var': box_feat_for_agg, 'agg': ['mean', 'max', 'min', 'std', 'nunique', 'rank']},
    ...
]

gt = GroupbyTransformer(param_dict = param_dict)
gt.fit(df)
df = gt.transform(df)

gt = DiffGroupbyTransformer(param_dict = param_dict)
gt.fit(df)
df = gt.transform(df)
...

特徴量が増えてくると、Aggregateの実行時間がかかるため、PFNさんが公開しているxfeatを利用しました。 こちらもkaggle_utils同様に、TargetEncodingやAggregationを簡易に実装できるものですが、いくつかのFeatureSelectionのアルゴリズムが実装されているだけでなく、入力をcudfにする事で高速に前処理を行う事ができます。

事前に作った1次特徴量をcudfに変換し、AggregationやFeatureSelectionを行いました。

import cudf
from xfeat import aggregation
from xfeat.pipeline import Pipeline
from xfeat.selector import (ConstantFeatureEliminator,
                            DuplicatedFeatureEliminator,
                            SpearmanCorrelationEliminator)

cdf = cudf.DataFrame.from_pandas(df)
cdf, agg_cols = aggregation(cdf, group_key=key, group_values=cols, agg_methods=['mean', 'max', 'min', 'sum', 'std', 'rank'])

selector = Pipeline([
    DuplicatedFeatureEliminator(),
    ConstantFeatureEliminator(),
    SpearmanCorrelationEliminator(threshold=0.9),
])
cdf = selector.fit_transform(cdf[cols])

GPUに乗せる事で、半日程掛かっていた前処理が15分前後で終わるようになり、コンペティション中の作業効率促進に繋がりました。

竹原がLightGBM単体でsubmitを繰り返し様々な特徴量の実験を行っている間に、河合がAWS Labsが公開しているAutoGlounを用いてstacking、bagging、Out of Folds(oof)の生成を実行していました。

from autogluon.tabular import TabularPredictor

# 利用するmodelの指定
models = ['GBM', 'CAT', 'XGB', 'NN', 'LR', 'KNN']

predictor = TabularPredictor(
    label=label,
    eval_metric='f1_macro',  # 今回のコンペティションに合わせて
    problem_type='multiclass',
    groups=groups  # group fold
)
predictor = predictor.fit(
    train_df,  # validationを自前で設定する場合は tuning_data=val_df
    time_limit=time_limit,  # N sec以内にfitが終わるようモデル選択が行われる (default=0)
    presets='best_quality',
    num_bag_folds=num_bag_folds,
    num_bag_sets=num_bag_sets,
    num_stack_levels=num_stack_levels,
    hyperparameters={x:{} for x in models}
)

# oofの取得
oof = predictor.get_oof_pred_proba(train_data=train_df)

# predの取得
pred = predictor.predict_proba(df)

# 各modelファイルはfit毎に別ディレクトリにあり
# predictor自体のsave/loadはpickleで行える
import pickle
with open('./predictor.pkl', 'wb') as f:
    pickle.dump(predictor, f)

今回のコンペティションでは、周囲のデータのoofを用いた特徴量が順位に大きく響きました。 AutoGlounは、modelごとのoof抽出などのコンペティションに必須な機能を備えているだけでなく、GPUを自動検知してLightGBM等をGPUベースで処理したり、ハイパパラーメタチューニングや、timeoutを指定して「指定時間内に終わるようにstacking、emsembleモデルを作る」ことを自動で行ってくれるため、特徴量の追加、削除などの側面でかなりPDCAを回しやすかったです。 今回は自前で特徴量加工を行いましたが、自動での特徴量加工も実装されているようです。 また、LightGBMやCatBoost、XGBoost、NeuralNetなど、実際によく使われるアルゴリズムを利用する実装になっているため、AutoMLとしての信頼度も高かったと言えます。

コンペティション終盤では、Optunaによる複合ルールベースのpost process最適化を実施しました。 今回のコンペティションでは、あるclass Xの処理を行うかどうかで大きく順位が変動したのですが、河合がtestとtarin間のXの検出量の差の最小化問題として捉えたpost processを実装し、大きく順位を上昇させました。 最小化問題が目の前にある時、Optunaが非常に使い勝手が良く、これが最後のTop10争いに効きました。

次はこうしたい

良い点が多かった反面、最終的に入賞できなかったのが非常に残念な所です。

短期の機械学習コンペティションをチームで戦う上での反省点として、以下のように振り返りました。

  • 誰かがリーダーシップを取り役割を割り振る
  • 利用するパイプラインを決めておく
  • データのやり取り等のフォーマットを固めておく

今回各々が簡易コードで特徴量加工部などを共有するのみで終始参加していたため、お互いの進捗が見えづらいだけでなく、前半同じような特徴量生成を全員が書くことに時間を使ってしまいました。 個々がデータをよく見る時間としては有用だったかもしれませんが、全員でディスカッションしていればすぐ出たもので、実装も1つで済んだ所です。 3人のうち1人を挑戦的なモデルチューニングに割り当てる事で、入賞が狙えた位置にいると思うと、実際悔しい限りです。

また、短いコンペティションかつ、互いの開発のバックグラウンドが分かっていない状況だったため、パイプラインツールの統一やデータのやり取りも雑多に行っており、途中特徴量データの受け渡しに失敗して時間をロスしたりしていました。 時間が限られたコンペティションでは、事前にチームを組むメンバーで共同開発の練習をしておくと良さそうです。

全体振り返り

チーム名の通り、現在CADDiでは機械学習・データサイエンスのチームを立ち上げようとしている所です。 既に数人メンバーが社内で活躍していますが、まだまだプロダクトを推し進めてくれるメンバー探しをしています。 そこで、業界内でのプレゼンスを高め実績を残す意味でも「絶対優勝するぞ!」と応募しました。

atmaCup #12は、一週間という非常に短い期間かつオンラインで開かれました。 タスクもテーブルデータを扱うもので、長期間のコンペティションに比べ、立ち上げ期の我々にとっては非常に参加しやすいものでした。

実際参加してみて、運営からのベースライン、チュートリアルなどのヒントや、guruguru*1上でのディスカッションの盛り上がりが、一週間で行われるものとは思えない程濃密で質が高く、非常に楽しかったです。

チームメンバーが互いに信頼する意味合いでも、同一のタスクをオンラインかつ短期間という制約のある状態で解く事自体に、価値があったと感じています。

各々仕事の隙間を縫っての参加となりましたが、特徴量や手法に関するディスカッションは普段の業務以上に行う事ができました。 この経験を活かし、次回はより良い結果を残したい所です。

おわりに

今回はSansan様のホストするコンペティションで、名刺に関連した内容でした。

CADDiでは、図面を多く扱うプロダクトを作っていますが、今回のコンペティションの課題に似たテーブルデータ、画像認識の課題が山積みとなっています。

是非、新しい機械学習・データサイエンスチームへジョインして、より強い力で一緒に前に進んでくれるメンバーを募集しています。

We are hiring!

*1:atmaCupがホストされているWebサイト