DMM.comの、一番深くておもしろいトコロ。

DMMの検索に機械学習を導入して、A/B テストで圧勝した考え方

DMMの検索に機械学習を導入して、A/B テストで圧勝した考え方

  • このエントリーをはてなブックマークに追加

はじめに

はじめまして! グロースマーケティング部 検索グロースチームの押条です。
DMMでは50を超えるサービスを提供しており、さまざまなデジタルコンテンツを取り扱っています。 多種多様な商品の中から欲しい商品に簡単にたどり着けるように、私たち検索グロースチームでは日々検索の改善に取り組んでいます。今回はその一例として機械学習を用いて「人気順リスト」を改善した話をご紹介します。

目次

A/Bテスト勝利までの道のり

現行手法とモチベーション

DMMでは各デジタルコンテンツのサイトにて、「人気順」で検索できるようになっています。 上述の通り、多種多様な商品がある中で、ピンポイントで自分の嗜好にあった商品を見つけるのは困難です。そのような状況で、周りの人が買っているものや人気商品を参考にしたいというユーザーも多いのではないでしょうか。そこでDMMの検索では「人気順」という順位をデフォルトソートにしています。

従来は、直近の売上などの情報を元に、各コンテンツに対して一意な順位をつけるだけという非常にナイーブな手法でした。以降この順位のことをrankと呼びます。

この手法だと特に新作コンテンツが発売された時に、まだ売上を上げていないコンテンツが検索結果上位にやってくることはなく、売上毀損の原因と考えられていました。つまり、コールドスタート問題に直面していました。そこで商品に紐づいたメタ情報、売上情報をもとに機械学習で高い売上が見込めるrankを出力する開発がスタートしました。

仮説立案、PoC

今回のケースで機械学習を適用するには、大きく分けて以下のようなプロセスを踏む必要があります。

  1. 現実の問題を機械学習で解けるような問題に落とし込む(問題設計、オフライン評価指標の策定)
  2. 特徴量・モデルの作成

順番に見ていきます。
まず、一番重要と言っても過言でないプロセスとして「現実の問題を機械学習でも解けるような問題に落とし込む」があります。 ここでは「良い人気順を得たい」という抽象的な問題を、数学的に解決できるような問題に置き換えるようなプロセスを指しています。

今回は「その日の売上順が再現された状態」を「その日の良い人気順」と仮定して、前日(N-1日)までの売上の情報を使用して当日(第N日目)の売上順を予測する問題に帰着させて取り組みました。

その日の売上順を当てたいモチベーションですが、DMMでは検索結果のUIが数行に渡って表示されるため、必ずしもrank:10のコンテンツがrank:15のコンテンツよりも良いrankとは言えない現状がありました。これは画面サイズによってはrank:10のコンテンツが1行目の右端に表示され、rank:15のコンテンツが2行目の左端に表示されるためrank:15のコンテンツの方が目に付きやすいと考えられるためです。 したがって、今回は厳密なランキングを予測するのではなく、検索結果の顔ぶれを予測できれば良いと判断し、オフライン評価指標にはRecall@Kを用いました。 Kは、PC版のサイトで1ページに表示できる検索結果の最大数である120を採用しました。

評価指標まで決まったら、PoCを開始しました。PoCでは、本番で扱うモデルを使用しつつ、特徴量の作成と選択を行い、ベースライン(先述の売上金額などを元にソートしている既存手法)に勝てるようオフライン評価指標の最適化を目指します。 特徴量は、曜日、過去N日(週,年)の売上高、販売個数,セールの情報などシンプルなものを採用し、それらを使って学習する際もシンプルな機械学習モデルを採用しました。 理由としては、初手で複雑なことをするより、シンプルなものから始め、どの特徴が効くのか知見を溜めていく方が良いと判断したためです。

シンプルな特徴量やモデルを採用しましたが、結果としてオフライン評価指標において、ベースラインを上回るモデルが作れました。

バッチ実装

PoCが完了したら次はバッチ実装に移ります。 PoCではあくまで手元の開発環境で動作を確認しているため、DMMの実際のサービスにデプロイしているわけではありません。 弊チームではPoCでベースラインを超えられるようになったら、ステージング環境、本番環境と順にPoCの手法を適用し動作確認をしていくことになります。また、PoCでは各コンテンツに対してどのようなrankを与えるべきかの手法は決められましたが、rankをどのようにして現検索システムに登録するかは決められていません。

