kaggle: Avito Demand Prediction Challenge まとめ

f:id:copypaste_ds:20190205172733p:plain

はじめに

過去コンペまとめ記事の4作目です。タイトルにもあるように今回は2018年4月にkaggleで開催されたAvito Demand Prediction Challengeをまとめます。これまでのまとめ記事に興味ある方は下記のリンクから辿ってください。

コンペ略称 リンク
Toxicコンペ kaggle: Toxic Comment Classification Challenge まとめ - copypasteの日記
Porto Seguroコンペ kaggle: Porto Seguro's Safe Driver Prediction まとめ - copypasteの日記
mercariコンペ kaggle: Mercari Price Suggestion Challenge まとめ - copypasteの日記

コンペ概要

Avito(ロシアの大手広告サイト)が主催する商品の購入予測コンペです。データは数値データ + カテゴリデータ + 文章データ + 画像データでNLPとCVの知識が求められます。
avitoはネットで簡単に個人間取引ができるサイト(メルカリのようなものを想像すればよいが、不動産や自動車、仕事なども売買できるらしい)です。出品時には商品説明や商品画像、価格などの情報を添えるのですが、それらの組合せによって商品の需要は大きく変化します。例えば以下の3つの画像を見てください。

f:id:copypaste_ds:20190210091617p:plain

全て自転車の写真ですが、左はかっこつけすぎですし、右はパット見だと何の写真かわかりません。自転車を買いたいユーザにとっては中央の写真が良さそうですね。(個人差はあるでしょうが)
商品説明文も非常に重要で、以下の例を見ると中央の説明文が良さそうです。

f:id:copypaste_ds:20190210091641p:plain

広告リスティングや顧客満足度の観点から、出品時に商品需要を予測できれば非常に有益です。Avitoコンペではタイトル、説明文、画像、価格などの情報から商品需要を予測することを目的とします。 賞金・期間・参加者数などは以下の通りで、多様なデータを扱う難易度を考えると1873チームの参加は比較的多い印象です。

賞金 期間 参加チーム数 参加者数
$12,000 2018/04/25 ~ 2018/06/25 1,873 2,363

データの種類とタスク

数値データ・カテゴリデータ・文章データ・画像データを用いた回帰問題です。通常のテーブルデータのように数値データやカテゴリデータがあるのは勿論、商品タイトルや商品説明文(文章データで)や商品画像データも与えられるため、データハンドリングの難易度は比較的高いと思います。
ファイルの種類とサイズは以下のとおりです。テーブルデータのサイズは小さいですが、画像データはそれなりに大きいです。ストレス無く実験するにはハイスペックPCかクラウド環境が必須になりそうです。主に使用するのはtrain.csv, test.csv, train_jpg.zip, test_jpg.zipの4つで、その他は追加データで必ずしも使用する必要はありません。(上位チームは追加データも上手く使用していた印象です)少し詳しく説明すると今回のtrain.csv, test.csvは広告の掲載期間で時系列分割されており、train_active.csv, periods_train.csv, test_active.csv, periods_test.csvの4ファイルは、train.csv, test.csvに含まれていないが同じ期間に掲載された商品のデータです。これらのデータは deal_probability(目的変数), image, image_top_1のデータが無いことを除いてtrain.csv, test.csvのデータを情報量と同じです。

ファイル名 データサイズ レコード数 カラム数 画像枚数
train.csv 909M 1,503,424 18
test.csv 316M 508,438 17
train_jpg.zip 49.4G 1,390,837
test_jpg.zip 18.7G 465,830
periods_train.csv 732M 16,687,412 4
periods_test.csv 602M 13,724,922 4
train_active.csv 8.6G 14,129,821 15
test_active.csv 7.9G 12,824,068 15

目的変数の分布は下図のとおりです。目的変数の値域は0~1の範囲で、64.8%は0です。画像は後ほど紹介しますがこのkernelを参考に微修正しました。 f:id:copypaste_ds:20190210120052p:plain

ちなみにtrain.csvのheadはこんな感じです。(カラム数の都合で画像を3枚に分けました)
各カラムの意味はデータ説明ページを見ていただきたいですが、商品ごとにカテゴリ、タイトル、説明文、価格などのデータがあることがわかります。また文章データはロシア語なので、データの理解は大変そうです。

f:id:copypaste_ds:20190210125949p:plain

f:id:copypaste_ds:20190210125938p:plain

f:id:copypaste_ds:20190210125958p:plain

train_jpg.zipに含まれる画像はこんな感じです。画像サイズや商品のとり方はバラバラであることがわかります。また、必ずしも全ての商品に商品画像があるわけではないことに注意が必要です。

f:id:copypaste_ds:20190210140831p:plain

評価方法

評価指標はRMSEです。

提出方法

