レコメンドに画像の情報を活用する方法

f:id:vasilyjp:20180927112657j:plain

データサイエンティストの中村です。 ファッションアイテムの画像から抽出した特徴量は検索以外にも利用することができます。 今回はレコメンドにおける画像特徴量の活用について、以下の3トピックを考えてみたいと思います。

  • 画像特徴量を利用したコンテンツベースレコメンド
  • モデルベース協調フィルタリングにおけるコールドスタート問題の軽減
  • 画像特徴量を利用したモデルベース協調フィルタリングの高度化

画像特徴量を利用したコンテンツベースレコメンド

あるアイテム iを好きなユーザーは iとよく似たアイテムも好きであると仮定します。このユーザーへの推薦は、 iと似ているアイテムを推薦すれば良いわけですが、このとき、アイテムの類似度を評価する尺度として画像特徴量が使えます。 アイテム iの画像特徴量を x_iとしたとき、アイテム iとアイテム jの類似度は x_i x_jの間の距離を用いて表現できます。類似度(距離)にはコサイン類似度やユークリッド距離がよく使われます。

 \displaystyle
sim(x_i, x_j) = \frac{x_i^Tx_j}{||x_i||\ ||x_j||}

実際にAutoencoderで抽出した特徴量を使ってアイテムを推薦すると、以下のようになります。

f:id:vasilyjp:20170524144916p:plain

コンテンツベースのレコメンドでは、画像特徴量に基づくアイテム類似度を予め計算しておき、直近の履歴に応じて推薦アイテムを決定します。 類似度計算にはトランザクションデータを必要としない為、コールドスタートに強い手法と言えます。

一方で、結果を見て分かる通り、推薦されるアイテムはどれも似通ってしまいます。同じデザインで価格やブランドが違う服を提案したいときには使えますが、新しい発見を得る機会は少なくなります。

モデルベース協調フィルタリングにおけるコールドスタート問題の軽減

モデルベース協調フィルタリングの例として行列分解(Matrix Factorization)があります。 行列分解のインプットはレーティング行列 Rであるため、レーティングが存在しないアイテムは推薦対象に含めることができません。 この問題は画像特徴量を利用することで軽減することが可能です。

行列分解

行列分解を用いてレーティング行列 R W = (w_1,\ldots, w_{|U|})^T H=(h_1,\ldots,h_{|I|})^T の積に分解できたとします。このときユーザー uとアイテム iの相性は W,H中の対応する要素の内積で表現できます。

 \displaystyle
\hat{r_{ui}} = w_u^Th_i

(バイアス項は省略しています)

 h \in \mathbb{R}^{K}はベクトルで、アイテムの特徴量とみなすことができます。 Kはファクターの数です。

画像特徴量の利用

ファッションECサイトには毎日新しいアイテムが追加されています。レコメンドにもなるべく早い段階で新着アイテムを反映したいという思いがあります。 上の式を参照すると、新着アイテムの特徴量 hさえ計算できれば、ユーザーベクトル wと内積をとることですべてのユーザーとの相性を計算できそうです。 行列分解の計算後に画像特徴量を利用して新着アイテムの特徴量を無理矢理与えてしまおうというのがここで紹介する方法です。