したがってこのフェーズではPoCで求めたrankがどのようなライフサイクルを辿るべきか考え、どのタイミングでrankを登録すべきか検討します。今回はコンテンツ1つ1つに対してrankを計算することになるわけですが、一般的なECサイトのように在庫が存在するようなサービスでは、オンラインでrankの予測を行い、予測の結果をサービスに即時反映させる必要があります。しかしながらわれわれが扱うようなデジタルコンテンツでは在庫がなくなったりすることはないため、日次のバッチ実行で十分であると判断しました。

バッチ実装はPoCで実装したことに加え、rankの値を正しく検索エンジンに渡す仕組みを実装する必要があります。 DMMでは主に開発環境、ステージング環境、本番環境の3つで検索エンジンを運用していますが、それぞれの検索エンジンはEKS基盤で動作しています。バッチジョブもEKS基盤上で動作しており、バッチジョブのワークフローエンジンにはtektonを採用しています。

rankの値を検索エンジンに渡すこと自体はさほど難しいことではないのですが、それでも苦労した点として、2点ほど挙げられます。

1つ目は、求めた各コンテンツのrankを検索エンジンに渡す際に負荷が低いわずかな時間帯で流し終えるよう調整する必要があった点です。 一度に全てに渡す実装にしてしまうと検索エンジンへの負荷が高まるため数千件ずつ渡していく実装が理想でした。しかし、他バッチの検索エンジンへの登録方法が定まっておらず、実行時間によっては検索エンジンの負荷が上がりすぎてエラーが頻発していました。今では他バッチの登録方法も定まり検索エンジンには一定の負荷で登録できるようになっています。

2つ目は、バッチ実行は1日1回であるため、バッチを実行するタイミングによっては正しい特徴量を取得できない問題が発生していた点です。この原因は各コンテンツの特徴をストアしているRDBのテーブルの更新タイミングの違いです。そのため、全ての特徴量の更新タイミングを見計らってバッチ実行される時間を決めました。施策リリース後も二転三転したくらい苦労したところでもあります。

A/Bテスト

バッチ実装が完了したら、いよいよこの施策に効果があるのか測定するためにA/Bテストを実施します。 DMMでは、このA/Bテストで効果が認めらた新規施策しか採用されません。

A/Bテストでは、テスト対象サービスの全ユーザーを現行施策適用ユーザーと新規施策適用ユーザーに分け、一定期間の観測結果でKPIに有意差が現れるか計測します。 さらに弊チームでは、メインKPIと、サブKPIの両方を観測し、一定期間の計測後、効果測定を行って有意差が認められた場合に勝ちと判定されます。 本施策は無事A/Bテストで両KPIに有意差が認められ、無事新規施策が採用されて現在も人気順に活用されています。

この施策がユーザー1人あたりに与えた効果は僅かなものでしたが、DMMには多くのアクティブユーザーが存在しているため、僅かな効果でも驚くほどの成果に繋がります。インパクトを与えられる施策を実施できることは日本最大級のプラットフォームを扱っているDMMならではだと思っています。

施策を振り返ってみてチャレンジングだったところ

本施策は、私がDMMにジョインして初めてのプロジェクトだったこともあって随分と苦戦してしまい、着手からリリースまで半年ほどかかってしまいました。(結構な時間をかけたこともあり、大勝したことがわかった時、感無量でした。)

どのような点で苦戦したかと言いますと、以下の2点かと思っています。

  • ①大規模データを扱った経験に乏しかったため、処理にかかる時間の見通しや専用のライブラリの扱いに苦戦した
  • ②ベースラインが強かった

1つ目は、前職で担当していたプロダクトで扱っていたデータ量を遥かに凌駕するような購買・閲覧ログの量と、それを扱うための開発言語・ライブラリ(SQLやSpark,Scalaなど)への不慣れさのダブルパンチで苦戦しました。

使用する生データにはユーザーの購買ログとアイテムに紐づくデータがあり、これらを加工して学習するデータを作成します。ユーザーの購買ログは過去1年間のデータを使用したのですがこれだけでも巨大でした。加えて、それに紐づくアイテムの数も数多く存在し、雑に集計してアイテムユニークな学習データを作成するには膨大です。 私は、前職でも機械学習関連のサービスに携わっていました。そのサービスは少ないユーザー向けで、日時の変化によらない自然言語データでした。そのため手元の環境にCSVで一旦出力してPythonのpandasという使い慣れたライブラリを使用して試行錯誤できていました。 一方弊社では、SQLを使用して学習データを作成仕切る必要がありました。なぜなら、アイテムの売上といった日次の変化が重要で、しかも大量にあるためです。ログデータを一旦手元の環境にCSVでダウンロードし、それをpandasを使って特徴量を生成するような慣れた手法は効率が悪く使えませんでした。

