糞糞糞ネット弁慶

読んだ論文についてメモを書きます.趣味の話は http://repose.hatenablog.com

Neural Collaborative Filtering (WWW 2017) 読んだ & Chainer で実装した

Neural Collaborative Filtering (pdf)

概要

タスクは user と item について評価しているか (1) していないか (0) の情報 (implicit feedback) から未知の user と item の評価を予測する,商品推薦において非常に古典的なもの.
一般的には協調フィルタリングや行列分解を行なうが,この論文では Neural Collaborative Filtering (NCF) を提案している.

手法

人の user と 個の item について,評価/購入しているかしていないかのデータ が与えられているとする.
NCF ではこの を行列分解と多層パーセプトロンの二つを同時に推定することで学習する.

行列分解

入力である の行列を となるように 次元の行列 に分解する.

多層パーセプトロン

それぞれを one-hot encoding したものを連結して多層パーセプトロンで学習.

NCF

行列分解と多層パーセプトロンとを同時に学習する.わかりやすく各処理での次元数を書く.

  • user と item をそれぞれ one-hot encoding したものを連結した M+N 次元のベクトルを作る
  • この入力をもとに M+N -> d_1 -> d_2 -> ... -> d_i 次元まで落とす i 層のニューラルネットを作る
  • 同時に, user と item を それぞれ K 次元に embedding し,要素ごとの積である K 次元のベクトルを作る.これが行列分解に相当する
  • d_i 次元のニューラルネット由来のベクトルと K 次元の行列分解由来のベクトルを連結し d_i + K 次元のベクトルとする
  • 最後に sigmoid に通して予測値 とする

Chainer による実装

はじめてまともに Chainer を書いた.簡単でいい.
とりあえずは二層で,宣言時にユーザ数,アイテム数,行列分解の次元数,ニューラルネットの層の数を指定する.
学習時には user と item ,それらを one-hot encoding したベクトル user_vec/item_vec ,およびラベルを与える.
movielens で実験したところ,素の行列分解よりは精度が高そうな感じがした. ensemble に近い内容だからではないのという気持ちも少しある.

import numpy as np

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

class NCF(chainer.Chain):
    def __init__(self, n_user, n_item, n_mf_dim, n_dim_1, n_dim_2):
        self.n_user = n_user
        self.n_item = n_item
        self.n_mf_dim = n_mf_dim
        self.n_dim_1 = n_dim_1
        self.n_dim_2 = n_dim_2

        self._layers = {
            'MFQ': L.EmbedID(self.n_user, self.n_mf_dim),
            'MFP': L.EmbedID(self.n_item, self.n_mf_dim),
            'l1': L.Linear(self.n_user + self.n_item, self.n_dim_1),
            'l2': L.Linear(self.n_dim_1, self.n_dim_2),
            'l_out': L.Linear(self.n_dim_2 + self.n_mf_dim, 1)
        }

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

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


    def predict(self, u, i, user_vec, item_vec):
        # train neural net
        input_vec = F.concat((user_vec, item_vec), axis = 1)
        h = F.relu(self.l1(input_vec))
        h = F.relu(self.l2(h))

        # matrix factorization
        mf_p_u = self.MFQ(u)
        mf_q_i = self.MFP(i)

        # concat matrix factorization
        h = F.concat((h, mf_p_u * mf_q_i), axis = 1)
        h = self.l_out(h)
        return F.sigmoid(h)


    def __call__(self, u, i, user_vec, item_vec, y):
        pred = self.predict(u, i, user_vec, item_vec)
        loss = F.sigmoid_cross_entropy(pred, y.reshape(len(y), 1))
        chainer.report({'loss': loss}, self)
        return loss