Wantedly Visitで開発チームのリーダーをしている久保長です。ここ最近は、Wantedly Visitのレコメンデーションを強化するために、推薦チームの立ち上げも行ってきていました。
推薦チームを発足して二年間で、大きな成果を生み出すことができました。具体的には、例えば「募集の推薦画面に表示されてから話を聞きに行く確率」といういうような主要指標が2倍になり、ユーザ体験にも大きな影響を与えることができました。
もともとレコメンデーションの経験がなかったこともあり、当初、全体的に取り組まないといけないことはなにか、どのように最適化を進めていくのかが分かっていませんでした。今回、一つ一つのトピックについて簡単にはなりますが、できるだけ関係しているプロジェクトの全体が分かるようにまとめています。機械学習を使った様々なアルゴリズムを取り入れたことに加えて、UIや基盤の改善についてもそれぞれお話します。
UI・基盤・推薦それぞれについて取り組んだことと成果
以下の図は2017年から2018年の間に、UI・基盤・推薦それぞれについて取り組んできたことのサマリと主要指標の推移です。
2017年前半は基盤構築を取り組み、2017年からUI・基盤・推薦それぞれ進めています。主要指標は、2017年後半は2017年前半と比べて130%、2018年前半は160%、2018年後半は200%と大きく、改善してきました。
2017年前半
BigQueryへのデータ集約
BigQueryへのデータの集約とは、ログデータやDBのデータを全てBigQueryに集約することです。Wantedlyでは、10GBほどのログを毎日Treasure Dataというログ収集サービスに保存していました。Treasure DataにもQueryを実行する機能はありましたが、大きなデータをJOINするような複雑なクエリは1時間以上かかり実行できないものもありました。BigQueryに移行することで、従来の100倍近いパフォーマンスで実行できるクエリもあり、生産性が大きく向上しました。また、コストも安いため、全てのデータを集約することができました。
フィルターとパーソナライズを両立させる仕組み
Visitでは、何も絞り込みを行わずに推薦することに加えて、職種や地域やタグなど様々な絞り込みの項目があります。また、自由入力のワードで絞り込みを行うこともできます。もともと検索はelasticsearchを利用していましたが、スコアが複雑になるに連れて仕組みを変える必要がありました。
redisのsorted setを利用することで利用頻度の高いデータをキャッシュしながら素早くフィルタリングする仕組みを実装しました。
再現性のあるデータの保存
アルゴリズムを評価しようと思った際に、過去のアルゴリズムを簡単に再現できませんでした。推薦で利用するスコア、利用する際にどのアルゴリズムをどの順番で表示したか、スコアを計算する上での元データそれぞれを残しておく必要があります。
推薦で利用するデータは毎日更新しますが、更新するタイミングでBigQueryにも保存しています。また、推薦のリクエストがあった時はどのアルゴリズムをどの順番で表示したかログに残しています。スコアを再現するために、RDBのデータのスナップショットもBigQuery上にコピーをしています。
オフラインの評価基盤
推薦の評価は以前のブログでも説明しましたが、いろいろな方法があります。
もっともシンプルなものは、推薦したデータに対して、クリックやユーザーアクション(ブックマーク、応援、応募、送信)がどのくらいの割合で行われたか、precisionを計算する方法です。再現性のあるデータを保存することができていれば、実際に表示したアルゴリズムと比較して新しいアルゴリズムのパフォーマンスを比較することができます。
評価の時、ユーザに表示したことがないものを評価することはできないと考えてしまうと思いますが、ユーザも目的のものを探すためにたくさんのアイテムを見ていることもあるため、過去のデータを利用することで評価は可能なことが多いです。少し新しいアルゴリズムの方が不利な状態で、既存のアルゴリズムに勝つことができると安心してリリースができます。Visitでリリースした全ての新しいアルゴリズムは過去のアルゴリズムに勝つ評価結果を見て、リリースしています。
オンラインの評価基盤
A/Bテストという手法を使いました。推薦アルゴリズムを複数のユーザー群に分けて実験することです。
運用していると、ウェブサービスのA/Bテストは、終わりを自分で決められるため、たまたま良くなったタイミングでテストが終わることが発生していました。評価する際にどのくらいデータがばらついているかはある時点の数字を見るだけだと判断できなく、結果だけ見ると正しいように見えて困りました。
対策として、1時間ごとに指標の変化率とp値の変化をグラフにし、判断の精度を上げました。最初に基盤を整えることで、徐々に推薦アルゴリズムの開発が行いやすくなりました。(airbnbのブログを参考に実施しました)
2017年後半
検索条件の利用の促進
ユーザーの体験を促進する上で、そのまま推薦したものを見せるのか、フィルタリングを行なって見せるのか、自由入力でキーワードを入力してから見せるのか、パフォーマンスを評価しました。結果的に検索条件や自由入力を即すようにUIを最適化しました。
基本的には、ユーザーの負荷は、自由入力->フィルタリング->そのまま推薦で、ユーザーの求めるものを見つける確率は、その反対になります。そのため、利用するためのハードルを上げるか、推薦した後の精度を上げるかのトレードオフになります。
目的があって(検索キーワードが思いついて)サービスにアクセスするサービスか、サービスを使い続ける人が増えて自由入力キーワードを考えてくれるようになっているか、推薦できるコンテンツの数など、サービスの特徴や成熟度によっても解は異なるかもしれません。
検索の高速化
ユーザー体験を上げる上で高速化は非常に重要です。推薦アルゴリズムの実行時間は200ms、すべてのデータを返すまでの速度は500msなど目標を決めています。
リクエストのログにもレスポンス速度を含めることで、特定のユーザや特定のクエリのパフォーマンスが落ちてないかチェックできるようにしています。
順番を返す、アイテムの詳細情報を取得する、ユーザー特有の情報(ブックマークしたか)など、処理を分けて適切にキャッシュを行うと速度を改善しやすくなります。
ランキングの最適化
ここでいうランキングとは、全ユーザーに対して共通のスコアです。当初はシンプルなアルゴリズムを使っていました。ユーザーのアクションがあるサイトで最も一般的なランキングである、ハッカーニュースのアルゴリズム(時間に対して指数的に価値を減衰させる方法)です。
多腕バンディットという、報酬に合わせて表示を最適化するアルゴリズムもあります。このアルゴリズムを使うとうまくおすすめ枠にアイテムを混ぜ込めます。ここで注意すべきは、最適化されるまでの時間は、アイテム数に負の相関があります。最適化されるまでは、ユーザー体験が下がるので、初期のアイテムの表示比率をどうするのかを考える必要があります。
この頃のランキングは、シンプルに1日ごとの報酬を元に、前日のスコアを更新するということを行っていました。ただスコアを加算するのでなく、統計的に意味のあるデータに変換することでスコアの精度が上がりました。
BigQueryを使ったコラボレーションフィルタリング
コラボレーションフィルタリングとは、ユーザーのアクションの関係性から近いものを推薦するというものです。
シンプルなのですが、実装する上でボトルネックはありました。アクションログの収集とジョブの実行基盤とスケーラビリティを考えた実装です。というのも、例えばあるアイテムを閲覧したというデータは非常に大きくなりやすく、シンプルなアルゴリズムでは実行ができなくなります。大きなグラフを扱うテクニックはありますが、実装のハードルは上がります。また、実行速度が遅くなることで、データクレンジングの試行錯誤が進めにくくなります。(データクレンジングは推薦において非常に重要で、様々なことを行っています。例えば、ボットやスパムのような動きをするユーザー、ランダム性の高いアクションなどです。)
BigQueryを利用することで、ジョブの実行ごとBigQueryで管理し、シンプルなアルゴリズムで非常に高速に処理を行うことができます。シンプルなアルゴリズムの具体例としては、ユーザーのアイテムごとの積集合と和集合の比やコサインを使った類似度の計算です。ざっくりいうと、BigQueryを使うことで、力業で目的を達成できます。
interleavingによるオンライン評価
もともとA/Btestingを行なっていましたが、評価を行う上での精度や速度に問題がありました。評価する精度や速度は、サンプル数と指標の変化量に依存します。想定よりも良い改善なら良いと判断できますが、悪い場合は判断できなくなります。
つまり、少し良い施策や少し悪い施策は判断できません。また、実務上常に判断できないものは取り込まないともできない中、施策を行なっているチームは良くなって欲しいというバイアスがあるので、結果的に悪いものを取り込みやすくなります。
interleavingはAとBにユーザーを分けるわけでなく、AとBというアルゴリズムを交互に同時に出すことで評価を行う手法です。interleavingを利用すると、指標は10倍以上の違いが生まれ、言い換えると、10倍以上速く判断できるようになり、実務的には逆効果な施策を取り込むことが減ります。(netflixのブログを参考に実装しました。techbookにも詳しく書いています。)
2018年前半
コンテンツリッチなUIに
ここでコンテンツリッチとは、画像サイズを大きくし、推薦する際の一つ一つのアイテムの大きさを大きくすることです。推薦の精度が上がってくると、コンテンツリッチなUIにしやすくなります。少ない推薦でもユーザーは目的としているアイテムを見つけることができるからです。推薦精度に合わせてユーザの行動は変わるので、それに合わせてUIを変更していくことで、よりよいUXが実現できます。
キーワード検索に計算したスコアを反映する仕組み
キーワード検索のスコア計算が複雑になることによるパフォーマンスの悪化の解決と、よりフレキシブルにキーワード検索にスコアを適用していくことを取り組もうとしていました。
elasticsearchは、シャードを増やすことでパフォーマンスを改善できますが、200ms以内にレスポンスを返す目標に対して、クエリ実行時にたくさんの計算を行うことは両立できませんでした。また、新しい推薦案ではアイテムごとのアクションではなく、検索キーワードごとのアクションを元に検索キーワードごとにスコアを計算することを考えていました。
調査すると、上位1000件のキーワードで、クエリ全体の90%をカバーし、ページネーションはあまり深くは行われておらず、上位200件で90%以上をカバー出来ることが分かりました。精度と速度のトレードオフを考慮して、適用範囲を制限して実装することにしました。具体的には、推薦を用意したキーワードは推薦を利用し、ない場合はelasticsearchにfallbackさせる。推薦の用意してないページまでスクロールした場合も、elasticsearchにfallbackさせました。
キーワード検索の最適化
もともとelasticsearchを利用しており、tf-idfを利用した文章中に含まれる単語の重要度の計算と、アイテムごとの特徴やパフォーマンスをスコアを計算する上で利用していました。具体的には、rubyというキーワードが含まれ、重要度の高いかというスコアに加えて、画像があるなどよいコンテンツであること・ソーシャルに拡散されやすい・ブックマークされやすいなどを考慮して、クエリ実行時にスコアを計算していました。
上記に加えて、検索キーワードごとのアクションのスコアへの反映を行いました。例えば「ruby」と検索した時に応募されやすい企業は、「ruby」と検索した時により上位に表示させるようにするということを行いました。シンプルですが、非常に強力で大きくキーワード検索のパフォーマンスは改善されました。
ランダムウォークによるコラボレーションフィルタリング
ランダムウォークとは、文字通りランダムにグラフ上を移動することです。例えば、ある地点から100回移動した時、よく通過した地点は類似度が高いという計算をします。
類似度の計算をする際にランダムウォークを用いるメリットとしては、より深いグラフの特徴を反映できる、カスタマイズしやすい、スケールさせて計算しやすいということがあります。
より深いグラフ構造を反映させられることで、精度を上げることができ、推薦できるアイテムの数を増やすことができます。カスタマイズは、例えば、プロフィールに共通な点が多いユーザーのアクションを優先させたい時、ランダムウォークの際の移動する確率を変更することで対応できます。また、Visitの場合季節性が強い(年ごとにイケてる企業は変わる)ので、時間が経つと移動する確率を下げる処理も行なっています。
2018年後半
通知など複数の領域で推薦を実施
推薦精度の高いスコアができることで、同じスコアを使うだけで様々な改善を行うことができます。例えば、Push通知やおすすめメール、アイテムの下部にある似ているアイテムのおすすめ、おすすめのキーワードなどです。
推薦エンジニアでなく、アプリケーションエンジニアでも最適化を行なっていける手順書や基盤を整えることで、さらに成長を加速させました。
推薦基盤のマイクロサービス化
もともとrailsの中で推薦アルゴリズムを構築していましたが、レスポンス速度や開発生産性をあげるために、golangを使った推薦のためのマイクロサービスを構築しました。
マイクロサービスにすることで、開発環境の高速化、テストの高速化、デプロイの高速化、スケールのしやすさなどの恩恵を受けることができます。
マイクロサービス間通信はgRPCを使い、言語の違いを吸収しやすくしています。データ基盤であるBigQueryから必要なデータを取得しますが、リアルタイムに同期したいデータはpubsubを通してデータを受け取っています。
自然言語処理を行い、コンテンツデータを推薦に利用
Visitには募集要項やユーザーのスキルタグなど様々なテキストデータがあります。それらのテキストデータから専門用語を抽出してタグとして認識したり、トピックモデルを利用してデータ化したりしています。
自然言語処理による良いところは、ユーザー入力しているデータを元に推薦しているので、納得感が得られやすいことです。
LightGBMの利用
推薦のパーソナライズは、ユーザーの特徴・アイテムの特徴・クエリの特徴・それぞれの特徴同士の関係性のデータから、目的となるスコアを計算し、最適化します。
現在、ユーザーの特徴・アイテムの特徴・それぞれの関係性のデータからスコアを計算しており、クエリの特徴は使っていません。スタッキングした学習を行なっており、ランダムウォークによるコラボレーションフィルタリングのスコアや自然言語処理によるデータも利用してます。
ランキング学習でもLightGBMを利用しています。アイテムの特徴からスコアを計算し、目的を最大化することも、利用することで従来よりも良い推薦を作ることができます。
機械学習を進める上で、推薦では、ただアイテムを見ただけのデータをネガティブと捉えて良いのか難しいですが、アイテムを見ただけのデータの中から、ダウンサンプリングをして対応しています。
NN(ニューラルネットワーク)の利用
現在は、シンプルなニューラルネットワークを構築しています。LightGBMと同じような特徴量を使い推薦のためのスコアを計算していますが、NNを利用することで、LightGBMの結果を超えることができています。
NNでも、LightGBMと同様にネガティブな評価をどのように行うのかは課題になっており、試行錯誤しながらチューニングを行なっています。
LightGBMと並行して開発をしていますが、LightGBMで効果的な特徴量がNNでどのように効いているのかなど、比較して進められることや、最適化の目標ラインに出来ることは便利でした。
今後
「UX with AI」をキャッチフレーズに、全てのプロジェクトで機械学習を利用したユーザー体験の向上を行おうとしています。また、グラフ・自然言語・NNそれぞれの領域で、これからはより深い専門性を使っていける開発を考えています。
さらに1.5倍以上成長していける余地があるとみています。また、推薦を行っていない領域もたくさんあり、それらを取り組んでいきます。以下の図のように、UI・基盤・推薦それぞれで取り組んでいきたいことがたくさんあります。
推薦は、Netflix, Airbnb, Spotifyのような企業が積極的に技術を公開しており、彼らが公開しているblogやpaperを見て、うまくWantedlyに取り入れることを進めてきました。今後もできるだけ彼らが構築している基盤に近づけていくことが直近の目標になります。
・UIでは、学習しやすいUI、推薦精度が上がることで実現できるUX、効果的な推薦のための特徴量を入力してもらえるonboardingなど進めようとしています。
・グラフでは、ランダムウォークを使ったコラボレーションフィルタリングをさらに最適化を進めるために、GCN(Graph Convolutional Network)を使うことを検討しています。
・自然言語でも、今はトピックモデルとして利用しているだけですが、より単語同士の意味を理解したスコアにしていくことを検討しています。
・NNの最適化も、現在はシンプルなネットワークを作っていますが、より深い最適化されたネットワークにしていくことを検討しています。
最後は宣伝になりますが、このような開発をしていくために、開発チームの中で機械学習エンジニアの割合を増やそうとしています。
プロダクトマネージャー、情報推薦エンジニア、機械学習エンジニア、データエンジニア、バックエンドエンジニアそれぞれ募集していますので、興味あれば気軽に話を聞きに来てください!