コネヒト開発者ブログ

コネヒト開発者ブログ

レコメンドエンジン導入までの取り組みとアーキテクチャについて

こんにちは!MLエンジニアのたかぱい(@takapy)です。

今回は、ママリのアプリ内にレコメンドエンジンを導入したので、導入までの取り組みやアーキテクチャについてご紹介できればと思います。


目次


ママリ内での課題

ママリはサービスとして6年目を迎え、サービスの成長とともにアプリ内の記事数も増えており、それに伴いユーザーが本来欲しい情報にたどり着くことも難しくなってきました。

加えて「子育て層のユーザー」という切り口1つとっても、0才児のママと1才児のママでは悩みや欲しい情報がまったく異なります。

このような背景から、これまで人的に行っていたルールベースでの記事配信に対し課題を感じており、機械学習で解決できないか、ということで取り組み始めました。

アーキテクチャ概要

全体のアーキテクチャは以下のようになっています。

アプリのログはBigQueryに蓄積されているため、そこからデータを取得・構造化した後、推薦記事を計算してDynamoDBに保存しています。

APIはFlaskで構築し、APIのログはAthena経由でRedashから参照できるようにしています。
このRedashはBigQueryのデータも参照できるので、ここでAPIのログとBigQueryの行動ログデータを突き合わせて、各種指標(CTRなど)をモニタリングしています。

f:id:taxa_program:20200330195638p:plain
アーキテクチャ

EDAとアルゴリズムについて

まずはデータの理解を深めるために、簡単なEDAを実施しました。
ママリの記事にはカテゴリ*1がついているのですが、あるユーザー属性別にカテゴリ毎の閲覧数を見てみると、大きな偏りがあることが分かりました。
(例えば、ユーザー属性Aの人は、「子育て・家族カテゴリ」の記事をよく見ているが、ユーザー属性Bの人は「住まいカテゴリ」の記事をよく見ている、など)

f:id:taxa_program:20200330184036p:plain
特定カテゴリにおけるユーザー属性別の記事閲覧数例

この結果を元に、ユーザーを数十種類のクラスタにハードクラスタリングし、「クラスタ単位」と「クラスタ×ユーザー単位」の2種類に分け、推薦記事を計算しました。

「クラスタ単位」とは、以下のようにクラスタ粒度でRatingテーブルを作成して推薦記事を計算するイメージです。こうすることで、新規ユーザーに対してもある程度嗜好性を考慮した記事を推薦してくれることを期待しています。

f:id:taxa_program:20200330184949p:plain
クラスタ単位の推薦記事計算例

「クラスタ×ユーザー単位」とは、ユーザーごとにそれぞれのクラスタ内で推薦記事を計算するイメージです。

f:id:taxa_program:20200330185134p:plain
クラスタ×ユーザー単位の推薦記事計算例

また、今回は「0→1」での導入だったため、レコメンドの精度よりは実装までのスピードを意識して取り組みました。
そのため、推薦アルゴリズムには業界内での成功事例があり、かつ比較的実装コストも低い協調フィルタリングと、Matrix Factorization(MF)を採用しようと決め、この2つのアルゴリズムでオフライン検証に進みました。

オフライン検証の失敗と学び

全ユーザーの直近3週間の行動ログ(アプリの閲覧履歴、ユーザーのアクションを含む)をBigQueryから取得し、構造化しました。
そこからデータを時系列に、古い2週間と直近1週間に分割し、前者を学習データ、後者をテストデータとしてオフライン検証を行いました。

オフライン検証では、推薦したアイテムをクリックするか否かを評価するため、Recallという指標を用いました。Recallとはユーザーが実際に嗜好したアイテムのうち、推薦したアイテムでカバーできたものの割合です。

結果、このオフライン検証では良い数値がでませんでした。