その上、学習データを用意したあとのフェーズにもつまづきポイントはありました。データが多いので1回の実験に時間がかかってしまいました。具体的には、以下のような処理に時間がかかっていました。

  • 過去1年間のログデータとそれに紐づくアイテムデータを整形するのに、クエリの可読性を高めるために処理単位ごとに中間テーブルを作成した
  • 機械学習モデルの学習に時間がかかった
  • ある程度モデルの精度を担保できるよう14日分の予測結果を見て判断するため、都度14日分処理を実行していた。

また、当時の社内基盤の都合でSparkを動かすにはScalaを使う必要がありました。筆者がPythonに慣れ親しんでいたこともあり、この処理はSQL,Spark,Scalaでどう書いたものかと変換に苦戦することも度々ありました。

2つ目は、思いの外ベースラインが強くて苦労しました。開発当初はユーザーが本当に欲しいものと実際に売れているものには乖離があると思っていました。つまり、ユーザーは検索結果上位から欲しい商品を見つけられていないか、検索結果の上位に表示されているから仕方なくその商品を購入しているかのどちらかだと考えていました。そこで、その乖離を埋められるようにさまざまな特徴量を使えば難なくベースラインに勝てると見込んでいました。

ところが実際は異なり、A/Bテストを実施する度、ベースラインの手法で十分ユーザーの要望が叶えられていることが分かりました。一度商品が上位に並ぶと目に触れる回数が増えることで更に売れてまた上位に並ぶスパイラルに入ります。そうは言っても、ユーザーは検索以外のページからも商品を購入できますし、検索時も絞り込み検索や人気順以外のソート順を選択して購入もできます。このような経路で購入したユーザーによって人気順で上位に並んだ商品よりも売れる商品が生まれ、人気順の新陳代謝は行われます。今回機械学習で解こうとした当日の売上を予測するという問題は、売上などの情報をベースとしたベースライン手法でほぼ解けていたと言えました。

ただし、新規施策のモデルでは、既存手法の 売上に関する情報 の他、予測対象の下記の特徴量を考慮したことで、優位性があったと考察しています。

  • 新商品であるか
  • セール中であるか
  • キャンペーン中である場合は,キャンペーン開始から何日経過していたか
  • 発売してからN日の総売上

より詳細な商品の情報やセールの情報も使った方がより精緻に当てられるだろうとの仮説のもと、試行錯誤し、結果として良いアルゴリズムを作成できて良かったと思います。

今後の課題

今回は「当日の売上順を予測する」という問題に帰着させて取り組みましたが、あるべきランキングの定義から再考することでより良いモデルを作成できる可能性があります。 また、シンプルなアルゴリズムを使用しましたが、より新しいアルゴリズムに置き換えたり、閲覧ログ、クリックログなどの特徴量を使用することでより精緻な予測ができそうです。

ここまでご紹介したような、ユーザーそれぞれの行動に関係なく同じリストを提供するような施策をわれわれは全体最適施策と呼んでいます。反対に、ユーザーそれぞれの購買行動や閲覧行動を踏まえた施策はパーソナライズ施策を呼んでいます。

全体最適ではユーザーごとの嗜好を強く反映できないので、現在、よりユーザーに合った商品をお届けできるようなパーソナライズ施策にも取り組んでいます。

おわりに

今回は、検索改善タスクのうち、全体最適施策である人気順リスト改善に機械学習を導入してA/Bテストで大勝利し、無事リリースできた話をしました。機械学習モデルを導入するのに必要な問題設定の仕方や、実装における様々な取り組み、僅かな改善でもDMMでは大きな効果があるとわかっていただけたかと思います。 先にも記したようなパーソナライズ施策など、統計・機械学習の技術を駆使してより良い検索結果を提供するタスクに取組中で、まだまだ改善の伸び代が満載です。

今回の記事を読んで、技術的に気になった点や話を聞いてみたいことなどがあればぜひ気軽に聞きにきていただければと思います。

カジュアル面談の場もご用意しております!)

最後に、DMM Groupでは一緒に働いてくれる仲間を募集しています。ご興味のある方はぜひ下記の募集ページをご確認ください!!

MLエンジニアの求人|採用情報|DMM Group

SREの求人|採用情報|DMM Group

データサイエンスを駆使した事業成長を手がける「Growth Scienceグループ」の正体とは? - DMM inside