レーティングが存在するアイテムの集合を I、新着アイテムを i' \not\in Iで表記します。画像特徴量はレーティングの有無に関係なくすべてのアイテム I \cup \{ i' \}について計算可能です。新着アイテム i'の画像特徴量 x_{i'}をクエリに近傍探索を実行すれば、新着アイテムの近傍 N(i')を得ることができます。

このとき、新着アイテムの特徴量 h_{i'}を以下のように定義します。

 \displaystyle
h_{i'} = \frac{\sum_{l \in N(i')} sim(x_{i'}, x_l) h_l}{\sum_{l \in N(i')} sim(x_{i'}, x_l)}

新着アイテムの特徴量 h_{i'}は、近傍のアイテムの特徴量 h_lの重み付き線形和とし、重みは新着アイテムとの類似度に依存する形で定義します。

これにより新着アイテムの特徴量が計算できました。実際に計算された特徴量を可視化すると以下のようになります。 左はクエリ画像、中央は画像が似ているアイテム、右は行列分解で計算した特徴量が似ているアイテムです。クエリ画像はレーティングの付いていない新着アイテムですが、行列分解で計算した特徴量を使って比較しても違和感のないアイテムが並んでいます。

f:id:vasilyjp:20170524145020p:plain

今回は重みを近傍法を利用して定義しましたが、この重みは学習によって求めることも可能です。詳しくはGantner2010*1をご覧ください。

画像特徴量を利用したモデルベース協調フィルタリングの高度化

ここでは行列分解のモデル自体を高度化する方法を考えます。 あるユーザーのファッションアイテムに対する好みには、アイテムの見た目も影響を与えていそうです。 この直感を定式化するため、一般的な行列分解の式に画像に依存する項を加えます。

 \displaystyle
\hat{r_{ui}} = w_u^T h_i + v_u^T Ex_i + b^T x_i

ここで、 v_uはユーザー uのアイテム iの見た目に対する重み、 Eはアイテムの特徴量を v_uと同じ次元にマッピングするための行列、 bはアイテム iの画像自体に対するバイアスです。 右辺第1項だけだと通常の行列分解となります。第2項および第3項が画像に関する項で、第2項はユーザーと画像の相互作用、第3項は画像自体の魅力を表現しています。

これをBayesian Personalized Ranking(BPR)という手法で解いたのがHe2015*2です。彼らはこの手法をVisual Bayesian Personalized Ranking(VBPR)と呼んでいます。

実装

VBPRをchainerで実装しました。VBPRのアルゴリズムはシンプルなのでフレームワークを使うまでもありませんが、深層学習フレームワークを利用して実装すれば実績のある最適化アルゴリズムなどの恩恵を受けることができます。

VBPRのモデルの定義は以下のようになります。

import numpy as np

import chainer
from chainer import functions as F
from chainer import links as L
from chainer import Variable

class VBPR(chainer.Chain):
    def __init__(self, n_user, n_item, n_latent, n_visual, d_image, reg=0.0):
        self.n_user = n_user
        self.n_item = n_item
        self.n_latent = n_latent
        self.n_visual = n_visual
        self.d_image = d_image
        self.reg = reg

        self._layers = {
            'latent_u': L.EmbedID(self.n_user, self.n_latent),
            'latent_i': L.EmbedID(self.n_item, self.n_latent),
            'visual_u': L.EmbedID(self.n_user, self.n_visual),
            'visual_i': L.Linear(self.d_image, self.n_visual, nobias=True),
            'bias_v': L.Linear(self.d_image, 1, nobias=True),
            }

        super(VBPR, self).__init__(**self._layers)

        for param in self.params():
            param.data[...] = np.random.uniform(-0.1, 0.1, param.data.shape)


    def __call__(self, u, i, j, xi, xj):
        gamma_u = self.latent_u(u)
        gamma_i = self.latent_i(i)
        gamma_j = self.latent_i(j)

        theta_u = self.visual_u(u)
        theta_i = self.visual_i(xi)
        theta_j = self.visual_i(xj)

        bias_i  = self.bias_v(xi)
        bias_j  = self.bias_v(xj)

        x_uij  = F.sum(gamma_u * (gamma_i - gamma_j), axis=1)
        x_uij += F.sum(theta_u * (theta_i - theta_j), axis=1)
        x_uij += F.reshape(bias_i - bias_j, (u.shape[0], ))

        loss = F.log1p(F.exp(-x_uij))
        if self.reg > 0:
            loss += self.reg * F.sum(gamma_u*gamma_u, axis=1)
            loss += self.reg * F.sum(gamma_i*gamma_i, axis=1)
            loss += self.reg * F.sum(gamma_j*gamma_j, axis=1)
            loss += self.reg * F.sum(theta_u*theta_u, axis=1)
            loss += self.reg * F.sum(theta_i*theta_i, axis=1)
            loss += self.reg * F.sum(theta_j*theta_j, axis=1)
            loss += self.reg * F.sum(bias_i *bias_i , axis=1)
            loss += self.reg * F.sum(bias_j *bias_j , axis=1)
        loss = F.sum(loss) / u.shape[0]

        chainer.report({'loss': loss,}, self)

行列 W,Hの各行はユーザー/アイテムの分散表現とみなせるので、EmbedIDで定義します。画像特徴量を任意の次元数にマッピングする場合はLinearが使えます。d_imageという変数は画像特徴量の次元数です。 forwardはユーザーID、アイテムIDおよび対応する画像の画像特徴量(ベクトル)を引数に取ります。

実験

参考程度に実験を行いました。実験用に加工したIQONのデータセットを使います。ユーザー数は21382、アイテム数は99337、レーティングの数は547880です。レーティングの値は{0,1}のバイナリになっています。BPR、VBPR、VBPRの第2項と第3項(画像に関する項)(ここではVLRと呼ぶことにします)の3手法をRecall@100とnDCG@100で比べた結果、以下のようになりました。

f:id:vasilyjp:20170524145056p:plain f:id:vasilyjp:20170524145111p:plain

残念ながら大勝というわけにはいきませんでした。ただし良い感触がまったくないわけではなく、オリジナルのVBPRを再現してパラメータを丁寧に決めればそれなりに差はつくのではないかと考えています。

まとめ

レコメンドに画像の情報を反映する方法を紹介しました。 とくに2つ目のコールドスタート対策は簡単に拡張できて使い勝手が良いのでおすすめです。IQONデータセットの場合、この手法を適用することで10万以上のアイテムを推薦候補に加えることができます。

最後に

VASILYでは、最新の研究にアンテナを張りながら、同時にユーザーの課題解決を積極的に行うメンバーを募集しています。 興味のある方はこちらからご応募ください。

*1:Gantner, Z., Drumond, L., Freudenthaler ,C., Rendle ,S., Schmidt-Thieme, L.: Learning Attribute-to-Feature Mappings for Cold-Start Recommendations. In: ICDM. (2010)

*2:R. He., J. McAuley.: VBPR: Visual bayesian personalized ranking from implicit feedback. In: CoRR. (2015)

カテゴリー