そもそもオフラインでの評価は、我々がおすすめしようとするまいと、ユーザーが嗜好(クリック)したものを予測しているので、それを正確に予測できることにどれだけ意味があるのか、という議論ポイントはあると思っています。
そんな中でオフライン検証ではあまり良い予測ができないケースもある(良い予測ができても、それがそのままオンラインでの精度にならない)ということを学べたのは、今回の収穫の1つであったと思います。

とはいえ、PoCばかり進めていても仕方ないよね、ということから、未知の部分は多いもののプロダクションでどういう動きをするか見てみよう、ということでA/Bテストを実施しました。

A/Bテストについて

冒頭で説明したアーキテクチャは最終的なもので、A/Bテスト時の構成はかなりシンプルにしました。

今回、オフライン検証で良い数値が出た訳でもないため、現状の表示条件(ルールベース)よりCTRが下がる恐れがありました。
そんな中で全体のアーキテクチャを先に設計・構築してしまうと、実装までのスピードが遅くなったり、仮に指標が下がってしまった場合の時間/金銭的コストが大きくなってしまいます。

そのため、下図のような最小限の構成としました。

f:id:taxa_program:20200330191934p:plain
A/Bテスト時のアーキテクチャ

結果的に、A/BテストではCTRが7ポイント向上し、検定を行いそのCTRに有意差が確認できたことから、プロダクションへ本格導入することに決定しました。

レコメンドアルゴリズムについて

今回試した2つのアルゴリズムについて簡単にご紹介します。
(詳細な実装方法などはWeb上に優良な記事がありますので、ここでは概要の説明に留めます)

強調フィルタリング(アイテムベース)

協調フィルタリングは大別して「ユーザーベース」と「アイテムベース」の手法があります。今回実装したのはアイテムベースの協調フィルタリングです。

まず、上記Rating行列から全てのアイテム間の類似度を計算して、類似度行列を生成します。この時の類似度計算方法には「ユークリッド距離」や「コサイン類似度」を使用できます。今回はコサイン類似度を使用しました。

f:id:taxa_program:20200330192217p:plain
類似度行列作成例

次に、推薦アイテムを計算したいユーザーのベクトルと、この類似度行列の積をとります。
下記例では、user Dに対して推薦したいアイテムを計算しています。計算結果からすでに評価しているitem 2を除外すると、item 4の値が一番高いため、user Dにはitem 4を推薦すればよい、ということになります。

f:id:taxa_program:20200330192410p:plain
userDに対する推薦記事計算例

Matrix Factorization

Matrix Factorizationはその名前の通り、Rating行列をuserの特徴量行列(P)とitemの特徴量行列(Q)に分解します。
例えば、m人のユーザーとn個のアイテムを考えたときに、m > k > 0であるk次元に次元削減して変換することを目的とします。
これは、評価値を表すRating行列(R)を、ユーザー要素を表すk × mの行列(P)と、アイテム要素を表すk × nの行列(Q)に近似することです。


R \approx PQ^T

図にすると以下のようなイメージです。

f:id:taxa_program:20200330193600p:plain
行列分解例

そして、分解された2つの行列の積をとると、新しいuser×itemのRating行列が生成されます。このRating行列は密な行列となるので、値の高いアイテムをそのまま推薦すればよいことになります。
例えば、user Aに対してはすでに評価しているitem 1とitem 2を除外すると、item 3を推薦すればよい、ということになります。

f:id:taxa_program:20200330193324p:plain
最終的な行列

最後に

今回は0からレコメンドエンジンを構築するまでの取り組みや、そのアーキテクチャについてお話しました。

私自身、レコメンドエンジンの構築はMovieLensのデータセットなどを使ってやってみた経験しかなかったので、実サービスに対して取り組むことで、実際にユーザーの反応を見ることができたり、安定して配信するための基盤作りに携われたり、とても面白かったです。

今回のレコメンドエンジンはまだまだ課題もあり、これからユーザーの反応を見ながらアップデートしていく予定なので、その時はまた取り組みをご紹介できればと思います。

*1:「子育て・家族」「病院」「住まい」など