提出のフォーマットは以下のとおりです。商品ごとに取引確率を出力します。

item_id deal_probability
2 0.1
5 0.25
6 1.0

勉強になる Kernel と Discussion

上位解法ほどのスコアは出せませんが、kernelとdiscussionは良いアイディアと実装で溢れています。どのアプローチも勉強になったので簡単にまとめておきます。

[Avito EDA, FE, Time Series, DT Visualization ✓✓ | Kaggle

EDA kernelです。データの確認、基礎集計・可視化、特徴量抽出、Treeモデルの変数重要度・木構造の確認までが簡単にまとめられています。以降の解法を読むにあたって簡単にデータを確認しておきたい方には見ておくことをおすすめします。ただし画像データに関しては触れられていないため別kernelを参照する必要があります。

Ideas for Image Features and Image Quality | Kaggle

画像データの基本特徴量抽出をまとめているkernelです。暗さ、明るさ、均一具合、代表色、画像サイズ、ぼやけ具合などの特徴量を算出しています。画像と特徴量の値が合わせて表示されているので各特徴量を直感的に理解できます。個人的にはぼやけ具合(Blurry)の特徴量が参考になりました。kernelにも参考リンクがありますが、ぼやけ具合特徴量はOpenCVのドキュメントが参考になります。

High Correlation Feature Image Classification Conf | Kaggle

学習済み画像識別モデルが確信度高く分類していれば、それは良質な画像であろうという仮説を検証するkerelです。 前提として、画像の質は需要に大きく影響するため、画像の質を表現できる特徴量は有効だろうと考えています。その上で、「画像識別モデルの確信度が高い」ことは「人間が画像内の商品を認識しやすい(画像の質が高い)」という仮説を立てています。画像識別モデルにはImageNetで学習済みのResNet50, InceptionV3, Xceptionを使用し、確信度(image confidence)には出力値(1000クラスの所属確率) の最大値を採用しています。 kernelの最後では、image confidenceはタイトルや文章の長さよりも目的変数との相関が高いことを確認しています。 仮説と実現方法のアイディアが面白いと感じたkernelです。

About image features & Image_top_1 features | Kaggle

Noohさんのコメントで、注目点(keypoint)の特徴量が紹介されています。実装にはcv2.FastFeatureDetector_createが使われており、注目点の説明はOpenCVのドキュメントが参考になります。イメージ図だけ載せておきます。

f:id:copypaste_ds:20190211122231p:plain

Aggregated features & LightGBM | Kaggle

複数テーブルの関係に関する基礎集計、基本特徴量抽出、LightGBMのモデリングまでがまとめられています。やっていること自体は単純ですが、確認すべきことをきちんと事前に確認しておく丁寧さが参考になりました。基本特徴量抽出では、出品期間・回数の特徴量、タイトル ・説明文の単語数由来の特徴量、CountVectorizer・TfidfVectorizerによる特徴量などが共有されています。

Simple CatBoost | Kaggle

基本特徴量を作成した上でcatboostでモデリングしています。特徴量は日付由来特徴量、カテゴリ変数をlabel encodingするといったシンプルなものですがcatboostを試してみたい方には参考になるかと思います。

Fasttext starter (description only) | Kaggle

特徴量に商品説明カラムのみ使用して fastText + RNN モデリングしているRNNのチュートリアルです。特別な工夫等は見られませんが、モデリングする上で必要最小限のコードが共有されているため初学者におすすめです。

text2image_top_1 | Kaggle

目的変数との相関が最も高い変数(image_top_1)の欠損をRNNの予測値で補完するkernelです。欠損値の補完はpd.DataFrame.fillnaで済ませることも多いですが、重要とわかっている変数に関してはモデリングして丁寧に補完する価値があるようで非常に参考になりました。
RNNの構造は embedding -> Bi-GRU -> concate[GlobalAveragePooling, GlobalMaxPooling] -> BN -> Denseと基本的なものなので、RNNのモデリングに興味ある方におすすめです。

load data (reduce memory usage) | Kaggle

Avitoコンペのkernelではありませんが、このAvitoのkernelが参考にしていたメモリ節約術に関するkernelです。pd.read_csvはデータの読み込み時に自動で型を認識するため便利ですが、メモリ使用効率はあまり良くありません。このkernelで共有されているreduce_mem_usage関数は使い勝手がいいので、別のテーブルデータに対してもpd.DataFrameのメモリを節約したいときにおすすめです。

Region and City Details with Lat, Lon and Clusters | Kaggle

region, cityの文字列とGoogle Maps APIから緯度・経度情報を情報を取得し、クラスタリングしています。クラスタリングにはHDBSCAN(Hierarchical Density-Based Spatial Clustering)を使用しています。(K-meansはクラスタサイズが均一となり不適切と判断したらしい)他のkernelがregion, cityを単にカテゴリで扱っているのに対して、region, cityの類似性に注目している点が面白いと思いました。HDBSCANと他のクラスタリングの違いについてはこのページが参考になります。

上位解法概要

上位解法はLightGBMとNNのアンサンブル解法が目立ちます。テーブルコンペの定石であるLightGBMに加えて、画像特徴とテキスト特徴を活かしたNNモデルを用意することが重要だったようです。マルチモーダルデータで上位を狙うにはGBDTとNNのアンサンブルは必須になりそうです。(Petコンペも然り)今回も1st ~ 3rdまでの解法をまとめたいと思います。

f:id:copypaste_ds:20190908181634p:plain

1st place solution: "Dance with Ensemble" Sharing Thread | Kaggle

grandmaster×3, master×1で構成されたdream teamです。最終的なモデルは3層のstackingで1層目(some lgb, some nn, some xgb)、2層目( some lgb, some xgb, one NN)、3層目(one nn)で構成されています。詳細なモデル数と役割分担はわかりませんが、各メンバーがそれぞれ強力なモデルを作成しています。 4人の解法が別々に公開されていましたが、個人的に勉強になった2人の解法をまとめます。

Little Boat さんの解法

NNのモデリングに集中したようで、最終的にシングルモデルでtop10に入るスコアを記録しています。最終モデルは下図のとおりで、モデルの構造は勿論、それに至るまでのプロセスがとても参考になります。 f:id:copypaste_ds:20190211104007p:plain

以下に実験プロセスをまとめておきます。ミニマムなモデルからはじめて、成功/失敗要因を切り分けながら改良を加え続けたようです。

  • 数値データ、カテゴリデータのembedding (0.227X)
  • title, descriptionの学習済みfastText + 2×RNNを追加 (0.221X)
  • train + testを使ってfastTextを一から学習(train_active, test_activeの使用も試したが失敗)(0.220X)
  • VGG16 + average poolingを追加(0.219)
  • fastText + 2×RNN を fastText + 2×LSTMに変更(CNNやAttensionも試したが失敗)(0.0003改善)
  • ResNet50を追加(他のCNN、fine tuningに失敗)(0.218X)
  • 直感を頼りにチューニング(spatial dropoutの追加など)(0.2165 - 0.217)
  • チームメイトが作成した特徴量を追加(0.215X)
  • ここまでの実験過程で保存したモデル群の最後にDenseを加えて学習(0.008改善: top10)

Arsenal さんの解法

Tree系モデルを担当したそうです。public LBでシングルモデルtop5を記録する強力なモデルを作成しています。 作成したモデルはstackingの1層目用に lgb×13, xgb×6、2層目用にlgb×12, xgb×7の計38です。特徴量について項目別にまとめます。

text features

  • title, description, title+description, title+description+param_1などの組合せから作成したTFIDF特徴
  • TFIDF特徴をSVDで次元削減したもの
  • TFIDF特徴量で学習させたRedge回帰の予測値
  • title, descriptionから作成した単語数/文字数/ユニーク単語数などの集計値

image features

  • このkernelで紹介されている統計量
  • 学習済みResNet50, InceptionV3, Xceptionの予測値(予測確率は連続値、予測カテゴリはカテゴリ変数として追加)
  • このdiscussionのコメントで紹介されている注目点特徴量

categorical features

  • カウント/ユニークカウント特徴量をデータセット・groupkey・ 集計対象を変えながら作成(組合せの例は以下の通り)
    • データセットはtrain+test, train+test+train_active+test_active
    • groupkeyはparent_category_name, category_name, param_1, region, cityなど
    • 集計対象はitem_id, user_idsなど
  • target encoding(組合せは様々)

predicted independent variables features

  • 重要変数(price, image_top_1, item_seq_number, daydiff(date_to - date_from))をxgb, lgb, rnnで予測し予測値を特徴量に追加
  • 様々なカテゴリでgroupbyした後、上記の予測値の平均値を特徴量として追加
  • 予測値も使用して差分特徴量を作成((price - xgb_price)/price, log(image_top_1) - log(lgb_image_top_1)など)

user_id features

user_id由来の特徴量は過学習するため、stacking2層目でのみ使用したとのこと

  • train+test, train+test+train_active+test_activeをuser_id, item_seq_number, activation_dateでソートし、user_idを含むカテゴリ(user_id+parent_category_name)でgroupbyした後、昇順/降順にインデックスを付与(user_idごとに0~1で正規化したインデックスも付与)
  • user_idをカテゴリ変数として追加
  • user_idと他のカテゴリ変数をgroupbyのkeyとして、unique item_seq_number, log1p(max(price)) - log1p(min(price))などの特徴量を作成
  • periods_train+periods_testから以下の手順で特徴量を作成
    • 行ごとにactivationtime(=date_from - activation_date), activationlen(date_to - date_from)などの差分特徴量を算出
    • item_id毎にlagactivation(=activationdate - lag(activationdate), toactdiff(=activationdate - lag(date_to))などのlag特徴量を算出
    • item_id毎に上記特徴量のmin, max, mean, std, countを算出
    • user_id毎に更に上記特徴量のmin, max, mean, stdを算出

thousandvoice さん、Geogiy Danshchinさんの解法

2人の解法はLittle Boatさん、Arsenalさん、既に紹介したkernelとの共通部分も多いのでリンクだけまとめておきます。

2nd place solution: second place solution | Kaggle

NN, LightGBM, FM_FTRL, Ridge, CatBoostで6層のstackingをしたそうです。特徴量について簡単にまとめます。

  • VGG16, ResNet50, MobileNetの予測値
  • fastTextによるベクトル表現(データには title, description, title+city, title+categoryなど)
  • 様々なカテゴリの組合せからprice, date_to - date_from などの基本統計量
  • autoencoderによるカテゴリ変数のベクトル表現
  • TFIDF, TFIDF+SVD
  • target encoding

6層もstackingすることがあるとは驚きでした。初学者が真似をしても過学習して終わりそうです。

3nd place solution: 3 place solution | Kaggle

最終モデルは2つのensembleモデルをensembleしたもので、 2つのモデルはvalidation setの作り方を変えて作成したそうです。(1つは通常の5-fold、もう一方が時系列分割)

LightGBM features

  • title, description から word, char それぞれでTFIDF特徴量を作成
  • Word2vec, fastTextによる単語のベクトル表現(Word2vecのほうがfastTextよりよかったとのこと)
  • 画像から算出した統計量
  • ResNet, Inception, Xception の予測値
  • 量的変数(priceなど)をbinに区切って離散化
  • user_idごとの単語数、掲載日などの基本統計量
  • 文章から算出した統計量
  • 緯度/経度などの位置情報

NN

  • best NN
    • fasttext, word3vecをconcatしてbidirectional GRUs
    • lgbで使用した特徴量
    • カテゴリ変数をemmbeding layerで100次元に圧縮
    • lossはbinary cross-entropy
    • textデータはnltkでstemming
    • title, description, paramsは分割を表す記号を挟んで結合

best NN 以外にチャネルごと(画像、テキストなど)にNNを構築したそうです。各々のNNの精度が高くなくてもstackingで有効だったとのこと。

その他の上位解法

discussionは解法の宝庫ですね。余力がある方はこちらもぜひ。

順位 リンク
4th 4th Place Solution | Kaggle
5th 5th place solution | Kaggle
7th 7th place solution | Kaggle
11th 11th Solution Overview | Kaggle
14th 14th Place Solution: The Almost Golden Defenders | Kaggle
13th The last gold solution | Kaggle
20th 20th place solution | Kaggle
22nd Vote of Thanks and Lessons Learnt | Kaggle
24th 25th place solution | Kaggle
30th Our 30th Solution: In which our heroes tried Quantum Gravity, Adaptive Noise and other cool stuff… | Kaggle
31th A silver solution (31st place) | Kaggle
35th 35th place: squeezing out the last -0.0011 RMSE | Kaggle
36th 37th place solution | Kaggle

日本語記事

Kaggle Avito Demand Prediction Challenge 9th Place Solution

第5回 kaggle meetup の発表スライドです。5th place solutionの取り組みが紹介されています。linear quiz blendingという手法でスコアを伸ばしたようです。

世界一のデータサイエンティストを目指して 〜Kaggle参加レポート4〜 - Kysmo’s Tech Blog

株式会社キスモの方がAvitoコンペの参加記録を書いています。7th place solutionのテキストデータに対する工夫がわかりやすく紹介されています。stacknetが有効だったそうです。

AMD製GPUでKaggleにチャレンジする - 実践編 - - Qiita

Avitoコンペを題材にデータのダウンロードからサブミットまでの手順がまとめられています。

おわりに

なぜか気乗りしなかったので記事執筆から公開まで半年かかりました。この記事を書いていた頃はPetFinderコンペ(Avitoコンペと同じマルチモーダルデータのコンペ)に参加していて、Avito解法が非常に参考になった記憶があります。過去コンペまとめ記事を書き始めた頃はメダル0個のNoviceでしたが、約半年ほど経過してなんとかExpertに昇進することができました。過去コンペの解法研究なくしてメダル獲得はなかったと思うので、参加コンペに類似する過去コンペは積極的に調査していきたいです。PetFinderコンペのまとめ記事もそのうち(類似コンペが開催された頃に)書きたいと思います。